iOS Exploiting

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

iOS Exploit Mitigations

1. Code Signing / Runtime Signature Verification

Εισήχθη νωρίς (iPhone OS → iOS) Αυτή είναι μία από τις βασικές προστασίες: όλος ο εκτελέσιμος κώδικας (apps, dynamic libraries, JIT-ed code, extensions, frameworks, caches) πρέπει να είναι κρυπτογραφικά υπογεγραμμένος από μια αλυσίδα πιστοποιητικών με ρίζα την εμπιστοσύνη της Apple. Στο runtime, πριν φορτωθεί ένα binary στη μνήμη (ή πριν εκτελεστούν jumps πέρα από ορισμένα όρια), το σύστημα ελέγχει την υπογραφή του. Αν ο κώδικας τροποποιηθεί (bit-flipped, patched) ή δεν είναι υπογεγραμμένος, το φορμάρισμα αποτυγχάνει.

  • Αποτρέπει: το “classic payload drop + execute” στάδιο στις αλυσίδες exploit; arbitrary code injection; την τροποποίηση ενός υπάρχοντος binary για εισαγωγή κακόβουλης λογικής.
  • Λεπτομέρεια μηχανισμού:
  • Ο Mach-O loader (και dynamic linker) ελέγχει code pages, segments, entitlements, team IDs, και ότι η υπογραφή καλύπτει τα περιεχόμενα του αρχείου.
  • Για περιοχές μνήμης όπως JIT caches ή dynamically generated code, η Apple επιβάλλει ότι οι σελίδες να είναι υπογεγραμμένες ή να επικυρώνονται μέσω ειδικών APIs (π.χ. mprotect με code-sign checks).
  • Η υπογραφή περιλαμβάνει entitlements και identifiers· το OS επιβάλλει ότι ορισμένα APIs ή privileged δυνατότητες απαιτούν συγκεκριμένα entitlements που δεν μπορούν να πλαστογραφηθούν.
Example Ας υποθέσουμε ότι ένα exploit αποκτά code execution μέσα σε μια διεργασία και προσπαθεί να γράψει shellcode σε ένα heap και να κάνει jump σε αυτό. Σε iOS, αυτή η σελίδα θα πρέπει να είναι σημειωμένη ως executable **και** να ικανοποιεί τα constraints του code-signature. Εφόσον το shellcode δεν είναι υπογεγραμμένο με το πιστοποιητικό της Apple, το jump αποτυγχάνει ή το σύστημα απορρίπτει το να γίνει εκείνη η περιοχή μνήμης executable.

2. CoreTrust

Εισήχθη γύρω στην εποχή iOS 14+ (ή σταδιακά σε νεότερες συσκευές / μεταγενέστερα iOS) Το CoreTrust είναι το υποσύστημα που εκτελεί runtime signature validation των binaries (συμπεριλαμβανομένων system και user binaries) απέναντι στην root certificate της Apple αντί να βασίζεται σε cached userland trust stores.

  • Αποτρέπει: post-install tampering των binaries, jailbreaking τεχνικές που προσπαθούν να αντικαταστήσουν ή να τροποποιήσουν system libraries ή user apps; το να ξεγελάσει κανείς το σύστημα αντικαθιστώντας trusted binaries με κακόβουλα αντίγραφα.
  • Λεπτομέρεια μηχανισμού:
  • Αντί να εμπιστεύεται μια τοπική trust database ή certificate cache, το CoreTrust αναφέρεται απευθείας στη ρίζα της Apple ή επαληθεύει intermediate certificates σε μια ασφαλή αλυσίδα.
  • Εξασφαλίζει ότι οι τροποποιήσεις (π.χ. στο filesystem) σε υπάρχοντα binaries ανιχνεύονται και απορρίπτονται.
  • Δένει entitlements, team IDs, code signing flags και άλλα metadata με το binary κατά το load time.
Example Ένα jailbreak μπορεί να προσπαθήσει να αντικαταστήσει το `SpringBoard` ή `libsystem` με μια patched έκδοση για να αποκτήσει persistence. Αλλά όταν ο loader του OS ή το CoreTrust ελέγξει, παρατηρεί τη διαφορά στην υπογραφή (ή τα τροποποιημένα entitlements) και αρνείται να εκτελέσει.

3. Data Execution Prevention (DEP / NX / W^X)

Εισήχθη σε πολλά OS νωρίτερα; iOS είχε NX-bit / w^x για μεγάλο διάστημα Το DEP επιβάλλει ότι σελίδες που έχουν σηματοδοτηθεί ως writable (για data) είναι μη εκτελέσιμες, και σελίδες που είναι executable είναι μη writable. Δεν μπορεί κάποιος απλά να γράψει shellcode σε heap ή stack και να το εκτελέσει.

  • Αποτρέπει: άμεση εκτέλεση shellcode; το κλασικό buffer-overflow → jump σε injected shellcode.
  • Λεπτομέρεια μηχανισμού:
  • Η MMU / flags προστασίας μνήμης (μέσω page tables) επιβάλλουν τον διαχωρισμό.
  • Κάθε προσπάθεια να σηματοδοτηθεί μια writable σελίδα ως executable ενεργοποιεί έλεγχο από το σύστημα (και είτε απαγορεύεται είτε απαιτεί code-sign approval).
  • Σε πολλές περιπτώσεις, το να γίνουν σελίδες executable απαιτεί χρήση OS APIs που επιβάλλουν επιπλέον constraints ή checks.
Example Ένα overflow γράφει shellcode στο heap. Ο επιτιθέμενος προσπαθεί `mprotect(heap_addr, size, PROT_EXEC)` για να την κάνει executable. Αλλά το σύστημα αρνείται ή επικυρώνει ότι η νέα σελίδα πρέπει να περάσει τους code-sign constraints (κάτι που το shellcode δεν μπορεί).

4. Address Space Layout Randomization (ASLR)

Εισήχθη σε iOS ~4–5 εποχή (περίπου iOS 4–5 timeframe) Το ASLR τυχαιοποιεί τις βασικές διευθύνσεις περιοχών μνήμης: libraries, heap, stack κ.λπ., σε κάθε εκκίνηση διεργασίας. Οι διευθύνσεις των gadgets μετακινούνται ανά εκτέλεση.

  • Αποτρέπει: hardcoding gadget addresses για ROP/JOP; στατικές αλυσίδες exploit; blind jumping σε γνωστά offsets.
  • Λεπτομέρεια μηχανισμού:
  • Κάθε φορτωμένη βιβλιοθήκη / dynamic module rebase-άρεται σε τυχαιοποιημένο offset.
  • Οι βάσεις του stack και heap τυχαιοποιούνται (εντός ορισμένων ορίων entropy).
  • Μερικές φορές και άλλες περιοχές (π.χ. mmap allocations) τυχαιοποιούνται.
  • Σε συνδυασμό με information-leak mitigations, αναγκάζει τον επιτιθέμενο να πρώτα leak-ώσει μια διεύθυνση ή pointer για να βρει base addresses στο runtime.
Example Μια ROP chain περιμένει gadget σε `0x….lib + offset`. Αλλά καθώς η `lib` έχει relocated διαφορετικά σε κάθε εκτέλεση, η hardcoded αλυσίδα αποτυγχάνει. Ένα exploit πρέπει πρώτα να leak-ώσει τη base address του module πριν υπολογίσει τις διευθύνσεις των gadgets.

5. Kernel Address Space Layout Randomization (KASLR)

Εισήχθη σε iOS ~ (iOS 5 / iOS 6 timeframe) Αναλόγως του user ASLR, το KASLR τυχαιοποιεί τη βάση του kernel text και άλλων kernel δομών κατά την εκκίνηση.

  • Αποτρέπει: kernel-level exploits που βασίζονται σε σταθερές θέσεις kernel code ή data; στατικά kernel exploits.
  • Λεπτομέρεια μηχανισμού:
  • Σε κάθε boot, η βάση του kernel (KERN_BASE) τυχαιοποιείται (εντός κάποιου εύρους).
  • Δομές kernel data (όπως task_structs, vm_map, κ.λπ.) μπορεί επίσης να μετατοπίζονται.
  • Οι επιτιθέμενοι πρέπει πρώτα να leak-ώσουν kernel pointers ή να χρησιμοποιήσουν information disclosure ευπάθειες για να υπολογίσουν offsets πριν χειραγωγήσουν kernel δομές ή code.
Example Μια τοπική ευπάθεια στοχεύει να καταστρέψει έναν kernel function pointer (π.χ. σε `vtable`) στο `KERN_BASE + offset`. Αλλά καθώς το `KERN_BASE` είναι άγνωστο, ο επιτιθέμενος πρέπει πρώτα να το leak-ώσει (π.χ. μέσω ενός read primitive) πριν υπολογίσει τη σωστή διεύθυνση για την καταστροφή.

6. Kernel Patch Protection (KPP / AMCC)

Εισήχθη σε νεότερα iOS / A-series hardware (μετά περίπου iOS 15–16 ή νεότερα chips) Το KPP (aka AMCC) παρακολουθεί συνεχώς την ακεραιότητα των kernel text pages (μέσω hash ή checksum). Αν ανιχνεύσει τροποποίηση (patches, inline hooks, code modifications) εκτός επιτρεπτών παραθύρων, προκαλεί kernel panic ή reboot.

  • Αποτρέπει: μόνιμο kernel patching (τροποποίηση kernel instructions), inline hooks, static function overwrites.
  • Λεπτομέρεια μηχανισμού:
  • Ένα hardware ή firmware module παρακολουθεί την περιοχή kernel text.
  • Περιοδικά ή κατά ζήτηση, επαναυπολογίζει hashes των pages και συγκρίνει με τις αναμενόμενες τιμές.
  • Αν υπάρχουν mismatches εκτός benign update windows, κάνει panic τη συσκευή (για να αποφευχθεί μόνιμη κακόβουλη πάτσα).
  • Οι επιτιθέμενοι πρέπει είτε να αποφύγουν τα detection windows είτε να χρησιμοποιήσουν νόμιμες διαδρομές patching.
Example Ένα exploit προσπαθεί να patch-άρει το prologue μιας kernel function (π.χ. `memcmp`) για να παρεμβαίνει στις κλήσεις. Αλλά το KPP παρατηρεί ότι η code page πλέον δεν ταιριάζει με την αναμενόμενη hash και προκαλεί kernel panic, κρασάροντας τη συσκευή πριν η patch σταθεροποιηθεί.

7. Kernel Text Read‐Only Region (KTRR)

Εισήχθη σε μοντέρνα SoCs (μετά ~A12 / νεότερο hardware) Το KTRR είναι ένας hardware-enforced μηχανισμός: μόλις το kernel text κλειδωθεί νωρίς κατά το boot, γίνεται read-only από EL1 (το kernel), αποτρέποντας περαιτέρω εγγραφές σε code pages.

  • Αποτρέπει: οποιεσδήποτε τροποποιήσεις στον kernel code μετά το boot (π.χ. patching, in-place code injection) στο επίπεδο privilege EL1.
  • Λεπτομέρεια μηχανισμού:
  • Κατά το boot (στο secure/bootloader στάδιο), ο memory controller (ή κάποιο secure hardware unit) σημειώνει τις φυσικές σελίδες που περιέχουν kernel text ως read-only.
  • Ακόμα κι αν ένα exploit αποκτήσει πλήρη kernel privileges, δεν μπορεί να γράψει σε αυτές τις σελίδες για να αλλάξει instructions.
  • Για να τις τροποποιήσει, ο επιτιθέμενος πρέπει πρώτα να compromize-άρει την αλυσίδα εκκίνησης ή να υπονομεύσει το ίδιο το KTRR.
Example Ένα privilege-escalation exploit κάνει jump σε EL1 και προσπαθεί να γράψει ένα trampoline σε μια kernel function (π.χ. στον `syscall` handler). Αλλά επειδή οι σελίδες έχουν κλειδωθεί ως read-only από το KTRR, η εγγραφή αποτυγχάνει (ή προκαλεί fault), οπότε οι patch δεν εφαρμόζονται.

8. Pointer Authentication Codes (PAC)

Εισήχθη με ARMv8.3 (hardware), Apple ξεκινώντας με A12 / iOS ~12+

  • Το PAC είναι μια hardware δυνατότητα εισαγόμενη στο ARMv8.3-A για να ανιχνεύει την παραποίηση των pointer τιμών (return addresses, function pointers, ορισμένοι data pointers) ενσωματώνοντας μια μικρή κρυπτογραφική υπογραφή (ένα “MAC”) στα αχρησιμοποίητα υψηλά bits του pointer.
  • Η υπογραφή (“PAC”) υπολογίζεται πάνω στην τιμή του pointer συν έναν modifier (μια τιμή context, π.χ. stack pointer ή κάποιο διακριτικό δεδομένο). Έτσι η ίδια pointer τιμή σε διαφορετικά contexts έχει διαφορετικό PAC.
  • Κατά τη χρήση, πριν γίνει dereference ή branch μέσω του pointer, μια εντολή authenticate ελέγχει το PAC. Αν είναι έγκυρο, το PAC αφαιρείται και λαμβάνεται το καθαρό pointer· αν είναι άκυρο, ο pointer γίνεται “poisoned” (ή προκαλείται fault).
  • Τα κλειδιά που χρησιμοποιούνται για την παραγωγή/επικύρωση PAC αποθηκεύονται σε privileged registers (EL1, kernel) και δεν είναι προσβάσιμα από user mode.
  • Επειδή δεν χρησιμοποιούνται όλα τα 64 bits ενός pointer σε πολλά συστήματα (π.χ. 48-bit address space), τα άνω bits είναι “ελεύθερα” και μπορούν να φιλοξενήσουν το PAC χωρίς να αλλάξουν τη λειτουργική διεύθυνση.

Architectural Basis & Key Types

  • Το ARMv8.3 εισάγει πέντε 128-bit keys (καθένα υλοποιημένο μέσω δύο 64-bit system registers) για pointer authentication.

  • APIAKey — για instruction pointers (domain “I”, key A)

  • APIBKey — δεύτερο key για instruction pointers (domain “I”, key B)

  • APDAKey — για data pointers (domain “D”, key A)

  • APDBKey — για data pointers (domain “D”, key B)

  • APGAKey — “generic” key, για signing μη-pointer δεδομένων ή άλλες γενικές χρήσεις

  • Αυτά τα keys αποθηκεύονται σε privileged system registers (προσβάσιμα μόνο σε EL1/EL2 κ.λπ.), μη προσβάσιμα από user mode.

  • Το PAC υπολογίζεται μέσω μιας κρυπτογραφικής συνάρτησης (η ARM προτείνει QARMA ως αλγόριθμο) χρησιμοποιώντας:

  1. Την τιμή του pointer (canonical portion)
  2. Έναν modifier (μια τιμή context, όπως ένα salt)
  3. Το secret key
  4. Κάποια εσωτερική tweak λογική Αν το παραγόμενο PAC ταιριάζει με αυτό που είναι αποθηκευμένο στα υψηλά bits του pointer, η authentication πετυχαίνει.

Instruction Families

Η ονοματολογία είναι: PAC / AUT / XPAC, έπειτα τα γράμματα domain.

  • PACxx instructions υπογράφουν ένα pointer και ενθέτουν PAC
  • AUTxx instructions επαληθεύουν + αφαιρούν (validate και remove το PAC)
  • XPACxx instructions αφαιρούν χωρίς επικύρωση

Domains / suffixes:

MnemonicΣημασία / 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

Υπάρχουν εξειδικευμένες / alias μορφές:

  • PACIASP είναι συντόμευση για PACIA X30, SP (sign the link register using SP as modifier)
  • AUTIASP είναι AUTIA X30, SP (authenticate link register with SP)
  • Συνδυασμένες μορφές όπως RETAA, RETAB (authenticate-and-return) ή BLRAA (authenticate & branch) υπάρχουν σε ARM extensions / compiler support.
  • Επίσης υπάρχουν zero-modifier παραλλαγές: PACIZA / PACIZB όπου ο modifier είναι ε implicitly μηδενικός, κ.λπ.

Modifiers

Ο κύριος στόχος του modifier είναι να δέσει το PAC σε ένα συγκεκριμένο context ώστε η ίδια διεύθυνση που έχει υπογραφεί σε διαφορετικά contexts να παράγει διαφορετικά PACs. Είναι σαν να προσθέτεις ένα salt σε ένα hash.

Επομένως:

  • Ο modifier είναι μια τιμή context (ένας register) που μιξάρεται στον υπολογισμό του PAC. Τυπικές επιλογές: το stack pointer (SP), ένας frame pointer, ή κάποιο object ID.
  • Η χρήση του SP ως modifier είναι κοινή για signing return addresses: το PAC δένεται στο συγκεκριμένο stack frame. Αν προσπαθήσεις να επαναχρησιμοποιήσεις το LR σε διαφορετικό frame, ο modifier αλλάζει και η επικύρωση του PAC αποτυγχάνει.
  • Η ίδια pointer τιμή υπογεγραμμένη με διαφορετικούς modifiers παράγει διαφορετικά PACs.
  • Ο modifier δεν χρειάζεται να είναι μυστικός, αλλά ιδανικά δεν πρέπει να είναι υπό τον έλεγχο του επιτιθέμενου.
  • Για εντολές που υπογράφουν ή επικυρώνουν pointers όπου δεν υπάρχει ουσιαστικός modifier, κάποιες μορφές χρησιμοποιούν μηδέν ή έναν implicit constant.

Apple / iOS / XNU Customizations & Observations

  • Η υλοποίηση PAC της Apple περιλαμβάνει per-boot diversifiers ώστε τα keys ή τα tweaks να αλλάζουν σε κάθε boot, αποτρέποντας την επαναχρησιμοποίηση μεταξύ boots.
  • Περιλαμβάνουν επίσης cross-domain mitigations έτσι ώστε PACs που έχουν υπογραφεί σε user mode να μην επαναχρησιμοποιούνται εύκολα σε kernel mode, κ.λπ.
  • Σε Apple M1 / Apple Silicon, το reverse engineering έδειξε ότι υπάρχουν nine modifier types και Apple-specific system registers για τον έλεγχο των keys.
  • Η Apple χρησιμοποιεί PAC σε πολλαπλά kernel υποσυστήματα: signing return addresses, pointer integrity σε kernel data, signed thread contexts, κ.λπ.
  • Η ομάδα Project Zero έδειξε ότι κάτω από ένα ισχυρό memory read/write primitive στο kernel, κάποιος θα μπορούσε να πλαστογραφήσει kernel PACs (για A keys) σε A12-era συσκευές, αλλά η Apple έφτιαξε πολλές από τις διαδρομές αυτές.
  • Στο σύστημα της Apple, κάποια keys είναι global across kernel, ενώ οι user processes μπορεί να λαμβάνουν per-process key randomness.

PAC Bypasses

  1. Kernel-mode PAC: θεωρητικά vs πραγματικά bypasses
  • Επειδή τα kernel PAC keys και η λογική είναι αυστηρά ελεγχόμενα (privileged registers, diversifiers, domain isolation), το να φτιάξεις arbitrary υπογεγραμμένα kernel pointers είναι πολύ δύσκολο.
  • Η εργασία του Azad το 2020 “iOS Kernel PAC, One Year Later” αναφέρει ότι σε iOS 12-13 βρήκε μερικά μερικά bypasses (signing gadgets, reuse of signed states, unprotected indirect branches) αλλά όχι ένα πλήρες generic bypass. bazad.github.io
  • Οι Apple customizations (“Dark Magic”) περιορίζουν περαιτέρω τις επιφάνειες εκμετάλλευσης (domain switching, per-key enabling bits). i.blackhat.com
  • Υπάρχει γνωστό kernel PAC bypass CVE-2023-32424 σε Apple silicon (M1/M2) αναφερθέν από Zecao Cai et al. i.blackhat.com
  • Αλλά αυτά τα bypasses συχνά εξαρτώνται από πολύ συγκεκριμένα gadgets ή implementation bugs· δεν είναι γενικού σκοπού.

Έτσι το kernel PAC θεωρείται πολύ ανθεκτικό, αν και όχι τέλειο.

  1. User-mode / runtime PAC bypass techniques

Αυτά είναι πιο συχνά, και εκμεταλλεύονται ατέλειες στον τρόπο που το PAC εφαρμόζεται ή χρησιμοποιείται στο dynamic linking / runtime frameworks. Παρακάτω κατηγορίες με παραδείγματα.

2.1 Shared Cache / A key issues

  • Το dyld shared cache είναι ένα μεγάλο pre-linked blob των system frameworks και libraries. Επειδή μοιράζεται ευρέως, function pointers μέσα στο shared cache είναι “pre-signed” και χρησιμοποιούνται από πολλές διεργασίες. Οι επιτιθέμενοι στοχεύουν αυτούς τους ήδη-υπογεγραμμένους pointers ως “PAC oracles”.

  • Κάποιοι bypass τεχνικές προσπαθούν να εξάγουν ή να επαναχρησιμοποιήσουν A-key υπογεγραμμένους pointers που υπάρχουν στο shared cache και να τους επαναχρησιμοποιήσουν σε gadgets.

  • Το talk “No Clicks Required” περιγράφει την κατασκευή ενός oracle πάνω στο shared cache για να συναχθούν relative addresses και να συνδυαστούν με signed pointers για bypass του PAC. saelo.github.io

  • Επίσης, imports function pointers από shared libraries σε userspace βρέθηκε ότι δεν προστατεύονται επαρκώς από PAC, επιτρέποντας σε έναν επιτιθέμενο να πάρει function pointers χωρίς να αλλάξει την υπογραφή τους. (Project Zero bug entry) bugs.chromium.org

2.2 dlsym(3) / dynamic symbol resolution

  • Ένα γνωστό bypass είναι να καλέσει κάποιος dlsym() για να λάβει έναν ήδη υπογεγραμμένο function pointer (υπογεγραμμένο με A-key, diversifier μηδέν) και στη συνέχεια να τον χρησιμοποιήσει. Εφόσον dlsym επιστρέφει ένα νόμιμα υπογεγραμμένο pointer, η χρήση του παρακάμπτει την ανάγκη να πλαστογραφηθεί PAC.

  • Το blog του Epsilon εξηγεί πώς κάποια bypasses εκμεταλλεύονται αυτό: κλήση dlsym("someSym") παράγει έναν υπογεγραμμένο pointer που μπορεί να χρησιμοποιηθεί για indirect calls. blog.epsilon-sec.com

  • Το άρθρο της Synacktiv “iOS 18.4 — dlsym considered harmful” περιγράφει ένα bug: κάποια symbols που επιλύονται μέσω dlsym στο iOS 18.4 επιστρέφουν pointers που είναι λανθασμένα υπογεγραμμένα (ή με buggy diversifiers), επιτρέποντας ανεπιθύμητο PAC bypass. Synacktiv

  • Η λογική στο dyld για το dlsym περιλαμβάνει: όταν result->isCode, υπογράφουν τον επιστρεφόμενο pointer με __builtin_ptrauth_sign_unauthenticated(..., key_asia, 0), δηλ. context zero. blog.epsilon-sec.com

Έτσι, το dlsym είναι συχνή διαδρομή σε user-mode PAC bypasses.

2.3 Other DYLD / runtime relocations

  • Ο DYLD loader και η λογική dynamic relocation είναι πολύπλοκα και μερικές φορές προσωρινά κάνουν map σελίδες ως read/write για να εκτελέσουν relocations, και μετά τις επαναφέρουν ως read-only. Οι επιτιθέμενοι εκμεταλλεύονται αυτά τα παράθυρα. Το talk της Synacktiv περιγράφει το “Operation Triangulation”, ένα timing-based bypass του PAC μέσω dynamic relocations. Synacktiv

  • Οι DYLD σελίδες τώρα προστατεύονται με SPRR / VM_FLAGS_TPRO (ορισμένα protection flags για dyld). Αλλά σε παλαιότερες εκδόσεις οι άμυνες ήταν ασθενέστερες. Synacktiv

  • Σε WebKit exploit chains, ο DYLD loader είναι συχνά στόχος για PAC bypass. Τα slides αναφέρουν ότι πολλά PAC bypasses είχαν στοχοποιήσει τον DYLD loader (μέσω relocation, interposer hooks). Synacktiv

2.4 NSPredicate / NSExpression / ObjC / SLOP

  • Σε userland exploit chains, Objective-C runtime μέθοδοι όπως NSPredicate, NSExpression ή NSInvocation χρησιμοποιούνται για να περάσουν control calls χωρίς προφανή pointer forging.

  • Σε παλαιότερο iOS (πριν PAC), ένα exploit χρησιμοποίησε fake NSInvocation objects για να καλέσει arbitrary selectors σε ελεγχόμενη μνήμη. Με PAC χρειάστηκαν τροποποιήσεις. Αλλά η τεχνική SLOP (SeLector Oriented Programming) επεκτάθηκε και υπό PAC. Project Zero

  • Η αρχική SLOP τεχνική επέτρεπε chaining ObjC calls δημιουργώντας fake invocations; το bypass στηρίζεται στο ότι ISA ή selector pointers μερικές φορές δεν προστατεύονται πλήρως με PAC. Project Zero

  • Σε περιβάλλοντα όπου η pointer authentication εφαρμόζεται μερικώς, methods / selectors / target pointers μπορεί να μην έχουν πάντα PAC protection, δίνοντας χώρο για bypass.

Example Flow

Example Signing & Authenticating ``` ; Example: function prologue / return address protection my_func: stp x29, x30, [sp, #-0x20]! ; push frame pointer + LR mov x29, sp PACIASP ; sign LR (x30) using SP as modifier ; … body … mov sp, x29 ldp x29, x30, [sp], #0x20 ; restore AUTIASP ; authenticate & strip PAC ret

; Example: indirect function pointer stored in a struct ; suppose X1 contains a function pointer PACDA X1, X2 ; sign data pointer X1 with context X2 STR X1, [X0] ; store signed pointer

; later retrieval: LDR X1, [X0] AUTDA X1, X2 ; authenticate & strip BLR X1 ; branch to valid target

; Example: stripping for comparison (unsafe) LDR X1, [X0] XPACI X1 ; strip PAC (instruction domain) CMP X1, #some_label_address BEQ matched_label

</details>

<details>
<summary>Example</summary>
Μια buffer overflow υπεργράφει μια return address στην στοίβα. Ο attacker γράφει τη διεύθυνση του target gadget αλλά δεν μπορεί να υπολογίσει το σωστό PAC. Όταν η συνάρτηση επιστρέφει, η CPU’s `AUTIA` instruction κάνει fault επειδή υπάρχει mismatch στο PAC. Η αλυσίδα αποτυγχάνει.
Project Zero’s analysis στο A12 (iPhone XS) έδειξε πώς χρησιμοποιείται το Apple’s PAC και μεθόδους για forging PACs αν ένας attacker έχει primitive για memory read/write.
</details>


### 9. **Branch Target Identification (BTI)**
**Εισήχθη με ARMv8.5 (πιο πρόσφατο hardware)**
το BTI είναι ένα hardware feature που ελέγχει τους **indirect branch targets**: όταν εκτελείται `blr` ή indirect calls/jumps, ο στόχος πρέπει να ξεκινάει με ένα **BTI landing pad** (`BTI j` ή `BTI c`). Το να πηδήξει κάποιος σε gadget addresses που δεν έχουν το landing pad προκαλεί exception.

Η υλοποίηση του LLVM σημειώνει τρεις παραλλαγές των BTI instructions και πώς αντιστοιχούν σε τύπους branches.

| BTI Variant | Τι επιτρέπει (ποιοι τύποι branch) | Τυπική τοποθέτηση / περίπτωση χρήσης |
|-------------|----------------------------------------|-------------------------------|
| **BTI C** | Targets of *call*-style indirect branches (π.χ. `BLR`, ή `BR` χρησιμοποιώντας X16/X17) | Τοποθετείται στην είσοδο συναρτήσεων που μπορεί να κληθούν έμμεσα |
| **BTI J** | Targets of *jump*-style branches (π.χ. `BR` που χρησιμοποιείται για tail calls) | Τοποθετείται στην αρχή block που είναι προσπελάσιμα μέσω jump tables ή tail-calls |
| **BTI JC** | Δρα ως συνδυασμός C και J | Μπορεί να στοχοποιηθεί είτε από call είτε από jump branches |

- Σε κώδικα που μεταφράζεται με branch target enforcement, οι compilers εισάγουν μία BTI instruction (C, J, ή JC) σε κάθε έγκυρο indirect-branch target (αρχές συναρτήσεων ή blocks που είναι προσβάσιμα με jumps) έτσι ώστε τα indirect branches να επιτυγχάνονται μόνο προς αυτά τα σημεία.
- **Direct branches / calls** (δηλαδή fixed-address `B`, `BL`) **δεν περιορίζονται** από BTI. Η υπόθεση είναι ότι οι code pages είναι αξιόπιστες και ο attacker δεν μπορεί να τις αλλάξει (άρα τα direct branches είναι ασφαλή).
- Επίσης, **RET / return** instructions γενικά δεν περιορίζονται από BTI επειδή τα return addresses προστατεύονται μέσω PAC ή return signing μηχανισμών.

#### Μηχανισμός και επιβολή

- Όταν η CPU αποκωδικοποιεί ένα **indirect branch (BLR / BR)** σε μια σελίδα που είναι επισημασμένη ως “guarded / BTI-enabled,” ελέγχει αν η πρώτη instruction στη διεύθυνση του στόχου είναι ένα έγκυρο BTI (C, J, ή JC όπως επιτρέπεται). Αν όχι, εμφανίζεται **Branch Target Exception**.
- Το encoding της BTI instruction έχει σχεδιαστεί ώστε να επαναχρησιμοποιεί opcodes που προηγουμένως ήταν reserved για NOPs (σε παλαιότερες ARM εκδόσεις). Έτσι, τα BTI-enabled binaries παραμένουν backward-compatible: σε hardware χωρίς BTI υποστήριξη, αυτές οι εντολές λειτουργούν ως NOPs.
- Τα compiler passes που προσθέτουν BTIs τα εισάγουν μόνο όπου χρειάζεται: σε συναρτήσεις που μπορεί να κληθούν έμμεσα, ή σε basic blocks που στοχεύονται από jumps.
- Ορισμένα patches και κώδικας LLVM δείχνουν ότι το BTI δεν εισάγεται για *όλα* τα basic blocks — μόνο σε εκείνα που είναι πιθανοί branch targets (π.χ. από switch / jump tables).

#### BTI + PAC συνεργία

Το PAC προστατεύει την τιμή του pointer (την πηγή) — εξασφαλίζει ότι η αλυσίδα των indirect calls / returns δεν έχει παραβιαστεί.

Το BTI εξασφαλίζει ότι ακόμη και ένας έγκυρος pointer πρέπει να στοχεύει μόνο σωστά επισημασμένα entry points.

Συνδυαστικά, ένας attacker χρειάζεται τόσο έναν έγκυρο pointer με σωστό PAC όσο και ο στόχος να έχει ένα BTI τοποθετημένο εκεί. Αυτό αυξάνει τη δυσκολία στο να κατασκευαστούν χρήσιμα exploit gadgets.

#### Παράδειγμα


<details>
<summary>Example</summary>
Ένα exploit προσπαθεί να pivot σε gadget στη διεύθυνση `0xABCDEF` που δεν ξεκινάει με `BTI c`. Η CPU, κατά την εκτέλεση του `blr x0`, ελέγχει τον στόχο και κάνει fault επειδή η ευθυγράμμιση/η πρώτη instruction δεν περιλαμβάνει ένα έγκυρο landing pad. Έτσι πολλά gadgets γίνονται μη χρησιμοποιήσιμα εκτός αν έχουν πρόθεμα BTI.
</details>


### 10. **Privileged Access Never (PAN) & Privileged Execute Never (PXN)**
**Εισήχθη σε πιο πρόσφατες επεκτάσεις ARMv8 / υποστήριξη iOS (για hardened kernel)**

#### PAN (Privileged Access Never)

- Το **PAN** είναι μια δυνατότητα που εισήχθη στο **ARMv8.1-A** και εμποδίζει τον **privileged code** (EL1 ή EL2) από το να **διαβάζει ή να γράφει** μνήμη που είναι σημειωμένη ως **user-accessible (EL0)**, εκτός αν το PAN απενεργοποιηθεί ρητά.
- Η ιδέα: ακόμη κι αν ο kernel παγιδευτεί ή παραβιαστεί, δεν μπορεί τυχαία να dereference user-space pointers χωρίς πρώτα να *καθαρίσει* το PAN, μειώνοντας έτσι τον κίνδυνο exploit τύπου **`ret2usr`** ή κακής χρήσης user-controlled buffers.
- Όταν το PAN είναι ενεργό (PSTATE.PAN = 1), οποιαδήποτε privileged load/store instruction που προσπελαύνει μια virtual address που “είναι accessible at EL0” προκαλεί **permission fault**.
- Ο kernel, όταν πρέπει νόμιμα να προσπελάσει user-space memory (π.χ. να αντιγράψει δεδομένα προς/από user buffers), πρέπει **προσωρινά να απενεργοποιήσει το PAN** (ή να χρησιμοποιήσει “unprivileged load/store” instructions) για να επιτρέψει την πρόσβαση.
- Στο Linux σε ARM64, η υποστήριξη PAN εισήχθη περίπου το 2015: patches στον kernel πρόσθεσαν detection της δυνατότητας και αντικατέστησαν `get_user` / `put_user` κλπ. με παραλλαγές που καθαρίζουν το PAN γύρω από τις προσβάσεις user memory.

**Κρίσινη λεπτομέρεια / περιορισμός / bug**
- Όπως αναφέρθηκε από τον Siguza και άλλους, ένα specification bug (ή ασαφής συμπεριφορά) στο σχεδιασμό του ARM σημαίνει ότι οι **execute-only user mappings** (`--x`) μπορεί **να μην ενεργοποιούν το PAN**. Με άλλα λόγια, αν μια user page είναι σημειωμένη executable αλλά χωρίς δικαίωμα ανάγνωσης, η προσπάθεια ανάγνωσης από τον kernel μπορεί να παρακάμψει το PAN επειδή η αρχιτεκτονική θεωρεί ότι “accessible at EL0” προϋποθέτει δικαίωμα ανάγνωσης, όχι μόνο εκτέλεσης. Αυτό οδηγεί σε PAN bypass σε ορισμένες διαμορφώσεις.
- Εξαιτίας αυτού, αν iOS / XNU επιτρέπουν execute-only user pages (όπως σε κάποια JIT ή code-cache setups), ο kernel ενδέχεται να διαβάσει κατά λάθος από αυτές ακόμη με ενεργοποιημένο PAN. Αυτό είναι γνωστό ως λεπτή, εκμεταλλεύσιμη περιοχή σε κάποια ARMv8+ συστήματα.

#### PXN (Privileged eXecute Never)

- Το **PXN** είναι ένα flag στα page table entries (σε leaf ή block entries) που υποδεικνύει ότι η σελίδα **δεν είναι εκτελέσιμη όταν εκτελείται σε privileged mode** (δηλαδή όταν εκτελείται από EL1).
- Το PXN αποτρέπει τον kernel (ή οποιοδήποτε privileged code) από το να κάνει jump ή να εκτελέσει εντολές από user-space pages ακόμη και αν ο έλεγχος έχει αλλοιωθεί. Στην ουσία, σταματάει το kernel-level control-flow redirection σε user memory.
- Σε συνδυασμό με το PAN, αυτό διασφαλίζει ότι:
1. Ο kernel δεν μπορεί (από προεπιλογή) να διαβάσει ή να γράψει user-space δεδομένα (PAN)
2. Ο kernel δεν μπορεί να εκτελέσει user-space κώδικα (PXN)
- Στο format των ARMv8 page tables, οι leaf entries έχουν ένα `PXN` bit (και επίσης `UXN` για unprivileged execute-never) στα attribute bits τους.

Ακόμη και αν ο kernel έχει έναν corrupted function pointer που δείχνει σε user memory, και προσπαθήσει να κάνει branch εκεί, το PXN bit θα προκαλέσει fault.

#### Μοντέλο δικαιωμάτων μνήμης & πώς το PAN και το PXN αντιστοιχούν σε bits του page table

Για να κατανοήσετε πώς λειτουργούν το PAN / PXN, πρέπει να δείτε πώς λειτουργεί το translation και το μοντέλο δικαιωμάτων του ARM (απλοποιημένα):

- Κάθε page ή block entry έχει fields attributes που περιλαμβάνουν τα **AP[2:1]** για δικαιώματα πρόσβασης (read/write, privileged vs unprivileged) και bits **UXN / PXN** για execute-never περιορισμούς.
- Όταν το PSTATE.PAN είναι 1 (enabled), το hardware επιβάλλει τροποποιημένη σημασιολογία: οι privileged προσβάσεις σε σελίδες που είναι σημειωμένες ως “accessible by EL0” (δηλαδή user-accessible) απαγορεύονται (fault).
- Εξαιτίας του αναφερόμενου bug, σελίδες που είναι σημειωμένες μόνο ως executable (χωρίς δικαίωμα ανάγνωσης) μπορεί να μην μετριούνται ως “accessible by EL0” σε ορισμένες υλοποιήσεις, και έτσι να παρακάμπτουν το PAN.
- Όταν το PXN bit μιας σελίδας είναι ενεργό, ακόμη κι αν το instruction fetch προέρχεται από υψηλότερο privilege level, η εκτέλεση απαγορεύεται.

#### Χρήση PAN / PXN από τον kernel σε hardened OS (π.χ. iOS / XNU)

Σε ένα hardened kernel design (όπως πιθανώς χρησιμοποιεί η Apple):

- Ο kernel ενεργοποιεί το PAN από προεπιλογή (ώστε ο privileged code να περιοριστεί).
- Σε μονοπάτια που νόμιμα χρειάζεται να διαβάσει ή να γράψει user buffers (π.χ. syscall buffer copy, I/O, read/write user pointer), ο kernel προσωρινά **απενεργοποιεί το PAN** ή χρησιμοποιεί ειδικές εντολές για να το παρακάμψει.
- Μετά το τέλος της πρόσβασης σε user data, πρέπει να επανενεργοποιήσει το PAN.
- Το PXN επιβάλλεται μέσω των page tables: οι user pages έχουν PXN = 1 (ώστε ο kernel να μην τις εκτελεί), οι kernel pages δεν έχουν PXN (ώστε ο kernel code να μπορεί να εκτελείται).
- Ο kernel πρέπει να διασφαλίσει ότι κανένα code path δεν προκαλεί εκτέλεση σε user memory regions (που θα παρακάμπταναν το PXN) — έτσι οι exploit chains που στηρίζονται στο “jump into user-controlled shellcode” αποκλείονται.

Εξαιτίας της σημειωμένης PAN bypass μέσω execute-only pages, σε ένα πραγματικό σύστημα, η Apple μπορεί να απαγορεύει ή να απενεργοποιεί execute-only user pages, ή να επιδιορθώνει το specification weakness.

#### Επιφάνειες επίθεσης, παρακάμψεις και mitigations

- **PAN bypass μέσω execute-only pages**: όπως συζητήθηκε, το spec αφήνει ένα κενό: user pages με execute-only (χωρίς δικαίωμα ανάγνωσης) μπορεί να μην θεωρούνται “accessible at EL0,” έτσι το PAN δεν θα εμποδίσει kernel reads από τέτοιες σελίδες σε ορισμένες υλοποιήσεις. Αυτό δίνει στον attacker ένα ασυνήθιστο μονοπάτι για να παρέχει δεδομένα μέσω “execute-only” sections.
- **Temporal window exploit**: αν ο kernel απενεργοποιεί το PAN για ένα παράθυρο μεγαλύτερο από το αναγκαίο, μια race ή κακόβουλη διαδρομή μπορεί να εκμεταλλευτεί αυτό το παράθυρο για να κάνει ανεπιθύμητη πρόσβαση σε user memory.
- **Forgotten re-enable**: αν κάποιο code path ξεχάσει να επανενεργοποιήσει το PAN, μετέπειτα kernel operations μπορεί να προσπελάσουν λανθασμένα user memory.
- **Misconfiguration of PXN**: αν τα page tables δεν θέτουν PXN στις user pages ή χαρτογραφούν λάθος user code pages, ο kernel μπορεί να παραπλανηθεί ώστε να εκτελέσει user-space code.
- **Speculation / side-channels**: ανάλογα με speculative bypasses, μπορεί να υπάρχουν microarchitectural παρενέργειες που προκαλούν transient παραβιάσεις των PAN / PXN checks (αν και τέτοιες επιθέσεις εξαρτώνται έντονα από το σχεδιασμό του CPU).
- **Σύνθετες αλληλεπιδράσεις**: Σε πιο προηγμένες λειτουργίες (π.χ. JIT, shared memory, just-in-time code regions), ο kernel μπορεί να χρειαστεί λεπτομερή έλεγχο για να επιτρέψει ορισμένες προσβάσεις μνήμης ή εκτέλεση σε user-mapped περιοχές· το σχεδιασμό τους με ασφάλεια υπό τους περιορισμούς PAN/PXN δεν είναι απλό.

#### Παράδειγμα

<details>
<summary>Code Example</summary>
Εδώ είναι ενδεικτικές pseudo-assembly ακολουθίες που δείχνουν το enabling/disabling του PAN γύρω από πρόσβαση σε user memory, και πώς μπορεί να συμβεί ένα fault.
</details>

// Suppose kernel entry point, PAN is enabled (privileged code cannot access user memory by default)

; Kernel receives a syscall with user pointer in X0 ; wants to read an integer from user space mov X1, X0 ; X1 = user pointer

; disable PAN to allow privileged access to user memory MSR PSTATE.PAN, #0 ; clear PAN bit, disabling the restriction

ldr W2, [X1] ; now allowed load from user address

; re-enable PAN before doing other kernel logic MSR PSTATE.PAN, #1 ; set PAN

; … further kernel work …

; Later, suppose an exploit corrupts a pointer to a user-space code page and jumps there BR X3 ; branch to X3 (which points into user memory)

; Because the target page is marked PXN = 1 for privileged execution, ; the CPU throws an exception (fault) and rejects execution

If the kernel had **not** set PXN on that user page, then the branch might succeed — which would be insecure.

If the kernel forgets to re-enable PAN after user memory access, it opens a window where further kernel logic might accidentally read/write arbitrary user memory.

If the user pointer is into an execute-only page (user page 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>Example</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**
**Εισήχθη στο ARMv8.5 / νεότερο (ή προαιρετική επέκταση)**
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*). It is a hardware feature (available in many ARMv8+ implementations) that **ignores the top 8 bits** (bits 63:56) of a 64-bit pointer when performing **address translation / load/store / instruction fetch**.
- In effect, the CPU treats a pointer `0xTTxxxx_xxxx_xxxx` (where `TT` = top byte) as `0x00xxxx_xxxx_xxxx` for the purposes of address translation, ignoring (masking off) the top byte. The top byte can be used by software to store **metadata / tag bits**.
- This gives software “free” in-band space to embed a byte of tag in each pointer without altering which memory location it refers to.
- The architecture ensures that loads, stores, and instruction fetch treat the pointer with its top byte masked (i.e. tag stripped off) before performing the actual memory access.

Thus TBI decouples the **logical pointer** (pointer + tag) from the **physical address** used for memory operations.

#### Why TBI: Use cases and motivation

- **Pointer tagging / metadata**: Μπορείτε να αποθηκεύετε επιπλέον metadata (π.χ. object type, version, bounds, integrity tags) σε αυτό το top byte. Όταν χρησιμοποιείτε αργότερα τον δείκτη, το tag αγνοείται σε επίπεδο hardware, οπότε δεν χρειάζεται να το αφαιρέσετε χειροκίνητα για την πρόσβαση στη μνήμη.
- **Memory tagging / MTE (Memory Tagging Extension)**: TBI is the base hardware mechanism that MTE builds on. In ARMv8.5, the **Memory Tagging Extension** uses bits 59:56 of the pointer as a **logical tag** and checks it against an **allocation tag** stored in memory.
- **Enhanced security & integrity**: By combining TBI with pointer authentication (PAC) or runtime checks, you can force not just the pointer value but also the tag to be correct. An attacker overwriting a pointer without the correct tag will produce a mismatched tag.
- **Compatibility**: Because TBI is optional and tag bits are ignored by hardware, existing untagged code continues to operate normally. The tag bits effectively become “don’t care” bits for legacy code.

#### Example
<details>
<summary>Example</summary>
A function pointer included a tag in its top byte (say `0xAA`). An exploit overwrites the pointer low bits but neglects the tag, so when the kernel verifies or sanitizes, the pointer fails or is rejected.
</details>

---

### 12. **Page Protection Layer (PPL)**
**Εισήχθη στα τέλη του iOS / σε σύγχρονο hardware (iOS ~17 / Apple silicon / high-end models)** (κάποιες αναφορές δείχνουν PPL στο macOS / Apple silicon, αλλά η Apple φέρνει ανάλογες προστασίες στο iOS)

- Το PPL σχεδιάστηκε ως ένα **intra-kernel protection boundary**: ακόμα κι αν ο kernel (EL1) έχει παραβιαστεί και διαθέτει read/write πρόσβαση, **δεν θα πρέπει να μπορεί ελεύθερα να τροποποιεί** ορισμένες **ευαίσθητες σελίδες** (ειδικά page tables, code-signing metadata, kernel code pages, entitlements, trust caches, κ.λπ.).
- Δημιουργεί ουσιαστικά έναν **“kernel μέσα στο kernel”** — ένα μικρότερο αξιόπιστο συστατικό (PPL) με **ανυψωμένα προνόμια** που μόνο αυτό μπορεί να τροποποιεί προστατευμένες σελίδες. Άλλος κώδικας του kernel πρέπει να καλεί ρουτίνες PPL για να κάνει αλλαγές.
- Αυτό μειώνει την attack surface για kernel exploits: ακόμα κι αν υπάρχει πλήρης αυθαίρετο R/W/execute σε kernel mode, ο exploit πρέπει να καταφέρει επίσης να μπει στον τομέα PPL (ή να παρακάμψει το PPL) για να τροποποιήσει κρίσιμες δομές.
- Σε νεότερα Apple silicon (A15+ / M2+), η Apple μεταβαίνει προς **SPTM (Secure Page Table Monitor)**, που σε πολλές περιπτώσεις αντικαθιστά το PPL για προστασία page-table σε αυτές τις πλατφόρμες.

Here’s how PPL is believed to operate, based on public analysis:

#### Use of APRR / permission routing (APRR = Access Permission ReRouting)

- Apple hardware uses a mechanism called **APRR (Access Permission ReRouting)**, which allows page table entries (PTEs) to contain small indices, rather than full permission bits. Those indices are mapped via APRR registers to actual permissions. This allows dynamic remapping of permissions per domain.
- PPL leverages APRR to segregate privilege within kernel context: only the PPL domain is permitted to update the mapping between indices and effective permissions. That is, when non-PPL kernel code writes a PTE or tries to flip permission bits, the APRR logic disallows it (or enforces read-only mapping).
- PPL code itself runs in a restricted region (e.g. `__PPLTEXT`) which is normally non-executable or non-writable until entry gates temporarily allow it. The kernel calls PPL entry points (“PPL routines”) to perform sensitive operations.

#### Gate / Entry & Exit

- When the kernel needs to modify a protected page (e.g. change permissions of a kernel code page, or modify page tables), it calls into a **PPL wrapper** routine, which does validation and then transitions into the PPL domain. Outside that domain, the protected pages are effectively read-only or non-modifiable by the main kernel.
- During PPL entry, the APRR mappings are adjusted so that memory pages in the PPL region are set to **executable & writable** within PPL. Upon exit, they are returned to read-only / non-writable. This ensures that only well-audited PPL routines can write to protected pages.
- Outside PPL, attempts by kernel code to write to those protected pages will fault (permission denied) because the APRR mapping for that code domain doesn’t permit writing.

#### Protected page categories

The pages that PPL typically protects include:

- Page table structures (translation table entries, mapping metadata)
- Kernel code pages, especially those containing critical logic
- Code-sign metadata (trust caches, signature blobs)
- Entitlement tables, signature enforcement tables
- Other high-value kernel structures where a patch would allow bypassing signature checks or credentials manipulation

The idea is that even if the kernel memory is fully controlled, the attacker cannot simply patch or rewrite these pages, unless they also compromise PPL routines or bypass PPL.

#### Known Bypasses & Vulnerabilities

1. **Project Zero’s PPL bypass (stale TLB trick)**

- A public writeup by Project Zero describes a bypass involving **stale TLB entries**.
- The idea:

1. Allocate two physical pages A and B, mark them as PPL pages (so they are protected).
2. Map two virtual addresses P and Q whose L3 translation table pages come from A and B.
3. Spin a thread to continuously access Q, keeping its TLB entry alive.
4. Call `pmap_remove_options()` to remove mappings starting at P; due to a bug, the code mistakenly removes the TTEs for both P and Q, but only invalidates the TLB entry for P, leaving Q’s stale entry live.
5. Reuse B (page Q’s table) to map arbitrary memory (e.g. PPL-protected pages). Because the stale TLB entry still maps Q’s old mapping, that mapping remains valid for that context.
6. Through this, the attacker can put writable mapping of PPL-protected pages in place without going through PPL interface.

- This exploit required fine control of physical mapping and TLB behavior. It demonstrates that a security boundary relying on TLB / mapping correctness must be extremely careful about TLB invalidations and mapping consistency.

- Project Zero commented that bypasses like this are subtle and rare, but possible in complex systems. Still, they regard PPL as a solid mitigation.

2. **Other potential hazards & constraints**

- If a kernel exploit can directly enter PPL routines (via calling the PPL wrappers), it might bypass restrictions. Thus argument validation is critical.
- Bugs in the PPL code itself (e.g. arithmetic overflow, boundary checks) can allow out-of-bounds modifications inside PPL. Project Zero observed that such a bug in `pmap_remove_options_internal()` was exploited in their bypass.
- The PPL boundary is irrevocably tied to hardware enforcement (APRR, memory controller), so it's only as strong as the hardware implementation.

#### Example
<details>
<summary>Code Example</summary>
Here’s a simplified pseudocode / logic showing how a kernel might call into PPL to modify protected pages:
```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

The kernel can do many normal operations, but only through ppl_call_* routines can it change protected mappings or patch code.

Example A kernel exploit tries to overwrite the entitlement table, or disable code-sign enforcement by modifying a kernel signature blob. Because that page is PPL-protected, the write is blocked unless going through the PPL interface. So even with kernel code execution, you cannot bypass code-sign constraints or modify credential data arbitrarily. On iOS 17+ certain devices use SPTM to further isolate PPL-managed pages.

PPL → SPTM / Replacements / Future

  • On Apple’s modern SoCs (A15 or later, M2 or later), Apple supports SPTM (Secure Page Table Monitor), which replaces PPL for page table protections.
  • Apple calls out in 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.”
  • The SPTM architecture likely shifts more policy enforcement into a higher-privileged monitor outside kernel control, further reducing the trust boundary.

MTE | EMTE | MIE

Here’s a higher-level description of how EMTE operates under Apple’s MIE setup:

  1. Tag assignment
  • When memory is allocated (e.g. in kernel or user space via secure allocators), a secret tag is assigned to that block.
  • The pointer returned to the user or kernel includes that tag in its high bits (using TBI / top byte ignore mechanisms).
  1. Tag checking on access
  • Whenever a load or store is executed using a pointer, the hardware checks that the pointer’s tag matches the memory block’s tag (allocation tag). If mismatch, it faults immediately (since synchronous).
  • Because it’s synchronous, there is no “delayed detection” window.
  1. Retagging on free / reuse
  • When memory is freed, the allocator changes the block’s tag (so older pointers with old tags no longer match).
  • A use-after-free pointer would therefore have a stale tag and mismatch when accessed.
  1. Neighbor-tag differentiation to catch overflows
  • Adjacent allocations are given distinct tags. If a buffer overflow spills into neighbor’s memory, tag mismatch causes a fault.
  • This is especially powerful in catching small overflows that cross boundary.
  1. Tag confidentiality enforcement
  • Apple must prevent tag values being leaked (because if attacker learns the tag, they could craft pointers with correct tags).
  • They include protections (microarchitectural / speculative controls) to avoid side-channel leakage of tag bits.
  1. Kernel and user-space integration
  • Apple uses EMTE not just in user-space but also in kernel / OS-critical components (to guard kernel against memory corruption).
  • The hardware/OS ensures tag rules apply even when kernel is executing on behalf of 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>

#### Περιορισμοί & προκλήσεις

- **Intrablock overflows**: Αν το overflow μένει στην ίδια κατανομή (δεν διασχίζει boundary) και το tag παραμένει το ίδιο, το tag mismatch δεν το εντοπίζει.
- **Tag width limitation**: Διατίθενται μόνο λίγα bits (π.χ. 4 bits, ή μικρό domain) για tag — περιορισμένος χώρος ονομάτων.
- **Side-channel leaks**: Αν τα tag bits μπορούν να leak (μέσω cache / speculative execution), ο attacker μπορεί να μάθει έγκυρα tags και να παρακάμψει. Το Tag Confidentiality Enforcement της Apple στοχεύει να μετριάσει αυτό.
- **Performance overhead**: Οι έλεγχοι tag σε κάθε load/store προσθέτουν κόστος· η Apple πρέπει να βελτιστοποιήσει το hardware για να μειώσει το overhead.
- **Compatibility & fallback**: Σε παλαιότερο hardware ή τμήματα που δεν υποστηρίζουν EMTE, πρέπει να υπάρχει fallback. Η Apple αναφέρει ότι το MIE ενεργοποιείται μόνο σε συσκευές με υποστήριξη.
- **Complex allocator logic**: Ο allocator πρέπει να διαχειρίζεται tags, retagging, ευθυγράμμιση ορίων και να αποφεύγει mis-tag collisions. Σφάλματα στη λογική του allocator μπορούν να εισάγουν ευπάθειες.
- **Mixed memory / hybrid areas**: Μέρες της μνήμης μπορεί να παραμείνουν untagged (legacy), καθιστώντας την interoperability πιο περίπλοκη.
- **Speculative / transient attacks**: Όπως με πολλές μικροαρχιτεκτονικές προστασίες, speculative execution ή micro-op fusions μπορεί προσωρινά να παρακάμψουν τους ελέγχους ή να leak tag bits.
- **Limited to supported regions**: Η Apple μπορεί να εφαρμόζει EMTE μόνο σε επιλεγμένες, υψηλού κινδύνου περιοχές (kernel, security-critical subsystems), όχι καθολικά.



---

## Κύριες βελτιώσεις / διαφορές σε σχέση με το standard MTE

Εδώ είναι οι βελτιώσεις και οι αλλαγές που τονίζει η Apple:

| Χαρακτηριστικό | Original MTE | EMTE (Apple’s enhanced) / MIE |
|---|---|---|
| **Check mode** | Υποστηρίζει synchronous και asynchronous modes. Σε async, τα tag mismatches αναφέρονται αργότερα (delayed) | Η Apple επιμένει στη **synchronous mode** εξ ορισμού — τα tag mismatches εντοπίζονται αμέσως, χωρίς παράθυρα καθυστέρησης/αγώνα. |
| **Coverage of non-tagged memory** | Οι προσβάσεις σε non-tagged memory (π.χ. globals) μπορεί να παρακάμπτουν ελέγχους σε κάποιες υλοποιήσεις | Το EMTE απαιτεί ότι και οι προσβάσεις από tagged περιοχή σε non-tagged memory επαληθεύουν γνώση του tag, καθιστώντας πιο δύσκολη την παράκαμψη μέσω ανάμειξης allocations. |
| **Tag confidentiality / secrecy** | Tags μπορεί να είναι παρατηρήσιμα ή leaked μέσω side channels | Η Apple προσθέτει **Tag Confidentiality Enforcement**, που προσπαθεί να αποτρέψει την leakage των τιμών tag (μέσω speculative side-channels κ.λπ.). |
| **Allocator integration & retagging** | Το MTE αφήνει μεγάλο μέρος της λογικής του allocator στο software | Οι secure typed allocators της Apple (kalloc_type, xzone malloc, κ.λπ.) ενσωματώνονται με EMTE: όταν δεσμεύεται ή απελευθερώνεται μνήμη, τα tags διαχειρίζονται σε λεπτή κλίμακα. |
| **Always-on by default** | Σε πολλές πλατφόρμες, το MTE είναι προαιρετικό ή απενεργοποιημένο εξ ορισμού | Η Apple ενεργοποιεί το EMTE / MIE εξ ορισμού σε υποστηριζόμενο hardware (π.χ. iPhone 17 / A19) για kernel και πολλές user processes. |

Εφόσον η Apple ελέγχει τόσο το hardware όσο και το software stack, μπορεί να επιβάλει το EMTE αυστηρά, να αποφύγει επιδόσεων προβλήματα και να κλείσει side-channel τρύπες.

---

## Πώς λειτουργεί το EMTE στην πράξη (Apple / MIE)

Παρακάτω περιγραφή σε υψηλό επίπεδο για το πώς λειτουργεί το EMTE στη διαμόρφωση MIE της Apple:

1. **Tag assignment**
- Όταν δεσμεύεται μνήμη (π.χ. στο kernel ή στο user space μέσω secure allocators), σε αυτό το block ανατίθεται ένα **secret tag**.
- Ο pointer που επιστρέφεται στον χρήστη ή το kernel περιλαμβάνει αυτό το tag στα high bits (χρησιμοποιώντας TBI / top byte ignore μηχανισμούς).

2. **Tag checking on access**
- Κάθε φορά που εκτελείται ένα load ή store με pointer, το hardware ελέγχει ότι το tag του pointer ταιριάζει με το tag του memory block (allocation tag). Σε mismatch, προκαλείται fault αμέσως (επειδή είναι synchronous).
- Επειδή είναι synchronous, δεν υπάρχει "delayed detection" παράθυρο.

3. **Retagging on free / reuse**
- Όταν απελευθερώνεται μνήμη, ο allocator αλλάζει το tag του block (έτσι οι παλαιότεροι pointers με παλιά tags δεν ταιριάζουν πλέον).
- Ένας use-after-free pointer θα έχει επομένως stale tag και θα κάνει mismatch κατά την πρόσβαση.

4. **Neighbor-tag differentiation to catch overflows**
- Γειτονικές δεσμεύσεις λαμβάνουν διαφορετικά tags. Αν ένα buffer overflow διαρρεύσει στη μνήμη του γείτονα, το tag mismatch προκαλεί fault.
- Αυτό είναι ιδιαίτερα ισχυρό στο να εντοπίζει μικρά overflows που διασχίζουν όρια.

5. **Tag confidentiality enforcement**
- Η Apple πρέπει να αποτρέψει τα tag values από το να γίνουν leaked (επειδή αν ο attacker μάθει το tag, μπορεί να κατασκευάσει pointers με σωστά tags).
- Συμπεριλαμβάνονται προστασίες (microarchitectural / speculative controls) για να αποφευχθεί η side-channel leakage των tag bits.

6. **Kernel and user-space integration**
- Η Apple χρησιμοποιεί το EMTE όχι μόνο στο user-space αλλά και σε kernel / OS-critical components (για να προστατεύει το kernel από memory corruption).
- Το hardware/OS εξασφαλίζει ότι οι κανόνες tag εφαρμόζονται ακόμα και όταν το kernel εκτελείται εκ μέρους του user space.

Εφόσον το EMTE είναι ενσωματωμένο στο MIE, η Apple χρησιμοποιεί το EMTE σε synchronous mode σε κρίσιμες επιφάνειες επίθεσης, όχι ως opt-in ή debugging mode.

---

## Διαχείριση εξαιρέσεων στο XNU

Όταν προκύπτει μια **exception** (π.χ., `EXC_BAD_ACCESS`, `EXC_BAD_INSTRUCTION`, `EXC_CRASH`, `EXC_ARM_PAC`, κ.λπ.), το **Mach layer** του XNU kernel είναι υπεύθυνο να την παρεμβάλλει πριν μετατραπεί σε UNIX-style **signal** (όπως `SIGSEGV`, `SIGBUS`, `SIGILL`, ...).

Αυτή η διαδικασία περιλαμβάνει πολλαπλά επίπεδα διάδοσης και διαχείρισης εξαιρέσεων πριν φτάσει στο user space ή μετατραπεί σε BSD signal.


### Ροή εξαιρέσεων (Υψηλού Επιπέδου)

1.  **Η CPU ενεργοποιεί μια synchronous exception** (π.χ., invalid pointer dereference, PAC failure, illegal instruction, κ.λπ.).

2.  **Ο low-level trap handler** εκτελείται (`trap.c`, `exception.c` στον κώδικα του XNU).

3.  Ο trap handler καλεί τη **`exception_triage()`**, τον πυρήνα της Mach exception handling.

4.  Η `exception_triage()` αποφασίζει πώς θα δρομολογήσει την exception:

-   Πρώτα στην **thread's exception port**.

-   Έπειτα στην **task's exception port**.

-   Έπειτα στην **host's exception port** (συχνά `launchd` ή `ReportCrash`).

Αν κανένα από αυτά τα ports δεν χειριστεί την exception, το kernel μπορεί:

-   **Να την μετατρέψει σε BSD signal** (για διεργασίες user-space).

-   **Να προκαλέσει panic** (για exceptions στο kernel-space).


### Κεντρική Συνάρτηση: `exception_triage()`

Η συνάρτηση `exception_triage()` δρομολογεί τις Mach exceptions στον αλυσίδα πιθανών handlers μέχρι κάποιος να την χειριστεί ή έως ότου γίνει τελικά θανατηφόρα. Ορίζεται στο `osfmk/kern/exception.c`.
```c
void exception_triage(exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt);

Τυπική ροή κλήσεων:

exception_triage() └── exception_deliver() ├── exception_deliver_thread() ├── exception_deliver_task() └── exception_deliver_host()

Αν όλα αποτύχουν → χειρίζεται από την bsd_exception() → μεταφράζεται σε σήμα όπως το SIGSEGV.

Exception Ports

Κάθε αντικείμενο Mach (thread, task, host) μπορεί να εγγράψει exception ports, στα οποία αποστέλλονται τα μηνύματα εξαίρεσης.

Ορίζονται από το API:

task_set_exception_ports()
thread_set_exception_ports()
host_set_exception_ports()

Κάθε exception port έχει:

  • Ένα mask (ποιες exceptions θέλει να λαμβάνει)
  • Ένα port name (Mach port για λήψη μηνυμάτων)
  • Μια behavior (πώς ο kernel στέλνει το μήνυμα)
  • Μια flavor (ποιο thread state να συμπεριλάβει)

Debuggers and Exception Handling

Ένας debugger (π.χ. LLDB) ορίζει ένα exception port στο target task ή thread, συνήθως χρησιμοποιώντας task_set_exception_ports().

Όταν συμβαίνει μια εξαίρεση:

  • Το Mach μήνυμα στέλνεται στη διεργασία του debugger.
  • Ο debugger μπορεί να αποφασίσει να χειριστεί (resume, τροποποίηση registers, παράβλεψη εντολής) ή να μην χειριστεί την εξαίρεση.
  • Αν ο debugger δεν την χειριστεί, η εξαίρεση προωθείται στο επόμενο επίπεδο (task → host).

Flow of EXC_BAD_ACCESS

  1. Το thread dereferences invalid pointer → CPU προκαλεί Data Abort.

  2. Ο kernel trap handler καλεί exception_triage(EXC_BAD_ACCESS, ...).

  3. Το μήνυμα αποστέλλεται σε:

  • Thread port → (ο debugger μπορεί να παρεμβαίνει σε breakpoint).

  • Αν ο debugger αγνοήσει → Task port → (handler σε επίπεδο process).

  • Αν αγνοηθεί → Host port (συνήθως ReportCrash).

  1. Αν κανείς δεν το χειριστεί → bsd_exception() μεταφράζει σε SIGSEGV.

PAC Exceptions

Όταν το Pointer Authentication (PAC) αποτύχει (mismatch υπογραφής), εγείρεται μια ειδική Mach εξαίρεση:

  • EXC_ARM_PAC (type)
  • Οι κώδικες μπορεί να περιλαμβάνουν λεπτομέρειες (π.χ., τύπο key, τύπο pointer).

Αν το binary έχει το flag TFRO_PAC_EXC_FATAL, ο kernel θεωρεί τις αποτυχημένες PAC ως fatal, παρακάμπτοντας την παρεμβολή του debugger. Αυτό γίνεται για να αποτραπεί η χρήση debugger από attackers για παράκαμψη των ελέγχων PAC και ενεργοποιείται για platform binaries.

Software Breakpoints

Ένα software breakpoint (int3 σε x86, brk σε ARM64) υλοποιείται προκαλώντας ένα εσκεμμένο fault.
Ο debugger το πιάσει μέσω του exception port:

  • Τροποποιεί το instruction pointer ή τη μνήμη.
  • Επαναφέρει την αρχική εντολή.
  • Επαναφέρει την εκτέλεση.

Αυτός ο ίδιος μηχανισμός είναι που επιτρέπει να “πιάσεις” μια PAC εξαίρεση — εκτός αν έχει οριστεί TFRO_PAC_EXC_FATAL, οπότε δεν φτάνει ποτέ στον debugger.

Conversion to BSD Signals

Αν κανένας handler δεν αποδεχτεί την εξαίρεση:

  • Ο kernel καλεί task_exception_notify() → bsd_exception().

  • Αυτό αντιστοιχίζει Mach exceptions σε signals:

Mach ExceptionSignal
EXC_BAD_ACCESSSIGSEGV or SIGBUS
EXC_BAD_INSTRUCTIONSIGILL
EXC_ARITHMETICSIGFPE
EXC_SOFTWARESIGTRAP
EXC_BREAKPOINTSIGTRAP
EXC_CRASHSIGKILL
EXC_ARM_PACSIGILL (on non-fatal)

Key Files in XNU Source

  • osfmk/kern/exception.c → Πυρήνας του exception_triage(), exception_deliver_*().

  • bsd/kern/kern_sig.c → Λογική παράδοσης των σημάτων.

  • osfmk/arm64/trap.c → Χαμηλού επιπέδου trap handlers.

  • osfmk/mach/exc.h → Κώδικες και δομές εξαίρεσης.

  • osfmk/kern/task.c → Ρυθμίσεις exception port για task.


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

Ο kernel χρησιμοποιούσε έναν zone allocator (kalloc) χωρισμένο σε fixed-size “zones.” Κάθε zone αποθηκεύει αιτήσεις μόνο μιας συγκεκριμένης size class.

Από το screenshot:

Zone NameElement SizeExample Use
default.kalloc.1616 bytesΠολύ μικρές kernel δομές, pointers.
default.kalloc.3232 bytesΜικρές δομές, headers αντικειμένων.
default.kalloc.6464 bytesIPC messages, πολύ μικρά kernel buffers.
default.kalloc.128128 bytesΜεσαία αντικείμενα όπως μέρη του OSObject.
default.kalloc.12801280 bytesΜεγάλες δομές, IOSurface/γραφικά metadata.

Πώς λειτουργούσε:

  • Κάθε αίτηση allocation στρογγυλοποιείτο προς τα πάνω στην πλησιέστερη zone size. (π.χ., ένα request των 50 bytes πήγαινε στη kalloc.64 zone).
  • Η μνήμη σε κάθε zone κρατιόταν σε ένα freelist — chunks που απελευθερώνονταν από τον kernel επέστρεφαν σε εκείνη τη zone.
  • Αν υπερχείλιζες ένα buffer των 64 bytes, θα έγραφες πάνω στο επόμενο αντικείμενο στην ίδια zone.

Γι’ αυτό το heap spraying / feng shui ήταν τόσο αποτελεσματικό: μπορούσες να προβλέψεις τους γείτονες των αντικειμένων ψεκάζοντας allocations της ίδιας size class.

The freelist

Μέσα σε κάθε kalloc zone, τα freed objects δεν επέστρεφαν άμεσα στο σύστημα — πήγαιναν σε ένα freelist, μια συνδεδεμένη λίστα διαθέσιμων chunks.

  • Όταν ένα chunk απελευθερωνόταν, ο kernel έγραφε έναν pointer στην αρχή του chunk → τη διεύθυνση του επόμενου free chunk στην ίδια zone.

  • Η zone κρατούσε έναν δείκτη HEAD στο πρώτο free chunk.

  • Η allocation πάντα χρησιμοποιούσε το τρέχον HEAD:

  1. Pop HEAD (επιστροφή αυτής της μνήμης στον caller).

  2. Ενημέρωση HEAD = HEAD->next (αποθηκευμένο στο header του freed chunk).

  • Το freeing έσπρωχνε τα chunks πίσω:

  • freed_chunk->next = HEAD

  • HEAD = freed_chunk

Άρα το freelist ήταν απλά μια συνδεδεμένη λίστα χτισμένη μέσα στην ίδια τη μνήμη που είχε απελευθερωθεί.

Normal state:

Zone page (64-byte chunks for example):
[ A ] [ F ] [ F ] [ A ] [ F ] [ A ] [ F ]

Freelist view:
HEAD ──► [ F ] ──► [ F ] ──► [ F ] ──► [ F ] ──► NULL
(next ptrs stored at start of freed chunks)

Εκμετάλλευση του freelist

Εφόσον τα πρώτα 8 bytes ενός free chunk = freelist pointer, ένας επιτιθέμενος μπορεί να το αλλοιώσει:

  1. Heap overflow σε διπλανό freed chunk → overwrite το “next” pointer του.

  2. Use-after-free εγγραφή σε freed object → overwrite το “next” pointer του.

Τότε, στην επόμενη κατανομή αυτού του μεγέθους:

  • Ο allocator κάνει pop στο αλλοιωμένο chunk.

  • Ακολουθεί το attacker-supplied “next” pointer.

  • Επιστρέφει pointer σε αυθαίρετη μνήμη, επιτρέποντας fake object primitives ή targeted overwrite.

Οπτικό παράδειγμα freelist poisoning:

Before corruption:
HEAD ──► [ F1 ] ──► [ F2 ] ──► [ F3 ] ──► NULL

After attacker overwrite of F1->next:
HEAD ──► [ F1 ]
(next) ──► 0xDEAD_BEEF_CAFE_BABE  (attacker-chosen)

Next alloc of this zone → kernel hands out memory at attacker-controlled address.

This freelist design made exploitation highly effective pre-hardening: predictable neighbors from heap sprays, raw pointer freelist links, and no type separation allowed attackers to escalate UAF/overflow bugs into arbitrary kernel memory control.

Heap Grooming / Feng Shui

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

Steps:

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

Why it works:

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

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

Apple hardened the allocator and made heap grooming much harder:

1. From Classic kalloc to kalloc_type

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

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

2. Slabs and Per-CPU Caches

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

3. Randomization inside zones

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

4. Guarded Allocations

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

5. Page Protection Layer (PPL) and SPTM

  • Even if you control a freed object, you can’t modify all of kernel memory:
  • PPL (Page Protection Layer) enforces that certain regions (e.g., code signing data, entitlements) are read-only even to the kernel itself.
  • On A15/M2+ devices, this role is replaced/enhanced by SPTM (Secure Page Table Monitor) + TXM (Trusted Execution Monitor).
  • These hardware-enforced layers mean attackers can’t escalate from a single heap corruption to arbitrary patching of critical security structures.
  • (Added / Enhanced): also, PAC (Pointer Authentication Codes) is used in the kernel to protect pointers (especially function pointers, vtables) so that forging or corrupting them becomes harder.
  • (Added / Enhanced): zones may enforce zone_require / zone enforcement, i.e. that an object freed can only be returned through its correct typed zone; invalid cross-zone frees may panic or be rejected. (Apple alludes to this in their memory safety posts)

6. Large Allocations

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

7. Allocation Patterns Attackers Target

Even with these protections, attackers still look for:

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

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

Example: Allocation Flow in Modern Heap

Suppose userspace calls into IOKit to allocate an OSData object:

  1. Type lookupOSData maps to kalloc_type_osdata zone (size 64 bytes).
  2. Check per-CPU cache for free elements.
  • If found → return one.
  • If empty → go to global freelist.
  • If freelist empty → allocate a new slab (page of 4KB → 64 chunks of 64 bytes).
  1. Return chunk to caller.

Freelist pointer protection:

  • Each freed chunk stores the address of the next free chunk, but encoded with a secret key.
  • Overwriting that field with attacker data won’t work unless you know the key.

Comparison Table

FeatureOld Heap (Pre-iOS 15)Modern Heap (iOS 15+ / A12+)
Allocation granularityFixed size buckets (kalloc.16, kalloc.32, etc.)Size + type-based buckets (kalloc_type)
Placement predictabilityHigh (same-size objects side by side)Low (same-type grouping + randomness)
Freelist managementRaw pointers in freed chunks (easy to corrupt)Encoded pointers (safe-linking style)
Adjacent object controlEasy via sprays/frees (feng shui predictable)Hard — typed zones separate attacker objects
Kernel data/code protectionsFew hardware protectionsPPL / SPTM protect page tables & code pages, 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)

In recent Apple OS versions (especially iOS 17+), Apple introduced a more secure userland allocator, xzone malloc (XZM). This is the user-space analog to the kernel’s kalloc_type, applying type awareness, metadata isolation, and memory tagging safeguards.

Goals & Design Principles

  • Type segregation / type awareness: group allocations by type or usage (pointer vs data) to prevent type confusion and cross-type reuse.
  • Metadata isolation: separate heap metadata (e.g. free lists, size/state bits) from object payloads so that out-of-bounds writes are less likely to corrupt metadata.
  • Guard pages / redzones: insert unmapped pages or padding around allocations to catch overflows.
  • Memory tagging (EMTE / MIE): work in conjunction with hardware tagging to detect use-after-free, out-of-bounds, and invalid accesses.
  • Scalable performance: maintain low overhead, avoid excessive fragmentation, and support many allocations per second with low latency.

Architecture & Components

Below are the main elements in the xzone allocator:

Segment Groups & Zones

  • Segment groups partition the address space by usage categories: e.g. data, pointer_xzones, data_large, pointer_large.
  • Each segment group contains segments (VM ranges) that host allocations for that category.
  • Associated with each segment is a metadata slab (separate VM area) that stores metadata (e.g. free/used bits, size classes) for that segment. This out-of-line (OOL) metadata ensures that metadata is not intermingled with object payloads, mitigating corruption from overflows.
  • Segments are carved into chunks (slices) which in turn are subdivided into blocks (allocation units). A chunk is tied to a specific size class and segment group (i.e. all blocks in a chunk share the same size & category).
  • For small / medium allocations, it will use fixed-size chunks; for large/huges, it may map separately.

Chunks & Blocks

  • A chunk is a region (often several pages) dedicated to allocations of one size class within a group.
  • Inside a chunk, blocks are slots available for allocations. Freed blocks are tracked via the metadata slab — e.g. via bitmaps or free lists stored out-of-line.
  • Between chunks (or within), guard slices / guard pages may be inserted (e.g. unmapped slices) to catch out-of-bounds writes.

Type / Type ID

  • Every allocation site (or call to malloc, calloc, etc.) is associated with a type identifier (a malloc_type_id_t) which encodes what kind of object is being allocated. That type ID is passed to the allocator, which uses it to select which zone / segment to serve the allocation.
  • Because of this, even if two allocations have the same size, they may go into entirely different zones if their types differ.
  • In early iOS 17 versions, not all APIs (e.g. CFAllocator) were fully type-aware; Apple addressed some of those weaknesses in iOS 18.

Allocation & Freeing Workflow

Here is a high-level flow of how allocation and deallocation operate in xzone:

  1. malloc / calloc / realloc / typed alloc is invoked with a size and type ID.
  2. The allocator uses the type ID to pick the correct segment group / zone.
  3. Within that zone/segment, it seeks a chunk that has free blocks of the requested size.
  • It may consult local caches / per-thread pools or free block lists from metadata.
  • If no free block is available, it may allocate a new chunk in that zone.
  1. The metadata slab is updated (free bit cleared, bookkeeping).
  2. If memory tagging (EMTE) is in play, the returned block gets a tag assigned, and metadata is updated to reflect its “live” state.
  3. When free() is called:
  • The block is marked as freed in metadata (via OOL slab).
  • The block may be placed into a free list or pooled for reuse.
  • Optionally, block contents may be cleared or poisoned to reduce data leaks or use-after-free exploitation.
  • The hardware tag associated with the block may be invalidated or re-tagged.
  • If an entire chunk becomes free (all blocks freed), the allocator may reclaim that chunk (unmap it or return to OS) under memory pressure.

Security Features & Hardening

These are the defenses built into modern userland xzone:

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)

  • Apple’s MIE (Memory Integrity Enforcement) is the hardware + OS framework that brings Enhanced Memory Tagging Extension (EMTE) into always-on, synchronous mode across major attack surfaces.
  • xzone allocator is a fundamental foundation of MIE in user space: allocations done via xzone get tags, and accesses are checked by hardware.
  • In MIE, the allocator, tag assignment, metadata management, and tag confidentiality enforcement are integrated to ensure that memory errors (e.g. stale reads, OOB, UAF) are caught immediately, not exploited later.

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.

JSKit-Based Safari Chains and PREYHUNTER Stagers

Renderer RCE abstraction with JSKit

  • Reusable entry: Recent in-the-wild chains abused a WebKit JIT bug (patched as CVE-2023-41993) purely to gain JavaScript-level arbitrary read/write. The exploit immediately pivots into a purchased framework called JSKit, so any future Safari bug only needs to deliver the same primitive.
  • Version abstraction & PAC bypasses: JSKit bundles support for a wide range of iOS releases together with multiple, selectable Pointer Authentication Code bypass modules. The framework fingerprints the target build, selects the appropriate PAC bypass logic, and verifies every step (primitive validation, shellcode launch) before progressing.
  • Manual Mach-O mapping: JSKit parses Mach-O headers directly from memory, resolves the symbols it needs inside dyld-cached images, and can manually map additional Mach-O payloads without writing them to disk. This keeps the renderer process in-memory only and evades code-signature checks tied to filesystem artifacts.
  • Portfolio model: Debug strings such as “exploit number 7” show that the suppliers maintain multiple interchangeable WebKit exploits. Once the JS primitive matches JSKit’s interface, the rest of the chain is unchanged across campaigns.

Kernel bridge: IPC UAF -> code-sign bypass pattern

  • Kernel IPC UAF (CVE-2023-41992): The second stage, still running inside the Safari context, triggers a kernel use-after-free in IPC code, re-allocates the freed object from userland, and abuses the dangling pointers to pivot into arbitrary kernel read/write. The stage also reuses PAC bypass material previously computed by JSKit instead of re-deriving it.
  • Code-signing bypass (CVE-2023-41991): With kernel R/W available, the exploit patches the trust cache / code-signing structures so unsigned payloads execute as system. The stage then exposes a lightweight kernel R/W service to later payloads.
  • Composed pattern: This chain demonstrates a reusable recipe that defenders should expect going forward:
WebKit renderer RCE -> kernel IPC UAF -> kernel arbitrary R/W -> code-sign bypass -> unsigned system stager

PREYHUNTER helper & watcher modules

  • Watcher anti-analysis: Μια αφιερωμένη δυαδική εφαρμογή watcher προφίλάρει συνεχώς τη συσκευή και διακόπτει το kill-chain όταν ανιχνεύεται περιβάλλον έρευνας. Ελέγχει security.mac.amfi.developer_mode_status, την παρουσία μιας diagnosticd κονσόλας, τις τοπικές ρυθμίσεις US ή IL, ίχνη jailbreak όπως Cydia, διεργασίες όπως bash, tcpdump, frida, sshd, ή checkrain, mobile AV apps (McAfee, AvastMobileSecurity, NortonMobileSecurity), προσαρμοσμένες ρυθμίσεις HTTP proxy και προσαρμοσμένα root CAs. Η αποτυχία οποιουδήποτε ελέγχου εμποδίζει την περαιτέρω παράδοση του payload.
  • Helper surveillance hooks: Το βοηθητικό συστατικό επικοινωνεί με άλλα στάδια μέσω /tmp/helper.sock, και στη συνέχεια φορτώνει σετ hooks ονομαζόμενα DMHooker και UMHooker. Αυτά τα hooks προσπελαύνουν τις VOIP audio διαδρομές (οι εγγραφές αποθηκεύονται κάτω από /private/var/tmp/l/voip_%lu_%u_PART.m4a), υλοποιούν system-wide keylogger, τραβούν φωτογραφίες χωρίς διεπαφή χρήστη, και κάνουν hook το SpringBoard για να καταστείλουν ειδοποιήσεις που κανονικά θα προκαλούσαν αυτές οι ενέργειες. Επομένως, το helper λειτουργεί ως κρυφή στρώση επικύρωσης και ελαφριάς επιτήρησης πριν εγκατασταθούν πιο βαριά implants όπως Predator.

WebKit DFG Store-Barrier UAF + ANGLE PBO OOB (iOS 26.1)

Webkit Dfg Store Barrier Uaf Angle Oob

iMessage/Media Parser Zero-Click Chains

Imessage Media Parser Zero Click Coreaudio Pac Bypass

Αναφορές

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks