Skip to content

SafeSEH

A compile/link-time Windows defense that embeds a read-only table of an image's legitimate exception-handler entry points so the OS can reject a corrupted SEH handler pointer before dispatching to it.

Mechanism

Note

32-bit x86 Windows'ta, bir thread'in structured-exception-handler (SEH) kayıtları stack üzerinde yaşar: her kayıt bir next pointer ve bir handler function pointer tutar. Bir stack buffer overflow bir kaydı overwrite edebilir ve sonra bir exception raise edildiğinde dispatcher zinciri walk eder ve saldırgan-kontrollü handler pointer'ını çağırır — cookie kontrol edilmeden önce tetiklenerek bir stack cookie'yi atlatan klasik SEH-overwrite kod yürütmesi.

SafeSEH bunu, linker'ın (/SAFESEH) image'deki her geçerli exception-handler entry point'inin bir tablosunu üretmesini sağlayarak kapatır; bu, PE load configuration directory'sine (SEHandlerTable / SEHandlerCount) kaydedilir. Dispatch sırasında OS aday handler'ı bu tabloya karşı kontrol eder; kayıtlı bir handler olmayan bir pointer reddedilir ve asla çağrılmaz. Sınır tutar çünkü meşru handler kümesi, saldırganın runtime'da genişletemeyeceği read-only metadata'da build time'da sabittir.

SafeSEH yalnızca x86'dır: x64/ARM'da exception handler'lar stack-walk edilen pointer'lar yerine table-based unwind data (PDATA/XDATA) ile tanımlanır, bu yüzden SEH-overwrite sınıfı aynı şekilde geçerli olmaz.

Walkthrough

Kavramsal; Microsoft'un linker dokümanlarından ve MSRC'nin SEHOP yazısından.

  1. Flag ile build et. /SAFESEH (ya da elle yazılmış MASM handler'ları için .SAFESEH) linker'a handler entry point'lerini toplamasını söyler:
link /SAFESEH app.obj         # emit SEHandlerTable in the load config

Image sonra kavramsal olarak __safe_se_handler_table[] (base) ve __safe_se_handler_count'a eşdeğer iki linker-üretimi sembol export eder.

  1. Dispatch'te. Bir exception tetiklendiğinde, dispatcher handler pointer'ını stack üzerindeki kayıttan alır ve onun image'in safe-handler tablosundaki address'lerden biri olduğunu doğrular.

  2. Saldırganın overwrite'ı başarısız olur. Overwrite edilmiş bir handler artık bir stack buffer'a, heap spray'e ya da arbitrary code'a işaret eder — kayıtlı bir handler'a değil — bu yüzden kontrol başarısız olur ve handler asla çağrılmaz.

Warning

Koruma yalnızca kapsadığı image kadar eksiksizdir. Linker mevcut bir binary'yi sonradan işaretleyemez; metadata build time'da eklenmelidir. Process'teki herhangi bir modül SafeSEH olmadan build edildiyse (legacy DLL, eski toolchain), onun handler'ları bir tabloda değildir ve enforcement politikasına bağlı olarak o modülün içine — ya da tüm image'lerin dışına — işaret eden bir handler hâlâ uygun bir hedef olabilir. Bu açık tam olarak SEHOP'un (runtime chain-integrity doğrulaması) tamamlayıcı, recompile gerektirmeyen bir savunma olarak getirilmesinin nedenidir.

Detection

SafeSEH'in kendisi enforcement bir handler'ı bloklayana kadar sessizdir; pratik sinyaller posture audit ve crash telemetry'dir. /SAFESEH olmadan build edilmiş modülleri bulmak için binary'leri safe-SEH tablosunun varlığı açısından denetleyin (PE load-config'in sıfır olmayan bir SEHandlerCount'u vardır). Runtime'da, bir SEH-overwrite denemesi tipik olarak exception-dispatch yolunda bir access-violation içeren bir process crash / Windows Error Reporting olayı ya da corrupt edilmiş bir exception chain üzerinde bir EDR exploit-guard uyarısı verir. Aksi halde hardened olan process'lerde tablodan yoksun beklenmedik legacy 32-bit modülleri avlayın.

Mitigation

  • Tüm 32-bit x86 modülleri için /SAFESEH ile compile/link edin ve MASM handler'larını kaydetmek için .SAFESEH kullanın; /SAFESEH:NO'dan kaçının.
  • Hassas process'lerden SafeSEH olmayan modülleri eleyin; karışık yüklemeler başlıca zayıflıktır.
  • Ek savunmalar katmanlayın: modüllerin nasıl build edildiğinden bağımsız olarak handler chain'ini runtime'da doğrulamak için SEHOP'u etkinleştirin, stack cookie'leri ve DEP/ASLR'yi açık tutun ve table-based unwind modelinin overwrite sınıfını kaldırdığı 64-bit build'leri tercih edin.
  • Bilinen bypass uyarıları: tarihsel bypass'lar tek bir SafeSEH olmayan modülü, herhangi bir image'in dışına yerleştirilmiş handler'ları ya da yolu yeniden etkinleştiren kod üzerinden zincirlemeyi kullandı. SafeSEH, SEH-overwrite primitive'ini daraltır ama heterojen bir process boyunca onu emekliye ayırmaz.

References