65.9K
CodeProject 正在变化。 阅读更多。
Home

PE 格式图解 – 第一部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (8投票s)

2023年3月10日

CPOL

9分钟阅读

viewsIcon

11702

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 文件由以下部分组成:

  1. DOS 头(又称“MZ 头”)
  2. DOS 存根
  3. NT 头(又称“PE 文件头”)
    它本身包含:
    1. PE 签名
    2. 文件头(又称“COFF 头”,“图像文件头”)
    3. 可选头(又称“图像可选头”)
      它本身包含:
      1. 通用部分
      2. 数据目录
  4. 节头(又称“节表”)
  5. 多个节(又称“节”)
    1. 第1节
    2. 第2节
    3. 节 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 程序集,此节包含:
    1. 元数据
    2. 托管资源
    3. 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. 参考文献

14. 历史

  • 2023年3月10日:初始版本
© . All rights reserved.