
Shellcode de modo kernel para Windows x64 elaborado a mano para el robo de tokens
Shellcode artesanal en modo kernel para Windows x64 que reemplaza el token de acceso primario del proceso en ejecución con el token del proceso SYSTEM para Elevación de privilegios (EoP).
Windows 7/Windows Server 2008 R2 Build 7601Windows 8/Windows Server 2012 Build 9200Windows 8.1/Windows Server 2012 R2 Build 9600Windows 10 1507/TS1 Build 10240Windows 10 1511/TS2 Build 10586Windows 10 1607/RS1/Windows Server 2016 Build 14393Windows 10 1703/RS2 Build 15063Windows 10 1709/RS3 Build 16299Windows 10 1803/RS4 Build 17134Windows 10 1809/RS5/Windows Server 2019 Build 17763Windows 10 1903/19H1 Build 18362Windows 10 1909/19H2 Build 18363Windows 10 2004/20H1 Build 19041Windows 10 2009/20H2 Build 19042Windows 10 2104/21H1 Build 19043Windows 10 2110/21H2 Build 19044Los requisitos previos para compilar este proyecto son:
Visual Studio 2019 (cualquier edición servirá)Windows 10 SDK, versión 2004Windows 10 WDK, versión 2004Python3Cabe señalar aquí que basta con tener un ensamblador (este proyecto usa MASM) porque técnicamente es todo lo que necesitas.
Después de instalar lo anterior, debería ser tan fácil como abrir la solución con Visual Studio y compilar para el destino x64.
Después de una compilación exitosa, los binarios se pueden encontrar dentro del directorio Bin, en el subdirectorio de la arquitectura correspondiente.
Alternativamente, puedes descargar shellcode independiente de posición listo para implementar desde Releases.
Por favor, NO intentes implementar el payload en una máquina de la que dependas para trabajar si no estás seguro de cómo funciona.
Consulta Microsoft docs para obtener información adicional.
Para fines de prueba, recomiendo encarecidamente usar flare-kscldr para implementar el shellcode en modo kernel en una VM de prueba y la guía de CodeMachine System setup for kernel development and debugging para configurar una VM invitada de Hyper-V con soporte completo de depuración del kernel.
Opcionalmente, también puedes considerar automatizar el proceso con kdbg-driver-vagrant para levantar rápidamente una VM de prueba con depuración completa del kernel usando Vagrant.

Como me señaló Dmytro Oleksiuk(@d_olex), hay algunas condiciones de carrera no tan sutiles en el código, específicamente relacionadas con:
nt!_EPROCESS enlazadas entre sí mediante una lista doblemente enlazada circular sin usar algún tipo de primitiva de sincronización/mecanismo de bloqueoActualmente carecen de cualquier protección contra cambios que se les hagan mientras trabajamos con ellos.
¿Es esto un problema? Sí, las condiciones de carrera siempre son problemáticas y pueden causar todo tipo de comportamientos indefinidos/desagradables bugchecks.
¿Afectará el uso de este payload a la estabilidad de mi exploit? Podría.
Bueno, ¿cuál es la solución? La solución consta de dos partes.
La parte 1 implica adquirir un bloqueo de tipo espera como Pushlocks - nt!PspActiveProcessLock (puntero pushlock) para acceso exclusivo usando nt!ExAcquirePushLockExclusive antes de recorrer la lista de procesos (la entrega normal de APC del kernel debe estar deshabilitada previamente) y nt!ExReleasePushLockExclusive para liberar el bloqueo una vez que hayamos terminado de usar la lista, momento en el cual se debe volver a habilitar la entrega normal de APC del kernel.
Sin embargo, dado que esta variable global no es exportada por el kernel nt, un enfoque mucho más decente y seguro sería usar la API nt!ZwQuerySystemInformation con SYSTEM_INFORMATION_CLASS == SystemProcessInformation para encontrar el PID a partir del ImageName y nt!PsLookupProcessByProcessId para obtener el VA de nt!_EPROCESS a partir del PID.
Sin embargo, si tienes curiosidad sobre cómo hace el kernel lo primero, te pediría que mires nt!PsGetNextProcess en un desensamblador.
La parte 2 implica referenciar objetos de forma segura usando la familia de APIs nt!ObReferenceObject para aumentar el contador de referencias del objeto de proceso, de modo que no pueda eliminarse hasta que lo decrementemos explícitamente al final, una vez que hayamos terminado con él, usando nt!ObDereferenceObject.
Ten en cuenta que aumentar el contador de referencias manualmente es redundante, ya que una llamada a nt!PsLookupProcessByProcessId, si tiene éxito, lo hace por nosotros.
Implementar estas correcciones, sin embargo, requeriría encontrar la dirección base de ntoskrnl.exe y resolver los símbolos dentro de él recorriendo la EAT para encontrar los punteros de función usando algún algoritmo de hash de cadenas, todo lo cual aumentaría drásticamente el tamaño del payload.
Puede que decida implementarlo algún día o simplemente escribir en C y saturar la salida del compilador :)
Gracias a Dmytro Oleksiuk(@d_olex) y Paul L.(@am0nsec) por señalar los errores y también por sugerir la solución.