如何在 Linux 中反汇编一个二进制可执行文件以获得汇编代码?

有人告诉我要用反汇编程序。gcc有内置的东西吗?最简单的方法是什么?

241414 次浏览

我不认为 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

还有 ndisasm 有些怪癖,但如果你用 nasm 会更有用。我同意 Michael Mrozek 的观点,obdump 可能是最好的。

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

你可能会发现 ODA 很有用,它是一个基于 Web 的反汇编程序,支持大量的架构。

Http://onlinedisassembler.com/

一个有趣的替代对象转储是 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)

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

这个答案是特定于 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
...

注意,这个输出已经准备好被组装回一个目标文件,所以您可以在 asm 源代码级别调整代码,而不是在机器代码上使用十六进制编辑器。(所以你不限于让东西保持相同的大小。)如果没有变化,结果应该是几乎相同的。不过,它可能不是,因为像

  (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位偏移量重写它。


如果您不想安装它 objecconv,GNU binutils objdump -Mintel -d是非常有用的,如果您有一个普通的 Linux gcc 安装程序,那么它就已经安装好了。

Ht Editor 可以分解多种格式的二进制文件,它类似于 Hiew,但是是开源的。

要反汇编,打开一个二进制文件,然后按 F6,然后选择小精灵/图像。

你可以非常接近(但没有雪茄)生成组装,将重新组装,如果这是你打算做的,使用这个相当粗糙和冗长的管道技巧(替换/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

然而,请注意这段时间有多长。我真的希望有一个更好的方法(或者,就此而言,反汇编程序能够输出汇编程序能够识别的代码) ,但不幸的是没有。

假设你有:

#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

一种更友好的方法是使用: 编译器资源管理器

使用 ghidra: https://ghidra-sre.org/。它已经安装在 Kali Linux 上了。