OSSerializer::serialize() gadget (fake object call)¶
Virtual
serialize()'i çağrılan sahte birOSSerializerobject'i, tek bir kontrollü C++ virtual call'u kontrollü argümanlarla keyfi bir function call'a çevirir; çünküserialize()basitçe(*callback)(target, ref, s)yapar.
Mechanism¶
serialize() neden kusursuz fake-object gadget'ı
IOKit'te kernel object'leri, object'in offset 0'ındaki bir vtable
pointer üzerinden erişilen C++ instance'larıdır. Bir kernel this'ini
attacker-controlled belleğe (bir fake object: bir UAF reclaim'den, bir
OOB write'tan veya sprey'lenmiş bir buffer'dan) yönlendirmeni sağlayan
bir primitive'e sahip olduğunda, herhangi bir virtual call
obj->method() şuna dönüşür: ( *(obj->vtable + idx) )(obj, ...) —
x0 = obj ile, kontrol ettiğin bir adrese yapılan bir call. Bunu
silahlandırmak için, gövdesi object'in içinden bir function pointer ve
argümanları okuyup onu çağıran gerçek bir kernel function'ı istersin.
OSSerializer::serialize() tam olarak budur:
class OSSerializer : public OSObject {
void *target; // offset 0x10
void *ref; // offset 0x18
OSSerializerCallback callback;// offset 0x20
virtual bool serialize(OSSerialize *s) const;
};
bool OSSerializer::serialize(OSSerialize *s) const {
return (*callback)(target, ref, s); // call ptr(arg0, arg1, arg2)
}
arm64'te gövde kabaca şuna derlenir:
LDP X1, X3, [X0,#0x18] ; X1 = ref (arg1), X3 = callback (the fn ptr)
LDR X9, [X0,#0x10] ; X9 = target (arg0)
MOV X0, X9 ; arg0 = target
MOV X2, X8 ; arg2 = the OSSerialize* (often ignored)
BR X3 ; tail-call the controlled function pointer
Yani fake object'in 0x10/0x18/0x20 offset'lerindeki byte'ları kontrol
edersen, serialize() şunu çağırır: callback(target, ref, ...) —
iki tamamen kontrollü argümanla keyfi bir function. Klasik devam adımı,
callback'i copyin/copyout'a (veya bir stack-pivot gadget'ına)
yönlendirmek; böylece tek bir virtual call, kernel-controlled bellek
hareketine ve nihayetinde tam bir read/write veya code-execution chain'ine
dönüşür.
Walkthrough¶
Exploit'in ihtiyacı olan şeyler: (a) bilinen kernel belleğinde sahte bir
OSSerializer, (b) tetiklenen virtual slot'un gerçek
OSSerializer::serialize'a düşmesi için bağlanmış vtable'ı ve (c) bir virtual
call'u tetiklemenin bir yolu.
/* Fake object, all fields in attacker-controlled memory at known kaddr */
struct fake_osserializer {
uint64_t vtable; // +0x00 -> resolves so vtable[idx] == &serialize
uint64_t pad8; // +0x08
uint64_t target; // +0x10 -> arg0 to the called function
uint64_t ref; // +0x18 -> arg1 to the called function
uint64_t callback; // +0x20 -> the function to call
};
-
Adresleri çöz. KASLR slide'ını leak'le (örn. gerçek bir
AGXCommandQueuevtable pointer'ından) veOSSerializer::serialize'ın runtime adresini hesapla. Fake object'in "vtable"'ını kendi datasıyla araya geçir ki trigger'ın kullandığı slot&serialize'ı göstersin. -
Call'u yerleştir.
callback = copyout,target = kernel_src,ref = user_dstayarla (copyiniçin tam tersi), böylece gadget kernel belleğini userland'e kopyalar — keyfi bir read'i bootstrap eder. -
Tetikle. Kernel'i fake object üzerinde bir virtual method çağırmaya yönlendir. Kanonik Project Zero örneğinde, bir
AGXCommandQueue'yu değiştirip MIG katmanının::release()'inin (veyagetMetaClasstarzı bir virtual call'un) fake object'e karşı çalışması gadget'ı ateşler.
trigger virtual call -> OSSerializer::serialize(fakeobj, s)
-> copyout(kernel_src, user_dst, len) (arg0,arg1)
Her seferinde tek call
Gadget, tetik başına tek bir kontrollü call verir. Gerçek exploit'ler
stabil bir read/write primitive kurmak için bunu tekrarlar (veya bir
JOP/stack-pivot dizisine chain'ler); PAC-hardened cihazlarda kontrollü
callback'in kendisi Pointer Authentication'dan sağ çıkmak zorundadır,
bu yüzden bu gadget tipik olarak bir PAC bypass ile birleştirilir.
Detection¶
- Bu bir memory-corruption post-exploitation primitive'idir; bir syscall imzası yoktur. Detection upstream tarafındadır: fake object'i üreten IOKit UAF/OOB'unu yakalamak (zone poisoning, freelist corruption panic'leri).
Mitigation¶
- Function pointer'lar/vtable'lar üzerinde PAC: kernel PAC'li cihazlarda
callback/vtable girdileri imzalanır, dolayısıyla forge edilmiş bir değer, ayrı bir PAC bypass ile birleştirilmediği sürece authentication'da başarısız olur. - kalloc_type / pointer-vs-data zone isolation, victim pointer'ın okunduğu yere doğru şekle sahip bir fake object yerleştirmeyi zorlaştırır.