缓冲区溢出攻击的分析及防范策略
(1) 植入法:
攻击者向被攻击的程序输入一个字符串,程序会把这个字符串放到缓冲区里。这个字符串所包含的数据是可以在这个被攻击的硬件平台运行的指令流。在这里攻击者用被攻击程序的缓冲区来存放攻击代码,具体方式有以下两种差别:
a.攻击者不必为达到此目的而溢出任何缓冲区,可以找到足够的空间来放置攻击代码;
b.缓冲区可设在任何地方:堆栈(存放自动变量)、堆(动态分配区)和静态数据区(初始化或未初始化的数据)。
(2) 利用已经存在的代码
有时候攻击者所要的代码已经存在于被攻击的程序中了,攻击者所要做的只是对代码传递一些参数,然后使程序跳转到想要执行的代码那里。比如,共及代码要求执行“exec(‘bin/sh’)”,而在libc库中的代码执行“exec(arg)”,其中arg是一个指向字符串的指针参数,那么攻击者只要把传入的参数指针改向指向“/bin/sh”,然后调转到libc库中相应的指令序列即可。
2.控制程序转移到攻击代码的方法
所有这些方法都是在试图改变程序的执行流程,使之跳转到攻击代码。其基本特点就是给没有边界检查或有其他弱点的程序送出一个超长的缓冲区,以达到扰乱程序正常执行顺序的目的。通过溢出一个缓冲区,攻击者可以用几乎暴力的方法(穷尽法)改写相邻的程序空间面直接跳过系统的检查。
这里的分类基准是攻击者所寻求的缓冲区溢出的程序空间类型。原则上可以是任意的空间。比如起初的Morris Worm(莫尔斯蠕虫)就是使用了fingerd程序的缓冲区溢出,扰乱fingerd要执行的文件的名字。实际上许多的缓冲区溢出是用暴力的方法来寻求改变程序指针的。这类程序不同的地方就是程序空间的突破和内存空间的定位不同。一般来说,控制程序转移到攻击代码的方法有以下几种:
(1) 函数返回地址
每当一个函数调用发生时,调用者会在堆栈中留下函数返回地址,它包含了函数结束时返回的地址。攻击者通过溢出这些自动变量,使这个返回地址指向攻击代码,这样就通过改变程序的返回地址,当函数调用结束时,程序跳转到攻击者设定的地址,而不是原先的地址。这类的缓冲区进出被称为“stack smashing attack”,是目前常用的缓冲区溢出攻击方式。
(2) 函数指针
“Void(*foo)()”中声明了一个返回值为Void函数指针的变量foo。函数指针定位任何地址空间,所以攻击者只需在任何空间内的函数指针附近找到一个能够溢出的缓冲区,然后溢出来改变函数指针,当程序通过函数指针调用函数时,程序的流程就会发生改变而实现攻击者的目的。
(3) 长跳转缓冲区
在C语言中包含了一个简单的检验/恢复系统,称为“setjmp/longjmp”,意思是在检验点设定“setjmp(buffer)”,用longjmp(buffer)“来恢复检验点。然而,如果攻击时能够进入缓冲区的空间,那么“longjmp(buffer)”实际上是跳转到攻击者的代码。像函数指针一样,longjmp缓冲区能够指向任何地方,所以攻击者所要做的就是找到一个可供溢出的缓冲区。一个典型的例子就是Perl 5.003,攻击者首先进入用来恢复缓冲区溢出的longjmp缓冲区,然后诱导进入恢复模式,这样就使Perl的解释器跳转到攻击代码上了。
3.综合代码植入和流程控制技术
最简单和常见的溢出缓冲区攻击类型就是在一个字符串里综合了代码植入和激活记录。攻击者定位一个可供溢出的自动变量,然后向程序传递一个很大的字符串,在引发缓冲区溢出改变激活记录的同时植入了代码(因为C语言程序员通常在习惯上只为用户和参数开辟很小的缓冲区)。
代码植入和缓冲区溢出不一定要在一次动作内完成,攻击者可以在一个缓冲区内放置代码(这个时候并不能溢出缓冲区),然后攻击者通过溢出另一个缓冲区来转移程序的指针。这样的方法一般用来解决可供溢出的缓冲区不够大(不能放下全部的代码)。如果攻击者试图使用已经常驻的代码而不是从外部植入代码,他们通常必须把代码做为参数。举例说明,在libc(几乎所有的C程序都用它来连接)中的一部分代码段会执行“exec(something)”,其中的something就是参数,攻击者使用缓冲区溢出改变程序的参数,然后利用另一个缓冲区溢出,使程序指针指向libc中的特定的代码段。
三 缓冲区溢出攻击的防范策略
缓冲区溢出攻击的防范是和整个系统的安全性分不开的。如果整个网络系统的安全设计很差,则遭受缓冲区溢出攻击的机会也大大增加。针对缓冲区溢出,我们可以采取多种防范策略。
1.系统管理上的防范策略
(1) 关闭不需要的特权程序
由于缓冲区溢出只有在获得更高的特权时才有意义,所以带有特权的Unix
下的suid程序和Windows下由系统管理员启动的服务进程都经常是缓冲区溢出攻击的目标。这时候,关闭一些不必要的特权程序就可以降低被攻击的风险。如Solaris下的fdformat是个有缓冲区溢出漏洞的suid程序,因为这个格式化软盘的命令用的较少,最直接的措施是去掉这个程序或者去掉suid位。当有缓冲区溢出漏洞的程序还没有补丁时,就可以用这种方法。
(2) 及时给程序漏洞打补丁
这是漏洞出现后最迅速有效的补救措施。大部分的入侵是利用一些已被公布的漏洞达成的,如能及时补上这些漏洞,无疑极大的增强了系统抵抗攻击的能力。
这两种措施对管理员来说,代价都不是很高,但能很有效地防止住大部分的攻击企图。
2.软件开发过程中的防范策略
发生缓冲区溢出的主要及各要素是:数组没有边界检查而导致的缓冲区溢出;函数返回地址或函数指针被改变,使程序流程的改变成为可能;植入代码被成功的执行等等。
所以针对这些要素,从技术上我们就可以采取一定的措施。
(1)编写正确的代码
只要我们在所有拷贝数据的地方进行数据长度和有效性的检查,确保目标缓冲区中数据不越界并有效,则就可以避免缓冲区溢出,更不可能使程序跳转到恶意代码上。但是诸如C/C++自身是一种不进行强类型和长度检查的一种程序设计语言,而程序员在编写代码时由于开发速度和代码的简洁性,往往忽视了程序的健壮性,从而导致缓冲区溢出,因此我们必须从程序语言和系统结构方面加强防范。
很多不安全程序的出现是由于调用了一些不安全的库函数,这些库函数往往没有对数组边界进行检查。这些函数有strcpy()、sprintf()、strcat()等,所以一种简单的方法是利用grep搜索源程序,找出对这些函数的调用,然后代以更安全的函数,如strncpy()替换strcpy()。进一步的查找可以是检查更广范围的不安全操作,如在一个不定循环中对数组的赋值等。
可用的另一种措施是漏洞探测。利用一些工具,人为随机地产生一些缓冲区溢出来寻找代码的安全漏洞。已有这方面的一些高级的查错工具,如fault injection等。
(2)缓冲区不可执行
通过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被植入被攻击程序输入缓冲区的代码,这种技术被称为缓冲区不可执行技术。事实上,很多老的Unix系统都是这样设计的,但是近来的Unix和MS Windows系统为实现更好的性能和功能,往往在数据段中动态地放入可执行的代码。所以为了保持程序的兼容性不可能使得所有程序的数据段不可执行。但是我们可以设定堆栈数据段不可执行,这样就可以最大限度地保证了程序的兼容性。Linux和Solaris都发布了有关这方面的内核补丁。因为几乎没有任何合法的程序会在堆栈中存放代码,这种做法几乎不产生任何兼容性问题。
通过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被殖入被攻击程序输入缓冲区的代码,这种技术被称为非执行的缓冲区技术。事实上,很多老的Unix系统都是这样设计的,但是近来的Unix和MS Windows系统由于实现更好的性能和功能,往往在在数据段中动态地放入可执行的代码。所以为了保持程序的兼容性不可能使得所有程序的数据段不可执行。
Linux和Solaris也发布了有关这方面的内核补丁。因为几乎没有任何合法的程序会在堆栈中存放代码,这种做法几乎不产生任何兼容性问题,除了在Linux中的两个特例,这时可执行的代码必须被放入堆栈中:
Tags:
作者:佚名评论内容只代表网友观点,与本站立场无关!
评论摘要(共 0 条,得分 0 分,平均 0 分)
查看完整评论