为什么反汇编本机 Win32 映像(例如内置 C/C++)的过程比反汇编 .NET 应用程序困难得多?
主要原因是什么?因为什么?
.net 程序集内置于Common Intermediate Language 中。当 CLR 编译它以在适当的系统上运行时,它不会被编译,直到它即将被执行。CIL 有很多元数据,因此它可以编译到不同的处理器架构和不同的操作系统上(在 Linux 上,使用 Mono)。类和方法基本保持不变。
.net 还允许反射,这需要将元数据存储在二进制文件中。
C 和 C++ 代码在编译时被编译为选定的处理器架构和系统。为 Windows 编译的可执行文件不能在 Linux 上运行,反之亦然。C 或 C++ 编译器的输出是汇编指令。源代码中的函数可能不作为二进制函数存在,但以某种方式进行了优化。编译器也可以有相当激进的优化器,它们将采用逻辑结构化的代码并使其看起来非常不同。代码将更有效(在时间或空间上),但会使逆向变得更加困难。
由于 .NET 的实现允许通过 CLI 和 CLR 实现 C#、VB 甚至 C/C++ 等语言之间的互操作性,这意味着必须将额外的元数据放入对象文件中以正确传输类和对象属性。这使得反汇编更容易,因为二进制对象仍然包含该信息,而 C/C++ 可以丢弃该信息,因为它不是必需的(至少对于代码的执行,当然在编译时仍然需要该信息)。
此信息通常仅限于与类相关的字段和对象。分配在堆栈上的变量在发布版本中可能没有注释,因为互操作性不需要它们的信息。
另一个原因 - 大多数 C++ 编译器在生成最终二进制文件时执行的优化不是在 IL 级别对托管代码执行的。
因此,与在 IL 中具有有意义名称的函数调用相比,容器上的迭代之类的东西看起来像是本机代码的耦合inc
/jnc
汇编指令。生成的执行代码可能与 JIT 编译器将内联一些类似于本机编译器的调用相同(或至少接近),但可以查看的代码在 CLR 领域更具可读性。
人们提到了一些原因;我会提到另一个,假设我们谈论的是反汇编而不是反编译。
x86 代码的问题在于区分代码和数据非常困难且容易出错。反汇编者必须依靠猜测才能正确完成,而且他们几乎总是会错过一些东西;相比之下,中间语言被设计为“反汇编”(以便 JIT 编译器可以将“反汇编”转换为机器代码),因此它们不会像您在机器代码中发现的那样包含歧义。最终结果是 IL 代码的反汇编非常简单。
如果您在谈论反编译,那是另一回事;它与(主要)缺乏对 .NET 应用程序的优化有关。大多数优化是由 JIT 编译器完成的,而不是 C#/VB.NET/etc。编译器,所以汇编代码几乎是源代码的 1:1 匹配,所以找出原始代码是很有可能的。但是对于本机代码,有上百万种不同的方式来翻译少量的源代码行(哎呀,即使是无操作也有无数种不同的编写方式,具有不同的性能特征!)所以很难弄清楚原始代码是什么.
在一般情况下,反汇编 C++ 和 .NET 代码没有太大区别。当然,C++ 更难反汇编,因为它做了更多的优化和类似的事情,但这不是主要问题。
主要问题是名称。反汇编的 C++ 代码将包含名为 A、B、C、D、...A1 等的所有内容。除非您可以识别这种格式的算法,否则您可以从反汇编的 C++ 二进制文件中提取的信息不多。
另一端的 .NET 库中包含方法名称、方法参数、类名称和类字段名称。它使反汇编代码的理解更加容易。所有其他的东西都是次要的。