|
段错误的产生一般是:
1. 用户程序访问一个虚拟地址,MMU 对这个地址进行检查,确定无权访问。
2. MMU 产生异常,CPU 从用户模式切换到特权模式,然后跳转到内核代码中执行异常服务程序。
3. 内核将这个异常解释为段错误,并把引发异常的进程终止掉。
下面的程序会产生段错误:
#include <stdio.h> | | void return_input (void) | { | char array[30]; | | gets (array); | printf ("%s | ", array); | } | | int main() | { | return_input(); | | return 0; | } |
|
编译程序方法: # cc -mpreferred-stack-boundary=2 -g stack.c -o stack
/tmp/ccgRJGyd.o: In function `return_input':
/root/shellcode/stack.c:7: warning: the `gets' function is dangerous and should not be used.
使用 -mpreferred-stack-boundary=2 选项表示优先栈边界编译,这里 2 表示栈以双字节为单位递增或递减,在 x86 体系结构里,堆栈是递减的。
程序编译后,可以生成可执行文件,但有警告 gets() 是个不安全的函数,它的不安全在于它是不对边界进行检查的,但这里正是想利用这个函数进行栈溢出的研究。
下面运行程序使发生段错误:
[root@centos shellcode]# ./stack
AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDD
AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDD
段错误
程序里,数组只能容纳 30 个字符,但我们输入了 40 个字符,越界后会发生的段错误预示着什么呢?在调试里查明这它。
(gdb) disas return_input
Dump of assembler code for function return_input:
0x080483d4 <return_input+0>: push %ebp
0x080483d5 <return_input+1>: mov %esp,%ebp
0x080483d7 <return_input+3>: sub $0x24,%esp
0x080483da <return_input+6>: lea -0x1e(%ebp),%eax
0x080483dd <return_input+9>: mov %eax,(%esp)
0x080483e0 <return_input+12>: call 0x80482c4 <[email=gets@plt]gets@plt[/email]> # 这里调用了 gets() 函数
0x080483e5 <return_input+17>: lea -0x1e(%ebp),%eax
0x080483e8 <return_input+20>: mov %eax,(%esp)
0x080483eb <return_input+23>: call 0x80482e4 <[email=puts@plt]puts@plt[/email]>
0x080483f0 <return_input+28>: leave
0x080483f1 <return_input+29>: ret # 这里 return_input 函数返回
End of assembler dump.
对于上面的汇编代码,分别在 0x080483e0 和 0x080483f1 下断点:
(gdb) break *0x080483e0
Breakpoint 1 at 0x80483e0: file stack.c, line 7.
(gdb) break *0x080483f1
Breakpoint 2 at 0x80483f1: file stack.c, line 9.
继续运行程序,程序会落在第一个断点处,在接受我们的输入后,会继续走到第二个断点处:
(gdb) run
Starting program: /root/shellcode/stack
Breakpoint 1, 0x080483e0 in return_input () at stack.c:7
7 gets (array);
不忙往下走输入数据,先看一下 main() 函数的代码和栈布局。
main() 函数汇编代码:
(gdb) disas main
Dump of assembler code for function main:
0x080483f2 <main+0>: push %ebp
0x080483f3 <main+1>: mov %esp,%ebp
0x080483f5 <main+3>: call 0x80483d4 <return_input>
0x080483fa <main+8>: mov $0x0,%eax
0x080483ff <main+13>: pop %ebp
0x08048400 <main+14>: ret
End of assembler dump.
栈布局:
(gdb) x/20x $esp
0xbfffcc8c: 0xbfffcc92 0x0012d175 0xbfffcd4c 0xbfffccb8
0xbfffcc9c: 0x00258ff4 0x00d50600 0x08048420 0x00000000
0xbfffccac: 0x00258ff4 0xbfffccb8 0x080483fa 0xbfffcd18
0xbfffccbc: 0x00116e9c 0x00000001 0xbfffcd44 0xbfffcd4c
0xbfffcccc: 0x00d5e810 0x00000000 0x00000001 0x00000001
上面的堆栈内容中,0xbfffccb8 是保存的 EBP,0x080483fa 正是 return_input() 返回的地址,也就是它的下一条指令。关于堆栈布局方面的内容参考:http://www.groad.net/bbs/read.php?tid-2602.html
现在可以执行上面的程序了:
(gdb) c
Continuing.
AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDD
AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDD
Breakpoint 2, 0x080483f1 in return_input () at stack.c:9
9 }
这时候,来到了第二个断点处,也就是 ret 指令,如果一切正常,它会返回到上面的 0x080483fa 这个地址处!现在再看一下栈上的情形:
(gdb) x/20x 0xbfffcc8c
0xbfffcc8c: 0xbfffcc92 0x4141d175 0x41414141 0x41414141
0xbfffcc9c: 0x42424242 0x42424242 0x43434242 0x43434343
0xbfffccac: 0x43434343 0x44444444 0x44444444 0xbf004444
0xbfffccbc: 0x00116e9c 0x00000001 0xbfffcd44 0xbfffcd4c
0xbfffcccc: 0x00d5e810 0x00000000 0x00000001 0x00000001
唉哟,原来的返回地址 0x080483fa 不见了!取而代之的是 0x44444444 --- 正是大写字符 DDDD 十六进制形式。到了这里,我们知道,MMU 经过检查此地址发现这个地址对于我们这个应用程序是无权访问的,所以触发了段错误!
综上可见,这就是栈溢出!对于这样的溢出,或许是可以搞点事情的。 |
|