iOS Εκμετάλλευση

Reading time: 58 minutes

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

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

  • Αμπαλάρει: το “classic payload drop + execute” στάδιο σε exploit chains; 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 capabilities απαιτούν συγκεκριμένα entitlements που δεν μπορούν να πλαστογραφηθούν.
Παράδειγμα Υποθέστε ότι ένα exploit αποκτά code execution σε μια διαδικασία και προσπαθεί να γράψει shellcode στο heap και να κάνει jump σ' αυτό. Στο iOS, αυτή η σελίδα θα πρέπει να σηματοδοτηθεί ως executable **και** να ικανοποιεί code-signature constraints. Εφόσον το shellcode δεν είναι υπογεγραμμένο με το πιστοποιητικό της Apple, το jump αποτυγχάνει ή το σύστημα απορρίπτει το να γίνει αυτή η μνήμη executable.

2. CoreTrust

Introduced around iOS 14+ era (or gradually in newer devices / later iOS) CoreTrust είναι το υποσύστημα που εκτελεί έλεγχο runtime signature των binaries (συμπεριλαμβανομένων system και user binaries) ενάντια στη root certificate της Apple αντί να εμπιστεύεται cached userland trust stores.

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

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

Introduced in many OSes earlier; iOS had NX-bit / w^x for a long time Το DEP επιβάλλει ότι σελίδες σημειωμένες ως writable (για δεδομένα) είναι μη εκτελέσιμες, και σελίδες σημειωμένες ως executable είναι μη εγγράψιμες. Δεν μπορείς απλά να γράψεις shellcode σε heap ή stack και να το εκτελέσεις.

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

4. Address Space Layout Randomization (ASLR)

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

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

5. Kernel Address Space Layout Randomization (KASLR)

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

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

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) παρακολουθεί συνεχώς την ακεραιότητα των kernel text pages (μέσω hash ή checksum). Αν ανιχνεύσει tampering (patches, inline hooks, code modifications) εκτός επιτρεπτών παραθύρων, προκαλεί kernel panic ή reboot.

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

7. Kernel Text Read‐Only Region (KTRR)

Introduced in modern SoCs (post ~A12 / newer 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 μονάδα) σηματοδοτεί τις physical pages που περιέχουν το kernel text ως read-only.
  • Ακόμα και αν ένα exploit αποκτήσει πλήρη kernel privileges, δεν μπορεί να γράψει σε αυτές τις σελίδες για να τροποποιήσει instructions.
  • Για να τις τροποποιήσει, ο επιτιθέμενος πρέπει πρώτα να compromize-άρει το boot chain ή να υπονομεύσει το ίδιο το KTRR.
Παράδειγμα Ένα privilege-escalation exploit μπαίνει στο EL1 και γράφει ένα trampoline μέσα σε μια kernel function (π.χ. στον `syscall` handler). Αλλά επειδή οι σελίδες είναι κλειδωμένες ως read-only από το KTRR, η εγγραφή αποτυγχάνει (ή προκαλεί fault), οπότε τα patches δεν εφαρμόζονται.

8. Pointer Authentication Codes (PAC)

Introduced with ARMv8.3 (hardware), Apple beginning with A12 / iOS ~12+

  • Το PAC είναι ένα hardware feature εισαγόμενο στο ARMv8.3-A για την ανίχνευση τροποποίησης των pointer values (return addresses, function pointers, certain data pointers) με την ενσωμάτωση μιας μικρής κρυπτογραφικής υπογραφής (ένα “MAC”) στα αχρησιμοποίητα high bits του pointer.
  • Η υπογραφή (“PAC”) υπολογίζεται πάνω στην pointer value συν έναν modifier (μια context τιμή, π.χ. stack pointer ή κάποιο διακριτικό δεδομένο). Έτσι η ίδια pointer value σε διαφορετικά contexts παράγει διαφορετικό PAC.
  • Στο χρόνο χρήσης, πριν dereference ή branch μέσω αυτού του pointer, μια authenticate οδηγία ελέγχει το PAC. Αν είναι έγκυρο, ο PAC απορρίπτεται και προκύπτει ο καθαρός pointer; αν είναι άκυρο, ο pointer “τοξινώνεται” (ή σηκώνεται fault).
  • Τα keys που χρησιμοποιούνται για παραγωγή/επικύρωση 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 — δεύτερο instruction pointer key (domain “I”, key B)

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

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

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

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

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

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

Instruction Families

Η σύμβαση ονοματοδοσίας είναι: PAC / AUT / XPAC, μετά τα domain letters.

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

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

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

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

Modifiers

Ο κύριος σκοπός του modifier είναι να δέσει τον PAC με ένα συγκεκριμένο context ώστε η ίδια διεύθυνση που έχει υπογραφεί σε διαφορετικά contexts να έχει διαφορετικό PAC. Είναι σαν να προσθέτεις ένα 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 validation αποτυγχάνει.
  • Η ίδια pointer value υπογεγραμμένη με διαφορετικούς modifiers παράγει διαφορετικούς PACs.
  • Ο modifier δεν χρειάζεται να είναι μυστικός, αλλά ιδανικά δεν είναι attacker-controlled.
  • Για οδηγίες που υπογράφουν ή επαληθεύουν pointers όπου δεν υπάρχει ουσιαστικός modifier, μερικές μορφές χρησιμοποιούν μηδενικό ή κάποιο implicit constant.

Apple / iOS / XNU Customizations & Observations

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

PAC Bypasses

  1. Kernel-mode PAC: theoretical vs real bypasses
  • Επειδή τα kernel PAC keys και η λογική είναι αυστηρά ελεγχόμενα (privileged registers, diversifiers, domain isolation), το να φτιάξεις ψευδείς signed kernel pointers είναι πολύ δύσκολο.
  • Το άρθρο του Azad 2020 "iOS Kernel PAC, One Year Later" αναφέρει ότι σε iOS 12-13 βρέθηκαν μερικά partial bypasses (signing gadgets, reuse of signed states, unprotected indirect branches) αλλά όχι πλήρης generic bypass. bazad.github.io
  • Οι Apple “Dark Magic” customizations στενεύουν ακόμα περισσότερο τις επιφάνειες εκμετάλλευσης (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· δεν είναι γενικής χρήσης bypass.

Έτσι το 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 signed pointers που υπάρχουν στη shared cache και να τους χρησιμοποιήσουν σε gadgets.

  • Το talk "No Clicks Required" περιγράφει την κατασκευή ενός oracle πάνω στη shared cache για την εξαγωγή relative διευθύνσεων και τη συνδυασμό τους με 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 zero) και να τον χρησιμοποιήσει. Εφόσον dlsym επιστρέφει νόμιμα signed pointer, η χρήση του παρακάμπτει την ανάγκη να forge-άρεις PAC.

  • Το blog της Epsilon εξηγεί πώς κάποια bypasses εκμεταλλεύονται αυτό: κλήση dlsym("someSym") επιστρέφει signed 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). Αλλά παλιότερες εκδόσεις είχαν ασθενέστερα guards. 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 χρησιμοποιούνται για να μεταφέρουν calls ελέγχου χωρίς προφανή forging pointers.

  • Σε παλαιότερο iOS (πριν PAC), ένα exploit χρησιμοποίησε fake NSInvocation objects για να καλέσει arbitrary selectors σε controlled memory. Με 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 στο stack. Ο επιτιθέμενος γράφει την target gadget address αλλά δεν μπορεί να υπολογίσει το σωστό PAC. Όταν η συνάρτηση επιστρέφει, η εντολή `AUTIA` της CPU προκαλεί σφάλμα λόγω ασυμφωνίας PAC. Η αλυσίδα αποτυγχάνει.
Η ανάλυση του Project Zero για το A12 (iPhone XS) έδειξε πώς χρησιμοποιείται το Apple PAC και μεθόδους πλαστογράφησης PACs αν ένας attacker διαθέτει memory read/write primitive.
</details>


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

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

| BTI Variant | What it permits (which branch types) | Typical placement / use case |
|-------------|----------------------------------------|-------------------------------|
| **BTI C** | Targets of *call*-style indirect branches (e.g. `BLR`, or `BR` using X16/X17) | Put at entry of functions that may be called indirectly |
| **BTI J** | Targets of *jump*-style branches (e.g. `BR` used for tail calls) | Placed at the beginning of blocks reachable by jump tables or tail-calls |
| **BTI JC** | Acts as both C and J | Can be targeted by either call or jump branches |

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

#### Mechanism and enforcement

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

#### BTI + PAC synergy

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

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

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

#### Example


<details>
<summary>Example</summary>
Ένα exploit προσπαθεί να κάνει pivot σε gadget στο `0xABCDEF` που δεν ξεκινά με `BTI c`. Η CPU, κατά την εκτέλεση `blr x0`, ελέγχει τον στόχο και προκαλεί σφάλμα επειδή η αρχή της εντολής δεν περιλαμβάνει έγκυρο landing pad. Επομένως πολλά gadgets γίνονται μη χρησιμοποιήσιμα εκτός αν έχουν το BTI prefix.
</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, μειώνοντας τους κινδύνους από `ret2usr` style exploits ή κακή χρήση buffer που ελέγχεται από χρήστη.
- Όταν το PAN είναι ενεργοποιημένο (PSTATE.PAN = 1), οποιαδήποτε privileged load/store εντολή που προσπελαύνει μια virtual address που είναι “accessible at EL0” προκαλεί **permission fault**.
- Ο kernel, όταν πρέπει νόμιμα να προσπελάσει user-space memory (π.χ. copy data to/from user buffers), πρέπει **προσωρινά να απενεργοποιήσει το PAN** (ή να χρησιμοποιήσει “unprivileged load/store” εντολές) για να επιτρέψει την πρόσβαση αυτή.
- Σε Linux σε ARM64, η υποστήριξη PAN εισήχθη περίπου το 2015: patches στον kernel πρόσθεσαν ανίχνευση του feature και αντικατέστησαν `get_user` / `put_user` κ.λπ. με παραλλαγές που καθαρίζουν το PAN γύρω από προσβάσεις σε user memory.

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

#### PXN (Privileged eXecute Never)

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

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

#### Memory-permission model & how PAN and PXN map to page table bits

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

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

#### Kernel usage of PAN / PXN in a hardened OS (e.g. iOS / XNU)

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

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

Εξαιτίας του PAN bypass μέσω execute-only pages, σε ένα πραγματικό σύστημα η Apple μπορεί να απαγορεύει execute-only user pages ή να εφαρμόζει patches γύρω από την ασθενή πλευρά της προδιαγραφής.

#### Attack surfaces, bypasses, and mitigations

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

#### Example

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

Εάν ο kernel **δεν** είχε ορίσει PXN σε εκείνη τη σελίδα χρήστη, τότε το branch μπορεί να πετύχει — κάτι που θα ήταν ανασφαλές.

Εάν ο kernel ξεχάσει να ενεργοποιήσει ξανά το PAN μετά από πρόσβαση στη μνήμη χρήστη, ανοίγει ένα παράθυρο όπου περαιτέρω λογική του kernel μπορεί κατά λάθος να διαβάσει/γράψει αυθαίρετη μνήμη χρήστη.

Εάν ο δείκτης χρήστη δείχνει σε σελίδα μόνο-εκτέλεσης (σελίδα χρήστη με μόνο δικαίωμα εκτέλεσης, χωρίς ανάγνωση/εγγραφή), υπό το σφάλμα του spec PAN, `ldr W2, [X1]` μπορεί να **μην** προκαλέσει fault ακόμα και με το PAN ενεργοποιημένο, επιτρέποντας παράκαμψη exploit, ανάλογα με την υλοποίηση.

</details>

<details>
<summary>Παράδειγμα</summary>
Μια ευπάθεια στον kernel προσπαθεί να πάρει έναν pointer συνάρτησης που παρέχει ο χρήστης και να τον καλέσει στο πλαίσιο του kernel (π.χ. `call user_buffer`). Υπό PAN/PXN, αυτή η ενέργεια απαγορεύεται ή προκαλεί fault.
</details>

---

### 11. **Παράβλεψη Ανώτερου Byte (TBI) / Pointer Tagging**
**Εισήχθη στο ARMv8.5 / νεότερο (ή προαιρετική επέκταση)**
TBI σημαίνει ότι το ανώτερο byte (το πιο σημαντικό byte) ενός 64-bit pointer παραβλέπεται στην μετάφραση διευθύνσεων. Αυτό επιτρέπει στο OS ή στο hardware να ενσωματώσει bits tag στο ανώτατο byte του pointer χωρίς να επηρεάζει την πραγματική διεύθυνση.

- TBI είναι συντομογραφία για Top Byte Ignore (μερικές φορές αποκαλείται Address Tagging). Είναι μια λειτουργία hardware (διαθέσιμη σε πολλές υλοποιήσεις ARMv8+) που **παραβλέπει τα ανώτερα 8 bits** (bits 63:56) ενός 64-bit pointer όταν εκτελείται **μετάφραση διευθύνσεων / load/store / ανάκτηση εντολών**.
- Στην πράξη, η CPU αντιμετωπίζει έναν δείκτη `0xTTxxxx_xxxx_xxxx` (όπου `TT` = το ανώτερο byte) ως `0x00xxxx_xxxx_xxxx` για τις ανάγκες μετάφρασης διευθύνσεων, αγνοώντας (masking off) το ανώτερο byte. Το ανώτερο byte μπορεί να χρησιμοποιηθεί από το software για αποθήκευση **metadata / tag bits**.
- Αυτό δίνει στο software "δωρεάν" in-band χώρο για να ενσωματώσει ένα byte tag σε κάθε δείκτη χωρίς να αλλάζει τη μνήμη στην οποία αναφέρεται.
- Η αρχιτεκτονική διασφαλίζει ότι οι φορτώσεις, αποθηκεύσεις και οι ανάκτες εντολών χειρίζονται τον δείκτη με το ανώτερο byte masked (δηλαδή χωρίς το tag) πριν εκτελέσουν την πραγματική πρόσβαση μνήμης.

Έτσι το TBI αποσυνδέει τον **λογικό δείκτη** (δείκτης + tag) από τη **φυσική διεύθυνση** που χρησιμοποιείται για τις λειτουργίες μνήμης.

#### Γιατί TBI: Χρήσεις και κίνητρα

- **Pointer tagging / metadata**: Μπορείτε να αποθηκεύσετε επιπλέον metadata (π.χ. τύπο αντικειμένου, version, bounds, integrity tags) σε εκείνο το ανώτερο byte. Όταν αργότερα χρησιμοποιήσετε τον δείκτη, το tag αγνοείται σε επίπεδο hardware, οπότε δεν χρειάζεται χειροκίνητο strip για την πρόσβαση μνήμης.
- **Memory tagging / MTE (Memory Tagging Extension)**: Το TBI είναι ο βασικός μηχανισμός hardware πάνω στον οποίο χτίζει το MTE. Στο ARMv8.5, το Memory Tagging Extension χρησιμοποιεί τα bits 59:56 του δείκτη ως **λογικό tag** και τα ελέγχει σε σχέση με ένα **allocation tag** που αποθηκεύεται στη μνήμη.
- **Ενισχυμένη ασφάλεια & ακεραιότητα**: Συνδυάζοντας το TBI με pointer authentication (PAC) ή runtime checks, μπορείτε να επιβάλετε όχι μόνο τη σωστή τιμή του δείκτη αλλά και το σωστό tag. Ένας εισβολέας που υπεργράφει έναν δείκτη χωρίς το σωστό tag θα προκαλέσει ασυμφωνία tag.
- **Συμβατότητα**: Επειδή το TBI είναι προαιρετικό και τα tag bits αγνοούνται από το hardware, ο υπάρχων μη-επισημασμένος κώδικας συνεχίζει να λειτουργεί κανονικά. Τα tag bits ουσιαστικά γίνονται "don't care" bits για legacy κώδικα.

#### Παράδειγμα
<details>
<summary>Παράδειγμα</summary>
Ένας pointer συνάρτησης περιείχε ένα tag στο ανώτερο byte του (π.χ. `0xAA`). Ένα exploit υπεργράφει τα χαμηλά bits του δείκτη αλλά παραλείπει το tag, οπότε όταν ο kernel επαληθεύει ή καθαρίζει, ο δείκτης απορρίπτεται ή αποτυγχάνει.
</details>

---

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

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

Ακολουθεί πώς πιστεύεται ότι λειτουργεί το PPL, βάσει δημόσιας ανάλυσης:

#### Χρήση του APRR / δρομολόγηση δικαιωμάτων (APRR = Access Permission ReRouting)

- Το Apple hardware χρησιμοποιεί έναν μηχανισμό που ονομάζεται **APRR (Access Permission ReRouting)**, ο οποίος επιτρέπει οι εγγραφές page table (PTEs) να περιέχουν μικρούς δείκτες (indices) αντί για πλήρη bits δικαιωμάτων. Αυτοί οι δείκτες αντιστοιχίζονται μέσω καταχωρητών APRR στα πραγματικά δικαιώματα. Αυτό επιτρέπει δυναμική επαναχάρτιση δικαιωμάτων ανά domain.
- Το PPL αξιοποιεί το APRR για να διαχωρίσει τα προνόμια εντός του kernel context: μόνο το domain PPL επιτρέπεται να ενημερώνει την αντιστοίχιση μεταξύ δεικτών και των αποτελεσματικών δικαιωμάτων. Δηλαδή, όταν μη-PPL kernel κώδικας γράφει ένα PTE ή προσπαθεί να αλλάξει bits δικαιωμάτων, η λογική APRR το αποτρέπει (ή εφαρμόζει read-only αντιστοίχιση).
- Ο κώδικας PPL ο ίδιος τρέχει σε μια περιορισμένη περιοχή (π.χ. `__PPLTEXT`) η οποία κανονικά δεν είναι εκτελέσιμη ή εγγράψιμη μέχρι οι πύλες εισόδου να την επιτρέψουν προσωρινά. Ο kernel καλεί σημεία εισόδου PPL ("PPL routines") για να εκτελέσει ευαίσθητες λειτουργίες.

#### Πύλη / Είσοδος & Έξοδος

- Όταν ο kernel χρειάζεται να τροποποιήσει μια προστατευόμενη σελίδα (π.χ. να αλλάξει δικαιώματα μιας σελίδας κώδικα kernel, ή να τροποποιήσει page tables), καλεί μια **περίβλεπτη ρουτίνα PPL**, η οποία εκτελεί έλεγχους και μετά μεταβαίνει στο domain PPL. Εκτός αυτού του domain, οι προστατευόμενες σελίδες είναι ουσιαστικά read-only ή μη-τροποποιήσιμες από τον κύριο kernel.
- Κατά την είσοδο στο PPL, οι αντιστοιχίσεις APRR προσαρμόζονται έτσι ώστε οι περιοχές μνήμης στο PPL να είναι **εκτελέσιμες & εγγράψιμες** εντός PPL. Μετά την έξοδο, επαναφέρονται σε read-only / μη εγγράψιμες. Αυτό διασφαλίζει ότι μόνο καλά ελεγχόμενες ρουτίνες PPL μπορούν να γράψουν σε προστατευόμενες σελίδες.
- Εκτός PPL, οι προσπάθειες από kernel κώδικα να γράψουν σε αυτές τις προστατευόμενες σελίδες θα προκαλέσουν fault (permission denied) επειδή η αντιστοίχιση APRR για εκείνο το domain δεν επιτρέπει εγγραφή.

#### Κατηγορίες προστατευμένων σελίδων

Οι σελίδες που το PPL συνήθως προστατεύει περιλαμβάνουν:

- Δομές των page tables (translation table entries, mapping metadata)
- Kernel code pages, ειδικά αυτές που περιέχουν κρίσιμη λογική
- Code-sign metadata (trust caches, signature blobs)
- Πίνακες entitlements, πίνακες επιβολής signatures
- Άλλες δομές υψηλής αξίας του kernel όπου ένα patch θα επέτρεπε παράκαμψη ελέγχων υπογραφής ή χειρισμό διαπιστευτηρίων

Η ιδέα είναι ότι ακόμη κι αν η μνήμη του kernel ελέγχεται πλήρως, ο εισβολέας δεν μπορεί απλά να πατσάρει ή να ξαναγράψει αυτές τις σελίδες, εκτός αν επίσης συμβιβάσει ρουτίνες PPL ή παρακάμψει το PPL.

#### Γνωστές Παράκαμψεις & Ευπάθειες

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

- Μια δημόσια ανάλυση από το Project Zero περιγράφει μια παράκαμψη που περιλαμβάνει **stale TLB entries**.
- Η ιδέα:

1. Allocate δύο φυσικές σελίδες A και B, επισημάνετέ τες ως PPL σελίδες (ώστε να προστατεύονται).
2. Map δύο virtual διευθύνσεις P και Q των οποίων οι L3 translation table pages προέρχονται από A και B.
3. Εκκινήστε ένα thread που συνεχώς προσπελάζει το Q, κρατώντας το TLB entry του ζωντανό.
4. Κάλεσε `pmap_remove_options()` για να αφαιρέσεις mappings που ξεκινούν από το P· λόγω ενός bug, ο κώδικας καταλάθος αφαιρεί τα TTEs για τόσο το P όσο και το Q, αλλά μόνο το TLB entry για το P γίνεται invalidated, αφήνοντας το stale entry του Q ζωντανό.
5. Επανεκμεταλλεύσου το B (το page του table του Q) για να map-άρεις αυθαίρετη μνήμη (π.χ. PPL-protected pages). Επειδή το stale TLB entry εξακολουθεί να χαρτογραφεί το παλιό mapping του Q, εκείνο το mapping παραμένει έγκυρο για εκείνο το context.
6. Μέσω αυτού, ο επιτιθέμενος μπορεί να θέσει εγγράψιμες αντιστοιχίσεις των PPL-protected pages χωρίς να περάσει από το interface του PPL.

- Αυτό το exploit απαιτούσε λεπτό έλεγχο των φυσικών αντιστοιχήσεων και της συμπεριφοράς του TLB. Δείχνει ότι ένα όριο ασφαλείας που βασίζεται σε σωστή συμπεριφορά TLB / mapping πρέπει να είναι εξαιρετικά προσεκτικό ως προς τις invalidations και τη συνοχή των mappings.

- Το Project Zero σχολίασε ότι παρακάμψεις σαν αυτή είναι λεπτές και σπάνιες, αλλά δυνατές σε σύνθετα συστήματα. Παρ' όλα αυτά, θεωρούν το PPL ως μια σοβαρή μείωση κινδύνου.

2. **Άλλοι πιθανοί κίνδυνοι & περιορισμοί**

- Εάν ένα kernel exploit μπορεί άμεσα να εισέλθει σε ρουτίνες PPL (μέσω κλήσης των PPL wrappers), μπορεί να παρακάμψει περιορισμούς. Επομένως ο έλεγχος των ορισμάτων είναι κρίσιμος.
- Σφάλματα στον ίδιο τον κώδικα PPL (π.χ. overflow αρίθμησης, έλεγχοι ορίων) μπορούν να επιτρέψουν out-of-bounds τροποποιήσεις μέσα στο PPL. Το Project Zero παρατήρησε ότι ένα τέτοιο bug στο `pmap_remove_options_internal()` εκμεταλλεύθηκε στην παράκαμψή τους.
- Το όριο PPL είναι άρρηκτα συνδεδεμένο με την επιβολή hardware (APRR, memory controller), οπότε είναι τόσο ισχυρό όσο και η υλοποίηση του hardware.

#### Παράδειγμα
<details>
<summary>Code Example</summary>
Εδώ ένα απλοποιημένο pseudocode / λογική που δείχνει πώς ο kernel μπορεί να καλέσει το PPL για να τροποποιήσει προστατευόμενες σελίδες:
</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
Παράδειγμα Το kernel μπορεί να εκτελεί πολλές κανονικές λειτουργίες, αλλά μόνο μέσω των ρουτινών `ppl_call_*` μπορεί να αλλάξει προστατευμένες αντιστοιχίσεις σελίδων ή να τροποποιήσει κώδικα.
Παράδειγμα Ένα kernel exploit προσπαθεί να αντικαταστήσει τον entitlement table, ή να απενεργοποιήσει την code-sign enforcement τροποποιώντας ένα kernel signature blob. Εφόσον αυτή η σελίδα προστατεύεται από PPL, η εγγραφή μπλοκάρεται εκτός αν γίνει μέσω του PPL interface. Έτσι, ακόμα και με εκτέλεση κώδικα στον kernel, δεν μπορείτε να παρακάμψετε τους περιορισμούς code-sign ή να τροποποιήσετε αυθαίρετα δεδομένα credentials. Σε iOS 17+ κάποια devices χρησιμοποιούν SPTM για περαιτέρω απομόνωση των σελίδων που διαχειρίζεται το PPL.

PPL → SPTM / Αντικαταστάσεις / Μέλλον

  • 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

Εδώ είναι μια σε υψηλότερο επίπεδο περιγραφή του πώς λειτουργεί το EMTE υπό την MIE ρύθμιση της Apple:

  1. Tag assignment
  • Όταν μνήμη δεσμεύεται (π.χ. στον kernel ή στον user space μέσω secure allocators), σε εκείνο το block ανατίθεται ένα secret tag.
  • Ο pointer που επιστρέφεται στον χρήστη ή στον kernel περιλαμβάνει αυτό το tag στα υψηλά του bits (χρησιμοποιώντας TBI / top byte ignore mechanisms).
  1. Tag checking on access
  • Όποτε εκτελείται ένα load ή store χρησιμοποιώντας έναν pointer, το hardware ελέγχει ότι το tag του pointer ταιριάζει με το tag του memory block (allocation tag). Σε περίπτωση mismatch, γίνεται άμεσο fault (επειδή είναι synchronous).
  • Επειδή είναι synchronous, δεν υπάρχει “delayed detection” παράθυρο.
  1. Retagging on free / reuse
  • Όταν η μνήμη απελευθερώνεται, ο allocator αλλάζει το tag του block (ώστε παλαιότεροι pointers με παλιά tags να μην ταιριάζουν πλέον).
  • Ένας use-after-free pointer θα έχει έτσι stale tag και θα προκαλεί mismatch όταν επιχειρήσει πρόσβαση.
  1. Neighbor-tag differentiation to catch overflows
  • Γειτονικές allocations λαμβάνουν διακριτά tags. Αν ένα buffer overflow spills into neighbor’s memory, το tag mismatch προκαλεί fault.
  • Αυτό είναι ιδιαίτερα αποτελεσματικό στην ανίχνευση μικρών overflows που διασχίζουν όρια.
  1. Tag confidentiality enforcement
  • Η Apple πρέπει να αποτρέψει τα tag values από being leaked (επειδή αν ο attacker μάθει το tag, θα μπορούσε να κατασκευάσει pointers με σωστά tags).
  • Περιλαμβάνονται protections (microarchitectural / speculative controls) για να αποφευχθεί side-channel leakage των tag bits.
  1. Kernel and user-space integration
  • Η Apple χρησιμοποιεί το EMTE όχι μόνο στον user-space αλλά και σε kernel / OS-critical components (για την προστασία του kernel από memory corruption).
  • Το hardware/OS διασφαλίζει ότι οι κανόνες των tags εφαρμόζονται ακόμα και όταν ο kernel εκτελείται εκ μέρους του user space.
Παράδειγμα ``` 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 παραμένει στην ίδια κατανομή (δεν διασχίζει όριο) και το tag μένει ίδιο, το tag mismatch δεν το ανιχνεύει.
- **Tag width limitation**: Διατίθενται μόνο λίγα bits για tag (π.χ. 4 bits, ή μικρό domain) — περιορισμένος namespace.
- **Side-channel leaks**: Εάν τα tag bits μπορούν να διαρρεύσουν (via cache / speculative execution), ο attacker μπορεί να μάθει έγκυρα tags και να παρακάμψει. Η επιβολή του Apple για tag confidentiality αποσκοπεί στη μείωση αυτού.
- **Performance overhead**: Οι έλεγχοι tag σε κάθε load/store προσθέτουν κόστος· η Apple πρέπει να βελτιστοποιήσει το hardware για να κρατήσει το overhead χαμηλό.
- **Compatibility & fallback**: Σε παλαιότερο hardware ή κομμάτια που δεν υποστηρίζουν EMTE, πρέπει να υπάρχει fallback. Η Apple ισχυρίζεται ότι το MIE ενεργοποιείται μόνο σε συσκευές με υποστήριξη.
- **Complex allocator logic**: Ο allocator πρέπει να διαχειρίζεται tags, retagging, alignment των ορίων, και να αποφεύγει collisions από mis-tag. Bugs στη λογική του allocator μπορούν να εισάγουν ευπάθειες.
- **Mixed memory / hybrid areas**: Μνήμη μπορεί να παραμείνει untagged (legacy), κάνοντας την αλληλεπίδραση πιο περίπλοκη.
- **Speculative / transient attacks**: Όπως με πολλές microarchitectural προστασίες, speculative execution ή micro-op fusions μπορούν να παρακάμψουν ελέγχους transiently ή να leak tag bits.
- **Limited to supported regions**: Η Apple μπορεί να εφαρμόσει EMTE μόνο σε επιλεγμένες, υψηλού κινδύνου περιοχές (kernel, security-critical subsystems), όχι ολικά.



---

## Key enhancements / differences compared to standard MTE

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

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

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

---

## How EMTE works in practice (Apple / MIE)

Εδώ είναι μια υψηλού επιπέδου περιγραφή του πώς λειτουργεί το EMTE στο πλαίσιο του Apple / MIE:

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

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

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

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

5. **Tag confidentiality enforcement**
- Η Apple πρέπει να αποτρέψει το να διαρρεύσουν οι τιμές tag (επειδή αν ο 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.



---

## Exception handling in XNU

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

Αυτή η διαδικασία περιλαμβάνει πολλαπλά στρώματα propagation και handling πριν φτάσει στο user space ή μετατραπεί σε BSD signal.


### Exception Flow (High-Level)

1.  **CPU triggers a synchronous exception** (π.χ. invalid pointer dereference, PAC failure, illegal instruction, κ.λπ.).

2.  **Low-level trap handler** τρέχει (`trap.c`, `exception.c` στο XNU source).

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

-   **Να κάνει panic** (για kernel-space exceptions).


### Core Function: `exception_triage()`

Η συνάρτηση `exception_triage()` δρομολογεί τις Mach exceptions κατά μήκος της αλυσίδας πιθανών handlers μέχρι κάποιος να τη χειριστεί ή μέχρι να καταστεί τελικά μοιραία. Ορίζεται σε `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);

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

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

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

Θύρες εξαίρεσης

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

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

task_set_exception_ports()
thread_set_exception_ports()
host_set_exception_ports()

Κάθε exception port έχει:

  • Ένα mask (ποιες εξαιρέσεις θέλει να λαμβάνει)
  • Ένα 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, παράκαμψη instrukcji) ή να μην χειριστεί την εξαίρεση.
  • Εάν ο debugger δεν τη χειριστεί, η εξαίρεση προωθείται στο επόμενο επίπεδο (task → host).

Flow of EXC_BAD_ACCESS

  1. Το thread κάνει dereference σε μη έγκυρη 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 (τύπος)
  • Οι κώδικες μπορεί να περιλαμβάνουν λεπτομέρειες (π.χ., τύπος key, τύπος pointer).

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

Software Breakpoints

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

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

Αυτός ο ίδιος μηχανισμός είναι που σου επιτρέπει να "πιάσεις" μια 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 → Core of exception_triage(), exception_deliver_*().

  • bsd/kern/kern_sig.c → Signal delivery logic.

  • osfmk/arm64/trap.c → Low-level trap handlers.

  • osfmk/mach/exc.h → Exception codes and structures.

  • osfmk/kern/task.c → Task exception port setup.


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

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

Από το screenshot:

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

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

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

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

The freelist

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

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

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

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

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

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

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

  • freed_chunk->next = HEAD

  • HEAD = freed_chunk

Έτσι η freelist ήταν απλώς μια συνδεδεμένη λίστα χτισμένη μέσα στην ίδια την ελευθερωμένη μνήμη.

Κανονική κατάσταση:

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 its “next” pointer.

  2. Use-after-free write σε ένα freed object → overwrite its “next” pointer.

Στη συνέχεια, στην επόμενη allocation αυτού του μεγέθους:

  • Ο allocator αφαιρεί (pops) το corrupted chunk.
  • Ακολουθεί τον attacker-supplied “next” pointer.
  • Επιστρέφει έναν pointer σε arbitrary memory, επιτρέποντας 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.

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