原文 by wangxiaolong_china
缓冲区溢出的攻击大致分为以下几类:
- 栈溢出(stack smashing) 未检查输入缓冲区长度,导致数组越界,覆盖栈中局部变量空间之上的栈桢指针%ebp以及函数返回地址retaddr, 当函数返回执行ret指令时,retaddr从栈中弹出,作为下一条指令的地址赋给%eip寄存器,继而改变原程序的执行流程指向我们的 shellcode.
- 堆溢出(malloc/free heapcorruption) 一种是和传统的栈溢出一样, 当输入超出malloc()预先分配的空间大小,就会覆盖掉这段空间之后的一段存储区域,如果该存储区域有一个重要的变量比如euid,那么我就可以用它 来攻击。另一种是典型的double-free堆腐败,在内存回收操作中,合并相邻空闲块重新插入双向链表时会有一个写4字节内存的操作,如果弱点程序由 于编程错误free()一个不存在的块,我们就可以精心伪造这个块,从而覆盖任何我们想要的值:函数的返回地址、库函数的.plt地址等
- 格式化字符串漏洞(format stringvulnerability) 如果格式串由用户定制,攻击者就可以任意伪造格式串,利用*printf()系列函数的特性就可以窥探堆栈空间的内容,超常输入可以引发传统的缓冲区溢出,或是用”%n”覆盖指针、返回地址等。
- 整形变量溢出(integer variableoverflow) 利用整数的范围、符号等问题触发安全漏洞,大多数整形溢出不能直接利用,但如果该整形变量决定内存分配等操作,我们就有可能间接利用该漏洞。
- 其他的攻击手法(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 kernelpatch 从名字可以看出这是一个linux上的内核补丁,该补丁最主要的特性是:用户区堆栈不可执行[Non-executableUser 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安置在数据段的攻击
- 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是个非常厉害的东西,好像天生就是缓冲区溢出的死对头,他严厉的审视每一种攻击方式,予以阻断。
- 基于x86段式内存管理的数据段不可执行
- 基于页式内存管理的数据段的页不可执行
- 内核页只读{
- Const结构只读
- 系统调用表只读
- 局部段描述符表(IDT)只读
- 全局段描述符表(GDT)只读
- 数据页只读
- 该特性不能与正常的LKM功能共存 } - 完全的地址空间随机映射{
- 每个系统调用的内核栈随机映射
- 用户栈随机映射
- ELF可执行映像随机映射
- Brk()分配的heap随机映射
- Mmap()管理的heap随机映射
- 动态链接库随机映射 } - 还有诸如把动态链接库映射到0x00开始的低地址的其他特性
这里顺便提一下Phrack58上Nergal写过的《The advanced return-into-lib(c) exploits》,这篇大作里提到用伪造栈桢(Fakeframe)和dl-resolve()技术突破PaX若干保护的方法,这极有可能*nix
应用层 exploit技术中最高级的技术,Nergal解决了几个问题:Stack/Heap/BSS不可执行、mmap随机映射,显然这种高级的技术仍然无法无条件的突破PaX,所以在一个运行完全版PaX的Linux上,你想发动缓冲区溢出可能是没有机会的。
- Grsecurity Grsec内含PaX,和Lids一样grsec支持内核MAC(Madatory AccessControl,强制访问控制),拥有非常多的特性,详见 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那样强,但硬件级别的支持无疑大大增加了软件和操作系统的兼容性,能够使缓冲区溢出的防护得到普及。
基本的栈溢出攻击,是最早产生的一种缓冲区溢出攻击方法,它是所有其他缓冲区溢出攻击的基础。但是,由于这种攻击方法产生的时间比较长,故而GCC编译器、Linux操作系统提供了一些机制来阻止这种攻击方法对系统产生危害。下面首先了解一下现有的用于保护堆栈的机制以及关闭相应保护机制的方法,为进一步分析基本栈溢出提供了良好的实验环境。
- 内存地址随机化机制
在Ubuntu和其他基于linux内核的系统中,目前都采用内存地址随机化的机制来初始化堆栈,这将会使得猜测具体的内存地址变得十分困难。
关闭内存地址随机化机制的方法是:
sysctl –w kernel.randomize_va_space=0
- 可执行程序的屏蔽保护机制
对于Federal系统,默认会执行可执行程序的屏蔽保护机制,该机制不允许执行存储在栈中的代码,这会使得缓冲区溢出攻击变得无效。而Ubuntu系统中默认没有采用这种机制。
关闭可执行程序的屏蔽保护机制的方法是:
sysctl –w kernel.exec-shield=0
- gcc编译器gs验证码机制
gcc编译器专门为防止缓冲区溢出而采取的保护措施,具体方法是gcc首先在缓冲区被写入之前在buf的结束地址之后返回地址之前放入随机的gs验证码,并在缓冲区写入操作结束时检验该值。通常缓冲区溢出会从低地址到高地址覆写内存,所以如果要覆写返回地址,则需要覆写该gs验证码。这样就可以通过比较写入前和写入后gs验证码的数据,判断是否产生溢出。
关闭gcc编译器gs验证码机制的方法是:
在gcc编译时采用-fno-stack-protector
选项。
在开启%gs校验时,exploit 代码只能在gdb调试环境下成功完成栈溢出。 - ld链接器堆栈段不可执行机制
ld链接器在链接程序的时候,如果所有的.o文件的堆栈段都标记为不可执行,那么整个库的堆栈段才会被标记为不可执行;相反,即使只有一个.o文件的堆栈段被标记为可执行,那么整个库的堆栈段将被标记为可执行。检查堆栈段可执行性的方法是:
如果是检查ELF库:readelf -lW $BIN | grep GNU_STACK
查看是否有E标记
如果是检查生成的.o文件:scanelf -e $BIN
查看是否有X标记
ld链接器如果将堆栈段标记为不可执行,即使控制了eip产生了跳转,依然会产生段错误。
关闭ld链接器不可执行机制的方法是:
在gcc编译时采用-z execstack
选项。
http://blog.csdn.net/wangxiaolong_china/article/details/6844304