Skip to content

Arbitrary physical mapping primitive (One Byte / dart)

Bir vm_map_copy type discriminator'ının one-byte heap corruption'ı, XNU kernel'inin attacker'ın kontrol ettiği inline data'yı sahte vm_map_entry objelerinden oluşan bir linked list olarak yorumlamasına yol açar; nihayetinde attacker'ın verdiği bir physical page number ile pmap_enter_options() çağrılır ve exploit process'i içinde keyfi bir physical-to-virtual mapping üretilir.

Mechanism

Note

XNU'nun vm_map_copy yapısı, offset 0x00'da union gövdesinin üç yorumundan birini seçen bir type field'ı taşır:

Değer Anlamı Union yorumu
1 ENTRY_LIST vm_map_entry objelerinden oluşan linked list
2 OBJECT tek bir vm_object_t reference'ı
3 KERNEL_BUFFER inline byte dizisi (kdata[])

Type'ı 3 → 1 değiştiren bir one-byte overflow, vm_map_copyout_internal()'in inline kdata'yı gerçek vm_map_entry objelerinin doubly-linked list'iymiş gibi gezmesine yol açar. Bir sahte entry'nin wired_count != 0 olduğunda kernel vm_fault_enter()pmap_enter_options() çağırır. ARM'de translation table entry yazılmadan önce, verilen physical page number üzerinde başka bir validation yapılmaz (pmap.c kaynağı, Brandon Azad tarafından doğrulandı). Sonuç: attacker'ın adlandırdığı herhangi bir physical page (PPL-korumalı, bir page-table page'i ya da bir DART I/O adresi olmayan), attacker'ın seçtiği protection'larla user-kontrollü bir virtual adrese map'lenir — temiz, keyfi bir physical read/write primitive'i.

Oradan exploit, kernelcache image'ına ait physical page'leri map'ler. Bilinen physical adres 0x200000680'deki AMCC (Apple Memory Controller) register'ı RORGNBASEADDR, kernelcache'in physical base'ini leak eder ve physical space'te KASLR'ı bypass eder. Attacker sonra sysctl_oid yapılarını map'ler, oid_arg1 pointer'larını yeniden yönlendirir ve keyfi virtual kernel read ve write'ları sentezlemek için sysctl node {0,3} (name2mib)'i kötüye kullanır.

Güvenlik açığı CVE-2020-3837 idi (oob_timestamp; komşu bir vm_map_copy'ye ilk one-byte overflow fırsatını veren ayrı bir heap overflow). Physical-mapping tekniğinin kendisi generic'tir ve one-byte corruption'ın nasıl iletildiğinden bağımsızdır.

Walkthrough

Brandon Azad'ın Project Zero yazısı "One Byte to rule them all"a (Temmuz 2020) dayanır; iOS 13.3 / A13 hedeflenir.

Step 1 — one-byte vm_map_copy type corruption elde et

oob_timestamp güvenlik açığı (CVE-2020-3837), bir vm_map_copy objesine komşu bir heap allocation üzerinde off-by-one bir write tetikler. İlk byte — type field'ı — 3 (KERNEL_BUFFER)'dan 1 (ENTRY_LIST)'e değiştirilir:

Before:  vm_map_copy { type=3, offset=X, size=Y, kdata=[...attacker bytes...] }
After:   vm_map_copy { type=1, offset=X, size=Y, hdr.links=<fake vm_map_entry ptr> }
                       ^^^^ changed by 1-byte overflow

Step 2 — Kontrol edilen memory'de sahte vm_map_entry chain'i craft et

kdata bölgesi (artık ENTRY_LIST olarak yorumlanıyor) sahte vm_map_entry struct'larıyla doldurulur. Önemli field'lar:

struct fake_vm_map_entry {
    // vm_map_links (must form valid sentinel)
    struct vm_map_entry *vme_prev;
    struct vm_map_entry *vme_next;  // points to sentinel to end list

    vm_map_offset_t vme_start;      // target VA in exploit process
    vm_map_offset_t vme_end;
    // ... padding ...

    uint16_t        wired_count;    // must be != 0 to enter fault path
    // VME_OBJECT / VME_OFFSET macros read object/offset from bitfield

    // Embedded vm_object_t → vm_page_t chain:
    // vm_page_lookup(object, offset) must return a fake vm_page
    // whose phys_page field = TARGET_PHYS_PAGE_NUMBER
};

Step 3 — Corrupt edilen vm_map_copy'yi OOL memory descriptor ile ilet

Corrupt edilen vm_map_copy, attacker'ın kontrol ettiği bir receive port'una giden bir Mach message'ı içinde out-of-line bir memory descriptor olarak gönderilir:

mach_msg_ool_descriptor_t ool = {
    .address    = corrupted_copy,
    .size       = sizeof(corrupted_copy),
    .deallocate = FALSE,
    .copy       = MACH_MSG_VIRTUAL_COPY,
    .type       = MACH_MSG_OOL_DESCRIPTOR,
};
// Sending this message causes kernel to call vm_map_copyout_internal()
// with our corrupted vm_map_copy, traversing the fake entry list.

Step 4 — pmap_enter_options() keyfi physical page'i map'ler

vm_map_copyout_internal içinde, wired_count != 0 olan her entry için:

// (simplified from osfmk/vm/vm_map.c)
m = vm_page_lookup(VME_OBJECT(entry), VME_OFFSET(entry));
// m->phys_page == TARGET_PHYS_PAGE_NUMBER (attacker-controlled)
vm_fault_enter(m, dst_map->pmap, target_va, VM_PROT_READ|VM_PROT_WRITE, ...);
// -> pmap_enter_options(pmap, target_va, TARGET_PHYS_PAGE_NUMBER, ...)
// ARM pmap.c: no further validation of phys_page before TTE write

Physical page TARGET_PHYS_PAGE_NUMBER artık userspace'te target_va'da erişilebilir.

Step 5 — AMCC register üzerinden kernelcache'i bul

AMCC register page'ini map'le (physical adres 0x200000680):

uint64_t amcc_phys  = 0x200000680ULL;
uint64_t amcc_va    = map_phys_page(amcc_phys);        // using primitive above
uint64_t kc_phys_base = *(uint64_t*)(amcc_va + RORGNBASEADDR_OFFSET);
// kc_phys_base = start of lockdown region containing kernelcache

Physical space'te KASLR, A13'te yalnızca ~32 MB entropy'dir; kc_phys_base'den tarama yapmak Mach-O header'ını bulur.

Step 6 — sysctl hijack ile virtual kernel R/W sentezle

Kernelcache page'lerini map'le, sysctl_oid node'larını bul:

// Overwrite oid_arg1 of a sandbox-accessible sysctl node
// to point to target kernel address.
// Read via: sysctl(OID_PATH, NULL, &buf, &len, NULL, 0)
// Write via: sysctl node {0,3} (sysctl.name2mib) undocumented behaviour

Beklenen sonuç: caller bir kernel_read64(addr) / kernel_write64(addr, val) fonksiyon çifti kazanır.

Detection

  • Beklenmedik bir physical adrese erişen vm_map_copyout_internalvm_fault_enter üzerinden geçen bir backtrace ile kernel panic.
  • Sandbox'lı bir process'ten OOL descriptor'lar içeren ve ardından physical-range memory'ye erişimin geldiği anormal mach_msg paternleri.
  • Attacker page-table page'leri map'lemeye çalışırsa PPL violation log'ları (PPL ARM'de panic'ler).

Mitigation

  • PPL (Page Protection Layer): kernelcache ve page-table physical page'lerinin map'lenmesini engeller; primitive'in etkisini sınırlar ama ortadan kaldırmaz.
  • zone_require: vm_map_copy allocation'larının doğru zone'dan kaynaklanmasını zorlamak, type-confusion'ı daha erken tespit ederdi.
  • pmap-io-ranges devicetree property'sindeki DART I/O protection flag'leri, IOMMU register page'lerinin map'lenmesini engeller.
  • Fix: dispatch'ten önce vm_map_copyout_internal'daki type field'ını doğrula; ENTRY_LIST traversal'ında size/bounds check'leri ekle.

References