目录

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
2
3
4
5
6
7
8
typedef struct _IMAGE_NT_HEADERS {
//标识
DWORD Signature;
//标准PE头
IMAGE_FILE_HEADER FileHeader;
//拓展PE头
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

64位

1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct _IMAGE_FILE_HEADER {
//可以运行的CPU型号,0表示任意,intel386以及后续:0x14C x64:0x8664
WORD Machine;
//节的数量
WORD NumberOfSections;
//编译器填写的时间戳,与文件属性里面(创建时间,修改时间)无关
DWORD TimeDateStamp;
//调试相关
DWORD PointerToSymbolTable;
//调试相关
DWORD NumberOfSymbols;
//拓展PE头大小,32位:0xE0,64位:0xF0
WORD SizeOfOptionalHeader;
//文件属性
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_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
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//

//***PE32:0x10B 64:0x20B
WORD Magic;
//链接器版本号
BYTE MajorLinkerVersion;
//链接器版本号
BYTE MinorLinkerVersion;
//所有代码节的总和,文件对齐后的大小,编译器填的,没用
DWORD SizeOfCode;
//包含所有已经初始化数据的节的总大小,文件对齐后的大小,编译器填的,没用
DWORD SizeOfInitializedData;
//包含未初始化数据的节的总大小,文件对气候的大小,编译器填的,没用
DWORD SizeOfUninitializedData;
//***程序入口,对于ImageBase的偏移,不是绝对地址。
DWORD AddressOfEntryPoint;
//代码开始的基址,编译器填的,没用
DWORD BaseOfCode;
//数据开始的基址,编译器填的,没用
DWORD BaseOfData;

//
// NT additional fields.
//

//***内存镜像基址
DWORD ImageBase;
//***内存对齐(所有头+节表和节数据)
DWORD SectionAlignment;
//***文件对齐(所有头+节表和节数据)
DWORD FileAlignment;
//操作系统主版本号
WORD MajorOperatingSystemVersion;
//操作系统次版本号
WORD MinorOperatingSystemVersion;
//PE文件自身主版本号
WORD MajorImageVersion;
//PE文件自身次版本号
WORD MinorImageVersion;
//运行所需子系统主版本号
WORD MajorSubsystemVersion;
//运行所需子系统次版本号
WORD MinorSubsystemVersion;
//子系统笨笨的值,必须为0
DWORD Win32VersionValue;
//***内存中整个PE文件的映射的尺寸,可比实际的值大,按照SectionAlignment对齐
DWORD SizeOfImage;
//***所有头+节表按照文件对齐后的大小,否则加载会出错
DWORD SizeOfHeaders;

//***校验和,一些系统文件有要求,用来判断文件是否被修改
DWORD CheckSum;
//子系统,驱动程序(1)图形界面(2)控制台、DLL(3)其他详见文档。
WORD Subsystem;
//文件特性,不是针对DLL文件的
WORD DllCharacteristics;
//初始化时保留的栈大小
DWORD SizeOfStackReserve;
//初始化时实际提交的栈大小
DWORD SizeOfStackCommit;
//初始化时保留的堆大小
DWORD SizeOfHeapReserve;
//初始化时实际提交的堆大小
DWORD SizeOfHeapCommit;
//调试相关
DWORD LoaderFlags;
//目录项数目(最后一个成员数组的数量)
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct _IMAGE_SECTION_HEADER {
//ASCII字符串,可自定义,只截取8个
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
//节数据在对齐前的真实大小,该值可以不准确(可修改)
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
//节区在内存中的偏移地址,加上ImageBase才是在内存中的真正地址
DWORD VirtualAddress;
//节在文件中对其后的尺寸
DWORD SizeOfRawData;
//节区在文件中的偏移
DWORD PointerToRawData;
//调试相关
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
//节的属性
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_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对齐