本文整理自《0day安全:软件漏洞分析技术》
1 堆
2 代码
#include "stdafx.h"
#include <Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
//HANDLE和HLOCAL 是 void*
HLOCAL h1,h2,h3,h4,h5,h6;
HANDLE hp;
hp=HeapCreate(0,0x1000,0x10000);//返回创建堆的HANDLE
//因为调试态堆和常态堆差异很大,所以在这里设置了一个int 3断点,方便调试器附加,调试常态堆。
__asm int 3
h1=HeapAlloc(hp,HEAP_ZERO_MEMORY,3);//HEAP_ZERO_MEMORY 代表分配的内存用0来进行初始化,第三个参数是分配的字节数
h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,5);
h3=HeapAlloc(hp,HEAP_ZERO_MEMORY,6);
h4=HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h5=HeapAlloc(hp,HEAP_ZERO_MEMORY,19);
h6=HeapAlloc(hp,HEAP_ZERO_MEMORY,24);
HeapFree(hp,0,h1);
HeapFree(hp,0,h3);
HeapFree(hp,0,h5);
HeapFree(hp,0,h4);
return 0;
}
3 堆块的分配
用VC++ 2010 Express ,Release版本编译的。
断点断下来之后,内存映射状态如图所示。
图:内存映射状态
HeapCreate创建的堆起始地址是0x3B0000
在堆偏移0x178处是空表索引区,空表索引区有128项指针数据,该数组的每一项包括两个指针,用于标识一条空表。除了freelist[0],其他空表的索引都指向自身。
图:空闲态堆块数据结构
图:空闲态堆块数据结构
尾块大小0x130,单位是8字节,所以大小是0x980字节。(包含块首在内)。
请求的是3字节,实际分配了2个堆单位(一个堆单位是8字节,共16字节,加上堆首),不足8字节的按8字节分配。
查看freelist[0]中的空表指针,指向0x3B0708,尾块块首之后。
4 堆块的释放
释放了h1(16字节),h3(16字节),h5(32字节),根据空表的链入规则,h1和h3被链入freelist[2],h5被链入freelist[4]。
5 堆块的合并
h4释放了之后,h3、h4和h5可以合并成一个8堆单位的新块,链入freelist[8]。h3,h4,h5原来在freelist[2],freelist[4],合并的时候先把他们从空表中摘下,合并完后再链入空表。
6 块表
int _tmain(int argc, _TCHAR* argv[])
{
HLOCAL h1,h2,h3,h4;
HANDLE hp;
hp=HeapCreate(0,0,0);
__asm int 3
h1=HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h3=HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
h4=HeapAlloc(hp,HEAP_ZERO_MEMORY,24);
HeapFree(hp,0,h1);
HeapFree(hp,0,h2);
HeapFree(hp,0,h3);
HeapFree(hp,0,h4);
h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
HeapFree(hp,0,h2);
return 0;
}
堆只有是在可扩展的时候,才会启用快表,使用hp=HeapCreate(0,0,0);创建一个可扩展的堆。一开始使用HeapAlloc的时候进行的是空表分配,释放的时候先链入快表。lookaside[1]会链入两个堆单位,003B1EA0和003B1E90。
HeapAlloc再次分配会优先分配快表。lookaside[2]又为空了。
7 DWORD SHOOT
堆溢出利用就是用数据去溢出下一个堆块的块首,改写块首中的前向指针(flink)和后向指针(blink),然后在分配、释放、合并等操作发生时伺机获得一次向内存任意地址写入任意数据的机会。
int remove(ListNode *node)
{
node->blink->flink=node->flink;
node->flink->blink=node->blink;
return 0;
}