iOS Exploiting
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.
iOS Exploit Mitigations
1. Code Signing / Verifica della firma a runtime
Introdotto presto (iPhone OS → iOS) Questa è una delle protezioni fondamentali: all executable code (apps, dynamic libraries, JIT-ed code, extensions, frameworks, caches) deve essere firmato crittograficamente da una catena di certificati radicata nella trust di Apple. A runtime, prima di caricare un binario in memoria (o prima di effettuare salti attraverso certi confini), il sistema verifica la sua firma. Se il codice è modificato (bit-flipped, patched) o non firmato, il caricamento fallisce.
- Blocca: la fase “classic payload drop + execute” nelle exploit chains; arbitrary code injection; modificare un binary esistente per inserire logica malevola.
- Dettagli sul meccanismo:
- Il Mach-O loader (e dynamic linker) controlla le code pages, i segmenti, gli entitlements, i team IDs, e che la signature copra il contenuto del file.
- Per regioni di memoria come JIT caches o codice generato dinamicamente, Apple impone che le pagine siano firmate o validate tramite API speciali (es.
mprotectcon controlli di code-sign). - La signature include entitlements e identificatori; l’OS impone che certe API o capacità privilegiate richiedano entitlements specifici che non possono essere falsificati.
Example
Supponiamo che un exploit ottenga code execution in un processo e provi a scrivere shellcode nell’heap e saltare lì. Su iOS, quella pagina dovrebbe essere marcata executable **e** soddisfare i vincoli di code-signature. Poiché lo shellcode non è firmato con il certificato di Apple, il salto fallisce o il sistema rifiuta di rendere eseguibile quella regione di memoria.2. CoreTrust
Introdotto intorno all’era iOS 14+ (o gradualmente su dispositivi più nuovi / versioni iOS successive) CoreTrust è il sottosistema che esegue la validazione della firma a runtime dei binari (inclusi system e user binaries) rispetto alla root certificate di Apple invece di fare affidamento su store di trust in userland cacheati.
- Blocca: manomissioni post-installazione dei binari, tecniche di jailbreaking che provano a sostituire o patchare system libraries o user apps; ingannare il sistema sostituendo binari trusted con controparti malevole.
- Dettagli sul meccanismo:
- Invece di fidarsi di un database di trust locale o cache di certificati, CoreTrust recupera o si riferisce alla root di Apple direttamente o verifica certificati intermedi in una catena sicura.
- Assicura che modifiche (es. nel filesystem) ai binari esistenti siano rilevate e rifiutate.
- Leghi entitlements, team IDs, code signing flags e altri metadata al binario al momento del load.
Example
Un jailbreak potrebbe provare a sostituire `SpringBoard` o `libsystem` con una versione patchata per ottenere persistenza. Ma quando il loader dell’OS o CoreTrust verifica, nota il mismatch di signature (o entitlements modificati) e rifiuta di eseguire.3. Data Execution Prevention (DEP / NX / W^X)
Introdotto in molti OS prima; iOS ha avuto NX-bit / w^x da molto tempo DEP impone che le pagine marcate writable (per i dati) siano non-executable, e le pagine marcate executable siano non-writable. Non si può semplicemente scrivere shellcode in heap o stack e poi eseguirlo.
- Blocca: direct shellcode execution; il classico buffer-overflow → jump to injected shellcode.
- Dettagli sul meccanismo:
- La MMU / i flag di protezione della memoria (tramite page tables) impongono la separazione.
- Qualsiasi tentativo di marcare una pagina writable come executable attiva un controllo di sistema (ed è o proibito o richiede approvazione di code-sign).
- In molti casi, rendere pagine executable richiede l’uso di API di sistema che applicano vincoli e controlli aggiuntivi.
Example
Un overflow scrive shellcode nell’heap. L’attaccante prova `mprotect(heap_addr, size, PROT_EXEC)` per renderla eseguibile. Ma il sistema rifiuta o valida che la nuova pagina debba passare i vincoli di code-sign (cosa che lo shellcode non può fare).4. Address Space Layout Randomization (ASLR)
Introdotto in iOS ~4–5 era (approssimativamente iOS 4–5) ASLR randomizza gli indirizzi base di regioni di memoria chiave: libraries, heap, stack, ecc., ad ogni lancio del processo. Gli indirizzi dei gadget si spostano tra le esecuzioni.
- Blocca: hardcoding degli indirizzi dei gadget per ROP/JOP; exploit chain statiche; salti ciechi a offset noti.
- Dettagli sul meccanismo:
- Ogni libreria / modulo dinamico caricato viene rebased a un offset randomizzato.
- Stack e heap base pointers sono randomizzati (entro certi limiti di entropia).
- A volte altre regioni (es. mmap allocations) sono anch’esse randomizzate.
- Combinato con mitigazioni di information- leak, forza l’attaccante a prima leak un indirizzo o un puntatore per scoprire le base addresses a runtime.
Example
Una ROP chain si aspetta un gadget a `0x….lib + offset`. Ma poiché `lib` viene ridefinita in modo diverso ad ogni esecuzione, la chain hardcodata fallisce. Un exploit deve prima leak la base address del modulo prima di calcolare gli indirizzi dei gadget.5. Kernel Address Space Layout Randomization (KASLR)
Introdotto in iOS ~ (iOS 5 / iOS 6 timeframe) Analogo a user ASLR, KASLR randomizza la base del kernel text e altre strutture kernel al boot.
- Blocca: kernel-level exploits che si affidano a locazioni fisse di kernel code o data; exploit kernel statici.
- Dettagli sul meccanismo:
- Ad ogni boot, la base del kernel viene randomizzata (entro un intervallo).
- Strutture dati del kernel (come
task_structs,vm_map, ecc.) possono essere relocate o spostate. - Gli attaccanti devono prima leak kernel pointers o usare vulnerabilità di information disclosure per calcolare gli offset prima di hijackare strutture o codice del kernel.
Example
Una vulnerabilità locale mira a corrompere un function pointer del kernel (es. in una `vtable`) a `KERN_BASE + offset`. Ma dato che `KERN_BASE` è sconosciuto, l’attaccante deve prima leakarlo (es. via una primitive di read) prima di calcolare l’indirizzo corretto da corrompere.6. Kernel Patch Protection (KPP / AMCC)
Introdotto in iOS più recenti / hardware A-series (dopo circa iOS 15–16 o sui chip più nuovi) KPP (aka AMCC) monitora continuamente l’integrità delle kernel text pages (via hash o checksum). Se rileva manomissioni (patch, inline hooks, modifiche al codice) fuori da finestre consentite, innesca un kernel panic o reboot.
- Blocca: persistent kernel patching (modificare istruzioni del kernel), inline hooks, sovrascritture statiche di funzioni.
- Dettagli sul meccanismo:
- Un modulo hardware o firmware monitora la regione di kernel text.
- Periodicamente o su richiesta ricalcola l’hash delle pagine e lo confronta con i valori attesi.
- Se si verificano mismatch fuori da finestre di update legittime, manda in panic il device (per evitare patch persistenti).
- Gli attaccanti devono o evitare le finestre di rilevamento o usare percorsi di patch legittimi.
Example
Un exploit prova a patchare il prologo di una funzione kernel (es. `memcmp`) per intercettare le chiamate. Ma KPP nota che l’hash della pagina di codice non corrisponde al valore atteso e scatena un kernel panic, bloccando il device prima che la patch si stabilizzi.7. Kernel Text Read‐Only Region (KTRR)
Introdotto in SoC moderni (post ~A12 / hardware più recenti) KTRR è un meccanismo hardware-enforced: una volta che il kernel text è locked early durante il boot, diventa read-only da EL1 (il kernel), prevenendo ulteriori scritture sulle code pages.
- Blocca: qualsiasi modifica al kernel code dopo il boot (es. patching, code injection in-place) a livello di privilegio EL1.
- Dettagli sul meccanismo:
- Durante il boot (nella fase secure/bootloader), il memory controller (o un’unità hardware sicura) marca le physical pages contenenti kernel text come read-only.
- Anche se un exploit ottiene privilegi kernel completi, non può scrivere su quelle pagine per patchare istruzioni.
- Per modificarle, l’attaccante deve prima compromettere la boot chain o sovvertire KTRR stesso.
Example
Un exploit di elevazione privilegi salta in EL1 e scrive un trampoline in una funzione kernel (es. nel handler di `syscall`). Ma poiché le pagine sono bloccate read-only da KTRR, la scrittura fallisce (o genera fault), quindi le patch non vengono applicate.8. Pointer Authentication Codes (PAC)
Introdotto con ARMv8.3 (hardware), Apple a partire da A12 / iOS ~12+
- PAC è una caratteristica hardware introdotta in ARMv8.3-A per rilevare la manomissione di valori di puntatore (return addresses, function pointers, certi data pointers) incorporando una piccola firma crittografica (un “MAC”) nei bit alti inutilizzati del puntatore.
- La signature (“PAC”) è calcolata sul valore del puntatore più un modifier (un valore di contesto, es. stack pointer o qualche dato distintivo). In questo modo lo stesso valore di puntatore in contesti diversi ottiene PAC differenti.
- Al momento dell’uso, prima di dereferenziare o fare branch via quel puntatore, un’istruzione di authenticate verifica la PAC. Se valida, la PAC viene rimossa e si ottiene il puntatore puro; se non valida, il puntatore diventa “poisoned” (o viene generato un fault).
- Le chiavi usate per produrre/validare le PAC vivono in registri privilegiati (EL1, kernel) e non sono leggibili da user mode.
- Poiché non tutti i 64 bit di un puntatore sono usati in molti sistemi (es. spazio indirizzi a 48-bit), i bit superiori sono “spare” e possono contenere la PAC senza alterare l’indirizzo effettivo.
Basi architetturali & Tipi di chiavi
-
ARMv8.3 introduce cinque chiavi a 128-bit (ciascuna implementata tramite due registri di sistema a 64-bit) per pointer authentication.
-
APIAKey — per instruction pointers (dominio “I”, key A)
-
APIBKey — seconda key per instruction pointers (dominio “I”, key B)
-
APDAKey — per data pointers (dominio “D”, key A)
-
APDBKey — per data pointers (dominio “D”, key B)
-
APGAKey — key “generic”, per firmare dati non-pointer o altri usi generici
-
Queste chiavi sono memorizzate in registri di sistema privilegiati (accessibili solo a EL1/EL2 ecc.), non accessibili da user mode.
-
La PAC è calcolata tramite una funzione crittografica (ARM suggerisce QARMA come algoritmo) usando:
- Il valore del puntatore (porzione canonica)
- Un modifier (un valore di contesto, come un salt)
- La secret key
- Alcuna logica interna di tweak Se la PAC risultante corrisponde a quella immagazzinata nei bit alti del puntatore, l’autenticazione riesce.
Famiglie di istruzioni
La convenzione di nomenclatura è: PAC / AUT / XPAC, poi lettere di dominio.
PACxxistruzioni signano un puntatore e inseriscono una PACAUTxxistruzioni autenticano + rimuovono (validate e strip) la PACXPACxxistruzioni rimuovono senza validare
Domini / suffissi:
| Mnemonic | Meaning / Domain | Key / Domain | Example Usage in Assembly |
|---|---|---|---|
| PACIA | Sign instruction pointer with APIAKey | “I, A” | PACIA X0, X1 — sign pointer in X0 using APIAKey with modifier X1 |
| PACIB | Sign instruction pointer with APIBKey | “I, B” | PACIB X2, X3 |
| PACDA | Sign data pointer with APDAKey | “D, A” | PACDA X4, X5 |
| PACDB | Sign data pointer with APDBKey | “D, B” | PACDB X6, X7 |
| PACG / PACGA | Firma generica (non-pointer) con APGAKey | “G” | PACGA X8, X9, X10 (sign X9 with modifier X10 into X8) |
| AUTIA | Authenticate APIA-signed instruction pointer & strip PAC | “I, A” | AUTIA X0, X1 — check PAC on X0 using modifier X1, then strip |
| AUTIB | Authenticate APIB domain | “I, B” | AUTIB X2, X3 |
| AUTDA | Authenticate APDA-signed data pointer | “D, A” | AUTDA X4, X5 |
| AUTDB | Authenticate APDB-signed data pointer | “D, B” | AUTDB X6, X7 |
| AUTGA | Authenticate generic / blob (APGA) | “G” | AUTGA X8, X9, X10 (validate generic) |
| XPACI | Strip PAC (instruction pointer, no validation) | “I” | XPACI X0 — remove PAC from X0 (instruction domain) |
| XPACD | Strip PAC (data pointer, no validation) | “D” | XPACD X4 — remove PAC from data pointer in X4 |
Esistono forme specializzate / alias:
PACIASPè shorthand perPACIA X30, SP(sign the link register using SP as modifier)AUTIASPèAUTIA X30, SP(authenticate link register with SP)- Forme combinate come
RETAA,RETAB(authenticate-and-return) oBLRAA(authenticate & branch) esistono nelle estensioni ARM / supporto del compilatore. - Anche varianti con modificatore zero:
PACIZA/PACIZBdove il modifier è implicitamente zero, ecc.
Modifiers
Lo scopo principale del modifier è legare la PAC a uno specifico contesto così che lo stesso indirizzo firmato in frame o oggetti diversi produca PAC diverse. È come aggiungere un salt a un hash.
Quindi:
- Il modifier è un valore di contesto (un altro registro) che viene miscelato nella computazione della PAC. Scelte tipiche: stack pointer (
SP), frame pointer, o un object ID. - L’uso di SP come modifier è comune per il signing del return address: la PAC viene legata al frame di stack specifico. Se provi a riutilizzare LR in un frame diverso, il modifier cambia, quindi l’autenticazione PAC fallisce.
- Lo stesso valore di puntatore firmato sotto modifier differenti genera PAC differenti.
- Il modifier non deve essere segreto, ma idealmente non è controllato dall’attaccante.
- Per istruzioni che firmano o verificano puntatori dove non esiste un modifier significativo, alcune forme usano zero o una costante implicita.
Personalizzazioni & Osservazioni Apple / iOS / XNU
- L’implementazione PAC di Apple include diversificatori per boot in modo che le chiavi o i tweak cambino ad ogni boot, impedendo il riuso cross-boot.
- Includono anche mitigazioni cross-domain così che PAC firmate in user mode non possano essere facilmente riutilizzate in kernel mode, ecc.
- Su Apple M1 / Apple Silicon, il reverse engineering ha mostrato che ci sono nove tipi di modifier e registri di sistema specifici Apple per il controllo delle chiavi.
- Apple usa PAC in molti subsystem kernel: signing degli indirizzi di ritorno, integrità dei pointer nei dati kernel, signed thread contexts, ecc.
- Google Project Zero ha mostrato come con una potente primitive di memory read/write nel kernel si potessero forgiare kernel PACs (per le A keys) su dispositivi A12-era, ma Apple ha corretto molte di quelle vie.
- Nel sistema Apple, alcune chiavi sono globali nel kernel, mentre i processi utente possono ottenere randomness per le chiavi per-processo.
PAC Bypasses
- Kernel-mode PAC: teorico vs bypass reali
- Poiché le chiavi e la logica PAC del kernel sono strettamente controllate (registri privilegiati, diversificatori, isolamento dei domini), forgiare puntatori kernel firmati arbitrariamente è molto difficile.
- Azad nel 2020 in “iOS Kernel PAC, One Year Later” riporta che in iOS 12-13 trovò alcuni bypass parziali (signing gadgets, reuse di signed states, indirect branches non protetti) ma nessun bypass generico completo. bazad.github.io
- Le personalizzazioni “Dark Magic” di Apple restringono ulteriormente le superfici sfruttabili (domain switching, per-key enabling bits). i.blackhat.com
- Esiste un noto kernel PAC bypass CVE-2023-32424 su Apple silicon (M1/M2) riportato da Zecao Cai et al. i.blackhat.com
- Ma questi bypass spesso si basano su gadget molto specifici o bug di implementazione; non sono bypass generici.
Quindi il kernel PAC è considerato molto robusto, anche se non perfetto.
- User-mode / runtime PAC bypass techniques
Questi sono più comuni e sfruttano imperfezioni nell’applicazione di PAC o nel modo in cui sono usate nel dynamic linking / runtime frameworks. Di seguito classi con esempi.
2.1 Shared Cache / A key issues
-
La dyld shared cache è un grande blob pre-linked di system frameworks e libraries. Poiché è ampiamente condivisa, function pointers dentro la shared cache sono “pre-signed” e poi usate da molti processi. Gli attaccanti puntano a questi puntatori già firmati come “PAC oracles”.
-
Alcune tecniche di bypass cercano di estrarre o riutilizzare A-key signed pointers presenti nella shared cache e riusarli in gadget.
-
Il talk “No Clicks Required” descrive la costruzione di un oracle sulla shared cache per inferire indirizzi relativi e combinarli con puntatori firmati per bypassare PAC. saelo.github.io
-
Inoltre, l’import di function pointers da shared libraries in userspace è stato trovato insufficientemente protetto da PAC, permettendo a un attacker di ottenere function pointers senza cambiare la loro signature. (Project Zero bug entry) bugs.chromium.org
2.2 dlsym(3) / dynamic symbol resolution
-
Un bypass noto è chiamare
dlsym()per ottenere un pointer a funzione già signed (signed con A-key, diversifier zero) e poi usarlo. Poichédlsymritorna un puntatore legittimamente firmato, usarlo aggira la necessità di forgiare PAC. -
Il blog di Epsilon dettaglia come alcuni bypass sfruttino questo: chiamare
dlsym("someSym")restituisce un puntatore signed e può essere usato per indirect calls. blog.epsilon-sec.com -
Synacktiv in “iOS 18.4 — dlsym considered harmful” descrive un bug: alcuni simboli risolti via
dlsymsu iOS 18.4 ritornano puntatori che sono firmati in modo scorretto (o con diversificatori buggy), abilitando un bypass PAC non voluto. Synacktiv -
La logica in dyld per dlsym include: quando
result->isCode, firmano il puntatore ritornato con__builtin_ptrauth_sign_unauthenticated(..., key_asia, 0), cioè contesto zero. blog.epsilon-sec.com
Quindi, dlsym è un vettore frequente nei bypass PAC in user-mode.
2.3 Altri DYLD / runtime relocations
-
Il loader DYLD e la logica di relocation dinamica sono complesse e talvolta mappano temporaneamente pagine come read/write per eseguire le relocation, poi le riportano read-only. Gli attaccanti sfruttano queste finestre. Il talk di Synacktiv descrive “Operation Triangulation”, un bypass basato su timing delle relocation dinamiche. Synacktiv
-
Le pagine DYLD ora sono protette con SPRR / VM_FLAGS_TPRO (alcuni flag di protezione per dyld). Ma versioni precedenti avevano guardie più deboli. Synacktiv
-
Nelle exploit chains di WebKit, il DYLD loader è spesso un target per bypass PAC. Le slides menzionano che molti bypass PAC hanno preso di mira il DYLD loader (via relocation, interposer hooks). Synacktiv
2.4 NSPredicate / NSExpression / ObjC / SLOP
-
Nelle exploit chains in userland, runtime Objective-C come
NSPredicate,NSExpressionoNSInvocationsono usati per contrabbandare call di controllo senza apparente forging di pointer. -
Su iOS più vecchi (prima di PAC), un exploit usava fake NSInvocation objects per chiamare selettori arbitrari su memoria controllata. Con PAC, la tecnica richiede modifiche. Ma la tecnica SLOP (SeLector Oriented Programming) è stata estesa anche sotto PAC. Project Zero
-
La tecnica originale SLOP permetteva di concatenare chiamate ObjC creando invocations false; il bypass si basa sul fatto che ISA o selector pointers a volte non sono completamente protetti da PAC. Project Zero
-
In ambienti dove pointer authentication è applicata parzialmente, metodi / selector / target pointers possono non avere sempre protezione PAC, lasciando spazio per bypass.
Example Flow
Example Signing & Authenticating
``` ; Example: function prologue / return address protection my_func: stp x29, x30, [sp, #-0x20]! ; push frame pointer + LR mov x29, sp PACIASP ; sign LR (x30) using SP as modifier ; … body … mov sp, x29 ldp x29, x30, [sp], #0x20 ; restore AUTIASP ; authenticate & strip PAC ret; Example: indirect function pointer stored in a struct ; suppose X1 contains a function pointer PACDA X1, X2 ; sign data pointer X1 with context X2 STR X1, [X0] ; store signed pointer
; later retrieval: LDR X1, [X0] AUTDA X1, X2 ; authenticate & strip BLR X1 ; branch to valid target
; Example: stripping for comparison (unsafe) LDR X1, [X0] XPACI X1 ; strip PAC (instruction domain) CMP X1, #some_label_address BEQ matched_label
</details>
<details>
<summary>Example</summary>
A buffer overflow overwrites a return address on the stack. The attacker writes the target gadget address but cannot compute the correct PAC. When the function returns, the CPU’s `AUTIA` instruction faults because the PAC mismatch. The chain fails.
Project Zero’s analysis on A12 (iPhone XS) showed how Apple’s PAC is used and methods of forging PACs if an attacker has a memory read/write primitive.
</details>
### 9. **Identificazione del Branch Target (BTI)**
**Introdotto con ARMv8.5 (hardware più recente)**
BTI è una funzionalità hardware che controlla i **target di branch indiretti**: quando si eseguono `blr` o chiamate/salti indiretti, il target deve iniziare con un **BTI landing pad** (`BTI j` o `BTI c`). Saltare verso indirizzi di gadget che non hanno il landing pad provoca un'eccezione.
Le note di implementazione di LLVM descrivono tre varianti di istruzioni BTI e come si mappano ai tipi di branch.
| BTI Variant | What it permits (which branch types) | Typical placement / use case |
|-------------|----------------------------------------|-------------------------------|
| **BTI C** | Targets of *call*-style indirect branches (e.g. `BLR`, or `BR` using X16/X17) | Put at entry of functions that may be called indirectly |
| **BTI J** | Targets of *jump*-style branches (e.g. `BR` used for tail calls) | Placed at the beginning of blocks reachable by jump tables or tail-calls |
| **BTI JC** | Acts as both C and J | Can be targeted by either call or jump branches |
- Nel codice compilato con branch target enforcement, i compiler inseriscono un'istruzione BTI (C, J o JC) in ogni valido target di branch indiretto (inizio di funzioni o blocchi raggiungibili tramite salti) in modo che i branch indiretti abbiano successo solo verso quei punti.
- **I branch / call diretti** (ossia gli indirizzi fissi `B`, `BL`) **non sono limitati** da BTI. L'assunzione è che le pagine di codice siano trusted e che un attacker non possa modificarle (quindi i direct branch sono considerati sicuri).
- Inoltre, le istruzioni **RET / return** generalmente non sono soggette a BTI perché gli indirizzi di ritorno sono protetti tramite PAC o meccanismi di return signing.
#### Meccanismo e enforcement
- Quando la CPU decodifica un **indirect branch (BLR / BR)** in una pagina marcata come “guarded / BTI-enabled”, verifica se la prima istruzione dell'indirizzo di destinazione è un BTI valido (C, J o JC a seconda di quanto permesso). Se non lo è, si verifica una **Branch Target Exception**.
- L'encoding delle istruzioni BTI è progettato per riutilizzare opcode precedentemente riservati per NOP (nelle versioni ARM precedenti). Quindi i binari BTI-enabled rimangono backward-compatible: sull'hardware senza supporto BTI quelle istruzioni agiscono come NOP.
- I passaggi del compiler che aggiungono BTI le inseriscono solo dove necessario: funzioni che possono essere chiamate indirettamente, o blocchi base target di salti.
- Alcune patch e codice LLVM mostrano che BTI non viene inserito per *tutti* i basic block — solo per quelli che sono potenziali target di branch (per esempio da switch / jump table).
#### Sinergia BTI + PAC
PAC protegge il valore del puntatore (la sorgente) — assicura che la catena di chiamate/ritorni indiretti non sia stata manomessa.
BTI assicura che anche un puntatore valido debba puntare solo a entry point opportunamente marcati.
Combinati, un attacker ha bisogno sia di un puntatore valido con PAC corretto sia che il target abbia un BTI posizionato lì. Questo aumenta la difficoltà nel costruire gadget per exploit.
#### Example
<details>
<summary>Example</summary>
An exploit tries to pivot into gadget at `0xABCDEF` that doesn’t start with `BTI c`. The CPU, upon executing `blr x0`, checks the target and faults because the instruction alignment doesn’t include a valid landing pad. Thus many gadgets become unusable unless they include BTI prefix.
</details>
### 10. **Privileged Access Never (PAN) & Privileged Execute Never (PXN)**
**Introdotto in estensioni ARMv8 più recenti / supporto iOS (per kernel hardened)**
#### PAN (Privileged Access Never)
- **PAN** è una funzionalità introdotta in **ARMv8.1-A** che impedisce al **codice privilegiato** (EL1 o EL2) di **leggere o scrivere** memoria marcata come **user-accessible (EL0)**, a meno che PAN non sia disabilitato esplicitamente.
- L'idea: anche se il kernel viene ingannato o compromesso, non può dereferenziare arbitrariamente puntatori user-space senza prima *disabilitare* PAN, riducendo così i rischi di exploit in stile **ret2usr** o l'abuso di buffer controllati dall'utente.
- Quando PAN è abilitato (PSTATE.PAN = 1), qualsiasi istruzione privilegiata di load/store che accede a un indirizzo virtuale “accessible at EL0” provoca un **permission fault**.
- Il kernel, quando deve legittimamente accedere alla memoria user-space (es. copiare dati da/a buffer utente), deve **disabilitare temporaneamente PAN** (o usare istruzioni di “unprivileged load/store”) per consentire quell'accesso.
- In Linux su ARM64, il supporto PAN è stato introdotto intorno al 2015: patch del kernel hanno aggiunto il rilevamento della feature e hanno sostituito `get_user` / `put_user` ecc. con varianti che puliscono PAN attorno agli accessi alla memoria utente.
**Sottigliezza / limitazione / bug**
- Come notato da Siguza e altri, un bug di specifica (o comportamento ambiguo) nel design ARM fa sì che le mappature user execute-only (`--x`) possano **non innescare PAN**. In altre parole, se una pagina user è marcata eseguibile ma senza permesso di lettura, il tentativo del kernel di leggere potrebbe bypassare PAN perché l'architettura considera “accessible at EL0” come richiesta di permesso di lettura, non solo di esecuzione. Questo porta a un bypass di PAN in alcune configurazioni.
- Per questo motivo, se iOS / XNU permettono pagine user execute-only (come in alcuni setup JIT o code-cache), il kernel potrebbe leggere accidentalmente da esse anche con PAN abilitato. Questa è un'area sottile nota per essere potenzialmente sfruttabile in alcuni sistemi ARMv8+.
#### PXN (Privileged eXecute Never)
- **PXN** è un flag della page table (nelle voci di page table, leaf o block entries) che indica che la pagina è **non eseguibile quando si esegue in privileged mode** (cioè quando EL1 esegue).
- PXN impedisce al kernel (o a qualunque codice privilegiato) di saltare o eseguire istruzioni da pagine user-space anche se il controllo viene dirottato. In pratica, impedisce l'esecuzione di codice user-space dal contesto kernel.
- Combinato con PAN, questo assicura che:
1. Il kernel non possa (di default) leggere o scrivere dati user-space (PAN)
2. Il kernel non possa eseguire codice user-space (PXN)
- Nel formato di page table ARMv8, le voci leaf hanno un bit `PXN` (e anche `UXN` per unprivileged execute-never) nei loro bit di attributo.
Quindi anche se il kernel ha un puntatore a funzione corrotto che punta a memoria user, e tenta di fare branch lì, il bit PXN causerebbe un fault.
#### Modello di permessi della memoria & come PAN e PXN si mappano ai bit di page table
Per capire come PAN / PXN funzionano, bisogna vedere come funziona la traduzione e il modello di permessi ARM (semplificato):
- Ogni entry di pagina o block ha campi attributo inclusi **AP[2:1]** per i permessi di accesso (read/write, privileged vs unprivileged) e bit **UXN / PXN** per restrizioni execute-never.
- Quando PSTATE.PAN è 1 (abilitato), l'hardware applica semantiche modificate: gli accessi privilegiati alle pagine marcate come “accessible by EL0” (cioè user-accessible) sono disabilitati (fault).
- A causa del bug menzionato, pagine marcate solo eseguibili (nessun permesso di lettura) potrebbero non essere considerate “accessible by EL0” in alcune implementazioni, bypassando così PAN.
- Quando il bit PXN di una pagina è impostato, anche se l'instruction fetch proviene da un livello di privilegio superiore, l'esecuzione è proibita.
#### Uso del kernel di PAN / PXN in un OS hardenizzato (es. iOS / XNU)
In un design di kernel hardenizzato (come quello che Apple potrebbe usare):
- Il kernel abilita PAN di default (quindi il codice privilegiato è vincolato).
- Nei percorsi che legittimamente devono leggere o scrivere buffer utente (es. syscall copy, I/O, read/write user pointer), il kernel disabilita temporaneamente **PAN** o usa istruzioni speciali per sovrascriverlo.
- Dopo aver finito l'accesso ai dati utente, deve riabilitare PAN.
- PXN è applicato tramite page table: le pagine utente hanno PXN = 1 (quindi il kernel non può eseguirle), le pagine kernel non hanno PXN (quindi il codice kernel può essere eseguito).
- Il kernel deve assicurarsi che nessun path di codice permetta il flusso di esecuzione verso regioni di memoria user (che aggirerebbero PXN) — quindi le catene di exploit che si basano su “jump into user-controlled shellcode” sono bloccate.
A causa del bypass PAN tramite pagine execute-only, in un sistema reale Apple potrebbe disabilitare o vietare pagine user execute-only, o correggere la debolezza della specifica.
#### Superfici di attacco, bypass e mitigazioni
- **PAN bypass via execute-only pages**: come discusso, la spec lascia un gap: pagine user con execute-only (nessun permesso di lettura) potrebbero non essere considerate “accessible at EL0,” quindi PAN non bloccherebbe letture kernel da tali pagine in alcune implementazioni. Questo dà all'attacker una via insolita per fornire dati tramite sezioni “execute-only”.
- **Exploit della finestra temporale**: se il kernel disabilita PAN per una finestra più lunga del necessario, una race o un percorso maligno potrebbe sfruttare quella finestra per effettuare accessi involontari alla memoria user.
- **Dimenticanza di riabilitazione**: se i percorsi di codice non riabilitano PAN, operazioni kernel successive potrebbero accedere erroneamente alla memoria user.
- **Misconfigurazione di PXN**: se le page table non impostano PXN sulle pagine user o mappano in modo errato le pagine di codice user, il kernel potrebbe essere ingannato nell'eseguire codice user-space.
- **Speculation / side-channels**: analogamente ai bypass speculativi, possono esistere effetti microarchitetturali transitori che causano violazioni temporanee dei controlli PAN / PXN (anche se tali attacchi dipendono molto dal design della CPU).
- **Interazioni complesse**: in funzionalità più avanzate (es. JIT, shared memory, code region just-in-time), il kernel potrebbe aver bisogno di controllo fine per permettere certi accessi memoria o esecuzioni in regioni mappate user; progettare questi percorsi in sicurezza sotto i vincoli PAN/PXN non è banale.
#### Example
<details>
<summary>Code Example</summary>
Here are illustrative pseudo-assembly sequences showing enabling/disabling PAN around user memory access, and how a fault might occur.
</details>
// Suppose kernel entry point, PAN is enabled (privileged code cannot access user memory by default)
; Kernel receives a syscall with user pointer in X0 ; wants to read an integer from user space mov X1, X0 ; X1 = user pointer
; disable PAN to allow privileged access to user memory MSR PSTATE.PAN, #0 ; clear PAN bit, disabling the restriction
ldr W2, [X1] ; now allowed load from user address
; re-enable PAN before doing other kernel logic MSR PSTATE.PAN, #1 ; set PAN
; … further kernel work …
; Later, suppose an exploit corrupts a pointer to a user-space code page and jumps there BR X3 ; branch to X3 (which points into user memory)
; Because the target page is marked PXN = 1 for privileged execution, ; the CPU throws an exception (fault) and rejects execution
Se il kernel avesse **non** impostato PXN su quella pagina user, allora il branch potrebbe avere successo — il che sarebbe insicuro.
Se il kernel si dimentica di riabilitare PAN dopo l'accesso alla memoria user, si apre una finestra in cui ulteriore logica del kernel potrebbe accidentalmente leggere/scrivere memoria user arbitraria.
Se il puntatore user punta a una pagina execute-only (pagina user con solo permesso di esecuzione, senza read/write), a causa di un bug nella specifica PAN, `ldr W2, [X1]` potrebbe **non** generare fault anche con PAN abilitato, permettendo un exploit di bypass, a seconda dell'implementazione.
</details>
<details>
<summary>Example</summary>
Una vulnerabilità del kernel prova a prendere un function pointer fornito dall'user e chiamarlo in contesto kernel (i.e. `call user_buffer`). Sotto PAN/PXN, quell'operazione è disabilitata o genera fault.
</details>
---
### 11. **Top Byte Ignore (TBI) / Pointer Tagging**
**Introdotto in ARMv8.5 / versioni più recenti (o estensione opzionale)**
TBI significa che il top byte (most-significant byte) di un puntatore a 64 bit è ignorato dalla traduzione degli indirizzi. Questo permette all'OS o all'hardware di incorporare **tag bits** nel top byte del puntatore senza influenzare l'indirizzo effettivo.
- TBI sta per **Top Byte Ignore** (talvolta chiamato *Address Tagging*). È una feature hardware (disponibile in molte implementazioni ARMv8+) che **ignora gli 8 bit più alti** (bit 63:56) di un puntatore a 64 bit quando esegue la **traduzione degli indirizzi / load/store / instruction fetch**.
- Di fatto, la CPU tratta un puntatore `0xTTxxxx_xxxx_xxxx` (dove `TT` = top byte) come `0x00xxxx_xxxx_xxxx` ai fini della traduzione degli indirizzi, ignorando (mascherando) il top byte. Il top byte può essere usato dal software per memorizzare **metadata / tag bits**.
- Questo fornisce al software uno spazio in-band “gratuito” per inserire un byte di tag in ogni puntatore senza modificare la locazione di memoria a cui fa riferimento.
- L'architettura garantisce che load, store e instruction fetch trattino il puntatore con il top byte mascherato (cioè tag rimosso) prima di effettuare l'accesso alla memoria.
Quindi TBI disaccoppia il **logical pointer** (pointer + tag) dall'**indirizzo fisico** usato per le operazioni di memoria.
#### Why TBI: Use cases and motivation
- **Pointer tagging / metadata**: Puoi memorizzare metadata aggiuntivi (es. object type, version, bounds, integrity tags) in quel top byte. Quando poi usi il puntatore, il tag è ignorato a livello hardware, quindi non è necessario rimuoverlo manualmente per l'accesso in memoria.
- **Memory tagging / MTE (Memory Tagging Extension)**: TBI è il meccanismo hardware di base su cui MTE si costruisce. In ARMv8.5, la **Memory Tagging Extension** usa i bit 59:56 del puntatore come una **logical tag** e li confronta con un **allocation tag** memorizzato in memoria.
- **Enhanced security & integrity**: Combinando TBI con pointer authentication (PAC) o controlli runtime, puoi forzare non solo il valore del puntatore ma anche che il tag sia corretto. Un attacker che sovrascrive un puntatore senza il tag corretto produrrà un tag non corrispondente.
- **Compatibility**: Poiché TBI è opzionale e i tag bits sono ignorati dall'hardware, il codice esistente senza tag continua a operare normalmente. I tag bits diventano effettivamente bit “don't care” per il codice legacy.
#### Example
<details>
<summary>Example</summary>
Un function pointer includeva un tag nel suo top byte (es. `0xAA`). Un exploit sovrascrive i bit bassi del puntatore ma trascura il tag, quindi quando il kernel verifica o sanifica, il puntatore fallisce o viene rifiutato.
</details>
---
### 12. **Page Protection Layer (PPL)**
**Introdotto in iOS recenti / hardware moderno (iOS ~17 / Apple silicon / modelli high-end)** (alcuni report mostrano PPL su macOS / Apple silicon, ma Apple sta portando protezioni analoghe su iOS)
- PPL è progettato come un **intra-kernel protection boundary**: anche se il kernel (EL1) è compromesso e ha capacità di read/write, **non dovrebbe essere in grado di modificare liberamente** certe **pagine sensibili** (soprattutto page tables, code-signing metadata, kernel code pages, entitlements, trust caches, ecc.).
- Effettivamente crea un “kernel within the kernel” — un componente più piccolo e trusted (PPL) con **privilegi elevati** che solo lui può usare per modificare pagine protette. Altro codice kernel deve chiamare le routine PPL per effettuare modifiche.
- Questo riduce la superficie d'attacco per exploit del kernel: anche con pieno arbitrario R/W/execute in kernel mode, il codice exploit deve in qualche modo entrare nel dominio PPL (o bypassare PPL) per modificare strutture critiche.
- Su Apple silicon più recenti (A15+ / M2+), Apple sta migrando verso **SPTM (Secure Page Table Monitor)**, che in molti casi sostituisce PPL per la protezione delle page-table su quelle piattaforme.
Ecco come si ritiene che PPL operi, basandosi su analisi pubbliche:
#### Use of APRR / permission routing (APRR = Access Permission ReRouting)
- L'hardware Apple usa un meccanismo chiamato **APRR (Access Permission ReRouting)**, che permette alle page table entries (PTEs) di contenere piccoli indici, invece dei full permission bits. Quegli indici sono mappati tramite registri APRR alle permission effettive. Questo permette il remapping dinamico delle permission per dominio.
- PPL sfrutta APRR per segregare i privilegi all'interno del contesto kernel: solo il dominio PPL è autorizzato ad aggiornare la mappatura tra indici e permission effettive. Cioè, quando codice kernel non-PPL scrive una PTE o prova a modificare i permission bits, la logica APRR lo disabilita (o impone una mappatura read-only).
- Il codice PPL stesso gira in una regione ristretta (es. `__PPLTEXT`) che normalmente è non-executable o non-writable fino a quando entry gates non lo consentono temporaneamente. Il kernel chiama i punti di ingresso PPL (“PPL routines”) per eseguire operazioni sensibili.
#### Gate / Entry & Exit
- Quando il kernel ha bisogno di modificare una pagina protetta (es. cambiare i permission di una kernel code page, o modificare le page tables), chiama una routine **PPL wrapper**, che fa la validazione e poi transizione nel dominio PPL. Fuori da quel dominio, le pagine protette sono effettivamente read-only o non modificabili dal kernel principale.
- Durante l'entry in PPL, le mappature APRR sono aggiustate in modo che le pagine di memoria nella regione PPL siano impostate come **executable & writable** all'interno di PPL. Al momento dell'uscita, tornano a essere read-only / non-writable. Questo assicura che solo le routine PPL ben revisionate possano scrivere sulle pagine protette.
- Fuori da PPL, i tentativi da parte del codice kernel di scrivere su quelle pagine protette genereranno fault (permission denied) perché la mapping APRR per quel dominio di codice non permette la scrittura.
#### Protected page categories
Le pagine che PPL tipicamente protegge includono:
- Strutture delle page table (translation table entries, mapping metadata)
- Kernel code pages, specialmente quelle contenenti logica critica
- Code-sign metadata (trust caches, signature blobs)
- Entitlement tables, signature enforcement tables
- Altre strutture kernel di alto valore dove una patch permetterebbe di bypassare i controlli di firma o manipolare credenziali
L'idea è che anche se la memoria del kernel è completamente controllata, l'attaccante non può semplicemente patchare o riscrivere queste pagine, a meno che non comprometta anche le routine PPL o non bypassi PPL.
#### Known Bypasses & Vulnerabilities
1. **Project Zero’s PPL bypass (stale TLB trick)**
- Una writeup pubblica di Project Zero descrive un bypass che implica **stale TLB entries**.
- L'idea:
1. Allocare due pagine fisiche A e B, marcarle come PPL pages (quindi protette).
2. Mappare due indirizzi virtuali P e Q le cui L3 translation table pages provengono da A e B.
3. Lanciare un thread che accede continuamente a Q, mantenendo viva la sua entry TLB.
4. Chiamare `pmap_remove_options()` per rimuovere le mappature a partire da P; a causa di un bug, il codice rimuove per errore le TTE sia per P che per Q, ma invalida solo la entry TLB per P, lasciando viva la stale entry di Q.
5. Riutilizzare B (la pagina della table di Q) per mappare memoria arbitraria (es. pagine protette PPL). Poiché la stale entry TLB mappa ancora la vecchia mapping di Q, quella mapping resta valida per quel contesto.
6. Attraverso ciò, l'attaccante può mettere in piedi una mapping scrivibile delle pagine protette PPL senza passare per l'interfaccia PPL.
- Questo exploit richiedeva un controllo preciso delle mappature fisiche e del comportamento del TLB. Dimostra che un confine di sicurezza che si basa sulla correttezza di TLB / mapping deve essere estremamente attento alle invalidazioni TLB e alla coerenza delle mappature.
- Project Zero ha commentato che bypass come questo sono sottili e rari, ma possibili in sistemi complessi. Tuttavia, considerano PPL una mitigazione solida.
2. **Other potential hazards & constraints**
- Se un exploit del kernel può entrare direttamente nelle routine PPL (chiamando i PPL wrappers), potrebbe bypassare le restrizioni. Perciò la validazione degli argomenti è critica.
- Bug nel codice PPL stesso (es. overflow aritmetico, controlli di confine) possono permettere modifiche out-of-bounds all'interno di PPL. Project Zero ha osservato che un bug del genere in `pmap_remove_options_internal()` è stato sfruttato nel loro bypass.
- Il confine PPL è irrevocabilmente legato all'enforcement hardware (APRR, memory controller), quindi è forte solo quanto l'implementazione hardware.
#### Example
<details>
<summary>Code Example</summary>
Ecco un pseudocodice / logica semplificata che mostra come un kernel potrebbe chiamare PPL per modificare pagine protette:
</details>
```c
// In kernel (outside PPL domain)
function kernel_modify_pptable(pt_addr, new_entry) {
// validate arguments, etc.
return ppl_call_modify(pt_addr, new_entry) // call PPL wrapper
}
// In PPL (trusted domain)
function ppl_call_modify(pt_addr, new_entry) {
// temporarily enable write access to protected pages (via APRR adjustments)
aprr_set_index_for_write(PPL_INDEX)
// perform the modification
*pt_addr = new_entry
// restore permissions (make pages read-only again)
aprr_restore_default()
return success
}
// If kernel code outside PPL does:
*pt_addr = new_entry // a direct write
// It will fault because APRR mapping for non-PPL domain disallows write to that page
Il kernel può eseguire molte operazioni normali, ma solo attraverso le routine ppl_call_* può modificare mappature protette o patchare il codice.
Example
Un exploit del kernel tenta di sovrascrivere la entitlement table, o di disabilitare code-sign enforcement modificando un kernel signature blob. Poiché quella pagina è protetta da PPL, la scrittura viene bloccata a meno di passare attraverso l'interfaccia PPL. Quindi, anche con esecuzione di codice in kernel, non è possibile bypassare le restrizioni di code-sign o modificare arbitrariamente i dati delle credenziali. Su iOS 17+ alcuni dispositivi usano SPTM per isolare ulteriormente le pagine gestite da PPL.PPL → SPTM / Replacements / Future
- Sui SoC moderni di Apple (A15 o successivi, M2 o successivi), Apple supporta SPTM (Secure Page Table Monitor), che sostituisce PPL per le protezioni delle page table.
- Apple indica nella documentazione: “Page Protection Layer (PPL) and Secure Page Table Monitor (SPTM) enforce execution of signed and trusted code … PPL manages the page table permission overrides … Secure Page Table Monitor replaces PPL on supported platforms.”
- L’architettura SPTM probabilmente sposta l’applicazione delle policy in un monitor con privilegi più elevati fuori dal controllo del kernel, riducendo ulteriormente il confine di trust.
MTE | EMTE | MIE
Di seguito una descrizione di alto livello di come EMTE opera nel contesto MIE di Apple:
- Tag assignment
- Quando la memoria viene allocata (es. in kernel o user space tramite secure allocators), a quel blocco viene assegnato un secret tag.
- Il pointer restituito all’utente o al kernel include quel tag nei suoi high bits (usando i meccanismi TBI / top byte ignore).
- Tag checking on access
- Ogni volta che viene eseguito un load o store usando un pointer, l’hardware verifica che il tag del pointer corrisponda al tag del blocco di memoria (allocation tag). In caso di mismatch, genera immediatamente un fault (essendo sincrono).
- Poiché è sincrono, non esiste una finestra di “delayed detection”.
- Retagging on free / reuse
- Quando la memoria viene liberata, l’allocator cambia il tag del blocco (quindi pointer vecchi con tag obsoleti non corrispondono più).
- Un use-after-free pointer avrà quindi un tag stale e mismatch quando verrà usato.
- Neighbor-tag differentiation to catch overflows
- Allocazioni adiacenti ricevono tag distinti. Se un buffer overflow travasa nella memoria del vicino, il mismatch di tag provoca un fault.
- Questo è particolarmente efficace per rilevare piccoli overflow che oltrepassano i confini.
- Tag confidentiality enforcement
- Apple deve prevenire che i valori dei tag vengano leaked (perché se un attacker scopre il tag, potrebbe creare pointer con i tag corretti).
- Includono protezioni (controlli microarchitetturali / speculative) per evitare side-channel leakage dei bit di tag.
- Kernel and user-space integration
- Apple usa EMTE non solo in user-space ma anche in componenti kernel / OS-critical (per proteggere il kernel dalla memory corruption).
- L’hardware/OS garantisce che le regole sui tag si applichino anche quando il kernel esegue per conto dello user space.
Example
``` Allocate A = 0x1000, assign tag T1 Allocate B = 0x2000, assign tag T2// pointer P points into A with tag T1 P = (T1 << 56) | 0x1000
// Valid store *(P + offset) = value // tag T1 matches allocation → allowed
// Overflow attempt: P’ = P + size_of_A (into B region) *(P’ + delta) = value → pointer includes tag T1 but memory block has tag T2 → mismatch → fault
// Free A, allocator retags it to T3 free(A)
// Use-after-free: *(P) = value → pointer still has old tag T1, memory region is now T3 → mismatch → fault
#### Limitazioni & sfide
- **Intrablock overflows**: Se l’overflow resta nella stessa allocazione (non attraversa il boundary) e il tag rimane lo stesso, il tag mismatch non lo rileva.
- **Tag width limitation**: Solo pochi bit (es. 4 bit, o piccolo dominio) sono disponibili per il tag—namespace limitato.
- **Side-channel leaks**: Se i bit del tag possono essere leaked (via cache / speculative execution), un attacker può apprendere tag validi e bypassare. La Tag confidentiality enforcement di Apple è pensata per mitigare questo.
- **Performance overhead**: I controlli di tag su ogni load/store aggiungono costo; Apple deve ottimizzare l’hardware per ridurre l’overhead.
- **Compatibility & fallback**: Su hardware più vecchio o su parti che non supportano EMTE, deve esistere un fallback. Apple dichiara che MIE è abilitato solo su dispositivi con supporto.
- **Complex allocator logic**: L’allocator deve gestire i tag, retagging, allineare i boundary e evitare collisioni di mis-tag. Bug nella logica dell’allocator potrebbero introdurre vulnerabilità.
- **Mixed memory / hybrid areas**: Alcune aree di memoria possono rimanere untagged (legacy), rendendo l’interoperabilità più complessa.
- **Speculative / transient attacks**: Come con molte protezioni microarchitetturali, speculative execution o micro-op fusions potrebbero bypassare controlli transientemente o leakare bit di tag.
- **Limited to supported regions**: Apple potrebbe far valere EMTE solo in aree selettive e ad alto rischio (kernel, subsistemi critici per la sicurezza), non universalmente.
---
## Miglioramenti chiave / differenze rispetto a standard MTE
Ecco i miglioramenti e i cambiamenti che Apple enfatizza:
| Caratteristica | Original MTE | EMTE (Apple’s enhanced) / MIE |
|---|---|---|
| **Check mode** | Supports synchronous and asynchronous modes. In async, tag mismatches are reported later (delayed)| Apple insists on **synchronous mode** by default—tag mismatches are caught immediately, no delay/race windows allowed.|
| **Coverage of non-tagged memory** | Accesses to non-tagged memory (e.g. globals) may bypass checks in some implementations | EMTE requires that accesses from a tagged region to non-tagged memory also validate tag knowledge, making it harder to bypass by mixing allocations.|
| **Tag confidentiality / secrecy** | Tags might be observable or leaked via side channels | Apple adds **Tag Confidentiality Enforcement**, which attempts to prevent leakage of tag values (via speculative side-channels etc.).|
| **Allocator integration & retagging** | MTE leaves much of allocator logic to software | Apple’s secure typed allocators (kalloc_type, xzone malloc, etc.) integrate with EMTE: when memory is allocated or freed, tags are managed at fine granularity.|
| **Always-on by default** | In many platforms, MTE is optional or off by default | Apple enables EMTE / MIE by default on supported hardware (e.g. iPhone 17 / A19) for kernel and many user processes.|
Poiché Apple controlla sia l’hardware sia lo stack software, può far rispettare EMTE in modo stringente, evitare problemi di performance e chiudere buchi di side-channel.
---
## Come EMTE funziona in pratica (Apple / MIE)
Qui una descrizione ad alto livello di come EMTE opera sotto il setup MIE di Apple:
1. **Tag assignment**
- Quando la memoria viene allocata (es. in kernel o user space tramite secure allocators), viene assegnato a quel blocco un **secret tag**.
- Il puntatore restituito all’utente o al kernel include quel tag nei bit alti (usando TBI / top byte ignore mechanisms).
2. **Tag checking on access**
- Ogni volta che viene eseguito un load o store usando un puntatore, l’hardware verifica che il tag del puntatore corrisponda al tag del blocco di memoria (allocation tag). Se mismatch, genera subito un fault (dato che è synchronous).
- Poiché è synchronous, non esiste una finestra di “rilevamento ritardato”.
3. **Retagging on free / reuse**
- Quando la memoria viene liberata, l’allocator cambia il tag del blocco (così i puntatori più vecchi con tag obsoleti non corrispondono più).
- Un use-after-free pointer quindi avrà un tag stale e mismatch quando verrà usato.
4. **Neighbor-tag differentiation to catch overflows**
- Allocazioni adiacenti ricevono tag distinti. Se un buffer overflow travasa nella memoria del vicino, il tag mismatch causa un fault.
- Questo è particolarmente efficace per rilevare piccoli overflow che oltrepassano il boundary.
5. **Tag confidentiality enforcement**
- Apple deve prevenire che i valori dei tag vengano leaked (perché se un attacker apprende il tag, può costruire puntatori con il tag corretto).
- Includono protezioni (microarchitectural / speculative controls) per evitare side-channel leakage dei bit di tag.
6. **Kernel and user-space integration**
- Apple usa EMTE non solo in user-space ma anche nel kernel / componenti critici dell’OS (per proteggere il kernel dalla corruzione di memoria).
- L’hardware/OS assicura che le regole sui tag si applichino anche quando il kernel esegue per conto dello user space.
Poiché EMTE è parte di MIE, Apple usa EMTE in synchronous mode sulle superfici di attacco chiave, non come opzione opt-in o modalità di debug.
---
## Exception handling in XNU
Quando si verifica un’**exception** (es., `EXC_BAD_ACCESS`, `EXC_BAD_INSTRUCTION`, `EXC_CRASH`, `EXC_ARM_PAC`, ecc.), il livello **Mach** del kernel XNU è responsabile di intercettarla prima che diventi un **signal** in stile UNIX (come `SIGSEGV`, `SIGBUS`, `SIGILL`, ...).
Questo processo coinvolge più livelli di propagazione e gestione delle exception prima di raggiungere lo user space o essere convertita in un BSD signal.
### Flusso delle Exception (High-Level)
1. **La CPU genera una exception sincrona** (es., dereferenziazione di un puntatore invalido, PAC failure, istruzione illegale, ecc.).
2. **Il low-level trap handler** viene eseguito (`trap.c`, `exception.c` nel sorgente XNU).
3. Il trap handler chiama **`exception_triage()`**, il cuore della gestione delle Mach exception.
4. `exception_triage()` decide come instradare l’exception:
- Prima alla **thread's exception port**.
- Poi alla **task's exception port**.
- Poi alla **host's exception port** (spesso `launchd` o `ReportCrash`).
Se nessuna di queste porte gestisce l’exception, il kernel può:
- **Convertirla in un BSD signal** (per processi user-space).
- **Panic** (per exception in kernel-space).
### Funzione core: `exception_triage()`
La funzione `exception_triage()` instrada le Mach exceptions lungo la catena di possibili handler finché una non la gestisce o finché non diventa infine fatale. È definita in `osfmk/kern/exception.c`.
```c
void exception_triage(exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt);
Flusso di chiamata tipico:
exception_triage() └── exception_deliver() ├── exception_deliver_thread() ├── exception_deliver_task() └── exception_deliver_host()
Se tutte falliscono → l’eccezione viene gestita da bsd_exception() → viene tradotta in un segnale come SIGSEGV.
Porte di eccezione
Ogni oggetto Mach (thread, task, host) può registrare exception ports, dove vengono inviati i messaggi di eccezione.
Sono definiti dall’API:
task_set_exception_ports()
thread_set_exception_ports()
host_set_exception_ports()
Each exception port has:
- A mask (which exceptions it wants to receive)
- A port name (Mach port to receive messages)
- A behavior (how the kernel sends the message)
- A flavor (which thread state to include)
Debuggers and Exception Handling
A debugger (e.g., LLDB) sets an exception port on the target task or thread, usually using task_set_exception_ports().
When an exception occurs:
- The Mach message is sent to the debugger process.
- The debugger can decide to handle (resume, modify registers, skip instruction) or not handle the exception.
- If the debugger doesn’t handle it, the exception propagates to the next level (task → host).
Flow of EXC_BAD_ACCESS
-
Thread dereferences invalid pointer → CPU raises Data Abort.
-
Kernel trap handler calls
exception_triage(EXC_BAD_ACCESS, ...). -
Message sent to:
-
Thread port → (debugger can intercept breakpoint).
-
If debugger ignores → Task port → (process-level handler).
-
If ignored → Host port (usually ReportCrash).
- If no one handles →
bsd_exception()translates toSIGSEGV.
PAC Exceptions
When Pointer Authentication (PAC) fails (signature mismatch), a special Mach exception is raised:
EXC_ARM_PAC(type)- Codes may include details (e.g., key type, pointer type).
If the binary has the flag TFRO_PAC_EXC_FATAL, the kernel treats PAC failures as fatal, bypassing debugger interception. This is to prevent attackers from using debuggers to bypass PAC checks and it’s enabled for platform binaries.
Software Breakpoints
A software breakpoint (int3 on x86, brk on ARM64) is implemented by causing a deliberate fault.
The debugger catches this via the exception port:
- Modifies instruction pointer or memory.
- Restores original instruction.
- Resumes execution.
This same mechanism is what allows you to “catch” a PAC exception — unless TFRO_PAC_EXC_FATAL is set, in which case it never reaches the debugger.
Conversion to BSD Signals
If no handler accepts the exception:
-
Kernel calls
task_exception_notify() → bsd_exception(). -
This maps Mach exceptions to signals:
| Mach Exception | Signal |
|---|---|
| EXC_BAD_ACCESS | SIGSEGV or SIGBUS |
| EXC_BAD_INSTRUCTION | SIGILL |
| EXC_ARITHMETIC | SIGFPE |
| EXC_SOFTWARE | SIGTRAP |
| EXC_BREAKPOINT | SIGTRAP |
| EXC_CRASH | SIGKILL |
| EXC_ARM_PAC | SIGILL (on non-fatal) |
### Key Files in XNU Source
-
osfmk/kern/exception.c→ Core ofexception_triage(),exception_deliver_*(). -
bsd/kern/kern_sig.c→ Signal delivery logic. -
osfmk/arm64/trap.c→ Low-level trap handlers. -
osfmk/mach/exc.h→ Exception codes and structures. -
osfmk/kern/task.c→ Task exception port setup.
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.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.64zone). - 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, you’d 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 weren’t 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:
-
Pop HEAD (return that memory to the caller).
-
Update HEAD = HEAD->next (stored in the freed chunk’s 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)
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 scrivere in un freed object → sovrascrivere il suo “next” pointer.
Poi, alla successiva allocazione di quella dimensione:
- L’allocator estrae il corrupted chunk.
- Segue il attacker-supplied “next” pointer.
- Restituisce un pointer verso memoria arbitraria, consentendo 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.
Questo freelist design rese lo sfruttamento molto efficace prima dell’hardening: vicini prevedibili da heap sprays, raw pointer nelle freelist links e l’assenza di separazione dei tipi permetteva agli attaccanti di trasformare bug UAF/overflow in controllo arbitrario della memoria del kernel.
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.
In questo modo, quando avviene la corruzione di memoria, l’attaccante può sovrascrivere in modo affidabile l’oggetto vittima con dati controllati.
Passaggi:
- Spray allocations (fill the holes)
- Col tempo, l’heap del kernel si frammenta: alcune zone contengono buchi dove oggetti vecchi sono stati liberati.
- Prima, l’attaccante effettua molte allocazioni fittizie per riempire questi spazi, così l’heap diventa “compattato” e prevedibile.
- Force new pages
- Una volta riempiti i buchi, le allocazioni successive devono arrivare da nuove pagine aggiunte alla zona.
- Pagine fresche significano che gli oggetti saranno raggruppati insieme, non sparsi nella memoria frammentata.
- Questo dà all’attaccante un controllo molto migliore sui vicini.
- Place attacker objects
- L’attaccante effettua di nuovo uno spray, creando molti oggetti controllati dall’attaccante in quelle nuove pagine.
- Questi oggetti sono prevedibili per dimensione e posizione (dato che appartengono alla stessa zona).
- Free a controlled object (make a gap)
- L’attaccante libera deliberatamente uno dei propri oggetti.
- Questo crea un “buco” nell’heap, che l’allocator riutilizzerà per la prossima allocazione di quella dimensione.
- Victim object lands in the hole
- L’attaccante induce il kernel ad allocare l’oggetto vittima (quello che vuole corrompere).
- Poiché il buco è la prima slot disponibile nella freelist, la vittima viene posizionata esattamente dove l’attaccante aveva liberato il proprio oggetto.
- Overflow / UAF into victim
- Ora l’attaccante ha oggetti controllati attorno alla vittima.
- Sovrascrivendo tramite overflow da uno dei propri oggetti (o riutilizzando uno freed), può sovrascrivere in modo affidabile i campi di memoria della vittima con valori scelti.
Perché funziona:
- Zone allocator predictability: le allocazioni della stessa dimensione provengono sempre dalla stessa zona.
- Freelist behavior: le nuove allocazioni riutilizzano prima il chunk più recentemente liberato.
- Heap sprays: l’attaccante riempie la memoria con contenuti prevedibili e controlla il layout.
- Risultato finale: l’attaccante controlla dove l’oggetto vittima viene allocato e quali dati si trovano vicino ad esso.
Modern Kernel Heap (iOS 15+/A12+ SoCs)
Apple ha rinforzato l’allocator e reso il heap grooming molto più difficile:
1. From Classic kalloc to kalloc_type
- Prima: esisteva una singola zona
kalloc.<size>per ogni classe di dimensione (16, 32, 64, … 1280, ecc.). Qualsiasi oggetto di quella dimensione veniva messo lì → gli oggetti controllati dall’attaccante potevano trovarsi accanto ad oggetti privilegiati del kernel. - Adesso:
- Gli oggetti del kernel sono allocati da typed zones (
kalloc_type). - Ogni tipo di oggetto (es.,
ipc_port_t,task_t,OSString,OSData) ha la propria zona dedicata, anche se hanno la stessa dimensione. - La mappatura tra tipo di oggetto ↔ zona è generata dal kalloc_type system a compile time.
Un attaccante non può più garantire che dati controllati (OSData) finiscano adiacenti a oggetti kernel sensibili (task_t) della stessa dimensione.
2. Slabs and Per-CPU Caches
- L’heap è diviso in slabs (pagine di memoria suddivise in chunk a dimensione fissa per quella zona).
- Ogni zona ha una cache per CPU per ridurre la contendibilità.
- Percorso di allocazione:
- Prova la per-CPU cache.
- Se vuota, preleva dalla freelist globale.
- Se la freelist è vuota, allocare una nuova slab (una o più pagine).
- Vantaggio: questa decentralizzazione rende gli heap sprays meno deterministici, dato che le allocazioni possono essere soddisfatte dalle cache di CPU diverse.
3. Randomization inside zones
- All’interno di una zona, gli elementi liberati non vengono restituiti in semplice ordine FIFO/LIFO.
- XNU moderno usa encoded freelist pointers (safe-linking come Linux, introdotto ~iOS 14).
- Ogni puntatore della freelist è XOR-encoded con un cookie segreto per zona.
- Questo impedisce agli attaccanti di forgiare un puntatore di freelist falso se ottengono un primitive di scrittura.
- Alcune allocazioni sono randomizzate nella loro collocazione all’interno di una slab, quindi lo spraying non garantisce l’adiacenza.
4. Guarded Allocations
- Certi oggetti critici del kernel (es., credenziali, strutture task) sono allocati in guarded zones.
- Queste zone inseriscono guard pages (memoria non mappata) tra le slab o usano redzones attorno agli oggetti.
- Qualsiasi overflow nella guard page causa un fault → panic immediato invece di corruzione silente.
5. Page Protection Layer (PPL) and SPTM
- Anche se controlli un oggetto liberato, non puoi modificare tutta la memoria del kernel:
- PPL (Page Protection Layer) impone che certe regioni (es., dati di code signing, entitlements) siano read-only anche per il kernel stesso.
- Su dispositivi A15/M2+, questo ruolo è sostituito/enhanced da SPTM (Secure Page Table Monitor) + TXM (Trusted Execution Monitor).
- Questi layer imposti dall’hardware significano che gli attaccanti non possono scalare da una singola corruzione dell’heap al patching arbitrario di strutture di sicurezza critiche.
- (Aggiunto / Potenziato): anche PAC (Pointer Authentication Codes) è usato nel kernel per proteggere i puntatori (soprattutto function pointers, vtables) rendendo più difficile forgiare o corrompere tali puntatori.
- (Aggiunto / Potenziato): le zone possono applicare zone_require / zone enforcement, cioè che un oggetto liberato possa essere restituito solo attraverso la sua corretta typed zone; free cross-zone non validi possono causare panic o essere rifiutati. (Apple accenna a questo nei loro post sulla memory safety)
6. Large Allocations
- Non tutte le allocazioni passano per
kalloc_type. - Richieste molto grandi (sopra ~16 KB) bypassano le typed zones e sono servite direttamente dal kernel VM (kmem) tramite allocazioni di pagina.
- Queste sono meno prevedibili, ma anche meno sfruttabili, poiché non condividono slab con altri oggetti.
7. Allocation Patterns Attackers Target
Anche con queste protezioni, gli attaccanti cercano ancora:
- Reference count objects: se puoi alterare i contatori retain/release, puoi provocare use-after-free.
- Objects with function pointers (vtables): corrompere uno di questi fornisce ancora controllo del flow.
- Shared memory objects (IOSurface, Mach ports): restano bersagli perché fungono da ponte user ↔ kernel.
Ma — a differenza di prima — non puoi semplicemente sprayare OSData e aspettarti che stia vicino a un task_t. Serve un bug specifico per tipo o info leaks per avere successo.
Example: Allocation Flow in Modern Heap
Supponiamo che userspace chiami IOKit per allocare un oggetto OSData:
- Type lookup →
OSDatamappa alla zonakalloc_type_osdata(size 64 bytes). - Controlla la per-CPU cache per elementi liberi.
- Se trovato → restituisci uno.
- Se vuoto → vai alla freelist globale.
- Se freelist vuota → allocare una nuova slab (pagina da 4KB → 64 chunk da 64 bytes).
- Restituisci il chunk al chiamante.
Protezione dei puntatori della freelist:
- Ogni chunk liberato memorizza l’indirizzo del successivo chunk libero, ma codificato con una chiave segreta.
- Sovrascrivere quel campo con dati dell’attaccante non funzionerà a meno di conoscere la chiave.
Comparison Table
| Caratteristica | 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, and PAC protects pointers |
| Allocation reuse validation | None (freelist pointers raw) | zone_require / zone enforcement |
| Exploit reliability | High with heap sprays | Much lower, requires logic bugs or info leaks |
| Large allocations handling | All small allocations managed equally | Large ones bypass zones → handled via VM |
Modern Userland Heap (iOS, macOS — type-aware / xzone malloc)
Nelle recenti versioni Apple OS (soprattutto iOS 17+), Apple ha introdotto un allocator userland più sicuro, xzone malloc (XZM). Questo è l’analogo in user-space di kalloc_type del kernel, applicando consapevolezza di tipo, isolamento dei metadata e meccanismi di memory tagging.
Goals & Design Principles
- Type segregation / type awareness: raggruppare le allocazioni per tipo o uso (pointer vs data) per prevenire type confusion e riuso cross-type.
- Metadata isolation: separare i metadata dell’heap (es. free lists, bit di size/state) dai payload degli oggetti in modo che scritture OOB siano meno propense a corrompere i metadata.
- Guard pages / redzones: inserire pagine non mappate o padding attorno alle allocazioni per catturare overflow.
- Memory tagging (EMTE / MIE): lavorare insieme al tagging hardware per rilevare use-after-free, out-of-bounds e accessi invalidi.
- Scalable performance: mantenere basso overhead, evitare frammentazione e supportare molte allocazioni al secondo con bassa latenza.
Architecture & Components
Di seguito gli elementi principali dell’allocator xzone:
Segment Groups & Zones
- Segment groups partizionano lo spazio di indirizzamento per categorie d’uso: es.
data,pointer_xzones,data_large,pointer_large. - Ogni segment group contiene segments (range VM) che ospitano allocazioni per quella categoria.
- Associato a ogni segment c’è una metadata slab (area VM separata) che memorizza i metadata (es. bit free/used, classi di dimensione) per quel segment. Questa out-of-line (OOL) metadata garantisce che i metadata non siano mescolati con i payload degli oggetti, mitigando la corruzione da overflow.
- I segments sono suddivisi in chunks (slice) che a loro volta sono suddivisi in blocks (unità di allocazione). Un chunk è legato a una classe di dimensione e a un segment group (cioè tutti i block in un chunk condividono la stessa dimensione & categoria).
- Per allocazioni small/medium si usano chunk a dimensione fissa; per large/huge può effettuare mapping separati.
Chunks & Blocks
- Un chunk è una regione (spesso diverse pagine) dedicata alle allocazioni di una classe di dimensione all’interno di un gruppo.
- All’interno di un chunk, i blocks sono slot disponibili per le allocazioni. I block liberati sono tracciati tramite la metadata slab — es. tramite bitmap o free lists memorizzate out-of-line.
- Tra i chunk (o al loro interno), possono essere inserite guard slices / guard pages (es. slice non mappate) per catturare scritture out-of-bounds.
Type / Type ID
- Ogni sito di allocazione (o chiamata a malloc, calloc, ecc.) è associato a un type identifier (un
malloc_type_id_t) che codifica che tipo di oggetto viene allocato. Quel type ID è passato all’allocator, che lo usa per selezionare quale zona/segment servire l’allocazione. - Per questo, anche se due allocazioni hanno la stessa dimensione, possono andare in zone completamente diverse se i tipi differiscono.
- Nelle prime versioni di iOS 17, non tutte le API (es. CFAllocator) erano pienamente type-aware; Apple ha corretto alcune di queste debolezze in iOS 18.
Allocation & Freeing Workflow
Ecco un flusso ad alto livello di come operano allocazione e deallocazione in xzone:
malloc / calloc / realloc / typed allocviene invocata con una size e un type ID.- L’allocator usa il type ID per scegliere il corretto segment group / zone.
- All’interno di quella zona/segment, cerca un chunk che abbia blocks liberi della dimensione richiesta.
- Può consultare local caches / per-thread pools o free block lists dalla metadata.
- Se non è disponibile nessun block libero, può allocare un nuovo chunk in quella zona.
- La metadata slab viene aggiornata (bit free cancellato, bookkeeping).
- Se il memory tagging (EMTE) è in uso, il block restituito riceve un tag, e la metadata viene aggiornata per riflettere lo stato “live”.
- Quando viene chiamato
free():
- Il block è marcato come liberato nella metadata (tramite OOL slab).
- Il block può essere messo in una free list o pooled per il riuso.
- Opzionalmente, il contenuto del block può essere cancellato o poisoned per ridurre leak di dati o sfruttamento UAF.
- Il tag hardware associato al block può essere invalidato o rietichettato.
- Se un intero chunk diventa libero (tutti i block liberati), l’allocator può reclaim quel chunk (unmapparlo o restituirlo al OS) sotto pressione di memoria.
Security Features & Hardening
Queste sono le difese integrate nel moderno xzone:
| Feature | Scopo | Note |
|---|---|---|
| Metadata decoupling | Impedire che overflow corrompano i metadata | I metadata risiedono in una regione VM separata (metadata slab) |
| Guard pages / unmapped slices | Catturare scritture out-of-bounds | Aiuta a rilevare buffer overflows invece di corrompere silenziosamente blocchi adiacenti |
| Type-based segregation | Prevenire riuso cross-type & type confusion | Anche allocazioni della stessa dimensione ma di tipi diversi vanno in zone separate |
| Memory Tagging (EMTE / MIE) | Rilevare accessi invalidi, riferimenti obsoleti, OOB, UAF | xzone lavora in concerto con EMTE in modalità sincrona (“Memory Integrity Enforcement”) |
| Delayed reuse / poisoning / zap | Ridurre la probabilità di sfruttare UAF | I block liberati possono essere poisonati, azzerati o messi in quarantena prima del riuso |
| Chunk reclamation / dynamic unmapping | Ridurre spreco di memoria e frammentazione | Interi chunk possono essere unmappati quando non usati |
| Randomization / placement variation | Impedire adiacenza deterministica | I block in un chunk e la selezione del chunk possono avere aspetti randomizzati |
| Segregation of “data-only” allocations | Separare allocazioni che non contengono puntatori | Riduce il controllo dell’attaccante sui metadata o campi di controllo |
Interaction with Memory Integrity Enforcement (MIE / EMTE)
- MIE (Memory Integrity Enforcement) di Apple è il framework hardware + OS che porta Enhanced Memory Tagging Extension (EMTE) in modalità always-on, sincrona su superfici di attacco rilevanti.
- L’allocator xzone è una base fondamentale di MIE in user space: le allocazioni effettuate tramite xzone ricevono tag, e gli accessi sono controllati dall’hardware.
- In MIE, l’allocator, l’assegnazione dei tag, la gestione dei metadata e l’enforcement della confidenzialità dei tag sono integrati per assicurare che errori di memoria (es. stale reads, OOB, UAF) vengano rilevati immediatamente, non sfruttati in seguito.
- Se vuoi, posso anche generare una cheat-sheet o un diagramma degli internals di xzone per il tuo libro. Vuoi che lo faccia adesso?
- :contentReference[oai:20]{index=20}
(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
.ipswfiles. - Decompress until you get the bin format of the kernelcache of both
.ipswfiles. You have information on how to do this on:
macOS Kernel Extensions & Kernelcache
- 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 BinDiffand 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.
JSKit-Based Safari Chains and PREYHUNTER Stagers
Renderer RCE abstraction with JSKit
- Reusable entry: Recent in-the-wild chains abused a WebKit JIT bug (patched as CVE-2023-41993) purely to gain JavaScript-level arbitrary read/write. The exploit immediately pivots into a purchased framework called JSKit, so any future Safari bug only needs to deliver the same primitive.
- Version abstraction & PAC bypasses: JSKit bundles support for a wide range of iOS releases together with multiple, selectable Pointer Authentication Code bypass modules. The framework fingerprints the target build, selects the appropriate PAC bypass logic, and verifies every step (primitive validation, shellcode launch) before progressing.
- Manual Mach-O mapping: JSKit parses Mach-O headers directly from memory, resolves the symbols it needs inside dyld-cached images, and can manually map additional Mach-O payloads without writing them to disk. This keeps the renderer process in-memory only and evades code-signature checks tied to filesystem artifacts.
- Portfolio model: Debug strings such as “exploit number 7” show that the suppliers maintain multiple interchangeable WebKit exploits. Once the JS primitive matches JSKit’s interface, the rest of the chain is unchanged across campaigns.
Kernel bridge: IPC UAF -> code-sign bypass pattern
- Kernel IPC UAF (CVE-2023-41992): The second stage, still running inside the Safari context, triggers a kernel use-after-free in IPC code, re-allocates the freed object from userland, and abuses the dangling pointers to pivot into arbitrary kernel read/write. The stage also reuses PAC bypass material previously computed by JSKit instead of re-deriving it.
- Code-signing bypass (CVE-2023-41991): With kernel R/W available, the exploit patches the trust cache / code-signing structures so unsigned payloads execute as
system. The stage then exposes a lightweight kernel R/W service to later payloads. - Composed pattern: This chain demonstrates a reusable recipe that defenders should expect going forward:
WebKit renderer RCE -> kernel IPC UAF -> kernel arbitrary R/W -> code-sign bypass -> unsigned system stager
PREYHUNTER helper & watcher modules
-
Watcher anti-analysis: Un watcher binario dedicato profila continuamente il dispositivo e interrompe la kill-chain quando viene rilevato un ambiente di ricerca. Ispeziona
security.mac.amfi.developer_mode_status, la presenza di una consolediagnosticd, le localizzazioniUSoIL, tracce di jailbreak come Cydia, processi comebash,tcpdump,frida,sshd, ocheckrain, app AV mobile (McAfee, AvastMobileSecurity, NortonMobileSecurity), impostazioni proxy HTTP custom e root CA custom. Il fallimento di qualsiasi controllo blocca la consegna successiva del payload. -
Helper surveillance hooks: Il componente helper comunica con le altre fasi tramite
/tmp/helper.sock, poi carica set di hook chiamati DMHooker e UMHooker. Questi hook intercettano i percorsi audio VOIP (le registrazioni finiscono sotto/private/var/tmp/l/voip_%lu_%u_PART.m4a), implementano un keylogger di sistema, catturano foto senza UI e hookano SpringBoard per sopprimere le notifiche che tali azioni solitamente genererebbero. L’helper agisce quindi come strato di validazione stealth + sorveglianza leggera prima che vengano deployati implant più pesanti come Predator. -
HiddenDot indicator suppression in SpringBoard: Con injection di codice a livello kernel, Predator hooka
SBSensorActivityDataProvider._handleNewDomainData:(il punto di aggregazione per l’attività dei sensori). L’hook azzera il puntatore Objective-Cself(x0) così la chiamata diventa[nil _handleNewDomainData:newData], eliminando gli aggiornamenti camera/microfono e sopprimendo sia i puntini verdi sia quelli arancioni. -
Mach exception-based hooking flow (DMHooker): Gli hook sono implementati tramite
EXC_BREAKPOINT+ exception ports, poithread_set_statemuta i registri e l’esecuzione riprende. Il codice di ritorno2significa ‘continua con lo stato del thread modificato’. -
PAC-aware redirection for camera access checks: In
mediaserverd, una pattern-scan (es.memmem) localizza una routine privata vicino aFigVideoCaptureSourceCreateWithSourceInfodentroCMCapture.framework. L’hook ritorna3per reindirizzare usando un indirizzo di ritorno PAC pre-signed e cached, soddisfacendo PAC mentre bypassa il controllo. -
VoIP capture pipeline in
mediaserverd: HookaAudioConverterNeweAudioConverterConvertComplexBuffer+52per intercettare i buffer, dedurre il sample rate dalle dimensioni dei buffer, convertire float32 PCM → int16 con NEON, downmixare da 4 canali a stereo e persistere tramiteExtAudioFileWrite(). Il modulo VoIP stesso non sopprime gli indicatori, quindi gli operatori devono abilitare HiddenDot separatamente.
WebKit DFG Store-Barrier UAF + ANGLE PBO OOB (iOS 26.1)
Webkit Dfg Store Barrier Uaf Angle Oob
iMessage/Media Parser Zero-Click Chains
Imessage Media Parser Zero Click Coreaudio Pac Bypass
Riferimenti
- https://www.jamf.com/blog/predator-spyware-ios-recording-indicator-bypass-analysis/
- Google Threat Intelligence – Intellexa zero-day exploits continue
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.


