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 节等等。对这些重要节进行分析,就是之后学习的主要内容。