t.exe 共 3072 bytes,下面是 t.exe 映象 PE 文件头的整体结构图:
windows 的 PE 文件头结构包括三大部分:DOS 文件头、NT 文件头以及 Section 表(节表),在 DOS 文件头后面有一小段 DOS 程序,被称为 DOS stub 程序。
DOS stub 程序是运行在 DOS 下面的 16 位程序,目的是指出:当 windows 程序在 dos 下运行时,将显示信息:This program cannot be run in DOS mode.... 然后终止执行。
这段 DOS stub 程序是这样的:
00000040 0E push cs |
信息字符串在位置 0x0e 上,即在:0x00000040 + 0x0e = 0x0000004e,这正好是字符信息“This program cannot be run in DOS mode....$”的地址。
6.1 MS-DOS 文件头
t.exe 映象文件头最开始部分是 MS-DOS 文件头部分,这个文件头结构定义在 WinNT.h 文件里,在我的 windows 7 系统 visual studio 2010 下,这个 winnt.h 在目录:C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include 下,在 WinNT.h 文件里定义了 IMAGE_DOS_HEADER 结构,它的定义如下:
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header |
这个 IMAGE_DOS_HEADER 结构共 64 bytes,从映象的 0x00000000 - 0x0000003F,参见上图标注的“DOS 文件头”部分,将这部分按照 IMAGE_DOS_HEADER 结构分解为:
00000000 4D 5A // e_magic |
其中最重要的是: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 在映象中的位置, 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 |
在本例 t.exe 映象里 e_lfanew 为 0x000000C0,它说明 IMAGE_NT_HEADERS 结构位于映象的 0x000000C0 处,此时尚不能断定这个 PE 是 32 位还是 64 位结构。
6.2 IMAGE_NT_HEADER 结构
在 IMAGE_NT_HEADERS 结构的定义里得出,它包含了一个签名和两个结构体,这个签名是 0x00004550 表示 PE 文件:
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00 |
6.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
属性
|
IMAGE_FILE_HEADER 结构中比较重要的域是:Machine 和 SizeOfOptionalHeader, Machine 可以用来判断目标平台,比如:值为 0x8664 是代表 AMD64(即:x64 平台)它也适合 Intel64 平台。SizeOfOptionalHeader 指出 IMAGE_OPTIONAL_HEADER 结构的大小。
在 WinNT.h 文件里定义了一系列的 Machine 值,这里举列一些,详细的参见 WinNT.h 文件:
#define IMAGE_FILE_MACHINE_UNKNOWN 0 |
也为 Characteristics 定义了一系列的常量,这些定义的常量值,代表映象是什么类型的文件:
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file. |
下面看看 t.exe 里的 IMAGE_FILE_HEADER 具体的值,如下图所示:
t.exe 映象的目标平台是 x64 平台,它指出了接下来的 IMAGE_OPTIONAL_HEADER 结构大小为:0x00F0 bytes
注意:虽然在 Machine 里指出目标平台,但是对于判断映象是 32 位还是 64 位的 PE 文件,Microsoft 官方认可以的方法是:通过 IMAGE_OPTIONAL_HEADER 结构里的 Magic 域
6.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 位。
6.3 观察 t.exe 映象的实际 IMAGE_NT_HEADER 结构
在我们实例 t.exe 映象中,IMAGE_NT_HEADER 的偏移量是 0x000000C0,IMAGE_NT_HEADER 结构在 32 位下是 244 bytes,在 64 位下是 264 bytes
下面我们来看一看 t.exe 映象中的 IMAGE_NT_HEADER 结构,从 0x0000000C0 - 0x000001C7(共 264 bytes)
IMAGE_NT_HEADER 结构的签名 Signature 是 0x00004550 表示 PE 文件头。
6.3.1 t.exe 的 IMAGE_FILE_HEADER 结构
蓝色部分是 IMAGE_FILE_HEADER 结构,IMAGE_FILE_HEADER 结构紧接着 PE 签名之后,共 20 bytes,其中的 Machine 是 0x8664,由上述定义所得,它代表的机器类型是 AMD64处理器
000000C4 64 86 // Machine = AMD64 |
它的文件属性是 0x0022,也就是:Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE,说明它是一个可执行的映象,可以在 >2G 地址上,并且指明了接下来的 IMAGE_OPTIONAL_HEADER 结构是 0xf0 bytes(240 个字节)。
6.3.2 t.exe 的 IMAGE_OPTIONAL_HEADER 结构
IMAGE_FILE_HEADER 结构接下来就是 IMAGE_OPTIONAL_HEADER 结构,前面的 SizeOfOptionHeader 域已经指出 IMAGE_OPTIONAL_HEADER 将会是 0xf0 个字节。
000000D8 0B 02 // Magic = 0x020b |
t.exe 的 IMAGE_OPTIONAL_HEADER 结构从 0xd8 ~ 0x1c7 共 0xF0 bytes,这个值在 IMAGE_FILE_HEADER 结构的 SizeOfOptionalHeader 域里已经给出。
在上面的可以看出 t.exe 映象是 64 位是 PE+ 文件结构,它的 ImageBase 是 0x00000001_40000000,t.exe 映象的入口点在 ImageBase + AddressOfEntryPoint = 0x00000001_40001000,这个信息可以从上面分析的 .text 节中所描述的信息中验证,IMAGE_OPTIONAL_HEADER 结构的 AddressOfEntryPoint 域的值是 RVA(Relative Virtual Address)值。
上面的 SectionAlinment 域值为 0x1000 是表示映象被加载到 virtual address 以是 0x1000(4K byte)为单位的倍数,也就是加载在 virtual address 的 4K 边界上,例如:t.exe 映象的 .text 节被加载到以 ImageBase(virtual address 为 0x00000001_40000000)为基址的第 1 个 4K 边界上(即:0x00000001_40001000 处),.rdata 节加载到第 2 个 4K 边界上(即:0x00000001_40002000 处)。
FileAlinment 域的值为 0x200 表示执行映象从 0x200 为边界开始加载到 virtual address 上。例如,t.exe 映象中 code 位于文件映象的 0x400 处(0x200 边界上),因此,t.exe 文件映象 code 从 0x400 处开始加载到 virtual address。
因此:.text 节在文件映象中位于 0x400 开始,在 virtual address 空间中是位于 0x00000001_40001000 开始,它说明映象的 0x400 处被加载到 virtual address 000000001_40001000 位置上。
6.3.3 t.exe 的 IMAGE_DATA_DIRECTORY 表格
上面所示:从 0x00000148 到 0x000001C7 是被称之为 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
|
那么,下面我们来看一看 t.exe 映象中使用了哪些表?
00000148 00 00 00 00 // IMAGE_DATA_DIRECTORY[0] |
我们的示例程序 t.exe 仅仅使用了 3 个 driectory 表格:import table、relocation table 以及 import address table
import table 的 RVA 是 0x00002010,size 是 0x28,relocation table 的 RVA 是 0x4000,size 是 0x0c, import address table 的 RVA 是 0x2000,size 是 0x10
t.exe 的 ImageBase 是 0x00000001_40000000,那么 import table 则在 0x00000001_40002010,relocation table 则在 0x00000001_40004000,import address table 则在 0x00000001_40002000。
6.3.3.1 t.exe 所使用的 import table
上面所述,t.exe 的 import talbe 在 0x00000001_40002010(virtual address),这个 import table 位于 .rdata 节中,从上面的 .rdata 节介绍中看出,.rdata 节位于文件映象中的 0x600 ~ 0x7ff 中共 512 bytes。
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 |
上面是一张关系图表,上面所示,在映象和内存中,存在 4 张表格:
● import table(IMAGE_IMPORT_DESCRIPTOR)
● Thunk table pointer-table(IMAGE_THUNK_DATA)
● IAT(Import Address Table)
● 导入函数的 Thunk Table(IMAGE_IMPORT_BY_NAME)。
下面看看实际中 t.exe 映象中的这几个表格是什么:
RAW DATA #2 Section contains the following imports: USER32.dll 212 MessageBoxA |
上面已经显示出了全部信息,在 IMAGE_DATA_DIRECTORY 结构里已给出了 Import table 的 RVA 是 0x00002010,即:t.exe 的 import table 在 virtual address 0x00000001_40002010 里,那么现在我们来看一看 import table 里的数据:
0000000140002010: 38 20 00 00 // OriginalFirstThunk(Thunk table pointer) |
因此 Thunk table pointer 放在 00000000140002038 里,导入的 DLL 名字放在 0000000140002056 里,Import address table 放在 0x0000000140002000 里。
6.4 t.exe 的节表
IMAGE_NT_HEADER 结构后面紧接着就是 section table(节表)结构, 从 0x1c8 ~ 0x267 共 160 bytes。
这个节表结构在 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 域指出。在前面所述的 t.exe 映象里 NumberOfSections 值是 4 那么表示将有 4 个 sections 存在于映象中。从前的面 dumpbin 工具输出可以得出。
在 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. |
下面以 .text 节为例,看看 t.exe 的 .text 是什么。
.text 节的 Name 是 ".text",VirtualSize 是 0x42 bytes,.text 节在 RVA 为 0x00001000 的位置上,section 的分配单位为 0x1000(4K bytes),第 1 个 section 一般都分配在 0x1000 位置上,SizeOfRawData 为 0x200,这是映像文件分配单位。PointerToRawData 为 0x400,说明 .text 节在映像文件的 0x400 处。Characteristics 是 0x60000020,说明 .text 是 executable/readable/code 属性。