IOSurface property spray¶
Bir IOSurface'e
s_set_valueexternal method'u üzerinden keyfi-uzunluktaOSDatadeğerleri eklemek, attacker-kontrollü, geri-okunabilir içerikle seçilmiş boyutta kernelkalloc.xallocation'ları spawn eder — iOS/macOS'ta hassas, non-destructive bir heap-grooming ve infoleak primitive.
Mechanism¶
Note
Bir IOSurface keyfi bir properties dictionary taşıyabilir. Binary data olan bir değeri serialize ettiğinde, kernel onu bir OSData object'ine deserialize eder ve OSData backing buffer'ını, boyut sınıfı veriye uyan anonim kalloc.x zone'undan allocate eder. Attacker'ın exploit ettiği invariant: allocation boyutu sağladığın veri uzunluğunun saf bir fonksiyonudur, yani 56-byte'lık bir payload seçerek deterministik olarak kalloc.64'e düşersin, 24-byte'lık bir payload kalloc.32'ye düşer, vb. Kritik olan şey değerin tüketilmemesi, saklanmasıdır — IOSurfaceCopyValue onu aynı kernel buffer'ından doğrudan geri okur. Bu aynı anda üç yetenek verir: (1) bir bug'ı tetiklemeden önce bitişikliği kontrol etmek için belirli bir zone'u tüket/doldur, (2) bilinen bir boyutta yeni-free edilmiş bir chunk'ı kontrollü byte'larla reclaim et, ve (3) bir corruption onu yeniden yazdıktan sonra değeri geri okuyarak kernel belleğini non-destructive şekilde disclose et. Bunlardan binlercesini spray'lemek ucuzdur ve heap'i öngörülebilir, incelenebilir bir state'te bırakır.
Kernel path'i IOSurfaceRootUserClient external method selector 9 = s_set_value'dur; geri-okuma için s_get_value / IOSurfaceCopyValue ile eşleşir.
Walkthrough¶
- IOSurface user client'ı aç.
io_service_t surf = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("IOSurfaceRoot"));
io_connect_t conn;
IOServiceOpen(surf, mach_task_self(), 0, &conn);
/* create one IOSurface to attach properties to; remember its surface ID */
- Selector 9 üzerinden kontrollü-boyutlu OSData spray'le.
s_set_valuedinamik-uzunlukta bir input struct alır: ilk 4 byte IOSurface ID'si, 4 byte padding, sonra<array><data>...</data><string>KEY</string></array>kodlayan bir XML/OSSerialize blob'u. Burada boyutu belirleyen şey decode edilmiş rawOSDatauzunluğudur (OSSerialize XML içinde base64 olarak taşınsa da): 56-byte'lık bir binary payload → kalloc.64'te backing'li birOSData:
/* conceptually: build {surfaceID, 0, OSArray[ OSData(56 bytes), "k0001" ]} */
IOConnectCallStructMethod(conn, 9 /* s_set_value */,
input_blob, input_len,
output, &output_len);
/* repeat with thousands of distinct keys to flood kalloc.64 */
Üst-seviye API kullanılarak aynı şey şudur:
CFDataRef d = CFDataCreate(NULL, payload, 56); /* -> kalloc.64 */
IOSurfaceSetValue(surface, CFSTR("k0001"), d); /* selector 9 under the hood */
-
Free edilmiş bir chunk'ı reclaim et. Bir use-after-free veya double-free 56-byte'lık bir slot'u
kalloc.64freelist'ine geri koyduktan sonra, bir sonraki spray'lenenOSDatatam olarak o slot'a düşer — artık senin byte'larınla doldurulmuş. Bu, standartspray_kernel_heap_with_zeroes()/ fake-object reclaim adımıdır. -
Geri oku (infoleak). Bir corruption primitive'i spray'lenen bir değerin içindeki byte'ları overwrite ederse, onları object'i free etmeden kurtar:
CFTypeRef leaked = NULL;
IOSurfaceCopyValue(surface, CFSTR("k0001"), &leaked); /* s_get_value */
/* CFDataGetBytePtr(leaked) now holds the disclosed kernel bytes */
extra_recipe zincirinde IOSurface OSData spray'i bu rolü oynadı: ~80 MB OSData, victim ipc_kmsg/OOL-ports layout'unu çevreleyip deterministik bitişiklik sağladı (bkz. ipc_kmsg ikm_size corruption). Project Zero writeup'ına göre KASLR slide'ın kendisi, free edilen bir preallocated-port slot'unun bir AGXCommandQueue user-client object'i olarak reclaim edilip o object'in vtable pointer'ının corrupt ipc_kmsg mesajı üzerinden geri okunmasıyla disclose edildi — bu yüzden IOSurface burada grooming/reclaim primitive'idir, KASLR leak'inin doğrudan geri-okuma kanalı değil.
Spray'i bir hedef zone'a boyutlandırma
| payload byte'ları | OSData backing zone'u |
|---|---|
| 1–32 | kalloc.32 |
| 33–64 (örn. 56) | kalloc.64 |
| 65–128 | kalloc.128 |
Payload uzunluğunu hedef object'in boyut sınıfıyla eşleşecek şekilde seç, sonra bug'ı tetiklemeden önce o zone'u tam olarak groom etmek için objs_per_zone_page x n_pages değer allocate et.
Warning
IOSurfaceCopyValue üzerinden geri okunan property'ler non-destructive'dir — object allocate'li kalır — bu da onu temiz bir disclosure primitive yapan şeydir, ama aynı zamanda eski spray'lerin oyalandığı ve zone'u temizlemek için IOSurfaceRemoveValue'lanması (veya surface'in serbest bırakılması) gerektiği anlamına gelir, yoksa sonraki grooming öngörülemez hale gelir. Ayrıca, farklı key'lerin çok büyük spray'leri surface'in kendi properties dictionary'sini büyütür; bu da allocate eder ve şekillendirmeye çalıştığın zone'ları bozabilir.
Detection¶
- Çok sayıda farklı key ve aynı boyutlu data ile
IOSurfaceRootUserClientexternal-method selector 9 çağrılarının patlamaları güçlü bir heap-spray imzasıdır. - IOSurface property aktivitesiyle ilişkili
kalloc.32/kalloc.64zone büyümesi üzerine telemetri. - Bir bug da mevcutken KASAN/zone red-zoning sonraki free-edilmiş-slot reclaim'ini yakalar.
Mitigation¶
- Apple'ın data-PAC'ı, zone ayrımı (
kalloc_type/kalloc_data) ve per-zone freelist randomization'ı bunun dayandığı deterministik bitişikliği kırar. - Typed allocation'ları (object'ler) ham data buffer'larından (
OSData) ayırmak, spray'lenen bir data buffer'ının free edilmiş bir object slot'unu reclaim etmesini engeller.