基本上是照搬AngelBoy大师傅的slides ,纯新手用的Windows heap笔记…
Windows Heap 101
windows heap结构
堆的种类
Defautl Heap:默认堆,当使用Windows api直接申请的时候使用的就是这个堆
存放在_PEB
中
CRT heap:使用C头分配的堆,本质上还是defaut,不过有自己封装了一层别的内容
存放在crt_heap
中
win10之后堆分为两种:Segment heap
和NT heap
。当一个进程分配堆的时候,大部分场合默认使用的堆都是后面那种,前面的segment heap
通常会在winapp或者某些特殊的进程(核心进程)中会使用到。(也可以控制注册表打开HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\
)
这两种堆称为前端堆(Frontend Heap)和 后端堆(Backend Heap)
官方文档中对于堆的介绍
最近查看MSDN的时候,发现官方也有了对于堆的介绍了!虽然可能和实际有点出入,不过好歹也是官方的,这里记录一下。
Light page heap block(alloc)
1 2 3 4 5 6 7 +-----+---------------+---+ | | | | +-----+---------------+---+ ^ ^ ^ | | 8 suffix bytes (filled with 0xA0) | User allocation (filled with E0 if zeroing not requested) Block header (starts with 0xABCDAAAA and ends with 0xDCBAAAAA)
Light page heap block(free)
1 2 3 4 5 6 7 +-----+---------------+---+ | | | | +-----+---------------+---+ ^ ^ ^ | | 8 suffix bytes (filled with 0xA0) | User allocation (filled with F0 bytes) Block header (starts with 0xABCDAAA9 and ends with 0xDCBAAA9)
Full page heap block – allocated:
1 2 3 4 5 6 7 8 +-----+---------+---+------- | | | | ... N/A page +-----+---------+---+------- ^ ^ ^ | | 0-7 suffix bytes (filled with 0xD0) | User allocation (if zeroing not requested, filled with C0) Block header (starts with 0xABCDBBBB and ends with 0xDCBABBBB)
Full page heap block – freed:
1 2 3 4 5 6 7 +-----+---------+---+------- | | | | ... N/A page +-----+---------+---+------- ^ ^ ^ | | 0-7 suffix bytes (filled with 0xD0) | User allocation (filled with F0 bytes) Block header (starts with 0xABCDBBA and ends with 0xDCBABBBA)
核心结构体
HEAP
管理堆最主要的结构体,在每一个被分配的堆的最前面。(就好像arena
一类的结构体)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 2:007> dt ntdll!_HEAP +0x000 Segment : _HEAP_SEGMENT +0x000 Entry : _HEAP_ENTRY +0x008 SegmentSignature : Uint4B +0x00c SegmentFlags : Uint4B +0x010 SegmentListEntry : _LIST_ENTRY +0x018 Heap : Ptr32 _HEAP +0x01c BaseAddress : Ptr32 Void +0x020 NumberOfPages : Uint4B +0x024 FirstEntry : Ptr32 _HEAP_ENTRY +0x028 LastValidEntry : Ptr32 _HEAP_ENTRY +0x02c NumberOfUnCommittedPages : Uint4B +0x030 NumberOfUnCommittedRanges : Uint4B +0x034 SegmentAllocatorBackTraceIndex : Uint2B +0x036 Reserved : Uint2B +0x038 UCRSegmentList : _LIST_ENTRY +0x040 Flags : Uint4B +0x044 ForceFlags : Uint4B +0x048 CompatibilityFlags : Uint4B +0x04c EncodeFlagMask : Uint4B +0x050 Encoding : _HEAP_ENTRY +0x058 Interceptor : Uint4B +0x05c VirtualMemoryThreshold : Uint4B +0x060 Signature : Uint4B +0x064 SegmentReserve : Uint4B +0x068 SegmentCommit : Uint4B +0x06c DeCommitFreeBlockThreshold : Uint4B +0x070 DeCommitTotalFreeThreshold : Uint4B +0x074 TotalFreeSize : Uint4B +0x078 MaximumAllocationSize : Uint4B +0x07c ProcessHeapsListIndex : Uint2B +0x07e HeaderValidateLength : Uint2B +0x080 HeaderValidateCopy : Ptr32 Void +0x084 NextAvailableTagIndex : Uint2B +0x086 MaximumTagIndex : Uint2B +0x088 TagEntries : Ptr32 _HEAP_TAG_ENTRY +0x08c UCRList : _LIST_ENTRY +0x094 AlignRound : Uint4B +0x098 AlignMask : Uint4B +0x09c VirtualAllocdBlocks : _LIST_ENTRY +0x0a4 SegmentList : _LIST_ENTRY +0x0ac AllocatorBackTraceIndex : Uint2B +0x0b0 NonDedicatedListLength : Uint4B +0x0b4 BlocksIndex : Ptr32 Void +0x0b8 UCRIndex : Ptr32 Void +0x0bc PseudoTagEntries : Ptr32 _HEAP_PSEUDO_TAG_ENTRY +0x0c0 FreeLists : _LIST_ENTRY +0x0c8 LockVariable : Ptr32 _HEAP_LOCK +0x0cc CommitRoutine : Ptr32 long +0x0d0 StackTraceInitVar : _RTL_RUN_ONCE +0x0d4 CommitLimitData : _RTL_HEAP_MEMORY_LIMIT_DATA +0x0e4 FrontEndHeap : Ptr32 Void +0x0e8 FrontHeapLockCount : Uint2B +0x0ea FrontEndHeapType : UChar +0x0eb RequestedFrontEndHeapType : UChar +0x0ec FrontEndHeapUsageData : Ptr32 Wchar +0x0f0 FrontEndHeapMaximumIndex : Uint2B +0x0f2 FrontEndHeapStatusBitmap : [257] UChar +0x1f4 Counters : _HEAP_COUNTERS +0x250 TuningParameters : _HEAP_TUNING_PARAMETERS
这里注我们关注如下的结构体对象
EncodeFlagMask:当在堆被初始化之后,会被设置为0x100000,这个标志位能够决定当前的堆头部是否会被加密
Encoding:这个值用来和chunk header进行亦或,从而得到真实的chunk header
BlocksIndex:这个值用来管理后端堆,其本质为_HEAP_LIST_LOOKUP_
FreeList:用于搜集所有的被释放后的堆块,这个链表和unsorted bin
有点像,是一个排列过的链表。其本质为FreeList
FrontEndHeap: 指向前端堆结构体的指针
FrontEndHeapUsageData: 记录不同大小的堆的使用情况,当到达一定水平的时候,堆将会使用前端堆而不是后端堆。
_HEAP_ENTRY
每一个堆的最基本的堆结构体
1 2 3 4 5 6 7 +-------------------------+ | PreviousBlockPrivateData| +-------------------------+ | chunk header | +-------------------------+ | data | +-------------------------+
chunk 为一个最基本的单位,在win10里面也是。
chunk分为三种不同的堆:
Allocated chunk
Free chunk
VirtualAlloc chunk(没有PreviousBlockPrivateData)
三者的结构体有一点不同,我们先介绍第一种。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 2:007> dt ntdll!_HEAP_ENTRY +0x000 UnpackedEntry : _HEAP_UNPACKED_ENTRY +0x000 Size : Uint2B +0x002 Flags : UChar +0x003 SmallTagIndex : UChar +0x000 SubSegmentCode : Uint4B +0x004 PreviousSize : Uint2B +0x006 SegmentOffset : UChar +0x006 LFHFlags : UChar +0x007 UnusedBytes : UChar +0x000 ExtendedEntry : _HEAP_EXTENDED_ENTRY +0x000 FunctionIndex : Uint2B +0x002 ContextValue : Uint2B +0x000 InterceptorValue : Uint4B +0x004 UnusedBytesLength : Uint2B +0x006 EntryOffset : UChar +0x007 ExtendedBlockSignature : UChar +0x000 Code1 : Uint4B +0x004 Code2 : Uint2B +0x006 Code3 : UChar +0x007 Code4 : UChar +0x004 Code234 : Uint4B +0x000 AgregateCode : Uint8B
这里我们关注如下的结构体:
PreviousBlockPrivateData(8 byte): 结构体中未显示,是上一个堆块的数据。
Size: 当前堆块的大小(向右移了3个字节)
Flag:当前的堆块是否busy(被使用)
SmallTagIndex: 和开头的三个字节异或,作为checknumber,用于verify当前的chunk
PreviousSize:上一个堆块的大小,这个值同样也是右移了三个字节
UnusedBytes:当分配了当前chunk之后,当前堆还剩余的大小,这个值通常用来检查当前chunk的状态
从偏移量+0x8
开始就是userdata
如果是free的堆块的话,则可能还有如下两个字段:
1 2 +0x008 FLink +0x00c Blink
Flink指向下一个在链表中的堆,Blink指向前一个在链表中的堆。此时的UnusedBytes
必须要为0
如果是VirualAlloc分配的堆的话,它的结构如下:
1 2 3 4 5 6 7 8 2:007> dt ntdll!_HEAP_VIRTUAL_ALLOC_ENTRY +0x000 Entry : _LIST_ENTRY +0x000 Flink : Ptr32 _LIST_ENTRY +0x004 Blink : Ptr32 _LIST_ENTRY +0x008 ExtraStuff : _HEAP_ENTRY_EXTRA +0x010 CommitSize : Uint4B +0x014 ReserveSize : Uint4B +0x018 BusyBlock : _HEAP_ENTRY
这种heap分配的堆在使用中就会用链表记录分配的堆情况。同时,在这个结构体中记录的size将会是未发生过位移的大小 。并且这里的UnusedByte
必须要为4
FreeList
当存在被释放的堆块的时候,内存中形式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 HEAP 0x80 +----------------+ | Encoding | +----------------+ ....... +----------------+ 0x150 | Previous Data | +----------------+ Not really chunk +----------------+ | FreeList | ------> +----------------+ | chunk header | +----------------+ | Flink | ----->+----------------+ +----------------+ | Flink |-----+ | Blink | +----------------+ | +----------------+ | Blink | | ^ | +----------------+ | | | | | | +----------------+ | | | | Previous Data | | | | +----------------+ | | | | chunk header | | | +---------->+----------------+<----+ | | Flink |-----+ | +----------------+ | | | Blink | | | +----------------+ | +---------------------------------------+ ``` PS: _Blink的线会和Flink交错,就不画出来了,方向和上一个chunk的顺序是反过来的_ 我们这边用一个简单的代码来说明一下这个过程: ```cpp HANDLE hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0x2000, 0x2000); PVOID heap[5]; heap[0] = HeapAlloc(hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, 0x80); heap[1] = HeapAlloc(hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, 0x80); heap[2] = HeapAlloc(hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, 0x80); heap[3] = HeapAlloc(hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, 0x80); for (int i = 0; i < 4; i++) { printf("%d: [0x%x]\n", i, heap[i]); } // now will free heap HeapFree(hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, heap[0]); // we don't know if it will merge... just reserve 1 HeapFree(hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, heap[2]);
这边打印的堆地址如下:
1 2 3 4 0: [0x18f0498] 1: [0x18f0530] 2: [0x18f05c8] 3: [0x18f0660]
代码后面,heap[0],heap[2]
都被我们释放了,所以应该可以形成如上的链表。如果我们用windbg,从内存中去看的_HEAP_ENTRY
的话,可能会有一点奇怪的感觉:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 0:001> dt _HEAP_FREE_ENTRY 0x18f0490 ntdll!_HEAP_FREE_ENTRY +0x000 HeapEntry : _HEAP_ENTRY +0x000 UnpackedEntry : _HEAP_UNPACKED_ENTRY +0x000 Size : 0xe91 +0x002 Flags : 0xf2 '' +0x003 SmallTagIndex : 0x79 'y' +0x000 SubSegmentCode : 0x79f20e91 +0x004 PreviousSize : 0xef60 +0x006 SegmentOffset : 0 '' +0x006 LFHFlags : 0 '' +0x007 UnusedBytes : 0 '' +0x000 ExtendedEntry : _HEAP_EXTENDED_ENTRY +0x000 FunctionIndex : 0xe91 +0x002 ContextValue : 0x79f2 +0x000 InterceptorValue : 0x79f20e91 +0x004 UnusedBytesLength : 0xef60 +0x006 EntryOffset : 0 '' +0x007 ExtendedBlockSignature : 0 '' +0x000 Code1 : 0x79f20e91 +0x004 Code2 : 0xef60 +0x006 Code3 : 0 '' +0x007 Code4 : 0 '' +0x004 Code234 : 0xef60 +0x000 AgregateCode : 0x0000ef60`79f20e91 +0x008 FreeList : _LIST_ENTRY [ 0x18f06f8 - 0x18f05c8 ]
可以看到,FreeList
似乎是正确的,我们追踪一下内存会发现确实没有问题:
1 2 3 4 5 6 0:001> dd 0x18f0498 <----- heap[0] 018f0498 018f06f8 018f05c8 feeefeee feeefeee 0:001> dd 0x18f05c8 <----- heap[2] 018f05c8 018f0498 018f00c0 feeefeee feeefeee 0:001> dd 0x18f00c0 <----- FreeList empty block 018f00c0 018f05c8 018f06f8 00000000 57da77c4
但是这个_HEAP_ENTRY
好像有点怪怪的,因为我们申请的堆块大小似乎不是0xe91
,而是0x80
。这是因为这个时候的chunk header
被_HEAP.Encoding
加密过。为了得到真正的header,我们需要将这个值修改一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 6ef60e82为Encoding中的值 0:001> ?6ef60e82^79f20e91 Evaluate expression: 386138131 = 17040013 0:001> ?0000ef60^6ef60e82 Evaluate expression: 1861673442 = 6ef6e1e2 0:001> ed 018f0490 17040013 0:001> ed 018f0494 6ef6e1e2 0:001> dt _HEAP_ENTRY 0x18f0490 ntdll!_HEAP_ENTRY +0x000 UnpackedEntry : _HEAP_UNPACKED_ENTRY +0x000 Size : 0x13 +0x002 Flags : 0x4 '' +0x003 SmallTagIndex : 0x17 '' +0x000 SubSegmentCode : 0x17040013 +0x004 PreviousSize : 0xe1e2 +0x006 SegmentOffset : 0xf6 '' +0x006 LFHFlags : 0xf6 '' +0x007 UnusedBytes : 0x6e 'n' +0x000 ExtendedEntry : _HEAP_EXTENDED_ENTRY +0x000 FunctionIndex : 0x13 +0x002 ContextValue : 0x1704 +0x000 InterceptorValue : 0x17040013 +0x004 UnusedBytesLength : 0xe1e2 +0x006 EntryOffset : 0xf6 '' +0x007 ExtendedBlockSignature : 0x6e 'n' +0x000 Code1 : 0x17040013 +0x004 Code2 : 0xe1e2 +0x006 Code3 : 0xf6 '' +0x007 Code4 : 0x6e 'n' +0x004 Code234 : 0x6ef6e1e2 +0x000 AgregateCode : 0x6ef6e1e2`17040013
这次看过来,首先我们知道开头的三个字节是需要过checknum的,我们确认一下0x13^0x00^0x4 -> 0x17
,也就是说SmallTagIndex
的验证是能通过的,说明这个就是我们此时的chunk header
。然后我们计算一下我们这个chunk的size,为0x13*8->0x98
有点奇怪?我们申请的应该是0x80才对呀,不过看到分配的地址:
1 2 3 0: [0x18f0498] 1: [0x18f0530] 0x18f0498+0x98=0x18f0530
说明大小是没错的,这个大小和我们申请有出入其实是因为我们申请的大小正好导致堆没有16字节地址对齐,同时没有把chunk header以及 PreviuseBlockPrivateData计算进去导致的 。sizeof(chunkheader)=8, previouse data=8
,如果按照(0x90)申请的话,chunk地址空间正好为0x18f0520
使用插件指令:
1 2 3 4 5 !heap -a <-- 列出所有的堆 或者 !haep -i heap <--- 指定堆 然后 !heap -i heap entry address <---指定堆地址
可以直接看到解密之后的堆信息,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 0:004> !heap -a 0:004> !heap -i 01690528 <--- 真实分配的地址-sizeof(chunkheader) Detailed information for block entry 01690528 Assumed heap : 0x01690000 (Use !heap -i NewHeapHandle to change) Header content : 0xDC7A7744 0x18004F36 (decoded : 0x14070013 0x18000013) Owning segment : 0x01690000 (offset 0) Block flags : 0x7 (busy extra fill ) Total block size : 0x13 units (0x98 bytes) Requested size : 0x80 bytes (unused 0x18 bytes) Previous block size: 0x13 units (0x98 bytes) Block CRC : OK - 0x14 Previous block : 0x01690490 Next block : 0x016905c0
BlocksIndex(_HEAP_LIST_LOOKUP)
用于管理不同大小的被free之后的堆块,这类堆块就和fastbin
类似,不会被merge(?)
1 2 3 4 5 6 7 8 9 10 11 0:004> dt _HEAP_LIST_LOOKUP ntdll!_HEAP_LIST_LOOKUP +0x000 ExtendedLookup : Ptr32 _HEAP_LIST_LOOKUP +0x004 ArraySize : Uint4B +0x008 ExtraItem : Uint4B +0x00c ItemCount : Uint4B +0x010 OutOfRangeItems : Uint4B +0x014 BaseIndex : Uint4B +0x018 ListHead : Ptr32 _LIST_ENTRY +0x01c ListsInUseUlong : Ptr32 Uint4B +0x020 ListHints : Ptr32 Ptr32 _LIST_ENTRY
ExtendedLookup: 指向下一个ExtendedLookup
,下一个BlocksIndex
将会管理更加大的chunks
ArraySize: 当前BlocksIndex
将会管理的最大的chunk的大小
第一个BlocksIndex
的ArraySize
大小为0x80(实际上为0x800)
ItemCount: 当前大小的chunks的数量
OutofRangeItems: 超过了当前BlocksIndex管理的chunks大小的chunk的数量
BeseIndex: 在Blocksindex
中起始的chunk的index。
通常可以用来在ListHint
中找到合适的空闲堆块
下一个BlocksIndex
中的BaseIndex
为当前BaseIndex
中的最大值
ListHead(_HEAP_ENTRY
): FreeList的Head
ListsInUseUlong: 是一个bitmap,用来确定当前ListHint中是否有合适的chunk
ListHint: 是一个指针数组,其中每一个指针将会指向对应大小的chunk 数组
这个结构体能够帮助更快的找到对应的chunk
chunk的间隔为0x10,即是chunk大小为0x20, 0x30 …
实例:BlocksIndex的使用
我们将代码修改一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 heap[0 ] = HeapAlloc (hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, 0x80 ); heap[1 ] = HeapAlloc (hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, 0x1 ); heap[2 ] = HeapAlloc (hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, 0x80 ); heap[3 ] = HeapAlloc (hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, 0x90 ); for (int i = 0 ; i < 4 ; i++) { printf ("%d: [0x%x]\n" , i, heap[i]); } puts ("check the alloc heap~" );getchar ();HeapFree (hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, heap[0 ]);HeapFree (hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, heap[2 ]);puts ("ok please debug it~" );getchar ();
打印堆内容如下:
1 2 3 4 0: [0x1110498] 1: [0x1110530] 2: [0x1110550] 3: [0x11105e8]
此时的场景为:
有两个堆被free了,大小一致
两个堆大小均为 0x80
heap[0]
早于heap[2]
被free
知道了上述三个条件之后,我们来看一下内存布局,大致如下:
BlocksIndex中的ListHead中存放了指向_HEAP
中FreeList(_HEAP
+0xc0)的指针
ListHint[0x13]
中存放的正好是指向大小为0x13*8=0x98
的chunk的地址
ListHint中记录的是后面释放的那个堆 ,也就是说堆块相当于是队列的形式进入FreeList中的
ListsUlong 的值为0x80000,如果从0开始数的话,正好是下标为0x13的地方为1,象征着ListHint[13]
不为空
内存分配
堆的分配大致可以分为三种不同的分配方式:
size<=0x4000
0x4000<size<=0xff000
size>0xff000
分配内存主要由APIRtlAllocateHeap
实现,我们分别介绍三种不同的堆块的分配方式
Size <= 0x4000
首先需要check FrontEndHeapStatusBitmap对应大小的标志位是否有设置(如果设置了,那么分配的堆块将会是LFH的)
如果没有设置的话,在FrontEndHeapUsageData
上加上0x21
2.1 检查当前的值是否超过了0xff00 ,或者&0x1f之后,是否大于0x10
2.2 如果2.1成立,那么此时还是使用LFH
检查ListHint 对应的大小是否有值。如果为证,那么将会把ListHint中对应的第一个chunk取出来
3.1 如果此时ListHint中大小正好合适,那么直接从ListHint中移除,然后确认chunk中Flink中是否为大小chunk
3.2 3.1为真的时候,将ListHint中的值替换成Flink的值
3.3 否则,清除ListHint
3.4 最后,从chunk的链表中unlink这个chunk
3.5 如果3.1 找不到合适大小的chunk,那么首先在ListHint中寻找比当前size更到的chunk是否有合适的大小
3.6 如果3.5找到了,那么将那个chunk从ListHint中移除,并且将chunk切分,并且将剩下的chunk重新插入到FreeList中,并且把其放入ListHint
3.7 如果3.5找不到,那么就调用ExtendHeap,扩展堆大小,然后从中挑选和是的堆大小。
0x4000 < size <= 0xff000
除了LFH相关的操作,其他的操作和]Size <= 0x4000
堆分配大小一样。
Size > 0xff000 (VirtualMemoryThredshold << 4)
使用ZwAllocateVirtualMenmory
来直接分配堆
与mmap相似,也是分配一个大的内存堆块,然后将其插入到_HEAP->VirtualAllocdBlocks
_HEAP->VirtualAllocdBlocks
是一个链表,用来管理后端堆使用VirualAllocate
分配的堆块
内存释放
内存释放的话,主要分为两种情况
Size <= 0xff000
Sizeo > 0xff000
Size <= 0xff000
首先检查当前堆块的对齐情况,并且使用unused byte来确认当前堆块的状态
1.1 如果LFH是disable的状态,那么将FrontEndHeapUsageData-1
1.2 之后check上一个chunk或者下一个chunk(内存相邻的)的堆块是否是freed的状态,如果是的话,合并这些堆块
1.3 如果1.2 发生了合并,那么unlink这些chunk,并且从ListHint中移除
1.4 如果1.2 发生了合并,那么更新当前的size和presize,并且检查这个chunk是否是FreeList的第一个/最后一个chunk。如果是的话,重新插入到FreeList中,否则的话插入ListHint,并且将其更新。
1.5 当发生插入的时候,将会检查链表的完整性(flink->blink = blink->flink),但是这个check并不会终止进程
Size > 0xff000
检查链表完整性,并且从_HEAP->VirtualAllcdBlocks
中取出来
然后使用RltSecMemFreeVirtualMemory
来取消对这段内存的映射
Unlink
基本上和linux下的unlink是一样的,可以模仿这里
最重要的是要模仿free过程中的check过程,但是Windows比较麻烦的是要能够保证checksum那个check要能够通过,所以最好还是有一个任意地址写比较方便。其次,与linux上的unlink区别是,windows计算freelist的时候,是不会将heap的头部计算进去的 ,所以不能无脑套公式,区别如下:
1 2 3 4 5 6 7 // Linux fd = &block-0xc bk = &block-0x8 // Windows fd = &block-0x4 bk = &block
Leak
Windows heap pwn的话,主要考虑leak如下的内容
kernelbase
KERNELBASE!BasepFilterInfo
stack address
ntdll.dll
这个模块下主要要泄露的内容有
_HEAP_LOCK
_HEAP->LockVariable.Lock
CriticalSection->DebugInfo
指向ntdll的指针
PebLdr
_PEB_LDR_DATA
找到dll的所有位置
然而,缺点是最后的两个字节通常是0
可以线找到binary base地址,然后从IAT中找到kernel32
BinaryBase
Kernel32
这是一个很重要的dll,在这里能够找到很多有用的函数,并且能够从IAT中找到kernelbase.dll的地址
kernelbase
踩坑
最近windbg下载符号的时候好像是被墙了,这里需要在系统环境变量中添加_NT_SYMBOL_PROXY
:本地代理地址,然后重启windbg即可下载符号。