在Linux中反汇编机器代码
1. 概述
所有可执行文件都包含机器代码,可以由处理器执行。但是人类打开和读取二进制文件而不转换为另一种格式是没有意义的。
在本教程中,我们将检查如何在 Linux 中读取机器代码。
2. 问题
让我们看两个问题场景。假设我们将机器代码存储在文件或字符串中。
让我们看看如何使用不同的工具对其进行拆卸。
2.1. 从文件中读取
我们将使用一个简单的 C 程序创建一个二进制文件。然后我们可以检查如何将该二进制文件中的机器代码转换为汇编语言。
让我们从 C 程序中创建一个二进制文件:
$ cat test.c
#include
void main() {
int i = 0;
i += 20;
return;
}
$ gcc test.c -o test
$ ls
test test.c
$
如上所示,我们有一个将 20 加到变量i的 C 程序。然后我们编译 C 程序以生成二进制文件。如果我们使用*-c标志进行编译,它会输出一个带有.o*扩展名的目标文件:
$ gcc -c test.c
$ ls
test test.c test.o
$
现在我们准备好了一个二进制文件和一个目标文件。
2.2. 从字符串中读取
有时我们可能想分析一些随机的 shellcode 来看看它做了什么。
让我们看一些机器代码:
54: push esp
55: push ebp
90: nop
现在,让我们将其存储到以后可以读取和反汇编的文件中:
$ echo -ne '\x54\x55\x90' > code
$ ls
code test test.c test.o
$
使用上述命令,我们将 shellcode 字符串回显到名为 code 的二进制文件中。
接下来,我们将检查如何读取这些文件。
3. 使用objdump命令
objdump 命令通常用于检查目标文件和二进制文件。它打印目标文件中的不同部分、它们的虚拟内存地址、逻辑内存地址、调试信息、符号表和其他信息。
一般用法是:
objdump OPTIONS objfile ...
在这里,我们将看到如何使用这个工具来反汇编文件。
3.1.从文件中读取
使用*-d*选项,我们可以看到二进制文件的汇编代码:
$ objdump -d test
test: file format elf64-x86-64
..
00000000000005fa <main>:
5fa: 55 push %rbp
5fb: 48 89 e5 mov %rsp,%rbp
5fe: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
605: 83 45 fc 14 addl $0x14,-0x4(%rbp)
609: 90 nop
60a: 5d pop %rbp
60b: c3 retq
60c: 0f 1f 40 00 nopl 0x0(%rax)
0000000000000610 <__libc_csu_init>:
..
$
二进制文件包含许多ELF 格式的部分,其中包含地址和元数据,用于在启动时正确加载可执行文件。由于我们使用了*-d标志,它将打印所有可执行部分**。在这里,我们可以在剥离其他部分后看到相关的main*部分。
我们看到add 指令将 20 (0x14) 添加到内存地址 605 处的变量i。
为了确保这是反汇编,我们可能会修改 C 程序,编译它并再次对其运行objdump命令以查看更改。
同样,我们可以在目标文件上运行相同的命令来反汇编代码:
$ objdump -d test.o
test.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
b: 83 45 fc 14 addl $0x14,-0x4(%rbp)
f: 90 nop
10: 5d pop %rbp
11: c3 retq
$
正如我们在上面看到的,与二进制文件不同,目标文件只显示 main 部分。 默认情况下,它在 ATT mnemonic 中显示反汇编。如果我们需要更改为 Intel,那么我们可以使用*-M*选项:
$ objdump -d test.o -M intel
test.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
b: 83 45 fc 14 add DWORD PTR [rbp-0x4],0x14
f: 90 nop
10: 5d pop rbp
11: c3 ret
$
3.2. 从字符串中读取
将字符串保存到文件后,我们可以使用以下命令显示反汇编:
$ objdump -D -b binary -m i386 code
code: file format binary
Disassembly of section .data:
00000000 <.data>:
0: 54 push %esp
1: 55 push %ebp
2: 90 nop
$
如上所示,由于这是一个原始文件,我们需要向objdump命令提供更多信息以正确反汇编它。
上述命令中使用的选项是:
- -D:反汇编所有部分
- -b : 目标码格式,我们说是二进制
- -m : 代码是哪个架构的,我们说它是i386
而从结果中,我们可以看到文件中的shellcode在输出中被正确打印出来了。
4. 使用gdb命令
如果我们需要调试某些东西,gdb 是首选工具。使用gdb,我们也可以反汇编代码:
$ gdb test
(gdb) disassemble main
Dump of assembler code for function main:
0x00000000000005fa <+0>: push %rbp
0x00000000000005fb <+1>: mov %rsp,%rbp
0x00000000000005fe <+4>: movl $0x0,-0x4(%rbp)
0x0000000000000605 <+11>: addl $0x14,-0x4(%rbp)
0x0000000000000609 <+15>: nop
0x000000000000060a <+16>: pop %rbp
0x000000000000060b <+17>: retq
End of assembler dump.
(gdb) q
$
如上图,我们将二进制文件加载到gdb中,在main函数上执行 disassemble 命令就可以看到汇编代码了。
5. 使用ndisasm命令
ndisasm 实用程序随nasm包一起提供。主要用于反汇编shellcode。它可以反汇编二进制文件,但不能正确显示这些部分。所以很难弄清楚结构。 典型用法是:
ndisasm [-b16 | -b32] filename
让我们看一个示例,说明如何使用它来反汇编我们之前保存到文件中的一串机器代码:
$ ndisasm -b32 code
00000000 54 push esp
00000001 55 push ebp
00000002 90 nop
$
如上所示,我们将处理器模式传递为 32 位,并为此生成了汇编代码。