首页 理论教育 内存安全问题:程序的缓冲区溢出隐患

内存安全问题:程序的缓冲区溢出隐患

时间:2023-06-09 理论教育 版权反馈
【摘要】:图3-3 程序运行时内存分配状态程序代码区是由程序确定的,主要包括只读数据和代码(指令)。例如,在3.1.2节的代码中,strcpy()直接将input中的内容复制到buffer中。存在缓冲区溢出隐患的程序是不确定的,这使得对它们的调试异常棘手。在Bugtraq的调查中,有2/3的被调查者认为缓冲区溢出漏洞是一个很严重的安全问题。

内存安全问题:程序的缓冲区溢出隐患

1.缓冲区溢出

在程序设计过程中,很多场合下都会用到缓冲区的概念。缓冲区保存于内存中,是一块连续的计算机内存区域,可以保存相同数据类型的多个实例。

例如,3.1.2节中的C语言代码片段,函数strcpy()有一个输入参数input,该函数的功能是将input中的内容复制到buffer中。在该段代码中,buffer数组可以保存多个字符(数据类型相同),可以称为是缓冲区。

在C语言中,由于字符数组中字符个数的不确定,最常见的产生缓冲区问题的场合就是对字符数组的操作。与C语言中所有变量一样,字符数组可以被声明为静态数组或动态数组,静态数组在程序加载时定位于数据段,动态数组在程序运行时定位于堆栈之中。一般的缓冲区溢出问题,主要是针对动态缓冲区溢出问题。

进程在内存中运行时,被分成3个区域:程序代码区、静态存储区和动态存储区(堆栈),如图3-3所示。

978-7-111-39843-1-Chapter03-4.jpg

图3-3 程序运行时内存分配状态

程序代码区是由程序确定的,主要包括只读数据和代码(指令)。在可执行文件中,该区域相当于文本段。一般情况下,程序确定了,这部分内容也就确定了。程序代码区中的内容通常是只读的,无法对其内容进行修改,任何对其的写入操作都会导致错误

静态存储区包含已初始化和未初始化的数据,但里面的数据都是静态的,如静态变量就存储在这个区域中。实际上,该区域是在编译时分配存储单元,程序结束时才回收。

动态存储区(堆栈)中的变量是在程序运行期间,根据程序需要,随时动态分配存储空间,如局部变量所占据的空间就属于该区域内,该区域最容易发生缓冲区溢出。

缓冲区溢出是一种非常普遍、非常危险的漏洞。它有多种英文名称,如Buffer Overflow、Buffer Overrun、Smash the Stack等,它也是一种比较有历史的漏洞,多个著名的漏洞报告都和缓冲区溢出有关,在各种操作系统、应用软件中广泛存在。缓冲区溢出可以导致的后果包括:程序运行失败;系统死机,重新启动;攻击者可能利用它执行非授权指令,取得系统相应权限,进而进行各种非法操作等。

在某些情况下,溢出的数据只是覆盖在一些不太重要的内存空间上,不会产生严重后果;但是一旦溢出的数据覆盖在合法数据上,就可能给系统带来巨大的危害。

例如,在3.1.2节的代码中,strcpy()直接将input中的内容复制到buffer中。只要input的长度大于16,就会造成buffer的溢出。当然,这里所说的缓冲区,实际上就存在于“堆栈”区内。存在像strcpy()这样问题的标淮函数还有strcat(),sprint(),gets(),scanf()等。

由于C语言具有不安全性的某些特性,它允许程序溢出缓冲区。在程序中,当发生缓冲区溢出时,可能会导致很多不可预料的行为,如程序的执行很奇怪、程序完全失败等。当然,也有可能出现另一种情况,程序碰巧没有覆盖重要数据,程序可以继续执行,而且在执行中没有任何明显不正常,但是可能具有安全隐患,该问题给软件的维护带来了难度。存在缓冲区溢出隐患的程序是不确定的,这使得对它们的调试异常棘手。

从攻击者角度来说,让用户程序崩溃,属于没有太多技术含量的攻击。最常见的手段是:通过输入一段数据,造成缓冲区溢出,让程序运行一个用户命令。极端情况下,如果该程序属于管理员具有针对系统的任意操作权限的情况,攻击者就可以利用这个漏洞造成更大的危害。

下面用一段代码来介绍缓冲区攻击的原理。用户可在Microsoft Visual C++6.0环境下调试、测试。

978-7-111-39843-1-Chapter03-5.jpg

978-7-111-39843-1-Chapter03-6.jpg

代码编译后,运行“P03_01Security”,输出结果为“Call funl,Buf fer=Security”,这是正常的。

如果输入一个长度大于10的字符串“P03_01abcdefghijklmnopqrstuv”,输出结果为“Call funl,Buf fer=abcdefghijklmnopqrstuv”,同样显示正常,但这是碰巧正常。

但如果输入“P03_01 abcdefghijklmnopqrstuvwxyz1234567890”,则无法显示输出结果,出现“应用程序错误”的提示。

出现该问题的原因是,由于输入的字符串太长,数组buffer容纳不下,但是也要将多余的字符写入堆栈。这些多余的字符没有分配合法的空间,就会覆盖堆栈中以前的内容。如果覆盖的内容仅仅是一些普通数据,表面上也不会出什么问题,只是会造成原有数据的丢失。但是,堆栈中还有一块区域专门保存着指令指针,存放下一个CPU指令存放的内存地址(可以理解为某个函数的地址)。如果该处被覆盖,系统会错误地将覆盖的新值当成某个指令来执行。这样,会出现难以预料的后果。(www.xing528.com)

2.堆溢出

和堆栈类似的另一个概念是堆,堆在底层和堆栈的运行机制不一样,堆的底层区域是程序员编程时想要动态获得内存的地方,一般通过new、malloc等函数来分配空间,在这种情况下,如果处理不当,则会产生堆溢出。

978-7-111-39843-1-Chapter03-7.jpg

代码编译后,运行,出现“应用程序错误”的提示。造成以上问题的原因是,由于向bufferl中复制数据时,多复制了6B,这6B会覆盖buffer2的结构,在free(buffer2)时会发生异常。攻击者如果“精心”构造这6B,也可以达到攻击的目的。

3.缓冲区溢出攻击及防范

由于缓冲区溢出攻击实现起来比较方便,所以其成为一种常见的安全攻击手段。因此,相对于其他漏洞,缓冲区溢出漏洞比较普遍。

1998年,Lincoln实验室针对入侵检测,对各种远程攻击方法进行了评估,最后得出了5种最严重的远程攻击方法,其中有两种是缓冲区溢出;而在1998年CERT的13份建议中,有9份是与缓冲区溢出有关的,在1999年,至少有半数的建议是和缓冲区溢出有关的。在Bugtraq的调查中,有2/3的被调查者认为缓冲区溢出漏洞是一个很严重的安全问题。

攻击者可以通过很多手段利用缓冲区溢出漏洞并且进行攻击。一般来说,利用缓冲区溢出攻击的目的在于使攻击者取得某些程序的控制权,执行某些权限功能,实现非法操作;极端情况下,如果该程序具有管理员的权限,那么就相当于控制了整个主机。

一般情况下,攻击者为了达到目的,将攻击行为分为两步进行。

第一步:在程序的地址空间放入一些攻击性的数据,故意让缓冲区溢出。一般有两种攻击方式:

1)直接输入法。攻击者向被攻击的程序输入一个字符串,让缓冲区溢出,程序把这个字符串放到缓冲区里。而该字符串中包含某个指令序列,攻击者因而猜测出已攻击的漏洞地址。

2)传递参数法。在这种情况下,攻击者想要执行的代码存在于漏洞程序中,只要传递一些参数就可以让它运行。

第二步:“精心”设计溢出的数据,让程序执行攻击者预想的功能,也就是改变程序的执行流程,跳转到攻击者安排的攻击代码。一般情况下有如下方法:

1)利用另一个函数的返回地址。函数调用时,堆栈中会留下函数结束时返回的地址,指示函数结束后会执行的功能。攻击者可以通过缓冲区溢出,改变程序的返回地址,使返回地址为攻击代码。

2)直接利用函数指针。由于函数指针可以用来定位函数的位置,攻击者只需在函数指针附近将缓冲区溢出,用一个攻击函数的指针来覆盖原有函数指针,就可达到攻击目的。

缓冲区溢出的原理是,通过将远程恶意代码注入到目标程序中以实现攻击。就程序本质而言,缓冲区溢出的根本原因是C等语言本身没有任何数组的界限检查和指针引用的检查,因此,检查边界将会有效。

解决缓冲区溢出的方法有如下几种:

1)积极检查边界。由于C语言允许任意的缓冲区溢出,没有任何的缓冲区溢出边界检测机制来进行限制,因此,一般情况下,所有开发者需要手动在自己的代码中添加边界检测机制。

2)不让攻击者执行缓冲区内的命令。这种方法使攻击者即使在被攻击者的缓冲区中植入了执行代码,也无法执行被植入的代码。

3)编写良好的代码。养成一个习惯,不要因为一味追求程序性能,而编写一些安全隐患较多的代码,特别是不要使用一些可能有漏洞的API,以减少漏洞发生的可能。

4)程序指针检查。程序指针检查不同于边界检查,程序指针检查是一旦修改了程序指针,就会被检测到,被改变的指针将不被使用。这样,即使一个攻击者成功地改变了程序的指针,因为系统事先检测到了指针的改变,这个指针将不会被使用,攻击者就达不到攻击的目的。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈