第11章 PE文件格式

11.1 PE的基本概念

PE文件使用的是一个平面地址空间,所有代码和数据都被合并在一起,被分割为不同的区块,各个区按页边界对齐,没有大小限制,在内存中有独立的属性,是否包含代码,可读写等。

PE文件不是作为单一内存映射文件被载入内存的,PE装载器遍历PE文件并决定那一部分被映射。

11.1.1 基地址

当PE文件通过Windows加载器家载入内存后,内存中的版本被称为模块(Module)映射文件的起始地址称为模块句柄(hModule),这个初始内存地址也称为基地址(ImageBase)

11.1.2 虚拟地址

PE文件被系统加载器映射到内存中,每个程序都有自己的虚拟空间,这个虚拟空间的内存地址被称为虚拟地址(Virtual Address VA)

11.1.3 相对虚拟地址

虚拟地址(VA) = 基地址(ImageBase) + 相对虚拟地址(RVA)

11.1.4 文件偏移地址

数据位置相对于文件头的相对位置

11.2 MS-DOS 头部

每个PE文件都是以一个DOS程序开始的,用于DOS下识别出是否为有效的孩子形体,然后紧随MZ header的DOS stub (DOS块)

常见的

“This program cannot be run in MS-DOS mode”

PE文件的第一个字节位于一个传统的MS-DOS头部, 称作IMAGE_DOS_HEADER结构如下

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

e_magic 的值需要设置为 5A4Dh “MZ” 是MS-DOS的创建者之一Mark Zbikowski 名字的缩写。

e_lfanew字段 是真正的PE文件头的相对偏移(RVA),指出PE头的文件偏移位置,占用4字节。

11.3 PE文件头

PE文件头指针

PNTHeader = ImageBase+dosHeader->e_lfanew

typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature; //PE文件标识
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

11.3.1 Signature 字段

在一个有效的PE文件里Signature字段被设置为 0x00004550 为“PE00”

11.3.2 IMAGE_FILE_HEADER 结构

typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine;   //运行平台
  WORD  NumberOfSections; //文件的区块数
  DWORD TimeDateStamp; //文件创建日期和时间
  DWORD PointerToSymbolTable; //指向符号表 (COFF 符号表)
  DWORD NumberOfSymbols; //符号表中符号的个数
  WORD  SizeOfOptionalHeader; //IMAGE_OPTIONAL_HEADER32 结构的大小
  WORD  Characteristics; //文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

1.Machine 可执行文件的CPU类型

2. NumberofSecitons 顾名思义…

3.TimeDateStamp 表示文件的创建时间,需要_ctime函数翻译,时区敏感。

4.PointerToSymbolTable COFF符号表的偏移位置

5.NumberOfSymbols, 如果有COFF符号表,代表其中的符号数目

6.SizeOfOptionalHeader:表示数据的大小,32位PE文件这个域通常是00E0h,64位 PE32+文件,值为00F0h,均为要求的最小值。

7.Characterisitics 文件属性

11.3.3 IMAGE_OPTIONAL_HEADER结构

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic; //标志字
  BYTE                 MajorLinkerVersion; 
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode; //所有含有代码区块的大小
  DWORD                SizeOfInitializedData; //所有含有初始化数据区块的大小
  DWORD                SizeOfUninitializedData; //所有未初始化数据区块的大小
  DWORD                AddressOfEntryPoint; //程序执行入口
  DWORD                BaseOfCode; //代码区块起始点
  DWORD                BaseOfData; //数据区块起始点
  DWORD                ImageBase; //程序默认载入基地址
  DWORD                SectionAlignment; //内存中区块的对齐值
  DWORD                FileAlignment; //文件中的区块的对齐值
  WORD                 MajorOperatingSystemVersion; //操作系统主版本号
  WORD                 MinorOperatingSystemVersion; //操作系统次版本号
  WORD                 MajorImageVersion; //用户自定义主版本号
  WORD                 MinorImageVersion; //用户自定义次版本号
  WORD                 MajorSubsystemVersion; //所需子系统主版本号
  WORD                 MinorSubsystemVersion; //所需子系统次版本号
  DWORD                Win32VersionValue; //保留,通常被置为0
  DWORD                SizeOfImage; //映像载入内存后的总尺寸
  DWORD                SizeOfHeaders; //MS-DOS头部,PE文件头,区块表总大小
  DWORD                CheckSum;//映像校验和
  WORD                 Subsystem;//文件子系统
  WORD                 DllCharacteristics; //DLL特性
  DWORD                SizeOfStackReserve; //初始化时的栈大小
  DWORD                SizeOfStackCommit; //初始化时实际提交栈的大小
  DWORD                SizeOfHeapReserve; //初始化时保留堆的大小
  DWORD                SizeOfHeapCommit; //初始化时实际保留堆的大小
  DWORD                LoaderFlags;//与调试相关,默认值为0
  DWORD                NumberOfRvaAndSizes; //数据目录表的项数
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
typedef struct _IMAGE_OPTIONAL_HEADER64 {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  ULONGLONG            ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  ULONGLONG            SizeOfStackReserve;  //64位 几个数值类型为ULONGLONG
  ULONGLONG            SizeOfStackCommit;
  ULONGLONG            SizeOfHeapReserve;
  ULONGLONG            SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

1.Magic ROM镜像 为 0107h , 普通可执行镜像 010Bh, PE32+ 则是 020Bh

4.SizeOfCode 只入不舍,向上取证对其某一个值的整数倍

7.AddressOfEntryPoint 程序执行入口RVA, DLL中可设置为0

25.SizeOfStackReserve 在EXE文件里线程保留的栈大小,一开始只提交一部分,必要时才提交剩余部分

26.SizeOfStackCommit 在EXE文件里,一开始为派给栈的内存,默认为4KB

27.SizeOfHeapReserve 在EXE文件里,为进程的默认堆保留的内存,默认值时1MB

28.SizeOfHeapCommit 在EXE文件里,委派给堆的内存,默认值是4KB

30.NumberOfRvaAndSizes 数据目录的项数,自NT发布以来一直是16

31.DataDirectory[16] 数据目录表,由数个相同的IMAGE_DATA_DIRECTORY组成,指向输入表输出表,资源块等数据

typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress;
  DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

11.4 区块

11.4.1 区块表

IMAGE_SECTION_HEADER 结构的数组,每个结构包含了它所关联区块对的信息,如长度,属性。

数目由IMAGE_NT_HEADERS.FileHeader.NumberOfSections指出

typedef struct _IMAGE_SECTION_HEADER {
  BYTE  Name[IMAGE_SIZEOF_SHORT_NAME]; //8字节的块名
  union {
    DWORD PhysicalAddress;
    DWORD VirtualSize;
  } Misc;
  DWORD VirtualAddress; //区块的RVA地址
  DWORD SizeOfRawData;  //在文件中对齐后的尺寸
  DWORD PointerToRawData; //在文件中的偏移
  DWORD PointerToRelocations; //在OBJ文件中使用,重定位的偏移
  DWORD PointerToLinenumbers; //行号表的偏移 供调试用
  WORD  NumberOfRelocations; //在OBJ文件中使用,重定位项数目
  WORD  NumberOfLinenumbers; //行号表中行号的数目
  DWORD Characteristics; //区块的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

11.4.2常见区块与区块合并

.text 默认代码区块

.data 默认读写数据区块,全局变量,静态变量 一般放在这里

.rdata 只读数据区块

.idata 包含其他外来dll 函数及数据信息

.edata 输出表

.rsrc 资源

.reloc 可执行文件的基址重定位,一般只是dll需要

区块合并的有点事 节省磁盘和内存空间,每个区块至少占用一个内存页。

11.4.3 区块的对齐值

有两种区块对齐值,一种用于磁盘文件内,另一种用于内存中。PE文件头指出了这两个值,他们可以不同。

磁盘区块对齐值:FileAlignment ,例子:200h对齐值,第一个节在 200h,长度为90h, 400h~600h为第一个节,空余部分由0填充

内存区块对齐值: SectionAlignment

11.4.4 文件偏移与虚拟地址的转换

虚拟地址-块首地址+块在硬盘中首地址 = 硬盘中地址

11.5 输入表

可执行文件使用其他dll文件的代码或数据称为输入

11.5.1 输入函数的调用

隐式链接

过程完全由Windows加载器执行,始终运行LoadLibrary 和GetProcAddress

显式链接

手动调用LoadLibrary 和GetProcAddress

PE文件中有一组数据结构,分别对应于被输入的DLL,每一个这样的结构都给出了被输入的dll名称,并指向一组函数指针,这组函数指针别称为输入地址表(Import Address Table IAT)。

11.5.2 输入表的结构

PE文件头的可选映像头中,数据目录表的第二个成员指向输入表,输入表以一个IMAGE_IMPORT_DESCRIPTOR(IID)数组开始。 每个被PE文件隐式链接的DLL都有一个IID。没有字段指出该结构数组的项数,但它的最后一个单元是NULL,由此可以计算出项数。

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
DWORD    OriginalFirstThunk; //包含指向输入名称表(INT)的RVA
DWORD    TimeDateStamp; 
DWORD     ForwarderChain; //第一个被转向的API索引(DLL中的API引用另一个DLL中的API)
DWORD     Name; //DLL名字的指针,它是一个以“00”结尾的ASCII字符的RVA地址
DWORD      FirstThunk; //包含指向输入地址表(IAT)的RVA
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_THUNK_DATA {
    union {
        uint32_t* Function;             // address of imported function
        uint32_t  Ordinal;              // ordinal value of function
        PIMAGE_IMPORT_BY_NAME AddressOfData;        // RVA of imported name
        DWORD ForwarderStringl              // RVA to forwarder string
    } u1;
} IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;

当IMAGE_THUNK_DATA的最高位为1 时,表示函数以 序号方式输入,这时低31位(64位 位低63位),被看成一个函数序号。

当双字最高位为0 时,表示函数 以字符串类型的函数名方式输入,这时双字的值是一个RVA,指向一个IMAGE_IMPORT_BY_NAME结构 (单字大小)

IMAGE_IMPORT_BY_NAME STRUCT
    Hint  WORD   //本函数在其所主流DLL的输出表中的序号,非必须,一些连接器将其置0
    NAME       // 含有输入函数的函数名,ASCII字符串 以 NULL结尾,可变尺寸域
IMAGE_IMPORT_BY_NAME ENDS

11.5.3 输入地址表

OriginalFirstThunk 指向 INT,该结构不可改写, 加载时PE重载器 搜索OriginalFirstThunk,如果找到,就遍历迭代搜索数组中的每个指针,找出每个IMAGE_IMPORT_BY_NAME结构所指向的函数地址,然后加载器用函数真正的入口地址来代替由FirstThunk指向的IMAGE_THUNK_DATA数组里元素的值,当PE文件装在内存准备执行时,如下图被转换。

程序依赖IAT即可运行。

11

11.7 输出表

11.7.1 输出表的结构

 typedef struct _IMAGE_EXPORT_DIRECTORY {
     DWORD   Characteristics; //无定义,总为0
     DWORD   TimeDateStamp; //输出表创建的时间 (GTM时间)
     WORD    MajorVersion; // 输出表的主版本号 未使用,设置为0
     WORD    MinorVersion;// 输出表的次版本号 未使用,设置为0
     DWORD   Name; //指向一个ASCII字符串的RVA
     DWORD   Base; //包含用于这个PE文件输出表的起始序数值,正常情况为1
     DWORD   NumberOfFunctions; //EAT中条目的数量
     DWORD   NumberOfNames; // 输入函数名称表(ENT)里的条目数量
     DWORD   AddressOfFunctions;     // RVA from base of image
     DWORD   AddressOfNames;         // RVA from base of image
     DWORD   AddressOfNameOrdinals;  // RVA from base of image
 };
 

这面感觉记了也会忘..回头写读PE程序再回来补补…

11.8 基址重定位

当连接器生成PE文件会假设这个文件在执行时被装载到默认的基址地址处,并把code 和 data的相关地址都写入PE文件,如果载入时将默认值作为基地址载入,则不需要重定位。

但是如果将PE文件装载到虚拟内存的另一地址中,连接器登记的地址就为错误的,这时就需要重定位表来调整。

重定位表的常用区块为.reloc

11.8.2 基址重定位表的结构

找到基址重定位表的正确方式是 通过数据目录表的IMAGE_DIRECTORY_ENTRY_BASERELOC 条目查找

基址重定位数据采用类似按页分割的方法组织,是由许多重定位块串接成的,每个块中存放 4KB(100h)的重定位信息,每个重定位数据块的大小必须以DWORD(4字节)对齐。他们以一个IMAGE_BASE_RELOCATION结构开始。

typedef struct _IMAGE_BASE_RELOCATION
{
    DWORD VirtualAddress;  //重定位数据的开始RVA地址
    DWORD SizeOfBlock;     //重定位块的长度
    WORD TypeOffset[1];    //重定位项数组
}IMAGE_BASE_RELOCATION;

重定位即 在重定位数据指针中指向需要修改的数据,剩下的重定位工作由加载程序执行。

11.9 资源

Windows程序的各种界面称为资源 包括 加速键,位图,光标,对话框等

11.9.1 资源结构

资源用类似于磁盘目录结构的方式保存,通常包含3层,第一层目录类似于一个文件系统的根目录,第二层目录中的每一个都对应一个资源类型。

1.资源目录结构

typedef struct _IMAGE_RESOURCE_DIRECTORY {
    DWORD   Characteristics; //理论上是资源的属性标志,通常为0
    DWORD   TimeDateStamp; //资源建立的时间 
    WORD    MajorVersion; //放置资源的版本,通常为0
    WORD    MinorVersion; 
    WORD    NumberOfNamedEntries; //使用名字的资源条目的个数
    WORD    NumberOfIdEntries; //使用ID数字资源条目的个数
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;

NumberOfNamedEntries, NumberOfIdEntries 两者的和为本目录中的目录项总和,即紧随其后的IMAGE_RESOURCE_DIRECTORY_ENTR结构的数量

2.资源目录入口结构

紧跟资源目录结构的就是资源目录入口结构(Resource Dir Entry) 此结构长度为8字节,包含2个字段。

定义如下

typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
   DWORD Name ;   // 目录项的名称字符串指针或ID
   DWORD OffsetToData; // 资源数据偏移地址或子目录偏移地址
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;

Name 字段 当结构用于第一层目录时,定义为资源类型,二层目录,定义的是资源的名称,三层目录,定义的是代码也编号。最高位为0 时,表示字段的值作为ID使用,当最高位为1时,表示字段的地位作为指针使用。这个指针不直接指向字符串,而指向一个IMAGE_RESOURCE_DI_STRING_U结构。

typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
    WORD Length;  //下面字符串的长度
    WCHAR NameString; //Unicode字符串,按字对齐,长度可变
}

Offset 字段 是一个指针,当最高位为1 时,低位数据指向下一层目录块的起始地址,当最高位为0时,指针指向IMAGE_RESOURCE_DATA_ENTRY结构。 在将Name和Offset 作为指针时,从资源区块开始计算偏移量,而非根目录起始位置。

3.资源数据入口

typedef struct IMAGE_SOURCE_DATA_ENTRY {
    DWORD OffsetToData; // 资源数据的RVA
    DWORD Size; //资源数据的长度
    DWORD CodePage; //代码页,一般为0
    DWORD Reserved; //保留字段
}

此处的结构就是真正的资源数据了。

11.10 TLS初始化

使用线程本地储存器可以将数据与执行的特定线程联系起来,当使用__declspec(thread) 声明的TLS变量时,编译器将它们放入一个.tls区块。当应用程序加载到内存中时,系统要寻找可以执行文件中的.tls区块,并动态地分配一个足够大的内存区块,以便存放所有的TLS变量。 系统也将一个指向已分配内存的指针放到TLS数组,这个指针由FS:[2Ch] 指向 (x86架构下)

可执行文件中,TLS数据是由数据目录表中的IMAGE_DIRECTROY_ENTRY_TLS指出的,如果数据非0,这个字段指向一个IMAGE_TLS_DIRECTORY结构

typedef struct _IMAGE_TLS_DIRECTORY64 {
    ULONGLONG   StartAddressOfRawData; 
    ULONGLONG   EndAddressOfRawData;
    ULONGLONG   AddressOfIndex;
    ULONGLONG   AddressOfCallBacks;
    DWORD       SizeOfZeroFill;
    DWORD       Characteristics;
} IMAGE_TLS_DIRECTORY64, *PIMAGE_TLS_DIRECTORY64;

typedef struct _IMAGE_TLS_DIRECTORY32 {
    DWORD   StartAddressOfRawData; //内存起始地址,用于初始化一个新线程的TLS
    DWORD   EndAddressOfRawData; //内存起始地址,用于初始化一个新线程的TLS
    DWORD   AddressOfIndex; //运行库使用这个索引来定位线程局部数据
    DWORD   AddressOfCallBacks; //PIMAGE_TLS_CALLBACK 函数指针数组的地址
    DWORD   SizeOfZeroFill; //后面跟0 的个数
    DWORD   Characteristics; //保留,目前为0
} IMAGE_TLS_DIRECTORY32, *PIMAGE_TLS_DIRECTORY32;

AddressOfCallBacks 是线程建立和退出时的回调函数。

程序运行时,TLS数据初始化和回调函数都在入口点之前,也就是说TLS是程序开始运行的时候,许多病毒或外壳程序会利用这一点执行一些特殊操作,程序退出时TLS回调函数会再执行一次。

11.11 调试目录

数据目录表中的第7个条目(IMAGE_DIRECTORY_ENTRY_DEBUG)指向调试目录,它由一个IMAGE_DEBUG_DIRECTORY结构数组组成。这些结构用于保存储存在文件中变量的类型,尺寸和位置的调试信息。

typedef struct _IMAGE_DEBUG_DIRECTORY {
  DWORD Characteristics; //未使用,设为0
  DWORD TimeDateStamp; //debug 信息的时间/日期戳
  WORD  MajorVersion; //debug 信息的朱版本,未使用
  WORD  MinorVersion; //debug 信息的次版本,未使用
  DWORD Type; //debug 信息的类型
  DWORD SizeOfData; //debug数据的大小
  DWORD AddressOfRawData; //当被映射到内存时debug数据的RVA,为0 表示被映射
  DWORD PointerToRawData; //debug数据的文件偏移
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;

11.12 延迟载入数据

数据目录表中的IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT条目指向延迟载入的数据,这是一个指向ImgDelayDesr结构数组的RVA,每一个被延迟载入的DLL都对英语一个ImgDelayDescr结构,ImgDelayDescr结构的关键在于它包含对应DLL的IAT和INT的地址,这些表在格式上与常规的输入表是相同的,唯一的区别是,他们是由运行库代码而不是由操作系统来写入和读取的,第一次从一个延迟载入的DLL中调用一个API函数时,运行库会先调用LoadLibrary(如果需要),再调用GetProcAddress,得到的地址被曝存在延迟载入的IAT中,这样每次调用这个API都会直接来到这里

ImgDelyDescr中的地址均为虚拟地址,而非RVA

11.13 程序异常数据

一些体系结构,(包括IA-64)不使用基于框架的异常处理,例如x86 就使用 基于表的异常处理。 例如x86 就使用基于表的异常处理。表中包含所有可能受异常展开影响的函数信息,当一个异常发生时,系统通过遍历这个表来定位合适的入口并处理它,异常表是一个IMAGE_RUNTIME_FUNCTION_ENTRY结构数组,数组是由数据目录表中的IMAGE_DIRECTORY_ENTRY_EXCEPTION条目之乡的。

11.14 .NET头部

整体结构与传统PE文件一致,不同的是,.NET环境下的PE文件利用数据目录表中的IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR条目扩充了其结构,该条目原本用于COM,但一直没有被使用。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇