iOS Exploiting

Reading time: 14 minutes

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

iOS Exploit Mitigations

  • Code Signing in iOS funktioniert so, dass jeder ausführbare Code (Apps, Libraries, Extensions usw.) kryptografisch mit einem von Apple ausgestellten Zertifikat signiert sein muss. Beim Laden von Code überprüft iOS die digitale Signatur gegen Apples vertrauenswürdige Root. Ist die Signatur ungültig, fehlt oder wurde verändert, verweigert das OS die Ausführung. Das verhindert, dass Angreifer bösartigen Code in legitime Apps injizieren oder unsigned Binaries ausführen, und unterbricht somit die meisten Exploit-Ketten, die auf das Ausführen beliebigen oder manipulierten Codes angewiesen sind.
  • CoreTrust ist das iOS-Subsystem, das die Code-Signierung zur Laufzeit durchsetzt. Es verifiziert Signaturen direkt mit Apples Root-Zertifikat, ohne sich auf zwischengespeicherte Trust-Stores zu verlassen, was bedeutet, dass nur Binärdateien, die von Apple signiert sind (oder gültige Entitlements besitzen), ausgeführt werden können. CoreTrust stellt sicher, dass selbst wenn ein Angreifer eine App nach der Installation manipuliert, Systembibliotheken ändert oder versucht, unsigned Code zu laden, die Ausführung blockiert wird, es sei denn, der Code ist weiterhin korrekt signiert. Diese strikte Durchsetzung schließt viele Post-Exploitation-Vektoren, die ältere iOS-Versionen durch schwächere oder umgehbare Signaturprüfungen zuließen.
  • Data Execution Prevention (DEP) markiert Speicherbereiche als non-executable, sofern sie nicht explizit Code enthalten. Das verhindert, dass Angreifer Shellcode in Datenregionen (wie Stack oder Heap) injizieren und ausführen, und zwingt sie, auf komplexere Techniken wie ROP (Return-Oriented Programming) zurückzugreifen.
  • ASLR (Address Space Layout Randomization) randomisiert die Speicheradressen von Code, Libraries, Stack und Heap bei jedem Systemstart. Dadurch wird es für Angreifer deutlich schwieriger vorherzusagen, wo nützliche Instruktionen oder Gadgets liegen, was viele Exploit-Ketten, die von festen Speicherlayouts abhängen, bricht.
  • KASLR (Kernel ASLR) wendet dasselbe Randomisierungskonzept auf den iOS-Kernel an. Durch das Verschieben der Kernel-Basisadresse bei jedem Boot wird verhindert, dass Angreifer zuverlässig Kernel-Funktionen oder -Strukturen lokalisieren, was die Schwierigkeit von Kernel-Level-Exploits erhöht, die sonst die volle Systemkontrolle erlangen würden.
  • Kernel Patch Protection (KPP), auch bekannt als AMCC (Apple Mobile File Integrity) in iOS, überwacht kontinuierlich die Code-Seiten des Kernels, um sicherzustellen, dass diese nicht modifiziert wurden. Wird Manipulation festgestellt — z. B. wenn ein Exploit versucht, Kernel-Funktionen zu patchen oder bösartigen Code einzufügen — wird das Gerät sofort panicen und neu starten. Dieser Schutz macht persistente Kernel-Exploits deutlich schwieriger, da Angreifer Kernel-Instruktionen nicht einfach hooken oder patchen können, ohne einen Systemcrash auszulösen.
  • Kernel Text Readonly Region (KTRR) ist eine hardwarebasierte Sicherheitsfunktion, die auf iOS-Geräten eingeführt wurde. Sie nutzt den Speichercontroller der CPU, um den Kernel-Code (text section) nach dem Boot permanent als read-only zu markieren. Sobald diese Region gesperrt ist, kann selbst der Kernel diesen Speicher nicht mehr modifizieren. Das verhindert, dass Angreifer — und sogar privilegierter Code — Kernel-Instruktionen zur Laufzeit patchen, und schließt eine große Klasse von Exploits, die direkt Kernel-Code verändert haben.
  • Pointer Authentication Codes (PAC) verwenden kryptografische Signaturen, die in ungenutzte Bits von Pointern eingebettet werden, um deren Integrität vor der Nutzung zu verifizieren. Wenn ein Pointer (z. B. eine return address oder ein Function Pointer) erstellt wird, signiert die CPU ihn mit einem geheimen Schlüssel; vor dem Dereferenzieren prüft die CPU die Signatur. Wurde der Pointer manipuliert, schlägt die Prüfung fehl und die Ausführung wird gestoppt. Das verhindert, dass Angreifer gefälschte oder korrupte Pointer in Memory-Corruption-Exploits verwenden, und macht Techniken wie ROP oder JOP erheblich schwerer zuverlässig einzusetzen.
  • Privilege Access never (PAN) ist eine Hardware-Funktion, die verhindert, dass der Kernel (privilegierter Modus) direkt auf User-Space-Speicher zugreift, es sei denn, er aktiviert den Zugriff explizit. Das stoppt Angreifer, die Kernel-Code-Ausführung erlangt haben, daran, problemlos User-Memory zu lesen oder zu schreiben, um Exploits zu eskalieren oder sensible Daten zu stehlen. Durch die strikte Trennung reduziert PAN die Auswirkungen von Kernel-Exploits und blockiert viele gängige Privilege-Escalation-Techniken.
  • Page Protection Layer (PPL) ist ein iOS-Sicherheitsmechanismus, der kritische kernelverwaltete Speicherbereiche schützt, insbesondere solche, die mit Code Signing und Entitlements zu tun haben. Er erzwingt strikte Schreibschutzmaßnahmen mittels MMU (Memory Management Unit) und zusätzlichen Prüfungen, sodass selbst privilegierter Kernel-Code sensible Pages nicht beliebig modifizieren kann. Das verhindert, dass Angreifer mit Kernel-Privilegien sicherheitskritische Strukturen manipulieren, und erschwert Persistence-Mechanismen und Code-Signing-Bypässe erheblich.

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

Der Kernel verwendete einen zone allocator (kalloc), aufgeteilt in feste "Zonen". Jede Zone speicherte nur Allokationen einer einzelnen Größenklasse.

Aus dem Screenshot:

Zone NameElement SizeExample Use
default.kalloc.1616 bytesVery small kernel structs, pointers.
default.kalloc.3232 bytesSmall structs, object headers.
default.kalloc.6464 bytesIPC messages, tiny kernel buffers.
default.kalloc.128128 bytesMedium objects like parts of OSObject.
default.kalloc.12801280 bytesLarge structures, IOSurface/graphics metadata.

Wie es funktionierte:

  • Jede Allokationsanforderung wurde auf die nächsthöhere Zone-Größe aufgerundet. (z. B. landet eine 50-Byte-Anfrage in der kalloc.64-Zone).
  • Der Speicher in jeder Zone wurde in einer freelist gehalten — vom Kernel freigegebene Chunks gingen zurück in diese Zone.
  • Wenn du einen 64-Byte-Buffer overflowtest, würdest du das nächste Objekt in derselben Zone überschreiben.

Deshalb war heap spraying / feng shui so effektiv: man konnte Objekt-Nachbarn vorhersagen, indem man Allokationen derselben Größenklasse sprühte.

The freelist

Innerhalb jeder kalloc-Zone wurden freigegebene Objekte nicht direkt an das System zurückgegeben — sie landeten in einer freelist, einer verketteten Liste verfügbarer Chunks.

  • Wenn ein Chunk freigegeben wurde, schrieb der Kernel einen Pointer an den Anfang dieses Chunks → die Adresse des nächsten freien Chunks in derselben Zone.

  • Die Zone hielt einen HEAD-Pointer auf den ersten freien Chunk.

  • Bei einer Allokation wurde immer das aktuelle HEAD verwendet:

  1. Pop HEAD (gibt diesen Speicher an den Aufrufer zurück).

  2. Update HEAD = HEAD->next (gespeichert im Header des freigegebenen Chunks).

  • Freigeben drückte Chunks zurück auf die Liste:

  • freed_chunk->next = HEAD

  • HEAD = freed_chunk

Also war die freelist einfach eine verkettete Liste, die im freigegebenen Speicher selbst aufgebaut wurde.

Normalzustand:

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)

Exploiting the freelist

Da die ersten 8 Bytes eines free chunk dem freelist pointer entsprechen, könnte ein Angreifer diesen beschädigen:

  1. Heap overflow in einen angrenzenden freed chunk → überschreibt dessen “next” pointer.

  2. Use-after-free: Schreiben in ein freed object → überschreibt dessen “next” pointer.

Dann, bei der nächsten Allocation dieser Größe:

  • Der allocator entnimmt den korrupten chunk.

  • Folgt dem vom Angreifer gelieferten “next” pointer.

  • Gibt einen Pointer auf beliebigen Speicher zurück, wodurch fake object primitives oder gezielte Overwrites möglich werden.

Visuelles Beispiel für 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 made exploitation highly effective pre-hardening: predictable neighbors from heap sprays, raw pointer freelist links, and no type separation allowed attackers to escalate UAF/overflow bugs into arbitrary kernel memory control.

Heap Grooming / Feng Shui

The goal of heap grooming is to shape the heap layout so that when an attacker triggers an overflow or use-after-free, the target (victim) object sits right next to an attacker-controlled object.
That way, when memory corruption happens, the attacker can reliably overwrite the victim object with controlled data.

Steps:

  1. Spray allocations (fill the holes)
  • Over time, the kernel heap gets fragmented: some zones have holes where old objects were freed.
  • The attacker first makes lots of dummy allocations to fill these gaps, so the heap becomes “packed” and predictable.
  1. Force new pages
  • Once the holes are filled, the next allocations must come from new pages added to the zone.
  • Fresh pages mean objects will be clustered together, not scattered across old fragmented memory.
  • This gives the attacker much better control of neighbors.
  1. Place attacker objects
  • The attacker now sprays again, creating lots of attacker-controlled objects in those new pages.
  • These objects are predictable in size and placement (since they all belong to the same zone).
  1. Free a controlled object (make a gap)
  • The attacker deliberately frees one of their own objects.
  • This creates a “hole” in the heap, which the allocator will later reuse for the next allocation of that size.
  1. Victim object lands in the hole
  • The attacker triggers the kernel to allocate the victim object (the one they want to corrupt).
  • Since the hole is the first available slot in the freelist, the victim is placed exactly where the attacker freed their object.
  1. Overflow / UAF into victim
  • Now the attacker has attacker-controlled objects around the victim.
  • By overflowing from one of their own objects (or reusing a freed one), they can reliably overwrite the victim’s memory fields with chosen values.

Why it works:

  • Zone allocator predictability: allocations of the same size always come from the same zone.
  • Freelist behavior: new allocations reuse the most recently freed chunk first.
  • Heap sprays: attacker fills memory with predictable content and controls layout.
  • End result: attacker controls where the victim object lands and what data sits next to it.

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

Apple hardened the allocator and made heap grooming much harder:

1. From Classic kalloc to kalloc_type

  • Before: a single kalloc.<size> zone existed for each size class (16, 32, 64, … 1280, etc.). Any object of that size was placed there → attacker objects could sit next to privileged kernel objects.
  • Now:
  • Kernel objects are allocated from typed zones (kalloc_type).
  • Each type of object (e.g., ipc_port_t, task_t, OSString, OSData) has its own dedicated zone, even if they’re the same size.
  • The mapping between object type ↔ zone is generated from the kalloc_type system at compile time.

An attacker can no longer guarantee that controlled data (OSData) ends up adjacent to sensitive kernel objects (task_t) of the same size.

2. Slabs and Per-CPU Caches

  • The heap is divided into slabs (pages of memory carved into fixed-size chunks for that zone).
  • Each zone has a per-CPU cache to reduce contention.
  • Allocation path:
  1. Try per-CPU cache.
  2. If empty, pull from the global freelist.
  3. If freelist is empty, allocate a new slab (one or more pages).
  • Benefit: This decentralization makes heap sprays less deterministic, since allocations may be satisfied from different CPUs’ caches.

3. Randomization inside zones

  • Within a zone, freed elements are not handed back in simple FIFO/LIFO order.
  • Modern XNU uses encoded freelist pointers (safe-linking like Linux, introduced ~iOS 14).
  • Each freelist pointer is XOR-encoded with a per-zone secret cookie.
  • This prevents attackers from forging a fake freelist pointer if they gain a write primitive.
  • Some allocations are randomized in their placement within a slab, so spraying doesn’t guarantee adjacency.

4. Guarded Allocations

  • Certain critical kernel objects (e.g., credentials, task structures) are allocated in guarded zones.
  • These zones insert guard pages (unmapped memory) between slabs or use redzones around objects.
  • Any overflow into the guard page triggers a fault → immediate panic instead of silent corruption.

5. Page Protection Layer (PPL) and SPTM

  • Even if you control a freed object, you can’t modify all of kernel memory:
  • PPL (Page Protection Layer) enforces that certain regions (e.g., code signing data, entitlements) are read-only even to the kernel itself.
  • On A15/M2+ devices, this role is replaced/enhanced by SPTM (Secure Page Table Monitor) + TXM (Trusted Execution Monitor).
  • These hardware-enforced layers mean attackers can’t escalate from a single heap corruption to arbitrary patching of critical security structures.

6. Large Allocations

  • Not all allocations go through kalloc_type.
  • Very large requests (above ~16KB) bypass typed zones and are served directly from kernel VM (kmem) via page allocations.
  • These are less predictable, but also less exploitable, since they don’t share slabs with other objects.

7. Allocation Patterns Attackers Target

Even with these protections, attackers still look for:

  • Reference count objects: if you can tamper with retain/release counters, you may cause use-after-free.
  • Objects with function pointers (vtables): corrupting one still yields control flow.
  • Shared memory objects (IOSurface, Mach ports): these are still attack targets because they bridge user ↔ kernel.

But — unlike before — you can’t just spray OSData and expect it to neighbor a task_t. You need type-specific bugs or info leaks to succeed.

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 won’t work unless you know the key.

Comparison Table

FeatureOld Heap (Pre-iOS 15)Modern Heap (iOS 15+ / A12+)
Allocation granularityFixed size buckets (kalloc.16, kalloc.32, etc.)Size + type-based buckets (kalloc_type)
Placement predictabilityHigh (same-size objects side by side)Low (same-type grouping + randomness)
Freelist managementRaw pointers in freed chunks (easy to corrupt)Encoded pointers (safe-linking style)
Adjacent object controlEasy via sprays/frees (feng shui predictable)Hard — typed zones separate attacker objects
Kernel data/code protectionsFew hardware protectionsPPL / SPTM protect page tables & code pages
Exploit reliabilityHigh with heap spraysMuch lower, requires logic bugs or info leaks

(Old) Physical Use-After-Free via IOSurface

ios Physical UAF - IOSurface


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:

macOS Kernel Extensions & Debugging

  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.

iMessage/Media Parser Zero-Click Chains

Imessage Media Parser Zero Click Coreaudio Pac Bypass

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks