Chinaunix首页 | 论坛 | 博客
  • 博客访问: 394099
  • 博文数量: 61
  • 博客积分: 2525
  • 博客等级: 少校
  • 技术积分: 455
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-24 13:22
文章分类

全部博文(61)

文章存档

2008年(4)

2007年(57)

我的朋友

分类:

2007-07-25 19:28:26

软件漏洞及缓冲区溢出

一、 缓冲区溢出的发展简史
1、 基础知识
(1) 汇编语言
(2) 反汇编原理及Intel机器指令编码体系
(3) 调试器原理
(4) 异常处理(SEH)
(5) 调试技术
(6) Windows API 开发
2、 软件漏洞分类
逻辑漏洞:如条件竟争。
编码漏洞:如缓冲区溢出漏洞,格式化字符串漏洞等

3、 缓冲区溢出漏洞利用历史
在国外,早在80年代初就有人开始讨论溢出攻击。
1989年,Spafford提交了一份关于运行在VAX机上的BSD版UNIX的fingerd的缓冲区溢出程序的技术细节的分析报告,这引起了一部分安全人士对这个研究领域的重视,但毕竟仅有少数人从事研究工作,对于公众而言,没有太多具有学术价值的可用资料。来自L0pht heavy Industries的Mudge写了一篇如何利用BSDI上的libc/syslog缓冲区溢出漏洞的文章。
然而真正有教育意义的第一篇文章诞生在1996年,Aleph One在Underground发表的论文详细描述了Linux系统中栈的结构和如何利用基于栈的缓冲区溢出。Aleph One的贡献还在于给出了如何写开一个shell的Exploit的方法,并给这段代码赋予shellcode的名称,而这个称呼沿用至今,虽然已经部分失去了它原有的含义。我们现在对这样的方法耳熟能详--编译一段使用系统调用的简单的C程序,通过调试器抽取汇编代码,并根据需要修改这段汇编代码。他所给出的代码可以在x86/Linux,SPARC/Solaris和Sparc/SunOS系统正确的工作。受到Aleph One的文章的启发,Internet上出现了大量的文章讲述如何利用缓冲区溢出和如何写一段所需的Exploit。
1997年,Smith综合以前的文章,提供了如何在各种Unix变种中写缓冲区溢出Exploit更详细的指导原则。Smith还收集了各种处理器体系结构下的shellcode,包括Aleph One公布的和AIX和HPUX的。他在文章中还谈到了*nix操作系统的一些安全属性,例如SUID程序,Linux栈结构和功能性等,并对安全编程进行了讨论,附带了一些有问题的函数的列表,并告诉人们如何用一些相比更安全的代码替代它们。
1998年来自"Cult of the Dead Cow"的Dildog在Bugtrq邮件列表中以Microsoft Netmeeting为例子详细介绍了如何利用Windows的溢出,这篇文章最大的贡献在于提出了利用栈指针的方法来完成跳转,返回地址固定地指向地址,不论是在出问题的程序中还是在动态链接库中,该固定地址包含了用来利用栈指针完成跳转的汇编指令。Dildog提供的方法避免了由于进程线程的区别而造成栈位置不固定。Dildog还有另外一片经典之作The Tao of Windows Buffer Overflows。
集大成者是dark spyrit,在99年Phrack 55上总提出使用系统核心DLL中的指令来完成控制的想法,将Windows下的溢出Exploit推进了实质性的一步。Litchfield在1999年为Windows NT平台创建了一个简单的shellcode。他详细讨论了Windows NT的进程内存和栈结构,以及基于栈的缓冲区溢出,并以rasman.exe作为研究的实例,给出了提升权限创建一个本地shell的汇编代码。
1999年w00w00安全小组的Conover写了基于堆的缓冲区溢出的教程,开头写道:"基于Heap/BSS的溢出在当今的应用程序中已经相当普遍,但很少有被报道"。他注意到当时的保护方法,例如非执行栈,不能防止基于堆的溢出。并给出了大量的例子。
(以上资料来自xfocus)
4、 缓冲区溢出漏洞分类
(1)栈溢出
堆溢出
(2)远程溢出
本地溢出

二、 缓冲区溢出原理及成因
1、 体系结构
(1) 效率与安全性的矛盾实践证明,效率越高,安全性就越差,程序执行的效率与安全性成反比。
(2) 堆栈可执行
最流行的X86体系中,堆栈可执行
(3) 子程序调用结构
函数调用时,返回地址保存在堆栈中,造成函数调用时的不安全因素。
(4) 栈溢出堆栈向下生长

2、 程序设计
(1) C/C++比较自由的程序设计语言大量使用随着Unix系统的流行,C语言曾经一度成为程序设计的标准语言,从操作系统到应用软件,80%以上都使用C/C++语言。
(2) 不安全库函数调用,比如strcpy, strcat, strncpy, strncat
(3) 操作系统中不安全系统调用,如Windows中vsprintf, WideCharToMultiByte, wsprintf,等
(4) 程序设计人员的素质以及安全意识不够,一般的程序员没有经过系统的安全编程培训。同时,进行安全编程培训也没有一个比较科学的方法。
推荐:《编写安全的代码》(Writing Secure Code)
三、 缓冲区溢出漏洞利用
1、 技术准备
(1) 汇编语言
汇编语言是进行缓冲区溢出漏洞利用的基础,汇编语言与机器硬件体系联系紧密,X86体系的指令集与Sparc机器的指令集不一样就导致这两种体系下的汇编语言不同;同时,汇编语言还与操作系统的标准有关系,比如DOS-Windows系列的操作系统中使用的是INTEL的汇编语言标准,而*nix系列则使用AT&T的汇编语言标准。
由于Windows下发布的程序很多都不是公开源代码的,所以Windows下分析缓冲区溢出漏洞很多时候要涉及到反汇编,就是通过对2进制程序的反汇编,分析其程序算法,结构,以及产生漏洞的原因等。专业术语:逆向工程
推荐使用反汇编工具:IDApro。最新版本4.7。
http://www.datarescue.com/
IDA Pro是一个交互式智能的返汇编工具,工作于Windows和*nix系统下。IDA Pro可以分析出函数的参数,还能分析出函数中使用的局部变量,甚至还可以分析出函数调用关系图。

(2) 调试技术
在漏洞调试的过程中,需要实时对程序的执行流程,情况等进行控制,然后根据程序执行的情况再进行其他的一些处理。
根据自己的喜好或工作的需要选择一款调试器。SoftIce,Windbg,OllyDbg.VC6.0自带的调试器。
SoftIce功能强大,适合于对操作系统进行破解和分析,但是使用起来不方便,见面不够友好。Windbg是微软开发的一款系统及应用软件调试工具,短小精悍,功能强大,界面友好,和系统结合得非常完美。OllyDbg是一款应用软件调试工具。
断点设置:
a在指令上设断点
b在内存空间上设断点
c在特定的中断上设断点
d在I/O上设置断点
在Windows环境下,用到最多的就是在API函数调用上设置断点。可以说漏洞利用的关键技术就是调试技术,而调试技术的重点又在断点设置。
比如04011漏洞利用,根据分析我们可以知道有溢出漏洞的函数会将几个字符串写到一个日志文件中,由此推测它可能会调用createFile函数和WriteFile函数。
又比如在利用Office超长宏名溢出漏洞时,我们只知道在拷贝宏名时出现问题,那么就可以考虑Office装载Word文件时的过程,创建文件,为文件创建一个内存印象,等等,这样,我们就可以截获创建文件的函数调用后,查找宏名,然后在宏名上设置一个内存断点,当程序对宏名进行拷贝的时候就会中断,达到我们寻找溢出点的目的。
当然,很多专业的做软件安全的组织都使用自己开发的调试器,Windows提供了用户调试器接口,供用户自己开发调试器。同时,调试技术还用于软件漏洞发现。

(3) Windows结构化异常处理SEH
SEH是Windows下的一种程序错误处理机制。Windows 95、Windows 98 和 Windows 2000(即以前的 Windows NT)支持一种称为结构化异常处理的可靠的异常处理方法,此方法涉及与操作系统的协作,并且在编程语言中具有直接支持。比如C++中的Try{ }catch{ }语法经编译后就是SHE.
SEH结构是一个链表,链表的每一个节点表示一个异常处理函数。链表的头位于fs:0处。其中,fs是异常处理段寄存器,fs:0位于0x7fxxxxxx位置,可写。
构建SEH:
push offset ErrHandler
push fs:[0]
mov fs[0], esp

2、 栈溢出的利用
栈溢出是一种比较好利用的缓冲区溢出漏洞,栈溢出比较通用。
栈溢出的原理就是利用缓冲区溢出覆盖函数调用的返回地址,当函数返回时就可以控制函数的流程。
(1) 收集资料并构造程序溢出
首先就是要竟可能的收集资料。BugTraq和cve的漏洞列表是非常有用的资料库。在收集资料时尽可能的收集漏洞发现人或组织的最原始资料。


有些漏洞没有详细的漏洞描述,对于这种漏洞按照我们现在的能力或者说国内安全届现在的能力是没有什么希望做出来的。
其次要根据漏洞描述重现漏洞。详细的分析漏洞描述以及收集到的其他资料,重现漏洞。
在重现漏洞时要注意操作系统类型,补丁版本,以及软件环境等。比如说系统要求是Win2000还是XP,补丁版本是SP4以下,还是其他的,还有就是应用软件的版本以及补丁版本。
有些语言版本的操作系统并没有明确的提示当前操作系统的补丁版本,这是后就可以通过其他的一些途径来获得当前系统的补丁版本。比如可以通过关键文件如ntdll.dll的大小,以及修改日期等来获得相关信息。一般来说,每次升级补丁后ntdll.dll的文件大小都会增加。

(2) 分析程序溢出情况
每个漏洞的溢出情况都不一样,只能具体情况具体分析。但是在分析溢出情况的时候要注意收集相关的出错信息,包括程序出错时EIP的值,以及出错的原因等。
比如在分析Office宏名溢出漏洞时,重现漏洞后,程序报告访问内存出错—"0xaaaaaa引用得内存0xbbbbbbb不可读",通过这条信息我们就可以判断出,程序已经溢出,同时我们分析0xaaaaaaa,可以发现这是一个不可能会有指令的地址空间,然后在Word文档中查找0xaaaaaaa的二进制值(一般查找高3位字节,因为有可能程序在溢出后刚好跳转到完整的指令处,执行了若干条指令后才产生中断),找到后将其改为其他的值,重新溢出,如果错误报告时EIP的值为更改后的值,那么就可以确定该处为溢出的返回地址。
当然,实际的情况可能要复杂的多,只有临时解决。

(3) 控制程序溢出,并控制程序流程
由于堆栈空间的不确定性,每次溢出时堆栈所处的地址都不一样,所以不能直接将返回值重定向到堆栈中,但是因为当前esp指向的是堆栈空间,并且该空间位于我们可控区域,这样,溢出以后可以先返回到一条"jmp esp"或"call esp"处,然后利用跳转指令来实现shellcode的定位。
一般来说,栈溢出的通用性与jmp esp指令的通用地址有关,很多语言版本的2000和XP都有通用的jmp esp地址,但是在英文版和韩文版的系统中暂时未发现。对于这两种语言版本的系统,可以有针对性的进行溢出。
注意区分可利用溢出与不可利用溢出。有些漏洞在溢出发生后程序会访问已经被更改的变量,会导致程序在没有返回以前出错,出现不可利用的情况。

3、 堆溢出的利用
现在堆溢出的漏洞也越来越多,对于堆溢出漏洞,没有一种比较固定的利用方法,比较流行的就是利用SEH来实现对堆溢出漏洞的利用。
堆管理结构是一种双向链表。结构如下:
//插入堆结构双向链表图
当堆溢出后,会覆盖堆管理结构,当系统释放或再次分配堆的时候,就会产生错误,这就是堆溢出。
堆溢出的利用方式一般有两种,一种是在堆回收时利用,一种是在下一个堆分配时利用。
在堆分配是利用:
mov [eax], ecx
mov [ecx+4], eax
其中eax,和ecx的值都可以控制。这样,实际上我们可以控制当前系统中任意一个4字节的值,利用这4个字节来进行利用。
利用这4个字节可以改写某个常用函数的导出表,或者改写SEH的链表节点,利用SEH的特性来进行利用。比较有效的利用方法就是利用SEH来进行利用。
JPEG漏洞就属于堆溢出漏洞。根据分析,可以发现配合word使用的jpeg环境比较固定,也就是说溢出时的seh结构固定,并且esi指向原来的堆中,我们可控位置,这样,利用更改4个字节的功能将第一个SHE的处理函数改为一条call[esi+48]指令。因为在进入异常处理后所有的寄存器都会改变,而[esp+48]时原来的esi的值。
堆溢出比较灵活,利用方法很多,但是能够通用的比较少,受限制条件比较多。
参考资料:

四、 如何编写shellcode
1、 根据自己的需要编写shellcode
根据需求编写shellcode。比如说下载木马执行,绑定木马执行,返回一个shell,上传木马执行等。
2、 用win32汇编编写shelcode
现在一般都采用Win32汇编或者C语言编写shellcode.使用汇编语言编写shellcode比较简单,同时对编译器的编译行为也比较容易把握,汇编编译器一般尊重源程序,不会进行太多的优化。
首先,Shellocde涉及到一个代码自定位的问题。在x86体系中程序执行时,对数据段的访问默认使用绝对寻址方式,也就是说在我们编写shellcode时的数据地址在其他系统中使用时要通过其他的方法来进行定位。
通过下面的语句可以定位当前的shellcode所处的位置:
call at F
@@:
pop ebx
sub ebx, offset @B
其中offset @B是编译时就已经固化的一个地址,当访问数据时,就使用:[ebx + _testData]这种形式。其中,testData也是一个编译时固化的地址。
其次就是在shellcode中确定当前系统的kernel32 dot dll地址。然后再到kernel32.dll导出表中去寻找我们所需要的函数地址。
定位kernel32.dll地址的方法很多,最有效的一种就是利用当前进程的TEB/PEB定位,还有一种就是程序自己搜索进程空间,找到shellcode的起始地址。
;获得Kernel32.dll起始地址
assume fs:nothing
mov eax, fs:30h
mov eax, [eax + 0ch]
mov esi, [eax + 1ch]
lodsd
mov edx, [eax + 8h] ;edx 就是kernel32.dll的地址
mov at hModule, edx
获得GetProcAddress和LoadLibrary,GetModuleHandle函数地址。
在取得kernel32 dot dll起始地址后,可以通过搜索dll导出表的方法获得函数地址。
shellcode具体写法参考实例。

3、 从程序中提取Shellcode
shellcode编写好以后,还得将其从程序中倒出来,经过编码,最后才能使用到溢出程序中。
这里要注意的一点就是有些漏洞对shellcode有特殊的要求,比如不能有0,不能有0xff,等等。这时候就要先将shellcode编码成满足需求的代码,然后专门写一段解码程序添加到shellcode的前面,在溢出后先执行解码程序,将shellcode解码成为正常的指令,然后执行。

4、 编写sehllcode时的注意事项
在编写shellcode时,由于溢出的原因,堆栈空间已经被破坏,这时候必须保证堆栈空间是以4字节对齐,否则在调用某些函数时会有莫名其妙的错误。
其次就是要为shellcode中使用的局部变量预留足够的空间。

五、 缓冲区溢出的检测及如何防止缓冲区溢出
1、 从硬件体系结构上杜绝缓冲区溢出
到目前为止,X86+Windows体系结构中还没有类似的机制来防止缓冲区溢出。

2、 在软件开发中杜绝缓冲区溢出
编写安全的代码,在开发过程中减少缓冲区溢出的可能性。现在有一些工具可以通过对源代码的分析检测出程序中是否存在缓冲区溢出漏洞。比如:pscan,ITS4等。其中Pscan的开发者名叫:David A.Wheeler,他是IDA() 专门从事软件安全的高级顾问。
IDA成立于20世纪40年代后,是专门为美国政府提供技术支持的机构,由当时的国防部长James Forrestal组建。

这种从源代码中进行分析是通过一种不安全函数调用匹配+调用模式匹配的方法进行的。

3、 利用IDS或其他的工具软件检测缓冲区溢出攻击
通过进程监控的方法检测缓冲区溢出。
一种方法就是利用Windows提供的调试API函数接口,编写一个自己的调试器,用这个调试器对需要检测的进程进行监控,监控其敏感函数调用,然后匹配一个有限状态自动机模型,对缓冲区溢出进行检测。

4、 一个扩展话题——漏洞发现

推荐:




阅读(1814) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~