iOS Exploiting

{{#include ../../banners/hacktricks-training.md}}

iOS Exploit Mitigations

  • Code Signing in iOS works by requiring every piece of executable code (apps, libraries, extensions, etc.) to be cryptographically signed with a certificate issued by Apple. When code is loaded, iOS verifies the digital signature against Apples trusted root. If the signature is invalid, missing, or modified, the OS refuses to run it. This prevents attackers from injecting malicious code into legitimate apps or running unsigned binaries, effectively stopping most exploit chains that rely on executing arbitrary or tampered code.
  • CoreTrust is the iOS subsystem responsible for enforcing code signing at runtime. It directly verifies signatures using Apples root certificate without relying on cached trust stores, meaning only binaries signed by Apple (or with valid entitlements) can execute. CoreTrust ensures that even if an attacker tampers with an app after installation, modifies system libraries, or tries to load unsigned code, the system will block execution unless the code is still properly signed. This strict enforcement closes many post-exploitation vectors that older iOS versions allowed through weaker or bypassable signature checks.
  • Data Execution Prevention (DEP) marks memory regions as non-executable unless they explicitly contain code. This stops attackers from injecting shellcode into data regions (like the stack or heap) and running it, forcing them to rely on more complex techniques like ROP (Return-Oriented Programming).
  • ASLR (Address Space Layout Randomization) randomizes the memory addresses of code, libraries, stack, and heap every time the system runs. This makes it much harder for attackers to predict where useful instructions or gadgets are, breaking many exploit chains that depend on fixed memory layouts.
  • KASLR (Kernel ASLR) applies the same randomization concept to the iOS kernel. By shuffling the kernels base address at each boot, it prevents attackers from reliably locating kernel functions or structures, raising the difficulty of kernel-level exploits that would otherwise gain full system control.
  • Kernel Patch Protection (KPP) also known as AMCC (Apple Mobile File Integrity) in iOS, continuously monitors the kernels code pages to ensure they havent been modified. If any tampering is detected—such as an exploit trying to patch kernel functions or insert malicious code—the device will immediately panic and reboot. This protection makes persistent kernel exploits far harder, as attackers cant simply hook or patch kernel instructions without triggering a system crash.
  • Kernel Text Readonly Region (KTRR) is a hardware-based security feature introduced on iOS devices. It uses the CPUs memory controller to mark the kernels code (text) section as permanently read-only after boot. Once locked, even the kernel itself cannot modify this memory region. This prevents attackers—and even privileged code—from patching kernel instructions at runtime, closing off a major class of exploits that relied on modifying kernel code directly.
  • Pointer Authentication Codes (PAC) use cryptographic signatures embedded into unused bits of pointers to verify their integrity before use. When a pointer (like a return address or function pointer) is created, the CPU signs it with a secret key; before dereferencing, the CPU checks the signature. If the pointer was tampered with, the check fails and execution stops. This prevents attackers from forging or reusing corrupted pointers in memory corruption exploits, making techniques like ROP or JOP much harder to pull off reliably.
  • Privilege Access never (PAN) is a hardware feature that prevents the kernel (privileged mode) from directly accessing user-space memory unless it explicitly enables access. This stops attackers who gained kernel code execution from easily reading or writing user memory to escalate exploits or steal sensitive data. By enforcing strict separation, PAN reduces the impact of kernel exploits and blocks many common privilege-escalation techniques.
  • Page Protection Layer (PPL) is an iOS security mechanism that protects critical kernel-managed memory regions, especially those related to code signing and entitlements. It enforces strict write protections using the MMU (Memory Management Unit) and additional checks, ensuring that even privileged kernel code cannot arbitrarily modify sensitive pages. This prevents attackers who gain kernel-level execution from tampering with security-critical structures, making persistence and code-signing bypasses significantly harder.

Old Kernel Heap (Pre-iOS 15 / Pre-A12 era)

The kernel used a zone allocator (kalloc) divided into fixed-size "zones." Each zone only stores allocations of a single size class.

From the screenshot:

Zone Name Element Size Example Use
default.kalloc.16 16 bytes Very small kernel structs, pointers.
default.kalloc.32 32 bytes Small structs, object headers.
default.kalloc.64 64 bytes IPC messages, tiny kernel buffers.
default.kalloc.128 128 bytes Medium objects like parts of OSObject.
default.kalloc.256 256 bytes Larger IPC messages, arrays, device structures.
default.kalloc.1280 1280 bytes Large structures, IOSurface/graphics metadata.

How it worked:

  • Each allocation request gets rounded up to the nearest zone size. (E.g., a 50-byte request lands in the kalloc.64 zone).
  • Memory in each zone was kept in a free list — chunks freed by the kernel went back into that zone.
  • If you overflowed a 64-byte buffer, youd overwrite the next object in the same zone.

This is why heap spraying / feng shui was so effective: you could predict object neighbors by spraying allocations of the same size class.

The freelist

Inside each kalloc zone, freed objects werent returned directly to the system — they went into a freelist, a linked list of available chunks.

  • When a chunk was freed, the kernel wrote a pointer at the start of that chunk → the address of the next free chunk in the same zone.

  • The zone kept a HEAD pointer to the first free chunk.

  • Allocation always used the current HEAD:

  1. Pop HEAD (return that memory to the caller).

  2. Update HEAD = HEAD->next (stored in the freed chunks header).

  • Freeing pushed chunks back:

  • freed_chunk->next = HEAD

  • HEAD = freed_chunk

So the freelist was just a linked list built inside the freed memory itself.

Normal state:

Zone page (64-byte chunks for example):
[ A ] [ F ] [ F ] [ A ] [ F ] [ A ] [ F ]

Freelist view:
HEAD ──► [ F ] ──► [ F ] ──► [ F ] ──► [ F ] ──► NULL
(next ptrs stored at start of freed chunks)

freelist का शोषण

क्योंकि एक free chunk के पहले 8 bytes = freelist pointer होते हैं, एक attacker इसे corrupt कर सकता है:

  1. Heap overflow एक adjacent freed chunk में → उसके “next” pointer को overwrite कर देना।
  2. Use-after-free एक freed object में write करके → उसके “next” pointer को overwrite कर देना।

फिर, उस size की अगली allocation पर:

  • allocator corrupted chunk को pop कर देता है।
  • attacker-supplied “next” pointer का पालन करता है।
  • एक pointer arbitrary memory की ओर return करता है, जिससे fake object primitives या targeted overwrite संभव हो जाता है।

Visual example of freelist poisoning:

Before corruption:
HEAD ──► [ F1 ] ──► [ F2 ] ──► [ F3 ] ──► NULL

After attacker overwrite of F1->next:
HEAD ──► [ F1 ]
(next) ──► 0xDEAD_BEEF_CAFE_BABE  (attacker-chosen)

Next alloc of this zone → kernel hands out memory at attacker-controlled address.

This freelist design ने hardening से पहले exploitation को बहुत प्रभावी बना दिया था: predictable neighbors from heap sprays, raw pointer freelist links, और type separation का अभाव attackers को UAF/overflow बग्स को arbitrary kernel memory control में escalate करने की अनुमति देता था।

Heap Grooming / Feng Shui

The goal of heap grooming is to shape the heap layout ताकि जब attacker overflow या use-after-free trigger करे, तो target (victim) object ठीक attacker-controlled object के बगल में बैठे।
इस तरह, जब memory corruption होता है, attacker reliably victim object को controlled data से overwrite कर सकता है।

Steps:

  1. Spray allocations (fill the holes)
  • समय के साथ, kernel heap fragmented हो जाता है: कुछ zones में holes बन जाते हैं जहाँ पुराने objects free हुए थे।
  • attacker पहले बहुत सारे dummy allocations करके इन gaps को भरता है, ताकि heap “packed” और predictable बन जाए।
  1. Force new pages
  • एक बार holes भर दिए जाने पर, अगली allocations उन new pages से आनी चाहिए जो zone में जोड़ी गई हों।
  • Fresh pages का मतलब है कि objects cluster होंगे, पुराने fragmented memory में बिखरे नहीं होंगे।
  • इससे attacker को neighbors पर बेहतर control मिलता है।
  1. Place attacker objects
  • attacker अब फिर से spray करता है, उन new pages में बहुत सारे attacker-controlled objects बनाता है।
  • ये objects size और placement में predictable होते हैं (क्योंकि ये सभी उसी zone के हैं)।
  1. Free a controlled object (make a gap)
  • attacker जानबूझकर अपने एक object को free करता है।
  • इससे heap में एक “hole” बनता है, जिसे allocator बाद में उसी size की अगली allocation के लिए reuse करेगा।
  1. Victim object lands in the hole
  • attacker kernel को trigger करता है ताकि victim object (जिसे वे corrupt करना चाहते हैं) allocate हो।
  • चूँकि hole freelist में पहला उपलब्ध slot होता है, victim ठीक वहाँ रखा जाता है जहाँ attacker ने अपना object free किया था।
  1. Overflow / UAF into victim
  • अब attacker के पास victim के आसपास attacker-controlled objects होते हैं।
  • अपने किसी object से overflow कर के (या freed object को reuse करके), वे reliably victims memory fields को चुने हुए मानों से overwrite कर सकते हैं।

Why it works:

  • Zone allocator predictability: एक ही size की allocations हमेशा उसी zone से आती हैं।
  • Freelist behavior: नई allocations सबसे हाल ही में free हुए chunk को पहले reuse करती हैं।
  • Heap sprays: attacker predictable content से memory भरता है और layout को नियंत्रित करता है।
  • End result: attacker नियंत्रित करता है कि victim object कहाँ आएगा और उसके पास कौन सा data रहेगा।

Modern Kernel Heap (iOS 15+/A12+ SoCs)

Apple ने allocator को harden किया और heap grooming को काफी कठिन बना दिया:

1. From Classic kalloc to kalloc_type

  • Before: हर size class (16, 32, 64, … 1280, आदि) के लिए एक single kalloc.<size> zone मौजूद था। उसी size का कोई भी object वहाँ रखा जाता था → attacker objects privileged kernel objects के बगल में बैठ सकते थे।
  • Now:
  • Kernel objects अब typed zones (kalloc_type) से allocate होते हैं।
  • हर object type (उदाहरण के लिए, ipc_port_t, task_t, OSString, OSData) की अपनी dedicated zone होती है, भले वे same size ही क्यों न हों।
  • object type ↔ zone के बीच mapping compile time पर kalloc_type system से generate होती है।

एक attacker अब यह सुनिश्चित नहीं कर सकता कि controlled data (OSData) संवेदनशील kernel objects (task_t) के साथ adjacent हो जाएँ, भले उनका size समान हो।

2. Slabs and Per-CPU Caches

  • heap को slabs में विभाजित किया गया है (memory के pages जिन्हें उस zone के fixed-size chunks में काटा गया है)।
  • हर zone में contention कम करने के लिए एक per-CPU cache होता है।
  • Allocation path:
  1. पहले per-CPU cache को चेक करें।
  2. अगर खाली हो तो global freelist से लें।
  3. अगर freelist खाली हो तो नया slab (एक या अधिक pages) allocate करें।
  • Benefit: यह decentralization heap sprays को कम deterministic बनाती है, क्योंकि allocations अलग-अलग CPUs caches से पूरी हो सकती हैं।

3. Randomization inside zones

  • एक zone के भीतर, freed elements को simple FIFO/LIFO order में वापस नहीं दिया जाता।
  • Modern XNU encoded freelist pointers इस्तेमाल करता है (safe-linking जैसा Linux में, ~iOS 14 से introduce हुआ)।
  • हर freelist pointer को एक per-zone secret cookie के साथ XOR-encode किया जाता है।
  • इससे attacker write primitive मिलने पर भी fake freelist pointer forge नहीं कर सकता।
  • कुछ allocations को slab के भीतर उनके placement में randomize किया जाता है, इसलिए spraying adjacency की गारंटी नहीं देती।

4. Guarded Allocations

  • कुछ critical kernel objects (जैसे credentials, task structures) को guarded zones में allocate किया जाता है।
  • ये zones slabs के बीच guard pages (unmapped memory) insert करते हैं या objects के चारों ओर redzones का उपयोग करते हैं।
  • guard page में किसी भी overflow से fault trigger होता है → silent corruption के बजाय immediate panic।

5. Page Protection Layer (PPL) and SPTM

  • भले ही आप एक freed object को control कर लें, आप kernel memory का सब कुछ modify नहीं कर सकते:
  • PPL (Page Protection Layer) यह सुनिश्चित करता है कि कुछ regions (जैसे code signing data, entitlements) kernel को भी read-only रहते हैं।
  • A15/M2+ devices पर यह 역할 SPTM (Secure Page Table Monitor) + TXM (Trusted Execution Monitor) से बदला/बढ़ाया गया है।
  • ये hardware-enforced layers attackers को एक single heap corruption से critical security structures की arbitrary patching करने से रोकते हैं।

6. Large Allocations

  • सभी allocations kalloc_type के माध्यम से नहीं जातीं।
  • बहुत बड़े requests (~16KB से ऊपर) typed zones को bypass कर के सीधे kernel VM (kmem) से page allocations के माध्यम से serve किए जाते हैं।
  • ये कम predictable होते हैं, पर कम exploitable भी होते हैं, क्योंकि वे अन्य objects के साथ slabs share नहीं करते।

7. Allocation Patterns Attackers Target

इन protections के बावजूद, attackers अभी भी इन चीज़ों को खोजते हैं:

  • Reference count objects: अगर आप retain/release counters में छेड़छाड़ कर सकें, तो use-after-free पैदा हो सकता है।
  • Objects with function pointers (vtables): किसी एक को corrupt करना अभी भी control flow देता है।
  • Shared memory objects (IOSurface, Mach ports): ये अभी भी attack targets हैं क्योंकि ये user ↔ kernel को bridge करते हैं।

पर — पहले की तरह — आप बस OSData spray करके यह उम्मीद नहीं कर सकते कि वह task_t के बगल में आएगा। अब आपको सफलता के लिए type-specific bugs या info leaks की ज़रूरत होती है।

Example: Allocation Flow in Modern Heap

Suppose userspace calls into IOKit to allocate an OSData object:

  1. Type lookupOSData maps to kalloc_type_osdata zone (size 64 bytes).
  2. Check per-CPU cache for free elements.
  • If found → return one.
  • If empty → go to global freelist.
  • If freelist empty → allocate a new slab (page of 4KB → 64 chunks of 64 bytes).
  1. Return chunk to caller.

Freelist pointer protection:

  • Each freed chunk stores the address of the next free chunk, but encoded with a secret key.
  • Overwriting that field with attacker data wont work unless you know the key.

Comparison Table

Feature Old Heap (Pre-iOS 15) Modern Heap (iOS 15+ / A12+)
Allocation granularity Fixed size buckets (kalloc.16, kalloc.32, etc.) Size + type-based buckets (kalloc_type)
Placement predictability High (same-size objects side by side) Low (same-type grouping + randomness)
Freelist management Raw pointers in freed chunks (easy to corrupt) Encoded pointers (safe-linking style)
Adjacent object control Easy via sprays/frees (feng shui predictable) Hard — typed zones separate attacker objects
Kernel data/code protections Few hardware protections PPL / SPTM protect page tables & code pages
Exploit reliability High with heap sprays Much lower, requires logic bugs or info leaks

(Old) Physical Use-After-Free via IOSurface

{{#ref}} ios-physical-uaf-iosurface.md {{#endref}}


Ghidra Install BinDiff

Download BinDiff DMG from https://www.zynamics.com/bindiff/manual and install it.

Open Ghidra with ghidraRun and go to File --> Install Extensions, press the add button and select the path /Applications/BinDiff/Extra/Ghidra/BinExport and click OK and isntall it even if there is a version mismatch.

Using BinDiff with Kernel versions

  1. Go to the page https://ipsw.me/ and download the iOS versions you want to diff. These will be .ipsw files.
  2. Decompress until you get the bin format of the kernelcache of both .ipsw files. You have information on how to do this on:

{{#ref}} ../../macos-hardening/macos-security-and-privilege-escalation/mac-os-architecture/macos-kernel-extensions.md {{#endref}}

  1. Open Ghidra with ghidraRun, create a new project and load the kernelcaches.
  2. Open each kernelcache so they are automatically analyzed by Ghidra.
  3. Then, on the project Window of Ghidra, right click each kernelcache, select Export, select format Binary BinExport (v2) for BinDiff and export them.
  4. Open BinDiff, create a new workspace and add a new diff indicating as primary file the kernelcache that contains the vulnerability and as secondary file the patched kernelcache.

Finding the right XNU version

If you want to check for vulnerabilities in a specific version of iOS, you can check which XNU release version the iOS version uses at [https://www.theiphonewiki.com/wiki/kernel]https://www.theiphonewiki.com/wiki/kernel).

For example, the versions 15.1 RC, 15.1 and 15.1.1 use the version Darwin Kernel Version 21.1.0: Wed Oct 13 19:14:48 PDT 2021; root:xnu-8019.43.1~1/RELEASE_ARM64_T8006.

{{#include ../../banners/hacktricks-training.md}}