PE 格式图解 – 第二部分
PE 格式在 .NET 程序集上的初学者教程
1. 另一篇文章的续篇
本文是另一篇文章的续篇
1.1. 使用的工具 - 十六进制编辑器 ImHex
我使用的是 ImHex 十六进制编辑器(免费软件),它允许我给文件中的节着色([1])。
1.2. 使用的工具 - PE 查看器“PE-bear”
PE-bear(免费软件)是一个非常有用的可视化分析 PE 文件的工具。根据文档,它并不涵盖 PE 文件所有不同变体和类型,但对于简单的文件来说,它是一个很棒的查看器/解析器/分析器。我认为使用可视化工具学习主题总是更容易。([2])
1.3. 使用的工具 - 程序集查看器
该项目 [3] 提供了一个出色的“.NET 程序集查看器”(免费软件)。
1.4. 使用的工具 - CFF Explorer
另一个 PE 文件浏览器“CFF Explorer”(免费软件)可以在 [4] 找到。
2. .NET 头(又名“COR20 头”、“CLI 头”)
正如我们在上一篇文章 [11] 中看到的,PE-bear 工具显示的“NT Headers - Optional Header – Data Dictionary”如下所示
如果你查看文件偏移量 0x168,对于 **.NET Header**,它在地址 0x2008 (RVA) 处。再次计算,0x2008-0x2000+0x200= 0x208 (文件原始偏移)。大小为 0x48。
这是它在十六进制编辑器中的样子
这是 PE-bear 的解释
这是程序集查看器的解释
3. 资源(又名“托管资源”)
它显示 `ResourceDir` 是 0x2AFC (RVA),大小为 0xD8。再次计算,0x2AFC-0x2000+0x200= 0xCFC (文件原始偏移)。结束位置是 0xCFC+0xD8= 0xDD4 (文件原始偏移)。
这是十六进制编辑器
正如你所见,我们将 C# 代码资源中的 `string` “`HW`” 映射到了 “`Hello World!`” `string`。
4. 元数据
4.1. 简短理论背景
.NET 程序集中的元数据组织在 5 个流中。每个流的名称都以 `#` 开头。这些流是:
- `#~ stream` – 这是“元数据流”,包含有关程序集中的类型、方法、字段、属性和事件的信息。
- `#Strings stream` – 包含命名空间、类型和成员的名称
- `#US stream` – 这是“用户字符串堆”,包含代码中使用的所有 `string`
- `#GUID stream` – 存储程序集中使用的 GUID
- `#Blob stream` – 包含纯二进制数据
4.2. 文件内容
如上所示,在文件偏移量 0x210 处,对于 **MetaData**,它显示地址为 0x211C (RVA)。再次计算,0x211C-0x2000+0x200= 0x31C (文件原始偏移)。这是元数据头的所在地。可以在十六进制编辑器中看到。
这是程序集查看器的解释
所以,我们看到元数据包含在 5 个 `stream` 中。这里的偏移量是“相对于元数据头(0x31C)的开始位置”给出的。我们需要进行一些计算
- `Stream` `#Strings` 从 0x31C+0x3D0= 0x6EC (文件原始偏移) 开始。它结束于 0x6EC+0x410= 0xAFC (文件原始偏移)。
- `Stream` `#Blob` 从 0x31C+8280x= 0xB44 (文件原始偏移) 开始。它结束于 0xB44+0x1B8= 0xCFC (文件原始偏移)。
- `Stream` `#GUID` 从 0x31C+0x818= 0xB34 (文件原始偏移) 开始。它结束于 0xB34+0x10= 0xB44 (文件原始偏移)。
- `Stream` `#US` 从 0x31C+0x7E0= 0xAFC (文件原始偏移) 开始。它结束于 0xAFC+0x38= 0xB34 (文件原始偏移)。
- `Stream` `#~` 从 0x31C+0x6C= 0x388 (文件原始偏移) 开始。它结束于 0x388+0x364= 0x6EC (文件原始偏移)。
我们不会深入解析每个 `stream` 的内容,只是看看程序集查看器的解释。
`Stream #~` 是特殊的,所以它以不同的方式呈现
5. 方法体
以上就是元数据。我们的方法体,也就是 IL,在哪里?
为此,我们需要解析元数据。我们不会手动进行,而是使用工具来查看其解释。
让我们看看 CFF Explorer 的解释。
因此,我们可以看到元数据 `stream #~` 包含一个包含方法的文件夹,对于 `Main` 方法,我们看到偏移量 0x2069 (RVA),这是我们方法代码的起点。再次计算,0x2069-0x2000+0x200= 0x269 (文件原始偏移) 在 `.text` 节中的位置。
所以,方法体存储在 .NET 头和元数据头之间。在我们的文件中,这是区域 0x250 (文件原始偏移)-0x31B (文件原始偏移)。
6. .text 节概述
所以,这是大局,我们 .NET 程序集打包在我们文件 `.text` 节中的概览。
7. 结论
本文是文章 [11] 的续篇。我们在此概述了 .NET 程序集如何打包到 PE 文件格式中。我们试图用图文并茂的说明来解释,并没有深入研究字节打包的所有细节,而是依赖工具来解析和解释数据。
感兴趣的读者可以在文章 [6]-[9] 中找到更多详细信息。
8. 参考文献
- [1] https://github.com/WerWolv/ImHex/releases/tag/v1.27.0
- [2] https://hshrzd.wordpress.com/pe-bear/
- [3] https://codeproject.org.cn/Articles/3262/A-NET-assembly-viewer
- [4] https://ntcore.com/?page_id=388
- [5] https://learn.microsoft.com/en-us/windows/win32/debug/pe-format?redirectedfrom=MSDN
- [6] https://www.red-gate.com/simple-talk/blogs/anatomy-of-a-net-assembly-pe-headers/
- [7] https://www.red-gate.com/simple-talk/blogs/anatomy-of-a-net-assembly-clr-metadata-1/
- [8] https://www.red-gate.com/simple-talk/blogs/anatomy-of-a-net-assembly-clr-metadata-2/
- [9] https://www.red-gate.com/simple-talk/blogs/anatomy-of-a-net-assembly-clr-metadata-3/
- [10] https://www.red-gate.com/simple-talk/blogs/anatomy-of-a-net-assembly-methods/
- [11] https://codeproject.org.cn/Articles/5356568/PE-Format-Illustrated-Part-1
历史
- 2023 年 3 月 10 日:初始版本