如何使用 AI 编写 C 反编译器?

人工智能 神经网络 深度学习 参考请求 C
2021-10-28 03:42:33

我想了解更多关于它是否可能以及如何编写将可执行二进制文件(目标文件)反编译为 C 源代码的程序。我不是在问“如何”,而是在问如何实现。

给定以下hello.c文件(例如):

#include <stdio.h>
int main() {
  printf("Hello World!");
}

然后在编译(gcc hello.c)之后,我得到了如下二进制文件:

$ hexdump -C a.out | head
00000000  cf fa ed fe 07 00 00 01  03 00 00 80 02 00 00 00  |................|
00000010  0f 00 00 00 b0 04 00 00  85 00 20 00 00 00 00 00  |.......... .....|
00000020  19 00 00 00 48 00 00 00  5f 5f 50 41 47 45 5a 45  |....H...__PAGEZE|
00000030  52 4f 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |RO..............|
00000040  00 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  00 00 00 00 00 00 00 00  19 00 00 00 d8 01 00 00  |................|
00000070  5f 5f 54 45 58 54 00 00  00 00 00 00 00 00 00 00  |__TEXT..........|
$ wc -c hello.c a.out 
  60 hello.c
8432 a.out

对于学习数据集,我假设我必须拥有数千个源代码文件及其二进制表示,因此算法可以了解某些更改的移动部分。

你将如何解决这个问题?

我的担忧(和子问题)是:

  • 我的算法是否需要知道头文件,或者它足够“聪明”才能弄清楚?

  • 如果它需要知道头文件,我如何告诉我的算法“这是头文件”?

  • 什么应该是输入/输出映射(无论是部分到部分还是文件到文件)?

  • 我需要将我的源代码分成几个部分吗?

  • 我是否需要确切地知道反编译器是如何工作的,或者 AI 可以帮我弄清楚?

  • 我是否应该有两个神经网络,一个用于标头,另一个用于主体本身?

  • 或多个单独的神经网络,每个用于每个逻辑组件(例如字节->C 标记等)

2个回答

在您的输入和所需输出之间,显然有一个巨大的搜索空间。您作为特征包含的相关领域信息越多,深度学习 (DL) 算法找到所需映射的机会就越大。

在深度学习研究的早期阶段,没有太多的经验法则可以告诉你要明确编码哪些特征——尤其是因为它取决于你的训练语料库的大小。我的建议是:获取(或生成)大量 C 代码库,使用您认为可行的最简单的特征表示对其进行训练,然后重复收集数据并根据需要添加更多特征预处理。

下面的这篇论文描述了一种 DL 方法来解决几乎是您的“逆向问题”的问题 -为用自然语言描述的程序生成源代码

我发现本文报告的结果的强度令人惊讶,但它确实给了我一些希望,即您所要求的可能是可能的。

软件逆向工程是我的爱好之一。

首先要做的事情是:忘记标题。所有关于头文件和单独的 C 文件的信息都消失了。

恕我直言,您错过了一些关键步骤。

  • 编译创建一个或多个目标文件 (.o),然后链接器创建一个可执行文件。

  • 您应该使用反汇编代码。反汇编器在一些例外情况下工作得很好(自修改代码、自解压可执行文件、各种混淆技术),并且会为您处理很多工作:识别各个部分、查找函数、猜测(相当准确)调用约定.

  • 然后编译器优化将以一种非常聪明的方式弄乱您的代码,并且您的原始代码的某些部分将永远不会再被看到(嘿,看,您的 200 行错误代码总是返回 0,所以我将它们替换为“异或 eax,eax”)。

  • 有时,这很好,有时它会产生不可读的 C 代码(没有 C 等效项的向量化,将被反编译成数百行内在代码,而不是可读的“for”循环)。

  • 我还没搞定。您还有异常和中断、结构、联合、函数指针、函数内联、线程、系统调用和信号、循环展开等。

与上升(反编译)相比,下降(从人类可读到二进制)相对容易,因为在编译过程中丢失了很多信息。

我最好的选择是让反汇编程序生成一堆反汇编函数,并使用您的 AI 生成 LLVM 中间表示,然后将其与 Clang ( clang -S -emit-llvm foo.c) 生成的 LLVM IR 进行比较。

无限数量的 C 代码可以生成完全相同的代码。因此,我认为让一个AI读取C代码以进行反编译是没有意义的:信息永远丢失了。

商业和开放/免费的反编译器也不产生 C 代码,它们产生某种充满错误、缺失代码或比 ASM 可读性差的代码的伪 C。

以下代码:

int main() {
    int toto = 0x0000BEEF;
    int titi = 0xDEAD0000;
    toto = toto | titi;
    return toto;
}

产生这个:

int __cdecl main(int argc, const char **argv, const char **envp)
{
     return -559038737;
}

这是反汇编版本:

mov     eax, 0DEADBEEFh
retn

加上数千行与您的代码无关但需要使程序正常运行的程序集。

你不能回去,你无法知道这是完全相同的代码,除非

  1. 您可以进行静态分析(在这种情况下非常容易,但在现实世界中却非常困难)

  2. 或者比较两个代码生成的 IR 或 ASM 与相同的编译器在相同的架构和操作系统上具有相同的选项。