Windows堆溢出浅讲

堆表

堆表分为空表free和快表lookaside组成。

空表

free数组共有$128$项,每一项是个双向列表的下一项地址。free[1]链接了所有大小为$8$字节的堆块,free[2]链接大小为$16$字节的,以此类推到free[127]链接大小为$1016$字节的堆块。free[0]从小到大链接剩余的大于$1016$字节大小的堆块。

快表

这玩意儿跟空表差不多,但每一项最多链接$4$个堆块,且都被设置为占用态,不能被合并。

堆块分配

快表分配时必须精确分配。普通空表最优大小查找失败时会查找次优堆块。零号空表free[0]从大到小倒着找合适大小。

堆块合并

小块大小小于$1\mathrm{KB}$,大块大小在$[1,512)\mathrm{KB}$之间,巨块在$512\mathrm{KB}$及以上。

小块分配:快表、普通空表、堆缓存、零号空表、内存紧缩、NULL

小块释放:快表、空表

大块分配:堆缓存、零号空表

大块释放:堆缓存、零号空表

巨快分配:虚分配

巨快释放:直接释放

堆结构

这里使用HeapCreate创建堆,创建堆底层都为RtlAllocateHeap。调试堆时不能直接在调试态运行,否则策略差别很大,应手动插入int 3终端后Attach上。HeapCreatemallocGetProcessHeap都分别返回各自设定的堆区。堆区偏移0x178是空表索引区。当堆区刚刚初始化后,只有一个空闲态的大块叫“尾块”,零号空表指向这玩意儿。这第一个“尾块”位于堆区偏移0x688处,当堆设定为可扩展的HeapCreate(0,0,0);时启用快表,快表就在0x688处。

占用态堆块结构:

1
2
3
4
5
6
7
8
8byte Block head
2byte Self Size
2byte Preivous chunk size
1byte Segment index
1byte Flags(Busy/Extra present/Fill pattern/Virtual Alloc/Last entry/FFU1/FFU2/No coalesce)
1byte Unused bytes
1byte Tag index(debug)
Block body ...

空闲态在$8$字节的Block head后面还有$8$字节,分别是一个$4$字节的Flink in freelist和一个$4$字节的Blink in freelist。

堆块分配

堆块大小为申请的大小加上$8$字节的Block head,一个堆单位为$8$字节,不足的按$8$字节算。

DWORD SHOOT

当某堆块要被从双向链表中卸下来时,使用堆溢出方式改写该块的Flink和Blink。进行卸载操作时会向Flink指向的地址写入$4$字节的Blink的数据,造成任意内存位置$4$字节写操作。