Skip to content

VirtualProtect/mprotect via ROP (DEP disable)

Bir ROP chain'i ile mprotect (Linux) ya da VirtualProtect (Windows) çağırıp attacker-controlled bir region'ı executable'a çevirmek, ardından artık çalıştırılabilir shellcode'a jump etmek — NX/DEP'i yenmek.

Mechanism

NX/DEP, data page'lerini (stack, heap) non-executable işaretler, böylece enjekte edilen shellcode doğrudan çalışamaz. Ama page izinlerine karar veren kodun kendisi — mprotect/VirtualProtect — meşru, executable ve erişilebilirdir. Return-oriented programming, mevcut gadget'ları zincirleyerek argümanları hazırlar ve o function'ı çağırır; OS'ten shellcode'un page'ini executable yapmasını ister. Page bir kez RWX olduğunda, kontrol oraya transfer edilir ve native kod çalışır. Chain bir one-shot bootstrap'tır: ROP'un yalnızca bir function çağırıp pivot edecek kadar hayatta kalması yeterlidir.

Note

Argüman geçirme ayrıntısı işin tüm hilesidir. Linux x86-64'te SysV ABI argümanları rdi, rsi, rdx'e koyar; mprotect(void *addr, size_t len, int prot) için rdi=page-aligned addr, rsi=len, rdx=7 (PROT_READ|WRITE|EXEC) gerekir. Windows x64'te fastcall ABI rcx, rdx, r8, r9 kullanır; VirtualProtect ek olarak lpflOldProtect'in (4. argüman) writable bir dword'ü göstermesini ister, yoksa call başarısız olur.

Walkthrough

mprotect page-aligned bir adres ister ve PROT_READ(1), PROT_WRITE(2), PROT_EXEC(4) kabul eder — yani prot = 7 RWX'tir. Kavramsal x86-64 chain (adresler örnektir):

# goal: mprotect(buf & ~0xfff, 0x1000, 7); jmp buf
pop rdi ; ret          -> 0x404000        # page-aligned target page
pop rsi ; ret          -> 0x1000          # length
pop rdx ; ret          -> 0x7             # PROT_READ|PROT_WRITE|PROT_EXEC
mprotect               -> &mprotect       # call it
<addr of shellcode in that page>          # return into now-RWX shellcode

pwntools ile inşa etmek (örnek amaçlı, silah haline getirilmemiş):

from pwn import *
rop = ROP(elf)
rop.mprotect(page_base, 0x1000, 7)   # marks the page RWX
rop.raw(shellcode_addr)              # return into the RWX page
payload = b"A"*offset + rop.chain()

Windows'ta eşdeğeri VirtualProtect(lpAddress, dwSize, PAGE_EXECUTE_READWRITE /*0x40*/, &oldProtect) çağırır; 4. parametre geçerli writable bir değişkeni göstermek zorunda olduğundan, chain lpflOldProtect için (çoğu zaman .data'da) bir scratch dword reserve eder ve API'ye göre, region'ı çalıştırmadan önce cache coherency için FlushInstructionCache ile devam etmelidir.

mprotect(2) / VirtualProtect signature'ları
/* Linux */
int  mprotect(void *addr, size_t size, int prot);  /* addr page-aligned; prot=7 -> RWX */
/* Windows */
BOOL VirtualProtect(LPVOID lpAddress, SIZE_T dwSize,
                    DWORD flNewProtect /*0x40 PAGE_EXECUTE_READWRITE*/,
                    PDWORD lpflOldProtect /*must be writable*/);

Mitigation

Function-call ROP chain'leri CET shadow stack'ler (return-address bütünlüğü) ve IBT/endbr (indirect-branch landing pad'leri) ile körelir. W^X policy artı sertleştirilmiş loader'lar ve SELinux/grsecurity mprotect kısıtlamaları (writable mapping'lerde PROT_EXEC reddi), page flip'i engeller. ASLR, mprotect/VirtualProtect'i ve gadget'ları bulmak için önceden bir address-leak yapmayı zorunlu kılar.

References