您无法恢复原始源代码 - 编译过程本质上是有损的,一些细节不可避免地会丢失。损失多少将取决于源语言、目标语言和开发人员所做的选择。
让我们从简单的案例开始 - 一种编译成自己的字节码的高级语言。例如,Python 到 .pyc,C# 到 .NET IL (.dll),Java 到 .class/.dex。在这些示例中的每一个中,字节码都包含语言中高级概念的直接表示,例如类、方法、虚函数调用、类布局等。反编译器可以从编译后的代码中恢复出惊人的准确源代码。
这是一个 Python 的简短示例。原文出处:
class MyClass:
def function(self, a, b):
print("Hello, world:", a, b)
MyClass().function("test", 1234.5678)
使用 Python 3.6 编译,再次使用 uncompyle6 反编译:
# uncompyle6 version 3.3.5
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.4 (v3.6.4:d48ecebad5, Dec 18 2017, 21:07:28)
# [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)]
# Embedded file name: /private/tmp/test.py
# Compiled at: 2019-12-23 16:34:01
# Size of source mod 2**32: 121 bytes
class MyClass:
def function(self, a, b):
print('Hello, world:', a, b)
MyClass().function('test', 1234.5678)
# okay decompiling __pycache__/test.cpython-36.pyc
除了一些额外的 cmets 和空格外,输出基本上是 1:1 与原始的。 Java 和 C# 同样容易反编译。许多游戏是用 Java(例如 Android)和 C#(例如 Unity)编写的,并且有很多改装者/黑客使用反编译器来获取用这些语言编写的游戏的可用源代码。
开发人员可以选择通过使用混淆来防御反编译器,他们故意以某种方式破坏编译的输出(例如,将变量/函数/类重命名为乱码名称)以使这种类型的逆向工程更难。
更难的情况是当您获取代码并将其一直编译为机器代码(直接在 CPU 上运行的代码)时。默认情况下,Rust、Go、C++、Swift 等语言都直接编译为机器码。 CPU 指令不与高级语言中的概念一一对应。现在,有反编译器——美国国家安全局最近开源的 Ghidra 反编译器是最好的反编译器之一——但它们只能为你提供原始源代码的非常粗略的近似值,并且大多数只能反编译为 C(不是一直到 Rust /Go/C++/Swift/等)。这是一个简单的 C++ 程序:
#include <iostream>
class MyClass {
public:
void function(const char *a, const double b) {
std::cout << "Hello, world: " << a << " " << b << std::endl;
}
};
int main() {
MyClass m;
m.function("test", 1234.5678);
}
以下是 Ghidra 9.1 的反编译方式:
// MyClass::function(char const*, double)
void __thiscall MyClass::function(MyClass *this,char *param_1,double param_2)
{
char cVar1;
basic_ostream *pbVar2;
size_t sVar3;
long *plVar4;
long *plVar5;
undefined local_20 [8];
pbVar2 = std::__1::__put_character_sequence<char,std--__1--char_traits<char>>
((basic_ostream *)__ZNSt3__14coutE,"Hello, world: ",0xe);
sVar3 = __stubs::_strlen(param_1);
pbVar2 = std::__1::__put_character_sequence<char,std--__1--char_traits<char>>
(pbVar2,param_1,sVar3);
pbVar2 = std::__1::__put_character_sequence<char,std--__1--char_traits<char>>(pbVar2," ",1);
plVar4 = (long *)__stubs::__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEd(param_2,pbVar2);
__stubs::__ZNKSt3__18ios_base6getlocEv(local_20,*(long *)(*plVar4 + -0x18) + (long)plVar4);
plVar5 = (long *)__stubs::__ZNKSt3__16locale9use_facetERNS0_2idE(local_20,__ZNSt3__15ctypeIcE2idE)
;
cVar1 = (**(code **)(*plVar5 + 0x38))(plVar5,10);
__stubs::__ZNSt3__16localeD1Ev(local_20);
__stubs::__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE3putEc(plVar4,(ulong)(uint)(int)cVar1);
__stubs::__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE5flushEv(plVar4);
return;
}
undefined8 entry(void)
{
MyClass local_10 [8];
MyClass::function(local_10,"test",1234.56780000);
return 0;
}
一个有经验的逆向工程师可以理解这一点 - 但它远没有那么好。
所以你有它。如果您正在对编译为本机 CPU 代码的程序进行逆向工程,您可以获得源代码,但它会非常粗糙。如果您正在对编译为一些中间字节码的程序进行逆向工程,那么您将有更好的时间。在所有情况下,您都无法获得确切的原始源代码,但您可能能够非常接近。