以文本方式查看主题 - 中文XML论坛 - 专业的XML技术讨论区 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- Windows下的HEAP溢出及其利用 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=51277) |
-- 作者:一分之千 -- 发布时间:8/12/2007 4:27:00 PM -- Windows下的HEAP溢出及其利用 Windows下的HEAP溢出及其利用(上) 这种问题我遇到过,曾经费了不少周折,把此篇敬献给大家: 以下所有程序的测试环境为: 二、Windows的HEAP管理机制简述 对于一个进程来说可以有多个HEAP区,每一个HEAP的首地址以句柄来表示:hHeap,这也就是RtlAllocateHeap的第一个参数。每个HEAP区的整体结构如下: +-------------------------------------------------------------------+ heap总体管理结构区存放着一些用于HEAP总体管理的结构,这不是我们所关心的。双指针区存放着一些成对出现的指针,用于定位分配内存以及释放内存的位置,这可能是某种树结构,我还没完全搞清楚。用户分配内存区是用户动态分配内存时实际用到区域,也这是HEAP的主体。 当我们调用RtlAllocateHeap(HANDLE hHeap,DWORD dwFlags,SIZE_T dwBytes)来分配内存时将进行以下操作: 两块连续分配的内存块之间并不是紧挨着的,而是有8字节的管理结构,最末尾的一块内存后面还另外多了8字节的指针指向双指针区,就是上面提到过的。 假设有以下程序: 第一次分配后: 第二次分配后: 在第二次分配内存的时候会利用第一块内存管理结构后面那两个指针进行一些操作,其中会有一次写内存的操作: 77FCB397 mov [ecx], eax 这时的eax和ecx分别指向: 写到这里大家一定就明白HEAP溢出如何利用了吧?假设我们分配完buf1之后向其中拷贝内容,拷贝的内容大小超过buf1的大小,即16字节,就会发生溢出,当如果我们覆盖掉了那两个4字节的指针,而下一次分配buf2之前又没有把buf1释放掉的话,那么就会把一个4字节的内容写入一个地址当中,而这个内容和地址都是我们能够控制的,这样我们就可以控制函数的流程转向我们的shellcode了。 三、HEAP溢出的利用 /* //在进程的默认HEAP当中分配内存 //先分配一块16字节内存buf1 //把32字节的mybuf拷贝到16字节的buf1里面,发生溢出! //再次分配一块16字节的内存buf2,此时buf1还没有被释放 //释放这两块内存 return 0; 我们把这个程序用VC按照RELEASE方式编译,并在命令行下运行它(不要在MSDEV中调试运行)。如果你没有装SOFTICE的话就会弹出一个错误对话框显示:\"0x77fcb397\"指令引用的\"0x59595959\"内存。该内存不能为\"written\"。 可以注意到0x59595959就是YYYY,这就证明了程序在向YYYY指向的内存地址进行写操作,写的内容是什么呢?如果你启动了SOFTICE的话,运行这个程序的时候SOFTICE就会自动跳出来,并停在下面的指令处: 77FCB397 mov [ecx], eax 此时eax=0x58585858,ecx=0x59595959,因为0x59595959这个地址没有映射内存页面,所以执行这个指令的时候出错了。 0x58585858和0x59595959正是我们覆盖buf1所用的XXXX和YYYY,实际进行的内存分配操作就是上面我们说过的那样: 第一次分配后: 溢出后: 这样当第二次分配buf2的时候就会把XXXX写入到YYYY所指向的地址当中去,由于XXXX和YYYY都是我们所能够控制的,所以我们就可以把shellcode地址写入到堆栈中保存的函数返回地址去,这样当函数返回的时候就会跳到我们的shellcode去执行。 当然这是比较理想的情况,实际上利用这个漏洞还有很多问题,下面我们以一个实际的例子来看看具体利用这个漏洞的情况。 四、实战 /* #include int main() char *buf1, *buf2; if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) //我们自己建立一个HEAP,以免破坏掉进程默认HEAP以后shellcode无法正常运行 整个程序很简单,监听在1500端口,先分配了32字节的buf1,并把客户端发送过来的内容的前64字节拷贝到buf1里,这里是由于错误的使用了宏而发生的溢出(应该用BUFFLEN,但用了COPYLEN),这种情况在实际中也是很容易发生的。这样当再分配buf2的时候就会有写内存的操作,使得我们可以利用这个漏洞。 现在我们就可以写个攻击程序来溢出它,并且控制改写任意4字节的内存。那么到底改写什么地方比较合适呢?我想来想去有4种地方可以改写,用来控制去执行我们的shellcode: 1.堆栈中保存的函数返回地址 1和2都是保存在堆栈中的地址,因此在不同的系统中可能是不一样的,如果改写这两个地址的话虽然也可能成功,但是无法保证程序的通用性,从实际攻击的成功率的角度考虑,就不能用这两种地址。 3是线程默认异常处理指针(即顶层异常处理指针),它在同一版本的操作系统中是一个固定的值。这里稍微介绍一下Windows结构化异常处理的基本原理。Windows的结构化异常处理(SEH)是一种对程序异常的处理机制,它是按照链式层状结构进行处理的。当线程中发生异常时,操作系统首先找到线程环境块TEB指向的第一个内存单元(即fs:[0])中所包含的地址,这个地址指向的地方存放着上一层异常链指针,而在这个地址+4的地方存放着最低层异常处理指针,操作系统就自动跳到这个指针所指向的函数去执行来进行异常处理。当这个函数无法对异常进行处理的时候,再根据上一层的异常链指针来寻找到上一层的异常处理指针来处理,如果所有的异常处理函数都无法处理这个异常,那么系统就使用默认异常处理指针(即顶层异常处理指针)来处理异常情况,就是这个函数: LONG UnhandledExceptionFilter(STRUCT _EXCEPTION_POINTERS *ExceptionInfo); 这个函数负责显示一个错误对话框,来指出出错的原因,这就是我们一般的程序出错的时候显示错误对话框的原因。 我们可以通过SetUnhandledExceptionFilter这个函数来设置默认异常处理指针,把SetUnhandledExceptionFilter反汇编一下可以发现它非常简单: #include 运行这个程序就可以获得你当前系统中存放默认异常处理的地址了。再回到我们HEAP溢出的问题上,我们可以通过改写默认异常处理来改变程序的流程,也就是改写0x77ebf44c这个内存单元的值为shellcode的地址。这是一个比较通用的方法,成功率也比较高。 还有一种方法是改写TEB即fs:[0]的地方,系统发生异常的时候会从这个地方取出最底层的异常链来进行异常处理,我们可以自己构造一个异常处理结构指向我们的shellcode,这样就可以达到控制程序流程的目的了,这个fs:[0]对于单线程的程序是比较固定的,但是对于多线程的不同线程会有所变化,所以还是不如改写默认异常处理好,因此我们最后决定改写默认异常处理的内存单元。 下面就是shellcode存放在哪里的问题了,我觉得这个问题没有通用的方法,要根据发生溢出的程序的情况而定,如果可以放在一个发生异常时有寄存器能够指向的地方那就是最完美的情况,这样就可以用一个系统DLL中有JMP EXX指令的地址来改写默认异常处理,其中EXX是指向shellcode的寄存器。但是这种情况似乎比较少见,一般shellcode也没办法放到这种位置上来,那就只能用shellcode的地址来直接定位,可以在shellcode前面放上大量NOP来提高成功率。对于前面那个漏洞程序,我们就使用shellcode的地址来改写默认异常处理的方法。 但是这里还有一个小问题,发生写内存操作的有两个指令: 77FCB397 mov [ecx], eax 这样不但会把shellcode地址写进默认异常处理地址中,也会把默认异常处理地址写进[shellcode地址+4]的内存单元当中,这样就把shellcode中要执行的指令给破坏了。要解决这个问题,我们可以用一个jmp 6这样的指令来代替nop,这样就能够跳过后面被破坏的字节。
|
-- 作者:一分之千 -- 发布时间:8/12/2007 4:28:00 PM -- Windows下的HEAP溢出及其利用(下) 理论上的问题都解决了,现在就可以写出攻击程序来了: /* unsigned char shellcode[] = int main(int argc, char *argv[]) char buff[4096] = {0}; if(argc != 3) sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); i = 4096; closesocket(sClient); 我们首先以RELEASE模式来编译有漏洞的程序win_heap_vul.c,并运行起来。 C:\\HEAP\\client\\Debug>win_heap_exp localhost 1500 如果攻击成功,就会在目标主机上打开7788端口,用nc连上去。 Microsoft Windows 2000 [Version 5.00.2195] C:\\HEAP\\server\\Release>dir C:\\HEAP\\server\\Release> C:\\HEAP\\server\\Release 的目录 2002-05-28 18:10 C:\\HEAP\\server\\Release>exit 成功的攻击了HEAP溢出漏洞的程序,并打开了7788端口。 五、总结 但是现在利用这种漏洞还存在一些问题: 1、对于线程异常链上所有异常处理函数都无法处理的异常,系统才交给默认异常来处理,只有在这种情况下我们改写默认异常处理才有效。也就是说,只有溢出后弹出错误对话框的漏洞程序,我们才能够用上面方法来利用,否则的话,就必须改写其他地方,例如TEB的第一个内存单元,或者保存在堆栈中的函数返回地址等。 2、上面的程序用的是shellcode的地址直接定位的方法,这种方法在某些情况下可能会造成攻击程序的通用性比较差。其实对于上面那个漏洞程序,我们也可以用在系统DLL中的JMP EBP-XXX指令的方法来定位shellcode,这样的指令是可以找到的,但是这种方法并不具备通用性,因此在上面例子里还是用了直接定位shellcode的方法。对于一些可以反复攻击的漏洞程序,我们也可以采取暴力法来猜测这个地址。 3、如果溢出发生在进程的默认HEAP上(即通过GetProcessHeap()获得的),那么在执行shellcode时会出现一些问题,因为溢出破坏掉一些HEAP管理结构,而shellcode中调用的一些API函数会在进程默认堆上进行内存分配工作,因此会导致shellcode无法正常运行。要解决这个问题就需要在shellcode里下一些功夫,可以在shellcode实际功能之前恢复被破坏的管理结构,或者不使用进行HEAP分配的函数,这肯定是一个可以解决的问题。 Windows下的HEAP溢出的发展还不完善,没有统一通用的方法来利用,要根据出现溢出的程序的具体情况来使用不同的方法来进行攻击。我写下本文的目的在于抛砖引玉,希望众位高手能够提出更多更好的办法来解决这些问题,使得HEAP溢出能够像STACK溢出那样容易利用。 |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
3,846.191ms |