美文网首页
笔记 | 计算机系统基础:09-不得不防的缓冲区溢出攻击!

笔记 | 计算机系统基础:09-不得不防的缓冲区溢出攻击!

作者: KPlayer | 来源:发表于2020-02-19 19:48 被阅读0次
    零. 课程要点:
    • 缓冲区溢出
    • 缓冲区溢出攻击
    • 缓冲区溢出防范

    学完了数据的内存结构,以及函数调用的底层过程,我们就来看一下在各种操作系统和应用软件中广泛存在的漏洞:缓冲区溢出。

    一. 缓冲区溢出

    首先,缓冲区是什么?

    缓冲区是一块连续的计算机内存区域,可以是堆栈(自动变量)、堆(动态内存)和静态数据区(全局或静态)。在C语言中,通常使用字符数组和内存分配函数实现缓冲区。如下图所示:

    可执行文件的存储器映像

    那么什么是缓冲区溢出?

    缓冲区溢出就是指当计算机程序向缓冲区内填充的数据位数超过了缓冲区本身的容量,溢出的数据覆盖在其它的数据或受保护空间中。

    由于C语言没有数组越界检查机制,当向局部数组缓冲区里写入的数据超过为其分配的大小时,就会发生缓冲区溢出。举个例子:

    double fun(int i)
    {
        volatile double d[1] = {3.14};
        volatile long int a[2];
        a[i] = 1073741824;
        return d[0];
    }
    

    当调用这个函数时,我们发现结果如下:

    fun(0) = 3.14
    fun(1) = 3.14
    fun(2) = 3.1399998664856
    fun(3) = 2.00000061035156
    fun(4):Segmentation fault
    

    上面就是一个典型的对数组的越界访问,为什么会出现上面的结果?只需要看一下栈中的数据内容就能明白:

    栈中的数据

    i=0i=1时,1073741824存储于正确的位置,但是当i=2时,a[2] = 1073741824,虽然我们认为没有定义a[2],但是根据前面的知识,我们知道数组元素可以使用指针来访问,因此对数组的引用没有边界约束,因此数据覆盖了d[0]的地方,造成了错误。如果i更大,那就有可能在受保护的地方写入了数据,造成程序崩溃甚至被劫持。

    二. 缓冲区溢出攻击

    造成缓冲区溢出的原因是没有对栈中作为缓冲区的数组的访问进行越界检查。缓冲区溢出是一种非常普遍、非常危险的漏洞,在各种操作系统、应用软件中广泛存在。利用缓冲区溢出攻击,可导致程序运行失败、系统关机、重新启动等后果。

    在前面的学习中我们知道在函数调用过程中,会把返回地址入栈,并在函数调用结束后取出并跳转。如果利用缓冲区溢出将函数返回地址修改为指向一段精心安排的恶意代码,从而改变程序正常流向,则可达到危害系统安全的目的。最常见的手段是通过制造缓冲区溢出使程序运行一个用户shell,再通过shell执行其它命令。若该程序有root或suid执行权限,则攻击者就获得一个有root权限的shell,进而可对系统进行任意操作。

    举个例子:

    缓冲区溢出攻击

    上面的代码原意是想打印出第一个参数,但是只分配了16个字节的大小。当main函数调用outputs时,会先保存返回地址,然后保存EBP旧值,重新形成栈底。然后分配16个字节的变量,并将参数入栈,准备调用strcpy函数。

    若strcpy复制了25个字符到buffer中,并将hacker首址置于结束符‘\0’前4个字节,则在执行strcpy后,hacker代码首址被置于main栈帧返回地址处,当执行outputs代码的ret指令时,便会转到hacker函数实施攻击。

    即假定hacker首址为0x08048411,那么我们构造一个字符串作为参数传入:

    char code[]=
    "0123456789ABCDEFXXXX"
    "\x11\x84\x04\x08"
    "\x00";
    
    int main(void) {
        char *argv[3];
        argv[0]="./test";
        argv[1]=code;
        argv[2]=NULL;
        execve(argv[0],argv,NULL);
        return 0;
    }
    

    就可以转到hacker函数实施攻击。

    三. 缓冲区溢出防范
    函数 危险性 解决方案
    gets 最高 禁用gets(buf),改用fgets(buf, size, stdin)
    strcpy 检查目标缓冲区大小,或改用strncpy,或动态分配目标缓冲区
    strcat 改用strncat
    sprintf 改用snprintf,或使用精度说明符
    scanf 使用精度说明符,或自己进行解析
    sscanf 使用精度说明符,或自己进行解析
    fscanf 使用精度说明符,或自己进行解析
    vfscanf 使用精度说明符,或自己进行解析
    vsprintf 改为使用vsnprintf,或使用精度说明符
    vscanf 使用精度说明符,或自己进行解析
    vsscanf 使用精度说明符,或自己进行解析
    streadd 确保分配的目标参数缓冲区大小是源参数大小的四倍
    strecpy 确保分配的目标参数缓冲区大小是源参数大小的四倍
    strtrns 手工检查目标缓冲区大小是否至少与源字符串相等
    getenv 不可假定特殊环境变量的长度
    realpath 高(或稍低,实现依赖) 分配缓冲区大小为PATH_MAX字节,并手工检查参数以确保输入参数和输出参数均不超过PATH_MAX
    syslog 高(或稍低,实现依赖) 将字符串输入传递给该函数之前,将所有字符串输入截成合理大小
    getopt 高(或稍低,实现依赖) 将字符串输入传递给该函数之前,将所有字符串输入截成合理大小
    getopt_long 高(或稍低,实现依赖) 将字符串输入传递给该函数之前,将所有字符串输入截成合理大小
    getpass 高(或稍低,实现依赖) 将字符串输入传递给该函数之前,将所有字符串输入截成合理大小
    getchar 若在循环中使用该函数,确保检查缓冲区边界
    fgetc 若在循环中使用该函数,确保检查缓冲区边界
    getc 若在循环中使用该函数,确保检查缓冲区边界
    read 若在循环中使用该函数,确保检查缓冲区边界
    bcopy 确保目标缓冲区不小于指定长度
    fgets 确保目标缓冲区不小于指定长度
    memcpy 确保目标缓冲区不小于指定长度
    snprintf 确保目标缓冲区不小于指定长度
    strccpy 确保目标缓冲区不小于指定长度
    strcadd 确保目标缓冲区不小于指定长度
    strncpy 确保目标缓冲区不小于指定长度
    vsnprintf 确保目标缓冲区不小于指定长度

    相关文章

      网友评论

          本文标题:笔记 | 计算机系统基础:09-不得不防的缓冲区溢出攻击!

          本文链接:https://www.haomeiwen.com/subject/akxnfhtx.html