二进制反汇编究竟是什么以及它产生了什么?

逆向工程 拆卸 反编译
2021-06-13 10:18:11

第一次听说二进制反汇编时,我认为它可以称为汇编代码的完美反编译工具,但我仍然不明白为什么不是。我认为汇编操作码可以直接转换为二进制序列,然后直接从二进制序列返回操作码,但后来我听说了一些事情,比如混合代码和数据的可能性,可能还有一些其他事情让我想到我可以反汇编任何二进制文件并重新组装它以重新创建相同的二进制文件。请不要立即对我投反对票。我对逆向工程几乎一无所知,我正在考虑用它开始我的冒险。你能请你解释一下为什么事情是这样的吗?

3个回答

首先欢迎来到逆向工程的世界,如果真的有完美的反汇编器这样的工具,那么整个堆栈交换论坛甚至都不存在。

在立即解决您的问题之前,我想先谈谈反转的真正含义以及它的全部含义,因为我认为它是解决您的问题的更合适的方法。

对于二进制文件,您问的第一个问题是“什么?” 我的意思是在两种意义上,二进制转储似乎是无限大的零和一序列,实际上是您最喜欢的视频游戏或驱动程序。

你如何解释数据? 老实说,这个序列可以是任何文本文件、程序、驱动程序、图像、音乐、视频、一些特洛伊木马等等。假设你知道它是上述之一,你仍然不知道如何解释它,如果它是一些某种媒体,它是什么格式(png、mp3、avi...)?如果它是适用于哪个平台的程序(windows / Linux),或者更糟糕的是它甚至适用于哪种 CPU 架构(x86、ARM、PowerPC、MSP430...),以及它适用于哪个版本的 CPU?等等,但如果它是加密的呢?它是什么加密?我相信你现在明白了。

最后一段旨在说明这一系列代码可能代表的大量可笑的可能性。现在你的问题是专门关于代码反汇编的。反汇编正是将不同的二进制序列转换为其原始操作码的过程,但是当您获得程序并假设您知道平台/CPU/版本等时。

而且,假设操作码不能混淆。例如,设 0101(指令 a)、0011(b)为操作码,假设还有更长的不同操作码 01010011(c)和 00110101(d)。给定序列 0101001100110101 你怎么知道如何解释代码(abba, cd, cab..)?(剧透:通常 ISA 的设计方式是不可能发生此类冲突的)

太好了,我们现在应该有一个完美的反汇编器,现在我们想要更进一步,并获得原始代码。问题来了

以下面的代码为例:

.loop:
    xadd eax, edx
    loop .loop

基本上我们在这里看到的是一个加法和交换命令(将 edx 添加到 eax 然后切换它们的内容)现在制作原始代码的简单方法是这样的:

for (int i = n; i > 0; i--)
{
    a += b;
    switch(a, b);
}

然而,聪明的逆向工程师可以这样翻译:

genrate_nth_fibonnacci(n);

当 eax 和 edx 从 0 和 1 开始时

类似地,恶意软件中的一系列随机命令可能会被翻译成“makeAntivirusNotNotice”函数,或者在其他合法程序中成为针对特殊情况的非常有效的算法。

因此,编程在编写代码时有意图,因此当您尝试反转程序时,相同的代码或如前所述,看似混乱的字节序列可能具有不同的含义,具体取决于上下文、许多高级代码替代方案以及在写作的时候,仍然没有一个工具可以预测原始程序员的意图。最好的反编译器和逆向工具,如 Radare 和 IDA,试图更好地分析和模仿这些功能,但现在这是逆向工程师的任务。

考虑阅读《计算机系统:程序员的视角》的第1、2 和 3 章它解释了一切。在深入研究逆向工程之前,努力了解计算机体系结构和编程语言的基本概念。


第一次听说二进制反汇编时,我认为它可以称为汇编代码的完美反编译工具,但我仍然不明白为什么不是。

了解什么是重要的编译是什么,装配试图理解之前是反编译拆解

  • 编译是语言到语言的转换,其中保留了原始含义。通常,依赖计算机架构语言(例如 C)会转换为特定于架构的语言,例如 x86 汇编语言,它针对 Intel i386 系列 CPU。

    这是使用 GCC 将这种高级语言转换为低级语言的示例:

    源 C“hello_world.c”文件(ASCII 文本):

    #include <stdio.h>
    
    int main(void) {
      printf("Hello, world.\n");
      return 0;
    }
    

    编译器输出(使用-S标志生成的 x86 汇编 ASCII 文本):

    $ gcc -m32 -S hello_world.c 
    $ cat hello_world.s
      .file   "hello_world.c"
      .section    .rodata
    .LC0:
      .string "Hello, world."
      .text
      .globl  main
      .type   main, @function
    main:
    .LFB0:
      .cfi_startproc
      pushl   %ebp
      .cfi_def_cfa_offset 8
      .cfi_offset 5, -8
      movl    %esp, %ebp
      .cfi_def_cfa_register 5
      andl    $-16, %esp
      subl    $16, %esp
      movl    $.LC0, (%esp)
      call    puts
      movl    $0, %eax
      leave
      .cfi_restore 5
      .cfi_def_cfa 4, 4
      ret
      .cfi_endproc
    .LFE0:
      .size   main, .-main
      .ident  "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.4) 4.8.4"
      .section    .note.GNU-stack,"",@progbits
    

    如您所见,这都是 ASCII 文本。这里没有机器码。为了让 CPU 执行此代码,需要一个额外的步骤。

  • 上述86的汇编语言中的ASCII编码被变换成符合经由所述目标CPU的指令集规范的二进制值的序列的汇编汇编器将汇编语言作为输入,并从中生成 CPU 可以执行的机器语言。机器语言不是用 ASCII 编码的,也不是人类可读的。

  • 反汇编器的作用是将机器语言操作代码显示为人类可读的助记符下面的输出是x86 机器语言的反汇编

     0804841d <main>:
     804841d: 55                      push   %ebp
     804841e: 89 e5                   mov    %esp,%ebp
     8048420: 83 e4 f0                and    $0xfffffff0,%esp
     8048423: 83 ec 10                sub    $0x10,%esp
     8048426: c7 04 24 d0 84 04 08    movl   $0x80484d0,(%esp)
     804842d: e8 be fe ff ff          call   80482f0 <puts@plt>
     8048432: b8 00 00 00 00          mov    $0x0,%eax
     8048437: c9                      leave  
     8048438: c3                      ret    
     8048439: 66 90                   xchg   %ax,%ax
     804843b: 66 90                   xchg   %ax,%ax
     804843d: 66 90                   xchg   %ax,%ax
     804843f: 90                      nop
    

    左边是机器语言操作码的十六进制值,右边是机器语言操作码对应的助记符。

  • 反编译可以这样理解:

    反编译器或反向编译器是一种尝试执行编译器逆过程的程序:给定一个用任何高级语言编译的可执行程序,其目的是生成一个执行与编译器相同功能的高级语言程序可执行程序。因此,输入依赖于机器,输出依赖于语言。

    本质上,反编译是将包含二进制操作码和操作数的机器语言翻译成独立于体系结构的语言,如 C。反汇编和反编译是完全不同的概念,不应混淆。


我认为汇编操作码可以直接转换为二进制序列,然后直接从二进制序列返回操作码,但后来我听说了一些事情,比如混合代码和数据的可能性,可能还有一些其他事情让我想到我可以反汇编任何二进制文件并重新组装它以重新创建相同的二进制文件。

当您说“汇编操作码”时,您的意思是与机器语言操作码相对应的汇编语言助记符。虽然汇编语言和机器语言之间有很强的关系,但这种关系不一定是一对一的。看看为什么没有任何反汇编器可以生成可重新组装的 asm 代码?.

反汇编产生人类可读的助记符形式 hexbytes

处理器理解 0 和 1

二进制包含 0 和 1 以十六进制字节形式的编码版本

如果需要指示处理器 mov 1 注册 eax

它需要编码为0b1011100000000001000000000000000000000000

当转换为十六进制(基数为 16 而不是基数 2 如上所述)时将变为 > 0xb801000000

反汇编器将此 > 0xb801000000 解释并显示此十六进制字节
mov eax ,1

高级语言不知道您将编写代码的寄存器

int a = 1;

这可以翻译为mov eax,1 或 mov ebx,1 或 mov [addr] , 1

因此,不可能总是将几种形式之一转换回原始形式