ITKeyword,专注技术干货聚合推荐

注册 | 登录

浅谈缓冲区溢出(整理自《深入理解计算机系统》)

snowq 分享于 2007-08-17

推荐:《深入理解计算机系统》AT&T x86汇编学习

前言:文章采用AT&T格式的汇编(也叫做ATT汇编,AT&T是运行贝尔实验室多年的公司),是GCC,OBJDUM等工具默认的汇编格式。ATT汇编与INTEL汇编的不同在于:1.Inte

2020腾讯云“6.18”活动开始了!!!(巨大优惠重现!4核8G,5M带宽 1999元/3年),
地址https://cloud.tencent.com/act/cps/redirect?redirect=1059

2020阿里云最低价产品入口,含代金券(新老用户有优惠),
地址https://www.aliyun.com/minisite/goods

一、综述

在x86体系结构下, C++/C对数组引用不进行任何边界检查,而且函数调用过程中局部变量和状态信息(例如寄存器值和返回指针)都存放在栈中。当这两种情况结合到一起的时候,就有可能导致严重的程序错误,一个对越界的数组元素的写操作会破坏存储在栈中的状态信息,而当程序使用这个被破坏的状态,并试图重新加载寄存器或执行ret指令(返回调用函数)时,就会产生严重的错误。

二、C++/C中的数组

在C++/C语言中,数组元素被依次分配在一组连续的地址空间中,且长度固定不变。在表达式中使用数组名时,该名字会自动转换为指向数组第一个元素的指针。当在编程时没有检查数组下标,并且引用了越出数组边界的元素时,就会导致缓冲区溢出。除了程序员自己注意细节,并彻底测试自己的程序之外,没有别的办法可以防止数组越界。

三、x86体系中的栈结构

在x86体系结构中,栈是向低地址方向增长的,入栈是减小栈指针的值,并存放到寄存器中,出栈则是从存储器中读,并增加栈指针的值。

 

           

 

                               图1 栈结构图

 

四、例程分析

char  *gets(char  *s)

{

         int  c;

         char  *dest = s;

         while((c=getchar() != ’/n’ && c != EOF)

         *dest ++= c;

         *dest ++= ’/0’;

         If(c == EOF)

                   Return  NULL;

         return  s;

推荐:深入理解计算机系统-笔记1

CS:APP2e的辅助资源: 【1】CS:APP2e主页:http://csapp.cs.cmu.edu/public/students.html主页上的内容非常丰富,超乎你的想象,等你去探索。 【2】CS:APP2e

}

 

void  echo()

{

char  buf[4];

gets(buf);

puts(buf);

}

我们来看这个gets函数的实现,它从标准输入读入一行,在遇到一个“/n”字符或者某个错误情况时停止。它将这个字符串拷贝到参数s指明的位置,并在字符串结尾加上null字符。在函数echo中调用了gets,读入标准输入,并送出到标准输出。

gets的问题出在它使用了一个字符串数组的指针,没有办法确定是否为保存整个字符串分配了足够的空间。在我们的echo函数中,我们故意将缓冲区设得非常小,只有四个字节长。任何长度超过3个字符的字符串都会导致写越界。

          

                             图2  栈示意图

如图所示,字符数组保存在状态信息下方的四个字节 ,所有对buf[4]~buf[7]的写操作都会破坏状态信息的保存值。当程序随后试图以它为栈指针进行恢复时,所有后来的栈引用都是非法的。而所有对buf[8]~buf[11]的写操作都会导致返回地址被破坏,当在函数结尾执行ret指令时,程序会“返回”到错误的地址。这个示例说明了缓冲区溢出可能导致程序出现严重的错误。

五、利用缓冲区溢出的计算机网络攻击

缓冲区溢出的一个更加致命的使用是让程序执行它本来不愿意执行的函数,利用这一漏洞可以通过计算机网络攻击系统的安全。通常,输入给程序一个字符串,这个字符串包含一些可执行代码的字节编码,称为漏洞入侵代码(exploit code)。另外,还有一些字节会用一个指向缓冲区中那些可执行代码的指针覆盖掉返回指针。所以,执行ret指令的效果就是跳到漏洞入侵代码段。

第一种攻击形式为漏洞入侵代码使用系统调用启动一个shell程序,提供给攻击者一组操作系统的函数。另一种攻击形式,漏洞入侵代码会执行一些未授权的任务,修复对栈的破坏,然后第二次执行ret指令,看上去好像正常返回给调用者。

六、  改进程序设计

1、  在程序的自由存储区中创建并使用动态分配的数组,在C语言中使用malloc(C++中为new)操作符实现,在自由存储空间中创建的动态数组对象是没有名字的,程序员只能通过其地址间接访问堆中的对象。如果程序员能够准备计算出运行时需要的数组长度,就不必再担心因数组变量具有固定的长度而造成的溢出问题。

2、  在C++程序中,采用vector类型和迭代器取代一般的数组和指针访问。利用end操作可以返回迭代器指向vector“末端元素的下一个”,从而充当一个“哨兵”的作用,防止越界调用。

七、  参考文献

1、  Randal E. Bryant等,”Computer System A Programmer’s Perspective”

2、  Stanley B. Lippman等,”C++ Primer (Fourth Edition)”

 

推荐:深入理解计算机系统笔记

我的博客上的比这个排版显示的更好一些,特别是图片 http://notelzg.github.io/2016/06/29/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E8%AE%A1%E7%AE%97%E6%9C%BA%E

一、综述 在x86体系结构下, C++/C对数组引用不进行任何边界检查,而且函数调用过程中局部变量和状态信息(例如寄存器值和返回指针)都存放在栈中。当这两种情况结合到一起的时候,就有可能导致

相关阅读排行


相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。