Linux内核分析(一)

初衷

通过反汇编一个简单的C程序,分析汇编代码并理解计算机是如何工作的。
C程序名称为main.c,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int 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
25
g:
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
5
movl %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
2
3
4
pushl %eax
等价于:
subl $4,%esp
movl %eax,(%esp)
1
2
3
4
popl %eax
等价于:
movl (%esp),%eax
addl $4,%esp
1
2
3
4
call 0x12345
等价于:
pushl %eip(*)
movl $0x12345,%eip(*)

星号表示eip的值不能直接被修改

1
2
3
ret
等价于:
pop %eip(*)

函数返回值一般放在eax寄存器中

1
2
3
4
enter 
等价于:
pushl %ebp
movl %esp,%ebp

每一个进入函数时必做的开始序曲工作

1
2
3
4
leave
等价于:
movl %ebx,%esp
popl %ebp

每一个出函数时必做的收尾工作
运行栈是向下增长的

分析过程

如下图所示:
1
2

总结

理解函数调用过程,对信息安全是非常重要的。


【版权声明】
本文首发于戚名钰的博客,欢迎转载,但是必须保留本文的署名戚名钰(包含链接)。如您有任何商业合作或者授权方面的协商,请给我留言:qimingyu.security@foxmail.com
欢迎关注我的微信公众号:科技锐新

kejiruixin

本文永久链接:http://qimingyu.github.io/2016/03/28/Linux内核分析(一)/

坚持原创技术分享,您的支持将鼓励我继续创作!

热评文章