1. 缓冲区溢出
缓冲区溢出是指当计算机向缓冲区填充数据时, 一旦超过了缓冲区本身的容量, 溢出的数据就会覆盖在合法数据上。 缓冲区溢出的原理: 由于字符串处理函数(gets、 strcpy 等)没有对数组的越界监视和限制, 结果覆盖了老堆栈数据。
例如:
在C 语言中, 指针和数组越界不保护是Buffer overflow 的根源, 而且, 在C 语言标准库中就有许多能提供溢出的函数, 如strcat( )、 strcpy( )、 sprintf( )、 vsprintf( )、 bcopy( )、 gets( )和scanf( )。
向一个有限空间的缓冲区置入过长的字符串, 有可能导致以下两种后果:
(1) 过长的字符串覆盖了相邻的存储单元, 引起程序运行失败, 严重的可导致系统崩溃。
(2) 利用这种漏洞可以执行任意指令, 甚至可以取得系统特权, 由此而引发许多种攻击方法。
2. 缓冲区溢出攻击
缓冲区溢出攻击是利用缓冲区溢出漏洞而进行的攻击行动。 填充数据越界而导致程序原有流程改变, 黑客借此精心构造填充数据, 让程序转而执行特殊的代码, 最终获得系统的控制权。
缓冲区溢出是一种非常普遍、 非常危险的漏洞, 在各种操作系统、 应用软件中广泛存在。 利用缓冲区溢出攻击, 可以导致程序运行失败、 系统关机、 重新启动等后果。
缓冲区溢出攻击的目的在于扰乱具有某些特权运行的程序的功能, 从而使攻击者取得程序的控制权。 如果攻击者对该程序有足够的控制权, 就能将整个程序控制。
为了达到目的, 攻击者必须达到以下两个目标:
(1) 在程序的地址空间安排适当的代码。
(2) 通过适当的初始化寄存器和内存, 让程序跳转到攻击者安排的地址空间执行。
根据这两个目标来进行分类, 缓冲区溢出攻击可分为以下两类。
1) 在程序的地址空间安排适当的代码的方法
(1) 植入法。 攻击者向被攻击的程序输入一个字符串, 程序会把这个字符串放到缓冲区里。 这个字符串包含的资料是可以在这个被攻击的硬件平台上运行的指令序列。 在这里,攻击者用被攻击程序的缓冲区来存放攻击代码。 缓冲区可以设在任何位置, 如堆栈、 堆、 静态资料区。
(2) 利用已经存在的代码。 有时, 攻击者想要的代码已经在被攻击的程序中, 攻击者要做的只是对代码传递一些参数。 例如, 攻击代码要求执行“exec("/bin/sh")”, 而在libc库中的代码执行“exec(arg)”, 其中, arg 是一个指向某个字符串的指针参数, 那么攻击者只要把传入的参数指针改为指向“/bin/sh” 即可。(www.daowen.com)
2) 控制程序转移到攻击代码的方法
(1) 活动记录。 每当一个函数调用发生时, 调用者会在堆栈中留下一个活动记录, 它包含函数结束时返回的地址。 攻击者通过溢出堆栈中的自动变量, 使返回地址指向攻击代码。 改变程序的返回地址后, 当函数调用结束时, 程序就跳转到攻击者设定的地址, 而不是原先的地址。 这类缓冲区溢出称为堆栈溢出攻击(Stack Smashing Attack), 是目前最常用的缓冲区溢出攻击方式。
(2) 函数指针。 函数指针可以用来定位任何地址空间。 例如, “void(*foo)( )” 声明了一个返回值为void 的函数指针变量foo。 所以攻击者只需在任何空间内的函数指针附近找到一个能够溢出的缓冲区, 然后通过溢出这个缓冲区来改变函数指针。 在某一时刻, 当程序通过函数指针调用函数时, 程序的流程就按攻击者的意图实现了。 它的一个攻击范例就是在Linux 系统下的superprobe 程序。
(3) 长跳转缓冲区。 在C 语言中有一个简单的检验/恢复系统, 称为setjmp/longjmp,意思是在检验点设定“setjmp(buffer)”, 用“longjmp(buffer)” 来恢复检验点。 然而, 如果攻击者能够进入缓冲区的空间, 那么“longjmp(buffer)” 实际上是跳转到攻击者的代码。 与函数指针类似, longjmp 缓冲区能够指向任何地方, 所以攻击者所要做的就是找到一个可供溢出的缓冲区。 一个典型的例子就是Perl 5.003 的缓冲区溢出漏洞: 攻击者首先进入用于恢复缓冲区溢出的longjmp 缓冲区, 然后诱导程序进入恢复模式, 这样就使解释器跳转到攻击代码上。
所有攻击方法都在寻求改变程序的执行流程, 使之跳转到攻击代码, 最基本的就是溢出一个没有边界检查或者其他弱点的缓冲区, 以扰乱程序的正常执行顺序。 通过溢出一个缓冲区, 攻击者就可以用暴力的方法来改写相邻的程序空间, 从而直接跳过系统的检查。
3. 缓冲区溢出攻击的防范方法
1) 非执行的缓冲区
通过使被攻击程序的数据段地址空间不可执行, 使攻击者不可能执行被攻击程序输入缓冲区的代码, 这种技术被称为非执行的缓冲区技术。
早期的系统设计只允许程序代码在代码段中执行, 近年来为了实现更好的性能, 在设计系统时, 往往在数据段中动态放入可执行代码, 造成缓冲区溢出。 为了保持兼容, 可设定堆栈数据段不可执行。 非执行堆栈的保护可以有效地对付把代码植入自动变量的缓冲区溢出攻击, 对于其他形式的攻击则没有效果。 因此, 攻击者可以采用把代码植入堆或者静态数据段中来跳过保护。
2) 编写正确的代码
编写正确的代码是一件非常有意义的工作, 特别是编写C 语言这类风格自由且容易出错的程序。 尽管人们努力编写安全的程序, 但具有安全漏洞的程序依旧出现。 因此人们开发了一些工具和技术来帮助经验不足的程序员编写安全、 正确的程序。
最简单的方法就是用grep 命令来搜索源代码中容易产生漏洞的库的调用, 如对strcpy( )和sprintf( ) 的调用, 这两个函数都没有检查输入参数的长度。 事实上, 各版本的C 语言标准库均有这样的问题存在。
此外, 还可以使用一些高级的查错工具, 如fault injection 等。 采用这些工具后, 可通过人为随机地产生一些缓冲区溢出来寻找代码的安全漏洞。 另外, 一些静态分析工具也可以用于侦测缓冲区溢出。 由于C 语言的特点, 这些工具不可能找出所有缓冲区溢出漏洞。 因此,侦错技术只能用于减少缓冲区溢出的可能, 而不能完全地消除它。
3) 数组边界检查
数组边界检查能防止所有缓冲区溢出的产生和攻击。 为了实现数组边界检查, 所有对数组的读写操作都应当被检查, 以确保对数组的操作在正确范围内。 最直接的方法是检查所有数组操作, 通常可以采用一些优化技术来减少检查的次数。
4) 程序指针完整性检查
程序指针完整性检查与数组边界检查有略微不同, 程序指针完整性检查在程序指针被引用之前就检测到它的改变。 由此可知, 即使一个攻击者成功地改变了程序的指针, 但由于系统事先检测到了指针的改变, 因此这个指针将不会被使用。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。