第38章 PE32+

本文最后更新于:2022年5月27日 下午

第38章 PE32+

PE32+是64位Windows OS使用的可执行文件格式,本章将学习有关PE32+的知识。

64位Windows OS中进程的虚拟内存为16TB,其中低位的8TB分给用户模式,高位的8TB分给内核模式。为了适应改变后的虚拟内存,原PE文件格式(PE32)做了如上修改。

38.1 PE32+ (PE+、PE64)

64位本地模式中运行的PE文件格式被称为PE32+(或PE+、PE64)。为了保持向下兼容性,PE32+在原32位PE文件(PE32)的基础上扩展而来。所以如果你已经熟悉了原PE文件格式,那么就很容易熟悉PE32+这种新的文件格式。下面介绍PE32+文件格式时将主要讲解与原PE文件格式的不同。

38.1.1 IMAGE NT HEADERS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

#ifdef _WIN64
typedef IMAGE_OPTIONAL_HEADER64 IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER64 PIMAGE_OPTIONAL_HEADER;
#define IMAGE_NT_OPTIONAL_HDR_MAGIC IMAGE_NT_OPTIONAL_HDR64_MAGIC
#else
typedef IMAGE_OPTIONAL_HEADER32 IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER32 PIMAGE_OPTIONAL_HEADER;
#define IMAGE_NT_OPTIONAL_HDR_MAGIC IMAGE_NT_OPTIONAL_HDR32_MAGIC
#endif

PE32+使用IMAGE_NT_HEADER64结构体,而PE32使用的是IMAGE_NT_HEADER32结构体。这2种结构体的区别在于第三个成员,前者为IMAGE_OPTIONAL_HEADER64,后者为IMAGE_OPTIONAL_HEADER32。后面的#ifdef _WIN64预处理部分中,根据系统类型,将64位/32位结构体重定义为IMAGE_NT_HEADERS/PIMAGE_NT_HEADERS。

38.1.2 IMAGE_FILE_HEADER

PE32+中IMAGE_FILE_HEADER结构体的Machine字段值发生变化。PE32中该Machine的值固定为014C。适用于x64的PE32+文件的Machine值为8664 (IA-64中PE32+文件的Machine值为0200)。以下是Winnt.h文件中定义的对应于各种CPU类型的Machine值。

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
#define IMAGE_FILE_MACHINE_UNKNOWN           0
#define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.
#define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP 0x01a3
#define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5 0x01a8 // SH5
#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB 0x01c2 // ARM Thumb/Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARM Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_AM33 0x01d3
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1
#define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS
#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE 0x0520 // Infineon
#define IMAGE_FILE_MACHINE_CEF 0x0CEF
#define IMAGE_FILE_MACHINE_EBC 0x0EBC // EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R 0x9041 // M32R little-endian
#define IMAGE_FILE_MACHINE_CEE 0xC0EE

可以看到有多个Machine值,它们分别对应于不同类型的CPU。此处只需先留意014C (x86)、0200 (IA-64)、 8664 (x64)这3个值就可以了(其实IA-64环境是很难遇到的)。

38.1.3 IMAGE,_OPTIONAL_HEADER

与原来的PE32相比,PE32+中变化最大的部分就是IMAGE_OPTIONAL_HEADER结构体。

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
74
75
76
77
78
79
80
81
//
// Optional header format.
//

typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//

WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;

//
// NT additional fields.
//

DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
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;



typedef struct _IMAGE_OPTIONAL_HEADER64 {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
ULONGLONG ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

Magic

首先,Magic字段值发生了改变,PE32中Magic值为010B,PE32+中Magic值为020B。WindowsPE装载器通过检查该字段值来区分IMAGE_OPTIONAL_HEADER结构体是32位的还是64位的。

BaseOfData

PE32文件中该字段用于指示数据节的起始地址(RVA),而PE32+文件中删除了该字段。

ImageBase

ImageBase字段(或称成员)的数据类型由原来的双字(DWORD)变为ULONGLONG类型(8个字节)。这是为了适应增大的进程虚拟内存。借助该字段,PE32+文件能够加载到64位进程的虚拟内存空间(16TB)的任何位置(EXE/DLL文件被加载到低位的8TB用户区域,SYS文件被加载到高位的8TB内核区域)。

AddressOfEntryPoints、SizeOflmage 等字段大小与原 PE32位是一样的,都是 DWORD大小(4个字节,32位)。这些字段的数据类型都是DWORD,意味着PE32+格式的文件占用的实际虚拟内存中,各映像的大小最大为4GB (32位)。但是由于ImageBase的大小为8个字节(64位),程序文件可以加载到进程虚拟内存中的任意地址位置。

※ PE文件的讲解中经常会提到“映像”(Image)—词,希望各位记住这个常用术语。加载PE文件到内存时并非按磁盘文件格式原封不动地进行,而是根据节区头中定义的节区起始地址、节区大小等属性加载。所以磁盘文件中的PE与内存中的PE状态是不同的。为了区分,我们将加载到内存中的PE称为映像。

38.1.4 IMAGE_THUNK_DATA

IMAGE_THUNK_DATA结构体的大小由原来的4个字节变为8个字节。

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
typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString; // PBYTE
ULONGLONG Function; // PDWORD
ULONGLONG Ordinal;
ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA64;
typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;

typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;


#ifdef _WIN64
typedef IMAGE_THUNK_DATA64 IMAGE_THUNK_DATA;
typedef PIMAGE_THUNK_DATA64 PIMAGE_THUNK_DATA;
typedef IMAGE_TLS_DIRECTORY64 IMAGE_TLS_DIRECTORY;
typedef PIMAGE_TLS_DIRECTORY64 PIMAGE_TLS_DIRECTORY;
#else
typedef IMAGE_THUNK_DATA32 IMAGE_THUNK_DATA;
typedef PIMAGE_THUNK_DATA32 PIMAGE_THUNK_DATA;
typedef IMAGE_TLS_DIRECTORY32 IMAGE_TLS_DIRECTORY;
typedef PIMAGE_TLS_DIRECTORY32 PIMAGE_TLS_DIRECTORY;
#endif

在PE32文件中跟踪INT、IAT值,会见到IMAGE_THUNK_DATA32结构体(大小为4个字节)数组,而PE32+文件中会岀现IMAGE_THUNK_DATA64结构体(大小为8个字节)数组。所以跟踪IAT时要注意数组元素的大小,如图38-1所示。

图38-1中圆圈内的部分就是IMAGE_THUNK_DATA结构体数组,一个为INT,另一个为IAT。装载PE文件时,OS的PE装载器会向IAT中写入真正的API入口地址(VA), 64位OS中地址(指针)大小为8个字节(64位),所以IMAGE_THUNK_DATA结构体的大小只能增长到8个字节。

38.1.5 IMAGE_TLS_DIRECTORY

IMAGE_TLS_DIRECTORY结构体的部分成员为VA,它们在PE32+中被扩展为8个字节。

IMAGE_TLS_DIRECTORY 结构体的 StartAddressOfRawData , EndAddressOfRawData、AddressOflndex、AddressOfCallBacks字段持有的都是VA值。所以它们被扩展为64位OS的地址大小(8个字节)。对PE+文件格式中改动部分的讲解到此结束。幸运的是,PE32+是在原PE32文件格式的基础上扩展而来的,如果熟悉了PE32文件格式,那么就能很轻松地掌握PE32+。

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
typedef struct _IMAGE_TLS_DIRECTORY64 {
ULONGLONG StartAddressOfRawData;
ULONGLONG EndAddressOfRawData;
ULONGLONG AddressOfIndex; // PDWORD
ULONGLONG AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *;
DWORD SizeOfZeroFill;
DWORD Characteristics;
} IMAGE_TLS_DIRECTORY64;
typedef IMAGE_TLS_DIRECTORY64 * PIMAGE_TLS_DIRECTORY64;

typedef struct _IMAGE_TLS_DIRECTORY32 {
DWORD StartAddressOfRawData;
DWORD EndAddressOfRawData;
DWORD AddressOfIndex; // PDWORD
DWORD AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *
DWORD SizeOfZeroFill;
DWORD Characteristics;
} IMAGE_TLS_DIRECTORY32;
typedef IMAGE_TLS_DIRECTORY32 * PIMAGE_TLS_DIRECTORY32;

#ifdef _WIN64
typedef IMAGE_THUNK_DATA64 IMAGE_THUNK_DATA;
typedef PIMAGE_THUNK_DATA64 PIMAGE_THUNK_DATA;
typedef IMAGE_TLS_DIRECTORY64 IMAGE_TLS_DIRECTORY;
typedef PIMAGE_TLS_DIRECTORY64 PIMAGE_TLS_DIRECTORY;
#else
typedef IMAGE_THUNK_DATA32 IMAGE_THUNK_DATA;
typedef PIMAGE_THUNK_DATA32 PIMAGE_THUNK_DATA;
typedef IMAGE_TLS_DIRECTORY32 IMAGE_TLS_DIRECTORY;
typedef PIMAGE_TLS_DIRECTORY32 PIMAGE_TLS_DIRECTORY;
#endif

CFF Explorer

向各位推荐一个多功能的PE实用工具—CFF Explorer,它就像“瑞士军刀”(Swiss

Army Knife) —样,提供了多样化的功能,并且支持PE32+文件格式,是一款非常有

用的PE工具。

代码逆向分析人员进人64位环境后会遇到很多问题,其中最严重的是,原有的32位代码逆向分析工具无法继续在64位环境下使用。我喜欢用的PEView工具并不支持PE32+文件格式,所以这里向各位介绍CFF Explorer,它是一款支持PE32+的PE Viewer工具,如图38-2所示。

http://www.ntcore.com/exsuite.php

除了PE Viewer功能外,CFF Explorer还提供PE编辑器、PE重建、RVA↔RAW转换器、反汇编器等综合功能,它是一个集多种功能于一身的强大的代码逆向分析工具。后面需要操作PE32+文件时,希望各位多多使用它。

使用CFF Explorer工具可以非常方便地剪掉或添加节区。虽然使用好的工具可以增加处理的便利性,但是过分依赖它们将无益于提高自身的技术水平。所以刚开始学习代码逆向分析技术时,并不建议各位使用好的辅助工具,可以选择一些简单的工具,学习相关知识、了解它们的工作原理,然后通过练习进一步巩固所学的内容,这才是提升自身技术水平的正确途径。


第38章 PE32+
https://m0ck1ng-b1rd.github.io/1999/03/08/逆向工程核心原理/第38章 PE32+/
作者
何语灵
发布于
1999年3月8日
许可协议