1. 栈溢出的原因

栈溢出(stack-based buffer overflows)算是安全界常见的漏洞。一方面因为程序员的疏忽,使用了 strcpy、sprintf 等不安全的函数,增加了栈溢出漏洞的可能。另一方面,因为栈上保存了函数的返回地址等信息,因此如果攻击者能任意覆盖栈上的数据,通常情况下就意味着他能修改程序的执行流程,从而造成更大的破坏。这种攻击方法就是栈溢出攻击(stack smashing attacks)
        栈是从高地址到低地址增长的。 

栈溢出攻击的原因是由于程序中缺少错误检测,另外对缓冲区的潜在操作(比如字符串的复制)都是从内存低址到高址,而函数调用的返回地址往往就在缓冲区的上方(当前栈底),这为我们覆盖返回地址提供了条件。下面是stack smashing attacks示意图:


es栈内存溢出 栈内存溢出的情况_es栈内存溢出



 

   下面是一个存在栈溢出的DEMO:

#include <stdio.h>
           #include <string.h>           int bof(FILE *badfile){
            char buffer[20];
            fread(buffer, sizeof(char), 100, badfile); 
            return 1; 
          }          int main(){
            FILE *badfile;
            badfile = fopen("badfile", "r");
            bof(badfile);            printf("Returned Properly\n");
            fclose(badfile);
            return 0;
         }



         DEMO的逻辑很简单,就是从badfile文件中读取最长100字节的数据,然而buffer的长度只有20字节,所以这里是有可能发现栈溢出的。


下面是在cygwin的环境下编译出来的汇编代码(我已经把一些对逻辑理解无关的细节去掉):


_main:
 pushl %ebp
 movl %esp, %ebp
 andl $-16, %esp
 subl $32, %esp
 call ___main
 movl $LC0, 4(%esp)
 movl $LC1, (%esp)
 call _fopen
movl %eax, 28(%esp)
movl 28(%esp), %eax
movl %eax, (%esp)
call _bof
 movl $LC2, (%esp)
 call _puts
 movl 28(%esp), %eax
 movl %eax, (%esp)
 call _fclose
 movl $0, %eax
 leave
 ret _bof:
pushl %ebp
movl %esp, %ebp
 subl $56, %esp
 movl 8(%ebp), %eax
 movl %eax, 12(%esp)
 movl $100, 8(%esp)
 movl $1, 4(%esp)
 leal -28(%ebp), %eax
 movl %eax, (%esp)
 call _fread
 movl $1, %eax
leave
ret



       我们只关注从main进入bof以及bof执行完毕后返回main这个过程。


  • 在调用从call __fopen开始看,在进入__bof前,badfile地址已经入栈。
  • call _bof语句的作用则是把下一条指令(movl $LC2, (%esp))入栈,也就是_bof执行完毕后的返回地址
  • 在进入_bof后,第一时间把ebp入栈,ebp是当前栈底,用于恢复esp的。

       整个堆栈的内存布局如下所示:



es栈内存溢出 栈内存溢出的情况_堆栈_02


       从分布图可以看到,系统实际分配给buffer的长度是28字节,接下来就是旧的栈底地址,bof返回地址和badfile地址。因此当badfile的内容长度是低于28字节的情况下,程序依然可以正常运行。但当badfile的内容长度超出28字节,就会直接把old EBP和ret address覆盖掉,这就达到了修改返回地址的目的了。


2. 栈溢出漏洞的防护:

        stack smashing attacks并不是无敌的,其对抗技术就是DEP(Data Execution Prevention )ASLR(Address Space Layout Radomization),通过这两种技术的保护下Stack smashing attacks一定程度上揭制。

         DEP: 把stack 段设置为 不可执行。

         另一种针对stack smashing 的防护技术就是 : stack canary.


2.1  stack canary



要检测对函数栈的破坏,需要修改函数栈的组织,在缓冲区和控制信息(如 EBP 等)间插入一个 canary word。这样,当缓冲区被溢出时,在返回地址被覆盖之前 canary word 会首先被覆盖。通过检查 canary word 的值是否被修改,就可以判断是否发生了溢出攻击。



3. GCC 中使用 stack canary 技术的堆栈保护:


Stack Guard 是第一个使用 Canaries 探测的堆栈保护实现,它于 1997 年作为 GCC 的一个扩展发布。最初版本的 Stack Guard 使用 0x00000000 作为 canary word。尽管很多人建议把 Stack Guard 纳入 GCC,作为 GCC 的一部分来提供堆栈保护。但实际上,GCC 3.x 没有实现任何的堆栈保护。直到 GCC 4.1 堆栈保护才被加入,并且 GCC4.1 所采用的堆栈保护实现并非 Stack Guard,而是 Stack-smashing Protection(SSP,又称 ProPolice)。

SSP 在 Stack Guard 的基础上进行了改进和提高。它是由 IBM 的工程师 Hiroaki Rtoh 开发并维护的。与 Stack Guard 相比,SSP 保护函数返回地址的同时还保护了栈中的 EBP 等信息。此外,SSP 还有意将局部变量中的数组放在函数栈的高地址,而将其他变量放在低地址。这样就使得通过溢出一个数组来修改其他变量(比如一个函数指针)变得更为困难。


GCC与 stack 保护相关的编译选项:


CC 4.1 中三个与堆栈保护有关的编译选项

-fstack-protector:

启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码。

-fstack-protector-all:

启用堆栈保护,为所有函数插入保护代码。

-fno-stack-protector:

禁用堆栈保护。