首页
社区
课程
招聘
[原创]堆学习记录
发表于: 2018-1-17 19:25 6209

[原创]堆学习记录

2018-1-17 19:25
6209

准备刷漏洞战争中的堆溢出部分,但是对于堆的了解较少,在这里记录一下关于堆的学习记录,有错误请各位大大拍砖

参考:

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直播授课

收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 1233
活跃值: (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
谢谢分享!
2018-2-19 21:07
0
游客
登录 | 注册 方可回帖
返回
//