本文最后更新于: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文件格式的不同。
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。
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 #define IMAGE_FILE_MACHINE_R3000 0x0162 #define IMAGE_FILE_MACHINE_R4000 0x0166 #define IMAGE_FILE_MACHINE_R10000 0x0168 #define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 #define IMAGE_FILE_MACHINE_ALPHA 0x0184 #define IMAGE_FILE_MACHINE_SH3 0x01a2 #define IMAGE_FILE_MACHINE_SH3DSP 0x01a3 #define IMAGE_FILE_MACHINE_SH3E 0x01a4 #define IMAGE_FILE_MACHINE_SH4 0x01a6 #define IMAGE_FILE_MACHINE_SH5 0x01a8 #define IMAGE_FILE_MACHINE_ARM 0x01c0 #define IMAGE_FILE_MACHINE_THUMB 0x01c2 #define IMAGE_FILE_MACHINE_ARMNT 0x01c4 #define IMAGE_FILE_MACHINE_AM33 0x01d3 #define IMAGE_FILE_MACHINE_POWERPC 0x01F0 #define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1 #define IMAGE_FILE_MACHINE_IA64 0x0200 #define IMAGE_FILE_MACHINE_MIPS16 0x0266 #define IMAGE_FILE_MACHINE_ALPHA64 0x0284 #define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 #define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 #define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64 #define IMAGE_FILE_MACHINE_TRICORE 0x0520 #define IMAGE_FILE_MACHINE_CEF 0x0CEF #define IMAGE_FILE_MACHINE_EBC 0x0EBC #define IMAGE_FILE_MACHINE_AMD64 0x8664 #define IMAGE_FILE_MACHINE_M32R 0x9041 #define IMAGE_FILE_MACHINE_CEE 0xC0EE
|
可以看到有多个Machine值,它们分别对应于不同类型的CPU。此处只需先留意014C (x86)、0200 (IA-64)、 8664 (x64)这3个值就可以了(其实IA-64环境是很难遇到的)。
与原来的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
|
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData;
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; ULONGLONG Function; ULONGLONG Ordinal; ULONGLONG AddressOfData; } u1; } IMAGE_THUNK_DATA64; typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;
typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; DWORD Function; DWORD Ordinal; DWORD AddressOfData; } 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; ULONGLONG AddressOfCallBacks; 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; DWORD AddressOfCallBacks; 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工具可以非常方便地剪掉或添加节区。虽然使用好的工具可以增加处理的便利性,但是过分依赖它们将无益于提高自身的技术水平。所以刚开始学习代码逆向分析技术时,并不建议各位使用好的辅助工具,可以选择一些简单的工具,学习相关知识、了解它们的工作原理,然后通过练习进一步巩固所学的内容,这才是提升自身技术水平的正确途径。