WindowsHeap101

基本上是照搬AngelBoy大师傅的slides,纯新手用的Windows heap笔记…


Windows Heap 101

windows heap结构

堆的种类

  • Defautl Heap:默认堆,当使用Windows api直接申请的时候使用的就是这个堆
  • 存放在_PEB
  • CRT heap:使用C头分配的堆,本质上还是defaut,不过有自己封装了一层别的内容
  • 存放在crt_heap

win10之后堆分为两种:Segment heapNT 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的大小
    • 第一个BlocksIndexArraySize大小为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();
// 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]);
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

  1. 首先需要check FrontEndHeapStatusBitmap对应大小的标志位是否有设置(如果设置了,那么分配的堆块将会是LFH的)
  2. 如果没有设置的话,在FrontEndHeapUsageData上加上0x21
    2.1 检查当前的值是否超过了0xff00 ,或者&0x1f之后,是否大于0x10
    2.2 如果2.1成立,那么此时还是使用LFH
  3. 检查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

  1. 首先检查当前堆块的对齐情况,并且使用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

  1. 检查链表完整性,并且从_HEAP->VirtualAllcdBlocks中取出来
  2. 然后使用RltSecMemFreeVirtualMemory来取消对这段内存的映射
  1. 基本上和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

    • 可以从IAT中找到kernel32的地址

Kernel32

这是一个很重要的dll,在这里能够找到很多有用的函数,并且能够从IAT中找到kernelbase.dll的地址

kernelbase

  • Kernelbase!BasepFilterInfo

    • 指向堆的指针
    • 能够大概率的在这个结构体中找到栈指针
  • 如果BasepFilterInfo找不到stack的地址的话,那么可以从TEB找到地址。这个TEB地址通常会PEB在同一个页面中。

踩坑

最近windbg下载符号的时候好像是被墙了,这里需要在系统环境变量中添加_NT_SYMBOL_PROXY:本地代理地址,然后重启windbg即可下载符号。