逆向

1
2
3
# -g 产生调试信息
# -c 预编译,编译,汇编 生成obj文件
$ gcc -g -c file

objdump

1
$ objdump -D -M intel file.bin | grep main.: -A20

Mac OS X 上 objdump二进制文件实际上可能是指向llvm的 objdump 的链接,具有不同的命令行选项和行为。比如在我的Mac上’/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/objdump’

所以例子的 -M intel 无效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-d 
--disassemble
# 从objfile中反汇编那些特定指令机器码的section。

-D
--disassemble-all
# 与 -d 类似,但反汇编所有section.

-M intel
# 汇编代码以Intel语法显示

--x86-asm-syntax=intel
# OSX上汇编代码以Intel语法显示

| grep main.: -A20
# 显示main函数20行

国际惯例,先来感受 hello world

1
2
3
4
5
6
7
8
9
// hello.c

#include <stdio.h>

int main()
{
printf("Hello World\n");
return 0;
}

gcc -g -c hello.c生成hello.o

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
➜  hello  objdump  --source --x86-asm-syntax=intel hello.o

hello.o: file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
_main:
; {
0: 55 push rbp
1: 48 89 e5 mov rbp, rsp
4: 48 83 ec 10 sub rsp, 16
8: c7 45 fc 00 00 00 00 mov dword ptr [rbp - 4], 0
; printf("Hello World\n");
f: 48 8d 3d 14 00 00 00 lea rdi, [rip + 20]
16: b0 00 mov al, 0
18: e8 00 00 00 00 call 0 <_main+0x1d>
1d: 31 c9 xor ecx, ecx
1f: 89 45 f8 mov dword ptr [rbp - 8], eax
; return 0;
22: 89 c8 mov eax, ecx
24: 48 83 c4 10 add rsp, 16
28: 5d pop rbp
29: c3 ret

下面开始 通过一个例子函数调用

add.c

1
2
3
4
5
6
7
8
9
10
11
12
13
// add.c
//返回两个参数值之和的函数
int addNum(int a, int b)
{
return a + b;
}

//调用addNum函数的函数
void myFunc()
{
int c;
c = addNum(123, 456);
}

gcc -g -c add.c 得到add.o

objdump --source --x86-asm-syntax=intel add.o 得到反汇编Intel 格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
add.o:	file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
_addNum:
; {
0: 55 push rbp
1: 48 89 e5 mov rbp, rsp
4: 89 7d fc mov dword ptr [rbp - 4], edi
7: 89 75 f8 mov dword ptr [rbp - 8], esi
; return a + b;
a: 8b 75 fc mov esi, dword ptr [rbp - 4]
d: 03 75 f8 add esi, dword ptr [rbp - 8]
10: 89 f0 mov eax, esi
12: 5d pop rbp
13: c3 ret
14: 66 2e 0f 1f 84 00 00 00 00 00 nop word ptr cs:[rax + rax]
1e: 66 90 nop

_myFunc:
; {
20: 55 push rbp
21: 48 89 e5 mov rbp, rsp
24: 48 83 ec 10 sub rsp, 16
; c = addNum(123, 456);
28: bf 7b 00 00 00 mov edi, 123
2d: be c8 01 00 00 mov esi, 456
32: e8 00 00 00 00 call 0 <_myFunc+0x17>
37: 89 45 fc mov dword ptr [rbp - 4], eax
; }
3a: 48 83 c4 10 add rsp, 16
3e: 5d pop rbp
3f: c3 ret

objdump --source add.o 得到AT&T格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
add.o:	file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
_addNum:
; {
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: 89 7d fc movl %edi, -4(%rbp)
7: 89 75 f8 movl %esi, -8(%rbp)
; return a + b;
a: 8b 75 fc movl -4(%rbp), %esi
d: 03 75 f8 addl -8(%rbp), %esi
10: 89 f0 movl %esi, %eax
12: 5d popq %rbp
13: c3 retq
14: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:(%rax,%rax)
1e: 66 90 nop

_myFunc:
; {
20: 55 pushq %rbp
21: 48 89 e5 movq %rsp, %rbp
24: 48 83 ec 10 subq $16, %rsp
; c = addNum(123, 456);
28: bf 7b 00 00 00 movl $123, %edi
2d: be c8 01 00 00 movl $456, %esi
32: e8 00 00 00 00 callq 0 <_myFunc+0x17>
37: 89 45 fc movl %eax, -4(%rbp)
; }
3a: 48 83 c4 10 addq $16, %rsp
3e: 5d popq %rbp
3f: c3 retq

Mac电脑,就以AT&T为例了

热身准备

操作码 操作数 功能
mov A, B 把B的值赋给A
and A, B 把A的值与B的值相加,结果赋给A
push A 把A的值存储在栈中
pop A 从栈中读取值,并将其赋给A
call A 调用函数
ret 将返回到函数的调用源

CPU和内存的关系

字节序

大端法:最高有效位在最前面

小端法:最低有效位在最前面

内存地址是一致的,不同的是从高还是低位开始‘装入’

下面举例,一个int(32位)类型变量,位于地址0x100处,十六进制值为0x01234567.地址范围0x100~0x·03的字节顺序依赖于机器的类型:

0x100 0x101 0x102 0x103
大端法 01 23 45 67
小端法 67 45 23 01

通常

Intel机使用小端模式

ARM 可以按照大端法或小端法,一旦选定了也就固定下来了。我们熟知的移动操作系统Android和iOS运行小端模式

程序运行时,会在内存上申请栈空间。存储顺序为LIFO。对栈进行读写的内存地址是由esp进行管理的。push和pop指令运行后,esp的值会自动进行更新

Hopper

ida64

参考资料

x86 assembly language

objdump-wiki

linux-objdump

Linux命令大全

https://wiki.cdot.senecacollege.ca/wiki/X86_64_Register_and_Instruction_Quick_Start