Intro

This is my first time looking into Kernel Exploitation so i decided to practice on HEVD Driver by @hacksys. I’ve written this blog post to better understand the kernel space and upgrade my skills. Since this is my first time doing kernel exploitation, i’ll be going with the stack overflow. Our setup is:

  1. Windows 10 x64 bit
  2. HEVD 3.0
  3. Windbg Preview
  4. IDA

Looking into source code

Because i’m rookie on kernel exploitation, i want to look at the source code before reversing the driver.

NTSTATUS BufferOverflowStackIoctlHandler(
    _In_ PIRP Irp,
    _In_ PIO_STACK_LOCATION IrpSp
)
{
    SIZE_T Size = 0;
    PVOID UserBuffer = NULL;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;

    UNREFERENCED_PARAMETER(Irp);
    PAGED_CODE();

    UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
    Size = IrpSp->Parameters.DeviceIoControl.InputBufferLength;

    if (UserBuffer)
    {
        // If we manipulate the size of user buffer we'll trigger stackoverflow
        Status = TriggerBufferOverflowStack(UserBuffer, Size); 
    }

    return Status;
}
NTSTATUS
TriggerBufferOverflowStack(
    _In_ PVOID UserBuffer,
    _In_ SIZE_T Size
)
{
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG KernelBuffer[BUFFER_SIZE] = { 0 }; 
    // ... other codes

    DbgPrint("[+] Triggering Buffer Overflow in Stack\n");
    // This function will copy the UserBuffer into the KernelBuffer without
    // validating the size i.e., if the UserBuffer size is greater than 
    // size of the KernelBuffer and this will cause the overflow 
    RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size); 
}

From the Above code we can see that if the UserBuffer is not null then it’ll call the function TriggerBufferOverflowStack(UserBuffer,Size). Inside this function there’s a insecure version of code, in there RtlCopyMemory function copies the UserBuffer into the KernelBuffer without proper size check. This will help us on exploiting this vulnerability.

Disassembling the driver

There is the IrpDeviceIoCtlHandler() function which contains the jump table for each IOCTL code.

IrpDeviceIoCtlHandler

Inside the DriverEntry function we can see that IoCreateDevice() is being called. Drivers and user-mode components access most system-defined objects through handles. To obtain a handle to a driver object, a driver needs to expose at least one device object which is done through IoCreateDevice.

//The IoCreateDevice routine creates a device object for use by a driver
//and saves a pointer to it in the DeviceObject variable
NTSTATUS IoCreateDevice(
  PDRIVER_OBJECT  DriverObject,             //DriverObject
  ULONG           DeviceExtensionSize,      //DeviceExtensionSize
  PUNICODE_STRING DeviceName,               //DeviceName
  DEVICE_TYPE     DeviceType,               //DeviceType
  ULONG           DeviceCharacteristics,    //DeviceCharacteristics
  BOOLEAN         Exclusive,                //Exclusive
  PDEVICE_OBJECT  *DeviceObject             //DeviceObject
);

Inside the DriverEntry we can see the DeviceName as \\Device\\HackSysExtremeVulnerableDriver.

DeviceString

Let’s find BufferOverflowStackIoctlHandler inside IrpDeviceIoCtlHandler call. As we can see if the IOCTL is 0x222003h, it’ll jumps to the code block where BufferOverflowStackIoctlHandler function is present.

SwitchTable

From the BufferOverflowStackIoctlHandler function TriggerBufferOverflowStack function will be called if the UserBuffer is not empty.

TriggerBufferOverflowStack

If we open up the TriggerBufferOverflowStack function in IDA we can see the buffersize that KernelBuffer accepts which is 2048.

SizeOfKernelBuffer

Now we know that the KernelBuffer accepts the buffer size of 2048, and there is no buffer size checks while copying UserBuffer into KernelBuffer. So, to trigger the bug we need to send buffer with size greater than 2048.

Talking to driver

Since this is the kernel exploitation, we need a way to talk to the kernel driver because we cannot directly touch the kernel mode objects and device driver directly from the user mode. To communicate from the userland, We need to have a handle to that particular device. We can simply create a handle to the driver object using CreateFileA.

HANDLE CreateFileA(
  LPCSTR                lpFileName,
  DWORD                 dwDesiredAccess,
  DWORD                 dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD                 dwCreationDisposition,
  DWORD                 dwFlagsAndAttributes,
  HANDLE                hTemplateFile
);

Then we can interact with the driver by sending IOCTL codes using DeviceIoControl() Function. Each calls to the DeviceIoControl() function causes I/O manager to create an I/O Request Packet (IRP) whose major function is IRP_MJ_DEVICE_CONTROL and sends it to the corresponding dispatch routine of the device driver.

BOOL DeviceIoControl(
  HANDLE       hDevice,
  DWORD        dwIoControlCode,
  LPVOID       lpInBuffer,
  DWORD        nInBufferSize,
  LPVOID       lpOutBuffer,
  DWORD        nOutBufferSize,
  LPDWORD      lpBytesReturned,
  LPOVERLAPPED lpOverlapped
);

Writing Exploit

Till now we know three important things to begin with, DeviceName (\\Device\\HackSysExtremeVulnerableDriver), the length that the buffer accepts (0x800 or 2048), and the IOCTL code (0x222003h).

Triggering the crash


References

https://h0mbre.github.io/HEVD_Stackoverflow_64bit/#