浅谈缓冲溢出漏洞 |
缓冲区溢出的漏洞是众所周知的,这是一个非常普遍、非常危险的漏洞在各种操作系统、应用软件中广泛存在。以缓冲区溢出为类型的安全漏洞是最为常见的一种漏洞,也因此对缓冲区溢出漏洞的攻击占了远程网络攻击的绝大多数,有专门研究安全问题的人说这是对年来攻击和防卫的弱点”,可见,无论是一名黑客还是一名系统管理员,对于高级缓冲区溢出方面的知识是不可缺的。
缓冲溢出的概念与原理 缓冲溢出指的是一种系统攻击的手段,通过向程序的缓冲区写起出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈。使程序转而执行其它指令,以达到攻击的目的。据统计。通过缓冲区溢出进行的攻击占所有系统政击总数的 80%以上。造成经冲区溢出的原因是程序中没有仔细检查用户输入的参数。👨🎨👒🪝🥰👃 从上面的缓冲区溢出定义中可以看到,缓冲区溢出就是将一个超过缓冲区长度的字符未置入缓冲区的结果,而向一个有限空间的缓冲区中置入过长的字符串可能会带来两种后果,一是过长的字符率覆盖了相邻的存储单元引起程序运行失败,严重的可导致系统崩溃;另一种后果是利用这种漏洞可以执行任意指令甚至可以取得系统特权由此而引发了许多种攻击方法。 缓冲溢出的危害 👌🏝🍭🈳🦬 缓冲区溢出攻击之所以成为一种常见安全攻击手段,其原因在于缓冲区溢出漏洞太普通了,并且易于实现。这种攻击可以使得一个匿名的 Internet 用户有机会获得一台主机的部分或全部的控制权!而且,缓冲区溢出成为远程攻击的主要手段其原因在于缓冲区溢出漏洞给予了 攻击者他所想要的一切:植入井且执行攻击代码。被植入的攻击代码以一定的权限运行有缓冲区溢出漏洞的程序,从而得到被攻击主机的控制权。 👃🏠🍓❓🐒 在 1998 年 Lincoln 实验室用来评估入侵检测的 5 种远程攻击中有 3 种是基于社会工程学的信任关系,2 种是缓冲区溢出。而在 1998 年CERT 的 13 份建议中,有 9 份是与缓冲区溢出有关的,在 l999 年,至少有半数的建议是和缓冲区溢出有关的。在 Bugtraq 的调查中,有 2/3 的被调查者认为经对区溢出漏洞是一个很严重的安全问题。缓冲溢出漏洞及攻击 缓冲区溢出攻击的目的在于扰乱具有某些特权运行的程序的攻能。这样可以让攻击者取得程序的控制权,如果该程序具有足够的权限,那么整个主机就被控制了。一般而言,攻击者政击 root 程序,然后执行类似“exec(sh)"的执行代码来获得 root 的 shell。但并不总是这样的,为了达到这个目的,攻击者必须达到如下的两个目标: 👍🧳🥭📳🦊 * 在程序的地址空间里安排适当的代码; * 通过适当地初始化寄存器和存储器,让程序跳转到安排好的地址空间执行。 👍🚠🍭❗🐥 我们根据这两个目标来对缓冲区溢出攻击进行分类。 一、在程序的地址空间里安排适当的代码的方法 有两种在被攻击程序地址空间里安排攻击代码的方法:👩✈️🎩🪗🥲👏 l、植入法: 攻击者向被攻击的程序输入一个字符串,程序会把这个字符串放到缓冲区里。这个字符串包含的数据是可以在这个被攻击的硬件平台上运行的指令序列。在这里攻击者用被攻击程序的缓冲区来存放攻击代码。具体的方式有以下两种差别: 🥷🧣📷😥👁 (l)攻击者不必为达到此目的而溢出任何缓冲区.可以找到足够的空间来放置攻击代码。 (2)缓冲区可以设在任何地方:堆栈(自动变量)、堆(动态分配的和静态数据区(初始化或者未初始化的数据)。 2、利用已经存在的代码:👓🔑😄✊ 有时候,攻击者想要的代码已经在被攻击的程序中了,攻击者所要做的只是对代码传递一些参数,然后使程序跳转到我们的目标。比如,攻击代码要求执行“exec(‘/bin/sh')",而在libc 库中的代码执行“exec(arg)" ,其中 arg 是一个指向字符串的指针参数,那么攻击者只要把传入的参数指针改向指向“/bin/sh”,然后调转到 libc 库中的相应的指令序列即可。 二、控制程序转移到攻击代码的方法 🖐🗼🍊📳🐢 所有的这些万法都是在寻求改变程序的执行流程,使之跳转到攻击代码。最基本的就是溢出一个没有边界检查或者其他弱点的缓冲区,这样就扰乱了程序的正常的执行顺序。通过溢出一个缓冲区,攻击者可以用近乎暴力的方法改写相邻的程序空间而直接跳过系统的检查。这里分类的基准是攻击者所寻求的缓冲区溢出的程序空间类型。原则上可以是任意的空间。比如,最初的 Morris Worm(莫尔斯间虫)就是使用了 fingerd 程序的缓冲区溢出,扰乱 fingerd 要执行的文件的名字。 实际上,许多的缓冲区溢出是用暴力的方法来寻求改变程序指针的。这类程序的不同的地方就是程序空间的突破和内存空间的定位不同。一般来说,控制程序转移到攻击代码的方法有以下几种; 💪🏠🦞ℹ🐴 1、激活纪录(Activation Records): 每当一个函数调用发生时,调用者会在堆栈中留下一个激活纪录,它包含了函数结束时返回的地址。攻击者通过溢出这些自动变量,使这个返回地址指向攻击代码,通过改变程序的返回地址,当函数调用结束时,程序就跳转到攻击者设定的地址,而不是原先的地址。这类的缓冲区溢出被称为“stack smashing attack”,是目前常用的缓冲区溢出攻击方式。 🤟🗼🔪🈳🦄 2、函数指针(Function Pointers): "void(* foo)()"声明了一个返回值为 viod 函数指针的变量 foo。函数指针可以用来定位任何地址空间,所以攻击者只需在任何空间内的函数指针附近找到一个能够溢出的缓冲区,然后溢出这个缓冲区来改变函数指针。在某一时刻,当程序通过函数指针们用函数时程序的流程就按攻击者的意图实现了!它的一个攻击范例就是在 linux 系统下的 superprobe 程序。 3.长跳转缓冲区(Longjmpbuffers): 在 C 语言中包含了一个简单的检验/恢复系统,称为 setjmp/longjmp。意思是在检验点设定“setjmp(buffer)”,用“longjmp(buffer)”来恢复检验点。然而,如果攻击者能够进入缓冲区的空间,那么“longjmp(buffer)’实际上是跳转到攻击者的代码。像函数指针一样,longjmp 缓冲区能够指向任何地方,所以攻击者所要做的就是找到一个可供溢出的缓冲区。 👃⛴🌶🈚🐴 一个典型的例子就是 Perl 5.003,攻击者首先进入用来恢复缓冲区溢出的 longjmp 缓冲区,然后诱导进入恢复模式这样就使 Perl 的解释器跳转到攻击代码上了! 三、综合代码植入和流程控制技术 最简单和常见的缓冲区溢出攻击类型就是在一个字符串里综合了代码植入和激活纪录。攻击者定位一个可供溢出的自动变量,然后向程序传递一个很大的字符串,在引发缓冲区溢出改变激活纪录的同时植入了代码。这个是由 Levy 指出的攻击的模板。因为 C 在习惯上只为用户和参数对辟很小的缓冲区,因此这种漏洞攻击的实例不在少数。 👦👚📡😡🙏 代码植入和缓冲区溢出不一定要在一次动作内完成。攻击者可以在一个缓冲区内放置代码,这时不能溢出缓冲区。然后,攻击者通过溢出另外一个缓冲区来转移程序的指针。这种方法一般用来解决可供溢出的缓冲区不够大(不能放下全部的代码)的情况。如果攻击者试图使 用已经常驻的代码而不是从外部植入代码,他们通常必须把代码作为参数。举例来说,在 libc(几乎所有的 C 程序都要它来连接)中的部分代码段会执行“xexc(something)’,其中something 就是参数。攻击者使用缓冲区溢出改变程序的参数,然后利用另一个缓冲区溢出使 程序指针指向 libc 中的特定的代码段。 👨🎨🥾🧯🥰👊 缓冲区溢出的保护方法 目前有四种基本的方法保护缓冲区免受缓冲区溢出的攻击和影响。 一、编写正确的代码 编定正确的代码是一件非常有意义但耗时的工作,特别像编写 C 语言那种具有容易出错倾向的程序(如:字符率的零结尾),这种风格是由于追求性能而忽视正确性的传统引起的。 尽管花了很长的时间使得人们知道了如何编写安全的程序组,但具有安全漏洞的程序依旧出现。因此人们开发了一些工具和技术来帮助经验不足的程序员编写安全正确的程序。最简单的方法就是用 grep 来搜源代码中容易产生漏洞的库的调用,比如对 strcpy 和 sprintf 的调用,这两个函数都没有检查输入参数的长度。事实上各个版本 C 的标准库均有这样的问题存在。 👃🗺🍪✡🐥 为了寻找一些常见的诸如缓冲区溢出和操作系统竞争条件等漏洞,一些代码检查小组检查了很多的代码。然而依然有漏网之鱼存在。尽管采用了 strncpy 和 snprintf 这些替代函数来防止经冲区溢出的发生,但是由于编写代码的问题,仍旧会有这种情况发生。比如 lprm 程序就是最好的例子,虽然它通过了代码的安全检查,但仍然有缓冲区溢出的问题存在。 为了对付这些问题,人们开发了一些高级的查错工具,如 faultinjection 等。这些工具的目的在于通过人为随机地产生一些缓冲区溢出来寻找代码的安全漏洞。还有一些静态分析工具用于侦测缓冲区溢出的存在。 🧑🍳👙🧪😊🤝 虽然这些工具可以帮助程序员开发更安全的程序,但是由于 C 语言的特点,这些工具不可能找出所有的缓冲区溢出漏洞。所以,侦错技术只能用来减少缓冲区溢出的可能,并不能完全地消除它的存在,除非程序员能保证他的程序万无一失。 二、非执行的缓冲区 通过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被植入被攻击程序输入缓冲区的代码,这种技术被称为非执行的缓冲区技术。事实上,很多老的 Unix 系统都是这样设计的但是近来的 Unix 和 MS Windows 系统为实现更好的性能和功能,往往在数据段中动态地放入可执行的代码。所以为了保持程序的兼容性不可能使得所有程序的数据段不可执行。 👂🚤🍒🅰🦟 但是我们可以设定堆栈数据段不可执行,这样就可以最大限度地保证了程序的兼容性。Linux 和 Solaris 都发布了有关这方面的内核补丁。因为几乎没有任何合法的程序会在堆栈中存放代码,这种做法几乎不产生往问兼容性问题,除了在 Linux 中的两个特例,这时可执行的代码必须被放入堆栈中: 1.信号传递 Linux 通过向进程堆栈释放代码然后引发中断来执行在堆栈中的代码进而实现向进程发送Unix 信号。非执行缓冲区的补丁在发送信号的时候是允许缓冲区可执行的。 👊🚂🍖🆗🐮 2 GCC 的在线重用 研究发现 gcc 在堆栈区里放置了可执行的代码以便在线重用。然而关闭这个功能并不产生任何问题,只有部分功能似乎不能使用。非执行堆栈的保护可以有效地对付把代码植入自动变量的缓冲区溢出攻击,而对于其他形式的攻击则没有效果。通过引用一个驻留的程序的指针,就可以跳过这种保护措施。其他的攻击可以采用把代码植入堆或者静态数据段中来跳过保护。 三、数组边界检查 植入代码引起缓冲区溢出是一个方面,扰乱程序的执行流程是另一个方面。不像非执行缓冲区保护、数组边界检查完全没有了缓冲区溢出的产生和攻击。这样只要数组不能被溢出,溢出攻击也就无从谈起。为了实现教组边界检查,则所有的对数组的读写操作都应当被检查以确保对数组的操作在正确的范围内。最直接的方法是检查所有的数组操作,但是通常可以采用一些优化的技术来减少检查的次数。目前有以下的几种检查方法: 🙏⛵🍓ℹ🐴 l、Compaq C 编译器 Compaq 公司为 Alpha CPU 开发的 C 编译器支持有限度的边界检查(使用-checkl bounds 参数)。这些限制是:只有显示的数用引用才被检查,比如“a[3]”会被检查,而“*(a+3)”则不会。 由于所有的 C 数组在传送的时候是指针传递的,所以传递给函数的的数组不会被检查。带有危险性的库函数如 Strcpy 不会在编译的时候进行边界检查,即便是指定了边界检查。在 C语言中利用指针进行数组操作和传递是非常频繁的,因此这种局限性是非常严重的。通常这种边界检查用来程序的查错,而且不能保证不发生缓冲区溢出的漏洞。🧑💻🩲🪗😒👂 2、Jones&Kelly: C 的数组边界检查 Richard Jones 和 Paul Kelly 开发了一个 gcc 的补丁,用来实现对 C 程序完全的数组边界检查。由于没有改变指针的含义,所以被编译的程序和其他的 gcc 模块具有很好的兼容性。更进一步的是,他们由此从没有指针的表达式中导出了一个“基”指针,然后通过检查这个基指针来侦测表达式的结果是否在容许的范围之内。当然,这样付出的性能上的代价是巨大的;对于一个频繁使用指针的程序,如向量来法将由于指针的频繁使用而使速度慢 30 倍。 这个编译器目前还很不成熟,一些复杂的程序(如 elm)还不能在这个上面编泽、执行通过。然而在它的👩🦺🧹🤮🤙 一个更新版本之下,它至少能编译执行 ssh 软件的加密软件包,但其实现的性能要下降 12 倍。 3、Purify:存储器存取检查 Purify 是 C 程序调试时查看存储器使用的工具而不是专用的安全工具。Purify 使用“目标代码插入’技术来检查所有的存储器存取。通过用 Purify 连接工具连接,可执行代码在执行的时候带来的性能上的损失要下降 3-5 倍。 🤟⛪🥛🈸🐻 4、类型——安全语言 所有的缓冲区溢出漏洞都源于 C 语言的类型安全。如果只有类型-安全的操作才可以被允许执行,这样就不可能出现对变量的强制操作。如果作为新手,可以推荐使用具有类型一安全的语言如加 Java 和 ML。但是作为 Java 执行平台的 Java 虚拟机是 C 程序,因此攻击 JVM 的一条途径是使 JVM 的缓冲区溢出。因此在系统中采用缓冲区溢出防卫技术来使用强制类型- 安全的语言可以收到预想不到的效果。 四、程序指针完整性检查 ✍🗺🍚🚭🐒 程序指针完整性检查和边界检查有略微的不同。与防止程序指针被改变不同,程序指针完整性检查在程序指针被引用之前检测到它的改变。因此,即使一个攻击者成功地改变了程序的指针,由于系统事先检测到了指针的改变,因此这个指针将不会被使用。与数组边界检查相 比,这种方法不能解决所有的缓冲区溢出问题;采用其他的缓冲区溢出方法就可以避免这种检测。但是这种方法在性能上有很大的优势,而巨兼容性也很好。 l、手写的堆栈监测 Snarkii 为 FreeBSD 开发了一套定制的能通过监测 CPU 堆找来确定缓冲区溢出的 libc。这个应用完全用手工汇编写的,而且是保护 libc 中的当前有效纪录函数。这个应用达到了设计要求,对于基于 libc 库函数的攻击具有很好的防卫,但是不能防卫其它方式的攻击。 👩✈️👚🖌🤮🦴 2、堆栈保护 堆栈保护是一种提供程序指针完整性检查的编译器技术,通过检查函数活动纪录中的返回地址来实现。堆栈保护作为 gcc 的一个小的补丁,在每个函数中,加入了函数建立和销毁的代码。加入的函数建立代码实际上在堆栈中函数返回地址后面加了一些附加的字节。而在函数返回时,首先检查这个附加的字书是否被改动过,如果发生过缓冲区溢出的攻击,那么这种攻击很容易在函数返回前被检测到。但是,如果攻击者预见到这些附加字节的存在,并且能在溢出过程中同样地制造他们,那么它就能成功地跳过堆栈保护的检测。 通常,我们有如下两种方案对付这种欺骗; 🤟🌦🧊♏🐻 (l)终止符号 利用在 C 语言中的终止符号如 0(nul),CR,LF,-l(EOF)等这些符号不能在常用的字符串函数中使用,因为这些函数一旦遇到这些终止符号,就结束函数过程了。 (2)随机符号 利用一个在函数调用时产生的一个 32 位的随机数来实现保密,使得攻击者不可能猜测到附加字节的内容。而且,每次调用附加字节的内容都在改变,也无法预测。通过检查堆栈的完整性的堆栈保护法是从 Synthetix 方法演变来的。Symthetix 方法通过用准不变量来确保特定变量的正确性。 👊🚈🍟🅿🦚 这些特定的变量的改变是程序实现能预知的,而且只能在满足一定的条件才能可以改变。这种变量我们称为准不变量。 Synthetix 开发了一些工具用来保护这些变量。攻击者通过缓冲区溢出而产生的改变可以被系统当做非法的动作。在某些极端的情况下,这些准不变量有可能被非法改变,这时需要堆栈保护来提供更完善的保护了。 实验的数据表明,堆栈保护对于各种系统的缓冲区溢出攻击都有很好的保护作用,并能保持较好的兼容性和系统性能。分析表明,堆栈保护能有效抵御现在的和将来的基于堆栈的攻击。谁栈保护版本的 Red Hat Linux 5.1 已经在各种系统上运行了多年包括个人的笔记本电脑和工作组文件服务器。 🥷🪖📠🙂🤛 3、指针保护 在堆栈保护设计的时候,冲击堆找构成了缓冲区溢出攻击的常见的一种形式。有人推测存在一种模板来构成这些攻击(在 1996 年的时候)。从此,很多简单的漏洞被发现,实施和补丁后,很多攻击者开始用更一般的方法实施缓冲区溢出攻击。指针保护是难栈保护针对这种情况的一个推广。通过在所有的代码指针之后放置附加字书来检验指针在被调用之前的合法性,如果检验失败,会发出报警信号和退出程序的执行,就如同在堆栈保护中的行为一样。 这种方案有两点需要注意: 👎🚈🍧ℹ🦋 (1)附加字节的定位 附加字节的空间是在被保护的变量被分配的时候分配的,同时在被保护字节初始化过程中放初始化。这样就带来了问题:为了保持兼容性我们不想改变被保护变量的大小,因此我们不能简单地在变量的结构定义中加入附加字。还有,对各种类型也有不同附加字节数目。 (2)查附加字节 每次程序指针被引用的时候都要检查附加字节的完整性。这个也存在问题因为“从存取器读"在编译器中没有语义,编译器更关心指针的使用,而各种优化算法倾向于从存储器中读人变量。还有随着变量类型的不同,读人的方法也各自不同。到目前为止只有很少一部分使用非指针变量的攻击能逃脱指针保护的检测。但是,可以通过在编译器上强制对某一变量加入附加字书来实现检测,这时需要程序员自己手工加入相应的保护了。🧑🚀🩴✒🤖🦷
帖子热度 7553 ℃
小执念装成乞丐,骗取了楼主1 个 金币.
|
|
看见你们在厚颜无耻的刷经验,还刷的这么俗不可耐,对于你们这种丧心病狂令人发指的行为,我真的很想说:请带上我!
|