准备刷漏洞战争中的堆溢出部分,但是对于堆的了解较少,在这里记录一下关于堆的学习记录,有错误请各位大大拍砖
参考:
win32堆管理器由NTDLL.dll实现,目的是为用户态的应用程序提供内存服务,从实现角度上来讲,内核态的池管理器和用户态的win32堆管理器默用的是同一套基础代码,它们以运行时的方式存在于ntdll.dll和ntosknrl.exe模块中。
应用程序通过堆管理器提供的接口来分配或者是释放内存,整个堆管理器由堆管理api,前端堆,后端堆组成。在处理内存的分配和释放的时候,前端堆先做处理,前端堆有三种选择:none,预读列表(快表,lookaside-list),LFH(低碎片堆)。
和堆相关的数据结构主要有以下几个:
windows xp下的_HEAP结构
在这个结构体中,FreeLists是一个由128个LIST_ENTRY结构体组成的结构体数组,在调用HeapCreate时,Freelists[0]指向尾块,尾块也就是堆刚创建出时最大的一个空闲块,数组中的其他元素指向自己。
ntdll!KiUserApcDispather->ntdll!_LdrpInitialize->ntdll!LdrplInitializeProcess->ntdll!RtlCreateHeap,创建好的堆的句柄回保存在PEB结构的ProcessHeap字段中,PEB中关于堆的字段如下:
可以通过HeapCreate这个api来创建属于进程的私有堆,这个api实际上回去调用RtlCreateHeap函数,创建完毕之后会将创建好的堆句柄保存到peb结构中。HANDLE WINAPI HeapCreate(DWORD flOptions,SIZE_T dwInitialSize, SIZE_T dwMaximumSize)
这个api中,flOptions下面三个可选项:
在windbg中可以使用!heap -h指令来查看进程中的所有堆。!heap -h的查询结果也就是peb.ProcessHeaps中的值。
HeapDestory->ntdll!RtlDestroyHeap->NtFreeVirtualMemory。
ntdll!RtlDestroyHeap会将PEB堆列表中的堆要销毁堆的堆句柄移除掉,然后通过NtFreeVirtualMemory函数向内存管理器归还内存。
应用程序不应该也不许销毁默认堆,当进程退出和销毁进程对象时,系统都会调用MnCleanProcessSpace函数。
通过HeapAlloc这个api可以从堆中分配空间出来,LPVOID WINAPI HeapAlloc(HANDLE hHeap,DWORD dwFlags,SIZE_T dwBytes)
,dwFlags有下面几个选项:
ps:指定这些值中的任何值将覆盖使用HeapCreate创建堆时指定的对应值 。
也可以使用malloc,calloc或者是new运算符来从堆中分配内存,上面提到CRT堆有三种模式,大多数情况下,运行时库都会选择系统模式,此时CRT堆建立在win32堆之上。
通过HeapFree这个api可以从释放HeapAlloc申请处的内存空间,free或者是delete最终都会跳到这个api上来释放内存。
实验代码:
执行程序之后用windbg附加,然后f5,断到int 3,在windbg中查看空闲表
Heap.FreeLists[0]的flink和blink分别都指向0x003a0688.通过!heap -a 003a0000
查到的结果,我们来看分配到的堆块结构,在windbg中输入
!heap -x 003a0640
或者是dt ntdll!_HEAP_ENTRY
来查看堆结构:
其中_HEAP_ENTRY.Size为堆块的大小/堆块分配粒度,堆块分配粒度一般都为8,可以用!heap -v 堆基址来查看
然后单步执行完 h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,3),观察空闲块的变化
此时Heap.FreeLists[0]的flink和blink分别都指向0x003a0698,在windbg中输入!heap -x 0x003a0698
或者是dt ntdll!_HEAP_FREE_ENTRY 0x003a0698-8
来查看堆结构:
在_HEAP_FREE_ENTRY中多了一个LIST_ENTRY成员,而这个成员的flink和blink都只想freelist[0]。单步执行完HeapFree(hp,0,h1);
在windbg中观察下内存的变化:
可以看到Heap.FreeLists[2]成员的flink和blink分别都指向了0x003a0688,而在此之前,Heap.FreeLists[2]的两个成员都指向自己。单步执行完HeapFree(hp,0,h5)
观察下内存结构
FreeList[0].flink和blink都指向最后一个空闲块003a0708,FreeList[2].flink指向第一次释放的堆块,FreeList[2].blink指向第二次释放的堆块。在windbg中查看下这两个空闲块的结构
观察空闲块和Freelist表结构,我们可以得出结论:每个空闲块的前向指针指向前一个空闲块,第一个空闲块的前向指针指向相应的freelist头,每一个空闲块的后向指针指向下一个空闲块,最后一个空闲块的后向指针指向相应的freelist头,而freelist头的前向指针指向最后一个空闲块,freelist头的后向指针指向第一个空闲块,这样就构成了一个双循环链表。
单步执行完HeapFree(hp,0,h4); //coalese h3,h4,h5,link the large block to freelist[8]
,观察下空闲块的结构:
此时三个空闲块从freelist[2],freelist[4]中被摘下,合并之后链入freelist[8].
快表的结构和空闲表的结构相似,表中元素为单链表表头,组织结构如下:
实验代码:
执行程序之后用windbg附加,f5,断到int3,然后单步到HeapFree(hp,0,h1)
执行结束,此时观察下内存结构:
此时我们观察下空闲块,并没有发现有是释放过的空闲块。在内存中查看下快表:
发现在快表结构中多出了第一次我们申请到的地址0x003a1e90,在windbg中查询结果如下:
和空闲块有所不同的是,堆块的flag为busy。
单步执行完HeapFree(hp,0,h4);free heap4 to lookaside[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
+0x006 UnusedBytes : UChar
+0x007 SegmentIndex : UChar
0:000> dt ntdll!_HEAP_FREE_ENTRY
+0x000 Size : Uint2B
+0x002 PreviousSize : Uint2B
+0x000 SubSegmentCode : Ptr32 Void
+0x004 SmallTagIndex : UChar
+0x005 Flags : UChar
+0x006 UnusedBytes : UChar
+0x007 SegmentIndex : UChar
+0x008 FreeList : _LIST_ENTRY
0:000> dt ntdll!_HEAP 003b0000
+0x000 Entry : _HEAP_ENTRY
+0x008 Signature : 0xeeffeeff ;heap结构签名
+0x00c Flags : 0x50001060 ;堆属性
+0x010 ForceFlags : 0x40000060 ;
+0x014 VirtualMemoryThreshold : 0xfe00;最大堆块大小
+0x018 SegmentReserve : 0x100000
+0x01c SegmentCommit : 0x2000
+0x020 DeCommitFreeBlockThreshold : 0x200
+0x024 DeCommitTotalFreeThreshold : 0x2000
+0x028 TotalFreeSize : 0x130;空闲块总大小,以粒度为单位
+0x02c MaximumAllocationSize : 0x7ffdefff;堆可分配的最大内存
+0x030 ProcessHeapsListIndex : 6;堆在peb.processheaps数组中的索引+1
+0x032 HeaderValidateLength : 0x608
+0x034 HeaderValidateCopy : (null)
+0x038 NextAvailableTagIndex : 0
+0x03a MaximumTagIndex : 0
+0x03c TagEntries : (null)
+0x040 UCRSegments : (null)
+0x044 UnusedUnCommittedRanges : 0x003b0598 _HEAP_UNCOMMMTTED_RANGE
+0x048 AlignRound : 0x17
+0x04c AlignMask : 0xfffffff8
+0x050 VirtualAllocdBlocks : _LIST_ENTRY [ 0x3b0050 - 0x3b0050 ]
+0x058 Segments : [64] 0x003b0640 _HEAP_SEGMENT
+0x158 u : __unnamed
+0x168 u2 : __unnamed
+0x16a AllocatorBackTraceIndex : 0
+0x16c NonDedicatedListLength : 1
+0x170 LargeBlocksIndex : (null)
+0x174 PseudoTagEntries : (null)
+0x178 FreeLists : [128] _LIST_ENTRY [ 0x3b0688 - 0x3b0688 ];空闲块
+0x578 LockVariable : 0x003b0608 _HEAP_LOCK
+0x57c CommitRoutine : (null)
+0x580 FrontEndHeap : (null)
+0x584 FrontHeapLockCount : 0
+0x586 FrontEndHeapType : 0 ''
+0x587 LastSegmentIndex : 0 ''
0:000> dt ntdll!_PEB
+0x018 ProcessHeap : Ptr32 Void //堆句柄
+0x078 HeapSegmentReserve : Uint4B //默认堆的保留大小:1MB
+0x07c HeapSegmentCommit : Uint4B //默认堆的提交大小:8kb
+0x088 NumberOfHeaps : Uint4B //进程中堆的总数
+0x08c MaximumNumberOfHeaps : Uint4B //ProcessHeaps数组中数组元素的个数
+0x090 ProcessHeaps : Ptr32 Ptr32 Void//这个数组用来保存进程中创建的堆的句柄
#include <stdio.h>
#include <windows.h>
main()
{
HLOCAL h1,h2,h3,h4,h5,h6;
HANDLE hp;
hp = HeapCreate(0,0x1000,0x10000);
getchar();
__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);
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;
}
0:000>dd 0x003a0000+0x178
003a0178 003a0688 003a0688 003a0180 003a0180
003a0188 003a0188 003a0188 003a0190 003a0190
003a0198 003a0198 003a0198 003a01a0 003a01a0
003a01a8 003a01a8 003a01a8 003a01b0 003a01b0
0:000> !heap -x 003a0640
Entry User Heap Segment Size PrevSize Unused Flags
-----------------------------------------------------------------------------
003a0640 003a0648 003a0000 003a0640 40 640 0 busy
0:000> dt ntdll!_HEAP_ENTRY 003a0640
+0x000 Size : 8
+0x002 PreviousSize : 0xc8
+0x000 SubSegmentCode : 0x00c80008
+0x004 SmallTagIndex : 0 ''
+0x005 Flags : 0x1 ''
+0x006 UnusedBytes : 0 ''
+0x007 SegmentIndex : 0 '
0:000> dd 0x003a0000+0x178
003a0178 003a0698 003a0698 003a0180 003a0180
003a0188 003a0188 003a0188 003a0190 003a0190
003a0198 003a0198 003a0198 003a01a0 003a01a0
003a01a8 003a01a8 003a01a8 003a01b0 003a01b0
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课