Windows 的 PE 文件头结构包括三大部分:DOS 文件头、NT 文件头以及 Section 表(节表),在 DOS 文件头后面有一小段 DOS 程序,被称为 DOS stub 程序。
DOS stub 程序是运行在 DOS 下面的 16 位程序,目的是指出:当 windows 程序在 dos 下运行时,将显示信息:This program cannot be run in DOS mode.... 然后终止执行。
1.MS-DOS 文件头
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header |
其中最重要的是:e_magic 和 e_lfanew 域(上面红色部分标注),e_magic 是 MS-DOS 文件头的签名,它的值是:0x5A4D,这个签名在 WinNT.h 中定义为:
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ |
它的值代表字母 MZ,表示 MS-DOS 文件头。 e_lfanew 是一个 offset 偏移量,指出 IMAGE_NT_HEADER 在映象中的位置。
2.IMAGE_NT_HEADER 结构
IMAGE_NT_HEADER 是 PE 文件的核心部分, IMAGE_NT_HEADER 在 WinNT.h 中定义为两个版本,分别是: IMAGE_NT_HEADERS64 和 IMAGE_NT_HEADERS32,它们的定义如下: typedef struct _IMAGE_NT_HEADERS64 { typedef struct _IMAGE_NT_HEADERS { |
当使用在 win64 下时,IMAGE_NT_HEADER 使用的是 64 位版本 IMAGE_NT_HEADERS64,当使用在 win32 下时,IMAGE_NT_HEADER 使用的是 IMAGE_NT_HEADERS32
#ifdef _WIN64 |
在 IMAGE_NT_HEADERS 结构的定义里得出,它包含了一个签名和两个结构体,这个签名是 0x00004550 表示 PE 文件:
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00 |
2.1 IMAGE_FILE_HEADER 结构
在 IMAGE_NT_HEADER 的中 IMAGE_FILE_HEADER 定义如下:
typedef struct _IMAGE_FILE_HEADER { |
这个结构共 20 bytes,下面是这些域的描述:
域 | size | 值 | 描述 |
Machine | WORD | IMAGE_FILE_MACHINE_xxx | 表示目标平台 processor 类型,例:IMAGE_FILE_MACHINE_I386 |
NumberOfSection | WORD | --- | 表示映象中有多少个 section |
TimeDataStamp | DWORD | 从1970年1月1日0:00 以来的总秒数 | 表示文件创建的时间 |
PointerToSymbolTable | DWORD | COFF 符号表偏移量 | 在 PE 中很少见,总是为 0 |
NumberOfSymbols | DWORD | COFF 符号表的个数 | 如果存在的话,表示符号表的个数 |
SizeOfOptionHeader | WORD | IMAGE_OPTIONAL_HEADER 结构大小 | 该域表示 IMAGE_NT_HEADER 中的 IMAGE_OPTIONAL_HEADER 结构的大小 |
Characteristics | WORD | IMAGE_FILE_xxx | 表示文件属性,例如:IMAGE_FILE_DLL 属性 |
在 WinNT.h 文件里定义了一系列的 Machine 值,这里举列一些,详细的参见 WinNT.h 文件:IMAGE_FILE_HEADER 结构中比较重要的域是:Machine 和 SizeOfOptionalHeader, Machine 可以用来判断目标平台,比如:值为 0x8664 是代表 AMD64(即:x64 平台)它也适合 Intel64 平台。SizeOfOptionalHeader 指出 IMAGE_OPTIONAL_HEADER 结构的大小。
#define IMAGE_FILE_MACHINE_UNKNOWN 0 |
也为 Characteristics 定义了一系列的常量,这些定义的常量值,代表映象是什么类型的文件:
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file. |
虽然在 Machine 里指出目标平台,但是对于判断映象是 32 位还是 64 位的 PE 文件,Microsoft 官方认可以的方法是:通过 IMAGE_OPTIONAL_HEADER 结构里的 Magic 域。
2.2 IMAGE_OPTIONAL_HEADER 结构
与 IMAGE_NT_HEADER 一样,IMAGE_OPTIONAL_HEADER 也有 32 位版本和 64 位版本,因此,相应版本的 IMAGE_NT_HEADER 对应相应版本的 IMAGE_OPTIONAL_HEADER。虽然这个结构被称为 IMAGE_OPTIONAL_HEADER(可选),但是它却是必须存在于 IMAGE_NT_HEADER 结构中。
下面是 64 位版本的 IMAGE_OPTIONAL_HEADER 结构定义, 32 位版本的参见 WinNT.h 中的定义
typedef struct _IMAGE_OPTIONAL_HEADER64 { |
IMAGE_OPTIONAL_HEADER 结构的定义稍长一些,下面是自来 Matt Pietrek 所写的文章,名为《An In-Depth Look into the Win32 Portable Executable File Format》中对IMAGE_OPTIONAL_HEADER 结构的描述,地址在:http://msdn.microsoft.com/en-us/magazine/bb985997.aspx 其中的 Figure 5 IMAGE_OPTIONAL_HEADER 一节里对 IMAGE_OPTIONAL_HEADER 结构有详细的描述。这里就不再描述了。 :)
关键的一点: 在 IMAGE_OPTIONAL_HEADER 里,第 1 个域 magic 用来识别文件头是 32 位还是 64 位 |
这个 magic 的值在 WinNT.h 的定义如下:
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b |
当 magic = 0x10b 时,映象是 32 位,magic = 0x20b 时,映象是 64 位。
2.3 IMAGE_DATA_DIRECTORY 表格
IMAGE_DATA_DIRECTORY 结构在 WinNT.h 中定义如下:
typedef struct _IMAGE_DATA_DIRECTORY { |
这个结构十分重要,它用来描述 windows 执行映象中所使用的各种表格的位置和大小。VirtualAddress 域是一个 RVA(Relative Virtual Address)值,更明白一点就是:它是一个偏移量(基于 PE 文件头),Size 域表示这个表格有多大。
这个数组有 16 个元素,也就是表示,在执行映象中最多可以使用 16 个表格。在 WinNT.h 里定义为:
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 |
由于这个 IMAGE_DATA_DIRECTORY 表格用来描述在映象中所使用到的表格(最多 16 个表格)
实际上这 16 个表格是固定的,对于这些表格 Microsoft 都作了统一的规定,在 WinNT.h 里都作了定义:
// Directory Entries #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory |
从定义上得出,第 0 项是 export table(导出表),第 1 项是 import table(导入表)等等,在 Microsoft 的 MSDN 网站里有一个知识点介绍: http://msdn.microsoft.com/en-us/library/ms680305(VS.85).aspx
下面,我将这些表格归纳如下:
表项
|
表格
|
0
|
export table
|
1
|
import table
|
2
|
resource table
|
3
|
exception table
|
4
|
certificate table
|
5
|
base relocation table
|
6
|
debug
|
7
|
architecute
|
8
|
global pointer
|
9
|
TLS table
|
10
|
load configuration table
|
11
|
bound import
|
12
|
import address table
|
13
|
delay import descriptor
|
14
|
CLR runtime header
|
15
|
reserved, must bo zero
|
2.3.1 import table
import table 在 WinNT.h 中定义为一个 IMAGE_IMPORT_DESCRIPTOR 结构,如下:
typedef struct _IMAGE_IMPORT_DESCRIPTOR { DWORD ForwarderChain; // -1 if no forwarders |
IMAGE_IMPORT_DESCRIPTOR 结构 5 个域,共 20 bytes,OriginalFirstThunk 域指向一个 IMAGE_THUNK_DATA 结构,IMAGE_THUNK_DATA 实际上只有一个域AddressOfData,AddressOfData 指向一个 IMAGE_IMPORT_BY_NANE 结构,它们在 WinNT.h 中的定义为:
// typedef struct _IMAGE_IMPORT_BY_NAME { #include "pshpack8.h" // Use align 8 for the 64-bit IAT. typedef struct _IMAGE_THUNK_DATA64 { |
IMAGE_IMPORT_BY_NAME 是最终的 import table 结构,是原始的 Thunk 表格,这个 Thunk 表格是一个包含所有导入 function 的列表,这个 Thunk table 包括了 Hint, function name 和 DLL name。 Hint 代表每个函数的标识,是一个 16 位的数值,Hint 下面接着是 import 的函数名,最后是所导入的 DLL name。
IMAGE_IMPORT_DESCRIPTOR 里的 FirstThunk 指向出 Import Address Table (IAT)表格,这个 IAT 大小为 16 bytes ,前 4 bytes 是一个 RVA 指向上面所说的 Thunk table。
import table +----------------------+ IMAGE_THUNK_DATA |
● import table(IMAGE_IMPORT_DESCRIPTOR)
上面是一张关系图表,上面所示,在映象和内存中,存在 4 张表格:
● Thunk table pointer-table(IMAGE_THUNK_DATA)
● IAT(Import Address Table)
● 导入函数的 Thunk Table(IMAGE_IMPORT_BY_NAME)。
3 section table(节表)结构
IMAGE_NT_HEADER 结构后面紧接着就是 section table(节表)结构。
这个节表结构在 WinNT.h 中定义为
// #define IMAGE_SIZEOF_SHORT_NAME 8 typedef struct _IMAGE_SECTION_HEADER { #define IMAGE_SIZEOF_SECTION_HEADER 40 |
映象中包括有多少个节表结构,由 IMAGE_NT_HEADER 结构中的 IMAGE_FILE_HEADER 结构中的 NumberOfSections 域指出。
在 IMAGE_SECTION_HEADER 结构的第 1 个域 Name,用来标识 section table 的名字。它的长度固定为 8 bytes(前面定义的宏),这将意味着,不存在超过 8 bytes 的节表名。接下来使用 VirtualSize 来用表示 section talbe 大小。VirtualAddress 表示 section table 的 RVA。
域 | size | 描述 |
Name | 8 bytes | Section 表名字 |
VirtualSize | DWORD | Section 表的大小 |
VirtualAddress | DWORD | Section 表的 RVA,即:section 表的位置 |
SizeOfRawData | DWORD | section 表占用映像的大小,这个 size 是以 0x200 为单位的 |
PointerToRawData | DWORD | section 表在映像中的物理位置,即是:file 位置,而非 virtual 位置 |
PointerToRelocation | DOWRD | |
PointerToLinenumber | DWORD | |
NumberOfRelocation | WORD | |
NumberOfLineumbers | WORD | |
Characteristics | DWORD | section 的属性 flags,可用于 '|' 多个属性值 |
所有的 Characteristics 都在 WinNT.h 中有定义,下面是一些常用的 flags:
#define IMAGE_SCN_CNT_CODE 0x00000020 // Section contains code. |