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:
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:
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
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_VDSOsabit-adres mapping'ini devre dışı bırak ki page ASLR ile birlikte hareket etsin ve birAT_SYSINFO_EHDRleak'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.