iOS Exploiting
Reading time: 61 minutes
tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :
HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le đŹ groupe Discord ou le groupe telegram ou suivez-nous sur Twitter đŠ @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépÎts github.
iOS Exploit Mitigations
1. Code Signing / Runtime Signature Verification
Introduced early (iPhone OS â iOS) Ceci est lâune des protections fondamentales : tout code exĂ©cutable (apps, dynamic libraries, JIT-ed code, extensions, frameworks, caches) doit ĂȘtre signĂ© cryptographiquement par une chaĂźne de certificats enracinĂ©e dans la confiance dâApple. Ă lâexĂ©cution, avant de charger un binaire en mĂ©moire (ou avant dâeffectuer des sauts Ă travers certaines frontiĂšres), le systĂšme vĂ©rifie sa signature. Si le code est modifiĂ© (bit-flipped, patchĂ©) ou non signĂ©, le chargement Ă©choue.
- EmpĂȘche : lâĂ©tape « classic payload drop + execute » dans les chaĂźnes dâexploit ; lâinjection de code arbitraire ; la modification dâun binaire existant pour y insĂ©rer une logique malveillante.
- Détail du mécanisme :
- Le Mach-O loader (et le dynamic linker) vérifie les pages de code, les segments, les entitlements, les team IDs, et que la signature couvre le contenu du fichier.
- Pour les régions mémoire comme les caches JIT ou le code généré dynamiquement, Apple impose que les pages soient signées ou validées via des API spéciales (ex.
mprotectavec contrĂŽles de code-sign). - La signature inclut entitlements et identifiants ; lâOS impose que certaines API ou capacitĂ©s privilĂ©giĂ©es requiĂšrent des entitlements spĂ©cifiques qui ne peuvent pas ĂȘtre forgĂ©s.
Example
Supposons quâun exploit obtienne exĂ©cution de code dans un processus et essaie dâĂ©crire du shellcode dans le heap puis dây sauter. Sur iOS, cette page devrait ĂȘtre marquĂ©e exĂ©cutable **et** satisfaire les contraintes de code-signature. Comme le shellcode nâest pas signĂ© avec le certificat dâApple, le saut Ă©choue ou le systĂšme refuse de rendre cette rĂ©gion mĂ©moire exĂ©cutable.2. CoreTrust
Introduced around iOS 14+ era (or gradually in newer devices / later iOS) CoreTrust est le sous-systĂšme qui effectue la validation runtime des signatures des binaires (incluant les binaires systĂšme et utilisateurs) contre le certificat racine dâApple plutĂŽt que de sâappuyer sur des magasins de confiance userland mis en cache.
- EmpĂȘche : le tampĂ©rage post-install des binaires, les techniques de jailbreaking qui tentent de remplacer ou patcher des librairies systĂšme ou des apps utilisateur ; tromper le systĂšme en remplaçant des binaires de confiance par des Ă©quivalents malveillants.
- Détail du mécanisme :
- Au lieu de faire confiance Ă une base de confiance locale ou un cache de certificats, CoreTrust se rĂ©fĂšre directement au root dâApple ou vĂ©rifie les certificats intermĂ©diaires dans une chaĂźne sĂ©curisĂ©e.
- Il garantit que les modifications (ex. dans le filesystem) aux binaires existants sont détectées et rejetées.
- Il lie les entitlements, team IDs, flags de code signing et autres métadonnées au binaire au moment du chargement.
Example
Un jailbreak pourrait tenter de remplacer `SpringBoard` ou `libsystem` par une version patchĂ©e pour obtenir de la persistance. Mais quand le loader de lâOS ou CoreTrust vĂ©rifie, il remarque le mismatch de signature (ou des entitlements modifiĂ©s) et refuse dâexĂ©cuter.3. Data Execution Prevention (DEP / NX / W^X)
Introduced in many OSes earlier; iOS had NX-bit / w^x for a long time DEP impose que les pages marquĂ©es en Ă©criture (pour les donnĂ©es) soient non exĂ©cutable, et que les pages marquĂ©es exĂ©cutables soient non modifiables. On ne peut pas simplement Ă©crire du shellcode dans un heap ou une stack et lâexĂ©cuter.
- EmpĂȘche : lâexĂ©cution directe de shellcode ; le pattern classique buffer-overflow â saut vers shellcode injectĂ©.
- Détail du mécanisme :
- Le MMU / les flags de protection mémoire (via les tables de pages) imposent la séparation.
- Toute tentative de marquer une page writable comme executable déclenche un contrÎle systÚme (et est soit interdit soit requiert une approbation de code-sign).
- Dans de nombreux cas, rendre des pages exécutables nécessite de passer par des API OS qui appliquent des contraintes ou des vérifications additionnelles.
Example
Un overflow Ă©crit du shellcode sur le heap. Lâattaquant tente `mprotect(heap_addr, size, PROT_EXEC)` pour le rendre exĂ©cutable. Mais le systĂšme refuse ou valide que la nouvelle page doit satisfaire aux contraintes de code-sign (ce que le shellcode ne peut pas).4. Address Space Layout Randomization (ASLR)
Introduced in iOS ~4â5 era (roughly iOS 4â5 timeframe) ASLR randomise les adresses de base des rĂ©gions mĂ©moire clĂ©s : libraries, heap, stack, etc., Ă chaque lancement de processus. Les adresses des gadgets bougent entre les exĂ©cutions.
- EmpĂȘche : le hardcoding dâadresses de gadgets pour ROP/JOP ; les chaĂźnes dâexploit statiques ; sauts Ă lâaveugle vers des offsets connus.
- Détail du mécanisme :
- Chaque library / module dynamique chargé est rebased à un offset aléatoire.
- Les pointeurs de base de stack et heap sont randomisĂ©s (avec certaines limites dâentropie).
- Parfois dâautres rĂ©gions (ex. allocations mmap) sont aussi randomisĂ©es.
- CombinĂ© avec des mitigations dâinformation-disclosure, cela force lâattaquant Ă dâabord leak une adresse ou un pointeur pour dĂ©couvrir les bases Ă lâexĂ©cution.
Example
Une chaĂźne ROP attend un gadget Ă `0xâŠ.lib + offset`. Mais comme `lib` est relocatĂ© diffĂ©remment Ă chaque exĂ©cution, la chaĂźne hardcodĂ©e Ă©choue. Un exploit doit dâabord leak la base du module avant de calculer les adresses des gadgets.5. Kernel Address Space Layout Randomization (KASLR)
Introduced in iOS ~ (iOS 5 / iOS 6 timeframe) Analogique Ă lâASLR utilisateur, KASLR randomise la base du kernel text et dâautres structures kernel au dĂ©marrage.
- EmpĂȘche : les exploits kernel qui comptent sur une localisation fixe du code ou des donnĂ©es kernel ; les exploits kernel statiques.
- Détail du mécanisme :
- Ă chaque boot, lâadresse de base du kernel est randomisĂ©e (dans une plage).
- Les structures de données kernel (comme
task_structs,vm_map, etc.) peuvent aussi ĂȘtre dĂ©placĂ©es ou avoir des offsets modifiĂ©s. - Les attaquants doivent dâabord leak des pointeurs kernel ou utiliser des vulnĂ©rabilitĂ©s dâinformation pour calculer les offsets avant de dĂ©tourner des structures ou du code kernel.
Example
Une vuln locale vise Ă corrompre un pointeur de fonction kernel (ex. dans un `vtable`) Ă `KERN_BASE + offset`. Mais comme `KERN_BASE` est inconnu, lâattaquant doit dâabord le leak (ex. via une primitive de lecture) avant de calculer lâadresse correcte Ă corrompre.6. Kernel Patch Protection (KPP / AMCC)
Introduced in newer iOS / A-series hardware (post around iOS 15â16 era or newer chips) KPP (aka AMCC) surveille continuellement lâintĂ©gritĂ© des pages de texte kernel (via hash ou checksum). Si elle dĂ©tecte un tampĂ©rage (patchs, hooks inline, modifications de code) en dehors de fenĂȘtres autorisĂ©es, elle dĂ©clenche un kernel panic ou un reboot.
- EmpĂȘche : le patching persistant du kernel (modification dâinstructions kernel), les hooks inline, les overwrites statiques de fonctions.
- Détail du mécanisme :
- Un module hardware ou firmware surveille la région de texte du kernel.
- Il re-hashe périodiquement ou à la demande les pages et compare aux valeurs attendues.
- Si des mismatches apparaissent en dehors des fenĂȘtres de mise Ă jour bĂ©nignes, il panique lâappareil (pour Ă©viter une compromission persistante).
- Les attaquants doivent soit Ă©viter les fenĂȘtres de dĂ©tection soit utiliser des chemins de patch lĂ©gitimes.
Example
Un exploit tente de patcher le prologue dâune fonction kernel (ex. `memcmp`) pour intercepter les appels. Mais KPP remarque que le hash de la page de code ne correspond plus Ă la valeur attendue et dĂ©clenche un kernel panic, plantant lâappareil avant que le patch ne se stabilise.7. Kernel Text ReadâOnly Region (KTRR)
Introduced in modern SoCs (post ~A12 / newer hardware) KTRR est un mĂ©canisme hardware-enforced : une fois le texte kernel verrouillĂ© tĂŽt au dĂ©marrage, il devient en lecture seule depuis EL1 (le kernel), empĂȘchant toute Ă©criture ultĂ©rieure sur les pages de code.
- EmpĂȘche : toute modification du code kernel aprĂšs le boot (ex. patch in-place, injection de code) au niveau de privilĂšge EL1.
- Détail du mécanisme :
- Pendant le boot (dans la phase secure/bootloader), le memory controller (ou une unité hardware sécurisée) marque les pages physiques contenant le texte du kernel comme read-only.
- MĂȘme si un exploit obtient des privilĂšges kernel complets, il ne peut pas Ă©crire sur ces pages pour patcher des instructions.
- Pour les modifier, lâattaquant doit dâabord compromettre la chaĂźne de boot, ou subvertir KTRR lui-mĂȘme.
Example
Un exploit dâĂ©lĂ©vation de privilĂšges saute en EL1 et Ă©crit un trampoline dans une fonction kernel (ex. dans le handler `syscall`). Mais parce que les pages sont verrouillĂ©es en lecture seule par KTRR, lâĂ©criture Ă©choue (ou provoque une faute), et les patches ne sont pas appliquĂ©s.8. Pointer Authentication Codes (PAC)
Introduced with ARMv8.3 (hardware), Apple beginning with A12 / iOS ~12+
- PAC est une fonctionnalitĂ© hardware introduite dans ARMv8.3-A pour dĂ©tecter le tampĂ©rage des valeurs de pointeurs (return addresses, function pointers, certains data pointers) en incorporant une petite signature cryptographique (un âMACâ) dans les bits haut inutilisĂ©s du pointeur.
- La signature (âPACâ) est calculĂ©e sur la valeur du pointeur plus un modifier (une valeur de contexte, ex. stack pointer ou une donnĂ©e distincte). Ainsi, la mĂȘme valeur de pointeur dans des contextes diffĂ©rents obtient un PAC diffĂ©rent.
- Au moment de lâutilisation, avant le dĂ©rĂ©fĂ©rencement ou le branchement via ce pointeur, une instruction authenticate vĂ©rifie le PAC. Si valide, le PAC est retirĂ© et le pointeur pur est obtenu ; si invalide, le pointeur devient âpoisonedâ (ou une faute est levĂ©e).
- Les clés utilisées pour produire/valider les PAC résident dans des registres privilégiés (EL1, kernel) et ne sont pas lisibles directement depuis le mode user.
- Parce que toutes les 64 bits dâun pointeur ne sont pas utilisĂ©es dans de nombreux systĂšmes (ex. espace dâadressage 48-bit), les bits supĂ©rieurs sont âdisponiblesâ et peuvent contenir le PAC sans altĂ©rer lâadresse effective.
Architectural Basis & Key Types
-
ARMv8.3 introduit cinq clés 128-bit (chacune implémentée via deux registres systÚme 64-bit) pour pointer authentication.
-
APIAKey â pour instruction pointers (domaine âIâ, clĂ© A)
-
APIBKey â deuxiĂšme clĂ© dâinstruction pointer (domaine âIâ, clĂ© B)
-
APDAKey â pour data pointers (domaine âDâ, clĂ© A)
-
APDBKey â pour data pointers (domaine âDâ, clĂ© B)
-
APGAKey â clĂ© âgenericâ, pour signer des donnĂ©es non-pointer ou autres usages gĂ©nĂ©riques
-
Ces clés sont stockées dans des registres systÚme privilégiés (accessibles seulement à EL1/EL2 etc.), non accessibles depuis le mode user.
-
Le PAC est calculé via une fonction cryptographique (ARM suggÚre QARMA comme algorithme) en utilisant :
- La valeur du pointeur (portion canonique)
- Un modifier (une valeur de contexte, comme un salt)
- La clé secrÚte
- Une logique de tweak interne Si le PAC rĂ©sultant correspond Ă ce qui est stockĂ© dans les bits supĂ©rieurs du pointeur, lâauthentification rĂ©ussit.
Instruction Families
La convention de nommage est : PAC / AUT / XPAC, puis les lettres de domaine.
PACxxinstructions signent un pointeur et insĂšrent un PACAUTxxinstructions authentifient + retirent (valident et enlĂšvent le PAC)XPACxxinstructions retirent sans valider
Domains / suffixes:
| 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 | Generic (non-pointer) signing with 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 |
There are specialized / alias forms:
PACIASPis shorthand forPACIA X30, SP(sign the link register using SP as modifier)AUTIASPisAUTIA X30, SP(authenticate link register with SP)- Combined forms like
RETAA,RETAB(authenticate-and-return) orBLRAA(authenticate & branch) exist in ARM extensions / compiler support. - Also zero-modifier variants:
PACIZA/PACIZBwhere the modifier is implicitly zero, etc.
Modifiers
Lâobjectif principal du modifier est de lier le PAC Ă un contexte spĂ©cifique afin que la mĂȘme adresse signĂ©e dans des contextes diffĂ©rents produise des PAC diffĂ©rents. Câest comme ajouter un salt Ă un hash.
Ainsi :
- Le modifier est une valeur de contexte (un autre registre) qui est mixée dans le calcul du PAC. Choix typiques : le stack pointer (
SP), un frame pointer, ou un ID dâobjet. - Utiliser SP comme modifier est courant pour la signature des return addresses : le PAC est liĂ© Ă la frame de stack spĂ©cifique. Si on essaie de rĂ©utiliser le LR dans une autre frame, le modifier change, donc la validation PAC Ă©choue.
- La mĂȘme valeur de pointeur signĂ©e sous des modifiers diffĂ©rents produit des PAC diffĂ©rents.
- Le modifier nâa pas besoin dâĂȘtre secret, mais idĂ©alement il nâest pas contrĂŽlĂ© par lâattaquant.
- Pour les instructions qui signent ou vĂ©rifient des pointeurs oĂč il nâexiste pas de modifier significatif, certaines formes utilisent zĂ©ro ou une constante implicite.
Apple / iOS / XNU Customizations & Observations
- LâimplĂ©mentation PAC dâApple inclut des diversificateurs par boot afin que les clĂ©s ou tweaks changent Ă chaque dĂ©marrage, empĂȘchant la rĂ©utilisation entre boots.
- Ils incluent aussi des mitigations cross-domain de sorte que les PAC signĂ©s en user mode ne puissent pas ĂȘtre facilement rĂ©utilisĂ©s en kernel mode, etc.
- Sur Apple M1 / Apple Silicon, le reverse engineering a montrĂ© quâil existe neuf types de modifier et des registres systĂšme Apple-spĂ©cifiques pour le contrĂŽle des clĂ©s.
- Apple utilise PAC dans de nombreux sous-systÚmes kernel : signature des adresses de retour, intégrité des pointeurs dans les données kernel, signed thread contexts, etc.
- Google Project Zero a montré que, sous une primitive de lecture/écriture mémoire puissante dans le kernel, on pouvait forger des PAC kernel (pour les clés A) sur des appareils A12-era, mais Apple a patché beaucoup de ces chemins.
- Dans le systĂšme dâApple, certaines clĂ©s sont globales au kernel, tandis que les processus utilisateurs peuvent obtenir une randomness de clĂ© par-process.
PAC Bypasses
- Kernel-mode PAC: theoretical vs real bypasses
- Parce que les clés et la logique PAC kernel sont strictement contrÎlées (registres privilégiés, diversificateurs, isolation de domaine), forger des pointeurs kernel signés arbitraires est trÚs difficile.
- Azadâs 2020 "iOS Kernel PAC, One Year Later" rapporte que dans iOS 12-13, il a trouvĂ© quelques contournements partiels (signing gadgets, reuse of signed states, indirect branches non protĂ©gĂ©es) mais pas de bypass gĂ©nĂ©rique complet. bazad.github.io
- Les customizations âDark Magicâ dâApple rĂ©duisent encore les surfaces exploitables (domain switching, per-key enabling bits). i.blackhat.com
- Il existe un kernel PAC bypass CVE-2023-32424 connu sur Apple silicon (M1/M2) rapporté par Zecao Cai et al. i.blackhat.com
- Mais ces bypass reposent souvent sur des gadgets trĂšs spĂ©cifiques ou des bugs dâimplĂ©mentation ; ils ne sont pas des contournements gĂ©nĂ©raux.
Ainsi, PAC kernel est considéré comme hautement robuste, bien que pas parfait.
- User-mode / runtime PAC bypass techniques
Ceux-ci sont plus frĂ©quents, et exploitent des imperfections dans lâapplication de PAC ou son usage dans le dynamic linking / les frameworks runtimes. Ci-dessous des classes, avec exemples.
2.1 Shared Cache / A key issues
- Le dyld shared cache est un grand blob prĂ©-liĂ© de frameworks et libraries systĂšme. Parce quâil est largement partagĂ©, des function pointers Ă lâintĂ©rieur du shared cache sont âpre-signedâ puis utilisĂ©s par de nombreux processus. Les attaquants ciblent ces pointeurs dĂ©jĂ signĂ©s comme des âPAC oraclesâ.
- Certaines techniques de bypass tentent dâextraire ou de rĂ©utiliser des pointeurs signĂ©s A-key prĂ©sents dans le shared cache et de les rĂ©utiliser dans des gadgets.
- Le talk "No Clicks Required" dĂ©crit la construction dâun oracle sur le shared cache pour infĂ©rer des adresses relatives et les combiner avec des pointeurs signĂ©s pour contourner PAC. saelo.github.io
- Aussi, les imports de function pointers depuis des libraries partagĂ©es en userspace ont Ă©tĂ© trouvĂ©s insuffisamment protĂ©gĂ©s par PAC, permettant Ă un attaquant dâobtenir des function pointers sans changer leur signature. (EntrĂ©e bug Project Zero) bugs.chromium.org
2.2 dlsym(3) / dynamic symbol resolution
- Un contournement connu est dâappeler
dlsym()pour obtenir un pointeur de fonction dĂ©jĂ signĂ© (signĂ© avec A-key, diversifier Ă zĂ©ro) puis de lâutiliser. Parce quedlsymretourne un pointeur lĂ©gitimement signĂ©, lâutiliser contourne le besoin de forger un PAC. - Le blog dâEpsilon dĂ©taille comment certains bypass exploitent cela : appeler
dlsym("someSym")renvoie un pointeur signĂ© et peut ĂȘtre utilisĂ© pour des appels indirects. blog.epsilon-sec.com - Synacktivâs "iOS 18.4 --- dlsym considered harmful" dĂ©crit un bug : certains symboles rĂ©solus via
dlsymsur iOS 18.4 retournent des pointeurs incorrectement signés (ou avec des diversifiers bogués), permettant un bypass PAC non désiré. Synacktiv - La logique dans dyld pour dlsym inclut : when
result->isCode, they sign the returned pointer with__builtin_ptrauth_sign_unauthenticated(..., key_asia, 0), i.e. context zero. blog.epsilon-sec.com
Ainsi, dlsym est un vecteur fréquent dans les bypass PAC en user-mode.
2.3 Other DYLD / runtime relocations
- Le loader DYLD et la logique de relocation dynamique sont complexes et parfois mappent temporairement des pages en read/write pour effectuer des relocations, puis les remettent en read-only. Les attaquants exploitent ces fenĂȘtres temporelles. Le talk de Synacktiv dĂ©crit "Operation Triangulation", un bypass basĂ© sur le timing des relocations dynamiques. Synacktiv
- Les pages DYLD sont maintenant protégées avec SPRR / VM_FLAGS_TPRO (quelques flags de protection pour dyld). Mais les versions antérieures avaient des gardes plus faibles. Synacktiv
- Dans les chaĂźnes dâexploit WebKit, le loader DYLD est souvent une cible pour le bypass PAC. Les slides mentionnent que beaucoup de bypass PAC ont ciblĂ© le loader DYLD (via relocation, interposer hooks). Synacktiv
2.4 NSPredicate / NSExpression / ObjC / SLOP
- Dans les chaĂźnes dâexploit userland, des mĂ©thodes du runtime Objective-C telles que
NSPredicate,NSExpressionouNSInvocationsont utilisées pour faire passer des appels de contrÎle sans pointer forging évident. - Sur les anciens iOS (avant PAC), un exploit utilisait des fake NSInvocation objects pour appeler des selectors arbitraires sur de la mémoire contrÎlée. Avec PAC, des modifications sont nécessaires. Mais la technique SLOP (SeLector Oriented Programming) est étendue sous PAC aussi. Project Zero
- La technique SLOP originale permettait dâenchaĂźner des appels ObjC en crĂ©ant des invocations factices ; le bypass repose sur le fait que ISA ou des selector pointers ne sont parfois pas entiĂšrement protĂ©gĂ©s par PAC. Project Zero
- Dans des environnements oĂč pointer authentication est appliquĂ© partiellement, les mĂ©thodes / selectors / target pointers peuvent ne pas toujours bĂ©nĂ©ficier de la protection PAC, offrant une marge pour le 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>Exemple</summary>
Un buffer overflow écrase une adresse de retour sur la stack. L'attaquant écrit l'adresse du gadget cible mais ne peut pas calculer le PAC correct. Quand la fonction retourne, l'instruction CPU `AUTIA` fault parce que le PAC ne correspond pas. La chaßne échoue.
L'analyse de Project Zero sur A12 (iPhone XS) a montré comment le PAC d'Apple est utilisé et des méthodes pour forger des PAC si un attaquant dispose d'un primitive de lecture/écriture mémoire.
</details>
### 9. **Branch Target Identification (BTI)**
**Introduit avec ARMv8.5 (matériel plus récent)**
BTI est une fonctionnalité matérielle qui vérifie les **cibles de branchement indirectes** : lors de l'exécution de `blr` ou d'appels/sauts indirects, la cible doit commencer par un **BTI landing pad** (`BTI j` ou `BTI c`). Sauter vers des adresses de gadget qui ne possÚdent pas le landing pad déclenche une exception.
L'implémentation d'LLVM note trois variantes d'instructions BTI et comment elles se mappent aux types de branchement.
| BTI Variant | Ce qu'il permet (quels types de branche) | Placement / cas d'utilisation typique |
|-------------|----------------------------------------|-------------------------------|
| **BTI C** | Cibles des branches indirectes de style *call* (par ex. `BLR`, ou `BR` utilisant X16/X17) | PlacĂ© Ă l'entrĂ©e des fonctions susceptibles d'ĂȘtre appelĂ©es indirectement |
| **BTI J** | Cibles des branches de type *jump* (par ex. `BR` utilisé pour des tail calls) | Placé au début des blocs atteignables par des jump tables ou des tail-calls |
| **BTI JC** | Agit Ă la fois comme C et J | Peut ĂȘtre ciblĂ© par des branches de type call ou jump |
- Dans le code compilé avec enforcement des branch targets, les compilateurs insÚrent une instruction BTI (C, J, ou JC) à chaque cible valide de branchement indirect (débuts de fonctions ou blocs atteignables par des sauts) afin que les branches indirectes ne réussissent que vers ces emplacements.
- Les **branches / calls directs** (c.-à -d. adresses fixes `B`, `BL`) ne sont **pas restreints** par BTI. L'hypothÚse est que les pages de code sont de confiance et que l'attaquant ne peut pas les modifier (donc les branches directes sont sûres).
- De plus, les instructions **RET / return** ne sont généralement pas restreintes par BTI parce que les adresses de retour sont protégées via PAC ou des mécanismes de signature de retour.
#### Mécanisme et application
- Quand le CPU décode une **branche indirecte (BLR / BR)** dans une page marquée comme « guarded / BTI-enabled », il vérifie si la premiÚre instruction de l'adresse cible est un BTI valide (C, J ou JC selon autorisation). Sinon, une **Branch Target Exception** survient.
- L'encodage de l'instruction BTI est conçu pour réutiliser des opcodes précédemment réservés pour des NOPs (dans les versions ARM antérieures). Ainsi, les binaires BTI-enabled restent rétrocompatibles : sur du matériel sans support BTI, ces instructions agissent comme des NOPs.
- Les passes du compilateur qui ajoutent des BTI les insĂšrent uniquement oĂč c'est nĂ©cessaire : fonctions pouvant ĂȘtre appelĂ©es indirectement, ou blocs de base ciblĂ©s par des sauts.
- Certains correctifs et du code LLVM montrent que BTI n'est pas insĂ©rĂ© pour *tous* les blocks â seulement ceux qui sont des cibles potentielles de branches (par ex. depuis des switch / jump tables).
#### Synergie BTI + PAC
PAC protĂšge la valeur du pointeur (la source) â garantit que la chaĂźne d'appels indirects / retours n'a pas Ă©tĂ© modifiĂ©e.
BTI garantit que mĂȘme un pointeur valide ne peut cibler que des points d'entrĂ©e correctement marquĂ©s.
Combinés, l'attaquant a besoin à la fois d'un pointeur valide avec le PAC correct et que la cible possÚde un BTI placé là . Cela augmente la difficulté de construction de gadgets exploitables.
#### Exemple
<details>
<summary>Exemple</summary>
Un exploit tente de pivoter vers un gadget à `0xABCDEF` qui ne commence pas par `BTI c`. Le CPU, en exécutant `blr x0`, vérifie la cible et fault parce que l'alignement d'instruction n'inclut pas un landing pad valide. Ainsi, de nombreux gadgets deviennent inutilisables à moins qu'ils n'aient un préfixe BTI.
</details>
### 10. **Privileged Access Never (PAN) & Privileged Execute Never (PXN)**
**Introduit dans des extensions ARMv8 plus récentes / support iOS (pour noyau renforcé)**
#### PAN (Privileged Access Never)
- **PAN** est une fonctionnalitĂ© introduite en **ARMv8.1-A** qui empĂȘche le code **privilĂ©giĂ©** (EL1 ou EL2) de **lire ou Ă©crire** la mĂ©moire marquĂ©e comme **accessible par l'utilisateur (EL0)**, Ă moins que PAN ne soit explicitement dĂ©sactivĂ©.
- L'idĂ©e : mĂȘme si le kernel est trompĂ© ou compromis, il ne peut pas dĂ©rĂ©fĂ©rencer arbitrairement des pointeurs utilisateur sans d'abord *dĂ©sactiver* PAN, rĂ©duisant ainsi les risques d'exploits de type **`ret2usr`** ou de mauvaise utilisation de buffers contrĂŽlĂ©s par l'utilisateur.
- Quand PAN est activé (PSTATE.PAN = 1), toute instruction privilégiée de load/store accédant à une adresse virtuelle « accessible à EL0 » déclenche une **permission fault**.
- Le kernel, lorsqu'il doit légitimement accéder à la mémoire utilisateur (par ex. copier des données depuis/vers des buffers utilisateur), doit **désactiver temporairement PAN** (ou utiliser des instructions de load/store « non privilégiées ») pour permettre cet accÚs.
- Dans Linux sur ARM64, le support PAN a été introduit vers 2015 : des patchs du kernel ont ajouté la détection de la fonctionnalité, et remplacé `get_user` / `put_user` etc. par des variantes qui effacent PAN autour des accÚs mémoire utilisateur.
**Nuance / limitation / bug clé**
- Comme noté par Siguza et d'autres, un bug de spécification (ou un comportement ambigu) dans la conception ARM signifie que les **execute-only user mappings** (`--x`) peuvent **ne pas déclencher PAN**. En d'autres termes, si une page utilisateur est marquée exécutable mais sans permission de lecture, la tentative de lecture du kernel pourrait contourner PAN parce que l'architecture considÚre « accessible à EL0 » comme nécessitant la permission de lecture, pas seulement l'exécution. Cela conduit à un contournement de PAN dans certaines configurations.
- Ă cause de cela, si iOS / XNU autorise des pages utilisateur execute-only (comme certains setups JIT ou code-cache), le kernel pourrait lire accidentellement depuis elles mĂȘme avec PAN activĂ©. C'est une zone subtile connue comme exploitable sur certains systĂšmes ARMv8+.
#### PXN (Privileged eXecute Never)
- **PXN** est un bit dans la page table (dans les entrées de leaf ou block) qui indique que la page est **non exécutable en mode privilégié** (c.-à -d. quand EL1 exécute).
- PXN empĂȘche le kernel (ou tout code privilĂ©giĂ©) de sauter vers ou d'exĂ©cuter des instructions depuis des pages utilisateur mĂȘme si le contrĂŽle est dĂ©tournĂ©. En pratique, cela bloque la redirection de contrĂŽle au niveau kernel vers la mĂ©moire utilisateur.
- Combiné avec PAN, cela assure que :
1. Le kernel ne peut pas (par défaut) lire ou écrire les données utilisateur (PAN)
2. Le kernel ne peut pas exécuter le code utilisateur (PXN)
- Dans le format de table de pages ARMv8, les entrées leaf ont un bit `PXN` (et aussi `UXN` pour unprivileged execute-never) dans leurs bits d'attributs.
Ainsi, mĂȘme si le kernel a un pointeur de fonction corrompu pointant vers la mĂ©moire utilisateur et tente de brancher lĂ -bas, le bit PXN provoquerait un fault.
#### ModÚle de permissions mémoire & comment PAN et PXN se mappent aux bits de table de pages
Pour comprendre comment PAN / PXN fonctionnent, il faut voir comment la traduction ARM et le modÚle de permissions opÚrent (simplifié) :
- Chaque entrée de page ou block possÚde des champs d'attributs incluant **AP[2:1]** pour les permissions d'accÚs (read/write, privilégié vs non-privé) et les bits **UXN / PXN** pour les restrictions execute-never.
- Quand PSTATE.PAN = 1 (activé), le hardware applique une sémantique modifiée : les accÚs privilégiés aux pages marquées « accessibles par EL0 » (c.-à -d. accessibles par l'utilisateur) sont interdits (fault).
- Ă cause du bug mentionnĂ©, les pages marquĂ©es uniquement exĂ©cutables (sans permission de lecture) peuvent ne pas ĂȘtre considĂ©rĂ©es comme « accessibles par EL0 » selon certaines implĂ©mentations, contournant ainsi PAN.
- Quand le bit PXN d'une page est rĂ©glĂ©, mĂȘme si le fetch d'instruction provient d'un niveau de privilĂšge supĂ©rieur, l'exĂ©cution est interdite.
#### Utilisation du kernel de PAN / PXN dans un OS renforcé (par ex. iOS / XNU)
Dans une conception de kernel renforcé (comme ce que Apple pourrait utiliser) :
- Le kernel active PAN par défaut (donc le code privilégié est contraint).
- Dans les chemins qui doivent légitimement lire ou écrire des buffers utilisateur (par ex. copie dans/syscall, I/O, read/write user pointer), le kernel **désactive temporairement PAN** ou utilise des instructions spéciales pour outrepasser.
- AprÚs avoir fini l'accÚs aux données utilisateur, il doit réactiver PAN.
- PXN est appliqué via les tables de pages : les pages utilisateur ont PXN = 1 (donc le kernel ne peut pas les exécuter), les pages kernel n'ont pas PXN (donc le code kernel peut s'exécuter).
- Le kernel doit s'assurer qu'aucun chemin d'exĂ©cution n'amĂšne le flux d'exĂ©cution dans des rĂ©gions mĂ©moire utilisateur (ce qui contournerait PXN) â ainsi les chaĂźnes d'exploit reposant sur « sauter dans du shellcode contrĂŽlĂ© par l'utilisateur » sont bloquĂ©es.
à cause du contournement PAN via les pages execute-only, dans un systÚme réel, Apple pourrait désactiver ou interdire les pages utilisateurs execute-only, ou patcher autour de la faiblesse de spécification.
#### Surfaces d'attaque, contournements et atténuations
- **Contournement PAN via execute-only pages** : comme discutĂ©, la spec laisse une brĂšche : les pages utilisateur execute-only (pas de perm de lecture) pourraient ne pas ĂȘtre considĂ©rĂ©es comme « accessibles Ă EL0 », donc PAN ne bloquera pas les lectures du kernel selon certaines implĂ©mentations. Cela donne Ă l'attaquant un chemin inhabituel pour fournir des donnĂ©es via des sections « execute-only ».
- **Exploit via fenĂȘtre temporelle** : si le kernel dĂ©sactive PAN pour une fenĂȘtre plus longue que nĂ©cessaire, une course ou un chemin malveillant pourrait exploiter cette fenĂȘtre pour effectuer des accĂšs mĂ©moire utilisateur non voulus.
- **Oubli de réactivation** : si des chemins de code oublient de réactiver PAN, des opérations kernel ultérieures pourraient accéder incorrectement à la mémoire utilisateur.
- **Mauvaise configuration de PXN** : si les tables de pages ne mettent pas PXN sur les pages utilisateur ou mappent incorrectement des pages de code utilisateur, le kernel pourrait ĂȘtre trompĂ© en exĂ©cutant du code utilisateur.
- **Spéculation / side-channels** : analogues aux contournements spéculatifs, il peut exister des effets microarchitecturaux transients qui violent PAN / PXN (bien que de telles attaques dépendent fortement du design du CPU).
- **Interactions complexes** : avec des fonctionnalités avancées (par ex. JIT, shared memory, zones de code just-in-time), le kernel peut avoir besoin d'un contrÎle fin pour permettre certains accÚs mémoire ou exécutions dans des régions mappées utilisateur ; concevoir cela en sécurité sous les contraintes PAN/PXN est non trivial.
#### Exemple
<details>
<summary>Exemple de code</summary>
Voici des séquences pseudo-assembly illustratives montrant l'activation/désactivation de PAN autour d'un accÚs mémoire utilisateur, et comment un fault peut survenir.
</details>
<div class="codeblock_filename_container"><span class="codeblock_filename_inner hljs"> </span></div>
// 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
<details>
If the noyau had **not** set PXN on that page utilisateur, then the branch might succeed â which would be insecure.
If the noyau forgets to re-enable PAN after user memory access, it opens a window where further noyau logic might accidentally read/write arbitrary user memory.
If the user pointer is into an execute-only page (page utilisateur with only execute permission, no read/write), under the PAN spec bug, `ldr W2, [X1]` might **not** fault even with PAN enabled, enabling a bypass exploit, depending on implementation.
</details>
<details>
<summary>Exemple</summary>
A kernel vulnerability tries to take a user-provided function pointer and call it in kernel context (i.e. `call user_buffer`). Under PAN/PXN, that operation is disallowed or faults.
</details>
---
### 11. **Top Byte Ignore (TBI) / Pointer Tagging**
**Introduit dans ARMv8.5 / versions plus récentes (ou extension optionnelle)**
TBI means the top byte (most-significant byte) of a 64-bit pointer is ignored by address translation. This lets OS or hardware embed **tag bits** in the pointerâs top byte without affecting the actual address.
- TBI stands for **Top Byte Ignore** (sometimes called *Address Tagging*). C'est une fonctionnalité matérielle (disponible dans de nombreuses implémentations ARMv8+) qui **ignore les 8 bits supérieurs** (bits 63:56) d'un pointeur 64 bits lors de la **traduction d'adresses / load/store / instruction fetch**.
- En pratique, le CPU traite un pointeur `0xTTxxxx_xxxx_xxxx` (oĂč `TT` = top byte) comme `0x00xxxx_xxxx_xxxx` pour la traduction d'adresses, en ignorant (masquant) l'octet supĂ©rieur. L'octet supĂ©rieur peut ĂȘtre utilisĂ© par le logiciel pour stocker **mĂ©tadonnĂ©es / tag bits**.
- Cela donne au logiciel un espace intégré "gratuit" pour insérer un octet de tag dans chaque pointeur sans modifier la localisation mémoire référencée.
- L'architecture veille à ce que les loads, stores et instruction fetch traitent le pointeur avec son octet supérieur masqué (c.-à -d. tag retiré) avant d'effectuer l'accÚs mémoire réel.
Ainsi, TBI découple le **pointeur logique** (pointeur + tag) de l'**adresse physique** utilisée pour les opérations mémoire.
#### Pourquoi TBI : cas d'utilisation et motivation
- **Pointer tagging / metadata** : Vous pouvez stocker des métadonnées supplémentaires (p.ex. type d'objet, version, bornes, tags d'intégrité) dans cet octet supérieur. Lorsque vous utilisez ensuite le pointeur, le tag est ignoré au niveau matériel, donc vous n'avez pas besoin de le retirer manuellement pour l'accÚs mémoire.
- **Memory tagging / MTE (Memory Tagging Extension)** : TBI est le mécanisme matériel de base sur lequel MTE s'appuie. Dans ARMv8.5, la **Memory Tagging Extension** utilise les bits 59:56 du pointeur comme **tag logique** et le compare à un **tag d'allocation** stocké en mémoire.
- **Enhanced security & integrity** : En combinant TBI avec pointer authentication (PAC) ou des vérifications à l'exécution, vous pouvez exiger non seulement que la valeur du pointeur soit correcte mais aussi que le tag le soit. Un attaquant qui écrase un pointeur sans le tag correct produira un tag non concordant.
- **Compatibility** : Parce que TBI est optionnel et que les bits de tag sont ignorés par le hardware, le code existant non taggé continue de fonctionner normalement. Les bits de tag deviennent effectivement des bits « sans importance » pour le code hérité.
#### Exemple
<details>
<summary>Exemple</summary>
Un pointeur de fonction incluait un tag dans son octet supérieur (par ex. `0xAA`). Un exploit écrase les bits bas du pointeur mais néglige le tag, si bien que lorsque le noyau vérifie ou assainit, la vérification du pointeur échoue ou il est rejeté.
</details>
---
### 12. **Page Protection Layer (PPL)**
**Introduit dans les versions récentes d'iOS / hardware moderne (iOS ~17 / Apple silicon / modÚles haut de gamme)** (some reports show PPL circa macOS / Apple silicon, but Apple is bringing analogous protections to iOS)
- PPL est conçu comme une **frontiĂšre de protection intra-noyau** : mĂȘme si le noyau (EL1) est compromis et dispose de capacitĂ©s de lecture/Ă©criture, **il ne devrait pas ĂȘtre capable de modifier librement** certaines **pages sensibles** (notamment les tables de pages, les mĂ©tadonnĂ©es de signature de code, les pages de code du noyau, les entitlements, les trust caches, etc.).
- Il crĂ©e effectivement un **ânoyau dans le noyauâ** â un composant de confiance plus petit (PPL) avec **privilĂšges Ă©levĂ©s** qui seul peut modifier les pages protĂ©gĂ©es. Le reste du code noyau doit appeler des routines PPL pour effectuer des changements.
- Cela rĂ©duit la surface d'attaque pour les exploits du noyau : mĂȘme avec R/W/exĂ©cution arbitraire complĂšte en mode noyau, le code d'exploit doit aussi d'une maniĂšre ou d'une autre atteindre le domaine PPL (ou contourner PPL) pour modifier des structures critiques.
- Sur les nouvelles puces Apple silicon (A15+ / M2+), Apple migre vers **SPTM (Secure Page Table Monitor)**, qui dans de nombreux cas remplace PPL pour la protection des tables de pages sur ces plateformes.
Hereâs how PPL is believed to operate, based on public analysis:
#### Use of APRR / permission routing (APRR = Access Permission ReRouting)
- Le hardware Apple utilise un mécanisme appelé **APRR (Access Permission ReRouting)**, qui permet aux entrées de tables de pages (PTEs) de contenir de petits indices, plutÎt que des bits de permission complets. Ces indices sont mappés via des registres APRR vers des permissions effectives. Cela permet un remappage dynamique des permissions par domaine.
- PPL exploite APRR pour sĂ©parer les privilĂšges dans le contexte du noyau : seul le domaine PPL est autorisĂ© Ă mettre Ă jour la correspondance entre indices et permissions effectives. Autrement dit, lorsque du code noyau non-PPL Ă©crit une PTE ou tente de changer des bits de permission, la logique APRR l'en empĂȘche (ou impose un mapping en lecture seule).
- Le code PPL lui-mĂȘme s'exĂ©cute dans une rĂ©gion restreinte (p.ex. `__PPLTEXT`) qui est normalement non exĂ©cutable ou non modifiable jusqu'Ă ce que des portes d'entrĂ©e permettent temporairement l'accĂšs. Le noyau appelle des points d'entrĂ©e PPL (« routines PPL ») pour effectuer des opĂ©rations sensibles.
#### Gate / Entry & Exit
- Quand le noyau doit modifier une page protégée (p.ex. changer les permissions d'une page de code noyau, ou modifier les tables de pages), il appelle une routine **PPL wrapper**, qui effectue des validations puis bascule dans le domaine PPL. En dehors de ce domaine, les pages protégées sont effectivement en lecture seule ou non modifiables par le noyau principal.
- Pendant l'entrée en PPL, les mappings APRR sont ajustés pour que les pages mémoire dans la région PPL soient **exécutables & modifiables** au sein de PPL. à la sortie, elles sont remises en lecture seule / non modifiables. Cela garantit que seules des routines PPL correctement auditées peuvent écrire sur les pages protégées.
- Hors PPL, les tentatives du code noyau pour écrire sur ces pages protégées provoqueront une fault (permission denied) car le mapping APRR pour ce domaine de code n'autorise pas l'écriture.
#### Catégories de pages protégées
Les pages que PPL protĂšge typiquement incluent :
- Structures de tables de pages (entrées des tables de traduction, métadonnées de mapping)
- Pages de code du noyau, en particulier celles contenant une logique critique
- Métadonnées de signature de code (trust caches, blobs de signature)
- Tables d'entitlements, tables d'application des signatures
- Autres structures noyau Ă haute valeur oĂč un patch permettrait de contourner les vĂ©rifications de signature ou de manipuler des credentials
L'idĂ©e est que mĂȘme si la mĂ©moire du noyau est totalement contrĂŽlĂ©e, l'attaquant ne peut pas simplement patcher ou réécrire ces pages, sauf s'il compromet aussi les routines PPL ou contourne PPL.
#### Contournements et vulnérabilités connus
1. **Project Zeroâs PPL bypass (stale TLB trick)**
- Un article public de Project Zero décrit un contournement impliquant des **entrées TLB obsolÚtes**.
- L'idée :
1. Allouer deux pages physiques A et B, les marquer comme pages PPL (donc protégées).
2. Mapper deux adresses virtuelles P et Q dont les pages de table de traduction L3 proviennent de A et B.
3. Lancer un thread qui accÚde en continu à Q, maintenant son entrée TLB vivante.
4. Appeler `pmap_remove_options()` pour supprimer les mappings à partir de P ; en raison d'un bug, le code supprime par erreur les TTEs pour P et Q, mais invalide seulement l'entrée TLB pour P, laissant l'entrée obsolÚte de Q active.
5. Réutiliser B (la page de table de Q) pour mapper de la mémoire arbitraire (p.ex. des pages protégées par PPL). Comme l'entrée TLB obsolÚte mappe encore l'ancien mapping de Q, ce mapping reste valide pour ce contexte.
6. Grùce à cela, l'attaquant peut placer un mapping modifiable des pages protégées par PPL sans passer par l'interface PPL.
- Cet exploit nĂ©cessitait un contrĂŽle fin du mapping physique et du comportement du TLB. Il dĂ©montre qu'une frontiĂšre de sĂ©curitĂ© s'appuyant sur la correction des TLB/mapping doit ĂȘtre extrĂȘmement prudente quant Ă l'invalidation des TLB et Ă la cohĂ©rence des mappings.
- Project Zero a commenté que des contournements de ce type sont subtils et rares, mais possibles dans des systÚmes complexes. Ils considÚrent néanmoins PPL comme une atténuation solide.
2. **Autres risques potentiels & contraintes**
- Si un exploit du noyau peut entrer directement dans les routines PPL (en appelant les wrappers PPL), il peut contourner les restrictions. Ainsi, la validation des arguments est critique.
- Des bugs dans le code PPL lui-mĂȘme (p.ex. overflow arithmĂ©tique, vĂ©rifications de bornes) peuvent permettre des modifications hors limites Ă l'intĂ©rieur de PPL. Project Zero a observĂ© qu'un tel bug dans `pmap_remove_options_internal()` a Ă©tĂ© exploitĂ© dans leur contournement.
- La frontiÚre PPL est irrévocablement liée à l'application matérielle (APRR, memory controller), donc elle n'est aussi forte que l'implémentation matérielle.
#### Exemple
<details>
<summary>Exemple de code</summary>
Voici un pseudocode/une logique simplifiée montrant comment un noyau pourrait appeler PPL pour modifier des pages protégées:
</details>
<div class="codeblock_filename_container"><span class="codeblock_filename_inner hljs">c</span></div>
```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
Le kernel peut effectuer de nombreuses opérations normales, mais ce n'est que via les routines ppl_call_* qu'il peut modifier des mappings protégés ou patcher du code.
Example
Un kernel exploit tente d'Ă©craser l'entitlement table, ou de dĂ©sactiver code-sign enforcement en modifiant un kernel signature blob. Parce que cette page est PPL-protected, l'Ă©criture est bloquĂ©e Ă moins de passer par l'interface PPL. Ainsi, mĂȘme avec kernel code execution, vous ne pouvez pas contourner les contraintes de code-sign ou modifier arbitrairement les credential data. Sur iOS 17+ certains appareils utilisent SPTM pour isoler davantage les PPL-managed pages.PPL â SPTM / Remplacements / Futur
- Sur les SoC modernes d'Apple (A15 ou ultérieur, M2 ou ultérieur), Apple prend en charge SPTM (Secure Page Table Monitor), qui remplace PPL pour la protection des page tables.
- Apple indique dans la documentation : â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'architecture SPTM déplace probablement davantage l'application des politiques vers un moniteur de privilÚges supérieurs en dehors du contrÎle du kernel, réduisant encore la frontiÚre de confiance.
MTE | EMTE | MIE
Voici une description à haut niveau de la façon dont EMTE fonctionne dans le cadre du MIE d'Apple :
- Tag assignment
- Lorsqu'une mémoire est allouée (p.ex. dans le kernel ou l'espace utilisateur via des secure allocators), un secret tag est assigné à ce bloc.
- Le pointeur retourné à l'utilisateur ou au kernel inclut ce tag dans ses bits de poids fort (en utilisant TBI / top byte ignore mechanisms).
- Tag checking on access
- Chaque fois qu'un load ou store est exécuté en utilisant un pointeur, le hardware vérifie que le tag du pointeur correspond au tag du bloc mémoire (allocation tag). En cas de mismatch, il provoque un fault immédiatement (puisque synchrone).
- Parce que c'est synchrone, il n'y a pas de fenĂȘtre de âdelayed detectionâ.
- Retagging on free / reuse
- Quand la mémoire est freed, l'allocator change le tag du bloc (ainsi les anciens pointeurs avec de vieux tags ne correspondent plus).
- Un pointeur use-after-free aura donc un tag obsolĂšte et mismatch lors de l'accĂšs.
- Neighbor-tag differentiation to catch overflows
- Les allocations adjacentes reçoivent des tags distincts. Si un buffer overflow déborde dans la mémoire du voisin, le mismatch de tag cause un fault.
- C'est particuliÚrement efficace pour détecter les petits overflows qui franchissent la frontiÚre.
- Tag confidentiality enforcement
- Apple doit empĂȘcher que les valeurs de tag soient leaked (car si un attacker apprend le tag, il pourrait forger des pointeurs avec les tags corrects).
- Ils incluent des protections (microarchitectural / speculative controls) pour éviter les side-channel leakage des bits de tag.
- Kernel and user-space integration
- Apple utilise EMTE non seulement en user-space mais aussi dans les composants critiques du kernel/OS (pour protéger le kernel contre la memory corruption).
- Le hardware/OS garantit que les rĂšgles de tag s'appliquent mĂȘme lorsque le kernel s'exĂ©cute au nom de l'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
</details>
#### Limitations & défis
- **Intrablock overflows**: Si l'overflow reste dans la mĂȘme allocation (ne franchit pas la frontiĂšre) et que le tag reste le mĂȘme, le tag mismatch ne le dĂ©tecte pas.
- **Tag width limitation**: Seuls quelques bits (p. ex. 4 bits, ou petit domaine) sont disponibles pour le tag â espace de noms limitĂ©.
- **Side-channel leaks**: Si les bits de tag peuvent ĂȘtre leaked (via le cache / l'exĂ©cution spĂ©culative), un attaquant peut apprendre les tags valides et contourner. L'enforcement de confidentialitĂ© des tags d'Apple vise Ă attĂ©nuer cela.
- **Performance overhead**: Les vérifications de tag à chaque load/store ajoutent un coût ; Apple doit optimiser le matériel pour réduire cette surcharge.
- **Compatibility & fallback**: Sur du hardware plus ancien ou des parties ne supportant pas EMTE, il doit exister un fallback. Apple affirme que MIE n'est activé que sur les appareils disposant du support.
- **Complex allocator logic**: L'allocator doit gérer les tags, le retagging, aligner les frontiÚres et éviter les collisions de mis-tag. Des bugs dans la logique de l'allocator pourraient introduire des vulnérabilités.
- **Mixed memory / hybrid areas**: Certaines zones mémoire peuvent rester untagged (legacy), rendant l'interopérabilité plus délicate.
- **Speculative / transient attacks**: Comme pour beaucoup de protections microarchitecturales, l'exécution spéculative ou les fusions de micro-op peuvent contourner les vérifications de façon transitoire ou leak des bits de tag.
- **Limited to supported regions**: Apple pourrait n'appliquer EMTE que dans des zones sélectives et à haut risque (kernel, sous-systÚmes critiques pour la sécurité), pas universellement.
---
## Principales améliorations / différences par rapport au MTE standard
Voici les améliorations et changements mis en avant par Apple :
| Fonctionnalité | Original MTE | EMTE (amélioré par Apple) / MIE |
|---|---|---|
| **Check mode** | Prend en charge les modes synchrone et asynchrone. En async, les tag mismatches sont signalĂ©s plus tard (retardĂ©s) | Apple insiste sur le **mode synchrone** par dĂ©faut â les tag mismatches sont dĂ©tectĂ©s immĂ©diatement, sans fenĂȘtre de dĂ©lai/course autorisĂ©e. |
| **Coverage of non-tagged memory** | Les accÚs à la mémoire non-tagged (p. ex. globals) peuvent contourner les vérifications dans certaines implémentations | EMTE exige que les accÚs depuis une région tagged vers de la mémoire non-tagged valident aussi la connaissance du tag, rendant le contournement par mélange d'allocations plus difficile. |
| **Tag confidentiality / secrecy** | Les tags peuvent ĂȘtre observables ou leak via des side channels | Apple ajoute **Tag Confidentiality Enforcement**, qui tente d'empĂȘcher la leakage des valeurs de tag (via des side-channels spĂ©culatifs, etc.). |
| **Allocator integration & retagging** | MTE confie une grande partie de la logique d'allocator au software | Les allocators typés sécurisés d'Apple (kalloc_type, xzone malloc, etc.) s'intÚgrent à EMTE : quand la mémoire est allouée ou libérée, les tags sont gérés à granularité fine. |
| **Always-on by default** | Sur de nombreuses plateformes, MTE est optionnel ou désactivé par défaut | Apple active EMTE / MIE par défaut sur le hardware supporté (p. ex. iPhone 17 / A19) pour le kernel et de nombreux processus user. |
Parce qu'Apple contrÎle à la fois le hardware et la pile software, elle peut appliquer EMTE strictement, éviter les écueils de performance et combler les failles de side-channel.
---
## Comment EMTE fonctionne en pratique (Apple / MIE)
Voici une description Ă plus haut niveau du fonctionnement d'EMTE dans la configuration MIE d'Apple :
1. **Tag assignment**
- Quand la mémoire est allouée (p. ex. dans le kernel ou en user space via des secure allocators), un **secret tag** est assigné à ce bloc.
- Le pointeur renvoyé à l'user ou au kernel inclut ce tag dans ses high bits (en utilisant TBI / top byte ignore mechanisms).
2. **Tag checking on access**
- Chaque fois qu'un load ou un store est exécuté avec un pointeur, le hardware vérifie que le tag du pointeur correspond au tag du bloc mémoire (allocation tag). En cas de mismatch, il faulte immédiatement (puisque synchrone).
- Parce que c'est synchrone, il n'y a pas de fenĂȘtre de dĂ©tection diffĂ©rĂ©e.
3. **Retagging on free / reuse**
- Lorsque la mémoire est freed, l'allocator change le tag du bloc (ainsi les anciens pointeurs avec de vieux tags ne correspondent plus).
- Un pointeur use-after-free aura donc un tag obsolĂšte et provoquera un mismatch Ă l'accĂšs.
4. **Neighbor-tag differentiation to catch overflows**
- Les allocations adjacentes reçoivent des tags distincts. Si un buffer overflow déborde dans la mémoire du voisin, le tag mismatch provoque un fault.
- Ceci est particuliĂšrement efficace pour attraper les petits overflows qui franchissent une frontiĂšre.
5. **Tag confidentiality enforcement**
- Apple doit empĂȘcher que les valeurs de tag soient leaked (car si un attaquant apprend le tag, il pourrait fabriquer des pointeurs avec les bons tags).
- Ils intÚgrent des protections (contrÎles microarchitecturaux / spéculatifs) pour éviter la side-channel leakage des bits de tag.
6. **Kernel and user-space integration**
- Apple utilise EMTE non seulement en user-space mais aussi dans les composants critiques du kernel/OS (pour protéger le kernel contre la corruption mémoire).
- Le hardware/OS assure que les rĂšgles de tag s'appliquent mĂȘme lorsque le kernel s'exĂ©cute au nom de l'user space.
Parce qu'EMTE est intégré dans MIE, Apple utilise EMTE en mode synchrone sur les surfaces d'attaque clés, et non comme option ou mode debug.
---
## Exception handling in XNU
Lorsqu'une **exception** survient (p. ex., `EXC_BAD_ACCESS`, `EXC_BAD_INSTRUCTION`, `EXC_CRASH`, `EXC_ARM_PAC`, etc.), la **Mach layer** du kernel XNU est responsable de son interception avant qu'elle ne devienne un **signal** de type UNIX (comme `SIGSEGV`, `SIGBUS`, `SIGILL`, ...).
Ce processus implique plusieurs couches de propagation et de gestion d'exception avant d'atteindre l'user space ou d'ĂȘtre convertie en signal BSD.
### Exception Flow (High-Level)
1. Le CPU déclenche une exception synchrone (p. ex., déréférencement d'un pointeur invalide, PAC failure, instruction illégale, etc.).
2. Le trap handler bas niveau s'exécute (`trap.c`, `exception.c` dans les sources XNU).
3. Le trap handler appelle `exception_triage()`, le cĆur de la gestion des exceptions Mach.
4. `exception_triage()` décide comment router l'exception :
- D'abord vers le **thread's exception port**.
- Puis vers le **task's exception port**.
- Puis vers le **host's exception port** (souvent `launchd` ou `ReportCrash`).
Si aucun de ces ports ne gĂšre l'exception, le kernel peut :
- **Le convertir en signal BSD** (pour les processus user-space).
- **Panic** (pour les exceptions kernel-space).
### Fonction centrale : `exception_triage()`
La fonction `exception_triage()` achemine les exceptions Mach le long de la chaßne de handlers possibles jusqu'à ce que l'un s'en occupe ou que l'exception soit finalement fatale. Elle est définie dans `osfmk/kern/exception.c`.
<div class="codeblock_filename_container"><span class="codeblock_filename_inner hljs">c</span></div>
```c
void exception_triage(exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt);
Flux d'appels typique :
exception_triage() âââ exception_deliver() âââ exception_deliver_thread() âââ exception_deliver_task() âââ exception_deliver_host()
Si tout Ă©choue â gĂ©rĂ© par bsd_exception() â traduit en un signal comme SIGSEGV.
Ports d'exception
Chaque objet Mach (thread, task, host) peut enregistrer des ports d'exception, vers lesquels les messages d'exception sont envoyés.
Ils sont définis par l'API :
task_set_exception_ports()
thread_set_exception_ports()
host_set_exception_ports()
Chaque port d'exception possĂšde :
- Un masque (quelles exceptions il veut recevoir)
- Un port name (Mach port pour recevoir les messages)
- Un behavior (comment le noyau envoie le message)
- Un flavor (quel état du thread inclure)
Débogueurs et gestion des exceptions
Un debugger (p.ex., LLDB) définit un exception port sur la tùche ou le thread cible, généralement en utilisant task_set_exception_ports().
Lorsque une exception survient :
- Le message Mach est envoyé au processus debugger.
- Le debugger peut décider de gérer (reprendre, modifier les registres, sauter l'instruction) ou ne pas gérer l'exception.
- Si le debugger ne la gĂšre pas, l'exception est propagĂ©e au niveau suivant (task â host).
Flux de EXC_BAD_ACCESS
-
Le thread dĂ©rĂ©fĂ©rence un pointeur invalide â le CPU gĂ©nĂšre un Data Abort.
-
Le gestionnaire de trap du noyau appelle
exception_triage(EXC_BAD_ACCESS, ...). -
Message envoyé à :
-
Thread port â (le debugger peut intercepter le breakpoint).
-
Si le debugger ignore â Task port â (gestionnaire au niveau process).
-
Si ignorĂ© â Host port (gĂ©nĂ©ralement ReportCrash).
- Si personne ne gĂšre â
bsd_exception()traduit enSIGSEGV.
Exceptions PAC
Quand Pointer Authentication (PAC) échoue (incompatibilité de signature), une exception Mach spéciale est levée :
EXC_ARM_PAC(type)- Les codes peuvent inclure des détails (p.ex., type de clé, type de pointeur).
Si le binaire a le flag TFRO_PAC_EXC_FATAL, le noyau traite les Ă©checs PAC comme fatals, contournant l'interception par le debugger. C'est pour empĂȘcher des attaquants d'utiliser des debuggers pour contourner les vĂ©rifications PAC et c'est activĂ© pour les platform binaries.
Breakpoints logiciels
Un breakpoint logiciel (int3 sur x86, brk sur ARM64) est implémenté en provoquant une faute délibérée.
Le debugger l'attrape via le exception port :
- Modifie le pointeur d'instruction ou la mémoire.
- Restaure l'instruction originale.
- Reprend l'exécution.
Ce mĂȘme mĂ©canisme permet de "capturer" une exception PAC â sauf si TFRO_PAC_EXC_FATAL est dĂ©fini, auquel cas elle n'atteint jamais le debugger.
Conversion vers les signaux BSD
Si aucun gestionnaire n'accepte l'exception :
-
Le noyau appelle
task_exception_notify() â bsd_exception(). -
Cela mappe les exceptions Mach en signaux :
| 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 (si non fatal) |
Fichiers clés dans le code source XNU
-
osfmk/kern/exception.câ Coeur deexception_triage(),exception_deliver_*(). -
bsd/kern/kern_sig.câ Logique de livraison des signaux. -
osfmk/arm64/trap.câ Gestionnaires bas niveau des traps. -
osfmk/mach/exc.hâ Codes d'exception et structures. -
osfmk/kern/task.câ Configuration des task exception ports.
Ancien kernel heap (Úre pré-iOS 15 / pré-A12)
Le noyau utilisait un zone allocator (kalloc) divisé en "zones" de taille fixe.
Chaque zone ne stockait des allocations que d'une seule classe de taille.
D'aprÚs la capture d'écran :
| Zone Name | Element Size | Example Use |
|---|---|---|
default.kalloc.16 | 16 bytes | TrĂšs petites structures kernel, pointeurs. |
default.kalloc.32 | 32 bytes | Petites structures, en-tĂȘtes d'objets. |
default.kalloc.64 | 64 bytes | Messages IPC, petits buffers kernel. |
default.kalloc.128 | 128 bytes | Objets moyens comme des parties de OSObject. |
| ⊠| ⊠| ⊠|
default.kalloc.1280 | 1280 bytes | Grandes structures, métadonnées IOSurface/graphics. |
Comment ça fonctionnait :
- Chaque requĂȘte d'allocation Ă©tait arrondie vers le haut Ă la taille de zone la plus proche.
(P.ex., une requĂȘte de 50 octets allait dans la zone
kalloc.64). - La mĂ©moire dans chaque zone Ă©tait conservĂ©e dans une freelist â les chunks libĂ©rĂ©s par le noyau retournaient dans cette zone.
- Si vous dĂ©bordiez un buffer de 64 octets, vous Ă©crasiez l'objet suivant dans la mĂȘme zone.
C'est pour ça que le heap spraying / feng shui Ă©tait si efficace : on pouvait prĂ©dire les voisins d'objets en pulvĂ©risant des allocations de la mĂȘme classe de taille.
La freelist
Ă l'intĂ©rieur de chaque zone kalloc, les objets libĂ©rĂ©s n'Ă©taient pas rendus directement au systĂšme â ils allaient dans une freelist, une liste chaĂźnĂ©e de chunks disponibles.
-
Quand un chunk Ă©tait free, le noyau Ă©crivait un pointeur au dĂ©but de ce chunk â l'adresse du prochain chunk libre dans la mĂȘme zone.
-
La zone conservait un pointeur HEAD vers le premier chunk libre.
-
L'allocation utilisait toujours le HEAD courant :
-
Pop HEAD (retourne cette mémoire à l'appelant).
-
Met Ă jour HEAD = HEAD->next (stockĂ© dans l'entĂȘte du chunk libĂ©rĂ©).
-
La libération poussait les chunks en retour :
-
freed_chunk->next = HEAD -
HEAD = freed_chunk
Ainsi la freelist n'Ă©tait qu'une liste chaĂźnĂ©e construite Ă l'intĂ©rieur de la mĂ©moire libĂ©rĂ©e elle-mĂȘme.
Ătat normal :
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)
Exploiter la freelist
Parce que les 8 premiers octets d'un free chunk = freelist pointer, un attaquant pourrait le corrompre:
-
Heap overflow into an adjacent freed chunk â overwrite its ânextâ pointer.
-
Use-after-free write into a freed object â overwrite its ânextâ pointer.
Then, on the next allocation of that size:
- L'allocator pops the corrupted chunk.
- Follows the attacker-supplied ânextâ pointer.
- Returns a pointer to arbitrary memory, enabling fake object primitives or targeted overwrite.
Visual example of freelist poisoning:
Before corruption:
HEAD âââș [ F1 ] âââș [ F2 ] âââș [ F3 ] âââș NULL
After attacker overwrite of F1->next:
HEAD âââș [ F1 ]
(next) âââș 0xDEAD_BEEF_CAFE_BABE (attacker-chosen)
Next alloc of this zone â kernel hands out memory at attacker-controlled address.
This freelist design 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
Le but du Heap Grooming est de modeler la disposition du heap afin que, lorsquâun attaquant dĂ©clenche un overflow ou un use-after-free, lâobjet cible (victime) soit juste Ă cĂŽtĂ© dâun objet contrĂŽlĂ© par lâattaquant.
Ainsi, quand la corruption mĂ©moire se produit, lâattaquant peut de maniĂšre fiable Ă©craser lâobjet victime avec des donnĂ©es contrĂŽlĂ©es.
Ătapes :
- Spray allocations (fill the holes)
- Au fil du temps, le kernel heap se fragmente : certaines zones ont des trous oĂč dâanciens objets ont Ă©tĂ© free.
- Lâattaquant commence par effectuer de nombreuses allocations factices pour remplir ces espaces, de sorte que le heap devienne âcompactâ et prĂ©visible.
- Force new pages
- Une fois les trous remplis, les allocations suivantes doivent provenir de nouvelles pages ajoutées à la zone.
- Des pages fraßches signifient que les objets seront regroupés ensemble, et non dispersés dans de la mémoire fragmentée ancienne.
- Cela donne Ă lâattaquant un bien meilleur contrĂŽle sur les voisins.
- Place attacker objects
- Lâattaquant effectue ensuite un nouveau spray, crĂ©ant beaucoup dâobjets contrĂŽlĂ©s par lâattaquant dans ces nouvelles pages.
- Ces objets sont prĂ©visibles en taille et en placement (puisquâils appartiennent tous Ă la mĂȘme zone).
- Free a controlled object (make a gap)
- Lâattaquant libĂšre dĂ©libĂ©rĂ©ment un de ses propres objets.
- Cela crĂ©e un âtrouâ dans le heap, que lâallocator rĂ©utilisera ensuite pour la prochaine allocation de cette taille.
- Victim object lands in the hole
- Lâattaquant provoque maintenant le kernel pour allouer lâobjet victime (celui quâil veut corrompre).
- Ătant donnĂ© que le trou est le premier emplacement disponible dans le freelist, la victime est placĂ©e exactement lĂ oĂč lâattaquant a free son objet.
- Overflow / UAF into victim
- Maintenant lâattaquant a des objets contrĂŽlĂ©s autour de la victime.
- En overflowant depuis un de ses propres objets (ou en réutilisant un objet libéré), il peut écraser de maniÚre fiable les champs mémoire de la victime avec des valeurs choisies.
Pourquoi ça marche :
- PredictabilitĂ© de lâallocator de zone : les allocations de la mĂȘme taille proviennent toujours de la mĂȘme zone.
- Comportement du freelist : les nouvelles allocations rĂ©utilisent dâabord le chunk rĂ©cemment free.
- Heap sprays : lâattaquant remplit la mĂ©moire avec un contenu prĂ©visible et contrĂŽle la disposition.
- RĂ©sultat final : lâattaquant contrĂŽle oĂč lâobjet victime atterrit et quelles donnĂ©es se trouvent Ă cĂŽtĂ© de lui.
Modern Kernel Heap (iOS 15+/A12+ SoCs)
Apple a durci lâallocator et rendu le heap grooming beaucoup plus difficile :
1. From Classic kalloc to kalloc_type
- Before: un unique zone
kalloc.<size>existait pour chaque classe de taille (16, 32, 64, ⊠1280, etc.). Tout objet de cette taille y Ă©tait placĂ© â les objets de lâattaquant pouvaient se retrouver Ă cĂŽtĂ© dâobjets kernel privilĂ©giĂ©s. - Now:
- Les objets kernel sont alloués depuis des typed zones (
kalloc_type). - Chaque type dâobjet (ex.
ipc_port_t,task_t,OSString,OSData) a sa propre zone dĂ©diĂ©e, mĂȘme sâils ont la mĂȘme taille. - Le mapping entre type dâobjet â zone est gĂ©nĂ©rĂ© par le kalloc_type system Ă la compilation.
Un attaquant ne peut plus garantir que des donnĂ©es contrĂŽlĂ©es (OSData) se retrouvent adjacentes Ă des objets kernel sensibles (task_t) de la mĂȘme taille.
2. Slabs and Per-CPU Caches
- Le heap est divisé en slabs (pages mémoire découpées en chunks de taille fixe pour cette zone).
- Chaque zone possÚde un cache per-CPU pour réduire la contention.
- Chemin dâallocation :
- Essayer le cache per-CPU.
- Si vide, prendre depuis le global freelist.
- Si le freelist est vide, allouer un nouveau slab (une ou plusieurs pages).
- Avantage : cette dĂ©centralisation rend les heap sprays moins dĂ©terministes, puisque les allocations peuvent ĂȘtre satisfaites depuis les caches de diffĂ©rents CPUs.
3. Randomization inside zones
- Dans une zone, les éléments freed ne sont pas rendus dans un simple ordre FIFO/LIFO.
- XNU moderne utilise des encoded freelist pointers (safe-linking comme Linux, introduit ~iOS 14).
- Chaque pointer du freelist est XOR-encodé avec un cookie secret par zone.
- Cela empĂȘche un attaquant de forger un faux pointer du freelist sâil obtient une primitive dâĂ©criture.
- Certaines allocations sont randomisĂ©es dans leur placement Ă lâintĂ©rieur dâun slab, donc le spraying ne garantit pas lâadjacence.
4. Guarded Allocations
- Certains objets kernel critiques (ex. credentials, structures task) sont alloués dans des guarded zones.
- Ces zones insÚrent des guard pages (mémoire non mappée) entre les slabs ou utilisent des redzones autour des objets.
- Tout overflow dans la guard page dĂ©clenche un fault â panic immĂ©diat au lieu dâune corruption silencieuse.
5. Page Protection Layer (PPL) and SPTM
- MĂȘme si vous contrĂŽlez un objet freed, vous ne pouvez pas modifier toute la mĂ©moire kernel :
- PPL (Page Protection Layer) impose que certaines rĂ©gions (ex. code signing data, entitlements) soient read-only mĂȘme pour le kernel lui-mĂȘme.
- Sur les devices A15/M2+, ce rÎle est remplacé/amélioré par SPTM (Secure Page Table Monitor) + TXM (Trusted Execution Monitor).
- Ces couches matérielles forcées signifient que les attaquants ne peuvent pas escalader depuis une seule corruption de heap vers un patch arbitraire de structures de sécurité critiques.
- (Added / Enhanced) : aussi, PAC (Pointer Authentication Codes) est utilisé dans le kernel pour protéger les pointeurs (particuliÚrement les function pointers, vtables) de sorte que les falsifier ou les corrompre devienne plus difficile.
- (Added / Enhanced) : les zones peuvent appliquer zone_require / zone enforcement, c.-Ă -d. quâun objet free ne peut ĂȘtre rĂ©injectĂ© que via sa zone typĂ©e correcte ; des frees cross-zone invalides peuvent paniquer ou ĂȘtre rejetĂ©s. (Apple Ă©voque cela dans leurs posts sur memory safety)
6. Large Allocations
- Toutes les allocations ne passent pas par
kalloc_type. - Les requĂȘtes trĂšs larges (au-dessus dâenviron ~16 KB) contournent les typed zones et sont servies directement depuis le kernel VM (kmem) via des allocations de pages.
- Celles-ci sont moins prĂ©visibles, mais aussi moins exploitables, car elles ne partagent pas de slabs avec dâautres objets.
7. Allocation Patterns Attackers Target
MĂȘme avec ces protections, les attaquants cherchent toujours :
- Reference count objects : si vous pouvez altérer les compteurs retain/release, vous pouvez provoquer des use-after-free.
- Objects with function pointers (vtables) : corrompre lâun dâeux donne toujours du contrĂŽle de flux.
- Shared memory objects (IOSurface, Mach ports) : ils restent des cibles car ils font le pont user â kernel.
Mais â contrairement Ă avant â vous ne pouvez plus simplement sprayer OSData et espĂ©rer le voir voisin dâun task_t. Il faut des bugs spĂ©cifiques au type ou des info leaks pour rĂ©ussir.
Example: Allocation Flow in Modern Heap
Supposons que userspace appelle IOKit pour allouer un objet OSData :
- Type lookup â
OSDatamappe Ă la zonekalloc_type_osdata(taille 64 bytes). - Check per-CPU cache for free elements.
- Si trouvĂ© â retourner un Ă©lĂ©ment.
- Si vide â aller au global freelist.
- Si freelist vide â allouer un nouveau slab (page de 4KB â 64 chunks de 64 bytes).
- Retourner le chunk Ă lâappelant.
Freelist pointer protection :
- Chaque chunk freed stocke lâadresse du prochain chunk libre, mais encodĂ©e avec une clĂ© secrĂšte.
- Ăcraser ce champ avec des donnĂ©es contrĂŽlĂ©es par lâattaquant ne fonctionnera pas Ă moins de connaĂźtre la clĂ©.
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, 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)
Dans les versions rĂ©centes des OS Apple (surtout iOS 17+), Apple a introduit un allocator userland plus sĂ©curisĂ©, xzone malloc (XZM). Câest lâanalogue user-space de kalloc_type du kernel, appliquant la conscience de type, lâisolation des mĂ©tadonnĂ©es et des protections de memory tagging.
Goals & Design Principles
- Type segregation / type awareness : grouper les allocations par type ou usage (pointer vs data) pour prévenir les type confusion et la réutilisation cross-type.
- Metadata isolation : sĂ©parer les mĂ©tadonnĂ©es du heap (p.ex. free lists, bits de taille/Ă©tat) des payloads dâobjet pour que les Ă©critures hors bornes corrompent moins probablement les mĂ©tadonnĂ©es.
- Guard pages / redzones : insérer des pages non mappées ou du padding autour des allocations pour détecter les overflows.
- Memory tagging (EMTE / MIE) : travailler en conjonction avec le tagging matériel pour détecter use-after-free, out-of-bounds et accÚs invalides.
- Scalable performance : maintenir une faible surcharge, éviter la fragmentation excessive, et supporter de nombreuses allocations par seconde avec une latence basse.
Architecture & Components
Voici les Ă©lĂ©ments principaux de lâallocator xzone :
Segment Groups & Zones
- Segment groups partitionnent lâespace dâadressage par catĂ©gories dâusage : ex.
data,pointer_xzones,data_large,pointer_large. - Chaque segment group contient des segments (plages VM) qui hébergent les allocations pour cette catégorie.
- AssociĂ© Ă chaque segment se trouve une metadata slab (zone VM sĂ©parĂ©e) qui stocke les mĂ©tadonnĂ©es (p.ex. bits free/used, classes de taille) pour ce segment. Cette metadata out-of-line (OOL) garantit que les mĂ©tadonnĂ©es ne sont pas mĂ©langĂ©es aux payloads dâobjet, attĂ©nuant la corruption par overflow.
- Les segments sont dĂ©coupĂ©s en chunks (slices) qui sont Ă leur tour subdivisĂ©s en blocks (unitĂ©s dâallocation). Un chunk est liĂ© Ă une classe de taille et Ă un segment group spĂ©cifique (c.-Ă -d. tous les blocks dâun chunk partagent la mĂȘme taille & catĂ©gorie).
- Pour les allocations petites/moyennes, on utilise des chunks de taille fixe ; pour les grandes/trÚs grandes, on peut mapper séparément.
Chunks & Blocks
- Un chunk est une rĂ©gion (souvent plusieurs pages) dĂ©diĂ©e aux allocations dâune classe de taille dans un group.
- Ă lâintĂ©rieur dâun chunk, les blocks sont des emplacements disponibles pour les allocations. Les blocks freed sont suivis via la metadata slab â p.ex. via des bitmaps ou des free lists stockĂ©s out-of-line.
- Entre les chunks (ou Ă lâintĂ©rieur), des guard slices / guard pages peuvent ĂȘtre insĂ©rĂ©es (p.ex. slices non mappĂ©es) pour dĂ©tecter les Ă©critures hors bornes.
Type / Type ID
- Chaque site dâallocation (ou appel Ă malloc, calloc, etc.) est associĂ© Ă un type identifier (un
malloc_type_id_t) qui encode le type dâobjet allouĂ©. Cet type ID est passĂ© Ă lâallocator, qui lâutilise pour sĂ©lectionner quel zone / segment servira lâallocation. - De ce fait, mĂȘme si deux allocations ont la mĂȘme taille, elles peuvent aller dans des zones totalement diffĂ©rentes si leurs types diffĂšrent.
- Dans les premiĂšres versions dâiOS 17, toutes les APIs (p.ex. CFAllocator) nâĂ©taient pas entiĂšrement type-aware ; Apple a adressĂ© certaines de ces faiblesses dans iOS 18.
Allocation & Freeing Workflow
Voici un flux haut-niveau de lâallocation et du freeing dans xzone :
- malloc / calloc / realloc / typed alloc est invoqué avec une taille et un type ID.
- Lâallocator utilise le type ID pour choisir le segment group / zone appropriĂ©.
- Dans cette zone/segment, il cherche un chunk qui a des blocks libres de la taille demandée.
- Il peut consulter des caches locaux / per-thread pools ou des free block lists depuis la metadata.
- Si aucun block libre nâest disponible, il peut allouer un nouveau chunk dans cette zone.
- La metadata slab est mise à jour (bit free effacé, bookkeeping).
- Si le memory tagging (EMTE) est actif, le block retournĂ© reçoit un tag, et la metadata est mise Ă jour pour reflĂ©ter son Ă©tat âliveâ.
- Quand
free()est appelé :
- Le block est marquĂ© comme freed dans la metadata (via lâOOL slab).
- Le block peut ĂȘtre placĂ© dans une free list ou poolĂ© pour rĂ©utilisation.
- Optionnellement, le contenu du block peut ĂȘtre effacĂ© ou empoisonnĂ© pour rĂ©duire les fuites de donnĂ©es ou lâexploitation de UAF.
- Le tag matĂ©riel associĂ© au block peut ĂȘtre invalidĂ© ou re-tagged.
- Si un chunk entier devient libre (tous les blocks freed), lâallocator peut reclaim ce chunk (le unmaper ou le rendre Ă lâOS) sous pression mĂ©moire.
Security Features & Hardening
Voici les défenses intégrées dans xzone userland :
| Feature | Purpose | Notes |
|---|---|---|
| Metadata decoupling | Prevent overflow from corrupting metadata | Metadata lives in separate VM region (metadata slab) |
| Guard pages / unmapped slices | Catch out-of-bounds writes | Helps detect buffer overflows rather than silently corrupting adjacent blocks |
| Type-based segregation | Prevent cross-type reuse & type confusion | Even same-size allocations from different types go to different zones |
| Memory Tagging (EMTE / MIE) | Detect invalid access, stale references, OOB, UAF | xzone works in concert with hardware EMTE in synchronous mode (âMemory Integrity Enforcementâ) |
| Delayed reuse / poisoning / zap | Reduce chance of use-after-free exploitation | Freed blocks may be poisoned, zeroed, or quarantined before reuse |
| Chunk reclamation / dynamic unmapping | Reduce memory waste and fragmentation | Entire chunks may be unmapped when unused |
| Randomization / placement variation | Prevent deterministic adjacency | Blocks in a chunk and chunk selection may have randomized aspects |
| Segregation of âdata-onlyâ allocations | Separate allocations that donât store pointers | Reduces attacker control over metadata or control fields |
Interaction with Memory Integrity Enforcement (MIE / EMTE)
- Le MIE dâApple (Memory Integrity Enforcement) est le framework hardware + OS qui met lâEnhanced Memory Tagging Extension (EMTE) en mode toujours actif et synchrone sur les surfaces dâattaque majeures.
- Lâallocator xzone est une fondation fondamentale de MIE en user space : les allocations faites via xzone reçoivent des tags, et les accĂšs sont vĂ©rifiĂ©s par le hardware.
- Dans MIE, lâallocator, lâassignation des tags, la gestion des mĂ©tadonnĂ©es et lâapplication de la confidentialitĂ© des tags sont intĂ©grĂ©s pour garantir que les erreurs mĂ©moire (p.ex. lectures stale, OOB, UAF) sont dĂ©tectĂ©es immĂ©diatement, et non exploitĂ©es plus tard.
If you like, I can also generate a cheat-sheet or diagram of xzone internals for your book. Do you want me to do that next? ::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.
iMessage/Media Parser Zero-Click Chains
Imessage Media Parser Zero Click Coreaudio Pac Bypass
tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :
HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le đŹ groupe Discord ou le groupe telegram ou suivez-nous sur Twitter đŠ @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépÎts github.
HackTricks