Skip to content

ret2vdso

Execution'ı kernel'in sağladığı vDSO page'ine pivot et — her zaman map'li, executable bir shared object — ve NX'i (zayıf randomize edilmiş config'lerde ASLR'yi de) bypass etmek için ROP gadget'ları ve syscall stub'ları için onu maden gibi tara.

Mechanism

Note

vDSO ("virtual dynamic shared object"), kernel'in her userland process'e map'lediği küçük bir ELF'tir (/proc/<pid>/maps içinde [vdso] olarak görünür); amacı gettimeofday/clock_gettime gibi çağrıları tam bir syscall olmadan hızlandırmaktır. Executable'dır ve gerçek instruction dizileri içerir, yani syscall; ret ve register'a yükleme yapan gadget'lar dahil meşru bir ROP gadget kaynağıdır. Onu kullanışlı yapan invariant: vDSO address space'te her zaman mevcuttur ve base address'i programa stack üzerinden ELF auxiliary vector girişi AT_SYSINFO_EHDR ile export edilir. Yani auxv'yi okuyabiliyorsan (örneğin stack bellek leak'leyerek), binary'ye veya libc'ye ihtiyaç duymadan vDSO base'ini öğrenirsin. Modern kernel'lerde vDSO, ASLR ile per-execution randomize edilir; dolayısıyla pratikte o gadget'lara ulaşmak için bu AT_SYSINFO_EHDR leak'i ön koşuldur. Legacy CONFIG_COMPAT_VDSO seçeneğiyle (modern dağıtımlarda devre dışı bırakılmış eski compat mapping) derlenmiş kernel'lerde ise vDSO sabit bir adreste durur ve o gadget'lar için ASLR doğrudan etkisiz kalır.

Bu durum, ana binary ve libc gadget açısından fakir olduğunda vDSO'yu cazip kılar: syscall primitive'leri içeren, konumu bilinen executable bir code bölgesi ekler.

Walkthrough

1. Çalışan bir process'te vDSO'yu bul:

grep vdso /proc/$(pgrep vuln)/maps
# f7ffc000-f7ffe000 r-xp 00000000 00:00 0    [vdso]

gdb'de aynı bölge info proc mappings (veya pwndbg vmmap) ile gösterilir; [vdso] satırını ara.

2. Page'i bir dosyaya dump et ki onu offline analiz edebilesin:

dd if=/proc/76/mem of=vdso bs=1 skip=$((0xf7ffc000)) count=$((0x2000))

3. ROPgadget ile gadget'ları tara — asıl ödül syscall trampoline'leridir:

ROPgadget --binary vdso | grep 'int 0x80'
# ... : int 0x80
ROPgadget --binary vdso | grep ': syscall'
Bir Fedora 20 vDSO'sunda raporlanan gerçek gadget'lar (voidsecurity)

xor edx, edx ; mov QWORD PTR [rsi+0x8], rax ; ... mov eax, 0xe4 ; syscall
pop rsi ; pop r15 ; pop rbp ; ret
pop rdi ; pop rbp ; ret
Zincirlendiğinde bunlar rdi/rsi'yi yükler, rdx'i sıfırlar ve bir syscall'a ulaşır — NX açıkken execve("/bin/sh", 0, 0) hazırlamak için yeterli.

4. Base'i runtime'da leak et. Stack'e göreli bir leak ile (örneğin stack belleğin bir write'ını tetikleyerek), auxv'yi AT_SYSINFO_EHDR için parse et; o değer canlı vDSO base'idir. Adım 3'teki statik gadget offset'lerini buna ekleyerek runtime gadget adreslerini elde et, sonra onları herhangi bir ROP payload gibi zincirle (bkz. return-oriented-programming ve ret2syscall).

Warning

vDSO küçüktür; gadget seçimi sınırlıdır. Tarihsel olarak daha eski vsyscall page'i sabit 0xffffffffff600000 adresinde dururdu ve doğrudan istismar edilebilirdi, ama modern kernel'ler vsyscall'u emüle eder, yani orada yalnızca onun ABI fonksiyonlarını çağırabilirsin, ham bir syscall; ret'e jump edemezsin. Pratik "her zaman map'li executable" hedef olarak vDSO geçerliliğini korur.

Mitigation

  • Tam vDSO randomization için legacy CONFIG_COMPAT_VDSO sabit-adres mapping'ini devre dışı bırak ki page ASLR ile birlikte hareket etsin ve bir AT_SYSINFO_EHDR leak'i ön koşul haline gelsin.
  • Intel CET shadow stack ve CFI, gadget'lar nerede olursa olsun ret-zincirlemesini kırar.
  • Seccomp filter'ları, vDSO ile hazırlanmış bir execve'nin hangi syscall'lara ulaşabileceğini kısıtlar.

References