PreviousMode (KTHREAD.PreviousMode) overwrite primitive¶
Saldıran thread'in
KTHREAD'indeki tek byte'lıkPreviousModealanını sıfırlamak,NtReadVirtualMemory/NtWriteVirtualMemory'ninProbeForRead/ProbeForWritecheck'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'u0olan bir usermode thread anormaldir. - Kernel adreslerini hedefleyen
NtReadVirtualMemory/NtWriteVirtualMemoryçağrılarını izle.
Mitigation¶
- Microsoft zamanla
PreviousModekullanı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.