操作系统: Windows 2000 Service Pack 4
集成开发环境: Microsoft Visual C++ 6.0 SP6
构建版本:Release版本
实验代码:
1 #include <stdio.h> 2 #include <windows.h> 3 4 void main() 5 { 6 HLOCAL h1, h2, h3, h4; 7 HANDLE hp; 8 9 // 启用快表 10 hp = HeapCreate(0, 0, 0); 11 printf("hp: %p\n", hp); 12 13 // 避免程序监测出调试器而使用调试堆管理策略 14 __asm int 3 15 16 h1 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8); 17 printf("h1: %p\n", h1); 18 h2 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8); 19 printf("h2: %p\n", h2); 20 h3 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 16); 21 printf("h3: %p\n", h3); 22 h4 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 24); 23 printf("h4: %p\n", h4); 24 25 HeapFree(hp, 0, h1); 26 HeapFree(hp, 0, h2); 27 HeapFree(hp, 0, h3); 28 HeapFree(hp, 0, h4); 29 30 h2 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 16); 31 printf("h2: %p\n", h2); 32 33 HeapFree(hp, 0, h2); 34 }
启用快表之后,堆结构也会发生一些变化,比较主要的是初始化堆的最大块不再位于0x688偏移处了,这个位置被快表霸占了,查看0x178偏移处的空表索引区也可以发现。(我的机器上堆的首地址是0x00360000)
此时到0x00360688内存区查看,会发现快表区域的数据都是空的。这也是为什么要先申请堆块,然后释放堆块的原因,从空表中申请堆块,然后释放8,16,24个字节的堆空间,由于是8的整数倍,因此会优先链入快表中。
单步到四次释放操作结束,红框框出的分别是8,16,24个字节堆块的地址。
接下来再到0x00361EA0查看快表堆块的结构,,可以看到,和空表堆块有两个明显的不同,一个是块首的标志位始终是0x01,也就是busy,这也是快表堆块不进行合并的原因;另一个是块首只包含指向下一个堆块的指针,不存在指向前一个堆块的指针。(具体内容可以参见Windows环境下堆表的空闲双向链表结构)
经过前面的分配释放操作,快表已经非空了,如果再申请8,16,24字节的堆块空间的时候,系统会直接从块表中进行分配。此后进行h2释放操作,16字节的堆块又会重新链入快表中。
资料引用:
王清. 《0day安全:软件漏洞分析技术(第2版)》. 电子工业出版社. 2011
Matt Connover. "Windows Heap Internals". 2004
如有错误,欢迎指正,谢谢。
Windows环境下堆管理系统的快表介绍