Skip to content

IOSurface property spray

Bir IOSurface'e s_set_value external method'u üzerinden keyfi-uzunlukta OSData değerleri eklemek, attacker-kontrollü, geri-okunabilir içerikle seçilmiş boyutta kernel kalloc.x allocation'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

  1. 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 */
  1. Selector 9 üzerinden kontrollü-boyutlu OSData spray'le. s_set_value dinamik-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ş raw OSData uzunluğudur (OSSerialize XML içinde base64 olarak taşınsa da): 56-byte'lık bir binary payload → kalloc.64'te backing'li bir OSData:
/* 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 */
  1. Free edilmiş bir chunk'ı reclaim et. Bir use-after-free veya double-free 56-byte'lık bir slot'u kalloc.64 freelist'ine geri koyduktan sonra, bir sonraki spray'lenen OSData tam olarak o slot'a düşer — artık senin byte'larınla doldurulmuş. Bu, standart spray_kernel_heap_with_zeroes() / fake-object reclaim adımıdır.

  2. 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 IOSurfaceRootUserClient external-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.64 zone 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.

References