Windows堆溢出浅讲

堆表

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

空表

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

快表

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

堆块分配

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

堆块合并

小块大小小于1KB,大块大小在[1,512)KB之间,巨块在512KB及以上。

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

小块释放:快表、空表

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

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

巨快分配:虚分配

巨快释放:直接释放

堆结构

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

占用态堆块结构:

plaintext
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字节写操作。