-
-
PE文件格式(一)
-
发表于: 2023-7-27 16:32 3831
-
引言
PE文件格式是Windows操作系统下的可执行文件的格式,包括.exe文件和.dll文件,通过PE文件格式的学习,可以帮助我们更加熟悉有关Windows系统下的逆向分析和PC端病毒的学习,同时PE文件格式也是HOOK,加壳等知识的基础,在这里分享一下自己的有关PE文件格式学习的收获和如何编写一个PE文件解析器。
PE文件格式
PE文件由DOS头部,PE文件头,块表以及数据段,代码段等区块构成。
由于PE文件在磁盘和在内存当中的对齐值的不同,造成偏移量的不同,我们将其分为相对虚拟地址和文件偏移地址。现对这些名词进行一些解释。
1. 基地址(ImageBase):映射文件的起始地址称为模块句柄,通过该句柄可访问其他数据结构。这个起始地址即为基地址。基地址的值由文件本身决定,默认EXE文件的基地址为400000h,DLL文件基地址为10000000h.但是这样显然会产生冲突,所以后续用基地址重定位解决这个问题。
2. 虚拟地址(VA):载入内存后的地址。
3. 相对虚拟地址(RVA):载入内存后相对基地址的偏移量。RVA=VA-ImageBase。
4. 文件偏移地址:在磁盘当中相对基地址的偏移量。
DOS首部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | typedef struct _IMAGE_DOS_HEADER{ uint16_t signature; / / Magic number: 0x4D5A ( "MZ" in ASCII) uint16_t last_page_size; / / Size of the last page in bytes uint16_t num_pages; / / Total number of pages in the file uint16_t num_relocations; / / Number of relocation entries uint16_t header_size; / / Size of the header in paragraphs uint16_t min_alloc; / / Minimum number of paragraphs to allocate uint16_t max_alloc; / / Maximum number of paragraphs to allocate uint16_t init_ss; / / Initial SS value (relative to the start of the executable) uint16_t init_sp; / / Initial SP value uint16_t checksum; / / Checksum of the executable uint16_t init_ip; / / Initial IP value uint16_t init_cs; / / Initial CS value (relative to the start of the executable) uint16_t reloc_table_offset; / / Relative offset of the relocation table uint16_t overlay_number; / / Overlay number uint16_t reserved[ 4 ]; / / Reserved fields uint16_t oem_id; / / OEM identifier (used by OEM - specific programs) uint16_t oem_info; / / OEM information uint16_t reserved2[ 10 ]; / / Reserved fields uint32_t pe_header_offset; / / Offset of the PE header from the start of the executable } DOSHeader; |
这里我们需要关注的地方有两个。
1. e_magic即signature:位于起始位置,大小为word,作为标识符,值为5A4Dh,ASCII表示为MZ。
2. e_lfanew:偏移量为3ch,大小为double word,用于表示PE文件头的偏移量。
PE文件首部
1 2 3 4 5 6 | typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } |
1 2 3 4 5 6 7 8 9 | typedef struct _IMAGE_FILE_HEADER { WORD Machine; / / 目标机器类型 WORD NumberOfSections; / / 节表中的节的数量 DWORD TimeDateStamp; / / 文件创建时间戳 DWORD PointerToSymbolTable; / / COFF符号表的文件偏移 DWORD NumberOfSymbols; / / COFF符号表中符号的数量 WORD SizeOfOptionalHeader; / / 可选头的大小 WORD Characteristics; / / 文件属性标志 } IMAGE_FILE_HEADER, * PIMAGE_FILE_HEADER; |
FILE_HEADERS当中需要关注的是NumnerOfSections表示节的数目,即区块表的数目。
这其中我们重点关注的是OptionalHeader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | typedef struct _IMAGE_OPTIONAL_HEADER { / / 标识PE文件的格式,可以是 32 位或 64 位 WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; / / 仅在 32 位PE文件中存在 / / ImageBase是PE文件在内存中的基地址 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; ULONGLONG SizeOfStackCommit; ULONGLONG SizeOfHeapReserve; ULONGLONG SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; / / 数据目录表的数组,包含了PE文件中各个数据目录的位置和大小 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER, * PIMAGE_OPTIONAL_HEADER; |
其中,IMAGE_DATA_DIRECTORY是另一个结构体,用于描述PE文件的数据目录。IMAGE_NUMBEROF_DIRECTORY_ENTRIES是一个常量,表示数据目录表的数量。
1 2 3 4 | typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; / / 数据目录在内存中的虚拟地址 DWORD Size; / / 数据目录的大小(字节) } IMAGE_DATA_DIRECTORY, * PIMAGE_DATA_DIRECTORY; |
很多我们需要去分析的参数都可以在这个这个里面去找相应的数据,同时数据目录表当中包含了导入表,导出表和资源表等内容,这是我们需要关注的重点。
区块表
每一个区块都有一个对应的区块表,区块的数目由FILE_HEADERS当中的NumberOfSections决定。
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 区块名称
union {
DWORD PhysicalAddress; // 仅在OBJ文件中有效
DWORD VirtualSize; // 区块在内存中的虚拟大小
} Misc;
DWORD VirtualAddress; // 区块在内存中的虚拟地址
DWORD SizeOfRawData; // 区块在文件中的大小
DWORD PointerToRawData; // 区块在文件中的偏移地址
DWORD PointerToRelocations; // 重定位表的文件偏移地址
DWORD PointerToLinenumbers; // 行号表的文件偏移地址
WORD NumberOfRelocations; // 重定位表中的项数
WORD NumberOfLinenumbers; // 行号表中的项数
DWORD Characteristics; // 区块的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
区块
区块的对齐值:
FileAlignment表示磁盘区块的对齐值
SectionAlignment表示内存当中区块的对齐值
文件偏移地址和虚拟偏移地址:文件偏移地址是在磁盘当中,虚拟偏移地址是在内存当中,由对齐值影响其大小。
导入表
导入表和导出表本质其实是一样的,但是导入表相对而言更加复杂,而且导出表只在.dll文件当中存在,所以在这里我们对导入表进行分析。
1. IMAGE_OPTIONAL_HEADERS当中存储导入表的RVA和SIZE
2. 在对应的RVA即IMPORT Directory Table处是每一个模块对应的一个结构体。
分别为Import Name Table RVA,Name RVA,Import Address Table RVA,Forwarder Chain和Time Date Stamp。
Name RVA可以确定依赖的模块名称
也就是说我们需要关注的其实就是IAT在最后将地址载入之后的结果。