演示Heap Spray(堆喷射)的原理
Posted Yuri800
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了演示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分配得到的对象地址为:
12ff6c是指针变量baseObj在栈上的地址,其值指向0x4300A0----这是分配在堆上的base对象。从这开始的4B是base对象的虚函数表指针,其值:0:000> dd baseObj L1 0012ff6c 004300A0
虚函数表指针当然就指向虚函数表,表中每项都是函数指针,当程序调用虚函数时就会依次通过检索虚函数表指针->虚函数表->虚函数来定位要执行的代码。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
溢出并覆盖对象baseObj的虚表,使得虚表指针指向0x0c0c0c0c;这个地址又是个自指向的,从0x0c0c0c0c取到虚函数表。之后再从0x0c0c0c0c取出函数指针执行。当然,这又从0x0c0c0c0c继续执行下去,直到遇到最后的shellcode部分strcpy(buff,"12345678\\x0c\\x0c\\x0c\\x0c");
之后baseObj去虚函数表0x0c0c0c0c中取虚函数,由于0x0c0c0c0c附近的内存块取到的值都是0x0c0c0c0c,而这个值被进程当做函数指针,因此最后会发生类似call 0x0c0c0c0c,引导Eip去堆空间0x0c0c0c0c处取指令运行。eip将执行不痛不痒的指令,最终将执行到堆空间的最后部分----那是我们的Shellcode部分。0:000> dd buff L1 0012ff64 00000000 ;buff位于栈地址0x12ff64 0:000> dd baseObj L1 0012ff6c 004300a0 ;baseObj位于栈地址0x12ff6c,覆盖后baseObj的虚函数表vftable指针值被设置为0x0c0c0c0c
总结起来:堆喷射是比较简单的一种利用方式;不同以往将shellcode存放在栈中,堆喷射将shellcode放在堆中,通过多种溢出方式组合使Eip执行到0x0c0c0c0c之类的堆空间
以上是关于演示Heap Spray(堆喷射)的原理的主要内容,如果未能解决你的问题,请参考以下文章