
Shellcode in modalità kernel per Windows x64 realizzato artigianalmente per il furto di token
Shellcode kernel-mode x64 per Windows realizzato artigianalmente per sostituire il token di accesso primario del processo in esecuzione con il token di processo SYSTEM per Elevation of Privilege (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 19044I prerequisiti per compilare questo progetto sono:
Visual Studio 2019(any edition will do fine)Windows 10 SDK, version 2004Windows 10 WDK, version 2004Python3Va notato che puoi cavartela avendo solo un assembler (questo progetto usa MASM) perché tecnicamente è tutto ciò che serve.
Dopo aver installato quanto sopra, dovrebbe essere sufficiente aprire la soluzione con Visual Studio e compilare per la destinazione x64.
Dopo una compilazione riuscita, i binari si trovano nella directory Bin sotto l'apposita sottodirectory per architettura.
In alternativa, puoi scaricare shellcode indipendente dalla posizione pronto per la distribuzione da Releases.
Per favore, NON tentare di distribuire il payload su una macchina su cui fai affidamento per lavorare se non sei sicuro di come funziona.
Fai riferimento alla documentazione Microsoft per qualsiasi informazione aggiuntiva.
Per scopi di test, consiglio vivamente di usare flare-kscldr per distribuire lo shellcode kernel-mode su una VM di test e la guida di CodeMachine per la configurazione del sistema per lo sviluppo del kernel e il debug per configurare una VM Hyper-V Guest con pieno supporto al debug del kernel.
Opzionalmente, puoi anche considerare di automatizzare il processo con kdbg-driver-vagrant per avviare rapidamente una VM di test con debug del kernel completo usando Vagrant.

Come mi è stato fatto notare da Dmytro Oleksiuk(@d_olex), ci sono alcune race condition non proprio sottili nel codice, in particolare relative a:
nt!_EPROCESS collegate tra loro tramite lista doppiamente linkata circolare senza usare una sorta di primitiva di sincronizzazione/meccanismo di lockAttualmente mancano di qualsiasi protezione contro le modifiche apportate mentre ci stiamo lavorando.
È un problema? Sì, le race condition sono sempre problematiche e possono causare ogni sorta di comportamenti indefiniti/spiacevoli bugcheck.
L'uso di questo payload influirà sulla stabilità del mio exploit? Potrebbe.
E qual è la soluzione? La soluzione è in due passaggi.
La parte 1 prevede l'acquisizione di un lock di tipo wait, come i Pushlock - nt!PspActiveProcessLock (puntatore pushlock) per l'accesso esclusivo usando nt!ExAcquirePushLockExclusive, prima di attraversare la lista dei processi (la consegna normale delle APC del kernel deve essere disabilitata in anticipo), e nt!ExReleasePushLockExclusive per rilasciare il lock una volta terminato l'uso della lista, punto in cui la consegna normale delle APC del kernel dovrebbe essere riabilitata.
Tuttavia, poiché questa variabile globale non è esportata dal kernel nt, un approccio molto più decoroso e sicuro sarebbe usare l'API nt!ZwQuerySystemInformation con SYSTEM_INFORMATION_CLASS == SystemProcessInformation per trovare il PID dall'ImageName e nt!PsLookupProcessByProcessId per ottenere nt!_EPROCESS VA dal PID.
Se però sei curioso di sapere come il kernel fa la prima cosa, ti chiederei di guardare nt!PsGetNextProcess in un disassembler.
La parte 2 prevede di riferire in modo sicuro gli oggetti usando la famiglia di API nt!ObReferenceObject per incrementare il conteggio dei riferimenti sull'oggetto processo, così che non possa essere eliminato finché non lo decrementiamo esplicitamente alla fine, una volta terminato con esso, usando nt!ObDereferenceObject.
Nota che incrementare manualmente il conteggio dei riferimenti è ridondante, poiché una chiamata a nt!PsLookupProcessByProcessId, se andata a buon fine, lo fa per noi.
Implementare queste correzioni, tuttavia, richiederebbe trovare l'indirizzo di base di ntoskrnl.exe e risolvere i simboli al suo interno attraversando la EAT per trovare i puntatori alle funzioni usando un algoritmo di hashing delle stringhe, il che aumenterebbe drasticamente le dimensioni del payload.
Potrei decidere di implementarlo un giorno o semplicemente scrivere in C e riempire l'output del compilatore :)
Ringrazio Dmytro Oleksiuk(@d_olex) e Paul L.(@am0nsec) per aver segnalato gli errori e anche per aver suggerito la soluzione.