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

PE 格式图解 – 第二部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2023 年 3 月 10 日

CPOL

4分钟阅读

viewsIcon

6688

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 个流中。每个流的名称都以 `#` 开头。这些流是:

  1. `#~ stream` – 这是“元数据流”,包含有关程序集中的类型、方法、字段、属性和事件的信息。
  2. `#Strings stream` – 包含命名空间、类型和成员的名称
  3. `#US stream` – 这是“用户字符串堆”,包含代码中使用的所有 `string`
  4. `#GUID stream` – 存储程序集中使用的 GUID
  5. `#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. 参考文献

历史

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