初衷
通过反汇编一个简单的C程序,分析汇编代码并理解计算机是如何工作的。
C程序名称为main.c,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14int g(int x)
{
return x + 3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8) + 1;
}
生成反汇编的命令为:
1 | gcc –S –o main.s main.c -m32 |
其中-m32表示安全32位机器进行反汇编。
得到一个main.s的文件,其中代码内容为: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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 .file "main.c"
.text
.globl g
.type g, @function
g:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size g, .-g
.globl f
.type f, @function
f:
.LFB1:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1:
.size f, .-f
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl $8, (%esp)
call f
addl $1, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
分析
得到这样一份文件,首先不去管前面带“ . ”的字符,即把前面带点的内容全部去掉,得到如下精简代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
ret
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $8, (%esp)
call f
addl $1, %eax
leave
ret
准备知识(一)
在分析这段代码前,需要重点理解下面知识。1
2
3
4
5movl %eax,%edx edx=eax; register mode
movl $0x123,%edx edx=0x123; immediate
movl 0x123,%edx edx=*(int32_t*)0x123; direct
movl (%ebx),%edx edx=*(int32_t*)ebx; indirect
movl 4(%ebx),%edx edx=*(int32_t*)(ebx+4);displaced
下面逐条分析:
第一句是寄存器之间直接赋值,把eax的值赋给edx,“l”表示是32位。
第二句是立即数赋值,把十六进制表示的123赋值给edx。
第三句是直接赋值,把内存地址为0x123处的内容赋值为edx。
第四句是间接赋值,把ebx所指的内存地址的内容赋值为edx。
第五句是偏移赋值,把ebx所指的内存地址向上偏移4个字节的内容赋值给edx。
准备知识(二)
下面给出一组常见指令的等价转换:
1 | pushl %eax |
1 | popl %eax |
1 | call 0x12345 |
星号表示eip的值不能直接被修改1
2
3ret
等价于:
pop %eip(*)
函数返回值一般放在eax寄存器中1
2
3
4enter
等价于:
pushl %ebp
movl %esp,%ebp
每一个进入函数时必做的开始序曲工作1
2
3
4leave
等价于:
movl %ebx,%esp
popl %ebp
每一个出函数时必做的收尾工作
运行栈是向下增长的
分析过程
如下图所示:
总结
理解函数调用过程,对信息安全是非常重要的。
【版权声明】
本文首发于戚名钰的博客,欢迎转载,但是必须保留本文的署名戚名钰(包含链接)。如您有任何商业合作或者授权方面的协商,请给我留言:qimingyu.security@foxmail.com
欢迎关注我的微信公众号:科技锐新