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

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. mprotect avec 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 :

  1. La valeur du pointeur (portion canonique)
  2. Un modifier (une valeur de contexte, comme un salt)
  3. La clé secrÚte
  4. 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.

  • PACxx instructions signent un pointeur et insĂšrent un PAC
  • AUTxx instructions authentifient + retirent (valident et enlĂšvent le PAC)
  • XPACxx instructions retirent sans valider

Domains / suffixes:

MnemonicMeaning / DomainKey / DomainExample Usage in Assembly
PACIASign instruction pointer with APIAKey“I, A”PACIA X0, X1 — sign pointer in X0 using APIAKey with modifier X1
PACIBSign instruction pointer with APIBKey“I, B”PACIB X2, X3
PACDASign data pointer with APDAKey“D, A”PACDA X4, X5
PACDBSign data pointer with APDBKey“D, B”PACDB X6, X7
PACG / PACGAGeneric (non-pointer) signing with APGAKey“G”PACGA X8, X9, X10 (sign X9 with modifier X10 into X8)
AUTIAAuthenticate APIA-signed instruction pointer & strip PAC“I, A”AUTIA X0, X1 — check PAC on X0 using modifier X1, then strip
AUTIBAuthenticate APIB domain“I, B”AUTIB X2, X3
AUTDAAuthenticate APDA-signed data pointer“D, A”AUTDA X4, X5
AUTDBAuthenticate APDB-signed data pointer“D, B”AUTDB X6, X7
AUTGAAuthenticate generic / blob (APGA)“G”AUTGA X8, X9, X10 (validate generic)
XPACIStrip PAC (instruction pointer, no validation)“I”XPACI X0 — remove PAC from X0 (instruction domain)
XPACDStrip PAC (data pointer, no validation)“D”XPACD X4 — remove PAC from data pointer in X4

There are specialized / alias forms:

  • PACIASP is shorthand for PACIA X30, SP (sign the link register using SP as modifier)
  • AUTIASP is AUTIA X30, SP (authenticate link register with SP)
  • Combined forms like RETAA, RETAB (authenticate-and-return) or BLRAA (authenticate & branch) exist in ARM extensions / compiler support.
  • Also zero-modifier variants: PACIZA / PACIZB where 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

  1. 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.

  1. 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 que dlsym retourne 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 dlsym sur 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, NSExpression ou NSInvocation sont 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 :

  1. 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).
  1. 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”.
  1. 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.
  1. 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.
  1. 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.
  1. 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

  1. Le thread dĂ©rĂ©fĂ©rence un pointeur invalide → le CPU gĂ©nĂšre un Data Abort.

  2. Le gestionnaire de trap du noyau appelle exception_triage(EXC_BAD_ACCESS, ...).

  3. 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).

  1. Si personne ne gùre → bsd_exception() traduit en SIGSEGV.

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 ExceptionSignal
EXC_BAD_ACCESSSIGSEGV or SIGBUS
EXC_BAD_INSTRUCTIONSIGILL
EXC_ARITHMETICSIGFPE
EXC_SOFTWARESIGTRAP
EXC_BREAKPOINTSIGTRAP
EXC_CRASHSIGKILL
EXC_ARM_PACSIGILL (si non fatal)

Fichiers clés dans le code source XNU

  • osfmk/kern/exception.c → Coeur de exception_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 NameElement SizeExample Use
default.kalloc.1616 bytesTrĂšs petites structures kernel, pointeurs.
default.kalloc.3232 bytesPetites structures, en-tĂȘtes d'objets.
default.kalloc.6464 bytesMessages IPC, petits buffers kernel.
default.kalloc.128128 bytesObjets moyens comme des parties de OSObject.




default.kalloc.12801280 bytesGrandes 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 :

  1. Pop HEAD (retourne cette mémoire à l'appelant).

  2. 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:

  1. Heap overflow into an adjacent freed chunk → overwrite its “next” pointer.

  2. 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 :

  1. 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.
  1. 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.
  1. 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).
  1. 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.
  1. 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.
  1. 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 :
  1. Essayer le cache per-CPU.
  2. Si vide, prendre depuis le global freelist.
  3. 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 :

  1. Type lookup → OSData mappe à la zone kalloc_type_osdata (taille 64 bytes).
  2. 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).
  1. 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

FeatureOld Heap (Pre-iOS 15)Modern Heap (iOS 15+ / A12+)
Allocation granularityFixed size buckets (kalloc.16, kalloc.32, etc.)Size + type-based buckets (kalloc_type)
Placement predictabilityHigh (same-size objects side by side)Low (same-type grouping + randomness)
Freelist managementRaw pointers in freed chunks (easy to corrupt)Encoded pointers (safe-linking style)
Adjacent object controlEasy via sprays/frees (feng shui predictable)Hard — typed zones separate attacker objects
Kernel data/code protectionsFew hardware protectionsPPL / SPTM protect page tables & code pages, and PAC protects pointers
Allocation reuse validationNone (freelist pointers raw)zone_require / zone enforcement
Exploit reliabilityHigh with heap spraysMuch lower, requires logic bugs or info leaks
Large allocations handlingAll small allocations managed equallyLarge 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 :

  1. malloc / calloc / realloc / typed alloc est invoqué avec une taille et un type ID.
  2. L’allocator utilise le type ID pour choisir le segment group / zone appropriĂ©.
  3. 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.
  1. La metadata slab est mise à jour (bit free effacé, bookkeeping).
  2. 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”.
  3. 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 :

FeaturePurposeNotes
Metadata decouplingPrevent overflow from corrupting metadataMetadata lives in separate VM region (metadata slab)
Guard pages / unmapped slicesCatch out-of-bounds writesHelps detect buffer overflows rather than silently corrupting adjacent blocks
Type-based segregationPrevent cross-type reuse & type confusionEven same-size allocations from different types go to different zones
Memory Tagging (EMTE / MIE)Detect invalid access, stale references, OOB, UAFxzone works in concert with hardware EMTE in synchronous mode (“Memory Integrity Enforcement”)
Delayed reuse / poisoning / zapReduce chance of use-after-free exploitationFreed blocks may be poisoned, zeroed, or quarantined before reuse
Chunk reclamation / dynamic unmappingReduce memory waste and fragmentationEntire chunks may be unmapped when unused
Randomization / placement variationPrevent deterministic adjacencyBlocks in a chunk and chunk selection may have randomized aspects
Segregation of “data-only” allocationsSeparate allocations that don’t store pointersReduces 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

ios Physical UAF - IOSurface


Ghidra Install BinDiff

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

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

Using BinDiff with Kernel versions

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

macOS Kernel Extensions & Kernelcache

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

Finding the right XNU version

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

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

iMessage/Media Parser Zero-Click Chains

Imessage Media Parser Zero Click Coreaudio Pac Bypass

tip

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