dig into Windows Heap (1)

Welcome to the Windows Land

Overview

Windows10下的堆管理主要分为两类:

​ Nt heap: 默认使用的内存管理机制

​ LowFragmentationHeap: win10下全新的内存管理机制

从使用角度可分为两类:

​ 进程堆:存放在_PEB结构体中

​ 私有堆: 通过HeapCreate返回的句柄来create


Back-End

freelist

_HEAP

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
61
0:004> dt _HEAP
ntdll!_HEAP
+0x000 Segment : _HEAP_SEGMENT
+0x000 Entry : _HEAP_ENTRY
+0x010 SegmentSignature : Uint4B
+0x014 SegmentFlags : Uint4B
+0x018 SegmentListEntry : _LIST_ENTRY
+0x028 Heap : Ptr64 _HEAP
+0x030 BaseAddress : Ptr64 Void
+0x038 NumberOfPages : Uint4B
+0x040 FirstEntry : Ptr64 _HEAP_ENTRY
+0x048 LastValidEntry : Ptr64 _HEAP_ENTRY
+0x050 NumberOfUnCommittedPages : Uint4B
+0x054 NumberOfUnCommittedRanges : Uint4B
+0x058 SegmentAllocatorBackTraceIndex : Uint2B
+0x05a Reserved : Uint2B
+0x060 UCRSegmentList : _LIST_ENTRY
+0x070 Flags : Uint4B
+0x074 ForceFlags : Uint4B
+0x078 CompatibilityFlags : Uint4B
+0x07c EncodeFlagMask : Uint4B
+0x080 Encoding : _HEAP_ENTRY
+0x090 Interceptor : Uint4B
+0x094 VirtualMemoryThreshold : Uint4B
+0x098 Signature : Uint4B
+0x0a0 SegmentReserve : Uint8B
+0x0a8 SegmentCommit : Uint8B
+0x0b0 DeCommitFreeBlockThreshold : Uint8B
+0x0b8 DeCommitTotalFreeThreshold : Uint8B
+0x0c0 TotalFreeSize : Uint8B
+0x0c8 MaximumAllocationSize : Uint8B
+0x0d0 ProcessHeapsListIndex : Uint2B
+0x0d2 HeaderValidateLength : Uint2B
+0x0d8 HeaderValidateCopy : Ptr64 Void
+0x0e0 NextAvailableTagIndex : Uint2B
+0x0e2 MaximumTagIndex : Uint2B
+0x0e8 TagEntries : Ptr64 _HEAP_TAG_ENTRY
+0x0f0 UCRList : _LIST_ENTRY
+0x100 AlignRound : Uint8B
+0x108 AlignMask : Uint8B
+0x110 VirtualAllocdBlocks : _LIST_ENTRY
+0x120 SegmentList : _LIST_ENTRY
+0x130 AllocatorBackTraceIndex : Uint2B
+0x134 NonDedicatedListLength : Uint4B
+0x138 BlocksIndex : Ptr64 Void
+0x140 UCRIndex : Ptr64 Void
+0x148 PseudoTagEntries : Ptr64 _HEAP_PSEUDO_TAG_ENTRY
+0x150 FreeLists : _LIST_ENTRY
+0x160 LockVariable : Ptr64 _HEAP_LOCK
+0x168 CommitRoutine : Ptr64 long
+0x170 StackTraceInitVar : _RTL_RUN_ONCE
+0x178 CommitLimitData : _RTL_HEAP_MEMORY_LIMIT_DATA
+0x198 FrontEndHeap : Ptr64 Void
+0x1a0 FrontHeapLockCount : Uint2B
+0x1a2 FrontEndHeapType : UChar
+0x1a3 RequestedFrontEndHeapType : UChar
+0x1a8 FrontEndHeapUsageData : Ptr64 Wchar
+0x1b0 FrontEndHeapMaximumIndex : Uint2B
+0x1b2 FrontEndHeapStatusBitmap : [129] UChar
+0x238 Counters : _HEAP_COUNTERS
+0x2b0 TuningParameters : _HEAP_TUNING_PARAMETERS
  • +0x7c 8B: EncodeFlagMask: 设置为0x100000,表示需要encode该heap中chunk的header
  • +0x80 16B: Encoding(_HEAP_ENTRY): 用来与chunk header xor的cookie
  • +0x138 8B:BlocksIndex(_HEAP_LIST_LOOKUP): 指向 _HEAP_LIST_LOOKUP结构
  • +0x150 8B: FreeList(_HEAP_ENTRY): 双向链表的head,类似于unsorted bin,但是是有序链表
  • +0x198 8B: FrontEndHeap: 指向管理FrontEnd的Heap结构
  • +0x1a8 8B: FrontEndHeapUsageData: 记录各种大小的chunk使用次数,到达一定次数就会启用Front-End

(header encoding: 在存放chunk header时,会将header ^ _HEAP->Encoding) 再存入)

_HEAP_ENTRY(chunk)

  • Allocated chunk
  • Freed chunk
  • VitrualAlloc chunk

Allocated chunk

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
0:004> dt _HEAP_ENTRY
ntdll!_HEAP_ENTRY
+0x000 UnpackedEntry : _HEAP_UNPACKED_ENTRY
+0x000 PreviousBlockPrivateData : Ptr64 Void
+0x008 Size : Uint2B
+0x00a Flags : UChar
+0x00b SmallTagIndex : UChar
+0x008 SubSegmentCode : Uint4B
+0x00c PreviousSize : Uint2B
+0x00e SegmentOffset : UChar
+0x00e LFHFlags : UChar
+0x00f UnusedBytes : UChar
+0x008 CompactHeader : Uint8B
+0x000 ExtendedEntry : _HEAP_EXTENDED_ENTRY
+0x000 Reserved : Ptr64 Void
+0x008 FunctionIndex : Uint2B
+0x00a ContextValue : Uint2B
+0x008 InterceptorValue : Uint4B
+0x00c UnusedBytesLength : Uint2B
+0x00e EntryOffset : UChar
+0x00f ExtendedBlockSignature : UChar
+0x000 ReservedForAlignment : Ptr64 Void
+0x008 Code1 : Uint4B
+0x00c Code2 : Uint2B
+0x00e Code3 : UChar
+0x00f Code4 : UChar
+0x00c Code234 : Uint4B
+0x008 AgregateCode : Uint8B
  • +0x00 8B: PreviousBlockPrivateData: 作为前一块heap的data,用于0x10对齐
  • +0x08 2B: Size: 存放 size >> 4 的值
  • +0x0a 1B: Flag: 0x1表示处于占用状态、0x2表示存在额外描述、0x4表示使用固定模式填充、0x8表示VirtualAlloc、0x10表示为该段最后一个chunk
  • +0x0b 1B: SmallTagIndex: 是Size和Flag共三个字节的xor结果,用于检查header是否被修改
  • +0x0c 2B: PreviousSize: 上一个chunk的size >> 4 的值
  • +0x0e 1B: SegmentOffset: 某些情况下用来找segment
  • +0x0f 1B: UnusedBytes: 在inuse的时候,表示malloc之后剩下的chunk的空间大小,可以用来判断chunk是来自于Front-End还是Back-End;在freed的时候,恒为0
  • +0x10 : User Data区,在inuse的时候可以进行读写,在freed的时候存放Flink和Blink, Flink和Blink都指向User data区

_HEAP_VIRTUAL_ALLOC_ENTRY (VirtualAlloc chunk)

1
2
3
4
5
6
7
8
9
10
11
12
0:004> dt _HEAP_VIRTUAL_ALLOC_ENTRY
ntdll!_HEAP_VIRTUAL_ALLOC_ENTRY
+0x000 Entry : _LIST_ENTRY
+0x010 ExtraStuff : _HEAP_ENTRY_EXTRA
+0x020 CommitSize : Uint8B
+0x028 ReserveSize : Uint8B
+0x030 BusyBlock : _HEAP_ENTRY

0:004> dt _LIST_ENTRY
MSVCP140!_LIST_ENTRY
+0x000 Flink : Ptr64 _LIST_ENTRY
+0x008 Blink : Ptr64 _LIST_ENTRY
  • +0x00 16B: Entry: 链表的Flink和Blink,分别指向上一个和下一个通过VirtualAlloc分配出来的chunk。
  • +0x30 8B: BusyBlock: 与普通的_HEAP_ENTRY头基本一样,不同在于这里的Size是没有使用的size,储存时也没有进行size >> 4的操作,UnusedBytes恒为4

_HEAP_LIST_LOOKUP

BlocksIndex指向的结构体,用于快速寻找合适的chunk

1
2
3
4
5
6
7
8
9
10
11
0:004> dt _HEAP_LIST_LOOKUP
ntdll!_HEAP_LIST_LOOKUP
+0x000 ExtendedLookup : Ptr64 _HEAP_LIST_LOOKUP
+0x008 ArraySize : Uint4B
+0x00c ExtraItem : Uint4B
+0x010 ItemCount : Uint4B
+0x014 OutOfRangeItems : Uint4B
+0x018 BaseIndex : Uint4B
+0x020 ListHead : Ptr64 _LIST_ENTRY
+0x028 ListsInUseUlong : Ptr64 Uint4B
+0x030 ListHints : Ptr64 Ptr64 _LIST_ENTRY
  • +0x00 8B: ExtendedLookup: 指向下一个ExtendedLookup(通常管理更大chunk)
  • +0x08 4B: ArraySize: 该结构管理的最大chunk的大小,通常为0x80(实际上是 0x800 >> 4 = 0x80)
  • +0x10 4B: ItemCount: 目前该结构所管理的chunk数
  • +0x14 4B: OutofRangeItems: 超出该结构管理大小的chunk数
  • +0x18 4B: BaseIndex: 该结构所管理的chunk的起始index,用来从ListHint中找到合适大小的freed chunk
  • +0x20 8B: ListHead: 指向Freelist的head
  • +0x28 8B: ListsInUseUlong: bitmap,用来判断ListHint中是否有合适大小的chunk
  • +0x30 8B: ListHints: 用来指向对应大小的chunk array,该array以0x10大小为间隔,存放一个对应size的freed chunk的地址

distribution mechanism

Allocate(RtlpAllocateHeap)

Size <= 0x4000

  • 先去看该size对应的FrontEndHeapStatusBitmap是否启用LFH:
    • 没有的话,会对对应的FrontEndHeapUsageData加上0x21
    • 如果FrontEndHeapUsageData > 0xff00 || FrontEndHeapUsageData & 0x1f > 0x10,就会启用LFH
  • 接下来会查看对应ListHint中是否有值(也就是是否有对应size的freed chunk):
    • 如果有值,就检查该chunk的Flink是否是同样size的chunk:
      • 若是则将Flink写入对应的Flink
      • 若否则清空对应的Flink,并将该chunk从Freelist中unlink,并将header encoding
    • 如果对应ListHint没有值,则从比较大的ListHint找:
      • 如果找到了,就以上述同样方式处理该ListLink,并unlink找到的chunk,再切割该chunk为申请的大小,将剩下的chunk放入FreeList,对应大小的ListHint没值就会更新ListHint,最后将申请出来的chunk header encoding
      • 如果没找到,就尝试ExtendHeap加大Heap空间,再从extend出来的chunk拿并切割,将剩下的chunk放入FreeList,对应大小的ListHint没值就会更新ListHint,最后将申请出来的chunk header encoding
  • 最后放回分配chunk的指针

0x4000 < Size <= 0xff000

除了没有LFH相关的操作,其余都跟0x4000以下的情况一样

Size > 0xff000

直接调用ZwAllocateVirtualMemroy进行分配,类似于linux下的mmap直接给一大块地址,并且插入_HEAP->VirtualAllocdBlocks中


Free (RtlpFreeHeap)

Size <= 0xff000

  • 先检查是否0x10对齐,利用unused byte判断该chunk状态(为0表示freed,反之就是inused)
  • 如果是非LFH下,会对应的FrontEndHeapUsageData减1
  • 然后判断前面的和后面的chunk是否freed,是的话就合并
    • 此时会把前面的或后面的chunk从FreeList unlink,并更新ListHint
  • 合并完后,update Size 和 PreviousSize,然后检查是不是最前和最后,是就插入,否则从ListHint中插入,并且Update ListHint,插入时会对FreeList检查(但是此检查不会触发abort,原因在于没有unlink写入)

Size > 0xff000

检查该chunk的linked list并从_HEAP->VirtualAllocdBlocks中移除,接着使用RtlpSecMemFreeVirtualMemory将chunk整个munmap掉


Back-End Exploitation

基本上与Linux的Unlink类似,但要过更多的check:

  • check sum(SmallTagIndex == Size & 0xff ^ Size >> 8 ^ Flag)
  • double linked list的验证(chunk address->Flink->Blink == chunk address && chunk address->Blink->Flink == chunk address)

Exploit

  • 利用unlink attack可实现将一个指针指向自身的效果
  • 利用这个指针,可以控制周围的指针,达到任意地址读写的效果
  • 因没有hook函数来控制程序流,所以只能通过leak stack address来打ROP或者shellcode
1
2
3
4
5
6
7
8
9
10
11
binary base:
PEB-->binary base

dll:
binary base --> IAT --> xxx.dll --> xxx.dll
_HEAP-->LockVariable.Lock --> ntdll.dll
CriticalSection --> DebugInfo --> ntdll.dll

stack address:
kernel32.dll --> kernelbase.dll --> KERNELBASE!BasepFilterInfo --> stack address
kernel32.dll --> ntdll.dll --> ntdll!PebLdr --> PEB --> TEB --> stack address
  • 然后就可以覆盖返回地址打ROP,调用VirtualProtect获得rwx权限,然后 jmp shellcode 或者 ROP执行system(“cmd.exe”)

Front-End

LFH

_LFH_HEAP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0:002> dt _LFH_HEAP
ntdll!_LFH_HEAP
+0x000 Lock : _RTL_SRWLOCK
+0x008 SubSegmentZones : _LIST_ENTRY
+0x018 Heap : Ptr64 Void
+0x020 NextSegmentInfoArrayAddress : Ptr64 Void
+0x028 FirstUncommittedAddress : Ptr64 Void
+0x030 ReservedAddressLimit : Ptr64 Void
+0x038 SegmentCreate : Uint4B
+0x03c SegmentDelete : Uint4B
+0x040 MinimumCacheDepth : Uint4B
+0x044 CacheShiftThreshold : Uint4B
+0x048 SizeInCache : Uint8B
+0x050 RunInfo : _HEAP_BUCKET_RUN_INFO
+0x060 UserBlockCache : [12] _USER_MEMORY_CACHE_ENTRY
+0x2a0 MemoryPolicies : _HEAP_LFH_MEM_POLICIES
+0x2a4 Buckets : [129] _HEAP_BUCKET
+0x4a8 SegmentInfoArrays : [129] Ptr64 _HEAP_LOCAL_SEGMENT_INFO
+0x8b0 AffinitizedInfoArrays : [129] Ptr64 _HEAP_LOCAL_SEGMENT_INFO
+0xcb8 SegmentAllocator : Ptr64 _SEGMENT_HEAP
+0xcc0 LocalData : [1] _HEAP_LOCAL_DATA
  • +0x18 8B: Heap: 指向对应的_HEAP
  • +0x2a4 4B * 129: Buckets: 存放_HEAP_BUCKET结构体的数组,用来寻找配置大小对应到Block大小的阵列结构
  • +0x4a8 8B * 129: SegmentInfoArrays: 存放_HEAP_LOCAL_SEGMENT_INFO结构体的数组,不同大小对应到不同的_HEAP_LOCAL_SEGMENT_INFO结构体,主要管理对应到的_HEAP_SUBSEGMENT的信息
  • +0xcc0 0x28B: LocalData: _HEAP_LOCAL_DATA结构体,其中LocalData.LowFragHeap通常用于找回_LFH_HEAP

_HEAP_BUCKET

1
2
3
4
5
6
7
0:000> dt _HEAP_BUCKET
ntdll!_HEAP_BUCKET
+0x000 BlockUnits : Uint2B
+0x002 SizeIndex : UChar
+0x003 UseAffinity : Pos 0, 1 Bit
+0x003 DebugFlags : Pos 1, 2 Bits
+0x003 Flags : UChar
  • +0x0 2B: BlockUnits: 要分配出去的一个Block大小,存放的Size >> 4
  • +0x2 1B: SizeIndex: 用户需要的大小 >> 4

_HEAP_LOCAL_SEGMENT_INFO

1
2
3
4
5
6
7
8
9
10
ntdll!_HEAP_LOCAL_SEGMENT_INFO
+0x000 LocalData : Ptr64 _HEAP_LOCAL_DATA
+0x008 ActiveSubsegment : Ptr64 _HEAP_SUBSEGMENT
+0x010 CachedItems : [16] Ptr64 _HEAP_SUBSEGMENT
+0x090 SListHeader : _SLIST_HEADER
+0x0a0 Counters : _HEAP_BUCKET_COUNTERS
+0x0a8 LastOpSequence : Uint4B
+0x0ac BucketIndex : Uint2B
+0x0ae LastUsed : Uint2B
+0x0b0 NoThrashCount : Uint2B
  • +0x0 8B: LocalData: 指向对应_HEAP_LOCAL_DATA,即指向_LFH_HEAP->LocalData
  • +0x8 8B: ActiveSubsegment: _HEAP_SUBSEGMENT的结构体指针,用于管理UserBlocks,记录剩余等多chunk、该UserBlocks最大分配数等信息
  • +0x10 8 * 16B: CachedItems: _HEAP_SUBSEGMENT结构体指针数组,存放对应到该_HEAP_LOCAL_SEGMENT_INFO且还有可以分配chunk给用户的_HEAP_SUBSEGMENT指针,相当于内存池,当ActiveSubsegment用完时,就从CachedItems选择补充,替换掉ActiveSubsegment
  • +0xac 8B: BucketIndex: 对应的BucketIndex,也就是_LFH_HEAP->Buckets对应的下标

_HEAP_SUBSEGMENT

1
2
3
4
5
6
7
8
9
10
11
12
13
ntdll!_HEAP_SUBSEGMENT
+0x000 LocalInfo : Ptr64 _HEAP_LOCAL_SEGMENT_INFO
+0x008 UserBlocks : Ptr64 _HEAP_USERDATA_HEADER
+0x010 DelayFreeList : _SLIST_HEADER
+0x020 AggregateExchg : _INTERLOCK_SEQ
+0x024 BlockSize : Uint2B
+0x026 Flags : Uint2B
+0x028 BlockCount : Uint2B
+0x02a SizeIndex : UChar
+0x02b AffinityIndex : UChar
+0x024 Alignment : [2] Uint4B
+0x02c Lock : Uint4B
+0x030 SFreeListEntry : _SINGLE_LIST_ENTRY
  • +0x0 8B: LocalInfo: 指回对应_HEAP_LOCAL_SEGMENT_INFO的结构体指针
  • +0x8 8B: UserBlocks: 一个指向_HEAP_USERDATA_HEADER结构的指针,也就是指向LFH chunk的内存池
  • +0x20 4B: AggregateExchg: _INTERLOCK_SEQ结构,储存对应的UserBlocks的状态信息
  • +0x24 2B: BlockSize: 该UserBlocks中每个chunk的大小
  • +0x28 2B: BlockCount: 该UserBlocks中chunk的个数
  • +0x2a 1B: SizeIndex: 该UserBlocks对应的index

_INTERLOCK_SEQ

1
2
3
4
5
6
ntdll!_INTERLOCK_SEQ
+0x000 Depth : Uint2B
+0x002 Hint : Pos 0, 15 Bits
+0x002 Lock : Pos 15, 1 Bit
+0x002 Hint16 : Uint2B
+0x000 Exchg : Int4B
  • +0x0 2B: Depth: 用来管理对应到的UserBlocks还有多少freed chunk,LFH会用这个判断是否还从该UserBlock进行分配
  • +0x2 1b: Lock,即提供锁的作用,只占用第4 byte的最后一个bit

_HEAP_USERDATA_HEADER

UserBlock

1
2
3
4
5
6
7
8
9
10
11
12
ntdll!_HEAP_USERDATA_HEADER
+0x000 SFreeListEntry : _SINGLE_LIST_ENTRY
+0x000 SubSegment : Ptr64 _HEAP_SUBSEGMENT
+0x008 Reserved : Ptr64 Void
+0x010 SizeIndexAndPadding : Uint4B
+0x010 SizeIndex : UChar
+0x011 GuardPagePresent : UChar
+0x012 PaddingBytes : Uint2B
+0x014 Signature : Uint4B
+0x018 EncodedOffsets : _HEAP_USERDATA_OFFSETS
+0x020 BusyBitmap : _RTL_BITMAP_EX
+0x030 BitmapData : [1] Uint8B
  • +0x0 8B: SubSegment: 指回对应的_HEAP_SUBSEGMENT结构
  • +0x18 8B: EncodedOffsets: _HEAP_USERDATA_OFFSETS结构,用来验证chunk header是否被改过
  • +0x20 0x10B: BusyBitmap: 记录该UserBlocks那些chunk被使用

_HEAP_ENTRY(LFH chunk)

LFH chunk

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
ntdll!_HEAP_ENTRY
+0x000 UnpackedEntry : _HEAP_UNPACKED_ENTRY
+0x000 PreviousBlockPrivateData : Ptr64 Void
+0x008 Size : Uint2B
+0x00a Flags : UChar
+0x00b SmallTagIndex : UChar
+0x008 SubSegmentCode : Uint4B
+0x00c PreviousSize : Uint2B
+0x00e SegmentOffset : UChar
+0x00e LFHFlags : UChar
+0x00f UnusedBytes : UChar
+0x008 CompactHeader : Uint8B
+0x000 ExtendedEntry : _HEAP_EXTENDED_ENTRY
+0x000 Reserved : Ptr64 Void
+0x008 FunctionIndex : Uint2B
+0x00a ContextValue : Uint2B
+0x008 InterceptorValue : Uint4B
+0x00c UnusedBytesLength : Uint2B
+0x00e EntryOffset : UChar
+0x00f ExtendedBlockSignature : UChar
+0x000 ReservedForAlignment : Ptr64 Void
+0x008 Code1 : Uint4B
+0x00c Code2 : Uint2B
+0x00e Code3 : UChar
+0x00f Code4 : UChar
+0x00c Code234 : Uint4B
+0x008 AgregateCode : Uint8B
  • +0x8 4B: SubSegmentCode: encode过的metadata,用来推回UserBlocks的位置
  • +0xc 2B: PreviousSize: 该chunk在UserBlock中的index,实际上是第0xd个byte
  • +0xf 1B: UnusedBytes: 用来判断该LFH chunk是否为freed状态,如果是busy状态,则为0x80

Etc

_HEAP_USERDATA_HEADER->EncodedOffsetsUserBlocks初始化的时候设置,其算法为下面四个值进行xor:

  • (sizeof(_HEAP_USERDATA_HEADER)) | ((_HEAP_BUCKET->BlockUnits) * 0x10 << 16)
  • LFHkey
  • UserBlocks的地址
  • _LFH_HEAP的地址

所有UserBlocks里的chunk header在初始化的时候都会经过xor,其算法为下面各个值得xor:

  • _HEAP的地址
  • LFHkey
  • chunk本身的地址address >> 4
  • ((chunk address) - (UserBLocks address)) << 12

distribution mechanism

LFH initialization

在Back-End中也对LFH也有所提及,也就是在FrontEndHeapUsageData[x] & 0x1F > 0x10的时候,置位_HEAP->CompatibilityFlag |= 0x20000000,下一次Allocate就会对LFH进行初始化

  1. 首先会ExtendFrontEndUsageData及增加更大的_HEAP->BlocksIndex,因为这里_HEAP->BlocksIndex可以理解为一个_HEAP_LIST_LOOKUP结构的单向链表(参考上面Back-End的解释),且默认初始情况下只存在一个管理比较小的(0x0 ~ 0x80)的chunk的_HEAP_LIST_LOOKUP,所以这里会扩展到(0x80 ~ 0x400),即在链表尾追加一个管理更大chunk的_HEAP_LIST_LOOKUP结构体结点
  2. 建立并初始化_HEAP->FrontEndHeap(通过mmap),即初始化_LFH_HEAP的一些metadata
  3. 建立并初始化_LFH_HEAP->SegmentInfoArrays[x],在SegmentInfoArrays[BucketIndex]处填上对应的_HEAP_LOCAL_SEGMENT_INFO结构体指针

再接下来Allocate相同大小的chunk就会开始使用LFH:

  1. 分配UserBlocks并进行初始化,即设置对应大小的chunk
  2. 然后再设置对应_HEAP_LOCAL_SEGMENT_INFO->ActiveSubsegment
  3. 随机地从UserBlocks中返回一个chunk

Allocate (RtlpLowFragHeapAllocFromContext)

  1. 先看看ActiveSubsegment中有没有空闲的chunk,也就是通过ActiveSubsegment->AggregateExchg.depth(free chunk的个数)判断:

    • 如果没有则从CacheedItems中找,找到有存在空闲chunk的Subsegment就替换掉当前的ActiveSubsegment
    • 如果有则继续往下
  2. 取得RtlpLowFragHeapRandomData[x]上的值;且取值是依次循环取的,x为1 byte大小的值,即下一次x = (x + 1) % 256;由于RtlpLowFragHeapRandomData是一个存放256个随机数的数列(范围为0x0 ~ 0x7F),所以这里相当于在取随机数

  3. 计算相应的UserBlocks里chunk的index,通过
    RtlpLowFragHeapRandomData[x] * max_index >> 7

    (其中max_index显然是能取到的最大的index):

    • 如果发生了collision,即该index对应的chunk是busy的,那么往后取最近的;细节上,就是检查index对应到的bitmap是否为0,如果是0就返回对应的bitmap,否则选取最近的下一个
    • 如果没有发生,则继续往下
  4. 检查chunk->UnusedBytes & 0x3F != 0,因为满足此式表示chunk是free状态的,否则状态非法;该过程中还会设置对应的bitmap,以及更新ActiveSubsegment->AggregateExchg.depth等相关信息

  5. 最后设置index(即chunk->PreviousSize)以及chunk->UnusedBytes,并把chunk返回给用户


Free(RtlpFreeHeap)

  1. 首先更新chunk->UnusedBytes
  2. 找到该chunk对应的在UserBlocks中的index,并且置UserBlocks->BusyBitmap对应的bit为0
  3. 更新ActiveSubsegment->AggregateExchg
  4. 如果该chunk不属于当前的ActiveSubsegment则看能不能放进CachedItems中去,如果可以就放进去

Front-End Exploitation

Reused attack:

假如我们拥有UAF的漏洞可以利用,但是因为LFH分配的随机性,我们无法预测下一个那到的chunk是在哪个位置,也就是说现在我们free的chunk,下一次malloc不一定拿得到
那么此时可以通过填满UserBlocks的方式,再free掉目标chunk,这样下一次malloc就必然会拿到目标chunk(因为只剩下一个),然后可以利用这个特性构造chunk overlap做进一步利用

Reference

Angel Boy

n0p