kernel pool 基础
kernel pools 被分为 Non-Paged Pools, Paged Pools, Session Pools, etc. 被定义在POOL_TYPE 中
Non-Paged Pool
- 不可分页的系统内存
- 保证在任何时候都驻留在物理内存中
- pools 的数量储存于 nt!ExpNumberOfNonPagedPools
- 在单处理器系统中,nt!PoolVector 数组的第一个节点指向 non-paged pool 描述符
kd> dt nt!_POOL_DESCRIPTOR poi(nt!PoolVector)
+0x000 PoolType : 0 ( NonPagedPool )
+0x004 PagedLock : _KGUARDED_MUTEX
+0x004 NonPagedLock : 0
+0x040 RunningAllocs : 0n769209
+0x044 RunningDeAllocs : 0n679297
+0x048 TotalBigPages : 0n2518
+0x04c ThreadsProcessingDeferrals : 0n0
+0x050 TotalBytes : 0x12a3618
+0x080 PoolIndex : 0
+0x0c0 TotalPages : 0n3184
+0x100 PendingFrees : 0x866582c0 -> 0x8688cb40 Void
+0x104 PendingFreeDepth : 0n14
+0x140 ListHeads : [512] _LIST_ENTRY [ 0x8414cfc0 - 0x8414cfc0 ]
- 在多处理器中,每个节点又它自己的 non-paed pool 秒素符, 指针保存在 nt!ExpNunPagedPoolDescriptor 数组中
Paged Pool
- 分页系统内存
- 在IRQL < DPC/Dispatch level 时才可以被访问
- pools 的数量保存于 nt!ExpNumberOfPagedPools
- 在单处理器系统中,有4 pages pool 描述符, 从1-4 在 nt!ExpPagedPoolDescriptor
- 在多处理器系统中,在每个节点有1个paged pool 描述符
- 为原型池/整页分配定义了一个额外的paged pool 描述符, 下标0 的 nt!ExpPagedPoolDescriptor
Session Paged Pool
- 会话空间的可分页系统内存, 每个登录用户都是唯一的
- 初始化在 nt!MilnitializeSessionPool
- 在Vista, pool 描述符保存在 nt!ExpSessionPoolDescriptor(session space)
- 在win7, 使用从当前线程指向池描述符的指针,
KTHREAD->Process->Session.PagedPool - Non-paged session 分配使用全局 non-paged pools
每个kernel pool 由 POOL_DESCRIPTOR 结构定义,
-可以用于追踪 分配/释放的数量,使用中的pages ,etc,
- 维持free pool chunks 列表
paged 和 non-paged pool 的初始描述符在nt!PoolVector数组中定义
- 每个索引都指向一个或多个描述符的数组
win7 x86
kd> dt nt!_POOL_DESCRIPTOR
+0x000 PoolType : _POOL_TYPE
+0x004 PagedLock : _KGUARDED_MUTEX
+0x004 NonPagedLock : Uint4B
+0x040 RunningAllocs : Int4B
+0x044 RunningDeAllocs : Int4B
+0x048 TotalBigPages : Int4B
+0x04c ThreadsProcessingDeferrals : Int4B
+0x050 TotalBytes : Uint4B
+0x080 PoolIndex : Uint4B
+0x0c0 TotalPages : Int4B
+0x100 PendingFrees : Ptr32 Ptr32 Void
+0x104 PendingFreeDepth : Int4B
+0x140 ListHeads : [512] _LIST_ENTRY
每个pool chunk 有8字节的pool header,空闲的chunks 根据block size 放到ListHeads 中
x86
kd> dt nt!_POOL_HEADER
+0x000 PreviousSize : Pos 0, 9 Bits
+0x000 PoolIndex : Pos 9, 7 Bits
+0x002 BlockSize : Pos 0, 9 Bits
+0x002 PoolType : Pos 9, 7 Bits
+0x000 Ulong1 : Uint4B
+0x004 PoolTag : Uint4B
+0x004 AllocatorBackTraceIndex : Uint2B
+0x006 PoolTagHash : Uint2B
PreviousSize: 上一个cunk 的blocksize
PoolIndex: 在pool 描述符中的索引
BlockSize: (NumberOfBytes + 0xF) >> 3
PoolType: Free = 0, Allocated = (PoolType|2) PoolTag;4
x64
BlockSize: (NumberOfBytes + 0x1F) >> 4 (256 ListHeads entries due to 16 byte block size)
ProcessBilled: 指向为池分配收取的过程对象的指针(用于配额管理)
释放
当pool chunk 被释放加入到ListHeads 中,头部将又LIST_ENTRY 结构
kd> dt nt!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY
Lookaside Lists
- 内核使用lookaside lists 来 申请/释放 小的pool chunks
- 独立的每处理器lookaside列表为可分页和不可分页的分配
- 在KPRCB(Processor Control Block) 中定义
- 最大blocksize 为0x20 ( 256 bytes)
- 单向
Large Pool Allocations
- 分配超过0xff0 (4080) bytes
- 由nt!ExpAllocateBigPool 处理
- 每个处理器又4 个单向lookaside lists for big pool allocations
- 1 paged 分配一个页面
- 3 non-paged 分配页面计数为1,2,3
- 如果不能分配lookaside list, 使用bitmap 来获取请求的pool pages
- 在位图中搜索包含所请求数量的未使用页面的第一个索引
- bitmap 使用它自己的memory 来定义主要的pool type
- bits 数组位于pool 内存范围的开始
分配算法
- Lookaside list
- ListHeads List
- Pool page allocator
释放
ExFreePoolWithTag
相邻的free chunk 可能会被会被合并
利用
参考
http://www.nosuchcon.org/talks/2013/D3_02_Nikita_Exploiting_Hardcore_Pool_Corruptions_in_Microsoft_Windows_Kernel.pdf
https://infiltratecon.com/archives/kernelpool_infiltrate2011.pdf
https://www.cnblogs.com/flycat-2016/p/5449738.html