PE文件结构概述
PE文件的整体结构
PE文件所有结构体的定义都在winnt.h文件里,具体详见官方文档

IMAGE_DOS_HEADER(64)
IMAGE_FILE_HEADER(20)
IMAGE_OPTIONAL_HEADER32(224)
IMAGE_SECTION_HEADER(40)
DOS部分
IMAGE_DOS_HEADER结构占64字节,DOS Stub大小是不确定的,IMAGE_DOS_HEADER结构的最后一个成员e_lfanew是一个LONG类型的偏移,它标识了PE头从哪开始。MZ头+偏移+PE头标识就是PE指纹。IMAGE_DOS_HEADER和PE头中间的部分就是DOS Stub。除了首尾两个成员外其他的成员在32位和64位程序中不使用。
DOS Stub由链接器生成,在Windows系统中没有作用,一般输入内容是This program cannot be run in DOS mode

PE头
32位
1 | typedef struct _IMAGE_NT_HEADERS { |
64位
1 | typedef struct _IMAGE_NT_HEADERS64 { |
PE头由三部分组成:PE标识(占4字节)、标准PE头(IMAGE_FILE_HEADER占20字节)、拓展PE头(IMAGE_OPTIONAL_HEADER32大小为0xE0,224字节。64位中为0xF0,240字节),拓展PE头的大小可以在IMAGE_FILE_HEADER结构(标准PE头)中修改。
标准PE头
1 | typedef struct _IMAGE_FILE_HEADER { |
IMAGE_FILE_HEADER.Characteristics
| 数据位 | Value | 为1时含义 |
|---|---|---|
| 0 | IMAGE_FILE_RELOCS_STRIPPED 0x0001 |
文件中不存在重定位信息 |
| 1 | IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 |
文件是可执行的 |
| 2 | IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 |
不存在行信息 |
| 3 | IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 |
不存在符号信息 |
| 4 | IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 |
调整工作集 |
| 5 | IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 |
应用程序可以处理大于2GB的地址 |
| 6 | 此标志位保留 | |
| 7 | IMAGE_FILE_BYTES_REVERSED_LO 0x0080 |
The bytes of the word are reversed. This flag is obsolete. |
| 8 | IMAGE_FILE_32BIT_MACHINE 0x0100 |
只在32位平台上运行 |
| 9 | IMAGE_FILE_DEBUG_STRIPPED 0x0200 |
不包含调试信息 |
| 10 | IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 |
不能从移动盘运行 |
| 11 | IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 |
不能从网络运行 |
| 12 | IMAGE_FILE_SYSTEM 0x1000 |
系统文件(如驱动程序),不能直接运行 |
| 13 | IMAGE_FILE_DLL 0x2000 |
The image is a DLL file. While it is an executable file, it cannot be run directly. |
| 14 | IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 |
The file should be run only on a uniprocessor computer. |
| 15 | IMAGE_FILE_BYTES_REVERSED_HI 0x8000 |
The bytes of the word are reversed. This flag is obsolete |
拓展PE头
1 | typedef struct _IMAGE_OPTIONAL_HEADER { |
DllCharacteristics
| 数据位 | value | 为1时含义 |
|---|---|---|
| 0x0001 | 保留,必须为0 | 保留,必须为0 |
| 0x0002 | 保留,必须为0 | 保留,必须为0 |
| 0x0004 | 保留,必须为0 | 保留,必须为0 |
| 0x0008 | 保留,必须为0 | 保留,必须为0 |
| 0x0040 | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040 |
DLL可以在加载时被重定位(随机基址) |
| 0x0080 | IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 0x0080 |
强制代码实施完整性验证 |
| 0x0100 | IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100 |
该映像兼容DEP |
| 0x0200 | IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200 |
可以隔离,但并不隔离此映像 |
| 0x0400 | IMAGE_DLLCHARACTERISTICS_NO_SEH 0x0400 |
映像不使用SEH |
| 0x0800 | IMAGE_DLLCHARACTERISTICS_NO_BIND 0x0800 |
不绑定映像 |
| 0x1000 | 0x1000 | Reserved. 必须为0 |
| 0x2000 | IMAGE_DLLCHARACTERISTICS_WDM_DRIVER 0x2000 |
该映像为一个WDM driver |
| 0x4000 | 0x4000 | Reserved. 必须为0 |
| 0x8000 | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0x8000 |
可用于终端服务器 |
节表
节表是一个IMAGE_SECTION_HEADER结构类型的数组,每个成员占40个字节,IMAGE_FILE_HEADER.NumberOfSections标识了其数量
定位节表需要先确定拓展PE头的大小,IMAGE_FILE_HEADER.SizeOfOptionalHeader标识了拓展PE头的大小。
注意,Name成员并不要求遵守以
\0结尾,最安全的做法是截取其中8个字节处理,否则会越界。
1 | typedef struct _IMAGE_SECTION_HEADER { |
Misc之所以使用匿名联合主要是为了可读性。
Characteristics常用特征值:
| 标志位 | 含义 |
|---|---|
| IMAGE_SCN_CNT_CODE 0x00000020 |
The section contains executable code. 包含代码,常与 0x10000000一起设置。 |
| IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 |
The section contains initialized data. 该区块包含以初始化的数据。 |
| IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 |
The section contains uninitialized data. 该区块包含未初始化的数据。 |
| IMAGE_SCN_MEM_DISCARDABLE 0x02000000 |
The section can be discarded as needed. 该区块可被丢弃,因为当它一旦被装入后,进程就不在需要它了,典型的如重定位区块。 |
| IMAGE_SCN_MEM_SHARED 0x10000000 |
The section can be shared in memory. 该区块为共享区块。 |
| IMAGE_SCN_MEM_EXECUTE 0x20000000 |
The section can be executed as code. 该区块可以执行。通常当0x00000020被设置时候,该标志也被设置。 |
| IMAGE_SCN_MEM_READ 0x40000000 |
The section can be read. 该区块可读,可执行文件中的区块总是设置该标志。 |
| IMAGE_SCN_MEM_WRITE 0x80000000 |
The section can be written to. 该区块可写。 |
PE文件的两种状态
在拓展PE头中有一个成员IMAGE_OPTIONAL_HEADER32.SizeOfHeaders标识了所有头+节表的大小,大小按照拓展PE头中的另一成员FileAlignment对齐,节数据在磁盘上也按照FileAlignment对齐

如上图,在拓展PE结构往后偏移36个字节找到FileAlignment,再往后偏移20个字节找到SizeOfHeaders。SizeOfHeaders按照FileAlignment对齐,大小一定是其整数倍。此处到0x400为止,往后就是节数据了
IMAGE_OPTIONAL_HEADER32.SectionAlignment标识了PE文件在内存中时的对齐方式,在FileAlignment前面4个字节就是SectionAlignment
在内存中所有头+节表(SizeOfHeaders)也按照文件对齐,但会一直延伸到SectionAlignment的位置,节数据实际开始的位置一定是SectionAlignment的整数倍。整个PE镜像内存也按照SectionAlignment对齐

