【问题标题】:Can you decompile a game down to its original source code?你能把游戏反编译成它的原始源代码吗?
【发布时间】:2014-01-19 14:07:44
【问题描述】:

我以《战地风云 4》为例,这真的适用于任何游戏。我一直想知道这样的事情是否可行:

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

是否可以“从技术上”反编译代码并查看其源代码?

一直到游戏的核心机制?还是有某种加密保护它?

我确实意识到,如果你成功地反编译了类似的东西,那将是一团糟,而且根本没有组织,但是嘿,它仍然是源代码。

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

【问题讨论】:

  • 至其原始来源:没有。
  • 很快,很可能在 1-2 年内,反编译器将变得如此先进,以至于他们能够将任何游戏反编译成最好的源代码解释。我很惊讶没有人为 C 代码修复 sn-ps 制作反编译器的在线数据库,程序将在线查找数据库并从实际人类思维的工作中优化反编译代码,这样人们会越来越多地改进生成的代码和反编译器将从中学习很可能通过定义更新保留在线和客户端学习的所有知识。

标签: windows exe decompiler


【解决方案1】:

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

不,编译器会破坏程序的结构,没有其他词可以形容它,调度和减少某些点的寄存器压力可能意味着来自同一操作的指令可以彼此相距多达 150,000 条指令(IIRC 这是 GCC 的股票上限,您当然可以使用 -f 选项更改它:P)

不,不,不。

复杂化过程提供的唯一保证是结果将一样工作,它实际上是按照程序员编写的那样工作。而已。

研究 Stuxnet 很有趣(是的,不是游戏,我知道)而且很实用,因为它很小,单独驱动场景图的程序部分将很大并且优化得非常好。如果他们不使用删除更多结构的链接时间优化,我也会感到震惊。

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

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

我已经链接了很多次,它有一些代码映射到寄存器指令的示例。这没有经过优化,它们只是用于更简单(有点,取决于你如何看待它)机器的小样本,你知道即使逆转这些机器会有多困难吗?

最后,使用 -O3 进行调试是个笑话,我们现在有 -Og,编译器会优化但避免结构更改优化,因此调试不会跳得太多,当您使用 -g 时,生成的目标文件是在他们生成的指令之上,到处都是他们来自的代码和东西。有趣的事实!

【讨论】:

    【解决方案2】:

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

    让我们从简单的案例开始 - 一种编译成自己的字节码的高级语言。例如,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 代码的程序进行逆向工程,您可以获得源代码,但它会非常粗糙。如果您正在对编译为一些中间字节码的程序进行逆向工程,那么您将有更好的时间。在所有情况下,您都无法获得确切的原始源代码,但您可能能够非常接近。

    【讨论】:

      【解决方案3】:

      其他答案不准确。

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

      真正的答案?是的。您能否合理地为每个函数找到 1:1 匹配的成员?应该不会吧。

      【讨论】:

        【解决方案4】:

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

        反编译代码也是如此。除了最小和最简单的情况外,试图弄清楚是什么 c 或 c++ 代码生成了您所拥有的一些汇编代码是非常困难的。在某些情况下,反编译器会失败并且无法生成 c 代码,并且您会被困在试图弄清楚一些大型汇编块的含义。

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

        更糟糕的是,一些开发人员随后将他们的代码通过了混淆程序,而现在反编译器已经很困难的工作变得更加困难了

        【讨论】:

        • 我知道这甚至都不容易。我想知道这是否可能。不过谢谢你的回复!
        • @AlecTeal - 那你真的应该做你的功课。你是对的,你是错的——一般来说,编译器在生成良好的汇编方面比人类要好得多。但是,对于代码中对性能至关重要的部分,开发人员通常会手动编写程序集——fftw 就是一个简单的例子。另外,如果你认为汇编中没有不能翻译成高级语言的概念,那么在这里,把这篇文章中的汇编代码转换成有效的 C:stackoverflow.com/a/7964376/344638
        猜你喜欢
        • 2020-09-19
        • 1970-01-01
        • 1970-01-01
        • 2011-11-07
        • 2021-10-05
        • 2010-12-19
        • 2019-09-20
        • 2012-04-10
        • 1970-01-01
        相关资源
        最近更新 更多