你可以将游戏反编译为它的原始源代码吗?

nov*_*s12 3 windows exe decompiler

我以战地4为例,这可以用于任何游戏.

我一直想知道这样的事情是否可行:

由于BF4正在运行客户端,这意味着您拥有构成游戏的所有代码.

它在技术上是否可以反编译代码并查看它的来源?

一直到游戏的核心机制?

或者是否有某种加密保护它?

我确实意识到,如果你成功地反编译这样的东西,那将是一个混乱处理而根本没有组织,但是,嘿,它仍然是源头.

只是一些我无法找到任何其他地方的答案.

Ale*_*eal 6

不,因为从指令到代码的映射不是1:1.

不,编译器破坏了程序的结构,没有其他的说法,调度和在某些点减少寄存器压力的任务可能意味着来自同一操作的指令可以相互远离150,000条指令(IIRC这个是GCC的股票上限,你当然可以用-f选项改变它:P)

不不不.

复杂化过程提供的唯一承诺是,结果将程序员编写的那样工作.而已.

看Stuxnet很有趣(是的,不是游戏,我知道)和实用因为它很小,仅仅驱动场景图的程序部分将是巨大的并且如此优化.如果他们没有使用链接时间优化来消除更多的结构,我也会感到震惊.

这个答案缺乏很多细节,但那是因为一个解释一切都很大,你显然不知道这是如何工作的,你想要学习它是好的.

http://luaforge.net/docman/83​​/98/ANoFrillsIntroToLua51VMInstructions.pdf

我已多次链接这个,它有一些代码映射到寄存器指令的例子.这是没有优化的,它们是一个更简单的小样本(有点,取决于你如何看待它)机器,你能看到甚至逆转这些会有多困难吗?

最后,使用-O3进行调试是一个笑话,我们现在已经-Og,编译器优化但避免了结构更改优化,因此调试不会如此频繁地跳转,当您使用-g生成的对象时,文件中充满了他们来自的代码来自和东西,超出他们生成的指示.有趣的事实!


nne*_*neo 6

您无法恢复原始源代码- 编译过程本质上是有损的,并且不可避免地会丢失一些细节。损失多少取决于源语言、目标语言和开发人员所做的选择。

让我们从简单的情况开始——一种编译成自己字节码的高级语言。例如,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)
Run Code Online (Sandbox Code Playgroud)

使用 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
Run Code Online (Sandbox Code Playgroud)

除了一些额外的注释和空格外,输出与原始内容基本上是 1:1 的。Java 和 C# 同样容易反编译。许多游戏是用 Java(例如 Android)和 C#(例如 Unity)编写的,并且有很多 modders/hackers 使用反编译器来获取用这些语言编写的游戏的可用源代码。

开发人员可以选择使用混淆来防御反编译器,在这种情况下,他们故意以某种方式破坏编译后的输出(例如,将变量/函数/类重命名为乱码),使这种类型的逆向工程更加困难。


更难的情况是当您获取代码并将其一直编译为机器代码(直接在 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);
}
Run Code Online (Sandbox Code Playgroud)

以下是 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;
}
Run Code Online (Sandbox Code Playgroud)

一个有经验的逆向工程师可以理解这一点——但它不太好。

所以你有它。如果您对编译为本机 CPU 代码的程序进行逆向工程,您可以获得源代码,但它会非常粗糙。如果您对编译为一些中间字节码的程序进行逆向工程,您将有更好的时间。在所有情况下,您都无法获得准确的原始源代码,但您可能会非常接近。


小智 5

其他答案都不准确。

有几个逆向工程项目可以完美地重建 1:1 准确的 C 代码,并编译为与原始编译器完全相同的字节。请参阅https://github.com/pret/pokeemerald。当然,你会失去名字和评论,但在这里对这个问题说不是不准确的。构造可重新编译的匹配 C 代码是完全可能的(无论如何,纯粹是在这种狭隘的情况下),这确实很乏味,而且是一个足够快地通过 C 集进行排列的问题,以找到匹配的成员。

真正的答案是?是的。你能合理地为每个函数找到1:1匹配的成员吗?可能不会。