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
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
iOS Exploit Mitigations
1. Code Signing / Runtime Signature Verification
Introduced early (iPhone OS → iOS) Αυτή είναι μια από τις βασικές προστασίες: όλος ο εκτελέσιμος κώδικας (apps, dynamic libraries, JIT-ed code, extensions, frameworks, caches) πρέπει να είναι κρυπτογραφικά υπογεγραμμένος με μια αλυσίδα πιστοποιητικών που ρίζωσε στην εμπιστοσύνη της Apple. Στο runtime, πριν φορτωθεί ένα 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 που δεν μπορούν να πλαστογραφηθούν.
Example
Αν ένα 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 είναι το subsystem που πραγματοποιεί runtime signature validation των binaries (συμπεριλαμβανομένων system και user binaries) ενάντια στο Apple’s root certificate αντί να βασίζεται σε τοπικά cached userland trust stores.
- Αποτρέπει: μετα-εγκαταστατική παραβίαση (post-install tampering) των binaries, jailbreak τεχνικές που προσπαθούν να swap/patch system libraries ή user apps; το να ξεγελαστεί το σύστημα με την αντικατάσταση trusted binaries με κακόβουλα αντίγραφα.
- Λεπτομέρειες μηχανισμού:
- Αντί να εμπιστεύεται μια τοπική trust database ή certificate cache, το CoreTrust αναφέρεται άμεσα ή ελέγχει την root της Apple ή επαληθεύει intermediate certificates σε μια ασφαλή αλυσίδα.
- Εξασφαλίζει ότι οι τροποποιήσεις (π.χ. στο filesystem) σε υπάρχοντα binaries ανιχνεύονται και απορρίπτονται.
- Δένει entitlements, team IDs, code signing flags και άλλα metadata στο binary κατά το load time.
Example
Ένα jailbreak μπορεί να προσπαθήσει να αντικαταστήσει το `SpringBoard` ή `libsystem` με patched έκδοση για να αποκτήσει 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 (για data) είναι non-executable, και σελίδες που είναι executable είναι non-writable. Δεν μπορείς απλά να γράψεις shellcode στο heap ή στο stack και να το εκτελέσεις.
- Αποτρέπει: direct shellcode execution; κλασικό buffer-overflow → jump to injected shellcode.
- Λεπτομέρειες μηχανισμού:
- Ο MMU / οι flags προστασίας μνήμης (μέσω page tables) επιβάλλουν τον διαχωρισμό.
- Κάθε προσπάθεια να σημειωθεί μια writable σελίδα ως executable ενεργοποιεί έναν έλεγχο του συστήματος (και είτε απαγορεύεται είτε απαιτεί code-sign approval).
- Σε πολλές περιπτώσεις, το να γίνει μια σελίδα executable απαιτεί χρήση OS APIs που επιβάλλουν επιπλέον περιορισμούς ή checks.
Example
Ένα 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 τυχαιοποιεί τις base addresses βασικών περιοχών μνήμης: libraries, heap, stack, κ.λπ., σε κάθε εκκίνηση διεργασίας. Οι διευθύνσεις των gadgets μετακινούνται μεταξύ runs.
- Αποτρέπει: hardcoding gadget addresses για ROP/JOP; static exploit chains; blind jumping σε γνωστά offsets.
- Λεπτομέρειες μηχανισμού:
- Κάθε loaded library / dynamic module rebased-εται σε ένα randomized offset.
- Οι base pointers του stack και του heap είναι randomized (εντός ορισμένων ορίων entropy).
- Μερικές φορές και άλλες περιοχές (π.χ. mmap allocations) επίσης randomized.
- Σε συνδυασμό με information-leak mitigations, αναγκάζει τον attacker να πρώτα leak-άρει μια διεύθυνση ή pointer για να ανακαλύψει base addresses στο runtime.
Example
Ένας ROP chain περιμένει gadget στη διεύθυνση `0x….lib + offset`. Αλλά αφού η `lib` relocated-εται διαφορετικά κάθε run, ο hardcoded chain αποτυγχάνει. Ένα exploit πρέπει πρώτα να leak-άρει το base address του module πριν υπολογίσει τις διευθύνσεις των gadgets.5. Kernel Address Space Layout Randomization (KASLR)
Introduced in iOS ~ (iOS 5 / iOS 6 timeframe) Αναλογικά με το user ASLR, το KASLR τυχαιοποιεί το base του kernel text και άλλων kernel structures κατά το boot.
- Αποτρέπει: kernel-level exploits που βασίζονται σε fixed location του kernel code ή δεδομένων; static kernel exploits.
- Λεπτομέρειες μηχανισμού:
- Σε κάθε boot, το kernel’s base address randomized-εται (εντός ενός range).
- Kernel data structures (π.χ.
task_structs,vm_map, κ.λπ.) μπορεί επίσης να μετακινηθούν ή να έχουν offsets. - Οι attackers πρέπει πρώτα να leak-άρουν kernel pointers ή να χρησιμοποιήσουν information disclosure vulnerabilities για να υπολογίσουν offsets πριν hijack-άρουν kernel structures ή code.
Example
Ένα local vulnerability στοχεύει να διαφθείρει ένα kernel function pointer (π.χ. σε `vtable`) στο `KERN_BASE + offset`. Αλλά αφού το `KERN_BASE` είναι άγνωστο, ο επιτιθέμενος πρέπει πρώτα να το leak-άρει (π.χ. μέσω ενός read primitive) πριν υπολογίσει τη σωστή διεύθυνση για την τροποποίηση.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.
- Περιοδικά ή κατ’ απαίτηση ξανα-υπολογίζει hashes των pages και συγκρίνει με τα αναμενόμενα.
- Αν υπάρξουν mismatches εκτός benign update windows, προκαλεί panic στη συσκευή (για να αποφευχθεί persistent malicious patch).
- Οι attackers πρέπει είτε να αποφύγουν τα detection windows είτε να χρησιμοποιήσουν legitimate patch paths.
Example
Ένα exploit προσπαθεί να patch-άρει το prologue μιας kernel function (π.χ. `memcmp`) για να παρεμβαίνει σε κλήσεις. Αλλά το KPP παρατηρεί ότι η σελίδα κώδικα δεν ταιριάζει πλέον με την αναμενόμενη τιμή hash και προκαλεί kernel panic, προκαλώντας crash στη συσκευή πριν το 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), αποτρέποντας περαιτέρω writes σε code pages.
- Αποτρέπει: οποιεσδήποτε τροποποιήσεις στον kernel code μετά το boot (π.χ. patching, in-place code injection) σε επίπεδο EL1.
- Λεπτομέρειες μηχανισμού:
- Κατά το boot (στο secure/bootloader στάδιο), ο memory controller (ή ένα secure hardware unit) χαρακτηρίζει τις φυσικές σελίδες που περιέχουν kernel text ως read-only.
- Ακόμη και αν ένα exploit αποκτήσει πλήρη kernel privileges, δεν μπορεί να γράψει σε αυτές τις σελίδες για να κάνει patch σε instructions.
- Για να τις τροποποιήσει, ο attacker πρέπει πρώτα να συμβιβάσει την boot chain ή να υπονομεύσει το ίδιο το KTRR.
Example
Ένα privilege-escalation exploit πηδάει σε EL1 και γράφει ένα trampoline μέσα σε μια kernel function (π.χ. στον `syscall` handler). Αλλά επειδή οι σελίδες έχουν κλειδωθεί ως read-only από το KTRR, η write αποτυγχάνει (ή προκαλεί fault), οπότε τα patches δεν εφαρμόζονται.8. Pointer Authentication Codes (PAC)
Introduced with ARMv8.3 (hardware), Apple beginning with A12 / iOS ~12+
- Το PAC είναι μια hardware λειτουργία εισαχθείσα στο ARMv8.3-A για να ανιχνεύει την παραποίηση των pointer values (return addresses, function pointers, certain data pointers) ενσωματώνοντας μια μικρή κρυπτογραφική υπογραφή (“MAC”) στα unused high bits του pointer.
- Η υπογραφή (“PAC”) υπολογίζεται πάνω στο pointer value συν έναν modifier (μια context τιμή, π.χ. stack pointer ή κάποια διακριτική τιμή). Έτσι το ίδιο pointer value σε διαφορετικά contexts έχει διαφορετικό PAC.
- Στη χρήση, πριν το dereference ή το branch μέσω αυτού του pointer, μια authenticate instruction ελέγχει το PAC. Αν είναι έγκυρο, το PAC αφαιρείται και προκύπτει το καθαρό pointer· αν είναι άκυρο, ο pointer γίνεται “poisoned” (ή προκαλείται fault).
- Τα keys που χρησιμοποιούνται για παραγωγή/επικύρωση PAC ζουν σε privileged registers (EL1, kernel) και δεν είναι απευθείας αναγνώσιμα από user mode.
- Επειδή δεν χρησιμοποιούνται όλα τα 64 bits ενός pointer σε πολλά συστήματα (π.χ. 48-bit address space), τα upper bits είναι “spare” και μπορούν να φιλοξενήσουν το 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 non-pointer data ή άλλες generic χρήσεις
-
Αυτά τα keys αποθηκεύονται σε privileged system registers (πρόσιτα μόνο σε EL1/EL2 κ.λπ.), όχι σε user mode.
-
Το PAC υπολογίζεται μέσω μιας κρυπτογραφικής συνάρτησης (το ARM προτείνει QARMA ως αλγόριθμο) χρησιμοποιώντας:
- Το pointer value (canonical portion)
- Έναν modifier (μία context τιμή, όπως salt)
- Το secret key
- Κάποιο εσωτερικό tweak logic Αν το παραγόμενο PAC ταιριάζει με ό,τι βρίσκεται στα upper bits του pointer, η authentication πετυχαίνει.
Instruction Families
Η ονοματολογία είναι: PAC / AUT / XPAC, μετά τα domain letters.
PACxxinstructions sign ένα pointer και εισάγουν PACAUTxxinstructions authenticate + strip (επαληθεύουν και αφαιρούν το PAC)XPACxxinstructions strip χωρίς validation
Domains / suffixes:
| Mnemonic | Σημασία / Domain | Key / Domain | Example Usage in Assembly |
|---|---|---|---|
| PACIA | Sign instruction pointer with APIAKey | “I, A” | PACIA X0, X1 — sign pointer in X0 using APIAKey with modifier X1 |
| PACIB | Sign instruction pointer with APIBKey | “I, B” | PACIB X2, X3 |
| PACDA | Sign data pointer with APDAKey | “D, A” | PACDA X4, X5 |
| PACDB | Sign data pointer with APDBKey | “D, B” | PACDB X6, X7 |
| PACG / PACGA | Generic (non-pointer) signing with APGAKey | “G” | PACGA X8, X9, X10 (sign X9 with modifier X10 into X8) |
| AUTIA | Authenticate APIA-signed instruction pointer & strip PAC | “I, A” | AUTIA X0, X1 — check PAC on X0 using modifier X1, then strip |
| AUTIB | Authenticate APIB domain | “I, B” | AUTIB X2, X3 |
| AUTDA | Authenticate APDA-signed data pointer | “D, A” | AUTDA X4, X5 |
| AUTDB | Authenticate APDB-signed data pointer | “D, B” | AUTDB X6, X7 |
| AUTGA | Authenticate generic / blob (APGA) | “G” | AUTGA X8, X9, X10 (validate generic) |
| XPACI | Strip PAC (instruction pointer, no validation) | “I” | XPACI X0 — remove PAC from X0 (instruction domain) |
| XPACD | Strip PAC (data pointer, no validation) | “D” | XPACD X4 — remove PAC from data pointer in X4 |
Υπάρχουν εξειδικευμένες / 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 variants:
PACIZA/PACIZBόπου ο modifier είναι implicitly zero, κ.λπ.
Modifiers
Ο κύριος στόχος του modifier είναι να δεσμεύσει το PAC σε ένα συγκεκριμένο context έτσι ώστε η ίδια διεύθυνση υπογεγραμμένη σε διαφορετικά contexts να παράγει διαφορετικά PACs. Είναι σαν να προσθέτεις salt σε ένα hash.
Επομένως:
- Ο modifier είναι μια context τιμή (άλλο register) που αναμιγνύεται στον υπολογισμό του PAC. Συνηθισμένες επιλογές: το stack pointer (
SP), ένας frame pointer, ή κάποιο object ID. - Η χρήση του SP ως modifier είναι συχνή για signing του return address: το PAC δεσμεύεται στο συγκεκριμένο stack frame. Αν προσπαθήσεις να επαναχρησιμοποιήσεις το LR σε διαφορετικό frame, ο modifier αλλάζει και η PAC validation αποτυγχάνει.
- Το ίδιο pointer value υπογεγραμμένο με διαφορετικούς modifiers δίνει διαφορετικά PACs.
- Ο modifier δεν χρειάζεται να είναι μυστικός, αλλά ιδανικά να μην είναι attacker-controlled.
- Για instructions που sign ή verify pointers όπου δεν υπάρχει meaningful modifier, κάποιες μορφές χρησιμοποιούν zero ή ένα 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 για control των keys.
- Η Apple χρησιμοποιεί PAC σε πολλά kernel subsystems: return address signing, pointer integrity σε kernel data, signed thread contexts, κ.λπ.
- Το Google Project Zero έδειξε πως κάτω από ένα ισχυρό memory read/write primitive στον kernel, κάποιος μπορούσε να forge-άρει kernel PACs (για A keys) σε A12-era συσκευές, αλλά η Apple patched πολλά από αυτά τα paths.
- Στο σύστημα της Apple, κάποια keys είναι global across kernel, ενώ οι user processes μπορεί να έχουν per-process key randomness.
PAC Bypasses
- Kernel-mode PAC: theoretical vs real bypasses
- Επειδή τα kernel PAC keys και η λογική είναι αυστηρά ελεγχόμενα (privileged registers, diversifiers, domain isolation), το forging αυθαίρετων 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” προσαρμογές μειώνουν περαιτέρω επιφάνειες εκμετάλλευσης (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 θεωρείται πολύ ανθεκτικό, αν και όχι τέλειο.
- User-mode / runtime PAC bypass techniques
Αυτά είναι πιο συχνά και εκμεταλλεύονται ατέλειες στο πώς εφαρμόζεται το PAC ή στο runtime frameworks / dynamic linking. Παρακάτω κατηγορίες με παραδείγματα.
2.1 Shared Cache / A key issues
-
Το dyld shared cache είναι ένα μεγάλο pre-linked blob system frameworks και libraries. Επειδή μοιράζεται ευρέως, function pointers μέσα στο shared cache είναι “pre-signed” και χρησιμοποιούνται από πολλές processes. Οι επιτιθέμενοι στοχεύουν αυτούς τους ήδη-signed pointers ως “PAC oracles”.
-
Κάποιες τεχνικές bypass προσπαθούν να εξάγουν ή να επαναχρησιμοποιήσουν A-key signed pointers που υπάρχουν στο shared cache και να τα χρησιμοποιήσουν σε gadgets.
-
Το talk “No Clicks Required” περιγράφει τη δημιουργία ενός oracle πάνω στο shared cache για να εξαχθούν relative addresses και να συνδυαστούν με signed pointers για να παρακαμφθεί το PAC. saelo.github.io
-
Επίσης, imports function pointers από shared libraries σε userspace βρέθηκε ότι ήταν ανεπαρκώς προστατευμένα από PAC, επιτρέποντας στον attacker να πάρει function pointers χωρίς να αλλάξει την υπογραφή τους. (Project Zero bug entry) bugs.chromium.org
2.2 dlsym(3) / dynamic symbol resolution
-
Ένα γνωστό bypass είναι να καλέσεις
dlsym()για να πάρεις έναν already signed function pointer (υπογεγραμμένο με A-key, diversifier zero) και έπειτα να τον χρησιμοποιήσεις. Επειδή τοdlsymεπιστρέφει έναν νόμιμα υπογεγραμμένο 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 που είναι λανθασμένα signed (ή με buggy diversifiers), επιτρέποντας ανεπιθύμητο PAC bypass. Synacktiv -
Η λογική στο dyld για dlsym περιλαμβάνει: όταν
result->isCode, sign-άρουν τον returned pointer με__builtin_ptrauth_sign_unauthenticated(..., key_asia, 0), δηλ. context zero. blog.epsilon-sec.com
Έτσι, το dlsym είναι συχνό vector σε user-mode PAC bypasses.
2.3 Other DYLD / runtime relocations
-
Ο DYLD loader και η dynamic relocation λογική είναι περίπλοκη και μερικές φορές προσωρινά map-άρει pages ως read/write για να κάνει relocations, και μετά τα επιστρέφει σε read-only. Οι attackers εκμεταλλεύονται αυτά τα παράθυρα. Το talk της Synacktiv περιγράφει την “Operation Triangulation”, ένα timing-based bypass του PAC μέσω dynamic relocations. Synacktiv
-
Οι DYLD pages τώρα προστατεύονται με SPRR / VM_FLAGS_TPRO (κάποια protection flags για dyld). Αλλά παλαιότερες εκδόσεις είχαν ασθενέστερα guards. Synacktiv
-
Σε WebKit exploit chains, ο DYLD loader είναι συχνά στόχος για PAC bypass. Οι διαφάνειες αναφέρουν ότι πολλά PAC bypasses στόχευσαν τον DYLD loader (μέσω relocation, interposer hooks). Synacktiv
2.4 NSPredicate / NSExpression / ObjC / SLOP
-
Σε userland exploit chains, Objective-C runtime methods όπως
NSPredicate,NSExpressionήNSInvocationχρησιμοποιούνται για να μεταφέρουν calls ελέγχου χωρίς εμφανή pointer forging. -
Σε παλαιότερο 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-protected. 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 αντικαθιστά μια διεύθυνση επιστροφής στο stack. Ο επιτιθέμενος γράφει τη διεύθυνση του στοχευόμενου gadget αλλά δεν μπορεί να υπολογίσει το σωστό PAC. Όταν η συνάρτηση επιστρέφει, η εντολή `AUTIA` της CPU προκαλεί fault λόγω της ασυμφωνίας PAC. Η αλυσίδα αποτυγχάνει.
Η ανάλυση του Project Zero στο A12 (iPhone XS) έδειξε πώς χρησιμοποιείται το PAC της Apple και μεθόδους πλαστογράφησης PACs εάν ο επιτιθέμενος έχει primitive ανάγνωσης/εγγραφής μνήμης.
</details>
### 9. **Branch Target Identification (BTI)**
**Εισήχθη με ARMv8.5 (πιο πρόσφατο υλικό)**
Το BTI είναι ένα χαρακτηριστικό υλικού που ελέγχει τους **στόχους έμμεσων διακλαδώσεων**: όταν εκτελούνται `blr` ή έμμεσες κλήσεις/άλματα, ο στόχος πρέπει να αρχίζει με ένα **BTI landing pad** (`BTI j` ή `BTI c`). Το άλμα σε διευθύνσεις gadget που δεν έχουν το landing pad προκαλεί εξαίρεση.
Η υλοποίηση του LLVM σημειώνει τρεις παραλλαγές των εντολών BTI και πώς αντιστοιχίζονται σε τύπους διακλάδωσης.
| 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 |
- Σε κώδικα που έχει μεταγλωττιστεί με επιβολή branch target, οι μεταγλωττιστές εισάγουν μια εντολή BTI (C, J ή JC) σε κάθε έγκυρο στόχο έμμεσης διακλάδωσης (αρχές συναρτήσεων ή blocks προσβάσιμα από άλματα) έτσι ώστε οι έμμεσες διακλαδώσεις να επιτυγχάνονται μόνο σε αυτά τα σημεία.
- **Άμεσες διακλαδώσεις / κλήσεις** (π.χ. σταθερές διευθύνσεις `B`, `BL`) **δεν περιορίζονται** από το BTI. Η υπόθεση είναι ότι οι σελίδες κώδικα είναι αξιόπιστες και ο επιτιθέμενος δεν μπορεί να τις αλλάξει (οπότε οι άμεσες διακλαδώσεις θεωρούνται ασφαλείς).
- Επίσης, οι **RET / return** εντολές γενικά δεν περιορίζονται από το BTI επειδή οι διευθύνσεις επιστροφής προστατεύονται μέσω PAC ή μηχανισμών υπογραφής επιστροφής.
#### Μηχανισμός και επιβολή
- Όταν η CPU αποκωδικοποιεί μια **έμμεση διακλάδωση (BLR / BR)** σε μια σελίδα που έχει σημειωθεί ως “guarded / BTI-enabled,” ελέγχει αν η πρώτη εντολή της διεύθυνσης στόχου είναι ένα έγκυρο BTI (C, J ή JC όπως επιτρέπεται). Εάν όχι, συμβαίνει **Branch Target Exception**.
- Η κωδικοποίηση της εντολής BTI έχει σχεδιαστεί ώστε να επαναχρησιμοποιεί opcodes που προηγουμένως προορίζονταν για NOPs (σε παλαιότερες εκδόσεις ARM). Έτσι τα binaries με BTI παραμένουν συμβατά προς τα πίσω: σε hardware χωρίς υποστήριξη BTI, αυτές οι εντολές συμπεριφέρονται ως NOPs.
- Τα passes του compiler που προσθέτουν BTIs τα εισάγουν μόνο όπου χρειάζεται: συναρτήσεις που μπορεί να κληθούν έμμεσα, ή βασικά blocks που στοχεύονται από άλματα.
- Ορισμένα patches και κώδικας του LLVM δείχνουν ότι το BTI δεν εισάγεται για *όλα* τα basic blocks — μόνο για εκείνα που είναι πιθανοί στόχοι διακλάδωσης (π.χ. από switch / jump tables).
#### BTI + PAC συνεργία
Το PAC προστατεύει την τιμή του pointer (την πηγή) — διασφαλίζει ότι η αλυσίδα των έμμεσων κλήσεων / επιστροφών δεν έχει παραποιηθεί.
Το BTI διασφαλίζει ότι ακόμη κι ένας έγκυρος pointer πρέπει να στοχεύει μόνο σε σωστά επισημασμένα entry points.
Σε συνδυασμό, ο επιτιθέμενος χρειάζεται τόσο έναν έγκυρο pointer με σωστό PAC όσο και ο στόχος να έχει τοποθετημένο BTI εκεί. Αυτό αυξάνει τη δυσκολία κατασκευής exploit gadgets.
#### Example
<details>
<summary>Example</summary>
Ένα exploit επιχειρεί να κάνει pivot σε gadget στη διεύθυνση `0xABCDEF` που δεν ξεκινά με `BTI c`. Η CPU, κατά την εκτέλεση του `blr x0`, ελέγχει τον στόχο και προκαλεί fault επειδή η στοίχιση της εντολής δεν περιέχει έγκυρο 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 κώδικα** (EL1 ή EL2) από το να **διαβάζει ή να γράφει** μνήμη που έχει χαρακτηριστεί ως **user-accessible (EL0)**, εκτός αν το PAN απενεργοποιηθεί ρητά.
- Η ιδέα: ακόμη κι αν ο kernel παραπλανηθεί ή συμβιβαστεί, δεν μπορεί να αποδεσμευμένα να ακολουθήσει pointers του user-space χωρίς πρώτα να *απενεργοποιήσει* το PAN, μειώνοντας έτσι τον κίνδυνο για exploits τύπου **ret2usr** ή κακή χρήση buffers που ελέγχονται από χρήστη.
- Όταν το PAN είναι ενεργοποιημένο (PSTATE.PAN = 1), οποιαδήποτε privileged load/store εντολή που προσπελαύνει μια εικονική διεύθυνση που είναι “accessible at EL0” προκαλεί **permission fault**.
- Ο kernel, όταν πρέπει νόμιμα να προσπελάσει μνήμη user-space (π.χ. copy data to/from user buffers), πρέπει **προσωρινά να απενεργοποιήσει το PAN** (ή να χρησιμοποιήσει “unprivileged load/store” εντολές) για να επιτρέψει αυτή την πρόσβαση.
- Στο Linux σε ARM64, η υποστήριξη PAN εισήχθη περίπου το 2015: patches στον kernel πρόσθεσαν ανίχνευση του χαρακτηριστικού, και αντικατέστησαν `get_user` / `put_user` κ.λπ. με παραλλαγές που καθαρίζουν το PAN γύρω από προσβάσεις σε user μνήμη.
**Κύρια λεπτομέρεια / περιορισμός / bug**
- Όπως υπήρξε παρατήρηση από τον Siguza και άλλους, ένα σφάλμα στη σχεδίαση του spec (ή ασαφής συμπεριφορά) στο ARM σημαίνει ότι οι **execute-only user mappings** (`--x`) μπορεί **να μην ενεργοποιούν το PAN**. Με άλλα λόγια, αν μια user σελίδα είναι σημειωμένη ως εκτελέσιμη αλλά χωρίς δικαίωμα ανάγνωσης, η προσπάθεια ανάγνωσης από τον kernel μπορεί να παρακάμψει το PAN επειδή η αρχιτεκτονική θεωρεί ότι “accessible at EL0” απαιτεί δυνατότητα ανάγνωσης, όχι μόνο εκτέλεση. Αυτό οδηγεί σε παράκαμψη PAN σε ορισμένες ρυθμίσεις.
- Εξαιτίας αυτού, αν το iOS / XNU επιτρέπει execute-only user pages (όπως κάποιες ρυθμίσεις JIT ή code-cache), ο kernel μπορεί καταλάθως να διαβάσει από αυτές ακόμη και με ενεργό PAN. Αυτό είναι ένας γνωστός λεπτός εκμεταλλεύσιμος χώρος σε ορισμένα συστήματα ARMv8+.
#### PXN (Privileged eXecute Never)
- Το **PXN** είναι ένα bit στον πίνακα σελίδων (στην καταχώρηση πίνακα σελίδων, leaf ή block entries) που δείχνει ότι η σελίδα είναι **μη εκτελέσιμη όταν εκτελείται σε privileged mode** (δηλαδή όταν εκτελείται σε EL1).
- Το PXN εμποδίζει τον kernel (ή οποιονδήποτε privileged κώδικα) από το να κάνει jump σε ή να εκτελέσει εντολές από user-space σελίδες ακόμα κι αν ο έλεγχος μεταφερθεί εκεί. Στην ουσία, σταματάει την ανακατεύθυνση ροής εκτέλεσης σε κώδικα που βρίσκεται στη μνήμη του χρήστη.
- Σε συνδυασμό με το PAN, αυτό εξασφαλίζει ότι:
1. Ο kernel δεν μπορεί (κατά προεπιλογή) να διαβάσει ή να γράψει δεδομένα χρήστη (PAN)
2. Ο kernel δεν μπορεί να εκτελέσει κώδικα χρήστη (PXN)
- Στο format των σελίδων ARMv8, οι leaf καταχωρήσεις έχουν ένα bit `PXN` (και επίσης `UXN` για unprivileged execute-never) στα bits χαρακτηριστικών τους.
Έτσι, ακόμα κι αν ο kernel έχει έναν διεφθαρμένο function pointer που δείχνει σε μνήμη χρήστη και επιχειρήσει να κάνει branch εκεί, το bit PXN θα προκαλέσει fault.
#### Πρότυπο δικαιωμάτων μνήμης & πώς το PAN και το PXN αντιστοιχίζονται σε bits πίνακα σελίδων
Για να κατανοήσετε πώς δουλεύουν το PAN / PXN, πρέπει να δείτε πώς λειτουργεί το μοντέλο μετάφρασης και δικαιωμάτων του ARM (απλοποιημένο):
- Κάθε σελίδα ή block καταχώρηση έχει πεδία χαρακτηριστικών που περιλαμβάνουν τα **AP[2:1]** για δικαιώματα πρόσβασης (read/write, privileged vs unprivileged) και τα **UXN / PXN** bits για περιορισμούς execute-never.
- Όταν το PSTATE.PAN είναι 1 (ενεργοποιημένο), το hardware επιβάλλει τροποποιημένη σημασιολογία: οι privileged προσβάσεις σε σελίδες που σημειώνονται ως “accessible by EL0” (δηλαδή προσβάσιμες από χρήστη) απαγορεύονται (fault).
- Εξαιτίας του αναφερθέντος bug, σελίδες που σημειώνονται μόνο ως εκτελέσιμες (χωρίς δικαίωμα ανάγνωσης) μπορεί να μην θεωρούνται “accessible by EL0” σε κάποιες υλοποιήσεις, με αποτέλεσμα παράκαμψη του PAN.
- Όταν το bit PXN μιας σελίδας είναι ενεργό, ακόμη κι αν η fetch εντολής προέρχεται από υψηλότερο επίπεδο προνομίων, η εκτέλεση απαγορεύεται.
#### Χρήση του PAN / PXN στον kernel ενός hardened OS (π.χ. iOS / XNU)
Σε ένα σχεδιασμό hardened kernel (όπως αυτόν που μπορεί να χρησιμοποιεί η Apple):
- Ο kernel ενεργοποιεί το PAN από προεπιλογή (ώστε ο privileged κώδικας να περιοριστεί).
- Σε μονοπάτια που νόμιμα πρέπει να διαβάσουν ή να γράψουν buffers χρήστη (π.χ. syscall buffer copy, I/O, read/write user pointer), ο kernel προσωρινά **απενεργοποιεί το PAN** ή χρησιμοποιεί ειδικές εντολές για να παρακάμψει.
- Μετά το πέρας της πρόσβασης στα δεδομένα χρήστη, πρέπει να ξανα-ενεργοποιήσει το PAN.
- Το PXN επιβάλλεται μέσω των page tables: οι σελίδες χρήστη έχουν PXN = 1 (ώστε ο kernel να μην μπορεί να τις εκτελέσει), οι σελίδες kernel δεν έχουν PXN (ώστε ο kernel να μπορεί να εκτελέσει τον κώδικά του).
- Ο kernel πρέπει να διασφαλίσει ότι κανένα μονοπάτι κώδικα δεν οδηγεί σε εκτέλεση μέσα σε περιοχές μνήμης χρήστη (που θα παρακάμπτει το PXN) — έτσι οι αλυσίδες εκμετάλλευσης που βασίζονται στο “άλμα σε shellcode του χρήστη” αποκλείονται.
Εξαιτίας της αναφερθείσας PAN παράκαμψης μέσω execute-only σελίδων, σε ένα πραγματικό σύστημα η Apple μπορεί να απενεργοποιήσει ή να απαγορεύσει execute-only user pages, ή να επιδιορθώσει την ασθενή συμπεριφορά του specification.
#### Επιφάνειες επίθεσης, παρακάμψεις και μετριασμοί
- **PAN bypass μέσω execute-only pages**: όπως αναφέρθηκε, το spec αφήνει ένα κενό: user pages με execute-only (χωρίς δικαίωμα ανάγνωσης) μπορεί να μην θεωρούνται “accessible at EL0,” έτσι το PAN δεν θα εμποδίσει kernel reads από τέτοιες σελίδες σε κάποιες υλοποιήσεις. Αυτό δίνει στον επιτιθέμενο μια ασυνήθιστη οδό για να τροφοδοτήσει δεδομένα μέσω τμημάτων “execute-only”.
- **Temporal window exploit**: αν ο kernel απενεργοποιεί το PAN για μεγαλύτερο παράθυρο από το αναγκαίο, ένας race ή κακόβουλο μονοπάτι μπορεί να εκμεταλλευτεί αυτό το παράθυρο για να πραγματοποιήσει μη επιθυμητή πρόσβαση σε user μνήμη.
- **Ξεχνιέται η επανενεργοποίηση**: αν μονοπάτια κώδικα αποτύχουν να ξανα-ενεργοποιήσουν το PAN, επακόλουθες λειτουργίες του kernel μπορεί λανθασμένα να προσπελάσουν user μνήμη.
- **Λανθασμένη ρύθμιση του PXN**: αν οι page tables δεν βάλουν PXN στις user σελίδες ή χαρτογραφήσουν λανθασμένα σελίδες κώδικα χρήστη, ο kernel μπορεί να παραπλανηθεί ώστε να εκτελέσει κώδικα χρήστη.
- **Προπαρακάμψεις / side-channels**: ανάλογα με speculative bypasses, μπορεί να υπάρξουν μικροαρχιτεκτονικές παρενέργειες που προκαλούν παροδική παραβίαση των ελέγχων PAN / PXN (αν και τέτοιες επιθέσεις εξαρτώνται έντονα από το σχεδιασμό της CPU).
- **Σύνθετες αλληλεπιδράσεις**: Σε πιο προχωρημένα χαρακτηριστικά (π.χ. JIT, shared memory, περιοχές just-in-time κώδικα), ο kernel μπορεί να χρειαστεί λεπτομερή έλεγχο για να επιτρέψει ορισμένες προσβάσεις μνήμης ή εκτέλεση σε user-mapped περιοχές· το σχεδιασμό αυτών με ασφάλεια υπό τους περιορισμούς PAN/PXN είναι μη τριβιακό.
#### Example
<details>
<summary>Code Example</summary>
Εδώ είναι ενδεικτικές ψευδο-ακολουθίες assembly που δείχνουν την ενεργοποίηση/απενεργοποίηση του PAN γύρω από πρόσβαση σε μνήμη χρήστη, και πώς μπορεί να προκύψει ένα 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>Παράδειγμα</summary>
Μια ευπάθεια στον kernel προσπαθεί να πάρει έναν user-provided function pointer και να τον καλέσει σε kernel context (π.χ. `call user_buffer`). Υπό PAN/PXN, αυτή η ενέργεια απαγορεύεται ή προκαλεί fault.
</details>
---
### 11. **Top Byte Ignore (TBI) / Pointer Tagging**
**Introduced in ARMv8.5 / newer (or optional extension)**
TBI σημαίνει ότι το top byte (το πιο σημαντικό byte) ενός 64-bit pointer αγνοείται από τη μεταφράση διευθύνσεων. Αυτό επιτρέπει στο OS ή στο hardware να ενσωματώσει **tag bits** στο top byte του pointer χωρίς να επηρεάζεται η πραγματική διεύθυνση.
- TBI stands for **Top Byte Ignore** (sometimes called *Address Tagging*). Είναι ένα hardware feature (διαθέσιμο σε πολλές ARMv8+ υλοποιήσεις) που **αγνοεί τα top 8 bits** (bits 63:56) ενός 64-bit pointer όταν γίνεται **address translation / load/store / instruction fetch**.
- Στην πράξη, η CPU αντιμετωπίζει έναν pointer `0xTTxxxx_xxxx_xxxx` (όπου `TT` = top byte) ως `0x00xxxx_xxxx_xxxx` για τους σκοπούς της address translation, αγνοώντας (masking off) το top byte. Το top byte μπορεί να χρησιμοποιηθεί από το software για αποθήκευση **metadata / tag bits**.
- Αυτό δίνει στο software “δωρεάν” in-band χώρο για να ενσωματώνει ένα byte tag σε κάθε pointer χωρίς να αλλάζει σε ποια θέση μνήμης αναφέρεται.
- Η αρχιτεκτονική εξασφαλίζει ότι loads, stores, και instruction fetch αντιμετωπίζουν τον pointer με το top byte masked (δηλ. το tag αφαιρεμένο) πριν την πραγματική πρόσβαση στη μνήμη.
Έτσι, το TBI αποσυνδέει τον **logical pointer** (pointer + tag) από τη **physical address** που χρησιμοποιείται για λειτουργίες μνήμης.
#### Γιατί TBI: Χρήσεις και κίνητρο
- **Pointer tagging / metadata**: Μπορείς να αποθηκεύσεις επιπλέον metadata (π.χ. object type, version, bounds, integrity tags) σε εκείνο το top byte. Όταν χρησιμοποιήσεις αργότερα τον pointer, το tag αγνοείται σε hardware επίπεδο, οπότε δεν χρειάζεται να το αφαιρείς χειροκίνητα για την πρόσβαση στη μνήμη.
- **Memory tagging / MTE (Memory Tagging Extension)**: Το TBI είναι ο βασικός μηχανισμός hardware πάνω στον οποίο χτίζει το MTE. Στο ARMv8.5, η **Memory Tagging Extension** χρησιμοποιεί τα bits 59:56 του pointer ως **logical tag** και τα συγκρίνει με ένα **allocation tag** που αποθηκεύεται στη μνήμη.
- **Enhanced security & integrity**: Συνδυάζοντας το TBI με pointer authentication (PAC) ή runtime checks, μπορείς να απαιτήσεις όχι μόνο τη σωστή pointer τιμή αλλά και το σωστό tag. Ένας attacker που υπεργράφει έναν pointer χωρίς το σωστό tag θα προκαλέσει mismatch.
- **Compatibility**: Εφόσον το TBI είναι επιλογικό και τα tag bits αγνοούνται από το hardware, ο υπάρχων μη-tagged κώδικας συνεχίζει να λειτουργεί κανονικά. Τα tag bits λειτουργούν ουσιαστικά ως “don't care” bits για legacy κώδικα.
#### Παράδειγμα
<details>
<summary>Παράδειγμα</summary>
Ένας function pointer περιείχε ένα tag στο top byte του (π.χ. `0xAA`). Ένα exploit υπεργράφει τα χαμηλά bits του pointer αλλά παραλείπει το tag, οπότε όταν ο kernel κάνει verification ή sanitization, ο pointer απορρίπτεται.
</details>
---
### 12. **Page Protection Layer (PPL)**
**Introduced in late iOS / modern hardware (iOS ~17 / Apple silicon / high-end models)** (κάποιες αναφορές δείχνουν PPL στο macOS / Apple silicon, αλλά η Apple φέρνει ανάλογες προστασίες στο iOS)
- Το PPL έχει σχεδιαστεί ως ένα **intra-kernel protection boundary**: ακόμα κι αν ο kernel (EL1) έχει παραβιαστεί και διαθέτει R/W πρόσβαση, **δεν θα πρέπει να μπορεί να τροποποιεί ελεύθερα** ορισμένες **ευαίσθητες σελίδες** (ειδικά page tables, code-signing metadata, kernel code pages, entitlements, trust caches κ.λπ.).
- Δημιουργεί ουσιαστικά έναν **“kernel μέσα στο kernel”** — ένα μικρότερης επιφάνειας trust component (PPL) με **αυξημένα προνόμια** που μόνο αυτό μπορεί να τροποποιεί προστατευμένες σελίδες. Άλλος kernel κώδικας πρέπει να καλεί PPL routines για να κάνει αλλαγές.
- Αυτό μειώνει την επιφάνεια επίθεσης για kernel exploits: ακόμα και με πλήρη arbitrary R/W/execute σε kernel mode, ο exploit πρέπει επίσης να εισέλθει στο PPL domain (ή να παρακάμψει το PPL) για να τροποποιήσει κρίσιμες δομές.
- Σε νεότερα Apple silicon (A15+ / M2+), η Apple μεταβαίνει σε **SPTM (Secure Page Table Monitor)**, που σε πολλές περιπτώσεις αντικαθιστά το PPL για προστασία των page-tables σε αυτές τις πλατφόρμες.
Πώς πιστεύεται ότι λειτουργεί το PPL, με βάση δημόσιες αναλύσεις:
#### Use of APRR / permission routing (APRR = Access Permission ReRouting)
- Το Apple hardware χρησιμοποιεί έναν μηχανισμό που ονομάζεται **APRR (Access Permission ReRouting)**, που επιτρέπει στα page table entries (PTEs) να περιέχουν μικρούς δείκτες (indices), αντί για πλήρη permission bits. Αυτοί οι δείκτες χαρτογραφούνται μέσω APRR registers σε πραγματικά permissions. Αυτό επιτρέπει δυναμικό remapping των permissions ανά domain.
- Το PPL αξιοποιεί το APRR για να διαχωρίσει τα privilege εντός του kernel context: μόνο το PPL domain επιτρέπεται να ενημερώνει τη χαρτογράφηση μεταξύ indices και των αποτελεσματικών permissions. Δηλαδή, όταν μη-PPL kernel κώδικας γράφει ένα PTE ή προσπαθεί να αλλάξει permission bits, η λογική APRR το αποτρέπει (ή επιβάλλει read-only mapping).
- Ο PPL κώδικας τρέχει σε μια περιορισμένη περιοχή (π.χ. `__PPLTEXT`) που κανονικά δεν είναι executable ή writable μέχρι να επιτραπεί προσωρινά μέσω entry gates. Ο kernel καλεί PPL entry points (“PPL routines”) για να εκτελέσει ευαίσθητες λειτουργίες.
#### Gate / Entry & Exit
- Όταν ο kernel χρειάζεται να τροποποιήσει μια προστατευμένη σελίδα (π.χ. να αλλάξει permissions μιας kernel code page, ή να τροποποιήσει page tables), καλεί μια **PPL wrapper** ρουτίνα, η οποία κάνει validation και μετά μεταβαίνει στο PPL domain. Έξω από αυτό το domain, οι προστατευμένες σελίδες είναι ουσιαστικά read-only ή μη τροποποιήσιμες από τον κύριο kernel.
- Κατά την είσοδο στο PPL, οι APRR mappings προσαρμόζονται έτσι ώστε οι memory pages στην PPL περιοχή να είναι **executable & writable** μέσα στο PPL. Μετά την έξοδο, επαναφέρονται σε read-only / non-writable. Αυτό εξασφαλίζει ότι μόνο οι καλά ελεγχόμενες PPL ρουτίνες μπορούν να γράψουν σε προστατευμένες σελίδες.
- Εκτός PPL, προσπάθειες του kernel να γράψουν σε αυτές τις προστατευμένες σελίδες θα προκαλέσουν fault (permission denied) επειδή το APRR mapping για εκείνο το code domain δεν επιτρέπει την εγγραφή.
#### Protected page categories
Οι σελίδες που τυπικά προστατεύει το PPL περιλαμβάνουν:
- Page table structures (translation table entries, mapping metadata)
- Kernel code pages, ειδικά αυτές με κρίσιμη λογική
- Code-sign metadata (trust caches, signature blobs)
- Entitlement tables, πίνακες enforcement signatures
- Άλλες υψηλής αξίας kernel δομές όπου ένα patch θα επέτρεπε παράκαμψη των signature checks ή χειραγώγηση credentials
Η ιδέα είναι ότι ακόμη κι αν η kernel μνήμη είναι πλήρως ελεγχόμενη, ο attacker δεν μπορεί απλά να κάνεις patch ή να ξαναγράψει αυτές τις σελίδες, εκτός αν επίσης παραβιάσει τις PPL ρουτίνες ή παρακάμψει το PPL.
#### Known Bypasses & Vulnerabilities
1. **Project Zero’s PPL bypass (stale TLB trick)**
- Μια δημόσια ανάλυση από το Project Zero περιγράφει μια παράκαμψη που εμπλέκει **stale TLB entries**.
- Η ιδέα:
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.
- Αυτό το exploit απαιτούσε λεπτό έλεγχο των φυσικών mappings και της συμπεριφοράς της TLB. Δείχνει ότι ένα security boundary που βασίζεται στην ορθότητα των TLB / mapping πρέπει να είναι εξαιρετικά προσεκτικό όσον αφορά τις invalidations της TLB και τη συνοχή των mappings.
- Το Project Zero σχολίασε ότι παρακάμψεις σαν αυτή είναι λεπτές και σπάνιες, αλλά δυνατές σε σύνθετα συστήματα. Παρ’ όλα αυτά, θεωρούν το PPL ως μια σημαντική μετρίαση.
2. **Other potential hazards & constraints**
- Εάν ένας kernel exploit μπορεί να εισέλθει απευθείας σε PPL routines (μέσω κλήσης των PPL wrappers), μπορεί να παρακάμψει τους περιορισμούς. Επομένως, η έγκυρη επαλήθευση των ορισμάτων είναι κρίσιμη.
- Bugs στον ίδιο τον PPL κώδικα (π.χ. overflow στην αριθμητική, λάθη στα όρια) μπορούν να επιτρέψουν out-of-bounds τροποποιήσεις μέσα στο PPL. Το Project Zero παρατήρησε ότι ένα τέτοιο bug στο `pmap_remove_options_internal()` εκμεταλλεύτηκε στην παράκαμψη τους.
- Το PPL boundary είναι άρρηκτα δεμένο με την επιβολή από το hardware (APRR, memory controller), οπότε είναι τόσο ισχυρό όσο και η υλοποίηση του hardware.
#### Παράδειγμα
<details>
<summary>Code Example</summary>
Εδώ είναι ένα απλοποιημένο pseudocode / λογική που δείχνει πώς ο kernel μπορεί να καλέσει το PPL για να τροποποιήσει προστατευμένες σελίδες:
</details>
```c
// In kernel (outside PPL domain)
function kernel_modify_pptable(pt_addr, new_entry) {
// validate arguments, etc.
return ppl_call_modify(pt_addr, new_entry) // call PPL wrapper
}
// In PPL (trusted domain)
function ppl_call_modify(pt_addr, new_entry) {
// temporarily enable write access to protected pages (via APRR adjustments)
aprr_set_index_for_write(PPL_INDEX)
// perform the modification
*pt_addr = new_entry
// restore permissions (make pages read-only again)
aprr_restore_default()
return success
}
// If kernel code outside PPL does:
*pt_addr = new_entry // a direct write
// It will fault because APRR mapping for non-PPL domain disallows write to that page
Ο kernel μπορεί να εκτελεί πολλές κανονικές λειτουργίες, αλλά μόνο μέσω των ρουτινών ppl_call_* μπορεί να αλλάξει προστατευμένες αντιστοιχίσεις ή να κάνει patch σε κώδικα.
Παράδειγμα
Ένα kernel exploit προσπαθεί να αντικαταστήσει τον πίνακα entitlements, ή να απενεργοποιήσει την επιβολή του code-sign τροποποιώντας ένα kernel signature blob. Επειδή αυτή η σελίδα προστατεύεται από PPL, η εγγραφή μπλοκάρεται εκτός αν γίνει μέσω του PPL interface. Έτσι, ακόμα και με κώδικα σε επίπεδο kernel, δεν μπορείτε να παρακάμψετε τους περιορισμούς του code-sign ή να τροποποιήσετε αυθαίρετα δεδομένα credentials. Σε iOS 17+ ορισμένες συσκευές χρησιμοποιούν SPTM για να απομονώσουν περαιτέρω τις σελίδες που διαχειρίζεται το PPL.PPL → SPTM / Replacements / Future
- Σε σύγχρονα SoC της Apple (A15 ή νεότερα, M2 ή νεότερα), η Apple υποστηρίζει SPTM (Secure Page Table Monitor), το οποίο replaces PPL για τις προστασίες του page table.
- Η Apple αναφέρει στην τεκμηρίωση: “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.”
- Η αρχιτεκτονική του SPTM πιθανόν μεταφέρει περισσότερη επιβολή πολιτικών σε έναν πιο υψηλής προνομίας monitor εκτός του ελέγχου του kernel, μειώνοντας περαιτέρω το trust boundary.
MTE | EMTE | MIE
Ακολουθεί μια περιγραφή υψηλού επιπέδου του πώς λειτουργεί το EMTE στο πλαίσιο του MIE της Apple:
- Ανάθεση tag
- Όταν δεσμεύεται μνήμη (π.χ. στον kernel ή στο user space μέσω secure allocators), σε αυτό το μπλοκ ανατίθεται ένα μυστικό tag.
- Ο pointer που επιστρέφεται στον χρήστη ή τον kernel περιλαμβάνει αυτό το tag στα υψηλά του bits (με χρήση TBI / top byte ignore μηχανισμών).
- Έλεγχος tag κατά την πρόσβαση
- Όποτε εκτελείται ένα load ή store χρησιμοποιώντας έναν pointer, το hardware ελέγχει αν το tag του pointer ταιριάζει με το tag του μπλοκ μνήμης (allocation tag). Σε ασυμβατότητα, προκαλείται άμεσα fault (εφόσον είναι synchronous).
- Επειδή είναι synchronous, δεν υπάρχει «παράθυρο καθυστερημένης ανίχνευσης».
- Retagging κατά το free / reuse
- Όταν η μνήμη απελευθερώνεται, ο allocator αλλάζει το tag του μπλοκ (ώστε οι παλιότεροι pointers με παλιά tags να μην ταιριάζουν πια).
- Ένας use-after-free pointer θα έχει επομένως stale tag και θα προκαλέσει mismatch όταν προσπελαστεί.
- Διαφοροποίηση neighbor-tag για τον εντοπισμό overflows
- Οι γειτονικές δεσμεύσεις λαμβάνουν διαφορετικά tags. Αν ένα buffer overflow χύσει σε μνήμη του γείτονα, η ασυμφωνία tag προκαλεί fault.
- Αυτό είναι ιδιαίτερα ισχυρό για τον εντοπισμό μικρών overflows που διασχίζουν σύνορα.
- Επιβολή εμπιστευτικότητας των tag
- Η Apple πρέπει να αποτρέψει τις τιμές tag από being leaked (επειδή αν ένας επιτιθέμενος μάθει το tag, θα μπορούσε να δημιουργήσει pointers με σωστά tags).
- Περιλαμβάνουν προστασίες (microarchitectural / speculative controls) για να αποφευχθεί το side-channel leakage των bit του tag.
- Ενσωμάτωση kernel και user-space
- Η Apple χρησιμοποιεί το EMTE όχι μόνο στο user-space αλλά και σε kernel / κρίσιμα components του OS (για την προστασία του kernel από διαφθορά μνήμης).
- Το hardware/OS διασφαλίζει ότι οι κανόνες των tag εφαρμόζονται ακόμα και όταν ο 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 μπορούν να be leaked (μέσω cache / speculative execution), ο επιτιθέμενος μπορεί να μάθει έγκυρα tags και να παρακάμψει. Η επιβολή Tag Confidentiality από Apple σχεδιάστηκε για να το μετριάσει.
- **Performance overhead**: Οι ελέγχοι tag σε κάθε load/store προσθέτουν κόστος· η Apple πρέπει να βελτιστοποιήσει το hardware για να μειώσει το overhead.
- **Compatibility & fallback**: Σε παλαιότερο hardware ή σε τμήματα που δεν υποστηρίζουν EMTE, πρέπει να υπάρχει fallback. Η Apple ισχυρίζεται ότι το MIE ενεργοποιείται μόνο σε συσκευές με υποστήριξη.
- **Complex allocator logic**: Ο allocator πρέπει να διαχειρίζεται tags, retagging, alignment ορίων και να αποφεύγει mis-tag συγκρούσεις. Σφάλματα στη λογική του allocator μπορεί να εισάγουν ευπάθειες.
- **Mixed memory / hybrid areas**: Μερική μνήμη μπορεί να παραμείνει untagged (legacy), καθιστώντας την αλληλεπιδραστικότητα πιο περίπλοκη.
- **Speculative / transient attacks**: Όπως με πολλές μικροαρχιτεκτονικές προστασίες, speculative execution ή micro-op fusions μπορεί να παρακάμψουν ελέγχους παροδικά ή να leak tag bits.
- **Limited to supported regions**: Η Apple μπορεί να εφαρμόζει EMTE μόνο σε επιλεγμένες, υψηλού κινδύνου περιοχές (kernel, security-critical subsystems), όχι παγκοσμίως.
---
## Key enhancements / differences compared to standard MTE
Here are the improvements and changes Apple emphasizes:
| Feature | Original MTE | EMTE (Apple’s enhanced) / MIE |
|---|---|---|
| **Check mode** | Supports synchronous and asynchronous modes. In async, tag mismatches are reported later (delayed)| Apple insists on **synchronous mode** by default—tag mismatches are caught immediately, no delay/race windows allowed.|
| **Coverage of non-tagged memory** | Accesses to non-tagged memory (e.g. globals) may bypass checks in some implementations | EMTE requires that accesses from a tagged region to non-tagged memory also validate tag knowledge, making it harder to bypass by mixing allocations.|
| **Tag confidentiality / secrecy** | Tags might be observable or leaked via side channels | Apple adds **Tag Confidentiality Enforcement**, which attempts to prevent leakage of tag values (via speculative side-channels etc.).|
| **Allocator integration & retagging** | MTE leaves much of allocator logic to software | Apple’s secure typed allocators (kalloc_type, xzone malloc, etc.) integrate with EMTE: when memory is allocated or freed, tags are managed at fine granularity.|
| **Always-on by default** | In many platforms, MTE is optional or off by default | Apple enables EMTE / MIE by default on supported hardware (e.g. iPhone 17 / A19) for kernel and many user processes.|
Because Apple controls both the hardware and software stack, it can enforce EMTE tightly, avoid performance pitfalls, and close side-channel holes.
---
## How EMTE works in practice (Apple / 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).
2. **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.
3. **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.
4. **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.
5. **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.
6. **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.
Because EMTE is built into MIE, Apple uses EMTE in synchronous mode across key attack surfaces, not as opt-in or debugging mode.
---
## Exception handling in XNU
When an **exception** occurs (e.g., `EXC_BAD_ACCESS`, `EXC_BAD_INSTRUCTION`, `EXC_CRASH`, `EXC_ARM_PAC`, etc.), the **Mach layer** of the XNU kernel is responsible for intercepting it before it becomes a UNIX-style **signal** (like `SIGSEGV`, `SIGBUS`, `SIGILL`, ...).
This process involves multiple layers of exception propagation and handling before reaching user space or being converted to a BSD signal.
### Exception Flow (High-Level)
1. **CPU triggers a synchronous exception** (e.g., invalid pointer dereference, PAC failure, illegal instruction, etc.).
2. **Low-level trap handler** runs (`trap.c`, `exception.c` in XNU source).
3. The trap handler calls **`exception_triage()`**, the core of the Mach exception handling.
4. `exception_triage()` decides how to route the exception:
- First to the **thread's exception port**.
- Then to the **task's exception port**.
- Then to the **host's exception port** (often `launchd` or `ReportCrash`).
If none of these ports handle the exception, the kernel may:
- **Convert it into a BSD signal** (for user-space processes).
- **Panic** (for kernel-space exceptions).
### Core Function: `exception_triage()`
The function `exception_triage()` routes Mach exceptions up the chain of possible handlers until one handles it or until it's finally fatal. It's defined in `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.
Θύρες Εξαιρέσεων
Κάθε αντικείμενο Mach (thread, task, host) μπορεί να καταχωρήσει θύρες εξαίρεσης, στις οποίες αποστέλλονται μηνύματα εξαίρεσης.
Ορίζονται από το API:
task_set_exception_ports()
thread_set_exception_ports()
host_set_exception_ports()
Each exception port has:
- A mask (ποιες εξαιρέσεις θέλει να λαμβάνει)
- A port name (Mach port to receive messages)
- A behavior (πώς ο kernel στέλνει το μήνυμα)
- A flavor (ποιο thread state να συμπεριλάβει)
Αποσφαλματωτές και Διαχείριση Εξαιρέσεων
Ένας debugger (π.χ. LLDB) sets an exception port στον target task ή thread, συνήθως χρησιμοποιώντας task_set_exception_ports().
Όταν προκύπτει μια εξαίρεση:
- Το Mach μήνυμα αποστέλλεται στη διαδικασία του debugger.
- Ο debugger μπορεί να αποφασίσει να handle (resume, τροποποίηση registers, skip instruction) ή not handle την εξαίρεση.
- Αν ο debugger δεν την handle-άρει, η εξαίρεση προωθείται στο επόμενο επίπεδο (task → host).
Ροή του EXC_BAD_ACCESS
-
Το thread κάνει dereference ενός μη έγκυρου pointer → CPU σηκώνει Data Abort.
-
Ο kernel trap handler καλεί
exception_triage(EXC_BAD_ACCESS, ...). -
Το μήνυμα στέλνεται σε:
-
Thread port → (ο debugger μπορεί να παρεμβληθεί σε breakpoint).
-
Αν ο debugger αγνοήσει → Task port → (process-level handler).
-
Αν αγνοηθεί → Host port (συνήθως ReportCrash).
- Αν κανείς δεν την χειριστεί →
bsd_exception()μεταφράζει σεSIGSEGV.
PAC Exceptions
Όταν η Pointer Authentication (PAC) αποτύχει (ασυμφωνία υπογραφής), εγείρεται μια ειδική Mach εξαίρεση:
EXC_ARM_PAC(type)- Κώδικες μπορεί να περιλαμβάνουν λεπτομέρειες (π.χ., τύπος κλειδιού, τύπος pointer).
Αν το binary έχει τη σημαία TFRO_PAC_EXC_FATAL, ο kernel θεωρεί τις αποτυχίες PAC ως fatal, παρακάμπτοντας την παρέμβαση του debugger. Αυτό γίνεται για να αποτραπεί η χρήση debuggers από επιτιθέμενους για παράκαμψη των ελέγχων PAC και είναι ενεργοποιημένο για platform binaries.
Software Breakpoints
Ένα software breakpoint (int3 on x86, brk on ARM64) υλοποιείται προκαλώντας σκόπιμο σφάλμα.
Ο debugger το πιάνει μέσω του exception port:
- Τροποποιεί το instruction pointer ή τη μνήμη.
- Επαναφέρει την αρχική εντολή.
- Συνεχίζει την εκτέλεση.
Ο ίδιος μηχανισμός είναι αυτός που επιτρέπει να “πιάσετε” μια PAC εξαίρεση — εκτός αν TFRO_PAC_EXC_FATAL είναι ορισμένο, οπότε δεν φτάνει ποτέ στον debugger.
Conversion to BSD Signals
Αν κανένας handler δεν αποδεχτεί την εξαίρεση:
-
Ο kernel καλεί
task_exception_notify() → bsd_exception(). -
Αυτό αντιστοιχεί τις Mach εξαιρέσεις σε signals:
| Mach Exception | Signal |
|---|---|
| EXC_BAD_ACCESS | SIGSEGV or SIGBUS |
| EXC_BAD_INSTRUCTION | SIGILL |
| EXC_ARITHMETIC | SIGFPE |
| EXC_SOFTWARE | SIGTRAP |
| EXC_BREAKPOINT | SIGTRAP |
| EXC_CRASH | SIGKILL |
| EXC_ARM_PAC | SIGILL (σε μη-θανατηφόρο περίπτωση) |
### Key Files in XNU Source
-
osfmk/kern/exception.c→ Πυρήνας τουexception_triage(),exception_deliver_*(). -
bsd/kern/kern_sig.c→ Λογική παράδοσης signals. -
osfmk/arm64/trap.c→ Χειριστές trap χαμηλού επιπέδου. -
osfmk/mach/exc.h→ Κώδικες εξαιρέσεων και δομές. -
osfmk/kern/task.c→ Ρύθμιση task exception port.
Old Kernel Heap (Pre-iOS 15 / Pre-A12 era)
Ο kernel χρησιμοποιούσε έναν zone allocator (kalloc) χωρισμένο σε ζώνες (zones) με σταθερό μέγεθος. Κάθε zone αποθηκεύει μόνο allocations μιας μοναδικής size class.
Από το screenshot:
| Zone Name | Element Size | Example Use |
|---|---|---|
default.kalloc.16 | 16 bytes | Πολύ μικρές kernel structs, pointers. |
default.kalloc.32 | 32 bytes | Μικρές structs, object headers. |
default.kalloc.64 | 64 bytes | IPC μηνύματα, μικρά kernel buffers. |
default.kalloc.128 | 128 bytes | Μεσαία αντικείμενα όπως μέρη του OSObject. |
| … | … | … |
default.kalloc.1280 | 1280 bytes | Μεγάλες δομές, IOSurface/γραφικά metadata. |
Πώς λειτουργούσε:
- Κάθε αίτημα allocation στρογγυλοποιούνταν προς τα πάνω στο πλησιέστερο μέγεθος zone. (π.χ., ένα αίτημα 50-byte πηγαίνει στο
kalloc.64zone). - Η μνήμη σε κάθε zone διατηρούνταν σε μια freelist — κομμάτια που απελευθερώνονταν από τον kernel επέστρεφαν σε εκείνη τη zone.
- Αν υπερχείλιζες ένα buffer 64-byte, θα αντικαθιστούσες το επόμενο αντικείμενο στην ίδια zone.
Γι’ αυτό το heap spraying / feng shui ήταν τόσο αποτελεσματικό: μπορούσες να προβλέψεις τους γείτονες αντικειμένων ψεκάζοντας allocations της ίδιας size class.
The freelist
Μέσα σε κάθε kalloc zone, τα freed αντικείμενα δεν επέστρεφαν απευθείας στο σύστημα — πήγαιναν σε μια freelist, μια συνδεδεμένη λίστα διαθέσιμων κομματιών.
-
Όταν ένα chunk απελευθερωνόταν, ο kernel έγραφε έναν pointer στην αρχή του chunk → τη διεύθυνση του επόμενου ελεύθερου chunk στην ίδια zone.
-
Η zone κρατούσε έναν HEAD pointer στο πρώτο ελεύθερο chunk.
-
Η allocation πάντα χρησιμοποιούσε το τρέχον HEAD:
-
Pop HEAD (επιστρέφει τη μνήμη στον caller).
-
Ενημέρωση HEAD = HEAD->next (αποθηκευμένο στην επικεφαλίδα του freed chunk).
-
Η απελευθέρωση ωθούσε τα 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, ένας επιτιθέμενος θα μπορούσε να το αλλοιώσει:
-
Heap overflow σε έναν διπλανό freed chunk → overwrite its “next” pointer.
-
Use-after-free write σε ένα freed object → overwrite its “next” pointer.
Τότε, στην επόμενη allocation αυτού του μεγέθους:
-
Ο allocator pops το corrupted chunk.
-
Ακολουθεί τον παρεχόμενο από τον επιτιθέμενο “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:
- 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.
- 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.
- 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).
- 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.
- 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.
- 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:
- Try per-CPU cache.
- If empty, pull from the global freelist.
- 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:
- Type lookup →
OSDatamaps tokalloc_type_osdatazone (size 64 bytes). - 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).
- 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
| Feature | Old Heap (Pre-iOS 15) | Modern Heap (iOS 15+ / A12+) |
|---|---|---|
| Allocation granularity | Fixed size buckets (kalloc.16, kalloc.32, etc.) | Size + type-based buckets (kalloc_type) |
| Placement predictability | High (same-size objects side by side) | Low (same-type grouping + randomness) |
| Freelist management | Raw pointers in freed chunks (easy to corrupt) | Encoded pointers (safe-linking style) |
| Adjacent object control | Easy via sprays/frees (feng shui predictable) | Hard — typed zones separate attacker objects |
| Kernel data/code protections | Few hardware protections | PPL / SPTM protect page tables & code pages, and PAC protects pointers |
| Allocation reuse validation | None (freelist pointers raw) | zone_require / zone enforcement |
| Exploit reliability | High with heap sprays | Much lower, requires logic bugs or info leaks |
| Large allocations handling | All small allocations managed equally | Large ones bypass zones → handled via VM |
Modern Userland Heap (iOS, macOS — type-aware / xzone malloc)
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:
- malloc / calloc / realloc / typed alloc is invoked with a size and type ID.
- The allocator uses the type ID to pick the correct segment group / zone.
- 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.
- The metadata slab is updated (free bit cleared, bookkeeping).
- If memory tagging (EMTE) is in play, the returned block gets a tag assigned, and metadata is updated to reflect its “live” state.
- 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:
| Feature | Purpose | Notes |
|---|---|---|
| Metadata decoupling | Prevent overflow from corrupting metadata | Metadata lives in separate VM region (metadata slab) |
| Guard pages / unmapped slices | Catch out-of-bounds writes | Helps detect buffer overflows rather than silently corrupting adjacent blocks |
| Type-based segregation | Prevent cross-type reuse & type confusion | Even same-size allocations from different types go to different zones |
| Memory Tagging (EMTE / MIE) | Detect invalid access, stale references, OOB, UAF | xzone works in concert with hardware EMTE in synchronous mode (“Memory Integrity Enforcement”) |
| Delayed reuse / poisoning / zap | Reduce chance of use-after-free exploitation | Freed blocks may be poisoned, zeroed, or quarantined before reuse |
| Chunk reclamation / dynamic unmapping | Reduce memory waste and fragmentation | Entire chunks may be unmapped when unused |
| Randomization / placement variation | Prevent deterministic adjacency | Blocks in a chunk and chunk selection may have randomized aspects |
| Segregation of “data-only” allocations | Separate allocations that don’t store pointers | Reduces attacker control over metadata or control fields |
Interaction with Memory Integrity Enforcement (MIE / EMTE)
- 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
Ghidra Install BinDiff
Download BinDiff DMG from https://www.zynamics.com/bindiff/manual and install it.
Open Ghidra with ghidraRun and go to File –> Install Extensions, press the add button and select the path /Applications/BinDiff/Extra/Ghidra/BinExport and click OK and isntall it even if there is a version mismatch.
Using BinDiff with Kernel versions
- Go to the page https://ipsw.me/ and download the iOS versions you want to diff. These will be
.ipswfiles. - Decompress until you get the bin format of the kernelcache of both
.ipswfiles. You have information on how to do this on:
macOS Kernel Extensions & Kernelcache
- Open Ghidra with
ghidraRun, create a new project and load the kernelcaches. - Open each kernelcache so they are automatically analyzed by Ghidra.
- Then, on the project Window of Ghidra, right click each kernelcache, select
Export, select formatBinary BinExport (v2) for BinDiffand export them. - Open BinDiff, create a new workspace and add a new diff indicating as primary file the kernelcache that contains the vulnerability and as secondary file the patched kernelcache.
Finding the right XNU version
If you want to check for vulnerabilities in a specific version of iOS, you can check which XNU release version the iOS version uses at [https://www.theiphonewiki.com/wiki/kernel]https://www.theiphonewiki.com/wiki/kernel).
For example, the versions 15.1 RC, 15.1 and 15.1.1 use the version Darwin Kernel Version 21.1.0: Wed Oct 13 19:14:48 PDT 2021; root:xnu-8019.43.1~1/RELEASE_ARM64_T8006.
JSKit-Based Safari Chains and PREYHUNTER Stagers
Renderer RCE abstraction with JSKit
- Reusable entry: Recent in-the-wild chains abused a WebKit JIT bug (patched as CVE-2023-41993) purely to gain JavaScript-level arbitrary read/write. The exploit immediately pivots into a purchased framework called JSKit, so any future Safari bug only needs to deliver the same primitive.
- Version abstraction & PAC bypasses: JSKit bundles support for a wide range of iOS releases together with multiple, selectable Pointer Authentication Code bypass modules. The framework fingerprints the target build, selects the appropriate PAC bypass logic, and verifies every step (primitive validation, shellcode launch) before progressing.
- Manual Mach-O mapping: JSKit parses Mach-O headers directly from memory, resolves the symbols it needs inside dyld-cached images, and can manually map additional Mach-O payloads without writing them to disk. This keeps the renderer process in-memory only and evades code-signature checks tied to filesystem artifacts.
- Portfolio model: Debug strings such as “exploit number 7” show that the suppliers maintain multiple interchangeable WebKit exploits. Once the JS primitive matches JSKit’s interface, the rest of the chain is unchanged across campaigns.
Kernel bridge: IPC UAF -> code-sign bypass pattern
- Kernel IPC UAF (CVE-2023-41992): The second stage, still running inside the Safari context, triggers a kernel use-after-free in IPC code, re-allocates the freed object from userland, and abuses the dangling pointers to pivot into arbitrary kernel read/write. The stage also reuses PAC bypass material previously computed by JSKit instead of re-deriving it.
- Code-signing bypass (CVE-2023-41991): With kernel R/W available, the exploit patches the trust cache / code-signing structures so unsigned payloads execute as
system. The stage then exposes a lightweight kernel R/W service to later payloads. - Composed pattern: This chain demonstrates a reusable recipe that defenders should expect going forward:
WebKit renderer RCE -> kernel IPC UAF -> kernel arbitrary R/W -> code-sign bypass -> unsigned system stager
PREYHUNTER helper & watcher modules
-
Watcher anti-analysis: Ένα αφιερωμένο watcher binary προφίλάρει τη συσκευή συνεχώς και τερματίζει την kill-chain όταν ανιχνευθεί περιβάλλον έρευνας. Ελέγχει το
security.mac.amfi.developer_mode_status, την παρουσία ενόςdiagnosticdconsole, localesUSήIL, ίχνη jailbreak όπως Cydia, διεργασίες όπωςbash,tcpdump,frida,sshd, ήcheckrain, mobile AV apps (McAfee, AvastMobileSecurity, NortonMobileSecurity), προσαρμοσμένες ρυθμίσεις HTTP proxy, και προσαρμοσμένα root CAs. Αποτυχία σε οποιονδήποτε έλεγχο μπλοκάρει την περαιτέρω παράδοση του payload. -
Helper surveillance hooks: Το component helper επικοινωνεί με άλλα στάδια μέσω του
/tmp/helper.sock, και στη συνέχεια φορτώνει σετ hooks με ονόματα DMHooker και UMHooker. Αυτά τα hooks παίζουν στα VOIP audio paths (οι εγγραφές αποθηκεύονται κάτω από/private/var/tmp/l/voip_%lu_%u_PART.m4a), υλοποιούν έναν system-wide keylogger, λαμβάνουν φωτογραφίες χωρίς UI, και κάνουν hook στο SpringBoard για να καταστείλουν τις ειδοποιήσεις που κανονικά θα προκαλούσαν αυτές οι ενέργειες. Επομένως το helper λειτουργεί ως μια stealthy validation + light-surveillance στρώση πριν εγκατασταθούν βαρύτερα implants όπως το Predator. -
HiddenDot indicator suppression in SpringBoard: Με kernel-level code injection, το Predator κάνει hook στο
SBSensorActivityDataProvider._handleNewDomainData:(το σημείο συγκέντρωσης για την activity των αισθητήρων). Το hook μηδενίζει τον Objective-Cselfpointer (x0), ώστε η κλήση να γίνει[nil _handleNewDomainData:newData], απορρίπτοντας ενημερώσεις κάμερας/μικροφώνου και καταστέλλοντας τόσο την πράσινη όσο και την πορτοκαλί κουκκίδα. -
Mach exception-based hooking flow (DMHooker): Τα hooks υλοποιούνται μέσω
EXC_BREAKPOINT+ exception ports, στη συνέχειαthread_set_stateμεταβάλλει τα registers και η εκτέλεση συνεχίζεται. Ο κωδικός επιστροφής2σημαίνει “continue with modified thread state.” -
PAC-aware redirection for camera access checks: Στο
mediaserverd, ένα pattern-scan (π.χ.memmem) εντοπίζει μια ιδιωτική ρουτίνα κοντά στοFigVideoCaptureSourceCreateWithSourceInfoμέσα στοCMCapture.framework. Το hook επιστρέφει3για να αναδρομολογήσει χρησιμοποιώντας μια pre-signed PAC cached return address, ικανοποιώντας το PAC ενώ παρακάμπτει τον έλεγχο. -
VoIP capture pipeline in
mediaserverd: Κάνει hook ταAudioConverterNewκαιAudioConverterConvertComplexBuffer+52για να πατήσει buffers, να συμπεράνει το sample rate από τα μεγέθη buffers, να μετατρέψει float32 PCM → int16 με NEON, να κάνει downmix από 4-κανάλια σε stereo, και να αποθηκεύσει μέσωExtAudioFileWrite(). Το VoIP module αυτό καθ’ εαυτό δεν καταστέλλει indicators, οπότε οι χειριστές πρέπει να ενεργοποιήσουν το HiddenDot ξεχωριστά.
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
References
- https://www.jamf.com/blog/predator-spyware-ios-recording-indicator-bypass-analysis/
- Google Threat Intelligence – Intellexa zero-day exploits continue
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
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.


