linux缓冲区溢出保护机制说明

来源:互联网 发布:网络课程 知乎 编辑:程序博客网 时间:2024/06/05 00:08

http://blog.sina.com.cn/s/blog_63fe2708010171t3.html


再较新的linux版本中引入了一些缓冲区溢出保护机制,试图将缓冲区溢出的危害降到最低,这对于使用系统的我们来说当然是件好事,但是如果想通过系统来做缓冲区溢出实验则会遇到一些麻烦,下面通过《网络渗透技术》的一个例子对此进行说明。


代码如下:


 
#include
#include
char largebuff[] =
"1234512345123451234512345===ABCD";
void main (void)
{
    char smallbuff[16];
    strcpy (smallbuff, largebuff);
}
原理很简单,将一个长数组内容复制到一个小数组中,造成溢出。




1.内存地址随机化机制


在Ubuntu和其他基于Linux内核的系统中,目前都采用内存地址随机化的机制来初始化堆栈,这将会使得猜测具体的内存地址变得十分困难。


关闭内存地址随机化机制的方法是:


sysctl –w kernel.randomize_va_space=0


2.可执行程序的屏蔽保护机制


对于Federal系统,默认会执行可执行程序的屏蔽保护机制,该机制不允许执行存储在栈中的代码,这会使得缓冲区溢出攻击变得无效。而Ubuntu系统中默认没有采用这种机制。


关闭可执行程序的屏蔽保护机制的方法是:
sysctl –w kernel.exec-shield=0


此保护机制尚未在程序中找到直接证据,留着好好研究。


3.gcc编译器gs验证码机制


gcc编译器专门为防止缓冲区溢出而采取的保护措施,具体方法是gcc首先在缓冲区被写入之前在buf的结束地址之后返回地址之前放入随机的gs验证码,并在缓冲区写入操作结束时检验该值。通常缓冲区溢出会从低地址到高地址覆写内存,所以如果要覆写返回地址,则需要覆写该gs验证码。这样就可以通过比较写入前和写入后gs验证码的数据,判断是否产生溢出。


关闭gcc编译器gs验证码机制的方法是:


在gcc编译时采用-fno-stack-protector选项。
hufeng@hufeng:~$ gcc -z execstack simple_overflow.c -o simple_overflow
hufeng@hufeng:~$ gdb simple_overflow
(gdb) disassemble main
Dump of assembler code for function main:
   0x08048434 <+0>:    push    ebp
   0x08048435 <+1>:    mov     esp, ebp
   0x08048437 <+3>:    and    $0xfffffff0, esp
   0x0804843a <+6>:    sub    $0x30, esp
   0x0804843d <+9>:    mov     gs:0x14, eax
   0x08048443 <+15>:    mov     eax,0x2c( esp)
   0x08048447 <+19>:    xor     eax, eax
   0x08048449 <+21>:    mov    $0x804a040, eax
   0x0804844e <+26>:    mov     eax,0x4( esp)
   0x08048452 <+30>:    lea    0x1c( esp), eax
   0x08048456 <+34>:    mov     eax,( esp)
   0x08048459 <+37>:    call   0x8048350
   0x0804845e <+42>:    mov    0x2c( esp), eax
   0x08048462 <+46>:    xor     gs:0x14, eax
   0x08048469 <+53>:    je     0x8048470
   0x0804846b <+55>:    call   0x8048340 <__stack_chk_fail@plt>
   0x08048470 <+60>:    leave  
   0x08048471 <+61>:    ret    
End of assembler dump.




hufeng@hufeng:~$ gcc -fno-stack-protector -z execstack simple_overflow.c -o simple_overflow
hufeng@hufeng:~$ gdb simple_overflow
(gdb) disassemble main
Dump of assembler code for function main:
   0x080483e4 <+0>:    push    ebp
   0x080483e5 <+1>:    mov     esp, ebp
   0x080483e7 <+3>:    and    $0xfffffff0, esp
   0x080483ea <+6>:    sub    $0x20, esp
   0x080483ed <+9>:    mov    $0x804a040, eax
   0x080483f2 <+14>:    mov     eax,0x4( esp)
   0x080483f6 <+18>:    lea    0x10( esp), eax
   0x080483fa <+22>:    mov     eax,( esp)
   0x080483fd <+25>:    call   0x8048300
   0x08048402 <+30>:    leave  
   0x08048403 <+31>:    ret    
End of assembler dump.


比较上面在两段main函数的汇编代码,可以发现主要的区别就在于下面几条语句:
mov     gs:0x14, eax
mov     eax,0x2c( esp)


mov    0x2c( esp), eax
xor     gs:0x14, eax
je     0x8048470
call   0x8048340 <__stack_chk_fail@plt>


前两句从gs:0x14取值放入0x2c( esp)中,程序执行后再将0x2c( esp)的值与gs:0x14处的值对比,如果发生变化则说明缓冲区溢出,第一段汇编代码中为栈留出了足够的空间,原本不需要用到的地址如果发生变化则说明缓冲区溢出,然后调用__stack_chk_fail@plt函数进行处理。




4.ld链接器堆栈段不可执行机制


ld链接器在链接程序的时候,如果所有的.o文件的堆栈段都标记为不可执行,那么整个库的堆栈段才会被标记为不可执行;相反,即使只有一个.0文件的堆栈段被标记为可执行,那么整个库的堆栈段将被标记为可执行。检查堆栈段可执行性的方法是:


如果是检查ELF库:readelf -lW $BIN | grep GNU_STACK查看是否有E标记


如果是检查生成的.o文件:scanelf -e $BIN查看是否有X标记


ld链接器如果将堆栈段标记为不可执行,即使控制了eip产生了跳转,依然会产生段错误。


关闭ld链接器不可执行机制的方法是:


在gcc编译时采用-z execstack选项。


见下面例子:
hufeng@hufeng:~$ gcc -fno-stack-protector -z execstack simple_overflow.c -o simple_overflow
hufeng@hufeng:~$ readelf -lW simple_overflow | grep GNU_STACK
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
hufeng@hufeng:~$ gcc -fno-stack-protector simple_overflow.c -o simple_overflow
hufeng@hufeng:~$ readelf -lW simple_overflow | grep GNU_STACK  
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4


参考:
http://www.linuxidc.com/Linux/2011-10/44534.htm
http://www.win.tue.nl/~aeb/linux/hh/protection.html

0 0