Skip to content

ipc_kmsg spray / ikm_size corruption

Mach message body'leri, kapasitesi ikm_size'da kaydedilen kernel ipc_kmsg allocation'larında buffer'lanır; message'ları spray'lemek kalloc'u deterministik olarak groom eder ve bir victim ikm_size'ı şişiren bir heap overflow, kernel'ın sonraki message'ları gerçek buffer'ın ötesine yazmasını sağlar — klasik bir iOS OOB-write-to-anything primitive'i (extra_recipe / mach_portal soyu).

Mechanism

Note

Bir Mach message uçuştayken, kernel onu kalloc'tan allocate edilmiş bir struct ipc_kmsg'ye kopyalar. Buffer'ın gerçek kapasitesi kendi header alanında, ikm_size'da saklanır. Send path'indeki bounds check o alana güvenir: kabaca if (msg_and_trailer_size > kmsg->ikm_size - max_desc) /* too big */. İnvariant şu: ikm_size her zaman gerçek allocation boyutuna eşittir. İki gerçek bunu exploit edilebilir kılar:

  1. Grooming. ikm_kmsg allocation'ları tamamen attacker-tarafından sürülür — seçilmiş bir body boyutunda N message gönder ve seçilmiş bir kalloc zone'una N adet aynı boyutta object yerleştir; onu linear allocation'a tüket ki bir sonraki victim object bitişik düşsün. ipc_kmsg'nin kendisi spray'dir.
  2. Corruption. Bir heap overflow bug'ı, attacker-kontrollü bir allocation'ın ötesine bitişik bir ipc_kmsg'ye yazar ve onun ikm_size'ını yükseltirse, o ipc_kmsg artık olduğundan daha büyük olduğunu sanır. Kernel'ın ona buffer'ladığı bir sonraki message (örneğin bir exception message veya bir preallocated port üzerinden teslim edilen biri), şişirilmiş ikm_size'a karşı bounds-check edilir ve gerçek buffer'ı bitişik heap object'ine taşırır — uzunluğunu ve içeriğini attacker'ın kontrol ettiği bir out-of-bounds write.

Yapı (xnu osfmk/ipc/ipc_kmsg.h):

struct ipc_kmsg {
    mach_msg_size_t     ikm_size;     /* recorded buffer capacity */
    struct ipc_kmsg    *ikm_next;     /* freelist / queue linkage */
    struct ipc_kmsg    *ikm_prev;
    mach_msg_header_t  *ikm_header;   /* the actual message header */
    /* ... */
};

Bir port preallocated bir ipc_kmsg ile oluşturulabilir (IP_PREALLOC davranışıyla mach_port_allocate_full): kernel, port'a gönderilen message'lar için taze allocate etmek yerine o tek buffer'ı yeniden kullanır; belirli, konumlanmış bir ipc_kmsg'yi kontrol edilebilir ve yeniden-tetiklenebilir kılan şey budur.

Walkthrough

Ian Beer'in extra_recipe (iOS 10) zincirini izleyerek:

  1. kalloc zone'unu message'larla groom et. Hedef zone'u doldurmak ve taze, bitişik slab page'leri zorlamak için sabit bir body boyutunda çok sayıda Mach message gönder. Body'ler grooming spray'i olarak da iş görür.
mach_msg_header_t *m = build_message(body_size);   /* sizes the ipc_kmsg */
for (int i = 0; i < N; i++)
    mach_msg(m, MACH_SEND_MSG, m->msgh_size, 0,
             MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
  1. Victim bitişikliğini düzenle. Exploit, kernel map'i öyle groom eder ki kontrol edilebilir bir allocation, bir hedef ipc_kmsg'nin hemen önünde durur (ve sonraki leak'ler için yakınında bir OOL-ports array'i ve bir IOSurface/OSData spray'i). Beer'in layout'u 8-page'lik bir ipc_kmsg'yi 8-page'lik bir out-of-line ports array'inin yanına yerleştirir, etraflarına IOSurface property'leri üzerinden ~80 MB OSData spray'lenmiş.

  2. ikm_size'ı overflow et. Memory-corruption bug'ını tetikle, attacker buffer'ının ötesine komşu ipc_kmsg'ye birkaç kontrollü byte yaz ve onun ikm_size'ını yükselt (overflow byte sayısı çalışma zamanında hesaplanır). Victim ipc_kmsg artık takip eden allocation'ın ilk kısmıyla örtüşür.

  3. OOB write'a çevir. Bozuk (preallocated) ipc_kmsg'ye bir message gönder. Send-path kontrolü şişirilmiş ikm_size'ı kullanır, böylece message body'si gerçek buffer'ın ötesine bitişik object'e (örneğin OOL ports array'i) yazılır ve attacker'ın sahte Mach port'ları / object pointer'ları forge etmesine izin verir.

  4. Yükselt. Örtüşen IOSurface OSData'sını geri okumak kernel pointer'larını (KASLR) açığa çıkarır ve overwrite edilmiş ports array'inden port'lar forge etmek tfp0 / kernel task port'una doğru daha güçlü bir read/write'ı bootstrap eder.

Preallocated port'lar neden önemli

Normal bir mach_msg gönderimi yeniden konumlandıramayacağın yeni bir ipc_kmsg allocate eder. Preallocated bir buffer'la oluşturulan bir port, bir ipc_kmsg'yi bilinen, groom'lanmış bir konuma pinler ve kernel'ın her gönderimde tam olarak o buffer'ı yeniden doldurmasına izin verir — tek-seferlik bir bitişikliği aynı bozuk ikm_size'a karşı tekrarlanabilir bir OOB-write tetikleyicisine çevirir.

Warning

Overflow, ikm_size'ı sonraki write'ı hâlâ map'li bellek içinde tutan bir değere yükseltmeli, yoksa OOB write'ın kendisi panic verir. Grooming güvenilirliği kırılgandır: spray'inle tetikleyici arasındaki başıboş kernel allocation'ları bitişikliği bozar, bu yüzden exploit'ler genellikle bir zone-GC / oturma geçişi çalıştırır ve yeniden spray'ler. Apple sonradan ipc_kmsg allocation'larını izole etti ve size hardening ekledi, bu yüzden tam olarak bu primitive modern xnu'da değişmeden hayatta kalmaz.

Detection

  • Aynı body boyutlarıyla mach_msg gönderim taşkınları (zone grooming) ve preallocated port'ların yoğun kullanımı anormaldir.
  • Zone-poisoning / kalloc red-zoning ve ikm_size sanity assertion'ları şişirilmiş-kapasite write'ını yakalar.
  • ipc_kmsg_copyin* / message-send path'lerinden kaynaklanan bir OOB write'ı gösteren panic logları.

Mitigation

  • Modern xnu, ipc_kmsg'yi dedicated allocation'lara (kalloc_type) ayırır ve ikm_size'ı doğrular; hem bitişiklik spray'ini hem de bozuk alana olan güveni kırar.
  • PAC ve zone freelist randomization, spray'in dayandığı öngörülebilir layout'u yener.

References