Skip to content

setcontext magic gadget pivot

glibc'nin setcontext()'i, pointer'ını attacker'ın zaten kontrol ettiği bir ucontext_t'den tüm general-purpose register file'ını — rsp ve rip dahil — yeniden yükler; böylece tek bir hijack edilmiş call'u tam bir register-control ve stack-pivot "magic gadget"ına çevirir.

Mechanism

Suistimal edilen invariant: setcontext, güvenilmeyen bir pointer'la beslenen, güvenilir ve eksiksiz bir register-restore rutinidir

setcontext(), userspace context switching'i implemente etmek için vardır: bir ucontext_t'ye pointer alır ve saved machine state'i ondan geri yükler, saved instruction pointer'a kontrolü aktararak biter. Kavramsal olarak, her callee/argüman register'ını yapının sabit bir offset'inden yükleyen, saved stack pointer'dan yeni bir rsp yükleyen, saved rip'i push'layan ve ona ret'leyen elle yazılmış bir assembly rutinidir.

Bug class'ı setcontext'in kendisinde değildir — bir function pointer'ı (bir FILE vtable slot'u, __free_hook, __malloc_hook, bir exit handler, bir GOT entry'si) zaten corrupt etmiş bir attacker'ın, okuduğu pointer argümanını da kontrol ederken control flow'u setcontext'in içine yöneltebilmesidir. O pointer'ı tutan register glibc sürümüne göre değişir:

  • eski glibc: context pointer rdi'den alınır (entry point fonksiyonun başında).
  • glibc ≥ 2.29: yaygın kullanılan entry setcontext+61'dir; context pointer'ı rdx'te bekler (alias'lar: setcontext+61, setcontext+53, "magic gadget").

Rutin kontrollü yapıdan önce rsp'yi sonra rip'i yüklediğinden, tek bir yönlendirilmiş call attacker'a hem pivot edilmiş bir stack hem de arbitrary bir ilk rip verir — yani "bir indirect call ve bir register kontrol ediyorum"u "tüm register file'ı ve stack'i kontrol ediyorum"a çevirir, bir ROP zinciri için fırlatma rampası.

Walkthrough

Herkese açık House-of-Apple / angelboy tarzı FILE writeup'larını takip eden yüksek seviye yeniden üretim. Attacker'ın bir arbitrary write'ı ve bir indirect call ile biten kontrol edilebilir bir trigger'ı var.

  1. Sahte bir ucontext_t hazırla, attacker'ın kontrol ettiği bellekte (genellikle forge edilmiş bir FILE yapısıyla aynı heap bölgesi). setcontext'in rsp için (onu sahte bir stack / ROP zincirine yönelt) ve rip için (onu ilk gadget'a yönelt, örneğin bir mov rsp pivot'u veya syscall taşıyan gadget) okuduğu offset'leri doldur. Diğer register offset'leri syscall argümanlarını (rdi, rsi, rdx) hazırlamak için set edilir.

  2. Argüman register'ını ayarla. glibc ≥ 2.29'da setcontext+61 entry'si context pointer'ını rdx'ten okur — yani hijack'ten hemen önce rdx sahte context'e işaret etmelidir. Standart (*fp->vtable->slot)(fp) dispatch'inde fp rdi'ye geçer, rdx değil; dolayısıyla rdx iki yoldan biriyle ayarlanır: (a) hijack edilen call'dan önce çalışan, rdx'e kontrollü bir pointer yükleyen bir register-setting (mov rdx, ... / pop rdx) gadget'ı, ya da (b) belirli House-of-Apple tarzı IO variant'ları — burada zincir, controlled argümanı rdx'te taşıyan spesifik bir dolaylı call site'ına (örn. wide-data allocation path'lerindeki call [reg+off] formları) ulaşır. Hangi yolun geçerli olduğu glibc sürümüne ve seçilen IO path'ine bağlıdır ve hedef binary'de doğrulanmalıdır.

  3. Kontrolü setcontext'e yönlendir. Seçilen function pointer'ı (vtable slot / hook / handler) &setcontext+61 ile overwrite et. Program bir sonraki sefer o path'i tetiklediğinde, setcontext register'ları yeniden yükler ve şimdi pivot edilmiş stack üzerinde duran attacker'ın rip'ine ret'ler.

hijacked call --> setcontext+61 --> load rsp/rip/rdi.. from *rdx --> ret
                              (attacker-chosen registers + pivoted stack)
Neden bir one-gadget'a tercih edilir

Bir one-gadget, call site'ta sağlanması gereken belirli register/bellek constraint'lerine ihtiyaç duyar ve bunlar sıklıkla başarısız olur. setcontext constraint problemini tamamen atlatır: attacker, call-sonrası register state'ini kontrollü yapıdan tanımlar, dolayısıyla tek bir hijack edilmiş call'u güvenilir şekilde tam bir stack pivot'a ve ROP'a köprüler.

Detection

  • Control-flow anomalisi: bir indirect call site'ın (FILE vtable dispatch'i, bir libc hook'u, bir exit handler) normal caller'ları yerine setcontext'in ortasına (setcontext+61/+53) transfer etmesi son derece anormaldir. CFI/CET shadow-stack ihlalleri veya __libc_message abort'ları görünebilir.
  • Register/stack telemetrisi: setcontext-sonrası execution'da rsp'nin thread stack yerine heap'e veya başka bir yazılabilir data bölgesine (pivot edilmiş bir stack) işaret etmesi güçlü bir sinyaldir; core'larda/EDR backtrace'lerinde yakala.
  • Hook/vtable integrity: __free_hook/__malloc_hook veya FILE vtable pointer'larının non-null/libc-aralığı-dışı hale gelmesini izlemek, pivot'u mümkün kılan öncül overwrite'ı yakalar.
  • Upstream memory-corruption primitive'ini setcontext'e ulaşmadan yakalamak için hedefleri CI'da ASan/CFI altında çalıştır.

Mitigation

  • Intel CET shadow stack, kontrol edilen rip'e ret adımını kırar: modern setcontext shadow stack'i valide eder/kullanır, dolayısıyla forge edilmiş bir return hedefi bir control-protection fault'u tetikler.
  • glibc, __free_hook/__malloc_hook'u kaldırdı (2.34) ve FILE vtable validation'ı (_IO_vtable_check) ekledi; bu da setcontext'e inen bir hijack edilmiş call'a ulaşmanın yaygın yollarını daralttı.
  • glibc'yi güncel tut; CET (-fcf-protection) ile derle ve mümkün olan yerde donanım shadow stack'i etkinleştir.
  • Asıl çözüm upstream'dedir: control flow'un yönlendirilmesine olanak tanıyan arbitrary write / pointer overwrite'ı ortadan kaldır.

References