OD: Heap Overflow

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
需要对堆中的重要数据结构掌握到字节级别。下面是一段调试堆的程序:

 1 /*****************************************************************************
2 To be the apostrophe which changed "Impossible" into "I‘m possible"!
3
4 POC code of chapter 6.2 in book "Vulnerability Exploit and Analysis Technique"
5
6 file name : heap_debug.c
7 author : failwest
8 date : 2007.04.04
9 description : demo show of how heap works
10 Noticed : 1 only run on windows 2000
11 2 complied with VC 6.0
12 3 build into release version
13 4 only used for run time debugging
14 version : 1.0
15 E-mail : [email protected]
16
17 Only for educational purposes enjoy the fun from exploiting :)
18 ******************************************************************************/
19
20 #include <windows.h>
21 main()
22 {
23 HLOCAL h1,h2,h3,h4,h5,h6;
24 HANDLE hp;
25 hp = HeapCreate(0,0x1000,0x10000);
26 __asm int 3
27
28 h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,3);
29 h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,5);
30 h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,6);
31 h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
32 h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,19);
33 h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);
34
35 //free block and prevent coaleses
36 HeapFree(hp,0,h1); //free to freelist[2]
37 HeapFree(hp,0,h3); //free to freelist[2]
38 HeapFree(hp,0,h5); //free to freelist[4]
39
40 HeapFree(hp,0,h4); //coalese h3,h4,h5,link the large block to freelist[8]
41
42
43 return 0;
44 }

其中 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)处的空表索引与堆溢出的关系密切,这个也是关心的重点区域。

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

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

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

通过调试可以发现:

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

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

按照堆表数据结构的规定,这时 快表位于偏移
0x584

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

 1 /*****************************************************************************
2 To be the apostrophe which changed "Impossible" into "I‘m possible"!
3
4 POC code of chapter 6.2 in book "Vulnerability Exploit and Analysis Technique"
5
6 file name : heap_debug.c
7 author : failwest
8 date : 2007.04.04
9 description : demo show of how heap works
10 Noticed : 1 only run on windows 2000
11 2 complied with VC 6.0
12 3 build into release version
13 4 only used for run time debugging
14 version : 1.0
15 E-mail : [email protected]
16
17 Only for educational purposes enjoy the fun from exploiting :)
18 ******************************************************************************/
19
20 #include <windows.h>
21 main()
22 {
23 HLOCAL h1,h2,h3,h4;
24 HANDLE hp;
25 hp = HeapCreate(0,0,0);
26 __asm int 3
27
28 h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
29 h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
30 h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
31 h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);
32
33 HeapFree(hp,0,h1);
34 HeapFree(hp,0,h2);
35 HeapFree(hp,0,h3);
36 HeapFree(hp,0,h4);
37
38 h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
39 HeapFree(hp,0,h2);
40
41 return 0;
42 }

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

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

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

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

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

OD: Heap Overflow,布布扣,bubuko.com

时间: 2024-10-18 15:48:23

OD: Heap Overflow的相关文章

OD: Heap Exploit : DWORD Shooting &amp; Opcode Injecting

堆块分配时的任意地址写入攻击原理 堆管理系统的三类操作:分配.释放.合并,归根到底都是对堆块链表的修改.如果能伪造链表结点的指针,那么在链表装卸的过程中就有可能获得读写内存的机会.堆溢出利用的精髓就是用精心构造的数据去溢出下一个堆块的块首,改写块首中的前向指针 Flink 和后向指针 Blink,然后在分配.释放.合并操作发生时获得一次读写内存的机会. 这种利用内存读写机会在任意位置写入任意数据的做法在原书中称为"DWORD SHOT",在其它文献中叫做"Arbitrary

CVE-2010-2746分析 Microsoft Windows - Common Control Library (Comctl32) Heap Overflow (MS10-081)

相关链接: exploit-db:https://www.exploit-db.com/exploits/15963/ 微软安全公告:https://technet.microsoft.com/library/security/ms10-081 漏洞介绍: 根据微软官方说明,漏洞的原因是 在使用第三方可伸缩矢量图形 (SVG) 查看器时,Windows 公共控件库Comctl32.dll 没有正确处理某些消息存在一个远程执行代码漏洞,且说明了是一个堆溢出漏洞. 环境介绍: (1) XP Prof

【Valgrind】How to check buffer overflow/underflow in 10 mins

Introduction Buffer overflow/underflow frequently happens when we did something wrong with the array index, no matter the array is heap or stack, no matter you are reading the memory or writing the memory. Example 1: heap overflow // head_overflow.c

Java第三天听课视频

逻辑运算为何不存在短路的情况 //判断结构 if(exp){ } 2. if(exp1){ }else{ } 函数就是定义在类中的具有特定功能一段 独立 小程序 函数也叫做方法 Modifier:修改符 public static void 数组: ------------------------- 1.同一类型数据的数据的集合,下标从0开始 2通过下标,以0为基址 3.定义数组,类型[] 名称=new 类型[数字]; 类型[] 名称=new 类型{} 4.数组在内存当中是连续的,在内存当中开一

Microsoft Windows 2003 SP2 - &#39;ERRATICGOPHER&#39; SMB Remote Code Execution

EDB-ID: 41929 Author: vportal Published: 2017-04-25 CVE: N/A Type: Remote Platform: Windows Aliases: ERRATICGOPHER Advisory/Source: N/A Tags: N/A E-DB Verified:  Exploit:  Download/ View Raw Vulnerable App: N/A #!/usr/bin/env python # -*- coding: utf

linux(x86) exploit 开发系列2:整数溢出

What is Integer Overflow? Storing a value greater than maximum supported value is called integer overflow. Integer overflow on its own doesnt lead to arbitrary code execution, but an integer overflow might lead to stack overflow or heap overflow whic

[转]Even when one byte matters

Source:http://kernelbof.blogspot.jp/2009/07/even-when-one-byte-matters.html Common Vulnerabilities and Exposures http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-1046 "The console selection feature in the Linux kernel 2.6.28 before 2.6.28.4, 2.

free pascal 错误代码表

free pascal 错误代码表为了方便对照检查运行时错误代码,这里把所有的错误代码的含义整理出来.(最大序号为232,中间不一定连续.user.pdf P175)Run-time errors Applications generated by Free Pascal might generate run-time errors when certain abnormal conditions are detected in the application. This appendix li

Microsoft Edge 浏览器远程代码执行漏洞POC及细节(CVE-2017-8641)

2017年8月8日,CVE官网公布了CVE-2017-8641,在其网上的描述为: 意思是说,黑客可以通过在网页中嵌入恶意构造的javascript代码,使得微软的浏览器(如Edege),在打开这个网页时,造成堆溢出.通过精心构造javascript代码,可以通过浏览器在用户电脑上执行任意代码.受影响的版本包括下列操作系统中的浏览器(IE(9,10,11)和Edge): 1. Windows 7 SP1 2. Windows Server 2008 R2 SP1 3. Windows 8.1 4