Windows 堆溢出

MS 没有完全公开 Windows 的堆管理细节,目前对 Windows 堆的了解主要基于技术狂热者、黑客、安全专家、逆向工程师等的个人研究成果。

目前 Windows NT4/2000 SP4 上的堆管理策略基本(与攻击相关的数据结构和算法)研究清楚。

堆溢出的重要研究者:

Halvar Flake:2002 年 Blach Hat 上首次挑战 Windows 的堆溢出,揭秘了堆中一些重要的 Data Structure 和算法。演讲“Third Generation Exploitation”。

David Litchfield:安全界传奇人物。发现了横扫世界的蠕虫所利用的 0day,著名安全咨询公司 NGS(Next Generation Security)创始人。2004 年在 Black Hat 上演讲“Windows Heap Overflows”首次较全面地介绍 Windows 2000 下的堆溢出技术细节,包括 Data Structure、堆分配算法、利用思路、劫持进程的方法、执行 shellcode 时会遇到的问题等。那次演讲的白皮书是研究 Windows 堆溢出人员的必读文献

Matt Conover:演讲的“XP SP2 Heap Exploitation”全面提示了 Windows 堆中溢出相关的所有数据结构和分配策略,提出突破 Windows XP SP2 平台下重要安全机制的防护进行堆溢出的方法。

堆管理机制需要兼顾内存有效利用、分配决策速度、健壮性、安全性。MS 操作系统堆管理机制发展大致分为三个阶段:

1. Windows 2000 - Windows XP sp1:只考虑了完成分配任务和性能因素,没有考虑安全因素,较容易利用。

2. Windows XP 2 - Windows 2003:加入安全因素,如修改了快首的格式、加入安全 cookie、双向链表结点在删除时会做指针验证等。利用非常困难,需要高级攻击技术才可能利用成功。

3. Vista:效率和安全性上的里程碑。

原书主要讨论 Windows 2000 - Windows XP sp1 平台的堆管理策略。

堆与栈的区别

栈空间在程序设计时已经规定好怎么使用、使用多少内存空间。典型的栈变量包括函数内部的普通变量、数组等。使用时不需要申请,栈空间由系统维护,其分配和回收由系统完成。这些对程序员透明。

堆具备以下特性:

1. 堆在程序运行时动态内存分配。

2. 使用时需要程序员用专用函数进行申请,如 C 中的 malloc、C++ 中的 new 等。堆内存申请可能成功,也可能失败,与内存大小、机器性能和当前运行环境有关。

3. 一般使用一个指针来使用申请得到的内存(包括读、写、释放)。

4. 使用后需要将堆指针传给释放函数回收内存,否则会造成内存泄露。

5. 堆的增长方向为内存低址向高址排列(不考虑碎片),地址变化范围很大;栈由内存高址向低址增加。

与整齐的栈不同,堆往往显得“杂乱无章”,堆溢出利用是内存利用技术的一个转折点。

堆的数据结构与管理策略

OS 一般会提供一套 API 把复杂的堆管理机制屏蔽掉,但堆溢出利用需要了解这些知识。

现代操作系统的堆数据结构一般包括堆快和堆表两类。

堆块:堆区内存按照不同大小组织成块,以堆块为单位标识。一个堆块包括块首和块身。块首是堆块头部的几个字节,用来标识堆块信息,如大小、空闲/占用等;块身是分配给用户使用的数据区。进行连续内存申请时,可以发现内存之间存在“空隙”,“空隙”即是被块首占用的内存。

堆表:位于堆区的起始位置,用于索引堆区所有堆块的信息。堆表的数据结构决定了堆区的组织方式和效率,往往会采用平衡二叉树等高级数据结构。现代操作系统的堆表往往不只用一种数据结构。

Windows 中占用态的堆块被使用它的程序索引,而堆表只索引所有空闲态的堆块。最重要的堆表有空闲双向链表(空表)Freelist 和快速单向链表(快表)Lookaside。

空表

空闲堆块的块首包含一对重要的指针——将堆块组织成双向链表。按照堆块的大小,空表被分为 128 条。

堆区一开始的堆表区中有一个空表索引(Freelist Array),是一个 128 项的指针数组,数组的每一项包括两个指针,用于标识一条空表。

空表索引的第 i+1 项(free[i],i>0)标识了所有大小为 8*i 字节的空闲堆块(free[127] 标识的堆块大小是 1016);空表第一项(free[0])标识了所有大于等于 1024 字节的堆块(大于等于 512 字节),这些堆块按照大小的升序在 free[0] 中依次链接下去。

快表

快表是 Windows 用来加速堆块分配而采用的堆表。快表是单向链表,其中的堆块从来不会发生合并(空闲块块首被设置为占用态,防止堆块合并)。快表也有 128 条,组织结构与空表类似。快表总是被初始化为空,而且每条快表最多只有 4 个结点。块表是单链表,插入删除都比空表所用的指令少。

堆块操作可以分为堆块分配、堆块释放和堆块合并三种。分配和释放在程序提交内存申请和释放操作时执行,合并则由系统自动完成。

堆块分配

这里不讨论堆缓存、低碎片堆和虚分配,只考虑以下三种情况:

1. 快表分配:寻找大小匹配的空闲堆块,将其状态改为占用态并从堆表中卸下,最后返回指向堆块的指针。

2. 普通空表(小于 512 字节):先寻找最优的空闲块分配,若失败,则寻找次优(最小的并能满足要求的)空闲堆块分配。

3. 零号分配(free[0]):先从 free[0] 反射查出最大块的大小,若能满足要求,则正向寻找最小能满足要求的空闲块分配。

当发生次优分配时,一个大于所需空间的空闲堆块会被使用,这时会从这个空闲堆块中精确分配所需大小的空间,剩余空间重新标注块首并链入空表。快表只有在精确匹配时才分配,故不存在这个问题。

堆块释放

将堆块状态改为空闲,链入相应堆表。所有释放快都链入堆表末尾,分配时也先从末尾拿。

堆块合并

当堆块管理系统发现两个空闲堆块彼此相邻时,就会进行堆块合并操作。堆块合并会将两个块从空闲链表中卸下、合并堆块、调整合并块的块首信息并重新链入空闲链表。实际上,堆区还有一种内存紧缩操作(shrink the compact),由 RtlCompactHeap 执行,操作效果与磁盘碎片整理差不多,会对整个堆进行调整,尽量合并可用的碎片。

Windows 将堆内存大小分为三类,小块(size<1kb)、大块(1kb<=size<512kb)、巨块(size>=512kb)。对应的分配和释放算法也有三类:

堆中漫游

Windows 下众多的堆分配函数最终都将使用 ntdll.dll 中的 RtlAllocateHeap() 实现,这也是用户态能看到的最底层的堆分配函数。研究 Windows 堆只要研究这个函数。

漂亮的堆溢出 exploit 需要对堆中的重要数据结构掌握到字节级别。下面是一段调试堆的程序:

 /*****************************************************************************
To be the apostrophe which changed "Impossible" into "I'm possible"! POC code of chapter 6.2 in book "Vulnerability Exploit and Analysis Technique" file name : heap_debug.c
author : failwest
date : 2007.04.04
description : demo show of how heap works
Noticed : 1 only run on windows 2000
2 complied with VC 6.0
3 build into release version
4 only used for run time debugging
version : 1.0
E-mail : failwest@gmail.com Only for educational purposes enjoy the fun from exploiting :)
******************************************************************************/ #include <windows.h>
main()
{
HLOCAL h1,h2,h3,h4,h5,h6;
HANDLE hp;
hp = HeapCreate(,0x1000,0x10000);
__asm int h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,);
h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,);
h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,);
h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,); //free block and prevent coaleses
HeapFree(hp,,h1); //free to freelist[2]
HeapFree(hp,,h3); //free to freelist[2]
HeapFree(hp,,h5); //free to freelist[4] HeapFree(hp,,h4); //coalese h3,h4,h5,link the large block to freelist[8] return ;
}

其中 API 函数 HeapCreate() 定义如下:

HANDLE WINAPI HeapCreate(
__in DWORD flOptions, // 指定如何在堆上执行各种操作:是否独占、分配失败是否抛异常、内容是否可执行
__in SIZE_T dwInitialSize, // 初始大小
__in SIZE_T dwMaximumSize ); // 最大容量

对堆的调试比栈调试要求要高:堆分配算法依赖于操作系统、编译器版本、编译选项、build 类型、甚至 VM 版本等等。

调试堆时不能直接用 OllyDbg、WinDbg 等加载程序,否则堆管理函数会检测到当前进程处于调试状态,从而使用调试态的管理策略。

调试态的堆管理策略和常态管理策略差异很大:

1. 调试态不使用快表,只用空表分配。

2. 调试态堆块都被加上多余的 16 字节尾部(8 字节的 0xAB 和 8 字节的 0x00),用来防止程序发生溢出。

3. 块首的标志位不同。

如果堆溢出时发现调试器中能正常执行的 shellcode 在单独运行时发生错误,很可能是因为调试堆和常态堆之间的差异造成的。

为避免程序检测出调试器而使用调试态堆管理策略,如上代码中人工加入了 int 3 调试中断(机器码为 0xCC,又称 CC 中断,这个可以继续挖掘信息来学习),设置 OllyDbg 为系统默认调试器后(Options -> Just-In-Time Debugging -> Make OllyDbg Just-In-Time Debugger),手动执行 release 程序,程序会产生 int 3 中断,系统自动调用 OllyDbg 载入进程进行调试。

如上代码的高度环境为:Win2000 虚拟机、VC++6.0、默认编译选项、release 编译版本。

所有的堆块分配函数都要指明堆区的句柄,然后在堆区内进行堆表的修改等操作并完成分配工作。malloc() 虽然在使用时不用程序员明确指出使用哪个堆区进行分配,这是因为它已经使用 HeapCreate() 函数为自己创建了堆区(可以逆向去看看 malloc 的实现)。

通常一个进程有多个堆块:本例中位于 0x00130000 的堆块是进程堆,可以用 GetProcessHeap() 获取;malloc() 的堆区一般紧接着程序镜像。单击 OllyDbg 的 M 按键可以看到相应的映射关系。

HeapCreate() 创建堆后,会将创建的堆区的地址给 EAX,上述代码示例中创建的堆区在 0x00520000。

从 0x00520000 开始,堆表中包含的信息依次是段表索引(Segment List)、虚表索引(Virtual Allocation List)、空表使用标识(16 字节,freelist usage bitmap)和空表索引区。

从 Matt Conover & Oded Horovitz 在 CanSecWest2004 上发表的《Reliable Windows Heap Exploits》中得知,Freelist Usage Bitmap 是用来快速索引 Freelist 的,一共包括 128 个位而不是原书中说的 32 字节。

偏移 0x178(实际地址 0x00520178)处的空表索引与堆溢出的关系密切,这个也是关心的重点区域。

当一个堆刚被初始化时,情况比较简单:

. 只有一个空闲态的大块:尾块。
. 尾块位于偏移 0x668 处(如果启用了快表,这个位置将会是快表)。
. Freelist[] 指向尾块。
. 除 Freelist[i] (i>) 都指向自己,即为空。

空闲态堆块的数据结构和占用态的基本一致,只是块首后的 8 个字节被用来存放空表指针了。其中前 4 个字节代表 Flink 指向下一个堆块的地址,后 4 个字节代表 Blink 指向前一个堆块的地址。

通过调试可以发现:

. 本例中初始的尾块不是开始于 0x00520668,而是开始于 0x00520660,因为一般引用堆块的指针都会跃过堆块的前 8 字节块首,直接指向堆块的数据区
. 堆块大小的计量单位是 8 字节,本例中尾块的大小显示为 0x130,实际堆块大小为 0x130 * 0x8 = 0x980 字节
. 堆块大小包含块首的 字节。

调试可以看到,堆块分配时,实际分配的堆块大小会在申请的数量上加上块首所需的 8 个字节,并在这个结果上向前取整,使分配的大小为 8 的倍数。初始时只有尾块一个空闲块,这时对申请的堆将使用次优分配。

按照堆表数据结构的规定,这时 快表位于偏移 0x584 的位置(0x00520584),但在以上调试中,快表始终为空。这是因为只有在堆可扩展时,快表才会启用,要想创建可扩展的堆区,要使用 HeapCreate(0,0,0),调试快表的示例代码如下:

 /*****************************************************************************
To be the apostrophe which changed "Impossible" into "I'm possible"! POC code of chapter 6.2 in book "Vulnerability Exploit and Analysis Technique" file name : heap_debug.c
author : failwest
date : 2007.04.04
description : demo show of how heap works
Noticed : 1 only run on windows 2000
2 complied with VC 6.0
3 build into release version
4 only used for run time debugging
version : 1.0
E-mail : failwest@gmail.com Only for educational purposes enjoy the fun from exploiting :)
******************************************************************************/ #include <windows.h>
main()
{
HLOCAL h1,h2,h3,h4;
HANDLE hp;
hp = HeapCreate(,,);
__asm int h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,);
h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,); HeapFree(hp,,h1);
HeapFree(hp,,h2);
HeapFree(hp,,h3);
HeapFree(hp,,h4); h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,);
HeapFree(hp,,h2); return ;
}

这时偏移 0x688 处被快表占用,从偏移 0x178 处的空表也可以看到尾块不在偏移 0x688 处了。

调试可以看到堆块申请、释放的效果与之前表述一致。

对以上两段代码的调试,存在如下疑问:

1. Freelist[1] 指向大小为 1*8=8 字节的堆块,但根据堆块大小的定义,8 字节大小的堆块数据区大小为 0,实际使用中会产生数据区大小为 0 的堆块吗?

2. Freelist 中每个元素比较好理解,为 16 个字节,分别为 Flink 和 Blink(链表中前后节点的指针);但Lookaside(快表)每个元素大小为 48 个字节,除了元素开头的 8 个字节指向链表的下一节点,元素的其他 40 字节的含义还不清楚。

OD: Heap in Windows 2K & XP SP1的更多相关文章

  1. Windows XP SP1 Privilege Escalation

    MS05-018 MS05-018 Works for Windows 2K SP3/4 | Windows XP SP1/2 Download ms05-018.exe: https://githu ...

  2. OD: Heap Exploit : DWORD Shooting & Opcode Injecting

    堆块分配时的任意地址写入攻击原理 堆管理系统的三类操作:分配.释放.合并,归根到底都是对堆块链表的修改.如果能伪造链表结点的指针,那么在链表装卸的过程中就有可能获得读写内存的机会.堆溢出利用的精髓就是 ...

  3. Windows 7 with SP1简体中文旗舰版(微软MSDN原版)+ 激活密钥

    在Windows 7六个版本中,旗舰版和企业版功能性能完全一样,同属诸版本之中的最高版本.现提供Windows 7 with SP1简体中文旗舰版(微软MSDN最新原版)+ 激活密钥如下: 32位版本 ...

  4. Windows 7 With Sp1 简体中文旗舰版

    Windows 7 With Sp1 简体中文旗舰版(MSDN官方原版) 安装Windows 7对于硬件配置的基本要求: •1 GHz 32 位或 64 位处理器 •1 GB 内存(基于32 位)或 ...

  5. 浅议Windows 2000/XP Pagefile组织管理

    任何时候系统内存资源相对磁盘空间来说都是相形见拙的.因为虚拟内存机制,使我们可以有相对丰富的地址资源(通常32bit的虚拟地址,可以有4G的寻址 空间),而这些资源对物理内存来说一般情况是总是绰绰有余 ...

  6. OD: Heap Overflow (XP SP2 - 2003) & DWORD SHOOT via Chunk Resize

    微软在堆中也增加了一些安全校验操作,使得原本是不容易的堆溢出变得困难重重: * PEB Random:在 Windows XP SP2 之后,微软不再使用固定的 PEB 基址 0x7FFDF000,而 ...

  7. 利用ms08_067入侵window xp sp1(English)版本

    前几天上课,老师搬出实验,自己体验了一下 1.环境配置 需要准备kali(攻击机),window xp (我这里是sp1 英文版本,标题很清楚了),攻击机和目标靶机要在同意网段下我的kali(192. ...

  8. Windows 8.1 系统上用Oracle VM VirtualBox 安装windows 2008 R2 SP1 的虚拟机 出现 Error Code: 0x000000C4

    Windows 8.1 本来可以安装Hyper-v来安装虚拟机,但是我现在需要使用Oracle VM VirtualBox来安装虚拟机, 所以必须先卸载Hyper-v VirtualBox 安装的虚拟 ...

  9. PJSIP在windows(xp或者win7)下的编译,编译工具是vs2008,PJSIP版本2.3

    PJSIP是一个开源的SIP协议库,它实现了SIP.SDP.RTP.STUN.TURN和ICE.PJSIP作为基于SIP的一个多媒体通信框架提供了非常清晰的API,以及NAT穿越的功能.PJSIP具有 ...

随机推荐

  1. mysql复制表

    create table 表名 like 要复制的表名  //复制表结构 insert into 表名 select * from 要复制的表名  //复制数据 方法2,一般不推荐,如果后台加数据,可 ...

  2. 分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)

    BOOL型变量:if(!var)    int型变量: if(var==0)    float型变量:    const float EPSINON = 0.00001;    if ((x > ...

  3. Linq延迟执行

    LINQ中大部分查询运算符都有一个非常重要的特性:延迟执行.这意味着,他们不是在查询创建的时候执行,而是在遍历的时候执行(换句话说,当enumerator的MoveNext方法被调用时).让我们考虑下 ...

  4. hive 三种启动方式及用途,关注通过jdbc连接的启动

    http://blog.csdn.net/a221133/article/details/6734746

  5. web前端框架选型

    1.1.界面使用脚本语言有html5,php5,css3,js动态库jquery等 1.2.html采用技术为最新html5技术,html5生成的页面在phone.pad.Phablets.pc也可方 ...

  6. 【转】android小结(一)之menu

    原文网址:http://zhouyunan2010.iteye.com/blog/1151215 android提供有三种menu类型 一.Options Menu(选项菜单) 这是一组item选项的 ...

  7. linux 信号signal和sigaction理解

    今天看到unp时发现之前对signal到理解实在浅显,今天拿来单独学习讨论下. signal,此函数相对简单一些,给定一个信号,给出信号处理函数则可,当然,函数简单,其功能也相对简单许多,简单给出个函 ...

  8. Android调用系统相机和文件浏览器

    //拍照功能,调用系统的相机功能 Intent intent2 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResul ...

  9. npm 模块安装机制简介

    npm 是 Node 的模块管理器,功能极其强大.它是 Node 获得成功的重要原因之一. 正因为有了npm,我们只要一行命令,就能安装别人写好的模块 . $ npm install 本文介绍 npm ...

  10. Xshell远程连接Ubuntu

    在Ubuntu系统下执行命令: sudo apt-get install openssh-server 然后,在Xshell中新建会话,输入Ubuntu系统的IP,以及用户名.密码. 但是经常会出现连 ...