PE 格式图解 – 第一部分
PE 文件格式初学者教程,附插图
1. 图解教程
这计划是一个关于 PE 文件格式的图解教程。我看到许多教程都充斥着细节,涵盖了 PE 文件格式的所有不同可能性和分支 [9],结果变得难以理解。我们的计划是提供一个侧重于大局的概述教程,附带大量插图,如果读者对此主题感兴趣,以后可以到其他地方找到更多细节。
1.1. 所用工具 – 十六进制编辑器 ImHex
我正在使用十六进制编辑器 ImHex(免费软件),它能让我为文件的各个部分着色 ([1])。
1.2. 所用工具 – PE 查看器 “PE-bear”
PE-bear(免费软件)是一个非常有用的工具,可以直观地分析 PE 文件。根据文档,它不涵盖所有变体和类型的 PE 文件,但对于简单的文件,它是一个很棒的查看器/解析器/分析器。我认为使用可视化工具学习主题总是更容易。([2])
2. 可执行文件格式
在我们开始探索 PE 格式之前,让我们提一下 PE 格式属于“可执行文件格式”家族,这些格式在 [3] 中列出得很清楚,大约有 40 种,适用于不同的操作系统。让我们提一下其中几个最流行的:
- MZ – 用于 DOS 和 Windows,扩展名 .exe
- COFF - 用于 UNIX/Linux 类系统,无扩展名
- ELF - 用于 UNIX/Linux 类系统,无扩展名或扩展名 .elf
- Mach-O – 用于 macOS 和 iOS,无扩展名
- PE - 用于 Windows,扩展名 .exe, .dll, .sys 等。
- PE32+ - 用于 64 位 Windows,扩展名 .exe
有关 PE 和 PE32+ 格式的更多详细信息,请参阅 [4]。
3. PE 文件格式的历史
PE 代表“Portable Executable”(可移植可执行文件),这种格式发明于 20 世纪 80 年代。当时主流的格式是 MZ MS-DOS 格式,它在文件开头有一个特殊的标记来标识自身,即字母“MZ”,顺便说一下,这是 MS-DOS 开发者之一 Mark Zbikowski 的首字母缩写。PE 格式旨在针对 Windows 平台,它们保留了与 MZ 格式(.exe 文件)的向后兼容性,并使得 PE 格式(.exe 文件)如果在 MS-DOS 上意外运行,会报告“此程序无法在 DOS 模式下运行”,这在当时是微软的一个重要问题。因此,您仍然会看到 PE 格式包含 MS-DOS 风格的头,这意味着它以魔术字母“MZ”开头,并且还有一个打印该消息的 DOS 存根。这部分在今天是不必要的,但已成为标准的一部分。
PE 格式源自 Unix COFF 格式。
如今,PE 格式已扩展以承载 .NET 代码。
4. 技术细节
4.1. PE 旨在解决的问题
- 旨在支持不同硬件上的不同程序
- 目的是将程序与处理器分离
- 应对向 64 位处理器的转变
4.2. 将 PE 文件加载到内存中
- PE 文件字节在磁盘上的物理布局与它们加载到内存中的方式不同
- PE 文件包含多个节,为避免浪费空间,它们在磁盘上一个接一个地对齐
- PE 文件包含多个节,包含数据和程序,每个节都被加载到内存的一个独立段中
- 其中一个原因是,每个节都需要对齐到页面边界
- 然后,每个节可以分配不同的内存保护,通常程序节会获得执行/只读保护,数据节会获得不可执行/读写保护。
- 因此,文件中的大多数地址/偏移量都使用相对虚拟地址 (RVA) 来指定。这指定了从每个节开头开始的偏移量
这是一张图片,说明了不同节在磁盘上“原始对齐”时的样子,以及它们如何加载到内存(“虚拟对齐”)中,进入不同的虚拟地址,从而形成新的地址方案。
4.3. “原始地址”与“虚拟地址”之间的相互转换
- 经常出现的任务是使用相对虚拟地址 (RVA) 将“原始地址”(字节在文件中的对齐方式)转换为“虚拟地址”(字节在内存中的对齐方式),以及反向转换。
- 例如,在上图中,您可以看到 .text 节从 0x200(原始)开始,到 0xF32(原始)结束。这意味着,文件末尾相对于节开头的偏移量为 0xF32(原始)-0x200(原始)=0xD32。当 .text 节映射到内存地址 0x2000(虚拟)时,节的末尾地址为 0x2000+0xD32=0x2D32(RVA)。我们说节末尾的地址是 0x2D32(RVA)。
5. 示例程序
我们的演示将使用一个简单的 C#11/.NET-7“Hello World”程序,我们为字符串“Hello World!”创建了资源文件。如您所见,.NET 程序集被打包成 PE 文件格式。我们将其编译为 C#11/.NET7。
6. PE 格式定义
PE 格式的精确定义可以在 [5] 中找到。为了本教程的目的,并遵循 PE-bear 工具的概述,我们在此将其定义为:
一个典型的 PE 文件由以下部分组成:
- DOS 头(又称“MZ 头”)
- DOS 存根
- NT 头(又称“PE 文件头”)
它本身包含:- PE 签名
- 文件头(又称“COFF 头”,“图像文件头”)
- 可选头(又称“图像可选头”)
它本身包含:- 通用部分
- 数据目录
- 节头(又称“节表”)
- 多个节(又称“节”)
- 第1节
- 第2节
- …
- 节 n
以下是十六进制编辑器中头的样子,重点关注头
以下是整个文件的样子,只是为了了解头在文件中所占的比例(数量上)。
PE-bear 工具在其界面中概述了它
7. DOS 头(又称“MZ 头”)
这是十六进制编辑器中的 DOS 头
这是工具 PE-bear 对 DOS 头的分析
解释
- 请注意,头以“魔术数字”0x5A4D 开头,它代表“MZ”(顺便提一下,这是 MS-DOS 开发者之一 Mark Zbikowski 的首字母缩写),它作为文件格式标识符
- 请注意,在偏移量 0x3C 处,是新 exe 头的地址,即 0x80,指向“NT 头”
8. DOS 存根
这是十六进制编辑器中的 DOS 存根
解释
- 这是一小段与 DOS 兼容的代码,它只是打印一条错误消息“此程序无法在 DOS 模式下运行”,以防程序在 DOS 下运行。
9. NT 头(又称“PE 文件头”)
这是十六进制编辑器中的 NT 头
9.1. NT 头 - 签名
解释
- 这只是以“PE”开头的 4 个字节,表示这是 PE 格式。
9.2. NT 头 - 文件头(又称“COFF 头”、“图像文件头”)
这是工具 PE-bear 对文件头的分析
解释
- 请注意,在偏移量 0x88 处,它表明此文件将包含 3 个节。对于固定大小的节头,操作系统可以计算节头的大小以及要在节头中查找的条目数量。
- 请注意,在偏移量 0x94 处,它表示可选头的大小。
9.3. NT 头 - 可选头(又称“图像可选头”)
这是工具 PE-bear 对可选头的分析
解释
- 此头包含一些超出基本文件头中包含的基本信息的附加信息
- 查看文件偏移量 0x98,即所谓的魔术字。它实际上说明了文件类型。0x10B 代表 PE32 格式。
- 查看文件偏移量 0xA8 处的入口点很有趣。它显示地址 0x2D32 (RVA),我们需要将其转换为原始地址,即 0x2D32-0x2000+0x200=0xF32 (原始文件)。这在.text 节区域。入口点的地址是 PE 加载器将开始执行的地址。对于程序图像,这是起始地址。
9.3.1. NT 头 - 可选头 - 数据目录
解释
- 如果您查看文件偏移量 0x100 处的导入目录,它显示导入目录位于地址 0x2CDD(RVA),大小为 0x4F 字节。正如我们稍后将看到的,这是.text节。我们需要将 RVA 地址转换为原始偏移量,即 0x2CDD-0x2000+0x200=0xEDD(原始文件)。这在名为.text节的区域。这里发生的事情有点复杂,有一个表充当其他条目的目录,详细信息请参阅[5]。但关键是,最终您会得到导入依赖项,在本例中是“mscoree.dll, _CorExeMain”。您可以在下图中看到它
- PE-bear 工具提供了很好的解释
- 如果您查看文件偏移量 0x128 处的调试目录,它显示地址为 0x2BD4(RVA)。它与上述情况类似地解析,并且该信息同样保存在.text节中。同样,0x2BD4-0x2000+0x200=0xDD4(原始文件)。
这是十六进制编辑器中的样子PE-bear 工具提供了很好的解释
- 如果您查看文件偏移量 0x168 处的.NET 头,它显示地址为 0x2008(RVA)。我们不会在本教程中分析它,但计划为它准备其他教程。
- 如果您查看文件偏移量 0x108 处的资源目录,它显示地址为 0x4000(RVA)。这是.rsrc节的区域。现在我们计算 0x4000-0x4000+0x1000=0x1000(原始文件)。我们将在下面的节章中检查它。
- 如果您查看文件偏移量 0x120 处的基址重定位表,它显示地址为 0x6000(RVA)。这是.reloc节的区域。现在我们计算 0x6000-0x6000+0x1600=0x1600(原始文件)。我们将在下面的节章中检查它。
10. 节头(又称“节表”)
这是十六进制编辑器中的节头
这是工具 PE-bear 对节头的分析
11. 多个节(又称“节”)
11.1. 节名称
节可以有任何以“.”开头的 8 个字符的名称。但通常的约定是:
- .text – 包含可执行代码和数据
- .idata, .rdata – 包含导入 API
- .data, .bss– 包含数据
- .pdata – 包含异常信息
- .reloc – 包含重定位信息
- .rsrc – 包含资源
- .debug – 包含调试信息
11.2. .text 节
这是十六进制编辑器中的.text节
解释
- 在我们的例子中,由于这是一个 .NET 程序集,此节包含:
- 元数据
- 托管资源
- IL 代码
11.3. .rsrc 节
这是十六进制编辑器中的.rsrc节
这是工具 PE-bear 对.rsrc节的分析
解释
- 您可以从十六进制编辑器和 PE-bear 的分析中看到(这里看起来像是一个未完成的应用程序?),此节包含有关版本和应用程序清单的信息。
- 这些是非托管资源。托管 .NET 资源位于.text节内。
11.4. .reloc 节
这是十六进制编辑器中的.reloc节
这是工具 PE-bear 对.reloc节的分析
12. 结论
为了使本教程大小适中,我们将在此处结束。我们对 PE 格式进行了基本描述,足以提供良好的技术概述,感兴趣的读者可以在其他地方找到更多细节。本文没有深入探讨太多细节。
有关 PE 文件格式的更多详细信息,请参见 [6]、[7]、[8]。一个非常有趣的说明 PE 格式不同选项的幻灯片可以在 [9] 中找到。
13. 参考文献
- [1] https://github.com/WerWolv/ImHex/releases/tag/v1.27.0
- [2] https://hshrzd.wordpress.com/pe-bear/
- [3] https://en.wikipedia.org/wiki/Comparison_of_executable_file_formats
- [4] https://en.wikipedia.org/wiki/Portable_Executable
- [5] https://learn.microsoft.com/en-us/windows/win32/debug/pe-format?redirectedfrom=MSDN
- [6] https://tech-zealots.com/malware-analysis/pe-portable-executable-structure-malware-analysis-part-2/
- [7] https://resources.infosecinstitute.com/topic/2-malware-researchers-handbook-demystifying-pe-file/
- [8] https://www.red-gate.com/simple-talk/blogs/anatomy-of-a-net-assembly-pe-headers/
- [9] http://2.bp.blogspot.com/-SpKCuFfVJSU/UCL5rJhQ5AI/AAAAAAAAFjo/3TcOoqu-7X4/s1600/AwO4ffCCIAAdANF.png
14. 历史
- 2023年3月10日:初始版本