Java 和 Python 字节码比 C/C++ 编译器生成的已编译机器码相对容易反编译。
我无法找到令人信服的答案来解释为什么 -g 选项中的信息不足以进行反编译,但足以进行调试?Python/Java 字节码中包含哪些使反编译变得容易的额外内容?
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
,因此对其字段的访问表示为添加和强制转换。
这是符号文件加载后的相同功能:
void __thiscall mytree::write_page(mytree *this, PAGE *src)
{
if ( src->isChanged )
{
cache::set_changed(this->cache, src->baseAddr);
src->isChanged = 0;
}
}
你可以看到它已经改进了很多。
至于为什么反编译字节码通常更容易,除了 NPE 的答案检查还有这个.
一些处理器,如 x86 处理器,具有可变长度的指令。如果控制传递到指令的中间(= 第一个字节之后的任何位置),那也可以是有效指令(或多条指令)。这使得明确反汇编机器代码变得困难。C/C++ 代码可以利用此功能。
在某些处理器和操作系统上,可以像执行代码一样执行数据,并像使用数据一样使用代码。这使得很难明确地将两者分开。而且,这也是 C/C++ 程序通常可以轻松完成的事情。
在某些处理器和操作系统上,动态生成代码并执行它很容易,并且可以在运行时修改现有代码。这也导致了反编译代码的歧义。C/C++ 程序通常也可以做到这一点。
编辑:另外,一些 CPU 对同一条指令有多种不同的编码。例如,x86 CPU 有 2 条指令mov reg, reg/mem
和mov reg/mem, reg
. 这些使您可以在寄存器和内存位置(在任一方向)以及两个寄存器之间移动数据。这两条指令都可用于在两个寄存器之间传输数据,但它们具有不同的编码。如果程序以某种方式依赖于特定的编码(例如,为了通过校验和验证其完整性),那么从反汇编中mov eax, ebx
您将无法分辨mov
它最初是两条指令中的哪一条,因此如果您尝试重新组装反汇编,你可能会破坏程序。
您可以使用调试器调试带有或不带有调试/符号信息的程序。这些信息只会使人类更容易导航代码和数据,因为可以使用它们的名称和类型来识别和显示许多(但不一定是所有)例程和变量,而不仅仅是原始地址和原始无类型数据。
我猜想各种字节码不那么模棱两可,而且它们的功能更受限制,这就是让反编译更容易的原因。