| 
 | 
 
段错误的产生一般是:  
 
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 经过检查此地址发现这个地址对于我们这个应用程序是无权访问的,所以触发了段错误!  
 
综上可见,这就是栈溢出!对于这样的溢出,或许是可以搞点事情的。 |   
 
 
 
 |