基于缓冲溢出漏洞的攻击及其预防研究综述

基于缓冲溢出漏洞的攻击及其防卫研究综述彭斌(学号:200610601022)摘 要:近十几年来, 利用操作系统缓冲区溢出漏洞发起的攻击带来了严重的网络安全问题, 其的安全危害级别相当高. 如何有效地防

基于缓冲溢出漏洞的攻击及其防卫研究综述

彭斌(学号:200610601022)

摘 要:近十几年来, 利用操作系统缓冲区溢出漏洞发起的攻击带来了严重的网络安全问题, 其的安全危害级别相当高. 如何有效地防止基于缓冲区溢出的攻击, 可以消除一个相当普遍的安全隐患. 本文针对缓冲区溢出漏洞的基本原理, 系统分析了攻击过程. 最后介绍了现有的防止该攻击的主要方法.

关键词:缓冲区溢出 网络攻击 网络安全 Stackguard

一、引言

缓冲区溢出是一种在各种操作系统、应用软件中广泛存在普遍且危险的漏洞,利用缓冲区溢出攻击可以导致程序运行失败、系统崩溃等后果。更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。第一个缓冲区溢出攻击--Morris 蠕虫,发生在十年前,它曾造成了全世界6000多台网络服务器瘫痪,据统计, 通过缓冲区溢出进行的攻击占所有系统攻击总数的80以上。

二、缓冲区溢出的原理:

当正常的使用者操作程序的时候,所进行的操作一般不会超出程序的运行范围;而黑客却利用缓冲长度界限向程序中输入超出其常规长度的内容,造成缓冲区的溢出从而破坏程序的堆栈,使程序运行出现特殊的问题转而执行其它指令,以达到攻击的目的。造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数,属于程序开发过程考虑不周到的结果。

当然,随便往缓冲区中填东西造成它溢出一般只会出现“分段错误”

(Segmentation fault),而不能达到攻击的目的。最常见的手段是通过制造缓冲区溢出使程序运行一个用户shell ,再通过shell 执行其它命令。如果该程序属于root 且有suid 权限的话,攻击者就获得了一个有root 权限的shell ,可以对系统进行任意操作了。

缓冲区溢出攻击之所以成为一种常见安全攻击手段其原因在于缓冲区溢出漏洞普遍并且易于实现。而且缓冲区溢出成为远程攻击的主要手段其原因在于缓冲区溢出漏洞给予了攻击者他所想要的一切:植入并且执行攻击代码。被植入的攻击代码以一定的权限运行有缓冲区溢出漏洞的程序,从而得到被攻击主机的控制权。

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

三、缓冲区溢出的漏洞和攻击:

缓冲区溢出攻击的目的在于扰乱具有某些特权运行的程序的功能,这样可以使得攻击者取得程序的控制权,如果该程序具有足够的权限,那么整个主机就被控制了。它的具体实现过程是这样的:首先攻击者对ROOT 程序进行试探性攻击,然后执行类似“exec(sh)”的执行代码来获得具有root 权限的shell 。为了达到这个目的,攻击者必须达到如下的两个目标:

1、在程序的地址空间里安排适当的代码;

,

2、通过适当的初始化寄存器和内存,让程序跳转到入侵者安排的地址空间执行。

根据这两个目标来对缓冲区溢出攻击进行分类,缓冲区溢出攻击分为代码安排和控制程序执行流程两种方法:

1、在程序的地址空间里安排适当的代码的方法:

(1)植入法:

攻击者向被攻击的程序输入一个字符串,程序会把这个字符串放到缓冲区里。这个字符串包含的资料是可以在这个被攻击的硬件平台上运行的指令序列。在这里,攻击者用被攻击程序的缓冲区来存放攻击代码。缓冲区可以设在任何地方:堆栈(stack ,自动变量)、堆(heap ,动态分配的内存区)和静态资料区。

(2)利用已经存在的代码:

有时攻击者想要的代码已经在被攻击的程序中了,攻击者所要做的只是对代码传递一些参数。例如攻击代码要求执行exec (“/bin/sh”),而在libc 库中的代码执行exec (arg),其中arg 使一个指向一个字符串的指针参数,那么攻击者只要把传入的参数指针改向指向/bin/sh。

2、控制程序转移到攻击代码的方法:

所有的这些方法都是在寻求改变程序的执行流程,使之跳转到攻击代码。最基本的就是溢出一个没有边界检查或者其它弱点的缓冲区,这样就扰乱了程序的正常的执行顺序。通过溢出一个缓冲区,攻击者可以用暴力的方法改写相邻的程序空间而直接跳过了系统的检查。

分类的基准是攻击者所寻求的缓冲区溢出的程序空间类型。原则上是可以任意的空间。实际上,许多的缓冲区溢出是用暴力的方法来寻求改变程序指针的。这类程序的不同之处就是程序空间的突破和内存空间的定位不同。主要有以下三种: 1)、活动纪录(Activation Records):

每当一个函数调用发生时,调用者会在堆栈中留下一个活动纪录,它包含了函数结束时返回的地址。攻击者通过溢出堆栈中的自动变量,使返回地址指向攻击代码。通过改变程序的返回地址,当函数调用结束时,程序就跳转到攻击者设定的地址,而不是原先的地址。这类的缓冲区溢出被称为堆栈溢出攻击(Stack Smashing Attack),是目前最常用的缓冲区溢出攻击方式。

2)、函数指针(Function Pointers):

函数指针可以用来定位任何地址空间。例如:“void (* foo)()”声明了一个返回值为void 的函数指针变量foo 。所以攻击者只需在任何空间内的函数指针附近找到一个能够溢出的缓冲区,然后溢出这个缓冲区来改变函数指针。在某一时刻,当程序通过函数指针调用函数时,程序的流程就按攻击者的意图实现了。它的一个攻击范例就是在Linux 系统下的superprobe 程序。

3)、长跳转缓冲区(Longjmp buffers):

在C 语言中包含了一个简单的检验/恢复系统,称为setjmp/longjmp。意思是在检验点设定“setjmp(buffer)”,用“longjmp(buffer)”来恢复检验点。然而,如果攻击者能够进入缓冲区的空间,那么“longjmp(buffer)”实际上是跳转到攻击者的代码。象函数指针一样,longjmp 缓冲区能够指向任何地方,所以攻击者所要做的就是找到一个可供溢出的缓冲区。一个典型的例子就是Perl 5.003的缓冲区溢出漏洞;攻击者首先进入用来恢复缓冲区溢出的的longjmp 缓冲区,然后诱导进入恢复模式,这样就使Perl 的解释器跳转到攻击代码上了。

3、代码植入和流程控制技术的综合分析:

,

最简单和常见的缓冲区溢出攻击类型就是在一个字符串里综合了代码植入和活动纪录技术。攻击者定位一个可供溢出的自动变量,然后向程序传递一个很大的字符串,在引发缓冲区溢出,改变活动纪录的同时植入了代码。这个是由Levy 指出的攻击的模板。因为C 在习惯上只为用户和参数开辟很小的缓冲区,因此这种漏洞攻击的实例十分常见。

代码植入和缓冲区溢出不一定要在在一次动作内完成。攻击者可以在一个缓冲区内放置代码,这是不能溢出的缓冲区。然后,攻击者通过溢出另外一个缓冲区来转移程序的指针。这种方法一般用来解决可供溢出的缓冲区不够大(不能放下全部的代码)的情况。

如果攻击者试图使用已经常驻的代码而不是从外部植入代码,他们通常必须把代码作为参数调用。举例来说,在libc (几乎所有的C 程序都要它来连接)中的部分代码段会执行“exec(something)”,其中somthing 就是参数。攻击者然后使用缓冲区溢出改变程序的参数,然后利用另一个缓冲区溢出使程序指针指向libc 中的特定的代码段。

四、缓冲区溢出攻击的实验分析:

2000年1月,Cerberus 安全小组发布了微软的IIS 4/5存在的一个缓冲区溢出漏洞。攻击该漏洞可以使Web 服务器崩溃,甚至获取超级权限执行任意的代码。目前微软的IIS 4/5 是一种主流的Web 服务器程序;因而该缓冲区溢出漏洞对于网站的安全构成了极大的威胁;它的描述如下:

浏览器向IIS 提出一个HTTP 请求,在域名(或IP 地址)后,加上一个文件名,该文件名以“.htr”做后缀。于是IIS 认为客户端正在请求一个“.htr”文件,“.htr”扩展文件被映像成ISAPI (Internet Service API)应用程序,IIS 会复位向所有针对“.htr”资源的请求到 ISM.DLL 程序 ,ISM.DLL 打开这个文件并执行之。 浏览器提交的请求中包含的文件名存储在局部变量缓冲区中,若它很长(超过600个字符时),会导致局部变量缓冲区溢出,覆盖返回地址空间使IIS 崩溃。更进一步在2K 缓冲区中植入一段精心设计的代码,可以使之以系统超级权限运行。

五、缓冲区溢出攻击的防范方法:

缓冲区溢出漏洞和攻击有很多种形式,相应地防卫手段也随者攻击方法的不同而不同。 1.缓冲区溢出攻击分类

◆栈溢出(stack smashing)

未检查输入缓冲区长度,导致数组越界,覆盖栈中局部变量空间之上的栈桢指针�p以及函数返回地址retaddr, 当函数返回执行,ret 指令时,retaddr 从栈中弹出,作为下一条指令的地址赋给eip寄存器,继而改变原程序的执行流程指向我们的shellcode.

◆堆溢出(malloc/free heap corruption)

一种是和传统的栈溢出一样, 当输入超出malloc()预先分配的空间大小,就会覆盖掉这段空间之后的一段存储区域,如果该存储区域有一个重要的变量比如euid ,那么我就可以用它来攻击。另一种是典型的double-free 堆腐败,在内存回收操作中,合并相邻空闲块重新插入双向链表时会有一个写4字节内存的操作,如果弱点程序由于编程错误free()一个不存在的块,我们就可以精心伪造这个块,从而覆盖任何我们想要的值:函数的返回地址、库函数的.plt 地址等 ◆格式化字符串漏洞(format string vulnerability)

,

如果格式窜由用户定制,攻击者就可以任意伪造格式窜,利用*printf()系列函数的特性就可以窥探堆栈空间的内容,超常输入可以引发传统的缓冲区溢出,或是用”n”覆盖指针、返回地址等。

◆整形变量溢出(integer variable overflow)

利用整数的范围、符号等问题触发安全漏洞,大多数整形溢出不能直接利用,但如果该整形变量决定内存分配等操作,我们就有可能间接利用该漏洞。 ◆其他的攻击手法(others )

只能算是手法,不能算是一种单独的类别。利用ELF 文件格式的特性如:覆盖.plt (过程连接表)、.dtor (析构函数指针)、.got (全局偏移表)、return-to-libc (返回库函数)等的方式进行攻击。 2.防范方法

1)、编译保护技术

◆Stackguard

因为缓冲区溢出的通常都会改写函数返回地址,stackguard 是个编译器补丁,它产生一个"canary "值(一个单字) 放到返回地址的前面,如果当函数返回时,发现这个canary 的值被改变了,就证明可能有人正在试图进行缓冲区溢出攻击,程序会立刻响应,发送一条入侵警告消息给syslogd, 然后终止进程。"canary "包含:NULL (0x00), CR (0x0d ), LF (0x0a ) 和 EOF (0xff )四个字 符,它们应该可以阻止大部分的字符串操作,使溢出攻击无效。一 个随机数canary 在程序执行的时候被产生。所以攻击者不能通过搜索程序的二进制文件得到"canary "值。如果/dev/urandom存在,随机数就从那里取得。否则,就从通过对当前时间进行编码得到。其随机性足以阻止绝大部分的预测攻击。Immunix 系统为采用stackguard 编译的Red Hat Linux,但stackguard 所提供的保护并非绝对安全,满足一些条件就可以突破限制:如覆盖一个函数指针、可能存在的exit()或_exit()系统调用地址、GOT 等。

◆Stackshield

StackShield 使用了另外一种不同的技术。它的做法是创建一个特别的堆栈用来储存函数返回地址的一份拷贝。

它在受保护的函数的开头和结尾分别增加一段代码,开头处的代码用来将函数返回地址拷贝到一个特殊的表中,而结尾处的代码用来将返回地址从表中拷贝回堆栈。因此函数执行流程不会改变,将总是正确返回到主调函数中。在新的版本中已经增加了一些新的保护措施,当调用一个地址在非文本段内的函数指针时,将终止函数的执行。

Stackshield 无法防御只覆盖�p的单字节溢出,同样,我们也可以通过覆盖其他的ELF 结构来绕过限制。

2)、库函数链接保护

例如:

◆Formatguard

Formatguard 是个Glibc 的补丁,遵循GPL ,它使用特殊的CPP (gcc 预编译程序)宏取代原有的*printf()的参数统计方式,它会比较传递给*printf的参数的个数和格式窜的个数,如果格式窜的个数大于实际参数的个数,就判定为攻击行为,向syslogd 发送消息并终止进程。如果弱点程序调用Glibc 以外的库,formatguard 就无法保护。

immunix.org

,

◆Libsafe

Libsafe 是一个动态链接库,在标准的C 库之前被加载,主要加固了gets(),strcpy(),strcat(),sprintf()……等容易发生安全问题的C 函数,它设计为只针对stack smashing && format string类型的攻击。

3)、栈不可执行

通过减小代码段的虚拟地址来区分数据段和代码段,程序执行流返回

0xC0000000以下一段用户堆栈空间的操作都被认为是缓冲区溢出攻击行为,随即产生一个通用保护异常而终止进程。这样把shellcode 安置在buffer 或环境变量(都位于堆栈段)的exploit 都会失效。当然其安全也不是绝对的,利用PLT 返回库函数的文章里详细描述了突破该补丁的攻击方法。

在Solaris/SPARC下可以通过去掉堆栈的执行权限来禁止堆栈段执行,方法如下,在/etc/system中加入两条语句:

Set noexec_user_stack = 1

Set noexec_user_stack_log = 1

第一条禁止堆栈执行,第二条记录所有尝试在堆栈段运行代码的活动。Reboot 之后才会生效。

所有只让栈不可执行的保护是有限的。Return-to-libc 、fake frame之类的技术都可以突破限制,不过栈不可执行的保护已经极大了提升了攻击难度。

4)、数据段不可执行

数据段的页不可执行,撤销共享内存,加强对一些系统调用的限制。

5)、增强的缓冲区溢出保护及内核MAC

例如:*使用strlcpy()和strlcat()函数替换原有的危险函数

*内存保护:W^X、只读数据段、页保护、mmap()随机映射、malloc()随机映射、atexit()及stdio 保护、

*特权分离

*特权回收

*其他的很多特性

6)、硬件级别的保护

X86 CPU上采用4GB 平坦模式,数据段和代码段的线性地址是重叠的,页面只要可读就可以执行,所以上面提到的诸多内核补丁才会费尽心机设计了各种方法来使数据段不可执行。现在Alpha 、PPC 、PA-RISC 、SPARC 、SPARC64、AMD64、IA64都提供了页执行bit 位。Intel 及AMD 新增加的页执行比特位称为NX 安全技术,Windows XP SP2及Linux Kernel 2.6都支持NX ,虽然这种硬件级的页保护不如PaX 那样强,但硬件级别的支持无疑大大增加了软件和操作系统的兼容性,能够使缓冲区溢出的防护得到普及。

六、结束语

正因为缓冲区溢出漏洞和攻击形式的多样性,相应地防卫手段也必须随攻击方法的不同而不同。每一种防卫方法有其自身的优缺点,只有综合运用这些防卫技术才能更有效的抵御缓冲溢出攻击。

标签: