■扩展头(可选标头仅限映像文件)
OptionalHeader字段描述了可执行文件的更多细节和布局信息,如图像基址、入口点、数据目录、节表等。它的具体结构取决于文件的机器架构,可以是IMAGE_OPTIONAL_HEADER32(32位)或IMAGE_OPTIONAL_HEADER64(64位)结构体。
PE文件的扩展头位于文件头之后,其结构可以根据具体的文件类型和操作系统的架构而有所不同。
每个映像文件都有一个用于向加载程序提供信息的可选标头。 此标头是可选标头,因为某些文件(特别是obj对象文件)没有此标头。 对于映像文件,此标头是必需的。对象文件可以具有可选标头,但通常此标头在对象文件中没有函数,只是为了增加其大小。
注意
1.可选标头的大小不是固定的。 COFF 标头中的 SizeOfOptionalHeader 字段必须用于验证对特定数据目录的文件的探测是否未超出 SizeOfOptionalHeader。 有关详细信息,请参阅MSDN COFF 文件标头(对象和映像)。
2.还应该使用可选标头的 NumberOfRvaAndSizes 字段(数据目录的项目数量)来确保对特定数据目录条目的探测不会超出可选标头。 此外,请务必验证可选标头魔数,以确保格式兼容性。
可选标头魔数确定映像是 PE32 还是 PE32+ (64位PE)可执行文件。
魔数 | PE 格式 |
0x10b | PE32 |
0x20b | PE32+ |
PE32+ 映像允许使用 64 位地址空间,同时会将映像大小限制为 2 GB。 其他 PE32+ 修改会在各自的部分中进行介绍。
可选标头本身具有三个主要部分。
偏移量 (PE32/PE32+) | 大小 (PE32/PE32+) | 标头部分 | 说明 |
0 | 28/24 | 标准字段 | 为 COFF 的所有实现(包括 UNIX)定义的字段。 |
28/24 | 68/88 | 特定于 Windows 的字段 | 用于支持 Windows(例如子系统)的特定功能的其他字段。 |
96/112 | 变量 | 数据目录 | 在映像文件中找到并由操作系统使用的特殊表(例如,导入表和导出表)的地址/大小对。 |
●可选标头标准字段(仅限映像)
可选标头的前八个字段是为 COFF 的每个实现定义的标准字段。 这些字段包含可用于加载和运行可执行文件的常规信息。 对于 PE32+ 格式,它们保持不变。
Offset | 大小 | 字段 | 说明 |
0 | 2 | Magic | 标识映像文件状态的无符号整数。 最常见的数字是 0x10B,它将映像文件标识为普通可执行文件。 0x107 将映像文件标识为 ROM 映像,0x20B 将映像文件标识为 PE32+ 可执行文件。 |
2 | 1 | MajorLinkerVersion | 链接器主版本号。 |
3 | 1 | MinorLinkerVersion | 链接器次要版本号。 |
4 | 4 | SizeOfCode | 代码(文本)段的大小,或者如果有多个部分,则是所有代码段的和。 |
8 | 4 | SizeOfInitializedData | 初始化数据部分的大小,或者如果有多个数据部分,则是所有此类部分的和。 |
12 | 4 | SizeOfUninitializedData | 未初始化数据部分 (BSS) 的大小,或者如果有多个 BSS 部分,则是所有此类部分的和。 |
16 | 4 | AddressOfEntryPoint | 可执行文件加载到内存中时相对于映像基址的入口点地址。 对于程序映像,这是起始地址。 对于设备驱动程序,这是初始化函数的地址。 入口点对于 DLL 是可选的。 不存在入口点时,此字段必须为零。 |
20 | 4 | BaseOfCode | 加载到内存中后相对于代码开头部分映像基址的地址。 |
【注】PE32 包含位于 BaseOfCode 之后的此附加字段,此字段在 PE32+ 中不存在。
Offset | 大小 | 字段 | 说明 |
24 | 4 | BaseOfData | 加载到内存中后相对于数据开头部分映像基址的地址。 |
●可选标头 Windows 特定字段(仅限映像)
接下来的 21 个字段是 COFF 可选标头格式的扩展。 它们包含 Windows 中的链接器和加载程序所需的其他信息。
偏移量 (PE32/PE32+) | 大小(PE32/PE32+) | 字段 | 说明 |
28/24 | 4/8 | ImageBase | 映像加载到内存中后第一个字节的首选地址;必须是 64 K 的倍数。DLL 的默认值为 0x10000000。 Windows CE EXE 的默认值为 0x00010000。 Windows NT、Windows 2000、Windows XP、Windows 95、Windows 98 和 Windows Me 的默认值为 0x00400000。 |
32/32 | 4 | SectionAlignment | 各部分加载到内存中时的对齐值(以字节为单位)。 它必须大于或等于 FileAlignment(4KB)。 默认值为体系结构的页面大小。 |
36/36 | 4 | FileAlignment | 用于使映像文件中各部分的原始数据一致的对齐系数(以字节为单位)。 该值应为 2 的幂次方,介于 512 和 64K 之间(含)。 默认值为 512。 如果 SectionAlignment 小于体系结构的页面大小,则 FileAlignment 必须与 SectionAlignment 匹配。 |
40/40 | 2 | MajorOperatingSystemVersion | 所需操作系统的主版本号。 |
42/42 | 2 | MinorOperatingSystemVersion | 所需操作系统的次要版本号。 |
44/44 | 2 | MajorImageVersion | 映像的主版本号。 |
46/46 | 2 | MinorImageVersion | 映像的次要版本号。 |
48/48 | 2 | MajorSubsystemVersion | 子系统的主版本号。 |
50/50 | 2 | MinorSubsystemVersion | 子系统的次要版本号。 |
52/52 | 4 | Win32VersionValue | 保留,必须为零。 |
56/56 | 4 | SizeOfImage | 映像加载到内存中时的映像大小(以字节为单位),包括所有标头。 它必须是 SectionAlignment (4KB)的倍数。 |
60/60 | 4 | SizeOfHeaders | MS DOS 存根、PE 标头和节标头的组合大小,其向上舍入到 FileAlignment (200H)的倍数。 |
64/64 | 4 | CheckSum | 映像文件的校验和。 用于计算校验和的算法已合并到 IMAGHELP.DLL 中。 加载时会检查以下内容是否有效:所有驱动程序、启动时加载的任何 DLL 以及加载到关键 Windows 进程中的任何 DLL。链接器选项可设置。 |
68/68 | 2 | 子系统 | 运行此映像所需的子系统。 有关详细信息,请参阅MSDN Windows 子系统。 |
70/70 | 2 | DllCharacteristics | 有关详细信息,请参阅MSDN此规范后面的 DLL 特征。 |
72/72 | 4/8 | SizeOfStackReserve | 要保留的堆栈的大小。 仅提交 SizeOfStackCommit;其余部分一次提供一页,直到达到保留大小。默认1MB大小。 |
76/80 | 4/8 | SizeOfStackCommit | 要提交的堆栈的大小。默认4KB大小。 |
80/88 | 4/8 | SizeOfHeapReserve | 要保留的本地堆空间的大小。 仅提交 SizeOfHeapCommit;其余部分一次提供一页,直到达到保留大小。默认1MB大小。 |
84/96 | 4/8 | SizeOfHeapCommit | 要提交的本地堆空间的大小。默认4KB大小。 |
88/104 | 4 | LoaderFlags | 保留,必须为零。 |
92/108 | 4 | NumberOfRvaAndSizes | 可选标头剩余部分中数据目录项的数目。 每项都描述位置和大小。 |
下面是在winnt.h头文件中扩展头结构体的定义:
●IMAGE_OPTIONAL_HEADER32
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic; // PE文件的魔数,标识文件类型和操作系统架构
BYTE MajorLinkerVersion; //链接器的主版本号
BYTE MinorLinkerVersion; //链接器的次版本号
DWORD SizeOfCode; //执行代码的大小(字节)
DWORD SizeOfInitializedData; //已初始化数据的大小(字节)
DWORD SizeOfUninitializedData; //未初始化数据的大小(字节)
DWORD AddressOfEntryPoint; //程序的入口点(相对于映像基址的偏移量)
DWORD BaseOfCode; //代码节的起始虚拟地址
DWORD BaseOfData; //数据节的起始虚拟地址
//
// NT additional fields.
//
DWORD ImageBase; //可执行文件的首选基址(32位虚拟内存地址)
DWORD SectionAlignment; //节在内存中对齐的大小
DWORD FileAlignment; //节在文件中对齐的大小
WORD MajorOperatingSystemVersion;//操作系统的主版本号要求
WORD MinorOperatingSystemVersion;//操作系统的次版本号要求
WORD MajorImageVersion; //可执行文件的主要版本号
WORD MinorImageVersion; //可执行文件的次要版本号
WORD MajorSubsystemVersion; //子系统的主要版本号要求
WORD MinorSubsystemVersion; //子系统的次要版本号要求
DWORD Win32VersionValue; //保留字段,用于兼容性
DWORD SizeOfImage; //可执行文件在内存中的大小
DWORD SizeOfHeaders; //文件头和节表的总大小(字节)
DWORD CheckSum; // PE文件的校验和
WORD Subsystem; //可执行文件所依赖的子系统
WORD DllCharacteristics; // DLL文件的特性
DWORD SizeOfStackReserve; //为堆栈保留的内存大小(32位,1MB)
DWORD SizeOfStackCommit; //实际分配给堆栈的内存大小(32位,4KB)
DWORD SizeOfHeapReserve; //为堆保留的内存大小(32位,1MB)
DWORD SizeOfHeapCommit; //实际分配给堆的内存大小(32位,4KB)
DWORD LoaderFlags; //加载器标志
DWORD NumberOfRvaAndSizes; //数据目录项的数量
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
●IMAGE_OPTIONAL_HEADER64
typedef struct _IMAGE_OPTIONAL_HEADER64 {
//
// Standard fields.
//
WORD Magic; // PE文件的魔数,标识文件类型和操作系统架构
BYTE MajorLinkerVersion; //链接器的主版本号
BYTE MinorLinkerVersion; //链接器的次版本号
DWORD SizeOfCode; //执行代码的大小(字节)
DWORD SizeOfInitializedData; //已初始化数据的大小(字节)
DWORD SizeOfUninitializedData; //未初始化数据的大小(字节)
DWORD AddressOfEntryPoint; //程序的入口点(相对于映像基址的偏移量)
DWORD BaseOfCode; //代码节的起始虚拟地址
//DWORD BaseOfData; //数据节的起始虚拟地址(缺失字段)
//
// NT additional fields.
//
ULONGLONG ImageBase; //可执行文件的首选基址(64位虚拟内存地址)
DWORD SectionAlignment; //节在内存中对齐的大小
DWORD FileAlignment; //节在文件中对齐的大小
WORD MajorOperatingSystemVersion; //操作系统的主版本号要求
WORD MinorOperatingSystemVersion; //操作系统的次版本号要求
WORD MajorImageVersion; //可执行文件的主要版本号
WORD MinorImageVersion; //可执行文件的次要版本号
WORD MajorSubsystemVersion; //子系统的主要版本号要求
WORD MinorSubsystemVersion; //子系统的次要版本号要求
DWORD Win32VersionValue; //保留字段,用于兼容性
DWORD SizeOfImage; //可执行文件在内存中的大小
DWORD SizeOfHeaders; //文件头和节表的总大小(字节)
DWORD CheckSum; // PE文件的校验和
WORD Subsystem; //可执行文件所依赖的子系统
WORD DllCharacteristics; // DLL文件的特性
ULONGLONG SizeOfStackReserve; //为堆栈保留的内存大小(64位,1MB)
ULONGLONG SizeOfStackCommit; //实际分配给堆栈的内存大小(64位,4KB)
ULONGLONG SizeOfHeapReserve; //为堆保留的内存大小(64位,1MB)
ULONGLONG SizeOfHeapCommit; //实际分配给堆的内存大小(64位,4KB)
DWORD LoaderFlags; //加载器标志
DWORD NumberOfRvaAndSizes; //数据目录项的数量
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录表
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
●Magic Number(魔数):一个2字节的字段,指示文件类型和操作系统架构。常见的值有:
1.PE32(32位):0x10B
2.PE32+(64位):0x20B
我们可以通过这个字段来判断一个PE文件是32位PE文件还是64位PE文件。
●MajorLinkerVersion、MinorLinkerVersion:链接器的版本号。指示用于创建PE文件的链接器的主要和次要版本号。对执行PE文件没有任何影响。
●SizeOfCode:代码段的大小,以字节为单位。指示代码段的实际大小。该大小是基于文件对齐后的大小,而非内存对齐后的大小。PE文件以200H(512)字节为单位对齐。
●SizeOfInitializedData:已初始化数据段的大小,以字节为单位。指示已初始化数据段的实际大小。同样是基于文件对齐后的大小。
●SizeOfUninitializedData:未初始化数据段的大小,以字节为单位。指示未初始化数据段的实际大小。同样是基于文件对齐后的大小。
●AddressOfEntryPoint:程序入口点的RVA地址,相对于映像基址的偏移量。它指示PE文件在加载到内存后执行的第一条指令的地址。
程序的入口地址+ImageBase基址=程序在内存中开始执行的首地址(虚拟内存地址)。
●BaseOfCode:代码段的起始虚拟RVA地址。它指示代码段在内存中的起始位置。
●BaseOfData:数据段的起始虚拟RVA地址。它指示数据段在内存中的起始位置。
●ImageBase:可执行文件的首选基址(虚拟内存地址)。它指示PE文件在内存中加载时的首选基址。
●SectionAlignment:节在内存中的对齐大小。它指示节在内存中的边界对齐方式。通常内存以1000H(4KB)为单位对齐。
●FileAlignment:节在文件中的对齐大小。它指示节在PE文件中的边界对齐方式。通常内存以2000H(512)字节为单位对齐。
●MajorOperatingSystemVersion、MinorOperatingSystemVersion:所需的主操作系统版本号和次操作系统版本号。指示PE文件所需的最低操作系统版本。
●MajorImageVersion、MinorImageVersion:可执行文件的主要版本号和次要版本号。本PE映像文件的版本号。如果在这里存放版本号,则软件更新,网络升级时可以用于比对版本号。
●MajorSubsystemVersion、MinorSubsystemVersion:所需的主子系统版本号和次子系统版本号。指示PE文件所需的最低子系统版本。
●Win32VersionValue:保留字段,必须设置为0,用于兼容性。
●SizeOfImage:映像在内存中的大小,以字节为单位。指示PE文件(包括所有节区)在内存中占用的总大小。
●SizeOfHeaders:文件头和节表的总大小,以字节为单位。指示PE文件头部和节表的实际大小。
●CheckSum:PE文件的校验和。用于验证文件的完整性。校验和,在大多数的PE文件中,该值是0。但在一些内核模式的驱动程序和系统DLL中,该值则是必须存在且是正确的,比如kernel32.dll中PE的校验和是 0011E97Eh。在VS编译器的“项目属性”>“链接器”>“高级”>“设置校验和”选项中默认是关闭的。
●Subsystem:可执行文件所依赖的子系统类型。常见的值有:IMAGE_SUBSYSTEM_WINDOWS_GUI(2)表示图形用户界面子系统。IMAGE_SUBSYSTEM_WINDOWS_CUI(3)表示控制台用户界面子系统等。
我们可以在winnt.h头文件中查到这个字段的值,如下所示:
常量 | Value | 说明 |
IMAGE_SUBSYSTEM_UNKNOWN | 0 | 未知子系统 |
IMAGE_SUBSYSTEM_NATIVE | 1 | 设备驱动程序和本机 Windows 进程 |
IMAGE_SUBSYSTEM_WINDOWS_GUI | 2 | Windows 图形用户界面 (GUI) 子系统 |
IMAGE_SUBSYSTEM_WINDOWS_CUI | 3 | Windows 字符子系统 |
IMAGE_SUBSYSTEM_OS2_CUI | 5 | OS/2 字符子系统 |
IMAGE_SUBSYSTEM_POSIX_CUI | 7 | Posix 字符子系统 |
IMAGE_SUBSYSTEM_NATIVE_WINDOWS | 8 | 本机 Win9x 驱动程序 |
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI | 9 | Windows CE |
IMAGE_SUBSYSTEM_EFI_APPLICATION | 10 | 可扩展固件接口 (EFI) 应用程序 |
IMAGE_SUBSYSTEM_EFI_BOOT_ SERVICE_DRIVER | 11 | 具有启动服务的 EFI 驱动程序 |
IMAGE_SUBSYSTEM_EFI_RUNTIME_ DRIVER | 12 | 具有运行时服务的 EFI 驱动程序 |
IMAGE_SUBSYSTEM_EFI_ROM | 13 | EFI ROM 映像 |
IMAGE_SUBSYSTEM_XBOX | 14 | XBOX |
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION | 16 | Windows 启动应用程序。 |
●DllCharacteristics:DLL文件的特性标志。这个字段指定了DLL文件的一些特性和行为,如是否支持ASLR(地址空间布局随机化)、是否支持DEP(数据执行保护)、是否终止线程时卸载DLL等。常见的标志值包括IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE(0x40)表示支持ASLR,IMAGE_DLLCHARACTERISTICS_NX_COMPAT(0x100)表示支持DEP等。
常量 | Value | 说明 |
0x0001 | 保留,必须为零。 | |
0x0002 | 保留,必须为零。 | |
0x0004 | 保留,必须为零。 | |
0x0008 | 保留,必须为零。 | |
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | 0x0020 | 映像可处理高熵 64 位虚拟地址空间。 |
IMAGE_DLLCHARACTERISTICS_ | 0x0040 | DLL 可以在加载时重定位。 |
IMAGE_DLLCHARACTERISTICS_ | 0x0080 | 强制实施了代码完整性检查。 |
IMAGE_DLLCHARACTERISTICS_ | 0x0100 | 该映像与 NX 兼容。 |
IMAGE_DLLCHARACTERISTICS_ NO_ISOLATION | 0x0200 | 隔离感知,但不隔离映像。 |
IMAGE_DLLCHARACTERISTICS_ NO_SEH | 0x0400 | 不使用结构化异常 (SE) 处理。 该映像中无法调用任何 SE 处理程序。 |
IMAGE_DLLCHARACTERISTICS_ NO_BIND | 0x0800 | 请勿绑定此映像。 |
IMAGE_DLLCHARACTERISTICS_APPCONTAINER | 0x1000 | 映像必须在 AppContainer 中执行。 |
IMAGE_DLLCHARACTERISTICS_ WDM_DRIVER | 0x2000 | WDM 驱动程序。 |
IMAGE_DLLCHARACTERISTICS_GUARD_CF | 0x4000 | 映像支持控制流保护。 |
IMAGE_DLLCHARACTERISTICS_ TERMINAL_SERVER_AWARE | 0x8000 | 终端服务器感知。 |
以HelloWorld32.exe为例,其PE文件该字段的值为8140H,即支持终端服务器,应在AppContainer中执行,DLL可以被加载器基址随机化,例如X64 PE文件如果使用固定基址,则需要将DllCharacteristics字段改为8120H。而X86 PE文件改为8100H。
以VS2017版本为例,在使用VS编译时,关于基址的设置如图3-8所示。默认为随机基址。我们也可以将其改为“否”,设置固定基址。
基址随机化是一种安全机制,旨在增加系统的抵抗力,防止恶意攻击者利用已知的内存布局来定位和执行恶意代码。基址随机化通过在每次加载可执行文件时随机分配基址(基地址)来实现。这意味着可执行文件的代码和数据的实际内存位置在每次加载时都会发生变化。
为了支持基址随机化,PE文件中的重定位节区记录了需要进行地址重定位的位置和方式。重定位节区包含了可执行文件中使用绝对地址或固定偏移的位置,这些位置需要在加载时进行动态调整,以适应新的基址。当加载器加载PE文件时,会根据重定位节区的信息,计算并更新这些位置的值,使它们指向正确的内存位置。
因此,在Windows 10环境下编译的PE文件会包含重定位节区,以便在加载时进行地址重定位,实现基址随机化的安全机制。这有助于提高系统的安全性,减少恶意攻击的成功率。
图3-8 VS链接器关于基址的设置选项
●SizeOfStackReserve:为栈保留的内存大小。指定在进程初始化时为栈分配的虚拟内存大小。
●SizeOfStackCommit:实际分配给栈的内存大小。指定在进程运行时实际使用的栈内存大小。
●SizeOfHeapReserve:为堆保留的内存大小。指定在进程初始化时为堆分配的虚拟内存大小。
●SizeOfHeapCommit:实际分配给堆的内存大小。指定在进程运行时实际使用的堆内存大小。
图3-9 修改链接器选项设置堆栈大小
通常编译器初始化时保留栈和堆的大小为1MB,而实际为栈和堆提交的大小为4KB。
这4个值可以在链接时修改的,如图3-9所示。在VS项目属性“链接器”可以设置。例如我们观察WinHex中的32位和64位记事本程序堆和栈的大小。
1.32位记事本
DWORD SizeOfStackReserve; //为堆栈保留的内存大小(32位,256KB)
DWORD SizeOfStackCommit; //实际分配给堆栈的内存大小(32位,68KB)
DWORD SizeOfHeapReserve; //为堆保留的内存大小(32位,1MB)
DWORD SizeOfHeapCommit; //实际分配给堆的内存大小(32位,4KB)
2.64位记事本
ULONGLONG SizeOfStackReserve; //为堆栈保留的内存大小(64位,512KB)
ULONGLONG SizeOfStackCommit; //实际分配给堆栈的内存大小(64位,68KB)
ULONGLONG SizeOfHeapReserve; //为堆保留的内存大小(64位,1MB)
ULONGLONG SizeOfHeapCommit; //实际分配给堆的内存大小(64位,4KB)
练习
请读者将HelloWorld.c分别编译生成32位和64位程序,然后在WinHex中观察默认堆和栈的大小。
●LoaderFlags:加载器标志。这个字段保留供操作系统使用,用于指定加载器的一些行为和配置。
●NumberOfRvaAndSizes:数据目录项的数量。指示数据目录表中的目录项数量。
在32位和64位PE文件中,扩展头中的数据目录表中的目录项数量都是16个。这个字段的值是可以修改的。接下来我们就动手修改并测试。
实验十四:验证并修改32位和64位PE文件数据目录项的数量
我们分别在Windows10和XP两个不同操作系统环境中测试32位和64位HelloWorld.exe PE文件。我们分别在Windows10和XP系统中,分别编译HelloWorld.exe的汇编版本和C语言版本。在Windows10中编译环境为masm32和VS2017,XP系统中使用的编译环境为masm32和VC6.0。
●Windows 10操作系统测试环境
1.32位HelloWorld.exe(Win10汇编版本)文件偏移地址00000164H地址处,将默认0010H修改为0000~0010H之间的任一值,我们会发现,当数据目录项的数目大于等于6时,PE文件可以在Windows 10系统正常运行。如果数据目录项的数目小于6时,PE文件可以在Windows 10系统则不能正常运行。
2.32位HelloWorld32.exe(Win10 C语言版本)同样如此。
3.64位HelloWorld32.exe(Win10 C语言版本)同样如此。
4.32位HelloWorld.exe(Win XP汇编版本)文件偏移地址0000013CH地址处,将默认0010H修改为0000~0010H之间的任一值,我们会发现,当数据目录项的数目大于等于2时,PE文件可以在Windows 10系统正常运行。如果数据目录项的数目小于2时,PE文件可以在Windows 10系统则不能正常运行。
5.32位HelloWorld32.exe(Win XP C语言版本)同样如此。
●Windows XP操作系统测试环境
1.32位HelloWorld.exe(Win XP汇编版本)文件偏移地址0000013CH地址处,将默认0010H修改为0000~0010H之间的任一值,我们会发现,当数据目录项的数目大于等于2时,PE文件可以在Windows 10系统正常运行。如果数据目录项的数目小于2时,PE文件可以在Windows 10系统则不能正常运行。
2.32位HelloWorld32.exe(Win XP C语言版本)同样如此。
练习
请读者分别修改测试32位和64位记事本程序数据目录表中的目录项数量。
结论
1.数据目录表中的目录项数量是可以被修改的。
2.数据目录表中的目录项数量的最小值与编译器的版本和操作系统的运行环境相关。
3.PE文件所需数据目录项与具体的PE文件相关,不同的PE文件所依赖的数据目录项有所不同。
稍后我们将详细分析各个数据目录项所代表的含义。
●DataDirectory:数据目录表。这是一个由IMAGE_DATA_DIRECTORY结构组成的数组,用于指定PE文件中各个数据结构的位置和大小。
每个数据目录都提供 Windows 使用的表或字符串的地址和大小。 这些数据目录条目已全部加载到内存中,以便系统可以在运行时使用它们。数据目录是具有以下声明的 8 字节字段:
1.IMAGE_DATA_DIRECTORY结构
struct IMAGE_DATA_DIRECTORY
{
public uint VirtualAddress; // 数据结构的虚拟地址
public uint Size; // 数据结构的大小
};
第一个字段 (VirtualAddress) 实际上是表的 RVA。 RVA 是加载表后相对于映像基址的表地址。 第二个字段提供以字节为单位的大小。 下表中列出了构成可选标头的最后一部分的数据目录。
注意
目录数不是固定的。在查找特定目录之前,请检查可选标头中的 NumberOfRvaAndSizes 字段。此外,不要假定此表中的 RVA 指向节的开头,或者包含特定表的节具有特定名称。
偏移量 (PE/PE32+) | 大小 | 字段 | 说明 |
96/112 | 8 | 导出表 | 导出表地址和大小。 有关详细信息,请参阅MSDN .edata 部分(仅限映像)。 |
104/120 | 8 | 导入表 | 导入表地址和大小。 有关详细信息,请参阅MSDN .idata 部分。 |
112/128 | 8 | 资源表 | 资源表地址和大小。 有关详细信息,请参阅MSDN .rsrc 部分。 |
120/136 | 8 | 异常表 | 异常表地址和大小。 有关详细信息,请参阅MSDN .pdata 部分。 |
128/144 | 8 | 证书表 | 属性证书表地址和大小。 有关详细信息,请参阅MSDN 属性证书表(仅限映像)。 |
136/152 | 8 | 基址重定位表 | 基址重定位表地址和大小。 有关详细信息,请参阅MSDN .reloc 部分(仅限映像)。 |
144/160 | 8 | 调试 | 调试数据起始地址和大小。 有关详细信息,请参阅 MSDN .debug 部分。 |
152/168 | 8 | 体系结构 | 已保留,必须为 0 |
160/176 | 8 | 全局指针 | 要存储在全局指针寄存器中的值的 RVA。 此结构的大小成员必须设置为零。 |
168/184 | 8 | TLS 表 | 线程本地存储 (TLS) 表地址和大小。 有关详细信息,请参阅 MSDN .tls 部分。 |
176/192 | 8 | 加载配置表 | 加载配置表地址和大小。 有关详细信息,请参阅MSDN 加载配置结构(仅限映像)。 |
184/200 | 8 | 绑定导入 | 绑定导入表地址和大小。 |
192/208 | 8 | IAT | 导入地址表地址和大小。 有关详细信息,请参阅MSDN 导入地址表。 |
200/216 | 8 | 延迟导入描述符 | 延迟导入描述符地址和大小。 有关详细信息,请参阅MSDN 延迟加载导入表(仅限映像)。 |
208/224 | 8 | CLR 运行时标头 | CLR 运行时标头地址和大小。 有关详细信息,请参阅MSDN .cormeta 部分(仅限对象)。 |
216/232 | 8 | 保留,必须为零 |
3.16个数据目录表的目录项说明
■导出表(Export Table)
存储可执行文件导出的函数和符号信息,使其他模块可以访问这些导出。
■导入表(Import Table)
存储可执行文件导入的函数和符号信息,描述了可执行文件所依赖的其他模块和它们所提供的函数。
■资源表(Resource Table)
存储可执行文件使用的资源(如图标、位图、字符串等)的位置和大小信息。
■异常表(Exception Table)
存储可执行文件中的异常处理程序信息,用于处理程序中可能发生的异常情况。
■安全表(Security Table)
存储有关可执行文件的安全性设置和数字签名等信息。
■基址重定位表(Base Relocation Table)
存储基址重定位信息,用于处理可执行文件在加载时的基址变化,以便正确地调整内存中的地址。
■调试表(Debug Table)
存储调试符号和调试信息,用于程序的调试和错误追踪。
■体系结构相关(Architecture-specific)
存储与特定体系结构相关的数据,如IA-32特定的扩展数据。
■全局指针寄存器表(Global Pointer Register Table)
存储全局指针寄存器(如GP)的位置和大小信息。
■TLS表(Thread Local Storage Table)
存储线程本地存储(Thread Local Storage)相关的数据结构和信息。
■加载配置表(Load Configuration Table)
存储可执行文件的加载配置信息,如安全设置、堆栈大小等。
■绑定导入表(Binding Table)
存储可执行文件中的模块绑定信息,用于实现延迟绑定(lazy binding)和符号决议。
■导入函数地址表(Import Address Table)
存储导入函数的地址信息,用于在运行时解析和更新导入函数的地址。
■延迟加载导入表(Delay Load Import Table)
存储延迟加载导入函数的信息,用于在需要时才加载并解析导入函数。
■ CLR运行时头(CLR Runtime Header)
存储与托管代码和CLR(Common Language Runtime)相关的信息。
■保留(Reserved)
保留未使用的数据目录项。
注意
具体的数据目录表的目录项可能会因不同的可执行文件格式而有所不同。上述列表是PE格式下常见的数据目录表的目录项,其他格式可能具有不同的目录项和描述。在实际使用中,建议参考相应可执行文件格式的文档以获得准确的目录项注释和描述。