IOKit driver UserClient exploitation¶
Bir sürücünün
IOUserClient::externalMethodselector'larına unprivileged bir uygulamadan ulaşmak, başlıca iOS/macOS kernel attack surface'idir; zayıf per-selector input validation'ı (örneğin IOMobileFramebuffer'da kontrol edilmemiş bir index) tek birIOConnectCallMethod'u kernel içinde bir OOB read/write'a çevirir.
Mechanism¶
Note
Bir IOKit sürücüsüne her userspace bağlantısı tam olarak bir IOUserClient instance'ı (bir io_connect_t Mach port'u) tarafından aracılanır. Userspace, scalar ve structure in/out buffer'larıyla numaralı bir selector dispatch eder; kernel bunu IOUserClient::externalMethod üzerinden yönlendirir, bu da bir IOExternalMethodDispatch entry'si arar ve method'un function'ını çağırmadan önce argüman şeklini o entry'nin checkScalarInputCount / checkStructureInputSize / checkScalarOutputCount / checkStructureOutputSize alanlarına karşı doğrulaması gerekir. İnvariant: bir selector handler her scalar ve struct alanını tamamen attacker-kontrollü olarak ele almalı ve onu kendisi bounds-check etmeli — dispatch table yalnızca count/size'ları kontrol eder, asla değerleri değil. Bir handler bir scalar'a range kontrolü olmadan array index'i olarak güvendiğinde, genel dispatch katmanı onu durdurmak için hiçbir şey yapmaz ve kontrollü index kernel privilege'inde bir OOB erişime dönüşür. externalMethod surface'inin bu kadar verimli olmasının sebebi budur: onlarca sürücü boyunca yüzlerce selector, her biri yanlış olabilen elle yazılmış bir validation.
Legacy IOExternalMethod table şekli açık hale getirir:
struct IOExternalMethod {
IOService *object; /* the user client / target */
IOMethod func; /* the selector handler */
IOOptionBits flags; /* scalar vs structure I/O */
IOByteCount count0; /* input scalar count / struct size */
IOByteCount count1; /* output scalar count / struct size */
};
Modern path, handler'a bir IOExternalMethodArguments (args->scalarInput[], args->structureInput, args->scalarOutput[]) geçirir.
Walkthrough¶
Çalışılmış örnek: CVE-2021-30807, IOMobileFramebufferUserClient'taki (IOMFB / AppleCLCD framebuffer sürücüsü) bir OOB read; Saar Amar tarafından dokümante edildiği gibi.
- User client'ı aç. Bir uygulamadan (veya WebContent'ten) service'i al ve bir bağlantı aç:
io_service_t svc = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("AppleCLCD"));
io_connect_t conn;
IOServiceOpen(svc, mach_task_self(), 0 /*type*/, &conn);
- Zafiyetli selector'ı çağır. Selector 83,
IOMobileFramebufferUserClient::s_displayed_fb_surface'e map'lenir. Bir scalar girer, doğrudan index olarak kullanılır:
uint64_t scalars[1] = { attacker_index };
uint64_t out[1]; uint32_t out_cnt = 1;
IOConnectCallMethod(conn, 83,
scalars, 1, /* scalarInput, count */
NULL, 0, /* structureInput */
out, &out_cnt, /* scalarOutput */
NULL, NULL);
- Kontrol edilmemiş index. Handler,
args->scalarInput[0]'ı doğrudanIOMobileFramebufferLegacy::get_displayed_surface'e iletir; bu da üst sınır olmadan*(this + scalar0 + 0x331)hesaplar. Disassembly'de:
- OOB read'i bir primitive'e çevir. Sızdırılan pointer bir
IOSurface*olarak ele alınır veIOSurfaceRoot::copyPortNameForSurfaceInTasküzerinden çalıştırılır; bu da attacker'ın kontrollü/sahte bir IOSurface materyalize etmesine veIOSurfaceSendRight::initüzerinden virtual call'ları tetiklemesine izin verir — infoleak'ten control flow'a pivot.
Selector reversing metodolojisi
Bir sürücünün surface'ini map'lemek için: bağlantı type'ını öğrenmek için onun newUserClient'ını bul, sonra dispatch table'ı dökmek için externalMethod / getTargetAndMethodForIndex'i reverse et — her entry bir selector numarası, handler ve uyguladığı checkScalarInputCount / checkStructureInputSize'ı verir. Selector'ları aralık boyunca IOConnectCallMethod(conn, sel, ...) çağırarak fuzz'la; belirli bir selector'da (örneğin 83) bir panic eksik bir bounds check'i işaretler.
Warning
Erişilebilirlik yalnızca API'ye değil, entitlement'lara ve sandbox profillerine bağlıdır. CVE-2021-30807, WebKit WebContent process'inin tesadüfen tuttuğu com.apple.private.allow-explicit-graphics-priority'yi gerektiriyordu — onu sadece-yerel bir bug yerine remote-to-kernel bir LPE yapan şey buydu. iOS 16, hassas IOMFB selector'larını gate'lemeye (örneğin s_create_gain_map'i com.apple.private.gain-map-access arkasında) ve kısıtlı context'lerden hangi user client'ların açılabileceğini kısıtlamaya başladı.
Detection¶
- Per-driver selector denetimi:
externalMethodselector numaralarını ve argüman sayılarını logla; sandbox'lı process'lerden gelen politika-dışı selector'lar anormaldir. - Bir
IOConnectCallMethodpath'inde geçerli birIOSurface/object base'inden uzak bir faulting adresle gelen panic, klasik OOB-index işaretidir. - Apple'ın PAC'ı, zone hardening'i ve iOS 16 user-client allowlist'i (
ios16_restricted_iouserclients) erişilebilir kümeyi küçültür.
Mitigation¶
- Her selector handler'ın içinde her scalar/struct alanını bounds-check et; dispatch table'ın count/size kontrollerinin değer geçerliliğini ima ettiğine asla güvenme.
- Güçlü user client'ları entitlement'lar arkasında gate'le ve onları renderer/WebContent sandbox'larından kaldır.
- IOKit vtable'ları üzerindeki PAC ve iOS 16 restricted-user-client listesi, bir OOB'u sahte-object virtual call'a pivot etmenin maliyetini yükseltir.