PE文件格式总结
时间:202106
基本介绍
PE文件主要由 文件头 和 区段(Section) 构成(如图所示),文件头记录相关信息,而区段则存放相关数据。相连代表数据在文件中存储紧接上一部分。
文件头部分包括:
(1) DOS Header
(2) DOS Stub
(3) NT Headers
(4) Section Headers
运行时PE文件由PE加载器加载到内存,文件头部分将会被加载到 内存基址(Image Base) 。但程序运行时一般会加载多个模块,如果都固定 Image Base 可能会导致内存使用冲突,所以PE文件会设置一个首选 Image Base,但实际的 Image Base 由 PE 加载器决定。因此,PE 文件中一般不使用绝对的内存地址,而采用相对于 Image Base 偏移的地址,称为 相对内存地址(Relative Virtual Address, RVA) ,其 绝对内存地址(Virtual Address, VA) 在加载后计算得到,关系如下:VA = ImageBase + RVA
区段头(Section Header) 中包括了该区段的重要信息,主要包括:区段数据的文件位置和大小、加载到内存时的 RVA 和占用内存大小、区段属性。PE加载器通过读取区段头的信息,将文件中的区段数据加载到对应的内存地址中。文件中的位置由相对文件第一个字节的偏移描述,称为 文件偏移地址(File Offset Address, FOA)。
1.DOS Header 和 DOS stub
DESC (Description) : 为兼容 ms-dos 系统而存在,DOS stub 为 DOS 根存程序。
FOA : 0x00000000
e_magic
DESC : 标识符, 必须为 ”MZ”, 0x5A4D。e_lfanew
DESC : 新文件头 NT headers 的 FOA 。
2.NT headers
DESC : PE 文件头。
FOA : IMAGE_DOS_HEADER.elfanew
Signature
DESC : 标识符, 必须为 ”PE”,0x00004550。
2.1File header
FOA : IMAGE_DOS_HEADER.elfanew + 0x04
Machine
DESC : 标识符,映像文件适用的机器类型。
值 | 描述 |
---|---|
0x014C | x86 处理器 |
0x0200 | Intel Itanium 处理器 |
0x8664 | x64 处理器 |
TimeDateStamp
DESC : 文件的创建时间,等于 1970/01/01 00:00:00 起经过的秒数。SizeOfOptionalHeader
DESC : Optional Header 的大小,值可以为 224 字节和 240 字节,分别表示 32 位和 64 位。该值可以作为 32 位/ 64 位的判断依据。Characteristics
DESC : 文件属性。
位 | 描述 |
---|---|
0x0001 | 重新定位信息已从文件中删除,文件必须在其首选基址处加载,如果基地址不可用,加载器会报错 |
0x0002 | 文件可执行 |
0x0004 | COFF line number 已经从文件中删除 |
0x0008 | COFF 符号表条目已经从文件中删除 |
0x0010 | 弃用 |
0x0020 | 应用程序可以处理大于 2GB 的地址 |
0x0080 | 保留 |
0x0100 | 计算机支持 32 位体系架构 |
0x0200 | 调试信息被移除并保存在另一个文件中 |
0x0400 | 如果此镜像文件在可移动介质上,将其复制到交换文件中并从中运行 |
0x0800 | 如果此镜像文件在网络介质上,将其复制到交换文件中并从中运行 |
0x1000 | 此镜像文件是系统文件 |
0x2000 | 此镜像文件是动态链接库 (DLL) |
0x4000 | 此文件只能运行于单处理器机器上 |
0x8000 | 保留 |
值未列出表明官方文档缺失,之后的表格也是如此
2.2 Optional header
FOA : IMAGE_DOS_HEADER.elfanew + 0x18
Magic
DESC : 标识符。
值 | 描述 |
---|---|
0x10B | 文件为 32 位程序的可执行映像 |
0x20B | 文件为 64 位程序的可执行映像 |
0x107 | 文件为 ROM 映像。 |
SizeOfCode
SizeOfInitializedData
SizeOfUninitializedData
DESC : 代码段、已初始化和未初始化的数据段的字节大小,如果存在多个段则为所有段大小之和。
每个 Section 在文件中的数据都为已初始化的数据,可以直接加载到内存中。
未初始化数据应该是用于减小文件的大小,具体是:如果文件中 Section 某位置一直到该 Section 结束时的数据初始值均为 0x00,就可以将该部分删去,而 Section 加载到内存时的实际大小设置不变,加载时仅加载 Section 在文件中有的部分,剩下的内存则由PE加载器填充 0x00,即由PE加载器完成初始化工作。
AddressOfEntryPoint
DESC : 程序入口点相对于 ImageBase 的偏移地址,即入口点的 RVA。ImageBase
DESC : 映像被加载到内存时第一个字节的首选基址, 值为 64KB 的倍数。Dll 默认为 0x10000000; 应用程序除了在 Windows CE 上为默认为 0x00010000,其他默认为 0x00400000。
当加载到内存后, PE装载器会将其修改为实际加载的基址。
SectionAlignment
DESC : 区段加载到内存时的对齐字节值,即加载到 SectionAlignment 倍数的内存地址处。该值必须不小于 FileAlignment,默认值为系统的页大小。因此内存中相邻 Section 间一般存在间隙。FileAlignment
DESC : 区段数据在映像文件中的对齐字节值。该值为 512 到 64K 之间 2 的幂(含边界值),默认值为 512 字节。因此每个区段末一般会有填充 0x00 的部分,且未被使用。SizeOfImage
DESC : 映像加载到内存后占用的总大小,值必须为 SectionAlignment 的倍数。SizeOfHeaders
DESC : 文件头的大小,为以下几个值的和,且最终向上舍入到 FileAlignment 的倍数。
a) IMAGE_DOS_HEADER.e_lfanew
b) 4字节的标识符
c) IMAGE_FILE_HEADER 的大小
d) 可选头的大小
e) 所有区段头的大小
CheckSum
DESC : 校验和,在以下文件加载时验证:所有驱动程序、任何引导时加载的 DLL 和任何加载到关键系统进程中的 DLL。Subsystem
DESC : 运行该映像所需要的子系统类型。
值 | 描述 |
---|---|
0 | 未知子系统 |
1 | 无子系统要求(设备驱动程序和本地系统进程) |
2 | Windows 图形用户界面 (GUI) 子系统 |
3 | Windows 字符模式用户界面 (CUI) 子系统 |
5 | OS/2 CUI 子系统 |
7 | POSIX CUI 子系统 |
9 | Windows CE 子系统 |
10 | 可扩展固件接口 (EFI) 应用程序 |
11 | 带引导服务的 EFI 驱动 |
12 | 带运行时服务的 EFI 驱动 |
13 | EFI ROM 映像 |
14 | Xbox 子系统 |
DllCharacteristics
DESC : Dll 属性。
位 | 描述 | |
32 位 | 64 位 | |
0x0001 | 保留 | |
0x0002 | 保留 | |
0x0004 | 保留 | |
0x0008 | 保留 | |
0x0020 | 具有64位地址空间的ASLR | 无文档信息 |
0x0040 | DLL 可以在加载时重新定位 | |
0x0080 | 强制进行代码完整性校验 | |
0x0100 | 映像与数据执行保护 (DEP) 兼容 | |
0x0200 | 映像可以隔离,但不应该隔离 | |
0x0400 | 映像不使用结构化异常处理 (SHE),无法在映像中调用任何处理程序 | |
0x0800 | 不绑定映像 | |
0x1000 | 映像应该在 AppContainer 中执行 | 保留 |
0x2000 | WDM 驱动 | |
0x4000 | 映像支持控制流保护 | 保留 |
0x8000 | 映像支持终端服务器 |
SizeOfStackReserve
SizeOfStackCommit
DESC : 栈保留大小和栈提交大小。栈在加载时仅使用 SizeOfStackCommit 大小的内存, 剩余的在达到栈保留大小前每次仅使用一页。SizeOfHeapReserve
SizeOfHeapCommit
DESC : 堆保留大小和堆提交大小。堆在加载时仅使用 SizeOfHeapCommit 大小的内存, 剩余的在达到堆保留大小前每次仅使用一页。DataDirectory
DESC : 目录表,为 IMAGE_DATA_DIRECTORY 结构体数组,共 16 个元素,描述相应表项的位置和大小信息。
IMAGE_DATA_DIRECTORY.VirtualAddress 为表项内容的 RVA。
表项如下:
索引值 | 描述 |
---|---|
0 | 导出表 |
1 | 导入表 |
2 | 资源目录 |
3 | 异常目录 |
4 | 安全目录 |
5 | 基本重定位表 |
6 | 调试信息目录 |
7 | 特殊结构数据表 |
8 | 全局指针寄存器相对虚拟地址 |
9 | 线程局部储存 (TLS) 表 |
10 | 加载配置表 |
11 | 绑定导入表 |
12 | 导入地址表 |
13 | 延迟导入描述 |
14 | CLR 头 |
15 | 保留 |
3.Section headers
DESC : 区段表,由 IMAGE_SECTION_HEADER 结构体数组构成,每个元素描述一个区段的信息,以 NULL 元素结束。
FOA : IMAGE_DOS_HEADER.elfanew + NT headers大小 (紧跟 NT headers)
Name
DESC : 区段名, 一个 8 字节、0x00 填充的 UTF-8 字符串。
如果字符串长度正好为 8 个字符,则不存在 ’\0’ 终止符。如果名称较长, 此成员值为 正斜杠 (/) + ASCII 表示的十进制数, 该十进制数为名称在字符串表中的偏移量。可执行映像不使用字符串表,也不支持长度超过 8 个字符的区段名。
Misc.VirtualSize
DESC : 加载到内存时整个区段的实际大小, 如果值大于 SizeOfRawData , 说明有未初始化的数据,这部分将由 PE 加载器完成初始化。
仅可执行映像有效,对于 obj 文件该值为 0。
VirtualAddress
DESC : 区段的 RVA。
对于 obj 文件该值为重定位前的地址。
SizeOfRawData
DESC : 区段在磁盘上已初始化数据的大小。值必须为 IMAGE_OPTIONAL_HEADER.FileAlignment 的倍数。如果该值小于 Misc.VirtualSize,区段剩余部分由 PE 加载器填充零,完成初始化。如果区段仅包含未初始化数据, 该值为 0。PointerToRawData
DESC : 区段数据的 FOA, 值必须为 IMAGE_OPTIONAL_HEADER.FileAlignment 的倍数。如果区段仅包含未初始化数据, 该值为 0。PointerToRelocations
PointerToLinenumbers
DESC : 区段的重定位项和行号项的文件指针。如果没有, 该值为 0。NumberOfRelocations
NumberOfLinenumbers
DESC: 重定位项数和行号项数。
可执行映像的重定位项数为 0。
Characteristics
DESC : 区段的属性。
值 | 描述 |
---|---|
0x00000000 | 保留 |
0x00000001 | 保留 |
0x00000002 | 保留 |
0x00000004 | 保留 |
0x00000008 | 弃用 |
0x00000010 | 保留 |
0x00000020 | 区段包含可执行代码 |
0x00000040 | 区段包含已初始化数据 |
0x00000080 | 区段包含未初始化数据 |
0x00000100 | 保留 |
0x00000200 | 区段包含注释或其他信息, 仅对 obj 文件有效 |
0x00000400 | 保留 |
0x00000800 | 区段不会成为映像一部分, 仅对 obj 文件有效 |
0x00001000 | 区段包含 COMDAT 数据, 仅对 obj 文件有效 |
0x00002000 | 保留 |
0x00004000 | 重置 TLB 表的风险异常处理位 |
0x00008000 | 区段包含通过全局指针引用的数据 |
0x00010000 | 保留 |
0x00020000 | 保留 |
0x00040000 | 保留 |
0x00080000 | 保留 |
0x00100000 | 按1字节对齐数据, 仅对 obj 文件有效 |
0x00200000 | 按2字节对齐数据, 仅对 obj 文件有效 |
0x00300000 | 按4字节对齐数据, 仅对 obj 文件有效 |
0x00400000 | 按8字节对齐数据, 仅对 obj 文件有效 |
0x00500000 | 按16字节对齐数据, 仅对 obj 文件有效 |
0x00600000 | 按32字节对齐数据, 仅对 obj 文件有效 |
0x00700000 | 按64字节对齐数据, 仅对 obj 文件有效 |
0x00800000 | 按128字节对齐数据, 仅对 obj 文件有效 |
0x00900000 | 按256字节对齐数据, 仅对 obj 文件有效 |
0x00A00000 | 按512字节对齐数据, 仅对 obj 文件有效 |
0x00B00000 | 按1024字节对齐数据, 仅对 obj 文件有效 |
0x00C00000 | 按2048字节对齐数据, 仅对 obj 文件有效 |
0x00D00000 | 按4096字节对齐数据, 仅对 obj 文件有效 |
0x00E00000 | 按8192字节对齐数据, 仅对 obj 文件有效 |
0x01000000 | 区段包含扩展重定位 |
0x02000000 | 可以根据需要丢弃该区段 |
0x04000000 | 区段不能被缓存 |
0x08000000 | 区段不能分页 |
0x10000000 | 区段在内存中可共享 |
0x20000000 | 区段可执行 |
0x40000000 | 区段可读 |
0x80000000 | 区段可写 |
4.Sections
DESC: 每个区段在文件中的数据(已初始化数据),大小为相应区段表项的 IMAGE_SECTION_HEADER. SizeOfRawData 值。
FOA : 区段表各项的 PointerToRawData 成员值。
区段在文件中的数据为已初始化数据,因为对齐要求必须为 IMAGE_OPTIONAL_HEADER. FileAlignment 的倍数,所以文件中区段末尾一般存在编译时填充的 0x00 字节,而 Section Header 中 Misc.VirtualSize 值通常没有包括这部分填充的初始化数据。但 Misc.VirtualSize 值还包括了未初始化数据大小,所以他们的值在不同情况下有不同大小关系。
IMAGE_SECTION_HEADER.SizeOfRawData = 已初始化数据(含填充数据)大小
IMAGE_SECTION_HEADER.Misc.VirtualSize = 已初始化数据(一般不含填充数据)大小 + 未初始化数据大小
其他
通过 RVA 确定其 FOA 的方法:
a) 分析 Section Headers 确定各区段数据的 RVA 范围和 FOA 范围。
b) 比较已知 RVA 和各区段 RVA 范围,确定已知 RVA 属于哪个区段。
c) 计算已知 RVA 相对于该区段起始 RVA 的偏移值 Offset。
d) 已知 RVA 对应的 FOA 为该区段起始 FOA 加上一步得到的 Offset。