Before I start talking about the 4th vulnerability on HEVD (Uninitialized Stack Variable), 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 mode using an uninitialized stack variable
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.
The author mentions in the source code that in the secure version, UninitializedMemory variable is initialized to a value. However in the vulnerable version, it’s not initialized and later called in the the callback() function, which leads to this vulnerability.
Finding the IOCTL Code:
Got to Functions, select IrpDevideIOCTLHandler, in the text view scroll down until you see the call for UninitializedStackVariableIoctlHandler
We’ll follow the arrows for the function loc_15171A above and will see the following jmp statement.
We’ll add 4 bytes to find the ICOTL to 0x22202B which will give us 0x22202F
In order to see our affect in actions, we’ll put a breakpoint in windbg with !HEVD+0x571A for the function loc_15171A (1 in the address = a base address of 0x00010000 so we can remove it in the breakpoint)
Initial Exploit:
We’ll create the initial exploit with the IOCTL code to verify the vulnerability. My code is pretty same as the last times so shouldn’t be a shocker:
import struct, sys, ctypes from ctypes import * from subprocess import * ## DLL kernel32 = windll.kernel32 psapi = windll.Psapi ntdll = windll.ntdll 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) buffer = “\xb0\xb0\xd0\xba” kernel32.DeviceIoControl(handle, 0x22202f, buffer, len(buffer), None, 0, byref(c_ulong()), None) |
On windbg, write the following commands
- !sym noisy
- .reload
- lm m H*
- bp !HEVD+0x571A
- g
when we run the code, we hit the breakpoint which verifies the IOCTL code:
Let’s change the value for the buffer variable and check windbg again:
Source Code Analysis on IDA:
Let’s see in IDA again what does this function do:
If you follow the IOCTL handler for this function,
- we see our user buffer put in ecx following a test ecx, ecx statement.
- If we fail jz statement, we’ll follow the red lines meaning we’ll trigger the vulnerability
If you double click on TriggerUninitializedStackVariable, we’ll see what’s going on in the Graph view:
- We have a call to ProbeForRead and then
- we load the value 0xBAD0B0B0 into eax
- cmp esi (or our user provided buffer) and the arbitrary static value
We see that here we’re loading a value of 0xBAD0B0B0 into eax and then executing a cmp between that value and esi. This is probably a compare between our user provided buffer and 0xBAD0B0B0 .
If our user provided buffer value is the magic value (0xBAD0B0B0), we fail the jnz statement (as the value is 0), we’ll go with red (the short path) and execute the followings:
- we put eax into stack (eax was 0xBAD0B0B0 as we ended up here after the cmp esi,eax –> jnz statement)
- offset to the UninitializedStackVariableObjectCallback entity into stack
If you right click on UninitializedStackVariableObjectCallback , the hex value is 86670 which is the address of the callback function
In summary, when we take this path, we put 2 initialized variable values onto stack
If we take the second path, we’ll eventually do the followings:
cmp [ebp+UninitializedStackVariable.Callback], edi
As long as the function pointer is not initialized to NULL & we don’t provide the magic value as our user value, we’ll end up with the red flag to call [ebp+var_108]
If we right click on the value, it shows 108 in hex. So we will call whatever is located at ‘ebp-108’. The purpose here is get a pointer to shellcode at the address ‘ebp-108’.
Finding Offset:
On windbg, write the following commands once again:
- !sym noisy
- .reload
- lm m H*
- bp !HEVD+0x571A
- g
- dps esp
trigger the vulnerability again and run the following command in windbg:
kd> !thread
See that Kernel Stack Init is 0x82b7ded0 and &UninitializedStackVariable.Callback
kd> ?< Kernel Stack Init>-<&UninitializedStackVariable.Callback>
this will give us the offset 00000504 which means when we put our data at an offset of 0x504 from Stack Init, we can hijack the Instruction Pointer.
Kernel Stack Spraying:
We can do this using the API call NtMapUserPhyiscalPages to spray a pointer value onto the stack repeatedly. You can find the API documentation here:
https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapuserphysicalpages
We’ll have the following parameter values for the corresponding reasons:
- Page Array: none
- NumberofPages: 1024
- VirtualAddress: pointer address
We can spray upto 1024*sizeof(ULONG_PTR) using this API.
We’ll put a breakpoint on the last instruction of NtMapUserPhysicalPages routine and then run the final script:
kd >bp nt!NtMapUserPhysicalPages
kd> bp nt!NtMapUserPhysicalPages +0x5be
kd> g
kd> !thread
Once the breakpoint is hit, we will get Kernel Stack Init address and find that value at an offset of 0x504.
kd> !thread
Final Code:
As always, I’ll summarize what our final exploit will have before I show it all in one place:
When we run the code, we get the shell as NT AUTHORITY\SYSTEM