首先简要回顾一下缓冲区溢出的攻击大系:
◆栈溢出(stack smashing)
未检查输入缓冲区长度,导致数组越界,覆盖栈中局部变量空间之上的栈桢指针%ebp以及函数返回地址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(返回库函数)等的方式进行攻击。
一、编译保护技术
◆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等。
Stackguard官方链接:http://immunix.org/
◆Stackshield
StackShield使用了另外一种不同的技术。它的做法是创建一个特别的堆栈用来储存函数返回地址的一份拷贝。它在受保护的函数的开头和结尾分别增加一段代码,开头处的代码用来将函数返回地址拷贝到一个特殊的表中,而结尾处的代码用来将返回地址从表中拷贝回堆栈。因此函数执行流程不会改变,将总是正确返回到主调函数中。在新的版本中已经增加了一些新的保护措施,当调用一个地址在非文本段内的函数指针时,将终止函数的执行。
Stackshield无法防御只覆盖%ebp的单字节溢出,同样,我们也可以通过覆盖其他的ELF结构来绕过限制。
二、库函数链接保护
◆Formatguard
Formatguard是个Glibc的补丁,遵循GPL,它使用特殊的CPP(gcc预编译程序)宏取代原有的*printf()的参数统计方式,它会比较传递给*printf的参数的个数和格式窜的个数,如果格式窜的个数大于实际参数的个数,就判定为攻击行为,向syslogd发送消息并终止进程。如果弱点程序调用Glibc以外的库,formatguard就无法保护。
◆Libsafe
Libsafe是一个动态链接库,在标准的C库之前被加载,主要加固了gets(),strcpy(),strcat(),sprintf()……等容易发生安全问题的C函数,它设计为只针对stack smashing && format string类型的攻击。Alert7很早也写过如何绕过libsafe保护的文章。
三、栈不可执行
◆Solar designer’s nonexec kernel patch
从名字可以看出这是一个Linux上的内核补丁,该补丁最主要的特性是:用户区堆栈不可执行[Non-executable User Stack]由于x86 CPU上并没有提供页(page)执行的bit位,所以该补丁通过减小代码段的虚拟地址来区分数据段和代码段,程序执行流返回 0xC0000000以下一段用户堆栈空间的操作都被认为是缓冲区溢出攻击行为,随即产生一个通用保护异常而终止进程。这样把shellcode安置在buffer或环境变量(都位于堆栈段)的exploit都会失效。当然其安全也不是绝对的,利用PLT返回库函数的文章里详细描述了突破该补丁的攻击方法。
该补还有一些其他的特性:动态链接库映射到地址低端(0x00开始)、限制符号链接攻击、/tmp目录限制、/proc目
录限制、execve系统调用加固等。
◆Solaris/SPARC nonexec-stack protection
在Solaris/SPARC下可以通过去掉堆栈的执行权限来禁止堆栈段执行,方法如下,在/etc/system中加入两条语句:
Set noexec_user_stack = 1
Set noexec_user_stack_log = 1
第一条禁止堆栈执行,第二条记录所有尝试在堆栈段运行代码的活动。Reboot之后才会生效。
所有只让栈不可执行的保护是有限的。Return-to-libc、fake frame之类的技术都可以突破限制,不过栈不可执行的保护已经极大了提升了攻击难度。
四、数据段不可执行
◆kNoX
Linux内核补丁,功能:数据段的页不可执行,撤销共享内存,加强对execve系统调用的限制,对文件描述符0、1、2的特殊处理,/proc目录的限制,FIFO限制,符号链接限制,该补丁只支2.2内核。
◆RSX
Linux内核模块,数据段(stack、heap)不可执行。
◆Exec shield
Exec-shield从内核态显示的跟踪一个应用程序所包含的可执行映像的最大虚拟地址,动态的维护这个“可执行虚拟地址的最大值”称为“可执行限界”,每次发生进程切换的时候调度进程就会用这个值更新代码段描述符写入GDT,exec-shield动态的跟踪每个应用程序,所以每个程序运行时都有不同的“可执行限界”,因为可执行限界通常是个很低的虚拟地址,所以除了stack以外mmap()映射的区域以及malloc()分配的空间都处在可执行限界之上,因此都是不可执行的。
当然Exec-shield无法防御跳转到低16M地址空间和return-to-libc的攻击,不过还是能阻止绝大多数把shellcode安置在数据段的攻击。
五、增强的缓冲区溢出保护及内核MAC
◆OpenBSD security feature
OpenBSD和Hardened Gentoo、Adamantix、SELinux都是属于默认安全等级非常高的操作系统。OpenBSD经过代码审计,
漏洞非常少。同样他具有很多安全特性:
*使用strlcpy()和strlcat()函数替换原有的危险函数
*内存保护:W^X、只读数据段、页保护、mmap()随机映射、malloc()随机映射、atexit()及stdio保护、
*特权分离
*特权回收
*BSD chroot jail
*其他的很多特性
其中W^X有不少内容:stack、mmap随机映射,只读GOT/PLT/.ctor/.dtor等。虽然理论上OpenBSD无法阻止所有类型的攻击,但已经阻断了不少攻击手法。
◆PaX
PaX是个非常BT的东西,好像天生就是缓冲区溢出的死对头,他严厉的审视每一种攻击方式,予以阻断。
*基于x86段式内存管理的数据段不可执行
*基于页式内存管理的数据段的页不可执行
*内核页只读{
-Const结构只读
-系统调用表只读
-局部段描述符表(IDT)只读
-全局段描述符表(GDT)只读
-数据页只读
-该特性不能与正常的LKM功能共存 }
*完全的地址空间随机映射{
-每个系统调用的内核栈随机映射
-用户栈随机映射
-ELF可执行映像随机映射
-Brk()分配的heap随机映射
-Mmap()管理的heap随机映射
-动态链接库随机映射 }
*还有诸如把动态链接库映射到0x00开始的低地址的其他特性
这里顺便提一下Phrack58上Nergal写过的<>,这篇大作里提到用伪造栈桢(Fakeframe)和dl-resolve()技术突破PaX若干保护的方法,这极有可能*nix应用层exploit技术中最高级的技术,Nergal解决了几个问题:Stack/Heap/BSS不可执行、mmap随机映射,显然这种高级的技术仍然无法无条件的突破PaX,所以在一个运行完全版PaX的Linux上,你想发动缓冲区溢出可能是没有机会的!
◆Grsecurity
Grsec内含PaX,和Lids一样grsec支持内核MAC(Madatory Access Control,强制访问控制),拥有非常多的特性,详见http://grsecurity.net/features.php
六、硬件级别的保护
X86 CPU上采用4GB平坦模式,数据段和代码段的线性地址是重叠的,页面只要可读就可以执行,所以上面提到的诸多内核补丁才会费尽心机设计了各种方法来使数据段不可执行。现在Alpha、PPC、PA-RISC、SPARC、SPARC64、AMD64、IA64都提供了页执行bit位。Intel及AMD新增加的页执行比特位称为NX安全技术,Windows XP SP2及Linux Kernel 2.6都支持NX,虽然这种硬件级的页保护不如PaX那样强,但硬件级别的支持无疑大大增加了软件和操作系统的兼容性,能够使缓冲区溢出的防护得到普及。
Conclusion
安全和易用性总是站在对立面上,以上提及的保护技术都会引起少量的性能损耗,设计者们已经从性能的角度优化了他们的作品。然而人们更关心的问题是兼容性,也许你会发现在那些运营级的BOX上根本看不到这些东西,是的,人们希望的另一种安全是不发生错误,即稳定的运行,使用这些额外的保护会给人造成心理不安,我相信随着NX的流行以及保护技术本身的发展这些问题都会得到解决。也许你经常会看到这样或那样的文章讲述如何突破缓冲区溢出保护的高级exploit技术,其实很多内容只适合作为教学、或者技术本身还处在研究阶段,在实际的攻击中,使用高级的bypass技术通常需要满足一些条件,并不是单纯多花点力气增长了exploit代码的长度就能达到目的,在使用缓冲区溢出保护的系统上,攻击将变得非常困难,有些时候其实就是不可能,尤其是在远程无法精确得到必须的ELF符号地址的时候,很多技术都将变成纸上谈兵。
使用类似PaX的补丁,+iptables规则,再结合内核MAC,想入侵得到shell几乎是不可能的,可惜偶没钱,不然拿个Linux box放到Internet上公测,让牛人们尽兴的玩玩,哈~
在缓冲区溢出尚未成为历史的今天,暂且缅怀一下吧,这当然也不是什么悲观的论调,旧技术的消亡必然伴随着新技术的诞生,如果没有了Evil Hacking我们还坐在电脑前干什么呢?