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

时间:2014-01-02 06:39:50

标签: windows exe decompiler

我使用战地4作为一个例子,这可以用于任何游戏。

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

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

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

一直到游戏的核心机制?

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

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

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

4 个答案:

答案 0 :(得分:6)

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

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

不,不,不。

复杂化过程提供的唯一承诺是结果将好像它实际上完成了程序员编写的内容。而已。

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

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

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

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

最后,使用-O3进行调试是一个笑话,我们现在已经-Og,编译器优化但避免了结构更改优化,因此当您使用-g生成的对象时,调试不会跳得那么多文件充斥着它们来自的代码和填充,超出了它们生成的指令。有趣的事实!

答案 1 :(得分:3)

并不总是那么容易。取两个素数并将它们相乘是多么困难?简单,好吧,拿出一个大数字并确定它的主要组成部分有多难?如果数字足够大,非常困难。

反编译代码也是如此。试图找出c或c ++代码生成的一些汇编代码对于除了最小和最简单的情况之外的所有代码都是非常困难的。在某些情况下,反编译器会失败并且无法生成c代码,并且您仍然试图找出一些大块的汇编意味着什么。

更糟糕的是,一些关键部分可能从来没有用c或c ++编写,因此开发人员可能编写了一些无法翻译成更高语言的汇编代码,因为它做的事情不是拥有更高语言的镜像概念。

更糟糕的是,一些开发人员之后通过混淆程序编写了代码,现在反编译器已经很难完成工作了。

答案 2 :(得分:3)

其他答案不正确。

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

实际答案?是。您能否为每个功能合理找到1:1匹配成员?可能不是。

答案 3 :(得分:1)

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

让我们从简单的案例开始-一种编译成自己的字节码的高级语言。例如,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

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

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


最困难的情况是,您获取代码并将其完全编译为机器代码(直接在CPU上运行的代码)。默认情况下,Rust,Go,C ++,Swift等语言都可以直接编译为机器代码。 CPU指令与高级语言中的概念并不一一对应。现在,有了反编译器-NSA最近开源的Ghidra反编译器是目前最好的反编译器之一,但它们只能为您提供原始源代码的非常粗略的近似,并且大多数只能反编译为C(并非一直反编译为Rust) / Go / C ++ / Swift / etc。)。这是一个简单的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代码进行反向工程,则可以获取源代码,但这将非常困难。如果您对一个编译为某个中间字节码的程序进行逆向工程,您将拥有更好的时间。在所有情况下,您都无法获得完全原始的源代码,但您也许可以非常接近原始代码。