在Linux中如何拆解二进制可执行文件以获取汇编代码?

136

有人告诉我要使用反汇编器。 gcc 有内置的工具吗?最简单的方法是什么?

有人建议我使用反汇编器。请问是否 gcc 内置了相关工具?如何实现最简单的方法?

然后重新组装:https://dev59.com/D2855IYBdhLWcg3wfUfQ - Ciro Santilli OurBigBook.com
相关:如何从GCC/clang汇编输出中去除“噪音”? - 如果你只想看看编译器做了什么,你并不总是需要编译+链接+反汇编。 - Peter Cordes
10个回答

202

我认为gcc没有这个标志,因为它主要是一个编译器,但GNU开发工具中的另一个工具有。 objdump使用-d/--disassemble标志:

$ objdump -d /path/to/binary

反汇编看起来像这样:

080483b4 <main>:
 80483b4:   8d 4c 24 04             lea    0x4(%esp),%ecx
 80483b8:   83 e4 f0                and    $0xfffffff0,%esp
 80483bb:   ff 71 fc                pushl  -0x4(%ecx)
 80483be:   55                      push   %ebp
 80483bf:   89 e5                   mov    %esp,%ebp
 80483c1:   51                      push   %ecx
 80483c2:   b8 00 00 00 00          mov    $0x0,%eax
 80483c7:   59                      pop    %ecx
 80483c8:   5d                      pop    %ebp
 80483c9:   8d 61 fc                lea    -0x4(%ecx),%esp
 80483cc:   c3                      ret    
 80483cd:   90                      nop
 80483ce:   90                      nop
 80483cf:   90                      nop

23
用于Intel语法的反汇编命令是 objdump -Mintel -d。Agner Fog的 objconv 反汇编器是我试过的最好用的一个(请参见我的回答)。在分支目标上添加编号标签真的非常好。 - Peter Cordes
10
有用的选项:objdump -drwC -Mintel-r 显示符号表中的重定位信息。-C 解码 C++ 函数名。-W 避免长指令换行显示。如果你经常使用它,可以设置别名 alias disas='objdump -drwC -Mintel' 方便使用。 - Peter Cordes
4
在显示反汇编时,添加“-S”以混合显示源代码。(如另一个答案中所指出的。) - Alexander Pozdneev
我能知道是否有一种反汇编器只输出AT&T汇编代码?而不是所有地址、二进制编码等等... - user135142

61

一种有趣的 objdump 替代品是 gdb。您不需要运行二进制文件或拥有调试信息。

$ gdb -q ./a.out 
Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb) info functions 
All defined functions:

Non-debugging symbols:
0x00000000004003a8  _init
0x00000000004003e0  __libc_start_main@plt
0x00000000004003f0  __gmon_start__@plt
0x0000000000400400  _start
0x0000000000400430  deregister_tm_clones
0x0000000000400460  register_tm_clones
0x00000000004004a0  __do_global_dtors_aux
0x00000000004004c0  frame_dummy
0x00000000004004f0  fce
0x00000000004004fb  main
0x0000000000400510  __libc_csu_init
0x0000000000400580  __libc_csu_fini
0x0000000000400584  _fini
(gdb) disassemble main
Dump of assembler code for function main:
   0x00000000004004fb <+0>:     push   %rbp
   0x00000000004004fc <+1>:     mov    %rsp,%rbp
   0x00000000004004ff <+4>:     sub    $0x10,%rsp
   0x0000000000400503 <+8>:     callq  0x4004f0 <fce>
   0x0000000000400508 <+13>:    mov    %eax,-0x4(%rbp)
   0x000000000040050b <+16>:    mov    -0x4(%rbp),%eax
   0x000000000040050e <+19>:    leaveq 
   0x000000000040050f <+20>:    retq   
End of assembler dump.
(gdb) disassemble fce
Dump of assembler code for function fce:
   0x00000000004004f0 <+0>:     push   %rbp
   0x00000000004004f1 <+1>:     mov    %rsp,%rbp
   0x00000000004004f4 <+4>:     mov    $0x2a,%eax
   0x00000000004004f9 <+9>:     pop    %rbp
   0x00000000004004fa <+10>:    retq   
End of assembler dump.
(gdb)

有完整的调试信息会更好。

(gdb) disassemble /m main
Dump of assembler code for function main:
9       {
   0x00000000004004fb <+0>:     push   %rbp
   0x00000000004004fc <+1>:     mov    %rsp,%rbp
   0x00000000004004ff <+4>:     sub    $0x10,%rsp

10        int x = fce ();
   0x0000000000400503 <+8>:     callq  0x4004f0 <fce>
   0x0000000000400508 <+13>:    mov    %eax,-0x4(%rbp)

11        return x;
   0x000000000040050b <+16>:    mov    -0x4(%rbp),%eax

12      }
   0x000000000040050e <+19>:    leaveq 
   0x000000000040050f <+20>:    retq   

End of assembler dump.
(gdb)

objdump有一个类似的选项 (-S)


20
这个答案只适用于x86。可以反汇编AArch64、MIPS或其他机器代码的便携工具包括objdumpllvm-objdump

Agner Fog的反汇编器objconv,非常好用。它会为性能问题(例如使用16位立即常数导致的可怕LCP停顿)的反汇编输出添加注释。

objconv  -fyasm a.out /dev/stdout | less

(它不将-识别为标准输出的简写,而是默认将输出到与输入文件类似名称的文件中,并在其后面加上.asm。)

它还会给代码添加分支目标。其他反汇编器通常只对跳转指令进行反汇编,只有一个数字目标,并且不会在分支目标处放置任何标记,以帮助您找到循环顶部等。

它还比其他反汇编器更清楚地指示NOP指令(明确指示填充时,而不是将其作为另一条指令进行反汇编)。

它是开源的,容易在Linux上编译。它可以反汇编为NASM、YASM、MASM或GNU(AT&T)语法。

示例输出:

; Filling space: 0FH
; Filler type: Multi-byte NOP
;       db 0FH, 1FH, 44H, 00H, 00H, 66H, 2EH, 0FH
;       db 1FH, 84H, 00H, 00H, 00H, 00H, 00H

ALIGN   16

foo:    ; Function begin
        cmp     rdi, 1                                  ; 00400620 _ 48: 83. FF, 01
        jbe     ?_026                                   ; 00400624 _ 0F 86, 00000084
        mov     r11d, 1                                 ; 0040062A _ 41: BB, 00000001
?_020:  mov     r8, r11                                 ; 00400630 _ 4D: 89. D8
        imul    r8, r11                                 ; 00400633 _ 4D: 0F AF. C3
        add     r8, rdi                                 ; 00400637 _ 49: 01. F8
        cmp     r8, 3                                   ; 0040063A _ 49: 83. F8, 03
        jbe     ?_029                                   ; 0040063E _ 0F 86, 00000097
        mov     esi, 1                                  ; 00400644 _ BE, 00000001
; Filling space: 7H
; Filler type: Multi-byte NOP
;       db 0FH, 1FH, 80H, 00H, 00H, 00H, 00H

ALIGN   8
?_021:  add     rsi, rsi                                ; 00400650 _ 48: 01. F6
        mov     rax, rsi                                ; 00400653 _ 48: 89. F0
        imul    rax, rsi                                ; 00400656 _ 48: 0F AF. C6
        shl     rax, 2                                  ; 0040065A _ 48: C1. E0, 02
        cmp     r8, rax                                 ; 0040065E _ 49: 39. C0
        jnc     ?_021                                   ; 00400661 _ 73, ED
        lea     rcx, [rsi+rsi]                          ; 00400663 _ 48: 8D. 0C 36
...

请注意,此输出已准备好组装回对象文件,因此您可以在汇编源代码级别上调整代码,而不是通过机器码的十六进制编辑器进行修改。(因此,您不限于保持相同的大小。)如果没有更改,结果应该是几乎相同的。但是由于类似内容的反汇编可能会导致略有差异。
  (from /lib/x86_64-linux-gnu/libc.so.6)

SECTION .plt    align=16 execute                        ; section number 11, code

?_00001:; Local function
        push    qword [rel ?_37996]                     ; 0001F420 _ FF. 35, 003A4BE2(rel)
        jmp     near [rel ?_37997]                      ; 0001F426 _ FF. 25, 003A4BE4(rel)

...    
ALIGN   8
?_00002:jmp     near [rel ?_37998]                      ; 0001F430 _ FF. 25, 003A4BE2(rel)

; Note: Immediate operand could be made smaller by sign extension
        push    11                                      ; 0001F436 _ 68, 0000000B
; Note: Immediate operand could be made smaller by sign extension
        jmp     ?_00001                                 ; 0001F43B _ E9, FFFFFFE0

没有任何源代码来确保它组装成更长的编码,以便留出空间进行重定位并使用32位偏移量进行重写。
如果您不想安装objconv,GNU binutils的objdump -drwC -Mintel非常实用,并且如果您有一个正常的Linux gcc设置,它已经安装好了。我在我的系统上使用alias disas='objdump -drwC -Mintel'。(-w是无换行,-C是解码,-r打印目标文件中的重定位信息。)

llvm-objdump -d也可以工作,并且可以从单个二进制文件中反汇编多种架构的代码。(与GNU objdump不同,你需要为每个架构单独安装,例如aarch64-linux-gnu-objdump -d。)同样地,clang -O3 -target mips -c或者clang -O3 -target riscv32 -c等命令非常有用,可以为你感兴趣的架构进行编译,而无需费心安装交叉编译器。(https://godbolt.org/ Compiler Explorer也是一个很有用的资源;请参阅如何从GCC/clang汇编输出中去除“噪音”?了解更多信息以及编写能够生成有趣汇编代码的小函数。)


6

此外还有ndisasm,它有一些怪癖,但如果你使用nasm可能会更有用。我同意Michael Mrozek的观点,objdump可能是最好的选择。

[稍后] 您可能还想查看Albert van der Horst的ciasdis:http://home.hccnet.nl/a.w.m.van.der.horst/forthassembler.html。它可能很难理解,但具有一些您不太可能在其他地方找到的有趣功能。


2
特别是:http://home.hccnet.nl/a.w.m.van.der.horst/ciasdis.html 网站下的“最新进展”栏目中包含一个Debian软件包,您可以轻松安装。通过适当的说明(它支持脚本编写),它将生成一个源文件,该文件将重新组合成完全相同的二进制文件。我不知道有任何其他软件包可以做到这一点。从说明上来看可能很难使用,我打算在github上发布并提供详细的示例。 - Albert van der Horst

4

2
好主意。访问https://onlinedisassembler.com/odaweb/ 时出现服务器错误(500)- 希望这只是暂时的。 - jouell

4

IDA 对于这个有点过头了,特别考虑到它相当昂贵。 - Michael Mrozek
1
免费版本不适用于Linux,仅有限制的演示版本可用。(太遗憾了,因为在Windows上,那是我曾经使用过的最好的反汇编工具) - Adrien Plisson
IDA很好,但IDA的问题是如果用于小任务,你会变得懒惰。gdb可以完成大多数工作,gdb更容易吗?不,但是有可能。 - cfernandezlinux
1
IDA是专有软件,它不尊重用户的自由。它包含DRM,限制用户使用许多功能。此外,这是一款付费软件。请参见https://gnu.org/proprietary/proprietary.html。 - Akib Azmain Turja

3
你可以使用这个相当粗糙和极其冗长的管道技巧(将/bin/bash替换为你打算反汇编的文件,将bash.S替换为你打算将输出发送到的文件),以生成可以重新组装的汇编代码,如果这是你的意图的话。
objdump --no-show-raw-insn -Matt,att-mnemonic -Dz /bin/bash | grep -v "file format" | grep -v "(bad)" | sed '1,4d' | cut -d' ' -f2- | cut -d '<' -f2 | tr -d '>' | cut -f2- | sed -e "s/of\ section/#Disassembly\ of\ section/" | grep -v "\.\.\." > bash.S

请注意这里的长度,但是我真的希望有更好的方式(或者说,能够输出汇编程序可以识别的代码的反汇编器),但不幸的是目前还没有。

哇!这太棒了。顺便说一下,关于你的问题,为什么不使用别名来跳过输入这个巨大的命令呢? - Bat

1

ht editor 可以反汇编多种格式的二进制文件。它类似于 Hiew,但是开源的。

要进行反汇编,请打开一个二进制文件,然后按 F6 键,选择 elf/image 格式。


1
假设你有以下内容:
#include <iostream>

double foo(double x)
{
  asm("# MyTag BEGIN"); // <- asm comment,
                        //    used later to locate piece of code
  double y = 2 * x + 1;

  asm("# MyTag END");

  return y;
}

int main()
{
  std::cout << foo(2);
}

使用gcc获取汇编代码的方法如下:

 g++ prog.cpp -c -S -o - -masm=intel | c++filt | grep -vE '\s+\.'

c++filt 解析符号

grep -vE '\s+\.' 移除一些无用信息

现在,如果你想可视化标记部分,只需使用:

g++ prog.cpp -c -S -o - -masm=intel | c++filt | grep -vE '\s+\.' | grep "MyTag BEGIN" -A 20

我用我的电脑得到:
    # MyTag BEGIN
# 0 "" 2
#NO_APP
    movsd   xmm0, QWORD PTR -24[rbp]
    movapd  xmm1, xmm0
    addsd   xmm1, xmm0
    addsd   xmm0, xmm1
    movsd   QWORD PTR -8[rbp], xmm0
#APP
# 9 "poub.cpp" 1
    # MyTag END
# 0 "" 2
#NO_APP
    movsd   xmm0, QWORD PTR -8[rbp]
    pop rbp
    ret
.LFE1814:
main:
.LFB1815:
    push    rbp
    mov rbp, rsp

一种更友好的方法是使用:编译器浏览器


只有在禁用优化的情况下此才是可靠的,否则区域内部的操作部分可能会优化到区域外部,或者被优化掉。因此,您只能看到笨重的“-O0”汇编代码。 - Peter Cordes

-3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接