Access token theft via arbitrary kernel R/W (hasherezade)¶
SYSTEM process'e (PID 4) kadar
_EPROCESSlistesini gez, onunToken'ını oku ve kendi process'ininTokenpointer'ının üzerine yaz — unprivileged bir process'e SYSTEM security context'i veren data-only bir swap.
Mechanism¶
Tek bir pointer'ı kopyalamak neden tüm process'i escalate eder
Windows'ta her process bir nt!_EPROCESS structure'ı ile tanımlanır
ve security context'ine tek bir field üzerinden ulaşılır — Token —
ki bu bir nt!_TOKEN'a işaret eden bir _EX_FAST_REF'tir. Tüm access
check'leri (file, object, privilege, impersonation) bu token'a danışır.
Burada suistimal edilen invariant şu: kernel, _EPROCESS içinde
bulduğu Token pointer'ına ne olursa olsun güvenir — bir _EPROCESS
ile "kendi" token'ı arasında process başına cryptographic bir bağ
yoktur. Yani _EPROCESS.Token field'ını SYSTEM process'inin token
object'ine işaret ettirirsen, sonraki her access check senin
process'ini SYSTEM'in context'ine göre değerlendirir. Yeni privilege
yaratmadın; var olan, tam yetkili bir token object'i ödünç aldın.
SYSTEM process'i (PID 4) stabil, güvenilir bir kaynaktır: her zaman
var olur ve her zaman o her şeye gücü yeten SYSTEM token'ını tutar.
Onu bulmak için _EPROCESS structure'larının ActiveProcessLinks
LIST_ENTRY ile birbirine bağlı olduğu — dairesel, çift yönlü bağlı
bir liste oluşturduğu — gerçeğinden faydalanırsın. Flink'i takip
edip UniqueProcessId'yi 4 ile karşılaştırarak SYSTEM _EPROCESS'e
ulaşırsın.
Bir incelik: Token bir _EX_FAST_REF olduğundan, low bit'ler adres
bit'i değil bir reference count kodlar. Pointer değerini okurken
bunları mask'lemen gerekir; yazarken de mask'lenmiş SYSTEM token
değerini kendi Token field'ına yazarsın (stabilite için isteğe bağlı
olarak kendi orijinal refcount bit'lerini geri OR'layabilirsin). Bu saf
bir data-only technique'tir: kernel'de kod çalıştırmak gerekmez,
yalnızca bir read primitive (listeyi gezip token'ı çekmek için) ve bir
write primitive (kendi Token'ının üzerine yazmak için) yeter.
Walkthrough¶
Buraya bir bug'dan elde edilmiş, arbitrary bir kernel read/write
primitive ile geliyorsun. Aşağıdaki offset'ler build'e özgüdür — hedef
kernel için bir debugger'da (dt nt!_EPROCESS) doğrula. hasherezade'nin
orijinal yazısı 32-bit durumu gösterir (Token +0xF8'de, _EX_FAST_REF'in
low 3 bit'ini mask'leyerek).
1. Anchor on an _EPROCESS¶
Mevcut _EPROCESS base'ini çöz. Bir thread context'inden klasik zincir,
processor block'tan aşağı mevcut process'e iner:
_KPROCESS is the first member of _EPROCESS, so its base address is
the _EPROCESS base.
2. Walk ActiveProcessLinks to PID 4¶
// pseudo-C over a KernelRead(addr) primitive; offsets are build-specific
// (example offsets shown for one x64 build)
#define OFF_UNIQUE_PID 0x2e0 // _EPROCESS.UniqueProcessId
#define OFF_APLINKS 0x2e8 // _EPROCESS.ActiveProcessLinks (Flink)
#define OFF_TOKEN 0x4b8 // _EPROCESS.Token (_EX_FAST_REF)
ULONG64 eproc = current_eprocess; // from step 1
ULONG64 sys = 0;
ULONG64 start = eproc;
do {
ULONG64 pid = KernelRead(eproc + OFF_UNIQUE_PID);
if (pid == 4) { sys = eproc; break; } // SYSTEM
// Flink points at the NEXT entry's ActiveProcessLinks; back up to its base
ULONG64 flink = KernelRead(eproc + OFF_APLINKS);
eproc = flink - OFF_APLINKS;
} while (eproc != start);
3. Read SYSTEM's token (mask the _EX_FAST_REF)¶
(In the 32-bit original the mask is & 0xFFFFFFF8, clearing 3 low
bits.)
4. Overwrite our own token¶
ULONG64 my_token_field = KernelRead(current_eprocess + OFF_TOKEN);
ULONG64 refcnt = my_token_field & 0xF; // keep our refcount
KernelWrite(current_eprocess + OFF_TOKEN, sys_token | refcnt);
5. Confirm SYSTEM¶
No new process is needed — the current process now evaluates as SYSTEM. Spawning a shell from it yields a SYSTEM shell:
WinDbg dry-run of the same idea¶
You can rehearse the read/write by hand before scripting it:
kd> !process 0 0 System
PROCESS ffff... SessionId: none Cid: 0004 ...
kd> ? poi(ffff<SYSTEM_EPROCESS>+0x4b8) & 0xFFFFFFFFFFFFFFF0 ; SYSTEM token
kd> eq <CUR_EPROCESS>+0x4b8 <that value> ; steal it
This is the pointer-swap sibling of the data-only privilege bitmap edit
(see _TOKEN privilege bitmap overwrite): there you enable already-present
privileges in your own token; here you replace the token wholesale with
SYSTEM's. The Linux analogue is overwriting the cred struct — see
commit_creds and
cred struct overwrite.
Detection¶
- A process whose
_EPROCESS.Tokenpoints at the same object as the SYSTEM process, despite a non-SYSTEM origin, is a clear tampering signal; EDR/hypervisor can reconcile token ownership. - Sudden SYSTEM-level actions from a process that started unprivileged, with no legitimate impersonation/logon, indicates a token swap.
Mitigation¶
- Remove the underlying arbitrary kernel R/W bug; VBS/HVCI and kernel CFG raise the cost of obtaining one.
- Hypervisor-enforced token protection (e.g. monitoring writes to
_EPROCESS.Token) can catch the swap out of band.