跳转到指令,是不是`objdump`不能处理的情况?

逆向工程 拆卸 部件 x86 重新组装
2021-06-19 06:34:34

所以基本上我objdump在 32 位 x86 Linux 上使用,反汇编一些由编译的静态链接二进制文件gcc

在反汇编的汇编代码中,我发现:

 80ade23:       74 01                   je     0x80ade26
 80ade25:       f0 0f c1 16             lock xadd %edx,(%esi) // lock
 80ade29:       89 54 24 14             mov    %edx,0x14(%esp)
 80ade2d:       8b 54 24 14             mov    0x14(%esp),%edx
 80ade31:       3b 15 f0 0e 0f 08       cmp    0x80f0ef0,%edx
 80ade37:       73 75                   jae    0x80adeae
 80ade39:       65 83 3d 0c 00 00 00    cmpl   $0x0,%gs:0xc
 80ade40:       00
 80ade41:       74 01                   je     0x80ade44
 80ade43:       f0 0f c1 0d dc 0e 0f    lock xadd %ecx,0x80f0edc // lock
 80ade4a:       08

所以基本上,在我的理解中,lock是 x86 asm 操作码的前缀,在这里是合法的。

并且似乎是je紧接着就跳入了这个位置lock

所以这里是我的问题:

  1. 反汇编结果objdump是否正确?很少看到objdump生成这种“跳转到指令”的 asm 代码..(无论如何,我是逆向工程的新手,所以...... :))

  2. 那么如何调整它以使其重新组装?

我尝试用这种方式改变它并使用gcc重新组装它,它可以通过组装过程,但我真的不知道它是否是正确的方法。

 je     S_0x80ade26
 lock   
 S_0x80ade26: xadd %edx,(%esi) // lock
3个回答

我不会走那么远并声明提供的输出objdump不正确。没错,Linear Sweep 不能正确处理数据,并且跳转表和 shellcode 通常是反汇编错误的来源。但是,这仍然不是错误。

如果您仔细查看您的代码,您会发现您有je. 意思是,仅当前一条指令(当然是 acmp或 a test)返回 true 时才会执行跳转x86ISA(指令集),允许指令的中间跳,或字节流,如果你喜欢。这有时用于避免某些前缀,例如rep, ... ,在您的情况下是 a lock

我 100% 确定提供的输出是正确的,并且程序员(或编译器)使用这个技巧来避免不必要的额外代码。

其实就是objdump线性扫描算法来反汇编可执行文件。这意味着它一个接一个地反汇编指令。像这样:

  1. 首先它进入入口点并反汇编第一条指令(并获取其大小):

    4028c0:       41 57                   push   %r15
    
  2. 然后,知道前一条指令的大小,它将当前地址更新为下一条指令并反汇编(并再次获取其大小):

    4028c2:       41 56                   push   %r14
    
  3. 并且,它一次又一次地迭代(回到 2),直到它到达当前部分的末尾:

    4028c4:       41 55                   push   %r13
    4028c6:       41 54                   push   %r12
    4028c8:       55                      push   %rbp
    4028c9:       48 89 f5                mov    %rsi,%rbp
    4028cc:       53                      push   %rbx
    ...
    

objdump实现只在这个简单的算法上添加一个小增量,即使它出现在当前反汇编指令的中间,它也会从每个符号开始。这意味着您可能有以下情况(我在研究混淆软件时遇到过):

   4028c0:       41 57                   push   %r15
   4028c2:       41 56                   push   %r14
   4028c4:       41 55                   push   %r13
   4028c6:       41 54                   push   %r12
   4028c8:       55                      push   %rbp
   4028c9:       48 89 f5                mov    %rsi,%rbp
   4028cc:       48 85 c0                test   %rax,%rax

00000000004028cd <.f668>:
   4028cd:       85 c0                   test   %eax,%eax
   4028cf:       53                      push   %rbx
   ...

反汇编器首先反汇编4028cc为 amd64 指令,但符号在4028cd. 因此,算法重置为该值并从那里重新开始。

最后,请注意线性扫描算法被广泛认为是不正确的。它很容易被误导。它的主要问题是没有考虑所有指令的语义,因此当到达动态跳转(jmp %rax)时,算法将无法遵循执行流程。当然,还有很多其他方法可以误导这个算法,我不会在这里对所有这些技术进行详尽的介绍(请注意,递归遍历并不是真的更好)。

回到你最初的问题:

  1. 线性扫描算法无法跟踪程序的执行流程。如果数据位于指令中间,则无法跳过数据。然而,objdump当一个符号指向在前一条指令中间跳转的指令时可能偶然是正确的(参见我之前描述的情况)。

  2. 要对该程序进行适当的反汇编,没有希望使用objdump. 但是,您可以gdb通过 Python 脚本对其进行检测来使用和收集执行跟踪。此外,其他反汇编程序不会被这种简单的布局所欺骗。你可以试试,radare或者像 Benny 建议的那样,IDAPro。我也可以宣传一些我自己的工具,它cfgrecovery来自Insight 框架(但对于这样一个简单的技巧来说有点矫枉过正)。

我认为您所说的“跳转到指令”可能是一种称为反汇编去同步的抗静电分析技术,它将数据字节与代码交错以混淆反汇编器。这种技术和其他技术在IDA Pro 书籍的第 21 章(混淆代码分析)中进行了解释

使用 IDA Pro,您可以获得正确的代码反汇编,如果您:

  1. 使用 IDA(免费或专业)打开二进制文件

  2. 将光标放在有问题的一行代码上,在您的情况下是地址:80ade2580ade43

    lock xadd %edx,(%esi) // lock
    ...    
    lock xadd %ecx,0x80f0edc // lock
    
  3. 单击IDAEdit菜单,然后选择Undefine

  4. 现在将光标放在跳转指令指向的地址上,在您的情况下是0x80ade2680ade44

  5. 单击编辑再次IDA的菜单,然后选择代码这段时间

请注意,这种防静电分析技术在您的代码中应用了两次。所以你需要应用步骤 2-5 两次。

更新:但是,objdump正如 Peter Ferrie 在下面的评论中指出的那样,在您的输出中没有反汇编不同步。跳转到指令是提高性能的一种手段。但是,对于那些偶然发现您的问题并且其反汇编实际上遭受不同步的人,我将此答案作为提示。