Skip to content

OSSerializer::serialize() gadget (fake object call)

Virtual serialize()'i çağrılan sahte bir OSSerializer object'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
};
  1. Adresleri çöz. KASLR slide'ını leak'le (örn. gerçek bir AGXCommandQueue vtable pointer'ından) ve OSSerializer::serialize'ın runtime adresini hesapla. Fake object'in "vtable"'ını kendi datasıyla araya geçir ki trigger'ın kullandığı slot &serialize'ı göstersin.

  2. Call'u yerleştir. callback = copyout, target = kernel_src, ref = user_dst ayarla (copyin için tam tersi), böylece gadget kernel belleğini userland'e kopyalar — keyfi bir read'i bootstrap eder.

  3. 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 (veya getMetaClass tarzı 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.

References