iOS Exploiting
Reading time: 15 minutes
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Mitigazioni degli exploit su iOS
- Code Signing in iOS funziona richiedendo che ogni pezzo di codice eseguibile (apps, libraries, extensions, etc.) sia firmato crittograficamente con un certificato rilasciato da Apple. Quando il codice viene caricato, iOS verifica la firma digitale rispetto alla root di trust di Apple. Se la firma è invalida, mancante o modificata, il sistema rifiuta l'esecuzione. Questo impedisce agli attaccanti di iniettare codice malevolo in app legittime o eseguire eseguibili non firmati, bloccando efficacemente la maggior parte delle catene di exploit che si basano sull'esecuzione di codice arbitrario o manomesso.
- CoreTrust è il sottosistema iOS responsabile dell'enforcement della code signing a runtime. Verifica direttamente le firme usando il certificato root di Apple senza fare affidamento su store di trust cacheati, il che significa che solo i binari firmati da Apple (o con entitlements validi) possono essere eseguiti. CoreTrust garantisce che anche se un attaccante manomette un'app dopo l'installazione, modifica librerie di sistema o tenta di caricare codice non firmato, il sistema bloccherà l'esecuzione a meno che il codice non sia ancora correttamente firmato. Questa applicazione rigorosa chiude molte vie di post-exploitation che versioni iOS più vecchie permettevano tramite controlli di firma più deboli o bypassabili.
- Data Execution Prevention (DEP) marca le regioni di memoria come non eseguibili a meno che non contengano esplicitamente codice. Questo impedisce agli attaccanti di iniettare shellcode in regioni dati (come stack o heap) e eseguirlo, costringendoli a usare tecniche più complesse come ROP (Return-Oriented Programming).
- ASLR (Address Space Layout Randomization) randomizza gli indirizzi di memoria di codice, librerie, stack e heap a ogni esecuzione del sistema. Questo rende molto più difficile per gli attaccanti prevedere dove si trovino istruzioni utili o gadget, interrompendo molte catene di exploit che dipendono da layout di memoria fissi.
- KASLR (Kernel ASLR) applica lo stesso concetto di randomizzazione al kernel iOS. Mescolando l'indirizzo base del kernel a ogni boot, impedisce agli attaccanti di localizzare in modo affidabile funzioni o strutture del kernel, aumentando la difficoltà degli exploit a livello kernel che altrimenti otterrebbero il controllo completo del sistema.
- Kernel Patch Protection (KPP), noto anche come AMCC (Apple Mobile File Integrity) su iOS, monitora continuamente le pagine di codice del kernel per assicurarsi che non siano state modificate. Se viene rilevata qualsiasi manomissione — come un exploit che tenta di patchare funzioni del kernel o inserire codice malevolo — il dispositivo va immediatamente in panic e si riavvia. Questa protezione rende molto più difficili gli exploit persistenti a livello kernel, dato che gli attaccanti non possono semplicemente hookare o patchare istruzioni del kernel senza causare un crash del sistema.
- Kernel Text Readonly Region (KTRR) è una feature hardware-based introdotta sui dispositivi iOS. Usa il memory controller della CPU per marcare la sezione di codice (text) del kernel come permanentemente in sola lettura dopo il boot. Una volta bloccata, nemmeno il kernel stesso può modificare quella regione di memoria. Questo impedisce agli attaccanti — e persino al codice privilegiato — di patchare istruzioni del kernel a runtime, chiudendo una grande classe di exploit che si basavano sulla modifica diretta del codice kernel.
- Pointer Authentication Codes (PAC) usano firme crittografiche incise nei bit non usati dei pointer per verificarne l'integrità prima dell'uso. Quando un pointer (come un return address o un function pointer) viene creato, la CPU lo firma con una chiave segreta; prima di dereferenziare, la CPU controlla la firma. Se il pointer è stato manomesso, il controllo fallisce e l'esecuzione si interrompe. Questo impedisce agli attaccanti di forgiare o riutilizzare pointer corrotti in exploit di corruption della memoria, rendendo tecniche come ROP o JOP molto più difficili da eseguire in modo affidabile.
- Privilege Access never (PAN) è una feature hardware che impedisce al kernel (modalità privilegiata) di accedere direttamente alla memoria user-space a meno che non abiliti esplicitamente l'accesso. Questo blocca gli attaccanti che hanno ottenuto esecuzione di codice in kernel mode dal leggere o scrivere facilmente la memoria utente per escalare privilegi o rubare dati sensibili. Rafforzando la separazione, PAN riduce l'impatto degli exploit a livello kernel e blocca molte tecniche comuni di escalation di privilegio.
- Page Protection Layer (PPL) è un meccanismo di sicurezza iOS che protegge regioni critiche della memoria gestite dal kernel, specialmente quelle relative a code signing ed entitlements. Applica protezioni di scrittura rigide usando la MMU (Memory Management Unit) e controlli aggiuntivi, assicurando che anche il codice kernel privilegiato non possa modificare arbitrariamente pagine sensibili. Questo impedisce agli attaccanti che ottengono esecuzione a livello kernel di manomettere strutture critiche per la sicurezza, rendendo la persistenza e i bypass della code-signing significativamente più difficili.
Vecchio kernel heap (era Pre-iOS 15 / Pre-A12)
Il kernel usava un zone allocator (kalloc
) diviso in "zone" di dimensione fissa.
Ogni zone memorizzava solo allocazioni di una singola class di dimensione.
Dallo screenshot:
Zone Name | Element Size | Example Use |
---|---|---|
default.kalloc.16 | 16 bytes | Strutture molto piccole del kernel, puntatori. |
default.kalloc.32 | 32 bytes | Strutture piccole, header di oggetti. |
default.kalloc.64 | 64 bytes | IPC messages, buffer kernel minuscoli. |
default.kalloc.128 | 128 bytes | Oggetti di media dimensione come parti di OSObject . |
… | … | … |
default.kalloc.1280 | 1280 bytes | Strutture grandi, metadata di IOSurface/graphics. |
Come funzionava:
- Ogni richiesta di allocazione veniva arrotondata per eccesso alla dimensione della zone più vicina.
(E.g., una richiesta di 50 byte finiva nella zona
kalloc.64
). - La memoria in ogni zone veniva mantenuta in una freelist — i chunk liberati dal kernel tornavano in quella zona.
- Se overflowavi un buffer da 64 byte, sovrascrivevi il prossimo oggetto nella stessa zone.
Per questo motivo lo heap spraying / feng shui era così efficace: potevi prevedere i vicini degli oggetti spruzzando allocazioni della stessa class di dimensione.
The freelist
All'interno di ogni zona kalloc, gli oggetti liberati non venivano restituiti direttamente al sistema — andavano in una freelist, una linked list di chunk disponibili.
-
Quando un chunk veniva liberato, il kernel scriveva un pointer all'inizio di quel chunk → l'indirizzo del prossimo chunk libero nella stessa zona.
-
La zone manteneva un puntatore HEAD al primo chunk libero.
-
L'allocazione usava sempre l'HEAD corrente:
-
Pop HEAD (restituisci quella memoria al chiamante).
-
Aggiorna HEAD = HEAD->next (memorizzato nell'header del chunk liberato).
-
Il free pushava i chunk indietro:
-
freed_chunk->next = HEAD
-
HEAD = freed_chunk
Quindi la freelist era semplicemente una linked list costruita dentro la memoria liberata stessa.
Stato normale:
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)
Sfruttare la freelist
Poiché i primi 8 byte di un free chunk = freelist pointer, un attacker potrebbe corromperlo:
-
Heap overflow in un freed chunk adiacente → sovrascrivere il suo “next” pointer.
-
Use-after-free scrittura in un freed object → sovrascrivere il suo “next” pointer.
Poi, alla successiva allocazione di quella dimensione:
-
L'allocator estrae il chunk corrotto.
-
Segue il “next” pointer fornito dall'attacker.
-
Restituisce un puntatore a memoria arbitraria, permettendo fake object primitives o targeted overwrite.
Esempio visivo di 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:
- 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.
- 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.
- 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).
- 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.
- 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.
- 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:
- Try per-CPU cache.
- If empty, pull from the global freelist.
- 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:
- Type lookup →
OSData
maps tokalloc_type_osdata
zone (size 64 bytes). - 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).
- 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
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
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
- Go to the page https://ipsw.me/ and download the iOS versions you want to diff. These will be
.ipsw
files. - 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
- Open Ghidra with
ghidraRun
, create a new project and load the kernelcaches. - Open each kernelcache so they are automatically analyzed by Ghidra.
- Then, on the project Window of Ghidra, right click each kernelcache, select
Export
, select formatBinary BinExport (v2) for BinDiff
and export them. - 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
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.