PE文件结构

dos头:共64字节,前两个字节固定是4D5A,最后4个字节指向PE头。

dos存根: 无用。

PE头:4字节PE标识符,20字节的PE文件头,224字节的PE可选头。

节表:40字节,在PE文件头里已经定义好了节表数目(WORD NumberOfSections;)。

DOS头

为了兼容dos系统而遗留。

MZ头部

这也是真正的dos头,mz头部只是dos头的别名。
(PE内称的dos头往往包括dos存根,实际的dos程序的dos头只是当前这个头)

dos头的最后给出了PE头的位置(e_lfanew)

1
2
3
4
IMAGE_DOS_HEADER = {
e_magic: "4D 5A", // MZ头标识
e_lfanew: "40 00 00 00" // 指向PE标识符的偏移
} // 64字节 0x40

DOS存根

仅用于在exe执行与dos中时给出友好提示信息。(没用)

PE头

真正的windows程序的头部。

1
2
3
4
5
IMAGE_NT_HEADERS = {
'PE标识符', // 4字节 0x4
'文件头IMAGE_FILE_HEADER', // 20字节 0x10
'可选头IMAGE_OPTIONAL_HEADER' // 224字节 0xE0
}

可选头的最后一项是数据目录。
数据目录是一个数组,每一项包括两个字段,共20字节,分别是数据目录项的VitualAddress(实际上是RVA)和size

该头部分为32位和64位两个版本,其定义取决于是否定义了_WIN64宏。

如IMAGE_NT_HEADERS32的定义:

1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS{
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

节表

节表也算作头部的一部分。

需要多少个节表项,就构造多少节表头(IMAGE_SECTION_HEADER)

节表项(节)指 .text .data .idata 这样。

在之前的IMAGE_OPTIONAL_HEADER中已经指明了SizeOfHeader,根据其字段值在所有节表项(整个头部)的后面填充0补足。

节数据(程序体)

指具体的 .text .data .idata的内容,其所占空间大小在节表中定义。

关于PE

当文件对齐(FileAlignment)与节对齐(SegmentAlignment)相同时:

VA - ImageBase = FOA(文件偏移地址) = RVA(相对虚拟地址)

当二者不同时:

首先计算RVA=VA-ImageBase
判断RVA或FOA所在的节
RVA-VOffset == FOA - ROffset

总之就是无论什么情况下,节中的地址相对于节起始的偏移是不变的。

导入表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 导入表描述符
IMAGE_IMPORT_DESCRIPTOR = {
"OriginalFirstThunk", // 指向IMAGE_THUNK_DATA
"TimeDataStamp",
"ForwarderChain",
"Name",
"FirstThunk",
}

IMAGE_THUNK_DATA = {
//保存的是导入函数的序号或者指向导入函数名称的RVA
uinon = {
"DWORD ForwardString", // PBYTE
"DWORD Function",
"DWORD Ordinal",
"DWORD AddressOfData"
}u1;
}

_IMAGE_IMPORT_BY_NAME = {
"WORD Hint",
"BYTE NAME[1]"
}