PE 文件格式
PE 简介
PE 文件的全称是 Portable Executable ,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE 文件,PE 文件是微软Windows操作系统上的程序文件,可能是间接被执行,如DLL)。 一个 32-bits 的 PE 文件布局如下图所示:
+-------------------------------+ \
| MS-DOS MZ header | |
+-------------------------------+ |
| MS-DOS Real-Mode Stub program | |
+-------------------------------+ |
| PE Signature | | -> PE file header
+-------------------------------+ |
| IMAGE_FILE_HEADER | |
+-------------------------------+ |
| IMAGE_OPTIONAL_HEADER | |
+-------------------------------+ /
| section header #1 |
+-------------------------------+
| section header #2
+-------------------------
:
:
+------------------------------+
| section #1 |
+------------------------------+
| section #2
+--------------------
:
:
接下来将会以一个 32-bit 的 PE 文件作为标本介绍一下 PE 文件。
// 示例代码 test.c
#include <stdio.h>
int main(){
printf("Hello, PE!\n");
return 0;
}
通过 Devcpp
软件的 TDM-GCC 4.9.2 32-bit Release
方式编译文件生成 test.exe
,作为示例文件。
常用术语及其含义
映像文件
因为 PE 文件通常需要加载到内存中才能执行,相当于内存中的映像,所以 PE 文件也叫做映像文件。RVA
相对虚拟地址,映像文件在虚拟内存中相对于加载基址的偏移。VA
虚拟地址,映像文件在虚拟内存中的地址。FOA
文件偏移地址,映像文件在磁盘文件中相对于文件开头的偏移。
因为不论是在磁盘文件上,或是在虚拟内存中,数据相对于其所在节的相对偏移是固定的,据此可以实现 RVA 与 FOA 之间的转换,即RVA - 节区RVA = FOA - 节区FOA
。
假设某一个属于 .data 节的数据的 RVA 是 0x3100,.data 节的 节区RVA 为 0x3000,那么该数据相对于 .data 节的相对偏移就是 0x100。而 .data 节在的 节区FOA 为 0x1C00,那么该数据在磁盘文件中的 FOA 就是 0x1D00。完整的计算公式是:FOA = 节区FOA + (RVA - 节区RVA)
。如果该映像文件的加载基址为0x40000000,那么该数据的 VA 就是 0x40003100。
PE文件头
PE 文件的最开始便是 PE 文件头,它由 MS-DOS 文件头
和 IMAGE_NT_HEADERS
结构体组成。
MS-DOS 文件头
MS-DOS 文件头
包含 IMAGE_DOS_HEADER
和 DOS Stub
两个部分。
IMAGE_DOS_HEADER
结构体的定义如下:
typedef struct _IMAGE_DOS_HEADER
{
WORD e_magic; // "MZ"
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew; // NT 头相对于文件起始处的偏移
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
IMAGE_DOS_HEADER
结构体中有 2 个重要成员:
- e_magic
单字。DOS 签名 "4D5A",即 ASCII 值 "MZ"。所有 PE 文件的开头都有 DOS 签名。
- e_lfanew
单字。IMAGE_NT_HEADER
相对于文件起始处的偏移。
示例程序的 IMAGE_DOS_HEADER
如图 2 所示:
IMAGE_DOS_HEADER
结构体后紧接着是 DOS Stub
,它的作用很简单,当系统为 MS-DOS 环境时,输出 This program cannot be run in DOS mode.
并退出程序,表明该程序不能在 MS-DOS 环境下运行。这使得所有的 PE 文件都对 MS-DOS 环境兼容。利用该特性可以创建出一个在 MS-DOS 和 Windows 环境中都能运行的程序,在 MS-DOS 中执行 16-bit MS-DOS 代码,在 Windows 中执行 32-bit Windows 代码。
示例程序的 DOS Stub
如图 3 所示:
IMAGE_NT_HEADERS
IMAGE_NT_HEADERS
结构体,俗称 NT 头。紧跟在 DOS Stub
之后,其定义如下:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; /* +0000h PE 标识 */
IMAGE_FILE_HEADER FileHeader; /* +0004h PE 标准头 */
IMAGE_OPTIONAL_HEADER32 OptionalHeader; /* +0018h PE 可选头 */
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
示例程序的 IMAGE_NT_HEADERS
如图 4 所示:
接下来详细说一下 NT 头。
PE Signature
NT 头的第一个成员是PE Signature
,它是一个4字节大小的ASCII码字符串 PE\0\0
,用于指明当前文件是一个 PE 格式的映像文件。其位置可以通过 IMAGE_DOS_HEADER
的 e_lfanew
成员的值确定。
IMAGE_FILE_HEADER
PE Signature
后紧跟着是 IMAGE_FILE_HEADER
结构体,又称作 COFF 头(标准通用文件格式头)
。其定义如下:
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; /* +0004h 目标机器类型 */
WORD NumberOfSections; /* +0006h PE 中节的数量 */
DWORD TimeDateStamp; /* +0008h 时间戳 */
DWORD PointerToSymbolTable; /* +000ch 指向符号表的指针 */
DWORD NumberOfSymbols; /* +0010h 符号表中符号数目 */
WORD SizeOfOptionalHeader; /* +0012h 可选头的大小 */
WORD Characteristics; /* +0014h 文件属性标志 */
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
接下来依次对每一个字段做出解释:
Machine
单字。用于指明 CPU 类型。详细了解所支持的 CPU 类型请参考 微软 PE 格式 COFF 文件头 Machine 类型。NumberOfSections
单字。文件中存在的节区数量。PE 文件将代码、数据、资源的依据属性分类到不同节区中存储。TimeDateStamp
双字。低 32 位表示从 1970 年 1 月 1 日 00:00 到文件创建时经过的秒数。PointerToSymbolTable
双字。符号表的文件偏移。如果不存在符号表,其值为 0。NumberOfSymbols
双字。该字段表示符号表中的符号数量。由于字符串表紧跟在符号表之后,所有能通过该值定位字符串表。SizeOfOptionalHeader
单字。表示可选头的大小。在 32-bit 机器上默认是 0x00E0,在 64-bit 机器上默认是 0x00F0。Characteristics
单字。用于标识文件属性,以 bit OR 方式组合。下面是一些已定义的文件属性标志:
// 文件属性标志
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // 表示文件不包含重定位信息,只能在原定的基址加载。如果原定基址不可用,加载器会报出错误
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // 表示文件可执行,如果该位未设置,意味着存在链接器错误
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // 不存在行信息
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // 不存在符号信息
#define IMAGE_FILE_AGGRESSIVE_WS_TRIM 0x0010 // 已废弃
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // 应用可处理大于 2GB 的地址
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // 小尾存储。已废弃
#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 基于 32-bit 体系结构
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // 不存在调试信息
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // 如果映像文件在可移动介质上,完全加载并复制到内存交换文件中
#define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // 如果映像文件在网络介质上,完全加载并复制到内存交换文件中
#define IMAGE_FILE_SYSTEM 0x1000 // 映像文件是系统文件
#define IMAGE_FILE_DLL 0x2000 // 映像文件是动态链接库文件
#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // 文件只能在单处理器机器上运行
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // 大尾存储(已废弃)
示例程序的 IMAGE_FILE_HEADER
如下:
// 示例程序 IMAGE_FILE_HEADER
RVA Value Description
----------------------------------------------------
00000084 014C 机器类型
00000086 000F 节区数量
00000088 5D88E2A6 时间戳
0000008c 00012C00 符号表偏移
00000090 000004E4 符号数量
00000094 00E0 可选头大小
00000096 0107 文件属性
0001 IMAGE_FILE_RELOCS_STRIPPED
0002 IMAGE_FILE_EXECUTABLE_IMAGE
0004 IMAGE_FILE_LINE_NUMS_STRIPPED
0100 IMAGE_FILE_32BIT_MACHINE
IMAGE_OPTIONAL_HEADER
之所以IMAGE_OPTIONAL_HEADER
叫做可选头,是因为对于目标文件,它没有任何作用,只是平白增加了目标文件的大小;但对于映像文件来说,它提供了加载时必需的信息。定义如下:
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; /* +0018h 魔数 */
BYTE MajorLinkerVersion; /* +001ah 链接器主要版本号 */
BYTE MinorLinkerVersion; /* +001bh 链接器次要版本号 */
DWORD SizeOfCode; /* +001ch 所有含代码的节的总大小 */
DWORD SizeOfInitializedData; /* +0020h 所有含已初始化数据的节的总大小 */
DWORD SizeOfUninitializedData; /* +0024h 所有含未初始化数据的节的总大小 */
DWORD AddressOfEntryPoint; /* +0028h 程序入口点RVA */
DWORD BaseOfCode; /* +002ch 代码节起始RVA */
DWORD BaseOfData; /* +0030h 数据节起始RVA */
DWORD ImageBase; /* +0034h 映像文件加载时的首选地址 */
DWORD SectionAlignment; /* +0038h 内存中节对齐粒度*/
DWORD FileAlignment; /* +003ch 文件中节对齐粒度 */
WORD MajorOperatingSystemVersion; /* +0040h 操作系统主要版本号 */
WORD MinorOperatingSystemVersion; /* +0042h 操作系统次要版本号 */
WORD MajorImageVersion; /* +0044h 映像文件主要版本号 */
WORD MinorImageVersion; /* +0046h 映像文件次要版本号 */
WORD MajorSubsystemVersion; /* +0048h 子系统主要版本号 */
WORD MinorSubsystemVersion; /* +004ah 子系统次要版本号 */
DWORD Win32VersionValue; /* +004ch 保留。置0 */
DWORD SizeOfImage; /* +0050h 内存中映像文件的大小 */
DWORD SizeOfHeaders; /* +0054h 所有头+节表大小 */
DWORD CheckSum; /* +0058h 映像文件校验和 */
WORD Subsystem; /* +005ch 运行映像所需子系统 */
WORD DllCharacteristics; /* +005eh 映像文件的DLL属性 */
DWORD SizeOfStackReserve; /* +0060h 初始化时的保留的栈大小 */
DWORD SizeOfStackCommit; /* +0064h 初始化时实际提交的栈大小 */
DWORD SizeOfHeapReserve; /* +0068h 初始化时保留的堆大小 */
DWORD SizeOfHeapCommit; /* +006ch 初始化时实际提交的堆大小 */
DWORD LoaderFlags; /* +0070h 已废弃 */
DWORD NumberOfRvaAndSizes; /* +0074h 数据目录结构的数量 */
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; /* +0078h 指向数据目录中第一个 IMAGE_DATA_DIRECTORY 结构体的指针 */
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
Magic
单字。指明映像文件的类型。0x0107h
表示 ROM 映像;0x010B
表示 PE32;0x020B
表示 PE32+,即 64-bit 的 PE 文件。MajorLinkerVersion
字节。指定链接器主要版本号。MinorLinkerVersion
字节。指定链接器次要版本号。SizeOfCode
双字。所有包含代码的节的总大小。这里的大小指文件对齐后的大小。判断某个节是否包含代码的方法是根据节属性是否包含IMAGE_SCN_CNT_CODE
标志。SizeOfInitializedData
双字。所有包含已初始化数据节的总大小。SizeOfUninitializedData
双字。所有包含未初始化数据节的总大小。AddressOfEntryPoint
双字。入口点函数的指针相对于映像文件加载基址的偏移量。对于可执行文件,这是启动地址;对于设备驱动,这是初始化函数的地址;入口点函数对于 DLL 文件是可选的,如果不存在入口点,该成员必须置 0。BaseOfCode
双字。代码节的 RVA,代码节起始处相对于映像文件加载基址的偏移量。通常代码节紧跟在 PE 头 后面,节名为 ".text"。BaseOfData
双字。数据节的 RVA,数据节起始处相对于映像文件加载基址的偏移量。通常数据节位于文件末尾,节名为 ".data"。ImageBase
双字。映像文件加载时的优先载入地址,值必须是 64KB 的整数倍。应用程序的默认值是 0x00400000;DLL 的默认值是 0x10000000。当一个程序用到了多个 DLL 文件时,PE 加载器会调整 DLL 的载入地址,使所有 DLL 文件都能够被正确载入。SectionAlignment
双字。内存中的节对齐粒度。该成员的值必须不小于FileAlignment
成员的值。默认的值与系统的页大小相等。FileAlignment
双字。映像文件中原始数据的对齐粒度。值必须是在 512-64K 范围内的 2 的幂。默认值为512,但如果SectionAlignment
成员的值小于系统页大小,则FileAlignment
与SectionAlignment
两者成员的值必须相同。MajorOperatingSystemVersion
单字。操作系统主要版本号。MinorOperatingSystemVersion
单字。操作系统次要版本号。MajorImageVersion
单字。映像文件主要版本号。MinorImageVersion
单字。映像文件次要版本号。MajorSubsystemVersion
单字。子系统主要版本号。MinorSubsystemVersion
单字。子系统次要版本号。Win32VersionValue
双字。保留。置0。SizeOfImage
双字。映像文件在虚拟内存中所占的大小。值必须为SectionAlignment
的整数倍。SizeOfHeaders
双字。PE 文件头和所有节表大小的总和按照FileAlignment
对齐后的大小。第一节区在文件开始偏移为SizeOfHeaders
处。CheckSum
双字。映像文件的校验值。需要在装载时校验的文件有所有的驱动,任何在启动时装载的 DLL,以及任何加载到关键系统进程中的 DLL。Subsystem
单字。运行映像文件所需的子系统。已定义的子系统标志如下:
// Subsystem 标志
#define IMAGE_SUBSYSTEM_UNKNOWN 0 // 未知子系统
#define IMAGE_SUBSYSTEM_NATIVE 1 // 不需要子系统。设备驱动和本机系统进程
#define IMAGE_SUBSYSTEM_WINDOWS_GUI 2 // Windows 图形用户接口(GUI)子系统
#define IMAGE_SUBSYSTEM_WINDOWS_CUI 3 // Windows 字符模式用户接口子(CUI)系统
#define IMAGE_SUBSYSTEM_OS2_CUI 5 // OS/2 CUI 子系统
#define IMAGE_SUBSYSTEM_POSIX_CUI 7 // POSIX CUI 子系统
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 9 // Windows CE 系统
#define IMAGE_SUBSYSTEM_EFI_APPLICATION 10 // 可扩展固件接口(EFI)应用程序
#define IMAGE_SUBSYSTEM_EFI_BOOT_SERVEICE_DRIVER 11 // 带引导服务的 EFI 驱动程序
#define IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER 12 // 带运行时服务的 EFI 驱动程序
#define IMAGE_SUBSYSTEM_EFI_ROM 13 // EFI ROM 映像
#define IMAGE_SUBSYSTEM_XBOX 14 // XBOX 系统
#define IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION 16 // 引导应用程序
DllCharacteristics
单字。映像文件的 DLL 属性,以 bit OR 方式组合。各标志位的含义如下:
// DLL 属性标志
// 0x0001 0x0002 0x0004 0x0008 保留,值必须为 0。
#define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040 // DLL 可以在加载时重定位
#define IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 0x0080 // 强制实行代码完整性检验
#define IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100 // 映像兼容数据执行保护(DEP)
#define IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200 // 映像可以隔离,但不应该被隔离
#define IMAGE_DLLCHARACTERISTICS_NO_SEH 0x0400 // 映像不使用结构化异常处理(SEH)
#define IMAGE_DLLCHARACTERISTICS_NO_BIND 0x0800 // 不绑定映像
//#define IMAGE_DLLCHARACTERISTICS_APPCONTAINER 0x1000 // 在 32-bit 保留;64-bit 表示映像必须在 AppContainer 内执行
#define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER 0x2000 // WDM 驱动
//#define IMAGE_DLLCHARACTERISTICS_GUARD_CF 0x4000 // 在 32-bit 保留;64-bit 表示映像支持控制流保护
#define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0x8000 // 映像可用于终端服务器
SizeOfStackReserve
双字。初始化时保留的栈内存大小,默认值是 1MB。具体说是初始化时为栈保留的虚拟内存的大小,但并不是所有保留的虚拟内存都能直接作为栈使用。初始化时实际提交的栈大小由SizeOfStackCommit
成员指定。SizeOfStackCommit
双字。初始化时实际提交的栈内存大小。SizeOfHeapReserve
双字。初始化时保留的堆内存大小,默认值为 1MB。每一个进程至少为会有一个默认的进程堆,在进程启动的时候被创建,并且在进程的声明周期内不会被删除。SizeOfHeapCommit
双字。初始化时实际提交的堆内存大小,默认大小为 1 页。可以通过链接器的 "-heap" 参数指定起始保留的堆内存大小和实际提交的堆内存大小。LoaderFlags
成员已弃用。NumberOfRvaAndSizes
双字。数据目录结构的数量。通常为 0x00000010,即 16 个。DataDirectory
结构体。由IMAGE_DATA_DIRECTORY
结构体组成的数组,数组的每项都有被定义的值。结构体定义如下:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; /* 数据目录的 RVA */
DWORD Size; /* 数据目录的大小 */
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
各数组项如下:
// 数据目录
DataDirectory[0] = EXPORT Directory // 导入表 RVA 和大小
DataDirectory[1] = IMPORT Directory // 导入表 RVA 和大小
DataDirectory[2] = RESOURCE Directory // 资源表 RVA 和大小
DataDirectory[3] = EXCEPTION Directory // 异常表 RVA 和大小
DataDirectory[4] = CERTIFICATE Directory // 证书表 FOA 和大小
DataDirectory[5] = BASE RELOCATION Directory // 基址重定位表 RVA 和大小
DataDirectory[6] = DEBUG Directory // 调试信息 RVA 和大小
DataDirectory[7] = ARCH DATA Directory // 指定架构信息 RVA 和大小
DataDirectory[8] = GLOBALPTR Directory // 全局指针寄存器 RVA
DataDirectory[9] = TLS Directory // 线程私有存储表 RVA 和大小
DataDirectory[10] = LOAD CONFIG Directory // 加载配置表 RVA 和大小
DataDirectory[11] = BOUND IMPORT Directory // 绑定导入表 RVA 和大小
DataDirectory[12] = `IAT` Directory // 导入地址表 RVA 和大小
DataDirectory[13] = DELAY IMPORT Directory // 延迟导入描述符 RVA 和大小
DataDirectory[14] = CLR Directory // CLR数据 RVA 和大小
DataDirectory[15] = Reserverd // 保留
示例程序的 IMAGE_OPTIONAL_HEADER
如下图:
PE 数据主体
PE 数据主体包括 Section Header
和所有的节区。
Section Header
紧跟在可选头后面的是 Section Header
,也称作节表。PE 文件种所有节的属性都被定义在节表中。节表由一系列的 IMAGE_SECTION_HEADER
结构体组成,结构体大小均为 40 字节。每一个结构体描述一个节的信息,定义如下:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; /* 节区名 */
union {
DWORD PhysicalAddress; /* 物理地址 */
DWORD VirtualSize; /* 虚拟内存中节区大小 */
} Misc;
DWORD VirtualAddress; /* 虚拟内存中节区 RVA */
DWORD SizeOfRawData; /* 磁盘文件中节区大小 */
DWORD PointerToRawData; /* 磁盘文件中节区 FOA */
DWORD PointerToRelocations; /* 指向重定位表的指针 */
DWORD PointerToLinenumbers; /* 指向行号表的指针 */
WORD NumberOfRelocations; /* 重定位入口数量 */
WORD NumberOfLinenumbers; /* 行号数量 */
DWORD Characteristics; /* 节区属性 */
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
Name
节名称字符串。长度最多 8 个字节。Misc
PhysicalAddress
双字。文件地址。VirtualSize
双字。虚拟内存中的节区所占内存大小。VirtualAddress
双字。虚拟内存中节区 RVA。SizeOfRawData
双字。对于映像文件,表示磁盘上初始化数据的大小,值必须为FileAlignment
的整数倍;对于目标文件,表示节的大小。PointerToRawData
双字。磁盘文件中节区起始处的 FOA。值必须是FileAlignment
的整数倍。PointerToRelocations
双字。在对象文件中使用,指向重定位表的指针。PointerToLinenumbers
双字。行号信息位置(供调试用)。如果没有行号信息则置 0;同时因为不建议使用 COFF 调试信息,在映像文件中应置 0。NumberOfRelocations
单字。重定位入口的数量,在映像文件中置 0。NumberOfLinenumbers
单字。行号数量(供调试用)。因为不建议使用 COFF 调试信息,所以在映像文件中应置 0。Characteristics
双字。节区属性。,以 bit OR 方式组合。各标志位的含义如下:
// 节区属性
#define IMAGE_SCN_CNT_CODE 0x00000020 // 节区包含代码
#define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 // 节区包含已初始化数据
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 // 节区包含未初始化数据
#define IMAGE_SCN_ALIGN_1BYTES 0x00100000 // 1-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_2BYTES 0x00200000 // 2-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_4BYTES 0x00300000 // 4-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_8BYTES 0x00400000 // 8-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_16BYTES 0x00500000 // 16-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_32BYTES 0x00600000 // 32-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_64BYTES 0x00700000 // 64-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_128BYTES 0x00800000 // 128-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_256BYTES 0x00900000 // 256-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_512BYTES 0x00A00000 // 512-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_1024BYTES 0x00B00000 // 1024-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_2048BYTES 0x00C00000 // 2048-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_4096BYTES 0x00D00000 // 4096-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_8192BYTES 0x00E00000 // 8192-byte 对齐。仅用于目标文件
#define IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000 // 节区包含扩展的重定位项
#define IMAGE_SCN_MEM_DISCARDABLE 0x02000000 // 节区可根据需要丢弃,如 .reloc 在进程开始后被丢弃
#define IMAGE_SCN_MEM_NOT_CACHED 0x04000000 // 节区不会被缓存
#define IMAGE_SCN_MEM_NOT_PAGED 0x08000000 // 节区不可分页
#define IMAGE_SCN_MEM_SHARED 0x10000000 // 节区可共享给不同进程
#define IMAGE_SCN_MEM_EXECUTE 0x20000000 // 节区可作为代码执行
#define IMAGE_SCN_MEM_READ 0x40000000 // 节区可读
#define IMAGE_SCN_MEM_WRITE 0x80000000 // 节区可写
示例文件的节区头如下:
No. Name VirtualSize VirtualOffset RawSize RawOffset Characteristics
--------------------------------------------------------------------------
01 .text 00001670 00001000 00001800 00000400 60500020 R-X 包含可执行代码
02 .data 0000002C 00003000 00000200 00001C00 C0300040 RW- 包含已初始化数据
03 .rdata 00000168 00004000 00000600 00001E00 40300040 R-- 包含已初始化数据
04 .bss 00000450 00005000 00000000 00000000 C0700080 RW- 包含未初始化数据
05 .idata 00000564 00006000 00000600 00002400 C0300040 RW- 包含已初始化数据
06 .CRT 00000034 00007000 00000200 00002A00 C0300040 RW- 包含已初始化数据
07 .tls 00000020 00008000 00000200 00002C00 C0300040 RW- 包含已初始化数据
08 /4 000002D8 00009000 00000400 00002E00 42400040 R-- 包含已初始化数据
09 /19 0000A6D5 0000A000 0000A800 00003200 42100040 R-- 包含已初始化数据
0A /31 0000199E 00015000 00001A00 0000DA00 42100040 R-- 包含已初始化数据
0B /45 000018F3 00017000 00001A00 0000F400 42100040 R-- 包含已初始化数据
0C /57 00000780 00019000 00000800 00010E00 42300040 R-- 包含已初始化数据
0D /70 000002F2 0001A000 00000400 00011600 42100040 R-- 包含已初始化数据
0E /81 00000D1E 0001B000 00000E00 00012800 42100040 R-- 包含已初始化数据
0F /92 00000230 0001C000 00000400 00012C00 42100040 R-- 包含已初始化数据
Sections
紧跟在 Section Header
后面的就是各个 sections,即节区。PE 文件一般至少要求有两个节区,用于存储可执行数据的代码节区 .text,和存储数据的数据节区 .data。通过节区名可以猜测节区的用途,但节区名不是决定节区用途的因素,只作为一种参考。比如也可以将代码节区的节区名修改为 .data,对于程序执行不会有影响。这里讲一下常见节区的用途:
.text 默认的代码节区。用于保存可执行代码。
.data 默认的读/写数据节区。用于保存已初始化的全局变量,静态变量。
.rdata 默认的只读数据节区。
.idata 用于保存导入表信息。包含IAT, INT, 导入函数名称以及导入 DLL 名称等。
.edata 用于保存导出表信息。
.rsrc 用于保存资源表信息。
.bss 用于保存未初始化数据。
.tls 用于保存 TLS(线程局部存储)信息。
.reloc 用于保存重定位表信息。
其中有一些 Section 需要重点关注,比如保存着库文件导入相关数据的 .idata 节,或者与线程私有存储相关的 .tls 节等等。对这些重要节进行分析,就是之后学习的主要内容。