SROP + mprotect¶
Bir signal frame forge et ki tek bir
rt_sigreturn,mprotect(veyammap) syscall numarasını ve tüm argümanlarını tek seferde yüklesin, bir bellek bölgesini RWX'e çevirsin, sonra oraya yerleştirilen shellcode'a jump et.
Mechanism¶
Invariant
rt_sigreturn, tüm CPU context'ini mevcut rsp'de duran bir sigcontext/ucontext yapısından geri yükler. Kernel bu frame'i authenticate etmez — byte'ların kendi signal-delivery path'i tarafından yazıldığına güvenir. SROP o güveni suistimal eder: forge edilmiş bir frame, bir gadget'ın her general-purpose register'ı (rax, rdi, rsi, rdx, ...) artı rip ve rsp'yi tek adımda set etmesini sağlar (bkz. sigreturn-oriented-programming).
+mprotect varyantı o primitive'i NX'e yöneltir. Bir execve frame'i forge etmek yerine, attacker bir mprotect(addr, len, PROT_READ|PROT_WRITE|PROT_EXEC) frame'i forge eder. sigreturn frame'i rax, rdi, rsi ve rdx'i aynı anda set ettiğinden, bir trigger attacker'ın kontrol ettiği bir page'i RWX'e çevirir. Control flow sonra daha önce o page'e yazılmış shellcode'a transfer olur — küçük veya static bir binary'de kıt olabilen argüman başına pop gadget'larına ihtiyaç duymadan no-execute/DEP sınırını etkisiz kılar.
Walkthrough¶
Herkese açık CTF writeup'larını (örneğin TAMUctf, DarkCTF) ve pwntools SigreturnFrame API'sini takip eden yüksek seviye yeniden üretim. Zincir iki aşamalıdır:
- Trigger —
rax = 15(__NR_rt_sigreturn) elde et ve birsyscallinstruction'ına ulaş. Yaygın bir path: kontrol ettiğiniz birread()syscall'ı 15 byte okuyarak (böylecerax=15set eder) veya 15 yükleyen birpop rax; retgadget'ı. - Restore — trigger'ın altındaki forge edilmiş frame,
mprotectiçin context'i yeniden yükler;ripbirsyscallgadget'ına geri yöneltilir ki bir sonraki instruction tam damprotectçağrısı olsun.rsp, execution seçilen bir bölgeye devam edecek şekilde set edilir.
from pwn import *
context.arch = 'amd64'
frame = SigreturnFrame()
frame.rax = constants.SYS_mprotect # 10
frame.rdi = page_base # page-aligned region to mark RWX
frame.rsi = 0x1000 # length
frame.rdx = 7 # PROT_READ|WRITE|EXEC
frame.rip = syscall_ret # the mprotect call itself
frame.rsp = pivot_into_shellcode # continue here afterward
payload = b'A'*offset
payload += p64(pop_rax_15) + p64(syscall_ret) # stage 1: rax=15, trigger sigreturn
payload += bytes(frame) # stage 2: forged mprotect context
Neden execve yerine mprotect
execve("/bin/sh"), bilinen bir adreste yazılabilir bir "/bin/sh" string'ine ihtiyaç duyar ve doğrudan bir shell verir. mprotect varyantı, attacker'ın zaten büyük, kontrol ettiği bir buffer'ı olduğunda (örneğin daha önceki bir read ile doldurulmuş bir bss/stack bölgesi) ve sadece bir shell spawn etmek yerine arbitrary shellcode çalıştırmak istediğinde tercih edilir — hedef syscall seti filtrelendiğinde veya daha büyük bir payload hazırlarken yaygındır.
mprotect syscall'u return ettikten sonra bölge RWX'tir; zincir yeniden girer (genellikle rsp'yi pivot ederek veya bilinen bir entry'ye return ederek) ki oraya zaten hazırlanmış byte'lar code olarak çalışsın.
Detection¶
- Kernel'in signal trampoline'i olmayan bir return address'inden çağrılan
rt_sigreturn(syscall 15) anormaldir; onustrace/auditdsyscall auditing'i veya bir eBPF probe ile trace et. - Yazılabilir bir bölgede (W^X ihlali)
PROT_EXECisteyen, özellikle stack veya heap'te, hemen ardından o bölgeye bir control transfer'i gelen birmprotect/mmapçağrısı — güçlü bir EDR sinyali. - Stack'te tam bir register reload'unu besleyen
sigcontextboyutunda bir blob (~248 byte), memory forensics'te olağandışı bir stack layout olarak görünür.
Mitigation¶
rt_sigreturn,mprotectvemmap'i reddeden veya kısıtlayan seccomp-BPF allow-list'leri zinciri kırar;mprotect(PROT_EXEC)üzerindekiSECCOMP_RET_KILLetkilidir.- W^X zorunlu kıl ki hiçbir mapping aynı anda hem yazılabilir hem executable olmasın; SELinux
execmem/execmodpolitikası yazılabilir belleği executable yapmayı reddeder. - Genel anti-corruption savunmaları ilk stack kontrolü için çıtayı yükseltir: stack canary'leri, ASLR ve NX.