Skip to content

PreviousMode (KTHREAD.PreviousMode) overwrite primitive

Saldıran thread'in KTHREAD'indeki tek byte'lık PreviousMode alanını sıfırlamak, NtReadVirtualMemory/NtWriteVirtualMemory'nin ProbeForRead/ProbeForWrite check'lerini atlamasını sağlar; tek bir kontrollü write'ı, ntoskrnl'in kendi API'leri üzerinden temiz arbitrary kernel read/write'a çevirir.

Mechanism

Her Windows thread'i nt!_KTHREAD içinde bir PreviousMode byte'ı taşır (KTHREAD.PreviousMode olarak erişilebilir). Mevcut system call'u kimin başlattığını kaydeder: KernelMode = 0 ya da UserMode = 1.

Note

Trust invariant şudur: user mode'dan girilen bir syscall kendisine verilen her pointer'ı validate etmelidir, çünkü kötü niyetli bir user bir kernel adresi geçirebilir. Bu yüzden NtReadVirtualMemory/NtWriteVirtualMemory ExGetPreviousMode() çağırır ve UserMode döndüğünde buffer'ların user space'te durduğunu doğrulamak için ProbeForRead/ProbeForWrite koşturur. PreviousMode == KernelMode (0) olduğunda kernel, güvenilir bir in-kernel caller varsayar ve probe'ları tamamen atlar. Yani tek bir byte, bu copy primitive'lerinin argümanlarını denetleyip denetlemeyeceğine karar verir. Onu 0'a çevirin ve saldırganın kendi thread'i NtReadVirtualMemory/NtWriteVirtualMemory'ye kernel adresleri geçirebilir; bunlar da iyi test edilmiş ntoskrnl code path'leri kullanarak arbitrary kernel memory'ye/'den kopyalar.

Bu, en temiz Windows kernel R/W tekniklerinden biridir: pool-metadata corruption yok, object-lifetime jonglörlüğü yok ve build'e özgü gereken tek detay PreviousMode offset'idir.

Walkthrough

Şunlara ihtiyacın var: (a) mevcut thread'in KTHREAD'inin bir leak'i (örn. EPROCESS/ETHREAD'i bulmak için NtQuerySystemInformation(SystemExtendedHandleInformation) üzerinden) ve (b) KTHREAD.PreviousMode'a yönelmiş bir write/decrement primitive'i.

// 1. Resolve current thread's KTHREAD and the PreviousMode field address.
PVOID kthread = LeakCurrentKTHREAD();
PVOID pmAddr  = (PUCHAR)kthread + PREVIOUSMODE_OFFSET;  // build-specific

// 2a. Direct write-what-where: write a single 0 byte.
ArbitraryWrite(pmAddr, /*value=*/ 0);

// 2b. OR arbitrary decrement: many gadgets (e.g. ObfDereferenceObject-style
//     RefCnt decrement) subtract 1 at (ptr - 0x30); aim ptr = pmAddr + 0x30
//     to flip PreviousMode from 1 -> 0.

PreviousMode == 0 olduğunda, belgelenmiş syscall'larla tam kernel R/W yap:

// Arbitrary kernel read into a usermode buffer:
NtReadVirtualMemory(GetCurrentProcess(), kernelSrc, userBuf, len, &ret);
// Arbitrary kernel write from a usermode buffer:
NtWriteVirtualMemory(GetCurrentProcess(), kernelDst, userBuf, len, &ret);
Tipik devam: SYSTEM'e token swap

Arbitrary R/W ile EPROCESS.ActiveProcessLinks'i gez, PID 4'ü (System) bul, onun EPROCESS.Token'ını oku ve o token'ı saldırganın EPROCESS.Token'ına yaz. Saldıran process artık SYSTEM'dir. Bkz. EPROCESS token stealing.

Warning

Thread çıkmadan / alakasız syscall'lar yapmadan önce PreviousMode'u 1'e geri döndür — onu 0'da bırakmak process'i kararsızlaştırabilir ve modern Windows build'leri PreviousMode çevresinde integrity check'ler ekler.

Detection

  • EDR/PatchGuard'a komşu check'ler: user context'te çalışırken KTHREAD.PreviousMode'u 0 olan bir usermode thread anormaldir.
  • Kernel adreslerini hedefleyen NtReadVirtualMemory/NtWriteVirtualMemory çağrılarını izle.

Mitigation

  • Microsoft zamanla PreviousMode kullanımını sertleştirdi ve onun tek kapı olmamasını tercih ediyor; HVCI/VBS ve kCFG, ona ulaşmak için gereken ilk write primitive'inin eşiğini yükseltir.
  • Kernel CFG ve read-only/guarded yapılar, arbitrary-write primitive'lerinin KTHREAD'e temiz biçimde ulaşmasını engellemeyi amaçlar.

References