Token-stealing payloads
TL;DR: Walk the kernel
EPROCESSlinked list to find PID 4 (System), copy itsTokenfield into the current process’sEPROCESS, then return to user mode — the textbook Windows kernel local-privilege-escalation payload.
What it is
Every Windows process is represented by an EPROCESS structure in non-paged kernel pool. One of its fields, Token, points to a _TOKEN describing the process’s security context (SIDs, privileges, integrity level). Replacing the current process’s token pointer with System’s instantly grants the running thread System privileges — no code execution, no signature checks, purely a data-only edit. This makes token-stealing the dominant LPE payload because HVCI (hvci-vbs) cannot block writes to non-code pages.
Preconditions / where it applies
- Arbitrary kernel write primitive (often paired with type-confusion-kernel, use-after-free-kernel, or arbitrary-read-write-primitives)
- Knowledge of
EPROCESSfield offsets for the target Windows build — they change with every major release (see_EPROCESSin WinDbg withdt nt!_EPROCESS) - Either kernel-mode execution (older exploits) or pure data write (modern HVCI-resilient exploits)
Technique
Walk-and-swap in pseudo-C (offsets are build-specific):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
PEPROCESS current = (PEPROCESS) PsGetCurrentProcess();
PEPROCESS p = current;
do {
ULONG pid = *(ULONG*)((BYTE*)p + EPROCESS_PID_OFFSET);
if (pid == 4) { // PID 4 = System
ULONG_PTR sys_token = *(ULONG_PTR*)((BYTE*)p + EPROCESS_TOKEN_OFFSET);
sys_token &= ~0xF; // strip _EX_FAST_REF low bits
*(ULONG_PTR*)((BYTE*)current + EPROCESS_TOKEN_OFFSET) = sys_token;
break;
}
// ActiveProcessLinks is a LIST_ENTRY embedded in EPROCESS
p = (PEPROCESS)(*(ULONG_PTR*)((BYTE*)p + EPROCESS_LINKS_OFFSET) - EPROCESS_LINKS_OFFSET);
} while (p != current);
Subtleties:
Tokenis anEX_FAST_REF— the bottom bits encode a reference count and must be masked off- Newer Windows builds (1809+) introduced
EPROCESS.Token.RefCntquirks; some exploits increment the System token refcount before swapping to be safe - After the swap, return cleanly — usually by restoring the bug’s hijacked control flow or by sleeping in kernel until natural return
- From a pure write primitive (no kernel RIP control), iterate via repeated arbitrary reads to locate System’s EPROCESS through
PsInitialSystemProcess(KASLR-leaked, see kaslr-bypass), then issue the single token write
Post-payload, user-mode shell calling CreateProcessW("cmd.exe") inherits System.
Detection and defence
- HVCI does not block data-only token writes — it blocks code injection, not field overwrites
- Defender / kernel ETW: token-mismatch checks where
Process.Token.User != Process.PrimaryToken.Userpost-hoc - PPL / Protected Processes shield specific tokens (LSASS, csrss) from being source-of-truth
- Future direction: token integrity checks (Token Trust Levels) and
NoChildProcmitigation policies; some EDRs scanEPROCESS.Tokenperiodically
References
- Connor McGarr: Windows kernel exploitation — recurring token-stealing case studies on current builds
- Saar Amar: Data-only kernel exploits — research on payloads that survive HVCI
- HEVD walkthroughs — practice driver with token-steal exercises
Related: arbitrary-read-write-primitives, hvci-vbs, kaslr-bypass, windows-kernel-architecture