House of Husk¶
glibc'nin
printfcustom-specifier dispatch table'larını (__printf_function_table/__printf_arginfo_table) hijack et; böylece seçtiğin bir format karakteriyle yapılan sonraki herhangi birprintf-ailesi çağrısı bir attacker function pointer'a sıçrar.
Mechanism¶
Suistimal edilen invariant
glibc, programların custom printf conversion karakterleri için handler
register etmesine register_printf_function /
__register_printf_specifier ile izin verir. Bu registry iki libc global'inde
yaşar: __printf_function_table (output handler'ları) ve
__printf_arginfo_table (arg-parse eden handler'lar). vfprintf/__vfprintf_internal
fonksiyonunun başında tek bir check dispatch path'ini belirler:
if (__glibc_unlikely (__printf_function_table != NULL
|| __printf_modifier_table != NULL
|| __printf_va_arg_table != NULL))
goto do_positional; /* slow path -> printf_positional() */
Not: glibc'nin tam check'i ayrıca __printf_modifier_table ve
__printf_va_arg_table'ı da kapsar, ama exploit yalnızca iki table'ı
kullanır: __printf_function_table'ı slow path'i tetiklemek için ve
__printf_arginfo_table'ı handler dispatch'ini hijack etmek için. Diğer
ikisi burada yalnızca defensive bir OR-check olarak görünür.
Kritik nokta: iki table'dan birindeki non-NULL bir değer, "bir handler
register edilmiş" gibi muamele görür ve bunun gerçek bir handler table'a
işaret edip etmediğine dair hiçbir validation yoktur. Slow path'te bir
conversion karakteri c parse edilirken table karakterin kendisiyle index'lenir
ve üzerinden çağrı yapılır. Yani bir attacker __printf_arginfo_table'a fake bir
table base yazarsa (ve slow path'i zorlamak için __printf_function_table'a
non-NULL koyarsa) ve c-inci slot'a bir pointer yerleştirirse, sonraki
printf("%c"...) attacker kodunu çağırır. Attack anında, target'ın zaten
yaptığı bir tane dışında ekstra printf gerekmez.
Walkthrough¶
glibc < 2.28'e "kolayca" geniş ölçüde uygulanır ve aynı yüzeyle modern
sürümlere de (writeup'lar 2.35, 2.37 ve 2.41'i doğruluyor — Xprintf_buffer
refactor'ı table'ları kaldırmadı). Prerequisite'ler: bir libc leak ve libc
data'ya bir heap-pointer'ı yazabilen bir arbitrary-write primitive.
-
Delivery write'ı seç. House of Husk klasik olarak bir large-bin-attack (veya unsorted-bin-attack) ile eşleşir. İkisi de seçtiğin bir libc global'ine bir heap address yazmanı sağlar — tam da gereken şey, çünkü table base'i kontrol ettiğin bir heap chunk olacak.
-
__printf_function_table'ı non-NULL yap. Buradaki herhangi bir non-zero değerdo_positionalslow path'ini zorlar. Seçilen specifier onun üzerinden de handle edilmediği sürece junk'a işaret etmesi sorun değil; spec parse sırasında önce arginfo path tetiklenir. -
__printf_arginfo_table'ı fake table'ına yönlendir. Bin attack'i kullanarak bu global'i içeriğini kontrol ettiğin bir heap address ile overwrite et. Tablepointer-array[spec]şeklindedir:ckarakterinin entry'sicindex'inde yaşar. -
Function pointer'ı yerleştir. Heap chunk'ında, target pointer'ı (one-gadget, ORW stub, ROP pivot veya bir backdoor) seçtiğin specifier'ın slot'una koy. Yaygın bir konvansiyon, chunk metadata'sını hesaba katarak
ckarakterinin offset'ini forge ettiğin chunk'tan(c - 2) * 8olacak şekilde yerleştirir — normal output'u ezmemek için%Xgibi nadiren kullanılan bir conversion seç. -
Trigger. Target'ta seçtiğin specifier'ı emit eden (veya emit etmesini etkileyebileceğin) sonraki herhangi bir
printf/fprintf/snprintf,printf_positional→__parse_one_specmb→ senin__printf_arginfo_table[c]üzerinden geçer ve control senin pointer'ına aktarılır.
Yaklaşık slow-path akışı
Footgun'lar
- İki table da önemli:
__printf_function_table'ı NULL bırakmak fast path'in alınması ve arginfo table'ının asla danışılmaması demektir. - Programın meşru olarak kullanmadığı bir specifier seç, yoksa normal formatlama senin table'ından geçer ve output'u bozar / crash eder.
- Slot index'i ham karakter değeridir;
(c-2)*8/metadata hesabındaki bir off-by-one genellikle wild bir çağrının nedenidir.
Detection¶
register_printf_functionhiç çağrılmamış bir process'te non-NULL bir__printf_function_table/__printf_arginfo_tableoldukça anormaldir.- İki global'den birinin register edilmiş bir handler table yerine heap'e işaret etmesi attack'i işaret eder.
Mitigation¶
- Table'lar meşru glibc state'idir, dolayısıyla "fix" edilmezler; savunmacılar
write primitive'inin bloklanmasına güvenir — örn. glibc 2.30+ large-bin
check'leri ve 2.28 unsorted-bin
bck->fdcheck'i delivery adımındaki çıtayı yükseltir. - RELRO bunları korumaz (bunlar libc
.bss'idir, target'ın GOT'u değil). - Dispatch edilen çağrı üzerinde CFI / pointer-signing son sıçramayı nötralize eder.