用调试信息反编译C代码?

Java和Python字节代码比C / C ++编译器生成的编译机器代码相对容易反编译。

我无法找到一个令人信服的答案,为什么来自-g选项的信息不足以进行反编译,但足以进行调试? Python / Java字节代码中包含的额外内容是什么,这使得反编译变得容易?

我无法找到一个令人信服的答案,为什么来自-g选项的信息不足以进行反编译,但足以进行调试?

调试信息基本上仅包含生成的代码中的地址与源文件行号之间的映射。 调试器不需要反编译代码 – 它只显示原始源代码。 如果缺少源文件,调试器将不会神奇地显示它们。

也就是说,调试信息的存在确实使反编译更容易。 如果调试信息包括所使用类型和函数原型的布局,则反编译器可以使用它并提供更精确的反编译。 但是,在许多情况下,它仍然可能与原始来源不同。

例如,这是一个使用Hex-Rays反编译器反编译的函数,而不使用调试信息:

int __stdcall sub_4050A0(int a1) { int result; // eax@1 result = a1; if ( *(_BYTE *)(a1 + 12) ) { result = sub_404600(*(_DWORD *)a1); *(_BYTE *)(a1 + 12) = 0; } return result; } 

由于它不知道a1的类型,因此对其字段的访问表示为添加和强制转换。

加载符号文件后,这里的function相同:

 void __thiscall mytree::write_page(mytree *this, PAGE *src) { if ( src->isChanged ) { cache::set_changed(this->cache, src->baseAddr); src->isChanged = 0; } } 

你可以看到它已经得到了很大改善。

至于为什么反编译字节码通常比较简单,除了NPE的答案也要检查。

以下是一些原因:

  1. Java和Python字节码相对简单且高级,而某些CPU的指令集(想想x86)非常复杂。
  2. 字节码非常类似于他们设计的语言结构。
  3. 生成字节码时,Java和Python的执行很少通过优化。 这导致字节码与原始源代码的结构紧密对应。 一个好的优化C或C ++编译器能够生成远离原始源代码的程序集。
  4. 很少有Java和Python编译器,以及许多C和C ++编译器。 如果要定位单个已知编译器(或一小组已知编译器),则更容易生成高质量的反编译器。
  5. 与C ++相比,Python和Java是相对简单的语言(这一点不适用于C)。
  6. C ++模板对质量反编译提出了许多挑战(这一点也不适用于C)。
  7. C / C ++预处理器。
  8. 在Python中,源文件和字节码文件之间存在一对一的关系。 在Java中,关系是一个或多个字节码文件的一个源。 在C和C ++中,关系是多对多的,在源头上有很多重叠(想想标题)。

某些处理器(如x86处理器)具有可变长度的指令。 如果控制被传递到指令的中间(=第一个字节之后的任何地方),那么它也可以是有效指令(或几个指令)。 这使得很难明确地反汇编机器代码。 C / C ++代码可以利用此function。

在某些处理器和操作系统上,可以像执行代码一样执行数据,并像使用数据一样使用代码。 这使得很难明确地将两者分开。 而且,这也是C / C ++程序通常可以轻松完成的事情。

在某些处理器和操作系统上,可以轻松生成代码并执行它,并且可以在运行时修改现有代码。 这也会导致反编译代码的模糊性。 而且C / C ++程序通常也可以这样做。

编辑 :此外,一些CPU对同一指令有多种不同的编码。 例如,x86 CPU有2个指令mov reg, reg/memmov reg/mem, reg 。 这些允许您在寄存器和存储器位置(在任一方向上)和两个寄存器之间移动数据。 这两条指令都可用于在两个寄存器之间传输数据,但它们具有不同的编码。 如果程序以某种方式依赖于特定的编码(例如,为了通过校验和validation其完整性),那么从反汇编如mov eax, ebx你将无法分辨它最初的两个mov指令中的哪一个,所以如果您尝试重新组装反汇编,则可能会破坏程序。

您可以使用调试器调试带有或不带调试/符号信息的程序。 该信息仅使人类更容易导航代码和数据,因为可以使用它们的名称和类型来识别和显示许多(但不一定是所有)例程和变量,而不仅仅是原始地址和原始无类型数据。

我猜测各种字节码不那么含糊,而且在他们能做的事情上受到更多的限制,这就是让它们更容易反编译的原因。