Windows堆溢出基础

堆的工作原理

Windows堆的历史

微软没有完全公开windows系统中堆管理的细节,目前未知对windows堆的了解是基于安全专家、逆向工程师的研究成果。经过无数前辈的努力,Windows NT4\2000 sp4上的堆管理策略——这里主要堆对管理中与攻击相关的数据结构与算法——基本被研究清楚。

Windows系统的堆管理机制大致可以分为三个阶段:

  1. Windows2000~WindowsXP SP1:堆管理只考虑完成分配任务和性能因素,完全未考虑安全因素
  2. Windows XP 2~Windows2003: 加入安全因素,如修改块首格式、加入安全cookie,双向链表节点在删除时做指针验证等。这些措施加大了堆溢出攻击的难度,但高级攻击技术仍可能成功。
  3. Windows Vista~Windows 7:在堆分配效率、安全性、稳定性方面都是里程碑

本文使用Windows2000操作系统进行堆管理策略学习。

Windows堆与栈的区别

  • 栈变量包括函数内部的普通变量、数组等。栈变量使用时不需要额外申请,系统会根据函数中的变量声明自动在函数栈帧中预留空间。栈空间由系统维护。分配(sub esp,xx)和回收(add esp,xx)由系统完成,最终达到栈平衡。这些操作对程序员是透明的。
  • 堆是程序运行时动态分配的内存。堆在使用时需要使用专用函数进行申请,如C中的malloc函数、C++中的new函数。堆内存的申请可能成功也可能失败。一般使用一个堆指针操作(读、写、释放)申请得到的内存。使用完毕后要把堆指针传给堆释放函数回收内存,否则会造成内存泄露。释放函数如free、delete。

堆的数据结构和管理策略

  • 堆块:堆区的内存按不同大小组织成块。以堆块为单位进行标识,而不是按照字节。堆块包括两个部分:块首和块身。块首是一个堆块头部的前几个字节,用于标识这个堆块自身的信息:块的大小、空闲还是占用态;块身紧跟在块首后,是最终分配给用户使用的数据区。堆管理系统返回的指针一般指向块身的起始位置。
  • 堆表:堆表位于堆区的起始位置,用于索引堆区中所有堆块的信息:堆块的位置、堆块的大小、空闲还是占用。堆表只索引所有空闲态的堆块。

最重要的堆表有两种:空闲双向链表(Freelist,简称空表),快速单向链表(Lookaside,简称快表)。

空表

  1. 空闲堆块的块首包含一对重要的指针,用于将空闲堆块组织成双向链表。按照堆块的大小不同,空表被分为128条。堆表包含一个128项的指针数组,称为空表索引。数组的每一项包括两个指针,用于标识一条空表。
  2. 空闲堆块的大小 = 索引项(ID) * 8(字节) free[1] = 8Byte,free[127] = 1016Byte。1024<=free[0]<512KB,free[0]升序排列。

快表

  1. 单向链表,不会发生堆块合并(空闲块首块被设置为占用态,防止堆块合并)
  2. 快表总被初始化为空,且每条快表最多四个节点。
  • 快表空闲块被设置为占用态,不会发生堆块合并操作。
  • 只有精确匹配才会分配。
  • 快表是单链表,操作比双链表简单。
  • 分配释放优先使用快表,失败才使用空表。
  • 快表只有四项,容易被填满,空表也被频繁使用。

堆的操作可分为分配、释放、合并。堆的分配、释放由程序控制,合并由堆管理系统自动完成。

堆块的分配

  • 分为快表分配、普通空表分配、零号空表分配。
  • 快表分配:找到大小匹配的空闲堆块——状态修改为占用态——从堆表卸下——返回指向堆块块身的指针给程序使用。
  • 普通空表分配:首先寻找最优空闲块分配——寻找次优空闲块分配,即最小能满足要求的空闲块。
  • 零号空表分配:先从free[0]反向查找最后一个块(即最大块),若满足要求正向搜索满足要求的最小空闲堆块进行分配。

堆块的释放

  • 堆块状态改为空闲——链入相应的堆表。所有释放块都链入堆表的末尾,分配的时候从末尾拿。

堆块的合并

  • 发现两个块彼此相邻,进行堆块合并操作。
  • 合并包括:将两个块从空闲链表卸下——合并堆块——调整合并后块的块首信息——重新链入空闲链表。
  • 小块:<1KB;大块:1KB<=SIZE<512KB;巨块>=512KB。

堆中漫游

堆分配函数的调用关系

所有堆分配函数都将使用位于ntdll.dll中的RtlAllocateHeap()函数进行分配。

malloc虽然未指明用哪个堆区进行分配,实际已经使用HeapCreate()函数创建了堆区。

堆的调试方法

我们将使用OD对示例程序进行调试,以从字节层面学习堆的数据结构

验证程序

#include <windows.h>
main()
{
	HLOCAL h1,h2,h3,h4,h5,h6;
	HANDLE hp;
	hp = HeapCreate(0,0x1000,0x10000); //创建后将堆区起始地址返给EAX
	__asm int 3

	h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,3);
	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);
	
	//free block and prevent coaleses
	HeapFree(hp,0,h1); //free to freelist[2] 
	HeapFree(hp,0,h3); //free to freelist[2] 
	HeapFree(hp,0,h5); //free to freelist[4]
	
	HeapFree(hp,0,h4); //coalese h3,h4,h5,link the large block to freelist[8]

	
	return 0;
}
  1. 直接调用OD,WD加载程序,堆管理器会检测到进程处于调试状态。调试态堆管理策略与常态堆管理策略差异:
    • 调试态只使用空表分配,不使用快表分配
    • 调试态所有堆块加上16个字节尾部防止溢出(防止程序溢出而非堆溢出攻击),包括8个字节的0xAB和8个字节的0x00。
    • 块首的标志位不同。
  2. 因此在程序加入人工断点 _asm int 3,让程序执行,再用调试器attach进程。
  3. 将OD设置成默认调试器: options—”just-in-time-dubgging”——”Done”,此时直接运行调试程序,会自动唤起OD。

堆表的识别

OD attach上调试进程后,会自动停在“__asm int 3”处,此时点击“M”,查看内存映射状态

0x00130000是进程堆区,0x00520000是HeapCreate创建的堆区。

在内存区Ctrl+Gc查看0x00520000状态

  • ▪ 从0x00520000开始,堆表中依次包含的信息为段表索引(Segment List)、虚表索引(Virtual Allocation list)、空表使用索引(freelist usage bitmap)和空表索引区(偏移0x178处)。前三个与堆溢出关系不大。
  • 堆被初始化状态:
    • 只有一个空闲态的大块,称为”尾块”;
    • 位于堆偏移0x0688[0x00520688]处,启用快表后这个位置将是快表。
    • Freelist[0]指向”尾块”
    • -除零号空表索引外,其余各项索引均指向自己。即所有空闲链表没有空闲块。0170+HEX(128*8)=0170+400=0570,即0x00520570,此为Freelist[128]的地址。
  • – 快表指针位于偏移0x0584处。指针为NULL,堆只有在可扩展时快表才会启用。启用快表要用HeapCreate(0,0,0)创建可扩展堆。

首先介绍下堆块的块首数据含义

占用态如下:

空闲态:与占用态基本一致,但块首后数据区的前8个字节用于存放空表指针。此8个字节在堆变为占用态后重新分回块身用于存放数据。

  1. 实际堆块起始于0x520680,一般引用堆块的指针会越过8字节的块首,直接指向数据区。
  2. 尾块大小单位为8字节,此尾块为0130*8=0x90字节
  3. 堆块大小包含块首在内。

堆块的分配

分配细节:

  • 堆块大小包含块首在内,请求分配N字节,实际分配N+8字节。
  • 堆块的单位为8字节,不足8字节按8字节分配。
  • 初始状态下,快表和空表均为空。使用偏移0x0688处的”次优块”分配。
  • 由于次优分配,分配函数会陆续从尾块切走内存,修改尾块大小,把Freelist[0]指向新的尾块位置。

在OD中运行完6次分配

查看堆中情况:

Freelist[0]目前指向00520708,此为尾块地址。00520708-00520688=80H=8*16=128字节。查看尾块大小为0x0120,原尾块大小为0x0130,共切出0x10*8=16*8=128字节。(堆单位为8字节)

堆块的释放

OD中先执行释放h1,h3,h5:

前三次只释放奇数次分配的堆块,内存不连续,不会发生合并。新产生了Freelist[2]和Freelist[4]两个链表。h1和h3并入Freelist[2],h5并入Freelist[4]。

堆块的合并

  1. 在OD中释放h4,h3,h4,h5空闲且相邻,发生合并操作。共2+2+4=8个堆单位,将被链入Freelist[8].Freelist[2]由标识h1和h3变为只标识h1,freelist[4]由标识h5变为指向自身。
  2. 合并只修改了块首的数据。
  3. 合并需要修改多处指针,比较费时,故只发生在空表中。在 强调效率的快表中,一般通过设置堆块为占用态禁止合并。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇