DOS块的后面接着就是NT头了。NT头(NT Header)是PE文件中的一个数据结构,用于描述可执行文件的详细信息和布局。它位于PE文件的DOS头之后,紧接着DOS Stub,如图3-6所示。NT头是用于现代Windows操作系统的可执行文件格式的关键部分。NT头包含了PE文件的各种信息,包括文件的签名、文件头和扩展头。
■IMAGE_NT_HEADERS结构
NT头的结构可以通过一个名为IMAGE_NT_HEADERS的结构体来表示。我们在winnt.h头文件中搜索到这个结构体的定义如下:
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature; //PE文件的签名(PE特征码)
IMAGE_FILE_HEADER FileHeader; //文件头
IMAGE_OPTIONAL_HEADER64 OptionalHeader; //64位PE文件扩展头
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //PE文件的签名(PE特征码)
IMAGE_FILE_HEADER FileHeader; //文件头
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //32位PE文件扩展头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
我们发现有两个NT头结构的定义,一个是32位PE文件,另一个是64位PE文件。稍后我们将详细讲解它们的区别。
图3-6 PE文件结构
【注】块表为节表的同义词。
■PE特征码(签名)
文件的签名由Signature字段表示,它是一个4个字节的固定标识,用于表示该文件是一个PE文件。在32位的PE文件中,Signature的值为0x00004550(或者ASCII字符"PE\0\0"),在64位的PE文件中,Signature的值同样为0x00004550(或者ASCII字符"PE\0\0")。
特征码是一个固定的标识值,用于告知操作系统或其他工具,该文件遵循PE文件格式的规范,并提供了正确的结构和布局信息。操作系统在加载和解析PE文件时,会首先检查特征码,以确认文件是否为有效的PE文件。
特征码的位置在PE文件的NT头中,可以通过IMAGE_NT_HEADERS结构体中的Signature字段来访问。
■文件头(COFF文件标头)
在PE文件的开头,或紧接在映像文件签名之后,是以下格式的标准 COFF 文件头(20个字节)。 请注意,Windows 加载器将节区数限制为 96。
文件头(FileHeader)字段描述了文件的基本属性和布局,包括文件的机器架构、节表的数量和大小等信息。它是一个IMAGE_FILE_HEADER结构体,包含了各种字段,如Machine、NumberOfSections、SizeOfOptionalHeader等。我们在winnt.h头文件中搜索到文件头结构体的定义如下:
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // CPU架构类型
WORD NumberOfSections; // 节区数量
DWORD TimeDateStamp; // 文件创建时间戳
DWORD PointerToSymbolTable; // COFF符号表偏移地址
DWORD NumberOfSymbols; // COFF符号表条目数量
WORD SizeOfOptionalHeader; // 可选头的大小
WORD Characteristics;// 文件属性标志
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Offset | 大小 | 字段 | 说明 |
0 | 2 | 设备 | 标识目标计算机类型的数字。 有关详细信息,请参阅MSDN目标计算机类型。 |
2 | 2 | NumberOfSections | 节区数目。 这指示节表的大小,该表紧跟在PE头之后。 |
4 | 4 | TimeDateStamp | 自 1970 年 1 月 1 日 00:00 起的秒数的低 32 位(C 运行时 time_t 值),指示文件的创建时间。 |
8 | 4 | PointerToSymbolTable | COFF 符号表的文件偏移量;如果没有 COFF 符号表,则为零。 映像文件的此值应为零,因为 COFF 调试信息已被弃用。 |
12 | 4 | NumberOfSymbols | 符号表中的项数。 此数据可用于查找紧跟在符号表后面的字符串表。 映像文件的此值应为零,因为 COFF 调试信息已被弃用。 |
16 | 2 | SizeOfOptionalHeader | 可选标头(扩展头)的大小,它是可执行文件所必需的,但对象文件(obj)不需要它。 对于对象文件,此值应为零。 有关标头格式的说明,请参阅MSDN可选标头(仅限映像文件)。 |
18 | 2 | 特征 | 指示文件属性的标志。 有关特定标志值,请参阅MSDN特征。 |
以下是对每个字段的注释说明:
●Machine:指定了目标CPU架构的类型,例如x86、x64、ARM等。具体的架构类型可以通过相应的枚举值来表示,如IMAGE_FILE_MACHINE_I386、IMAGE_FILE_MACHINE_AMD64等。014cH代表x86 CPU,8664H表示x64 CPU。
我们可以在winnt.h头文件中搜索IMAGE_FILE_MACHINE_I386,查到所有CPU架构类型的枚举值:
常量 | Value | 说明 |
IMAGE_FILE_MACHINE_UNKNOWN | 0x0 | 假定此字段的内容适用于任何计算机类型 |
IMAGE_FILE_MACHINE_ALPHA | 0x184 | Alpha AXP,32 位地址空间 |
IMAGE_FILE_MACHINE_ALPHA64 | 0x284 | Alpha 64,64 位地址空间 |
IMAGE_FILE_MACHINE_AM33 | 0x1d3 | Matsushita AM33 |
IMAGE_FILE_MACHINE_AMD64 | 0x8664 | X64 |
IMAGE_FILE_MACHINE_ARM | 0x1c0 | ARM little endian |
IMAGE_FILE_MACHINE_ARM64 | 0xaa64 | ARM64 little endian |
IMAGE_FILE_MACHINE_ARMNT | 0x1c4 | ARM Thumb-2 little endian |
IMAGE_FILE_MACHINE_AXP64 | 0x284 | AXP 64 (与 Alpha 64 相同) |
IMAGE_FILE_MACHINE_EBC | 0xebc | EFI 字节代码 |
IMAGE_FILE_MACHINE_I386 | 0x14c | Intel 386 或更高版本的处理器和兼容的处理器 |
IMAGE_FILE_MACHINE_IA64 | 0x200 | Intel Itanium 处理器系列 |
IMAGE_FILE_MACHINE_LOONGARCH32 | 0x6232 | LoongArch 32 位处理器系列 |
IMAGE_FILE_MACHINE_LOONGARCH64 | 0x6264 | LoongArch 64 位处理器系列 |
IMAGE_FILE_MACHINE_M32R | 0x9041 | Mitsubishi M32R little endian |
IMAGE_FILE_MACHINE_MIPS16 | 0x266 | MIPS16 |
IMAGE_FILE_MACHINE_MIPSFPU | 0x366 | 将 MIPS 与 FPU 结合使用 |
IMAGE_FILE_MACHINE_MIPSFPU16 | 0x466 | 将 MIPS16 与 FPU 结合使用 |
IMAGE_FILE_MACHINE_POWERPC | 0x1f0 | Power PC little endian |
IMAGE_FILE_MACHINE_POWERPCFP | 0x1f1 | 支持浮点的 Power PC |
IMAGE_FILE_MACHINE_R4000 | 0x166 | MIPS little endian |
IMAGE_FILE_MACHINE_RISCV32 | 0x5032 | RISC-V 32 位地址空间 |
IMAGE_FILE_MACHINE_RISCV64 | 0x5064 | RISC-V 64 位地址空间 |
IMAGE_FILE_MACHINE_RISCV128 | 0x5128 | RISC-V 128 位地址空间 |
IMAGE_FILE_MACHINE_SH3 | 0x1a2 | Hitachi SH3 |
IMAGE_FILE_MACHINE_SH3DSP | 0x1a3 | Hitachi SH3 DSP |
IMAGE_FILE_MACHINE_SH4 | 0x1a6 | Hitachi SH4 |
IMAGE_FILE_MACHINE_SH5 | 0x1a8 | Hitachi SH5 |
IMAGE_FILE_MACHINE_THUMB | 0x1c2 | Thumb |
IMAGE_FILE_MACHINE_WCEMIPSV2 | 0x169 | MIPS little-endian WCE v2 |
●NumberOfSections:指定了文件中节区(Section Table)的数量。节表记录了不同段(如代码段、数据段等)的信息,每个段对应一个节表项。
【注意】PE文件中最少要有一个节区,即.text节区(代码段),最多可以有96个节区。
●TimeDateStamp:表示文件的创建时间戳,通常是一个以1970年1月1日以来的秒数表示的时间值。
●PointerToSymbolTable(已弃用):指定了COFF符号表的偏移地址。COFF符号表包含了与文件相关的符号信息,如函数、变量等。
COFF(Common Object File Format)符号表是一种用于存储目标文件中符号信息的数据结构。它通常用于编译后的可执行文件、动态链接库(DLL)和目标文件中,以支持符号的调试和链接。
COFF符号表包含了与文件相关的符号信息,例如函数、变量、类型等。这些符号信息在编译和链接过程中收集和生成,以便在调试和链接阶段使用。
COFF符号表的结构可能因不同的操作系统和编译器而有所差异,但通常包含以下类型的符号信息:
1.符号名称(Symbol Name):指定符号的名称,例如函数名、变量名等。
2.符号值(Symbol Value):表示符号的地址或相对地址,用于定位符号在内存中的位置。
3.符号大小(Symbol Size):表示符号的大小,用于确定符号所占用的内存空间。
4.符号类型(Symbol Type):指定符号的类型,例如函数、变量、类型等。
5.符号绑定(Symbol Binding):表示符号的绑定类型,如全局符号、局部符号等。
6.符号节表索引(Symbol Section Index):指定符号所属的节表索引,用于确定符号所在的段或节。
COFF符号表对于调试器和链接器非常重要。调试器可以使用符号表来解析和显示变量、函数的名称和值,以及调用堆栈信息。链接器可以使用符号表来解析和解决符号引用,以正确连接不同的目标文件和库文件。
【注意】COFF符号表是一种中间表示形式,它不直接在可执行文件中执行。在最终的可执行文件中,符号表通常会被压缩、优化或移除,以减小文件大小和提高执行效率。
●NumberOfSymbols(已弃用):表示COFF符号表中的符号条目数量。
●SizeOfOptionalHeader:指定了扩展头(Optional Header)的大小,即IMAGE_OPTIONAL_HEADER32或IMAGE_OPTIONAL_HEADER64结构体的大小。
实验十三:验证并修改32位和64位PE文件的扩展头大小
●验证扩展头大小
1.32位PE文件
将notepad32.exe拖入WinHex工具,文件偏移地址0xF4地址处的值为0E0H。
在文件偏移地址0xF8~0x1D8之间为该PE文件的扩展头,32位PE文件扩展头大小=0x1D8-0xF8=0E0H(224)字节。
2.64位PE文件
将notepad64.exe拖入WinHex工具,文件偏移地址0x10C地址处的值为0F0H。
在文件偏移地址0x110~0x200之间为该PE文件的扩展头,64位PE文件扩展头大小=0x200-0x110=0F0H(240)字节。
●修改扩展头大小
1.32位PE文件
将notepad32.exe拖入WinHex工具,观察在文件偏移地址0x320~0x400之间为空白区域,大小为0EH(224)字节,这是我们可以扩大扩展头大小的最大字节数。原因读者应该记得,为了不改变下面节区的偏移地址。
接下来我们选定这个区域,按“Delete”键删除该区域。然后在扩展头最后一个字节0x1D7处粘贴224个零字节。
最后一步,在文件偏移地址0xF4地址处将原来扩展头大小的值0E0H增加一倍,修改为01C0H。修改后保存文件,重命名为notepad32_1.exe,运行一切正常。
2.64位PE文件
将notepad64.exe拖入WinHex工具,观察在文件偏移地址0x2F0~0x400之间为空白区域,大小为110H(272)字节,这是我们可以扩大扩展头大小的最大字节数。
接下来我们选定这个区域,按“Delete”键删除该区域。然后在扩展头最后一个字节0x1FF处粘贴272个零字节。
最后一步,在文件偏移地址0x10C地址处将原来扩展头大小的值0F0H修改为0200H。修改后保存文件,重命名为notepad64_1.exe,我们会发现程序无法正常运行。
原因是运行程序时,系统使用LoadString函数加载命令行参数字符串,如果第一个参数为自身的句柄,则会在当前路径或系统路径下寻找资源PE文件(没有代码的资源文件),名称为***.exe.mui。我们在C盘根目录下搜索notepad.exe.mui文件名,找到其所在的文件夹位于“C:\Windows\zh-CN”中,将文件夹“zh-CN”复制到源代码目录“D:\code\winpe”,使记事本程序与资源文件夹位于同一目录,如图3-7所示。再将notepad64_1.exe改回notepad.exe,就可以正常运行了。
图3-7 添加记事本资源PE文件
练习
1.请读者以64位记事本程序为例,只修改其文件头中SizeOfOptionalHeader字段的大小为0F1H,其他保持不变,保存修改后观察是否记事本可以正常运行。
2.将64位记事本程序文件头中SizeOfOptionalHeader字段的大小为0F1H,并在扩展头结尾处添加一个字节,与此同时删除文件偏移地址00000400H之前一个零字节,保存修改后观察是否记事本可以正常运行。
3.以同样的方法测试32位记事本,将其扩展头的大小改为0F0H,增大16个字节。保存修改后,观察程序是否可以正常运行。
结论
1.不论32位还是64位PE程序都可以修改扩展头的大小。但需要注意,不能因此而改变下面节区在PE文件内的偏移地址。
2.增大扩展头的字节数必须是16的倍数。
●Characteristics:用于描述文件的属性标志,如是否为可执行文件、是否包含调试信息、是否可以作为系统驱动程序等。具体的属性标志可以通过位掩码来表示,如IMAGE_FILE_EXECUTABLE_IMAGE、IMAGE_FILE_DEBUG、IMAGE_FILE_SYSTEM等。
以下是我们在winnt.h头文件中查询到的文件属性标志:
标志 | 值 | 说明 |
IMAGE_FILE_RELOCS_STRIPPED | 0x0001 | 纯映像、Windows CE 和 Microsoft Windows NT 及更高版本。 这表示该文件不包含基址重定位,因此必须加载到其首选基址。 如果基址不可用,则加载程序将报告错误。 链接器的默认行为是从可执行文件 (EXE) 文件中去除基址重定位。 |
IMAGE_FILE_EXECUTABLE_IMAGE | 0x0002 | 纯映像。 这表示映像文件有效并且可以运行。 如果未设置此标志,则表示有链接器错误。 |
IMAGE_FILE_LINE_NUMS_STRIPPED | 0x0004 | COFF 行号已删除。 此标志已被弃用,应为零。 |
IMAGE_FILE_LOCAL_SYMS_STRIPPED | 0x0008 | 本地符号的 COFF 符号表条目已删除。 此标志已被弃用,应为零。 |
IMAGE_FILE_AGGRESSIVE_WS_TRIM | 0x0010 | 已过时。 主动剪裁工作集。 已针对 Windows 2000 及更高版本弃用此标志,此标志必须为零。 |
IMAGE_FILE_LARGE_ADDRESS_ AWARE | 0x0020 | 应用程序可以处理 > 2 GB 地址。 |
0x0040 | 此标志将保留以供将来使用。 | |
IMAGE_FILE_BYTES_REVERSED_LO | 0x0080 | Little endian:最低有效位 (LSB) 位于内存中最高有效位 (MSB) 之前。 此标志已被弃用,应为零。 |
IMAGE_FILE_32BIT_MACHINE | 0x0100 | 计算机基于 32 位字长度机器码体系结构。 |
IMAGE_FILE_DEBUG_STRIPPED | 0x0200 | 从映像文件中删除了调试信息。 |
IMAGE_FILE_REMOVABLE_RUN_ FROM_SWAP | 0x0400 | 如果映像位于可移动媒体上,请完全加载该映像并将其复制到交换文件。 |
IMAGE_FILE_NET_RUN_FROM_SWAP | 0x0800 | 如果映像位于网络媒体上,请完全加载该映像并将其复制到交换文件。 |
IMAGE_FILE_SYSTEM | 0x1000 | 映像文件是系统文件,而不是用户程序。 |
IMAGE_FILE_DLL | 0x2000 | 图像文件是动态链接库 (DLL)。 虽然无法直接运行此类文件,但这些文件被视为几乎适用于所有用途的可执行文件。 |
IMAGE_FILE_UP_SYSTEM_ONLY | 0x4000 | 该文件应仅在单处理器计算机上运行。 |
IMAGE_FILE_BYTES_REVERSED_HI | 0x8000 | Big endian:MSB 在内存中的 LSB 之前。 此标志已被弃用,应为零。 |
这些属性和标志位提供了关于PE文件的基本信息,帮助操作系统和工具正确解析和处理可执行文件。
我们在WinHex中观察32位记事本文件头结构的最后一个字段Characteristics的值为010FH,即IMAGE_FILE_32BIT_MACHINE+ IMAGE_FILE_LOCAL_SYMS_STRIPPED+ IMAGE_FILE_LINE_NUMS_STRIPPED+ IMAGE_FILE_EXECUTABLE_IMAGE+ IMAGE_FILE_RELOCS_STRIPPED
意为32位(局部符号信息、行号信息、重定位信息已被剥离)可执行文件。
64位记事本程序文件头的最后一个字段Characteristics的值为0022H,即IMAGE_FILE_LARGE_ADDRESS_AWARE+ IMAGE_FILE_EXECUTABLE_IMAGE,意为应用程序能够处理大于2GB的地址空间的64位可执行程序。