演示Heap Spray(堆喷射)的原理

来源:互联网 发布:汽车驾驶模拟软件 编辑:程序博客网 时间:2024/04/30 12:21

   Heap Spray原理浅析 这篇文章应该是网上阐释堆喷原理最为详尽和易懂的一篇。唯一让我感到遗憾的是:文章结尾处给出的例子是基于javascript的,这对于我这种门外汉来说并不是一个很好的练功房。结合自己对文章的理解,在这给出c++版本的堆喷射演示代码。

#include <windows.h>#include <stdio.h>class base{char m_buf[8];public:virtual int baseInit1(){printf("%s\n","baseInit1");return 0;}virtual int baseInit2(){printf("%s\n","baseInit2");return 0;}};int main(){unsigned int bufLen = 200*1024*1024;base* baseObj = new base;char buff[8] = {0};char* spray = new char[bufLen];memset(spray,0x0c,sizeof(char)*bufLen);memset(spray+bufLen-0x10,0xcc,0x10);strcpy(buff,"12345678\x0c\x0c\x0c\x0c");baseObj->baseInit1();return 0;}

    演示用的代码就长这样,堆喷射的过程容我缓缓道来。

初始时baseObj分配得到的对象地址为:

0:000> dd baseObj L10012ff6c  004300A0

12ff6c是指针变量baseObj在栈上的地址,其值指向0x4300A0----这是分配在堆上的base对象。从这开始的4B是base对象的虚函数表指针,其值:

0:000> dd 4300a0 L1004300A0 0042202c
虚函数表指针当然就指向虚函数表,表中每项都是函数指针,当程序调用虚函数时就会依次通过检索虚函数表指针->虚函数表->虚函数来定位要执行的代码

    基于上面这种程序定位虚函数的方法,要利用虚函数的核心就变成伪造虚函数表。常见方法有:溢出栈变量,因为baseObj指针保存在栈上,溢出后baseObj指向的不再是堆上的base对象,而是指向某个被伪造的数值。这样,baseObj就认为这个伪造的数值就是从new base;返回的对象。接着只要在这个伪造的数值上继续构造虚函数表以及虚函数指针,就能达到利用的目的。

    当程序调用new分配大约200M的虚拟空间后,应该会把分配得到的空间存放到crt堆的VirtualAllocdBlocks队列中:

0:000> dt _PEB 7ffdf000 ;查看进程堆分布ntdll!_PEB+0x088 NumberOfHeaps    : 4+0x090 ProcessHeaps     : 0x773a8500  -> 0x001c0000 Void;进程有4个堆 堆句柄记录在0x773a8500开始的数组中0:000> dd 0x773a8500   L8773a8500  001c0000 00010000 00020000 003c0000;crt堆一般是程序堆数组元素中最后一个 所以调用new char[bufLen];后挂入从0x3c0000开始处的堆虚拟分配的HEAP!VirtualAllocdBlocks队列0:000> dt _HEAP 003c0000ntdll!_HEAP+0x0a0 VirtualAllocdBlocks : _LIST_ENTRY [ 0x630000 - 0x630000 ] 0:000> dd 0x530000 00630000  003c00a0 003c00a0 ;<----00630000处的8B是_LIST_ENTRY结构,指向HEAP!VirtualAllocdBlocks队列头00630030  0c800000 ;<----00530030处的16进制0c800000正好是请求分配的内存00630040  cdcdcdcd cdcdcdcd cdcdcdcd cdcdcdcd ;

从上面的结果来看,刚才调用new申请了从0x630000开始的大约200MB的内存,spray分配到的起始地址是0x630040,从此处开始到0x0CE30040结束,覆盖了共200MB的0x0C---所谓的slidecode,这当然包含了Heap Spray的目标地址0x0c0c0c0c:

0:000> dd 0x0c0c0c0c0c0c0c0c  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c0c0c0c1c  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c
之后通过语句:

strcpy(buff,"12345678\x0c\x0c\x0c\x0c");
溢出并覆盖对象baseObj的虚表,使得虚表指针指向0x0c0c0c0c;这个地址又是个自指向的,从0x0c0c0c0c取到虚函数表。之后再从0x0c0c0c0c取出函数指针执行。当然,这又从0x0c0c0c0c继续执行下去,直到遇到最后的shellcode部分

0:000> dd buff L10012ff64  00000000 ;buff位于栈地址0x12ff640:000> dd baseObj L10012ff6c  004300a0 ;baseObj位于栈地址0x12ff6c,覆盖后baseObj的虚函数表vftable指针值被设置为0x0c0c0c0c
之后baseObj去虚函数表0x0c0c0c0c中取虚函数,由于0x0c0c0c0c附近的内存块取到的值都是0x0c0c0c0c,而这个值被进程当做函数指针,因此最后会发生类似call 0x0c0c0c0c,引导Eip去堆空间0x0c0c0c0c处取指令运行。eip将执行不痛不痒的指令,最终将执行到堆空间的最后部分----那是我们的Shellcode部分。

总结起来:堆喷射是比较简单的一种利用方式;不同以往将shellcode存放在栈中,堆喷射将shellcode放在堆中,通过多种溢出方式组合使Eip执行到0x0c0c0c0c之类的堆空间

0 0
原创粉丝点击