文章来自blackhat 2021的文章《Windows-Heap-Backed-Pool-The-Good-The-Bad-And-The-Encoded》
原文链接:https://i.blackhat.com/USA21/Wednesday-Handouts/us-21-Windows-Heap-Backed-Pool-The-Good-The-Bad-And-The-Encoded.pdf
Youtube:https://www.youtube.com/watch?v=VvxNc8GTFfk
粗略的分配
内核动态内存:和R3中的对差不多
类型为可分页和不可分页,程序按照4KB分页,之前写过一点相关的:Joe1sn’s Cabinet | windows内核驱动 2-页表探索
旧API初始化中不会将内存置零,导致信息泄露
一些API
在RS5(Redstone 5)版本前(及Windows 1809,在2018年10月前的版本)
没有任何校验、加密等等,但是之后就变得复杂得多了(严重怀疑微软借鉴了glibc的内存管理)
- 新版本的池设计管理和R3下是同一个库
- 每一个独立的池由
SEGMENT_HEAP
结构体管理,后文简写为SEG_HEAP
- 池的申请根据其大小进行不同的处理
SEGMENT_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
| kd> dt nt!_SEGMENT_HEAP +0x000 EnvHandle : RTL_HP_ENV_HANDLE +0x010 Signature : Uint4B +0x014 GlobalFlags : Uint4B +0x018 Interceptor : Uint4B +0x01c ProcessHeapListIndex : Uint2B +0x01e AllocatedFromMetadata : Pos 0, 1 Bit +0x020 CommitLimitData : _RTL_HEAP_MEMORY_LIMIT_DATA +0x020 ReservedMustBeZero1 : Uint8B +0x028 UserContext : Ptr64 Void +0x030 ReservedMustBeZero2 : Uint8B +0x038 Spare : Ptr64 Void +0x040 LargeMetadataLock : Uint8B +0x048 LargeAllocMetadata : _RTL_RB_TREE +0x058 LargeReservedPages : Uint8B +0x060 LargeCommittedPages : Uint8B +0x068 StackTraceInitVar : _RTL_RUN_ONCE +0x080 MemStats : _HEAP_RUNTIME_MEMORY_STATS +0x0d8 GlobalLockCount : Uint2B +0x0dc GlobalLockOwner : Uint4B +0x0e0 ContextExtendLock : Uint8B +0x0e8 AllocatedBase : Ptr64 UChar +0x0f0 UncommittedBase : Ptr64 UChar +0x0f8 ReservedLimit : Ptr64 UChar +0x100 SegContexts : [2] _HEAP_SEG_CONTEXT +0x280 VsContext : _HEAP_VS_CONTEXT +0x340 LfhContext : _HEAP_LFH_CONTEXT
|
文章选了一些重点来
根据上图缩略结构体
1 2 3 4 5 6 7 8 9 10 11 12
| kd> dt nt!_SEGMENT_HEAP .... +0x048 LargeAllocMetadata : _RTL_RB_TREE .... +0x080 MemStats : _HEAP_RUNTIME_MEMORY_STATS .... +0x0e8 AllocatedBase : Ptr64 UChar +0x0f0 UncommittedBase : Ptr64 UChar +0x0f8 ReservedLimit : Ptr64 UChar +0x100 SegContexts : [2] _HEAP_SEG_CONTEXT +0x280 VsContext : _HEAP_VS_CONTEXT +0x340 LfhContext : _HEAP_LFH_CONTEXT
|
根据文章,SEG_HEAP
通过两个SegContexts
来处理前两种大小的内存(小堆和中堆)
- 0~508KB(0-0x7F000)
- 508KB~8128KB(0x7F000-0x7F0000)
- 大于8128KB(大堆处理)
内核通过位图(bitmap,做过算法的应该知道吧)保存每次申请的大小的类型
1
| nt!ExPoolState‐>HeapManager‐>AllocTracker‐>AllocTrackerBitma
|
关于位图
- 每两字节(2Byte)表示在内核内存中的地址
- 位图存在三层
_HEAP_SEG_CONTEXT
结构体:
在SEG_HEAP
中
_HEAP_SEG_CONTEXT[0]
:以 1 页的基本单位处理 1MB 段
_HEAP_SEG_CONTEXT[1]
:以 16 页的基本单位处理 16MB 段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 1: kd> dt nt!_HEAP_SEG_CONTEXT +0x000 SegmentMask : Uint8B +0x008 UnitShift : UChar +0x009 PagesPerUnitShift : UChar +0x00a FirstDescriptorIndex : UChar +0x00b CachedCommitSoftShift : UChar +0x00c CachedCommitHighShift : UChar +0x00d Flags : <anonymous-tag> +0x010 MaxAllocationSize : Uint4B +0x014 OlpStatsOffset : Int2B +0x016 MemStatsOffset : Int2B +0x018 LfhContext : Ptr64 Void +0x020 VsContext : Ptr64 Void +0x028 EnvHandle : RTL_HP_ENV_HANDLE +0x038 Heap : Ptr64 Void +0x040 SegmentLock : Uint8B +0x048 SegmentListHead : _LIST_ENTRY +0x058 SegmentCount : Uint8B +0x060 FreePageRanges : _RTL_RB_TREE +0x070 FreeSegmentListLock : Uint8B +0x078 FreeSegmentList : [2] _SINGLE_LIST_ENTRY
|
简化一下重要信息
1 2 3 4 5 6
| 1: kd> dt nt!_HEAP_SEG_CONTEXT +0x000 SegmentMask : Uint8B ..... +0x048 SegmentListHead : _LIST_ENTRY ..... +0x078 FreeSegmentList : [2] _SINGLE_LIST_ENTRY
|
段(segment)链接的结构体为`HEAP_PAGE_SEGMENT
_HEAP_PAGE_SEGMENT
结构体
1 2 3 4 5 6
| 0: kd> dt nt!_HEAP_PAGE_SEGMENT +0x000 ListEntry : _LIST_ENTRY +0x010 Signature : Uint8B +0x018 SegmentCommitState : Ptr64 _HEAP_SEGMENT_MGR_COMMIT_STATE +0x020 UnusedWatermark : UChar +0x000 DescArray : [256] _HEAP_PAGE_RANGE_DESCRIPTOR
|
这里抹除类型,给点注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 0: kd> dt nt!_HEAP_PAGE_SEGMENT +0x000 ListEntry : +0x010 Signature : ........ +0x000 DescArray : [256]
|
总结下当前的内核相关信息,从SEG_HEAP
开始
关于两种大小的内存管理
两种子段 VS和LFH
1. LFH - Low Fragmentation Heap
_HEAP_LFH_SUBSEGMENT
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 0: kd> dt nt!_HEAP_LFH_SUBSEGMENT +0x000 ListEntry : _LIST_ENTRY +0x010 Owner : Ptr64 _HEAP_LFH_SUBSEGMENT_OWNER +0x010 DelayFree : _HEAP_LFH_SUBSEGMENT_DELAY_FREE +0x018 CommitLock : Uint8B +0x020 FreeCount : Uint2B +0x022 BlockCount : Uint2B +0x020 InterlockedShort : Int2B +0x020 InterlockedLong : Int4B +0x024 FreeHint : Uint2B +0x026 Location : UChar +0x027 WitheldBlockCount : UChar +0x028 BlockOffsets : _HEAP_LFH_SUBSEGMENT_ENCODED_OFFSETS +0x02c CommitUnitShift : UChar +0x02d CommitUnitCount : UChar +0x02e CommitStateOffset : Uint2B +0x030 BlockBitmap : [1] Uint8B
|
-
用于 129 种常见大小的分配
-
所有分配的子段有一样的大小
-
最大大小是0x4000
-
为了节省空间,没有独特的head
-
堆块的状态由head中的位图决定
2. VS - Variable Size
_HEAP_VS_SUBSEGMENT
1 2 3 4 5 6 7
| 0: kd> dt nt!_HEAP_VS_SUBSEGMENT +0x000 ListEntry : _LIST_ENTRY +0x010 CommitBitmap : Uint8B +0x018 CommitLock : Uint8B +0x020 Size : Uint2B +0x022 Signature : Pos 0, 15 Bits +0x022 FullCommit : Pos 15, 1 Bit
|
- 处理所有LFH不能处理的bucket
- 没有个块(block)都有一个自己的头描述
两者的对比
- VS头中的大小使用
heap key
加密(其实是编码,这里为了语义通顺),LFH使用自己的LFH Key
加密。(这显然让堆利用变得困难,你猜为啥我要反过来读这篇文章而不是直接做HEVD)
- 子段大小不固定
- 子段包含多个页,必须从头开始计算偏移去寻找(单独寻找某个页已经几乎不可能)
- 已分配的块仍然会存在
POOL_HEAD
,但是释放掉的不会
相关的“加密"
- HEAP_LFH_SUBSEGMENT
- 块大小和偏移量位于编码的 BlockOffsets 字段中
- 数据(Data) =
EncodedData ^ LfhKey ^ ((ULONG)(Subsegment) >> 12)
- 子段列表没被加密
- HEAP_VS_SUBSEGMENT
- 用于链接子段的列表被当前子段地址加密了
- HEAP_VS_CHUNK_HEADER
- 每一个VS子段中头存在
- 块大小和分配状态在
Size
头中被加密
- 数据(Data)=
Sizes.HeaderBits ^ HeapKey ^ ChunkHeader
两种上下文
最开始的SEG_HEAP
中有
1 2
| +0x280 VsContext : _HEAP_VS_CONTEXT +0x340 LfhContext : _HEAP_LFH_CONTEXT
|
_HEAP_LFH_CONTEXT
1 2 3 4 5 6 7 8 9 10 11
| 0: kd> dt nt!_HEAP_LFH_CONTEXT +0x000 BackendCtx : Ptr64 Void +0x008 Callbacks : _HEAP_SUBALLOCATOR_CALLBACKS +0x030 AffinityModArray : Ptr64 UChar +0x038 MaxAffinity : UChar +0x039 LockType : UChar +0x03a MemStatsOffset : Int2B +0x03c Config : _RTL_HP_LFH_CONFIG +0x040 BucketStats : _HEAP_LFH_SUBSEGMENT_STATS +0x048 SubsegmentCreationLock : Uint8B +0x080 Buckets : [129] Ptr64 _HEAP_LFH_BUCKET
|
注释关键信息
1 2 3 4 5 6 7
| 0: kd> dt nt!_HEAP_LFH_CONTEXT ... +0x008 Callbacks : ...... +0x080 Buckets : [129]
|
_HEAP_VS_CONTEXT
1 2 3 4 5 6 7 8 9 10 11 12
| 0: kd> dt nt!_HEAP_VS_CONTEXT +0x000 Lock : Uint8B +0x008 LockType : _RTLP_HP_LOCK_TYPE +0x010 FreeChunkTree : _RTL_RB_TREE +0x020 SubsegmentList : _LIST_ENTRY +0x030 TotalCommittedUnits : Uint8B +0x038 FreeCommittedUnits : Uint8B +0x040 DelayFreeContext : _HEAP_VS_DELAY_FREE_CONTEXT +0x080 BackendCtx : Ptr64 Void +0x088 Callbacks : _HEAP_SUBALLOCATOR_CALLBACKS +0x0b0 Config : _RTL_HP_VS_CONFIG +0x0b4 Flags : Uint4B
|
注释关键信息
1 2 3 4 5 6 7 8
| 0: kd> dt nt!_HEAP_VS_CONTEXT ...... +0x020 SubsegmentList : +0x030 TotalCommittedUnits : +0x038 FreeCommittedUnits : ...... +0x088 Callbacks : ......
|
总结下两种上下文