(2)缓冲区不可执行
通过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被植入被攻击程序输入缓冲区的代码,这种技术被称为缓冲区不可执行技术。事实上,很多老的Unix系统都是这样设计的,但是近来的Unix和MS Windows系统为实现更好的性能和功能,往往在数据段中动态地放入可执行的代码。所以为了保持程序的兼容性不可能使得所有程序的数据段不可执行。但是我们可以设定堆栈数据段不可执行,这样就可以最大限度地保证了程序的兼容性。Linux和Solaris都发布了有关这方面的内核补丁。因为几乎没有任何合法的程序会在堆栈中存放代码,这种做法几乎不产生任何兼容性
问题。
通过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被殖入被攻击程序输入缓冲区的代码,这种技术被称为非执行的缓冲区技术。事实上,很多老的Unix系统都是这样设计的,但是近来的Unix和MS Windows系统由于实现更好的性能和功能,往往在在数据段中动态地放入可执行的代码。所以为了保持程序的兼容性不可能使得所有程序的数据段不可执行。
Linux和Solaris也发布了有关这方面的内核补丁。因为几乎没有任何合法的程序会在堆栈中存放代码,这种做法几乎不产生任何兼容性
问题,除了在Linux中的两个特例,这时可执行的代码必须被放入堆栈中:
a.信号传递:
Linux通过向进程堆栈释放代码然后引发中断来执行在堆栈中的代码来实现向进程发送Unix信号。非执行缓冲区的补丁在发送信号的时候是允许缓冲区可执行的。
b.GCC的在线重用:
研究发现gcc在堆栈区里放置了可执行的代码作为在线重用之用。然而,关闭这个功能并不产生任何问题,只有部分功能似乎不能使用。
非执行堆栈的保护可以有效地对付把代码植入自动变量的缓冲区溢出攻击,而对于其他形式的攻击则没有效果。通过引用一个驻留的程序的指针,就可以跳过这种保护措施。其他的攻击可以采用把代码殖入堆或者静态数据段中来跳过保护。
(3)改进C语言函数库
C语言中存在缓冲区溢出攻击隐患的系统函数有很多。例如gets(),sprintf(),strcpy(),strcat(),fscanf(),scanf(),vsprintf()等。可以开发出更安全的封装了若干已知易受堆栈溢出攻击的库函数。修改后的库函数实现了原有功能,但在某种程度上可以确保任一缓冲区溢出都被控制在现有堆栈帧之内。
(4)数组边界检查
可以说缓冲区溢出的根本原因是没有数组边界检查,当数组被溢出的时候,一些关键的数据就有可能被修改,比如函数返回地址、过程帧指针、函数指针等。同时,攻击代码也可以被植入。
因此,对数组进行边界检查,使超长代码不可能植入,这样就完全没有了缓冲区溢出攻击产生的条件。只要数组不能被溢出,溢出攻击就无从谈起。
为了实现数组边界检查,则所有的对数组的读写操作都应当被检查,以确保对数组的操作在正确的范围内。最直接的
方法是检查所有的数组操作,但是会使性能下降很多,通常可以采用一些优化的技术来减少检查的次数。
(5)使堆栈向高地址方向增长
缓冲区溢出的一个重要要素是植入的代码成功地被执行。最常见的是被植入的代码放在堆栈区中。通过修改操作系统核心,在核心层引入保护机制,限制代码在堆栈区的执行,这样,缓冲区溢出攻击就不可能成功。
到
目前为止,我们讨论利用函数返回地址控制程序转移到攻击代码的攻击
方法时,有一个基本的前提,那就是当堆栈被压入数据时,栈顶向低地址方向增长,只有这样,缓冲区溢出时才可能覆盖低地址处的函数返回地址指针,从而控制程序转移到攻击代码。如果我们使用的机器堆栈压入数据时向高地址方向前进,那么无论缓冲区如何溢出,都不可能覆盖低地址处的函数返回地址指针,也就避免了缓冲区溢出攻击。但是这种方法仍然无法防范利用堆和静态数据段的缓冲区进行溢出的攻击。
(6)程序指针完整性检查
程序指针完整性检查是针对上述缓冲区溢出的另一个要素——阻止由于函数返回地址或函数指针的改变而导致的程序执行流程的改变。它的原理是在每次
在程序指针被引用之前先检测该指针是否已被恶意改动过,如果发现被改动,程序就拒绝执行。
因此,即使一个攻击者成功地改变程序的指针,由于系统事先检测到了指针的改变,因此这个指针不会被使用。与数组边界检查相比,这种方法不能解决所有的缓冲区溢出问题。但这种方法在性能上有很大的优势,而且兼容性也很好。
程序指针完整性检查大体上有三个
研究方向:第一,手写的堆栈检测;第二,堆栈保护;第三,保护指针。在手写的堆栈检测中会介绍Snarskii为FreeBSD开发了一套定制的能通过监测cpu堆栈来确定缓冲区溢出的libc。在堆栈保护中会介绍我们自己的堆栈保护方法所开发的一个编译器,它能够在函数调用的时候自动生成完整性检测代码。最后在保护指针中介绍正在开发中的指针保护方法,这种方法类似于堆栈保护,它提供对所有程序指针的完整性的保护。