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

   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 L1
0012ff6c  004300A0


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

0:000> dd 4300a0 L1
004300A0 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   L8
773a8500  001c0000 00010000 00020000 003c0000

;crt堆一般是程序堆数组元素中最后一个 所以调用new char[bufLen];后挂入从0x3c0000开始处的堆虚拟分配的HEAP!VirtualAllocdBlocks队列
0:000> dt _HEAP 003c0000
ntdll!_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 0x0c0c0c0c
0c0c0c0c  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c
0c0c0c1c  0c0c0c0c 0c0c0c0c 0c0c0c0c 0c0c0c0c
之后通过语句:

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

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

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

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页