OOL ports array spray¶
Bir out-of-line ports descriptor taşıyan Mach message'lar göndermek, kernel'i
ipc_port *pointer'lardan oluşan bir array'ikallocetmeye zorlar; bu controllable allocation kalloc zone'ları groom etmek, free edilmiş objeleri reallocate etmek ve kernel pointer'larını leak/control etmek için kullanılır.
Mechanism¶
Bir OOL ports descriptor neden heap-shaping primitive'idir
Bir Mach message body, descriptor taşıyabilir — message user/kernel
sınırını geçerken kernel'in özel olarak yorumladığı typed entry'ler.
Bunlardan biri MACH_MSG_OOL_PORTS_DESCRIPTOR'dır ("out-of-line ports"
descriptor, type value 2). Şöyle tanımlanır:
typedef struct {
void* address; // user array of port names
mach_msg_size_t count; // number of ports
boolean_t deallocate;
mach_msg_copy_options_t copy; // MACH_MSG_PHYSICAL_COPY / VIRTUAL_COPY
mach_msg_descriptor_type_t type; // MACH_MSG_OOL_PORTS_DESCRIPTOR
} mach_msg_ool_ports_descriptor_t;
Message gönderildiğinde, ipc_kmsg_copyin_ool_ports_descriptor()
user'ın sağladığı count adet port name'inden oluşan array'i,
in-kernel count adet ipc_port * pointer'dan oluşan bir array'e
çevirmek zorundadır. Bu çevrilmiş array'i tutmak için kernel bir
kalloc(count * sizeof(ipc_port_t)) yapar — yani 64-bit bir cihazda
count * 8 byte. Array, ipc_kmsg'e attach edilir ve message receive
edilene (ki bu port'ları dışarı kopyalar ve array'i free eder) ya da
destroy edilene kadar kernel heap'inde yaşar.
İki invariant bunu bir attacker için değerli kılar:
-
Controlled size, controlled contents.
counttamamen attacker'ın seçtiği bir değerdir, dolayısıyla allocation herhangi bir target kalloc zone'a düşecek şekilde boyutlandırılabilir (ör. 32 port içinkalloc.256). Byte'lar opaque data değildir — her 8-byte slot, kernel'in kendisinin hesapladığı bir kernel pointer'dır (gerçek biripc_port'un adresi). Bu sıra dışıdır: çoğu spray primitive sana data'yı control etme imkânı verir ama pointer şeklindeki kernel değerlerini değil. -
Allocation tutulur, sonra istendiğinde release edilir. Array, message bir port'un queue'sunda durduğu sürece kalıcıdır ve onu
mach_msg(MACH_RCV_MSG)ile aldığında deterministik olarak free edilir. Bu sana bir hole'un ne zaman açılacağı ve ne zaman yeniden doldurulacağı üzerinde hassas control verir — heap feng shui'nin temel gereksinimi.
Slot'lar canlı ipc_port pointer'ları olduğundan, bir OOL ports spray
amaca göre üç farklı iş yapabilir: free edilmiş bir objenin slot'unu
bilinen bir shape ile güvenilir biçimde reallocate etmek; free
edilmiş bir ipc_port *'ın bulunduğu yere seçilmiş bir pointer
yerleştirerek dangling bir port'u reconstruct etmek; ve message'ı
geri receive edip materialize olmuş port'u okuyarak bir kernel
pointer'ını leak etmek (kernel adresi, port context gibi field'larda
round trip boyunca hayatta kalır).
Walkthrough¶
Kanonik gönderim sırası: body'si tek bir OOL ports descriptor declare eden
bir message inşa et, sonra onu sahip olduğun bir port'a mach_msg ile gönder.
struct ool_ports_msg {
mach_msg_header_t hdr;
mach_msg_body_t body; // msgh_descriptor_count = 1
mach_msg_ool_ports_descriptor_t ool;
};
mach_port_t ports[32]; // 32 * 8 = 256 bytes -> kalloc.256
for (int i = 0; i < 32; i++)
ports[i] = mach_task_self(); // any send right works
struct ool_ports_msg msg = {0};
msg.hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0)
| MACH_MSGH_BITS_COMPLEX; // complex => has descriptors
msg.hdr.msgh_size = sizeof(msg);
msg.hdr.msgh_remote_port = target; // a port whose queue we fill
msg.body.msgh_descriptor_count = 1;
msg.ool.address = ports;
msg.ool.count = 32;
msg.ool.deallocate = FALSE;
msg.ool.copy = MACH_MSG_PHYSICAL_COPY;
msg.ool.type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
kern_return_t kr = mach_msg(&msg.hdr, MACH_SEND_MSG,
sizeof(msg), 0, MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
// kr == KERN_SUCCESS: the kernel kalloc'd a 256-byte array of ipc_port*
Grooming için spray: bu tür message'ların birçoğunu queue'ya koy ki kernel art arda aynı boyutta birçok allocation üretsin; bu fragmentation'ı doldurur ve bir sonraki free edilecek slot'u öngörülebilir biçimde konumlandırır.
mach_port_t holders[1000];
for (int i = 0; i < 1000; i++) {
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &holders[i]);
mach_port_insert_right(mach_task_self(), holders[i], holders[i],
MACH_MSG_TYPE_MAKE_SEND);
send_ool_ports(holders[i], ports, 32); // each call -> one kalloc.256 array
}
Free edilmiş bir objeyi reclaim etmek için, victim'i free et (ör. UAF'a
uğramış bir ipc_port'a veya başka bir kalloc-zone objesine olan son
reference'ı bırak), sonra hemen victim'in zone'una uygun boyutta OOL ports
message'ları spray et. Yüksek olasılıkla array'lerden biri free edilmiş
slot'a düşer; onun 8-byte pointer slot'ları artık victim'in field'larıyla
overlap eder.
Spray'i istendiğinde free etmek ve hole'ları yeniden açmak için message'ları receive et:
mach_msg(&recv.hdr, MACH_RCV_MSG, 0, sizeof(recv),
holders[i], MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
// receiving frees the in-kernel ipc_port* array for that message
Public exploit'ler bu primitive'i nasıl kullandı
Project Zero'nun iOS kernel exploit incelemesi, OOL ports (ve OOL
memory) descriptor'larını standart bir grooming/reallocation aracı
olarak not eder: mach_portal, dangling port page'ini host port'a
işaret eden bir OOL ports array'iyle reallocate etti; voucher_swap
ise dangling voucher'ı, önceden allocate edilmiş bir ipc_port'a
pointer taşıyan bir OOL ports array'iyle reclaim etti. (Buna karşılık
async_wake bu primitive'i kullanmaz — freed port page'ini forced
zone GC ile bir ipc_kmsg'in içeriğiyle reallocate eder, OOL ports
ile değil.) Her durumda heap layout'unu güvenilir kılan şey, herhangi
bir tek bug değil — descriptor'ın deterministik kalloc/free
davranışıdır.
Detection¶
- Bunlar meşru Mach message'larıdır; syscall seviyesinde bir anomali
yoktur. Detection heuristic'tir: büyük
countdeğerleri taşıyan OOL ports descriptor'lı çok sayıda complex message yayan bir process, özellikle kendi sahip olduğu port'lara yönelikse, grooming ile tutarlıdır. - Kalloc-zone churn (tek bir zone'da hızlı alloc/free) üzerine kernel telemetry'si, spray-then-reclaim pattern'leriyle korele olur.
Mitigation¶
- Modern XNU birçok obje tipini ayrılmış, type-segregated kalloc zone'lara
izole eder (
kalloc_type/ data-vs-pointer separation); bu, bir port-pointer array'inin ilgisiz bir victim objesiyle aynı zone'u paylaşmasını engelleyerek "victim'in generic zone'una pointer şeklinde bir array spray et" varsayımını kırar. - Per-CPU cache'ler, zone randomization ve PAC-signed pointer'lar, leak/place edilen pointer'ların güvenilirliğini ve değerini azaltır.