iOS Exploiting

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

iOS Exploit Mitigations

1. Code Signing / Runtime Signature Verification

Introduced early (iPhone OS → iOS) Dies ist eines der grundlegenden Schutzmechanismen: jeglicher ausführbarer Code (Apps, dynamic libraries, JIT-ed code, extensions, frameworks, caches) muss kryptographisch signiert sein durch eine Zertifikatskette, die in Apples Trust verwurzelt ist. Zur Laufzeit prüft das System vor dem Laden eines Binaries in den Speicher (oder bevor Sprünge über bestimmte Grenzen erfolgen) dessen Signatur. Wenn der Code verändert wurde (Bit-Flips, Patches) oder unsigniert ist, schlägt das Laden fehl.

  • Verhindert: die “klassische payload drop + execute”-Phase in Exploit-Ketten; arbitrary code injection; das Modifizieren eines existierenden Binaries, um bösartige Logik einzufügen.
  • Mechanismus-Detail:
  • Der Mach-O loader (und dynamic linker) prüft Code-Pages, Segmente, Entitlements, Team IDs und dass die Signatur den Inhalt der Datei abdeckt.
  • Für Speicherregionen wie JIT Caches oder dynamisch generierten Code erzwingt Apple, dass Pages signiert sind oder über spezielle APIs validiert werden (z. B. mprotect mit Code-Sign-Checks).
  • Die Signatur enthält Entitlements und Identifier; das OS stellt sicher, dass bestimmte APIs oder privilegierte Fähigkeiten spezifische Entitlements erfordern, die nicht gefälscht werden können.
Example Angenommen, ein Exploit erzielt Code-Ausführung in einem Prozess und versucht, Shellcode in einen Heap zu schreiben und dorthin zu springen. Auf iOS müsste diese Page sowohl als executable markiert sein **und** die Code-Signatur-Anforderungen erfüllen. Da der Shellcode nicht mit Apples Zertifikat signiert ist, schlägt der Sprung fehl oder das System verweigert, diese Speicherregion ausführbar zu machen.

2. CoreTrust

Introduced around iOS 14+ era (or gradually in newer devices / later iOS) CoreTrust ist das Subsystem, das die runtime signature validation von Binaries (inklusive System- und User-Binaries) gegen Apples root certificate durchführt anstatt sich auf gecachte userland trust stores zu verlassen.

  • Verhindert: post-install Tampering an Binaries, Jailbreaking-Techniken, die versuchen, System-Bibliotheken oder User-Apps zu tauschen oder zu patchen; das Täuschen des Systems durch Ersetzen vertrauenswürdiger Binaries mit bösartigen Gegenstücken.
  • Mechanismus-Detail:
  • Anstatt einer lokalen Trust-Datenbank oder eines Zertifikat-Caches zu vertrauen, bezieht sich CoreTrust direkt auf Apples Root oder verifiziert Intermediate-Zertifikate in einer sicheren Kette.
  • Es stellt sicher, dass Modifikationen (z. B. im Filesystem) an existierenden Binaries entdeckt und abgewiesen werden.
  • Es verbindet Entitlements, Team IDs, Code-Signing-Flags und andere Metadaten mit dem Binary zur Ladezeit.
Example Ein Jailbreak könnte versuchen, `SpringBoard` oder `libsystem` durch eine gepatchte Version zu ersetzen, um Persistenz zu erlangen. Wenn aber der OS-Loader oder CoreTrust prüft, bemerkt er die Signaturabweichung (oder veränderte Entitlements) und verweigert die Ausführung.

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

Introduced in many OSes earlier; iOS had NX-bit / w^x for a long time DEP erzwingt, dass als schreibbar markierte Pages (für Daten) nicht-executable sind und als ausführbar markierte Pages nicht-schreibbar sind. Man kann nicht einfach Shellcode in Heap- oder Stack-Regionen schreiben und ausführen.

  • Verhindert: direkte Shellcode-Ausführung; klassische buffer-overflow → Sprung zu injiziertem Shellcode.
  • Mechanismus-Detail:
  • Die MMU / Memory-Protection-Flags (via Page Tables) erzwingen die Trennung.
  • Jeglicher Versuch, eine schreibbare Page ausführbar zu machen, löst eine Systemprüfung aus (und ist entweder verboten oder erfordert Code-Sign-Freigaben).
  • In vielen Fällen erfordert das Ausführbar-Machen von Pages das Durchlaufen von OS-APIs, die zusätzliche Einschränkungen oder Prüfungen durchsetzen.
Example Ein Overflow schreibt Shellcode auf den Heap. Der Angreifer versucht `mprotect(heap_addr, size, PROT_EXEC)`, um ihn ausführbar zu machen. Das System verweigert dies jedoch oder validiert, dass die neue Page Code-Sign-Bedingungen erfüllen muss (was der Shellcode nicht kann).

4. Address Space Layout Randomization (ASLR)

Introduced in iOS ~4–5 era (roughly iOS 4–5 timeframe) ASLR randomisiert die Basisadressen wichtiger Speicherregionen: libraries, heap, stack, etc., bei jedem Prozessstart. Gadget-Adressen verschieben sich zwischen Runs.

  • Verhindert: das harte Kodieren von Gadget-Adressen für ROP/JOP; statische Exploit-Ketten; blindes Springen zu bekannten Offsets.
  • Mechanismus-Detail:
  • Jede geladene Library / jedes dynamische Modul wird bei einem randomisierten Offset rebased.
  • Stack- und Heap-Basiszeiger werden (innerhalb gewisser Entropiegrenzen) randomisiert.
  • Manchmal werden auch andere Regionen (z. B. mmap-Allocations) randomisiert.
  • In Kombination mit information-leak Mitigations zwingt es den Angreifer, zuerst eine Adresse oder einen Pointer zu leak-en, um Basisadressen zur Laufzeit zu bestimmen.
Example Eine ROP-Kette erwartet ein Gadget bei `0x….lib + offset`. Da `lib` aber bei jedem Lauf anders relociert wird, schlägt die hartkodierte Kette fehl. Ein Exploit muss zuerst die Basisadresse des Moduls leak-en, bevor er Gadget-Adressen berechnen kann.

5. Kernel Address Space Layout Randomization (KASLR)

Introduced in iOS ~ (iOS 5 / iOS 6 timeframe) Analog zu User-ASLR randomisiert KASLR die Basis der kernel text und anderer Kernel-Strukturen beim Booten.

  • Verhindert: Kernel-Level Exploits, die sich auf fixe Orte von Kernel-Code oder -Daten verlassen; statische Kernel-Exploits.
  • Mechanismus-Detail:
  • Bei jedem Boot wird die Kernel-Basisadresse (innerhalb eines Bereichs) randomisiert.
  • Kernel-Datenstrukturen (wie task_structs, vm_map etc.) können ebenfalls verschoben oder offsettet werden.
  • Angreifer müssen zuerst Kernel-Pointer leak-en oder information-disclosure Vulnerabilities nutzen, um Offsets zu berechnen, bevor sie Kernel-Strukturen oder -Code kapern.
Example Eine lokale Vulnerability zielt darauf ab, einen Kernel-Funktionspointer zu korrupten (z. B. in einer `vtable`) bei `KERN_BASE + offset`. Da `KERN_BASE` unbekannt ist, muss der Angreifer ihn zuerst leak-en (z. B. via eine read-Primitive), bevor er die korrekte Adresse für die Korruption berechnen kann.

6. Kernel Patch Protection (KPP / AMCC)

Introduced in newer iOS / A-series hardware (post around iOS 15–16 era or newer chips) KPP (auch AMCC genannt) überwacht kontinuierlich die Integrität der Kernel-Text-Pages (via Hash oder Checksum). Erkennt es Manipulationen (Patches, Inline-Hooks, Code-Modifikationen) außerhalb erlaubter Fenster, löst es einen Kernel-Panic oder Reboot aus.

  • Verhindert: persistentes Kernel-Patching (Modifizieren von Kernel-Instruktionen), Inline-Hooks, statische Funktionsüberschreibungen.
  • Mechanismus-Detail:
  • Ein Hardware- oder Firmware-Modul überwacht den Kernel-Textbereich.
  • Es re-hasht die Pages periodisch oder bei Bedarf und vergleicht die Werte mit erwarteten Daten.
  • Wenn Abweichungen außerhalb harmloser Update-Fenster auftreten, panict es das Gerät (um persistente bösartige Patches zu vermeiden).
  • Angreifer müssen entweder Detektionsfenster umgehen oder legitime Patch-Pfade verwenden.
Example Ein Exploit versucht, den Prolog einer Kernel-Funktion (z. B. `memcmp`) zu patchen, um Aufrufe abzufangen. KPP bemerkt jedoch, dass der Hash der Code-Page nicht mehr mit dem erwarteten Wert übereinstimmt und löst einen Kernel-Panic aus, wodurch das Gerät abstürzt, bevor der Patch stabil wird.

7. Kernel Text Read‐Only Region (KTRR)

Introduced in modern SoCs (post ~A12 / newer hardware) KTRR ist ein hardware-erzwungener Mechanismus: sobald der Kernel-Text früh während des Boots gesperrt wird, ist er von EL1 (dem Kernel) her schreibgeschützt und verhindert weitere Schreibzugriffe auf Code-Pages.

  • Verhindert: alle Modifikationen am Kernel-Code nach dem Boot (z. B. Patching, In-Place-Code-Injection) auf EL1-Privilege-Level.
  • Mechanismus-Detail:
  • Während des Boots (im secure/bootloader-Stadium) markiert der Memory Controller (oder eine secure Hardware-Einheit) die physikalischen Pages, die Kernel-Text enthalten, als read-only.
  • Selbst wenn ein Exploit volle Kernel-Privilegien erlangt, kann er diese Pages nicht schreiben, um Instruktionen zu patchen.
  • Um sie zu verändern, müsste der Angreifer zuerst die Boot-Chain kompromittieren oder KTRR selbst unterlaufen.
Example Ein Privilege-Escalation-Exploit springt in EL1 und schreibt ein Trampolin in eine Kernel-Funktion (z. B. im `syscall`-Handler). Weil die Pages jedoch durch KTRR als read-only gesperrt sind, schlägt der Schreibversuch fehl (oder löst einen Fault aus), sodass Patches nicht angewendet werden können.

8. Pointer Authentication Codes (PAC)

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

  • PAC ist ein Hardware-Feature, eingeführt in ARMv8.3-A, um Manipulationen an Pointer-Werten (Return-Adressen, Funktionspointer, bestimmte Datenpointers) zu erkennen, indem eine kleine kryptographische Signatur (eine “MAC”) in ungenutzte hohe Bits des Pointers eingebettet wird.
  • Die Signatur (“PAC”) wird über den Pointer-Wert plus einen modifier (einen Kontextwert, z. B. Stack Pointer oder anderes Unterscheidungsmerkmal) berechnet. Dadurch erhält derselbe Pointer-Wert in unterschiedlichen Kontexten eine andere PAC.
  • Beim Gebrauch prüft eine authenticate-Instruktion die PAC. Ist sie gültig, wird die PAC entfernt und der reine Pointer verwendet; ist sie ungültig, wird der Pointer “poisoned” (oder ein Fault ausgelöst).
  • Die Keys, die zur Erzeugung/Validierung von PACs verwendet werden, liegen in privilegierten Registern (EL1, Kernel) und sind aus dem User-Mode nicht direkt lesbar.
  • Weil nicht alle 64 Bits eines Pointers in vielen Systemen genutzt werden (z. B. 48-bit Adressraum), können die oberen Bits “frei” sein und die PAC aufnehmen, ohne die effektive Adresse zu verändern.

Architectural Basis & Key Types

  • ARMv8.3 führt fünf 128-bit Keys ein (jeweils implementiert über zwei 64-bit System-Register) für Pointer Authentication.

  • APIAKey — für instruction pointers (Domain “I”, Key A)

  • APIBKey — zweiter instruction pointer Key (Domain “I”, Key B)

  • APDAKey — für data pointers (Domain “D”, Key A)

  • APDBKey — für data pointers (Domain “D”, Key B)

  • APGAKey — “generic” Key, zum Signieren von non-pointer Daten oder anderen generischen Zwecken

  • Diese Keys werden in privilegierten System-Register gespeichert (nur auf EL1/EL2 etc. zugreifbar), nicht vom User-Mode aus zugänglich.

  • Die PAC wird via einer kryptographischen Funktion berechnet (ARM schlägt QARMA als Algorithmus vor) unter Verwendung von:

  1. Dem Pointer-Wert (kanonischer Teil)
  2. Einem modifier (ein Kontextwert, wie ein Salt)
  3. Dem geheimen Key
  4. Einiger interner Tweak-Logik Wenn die resultierende PAC mit den im oberen Bits des Pointers gespeicherten Werten übereinstimmt, gelingt die Authentifizierung.

Instruction Families

Die Namenskonvention ist: PAC / AUT / XPAC, gefolgt von Domain-Buchstaben.

  • PACxx Instruktionen signieren einen Pointer und fügen eine PAC ein
  • AUTxx Instruktionen authentifizieren + strippen (validieren und die PAC entfernen)
  • XPACxx Instruktionen strippen ohne Validierung

Domains / Suffixe:

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

Es gibt spezialisierte / Alias-Formen:

  • PACIASP ist Kurzform für PACIA X30, SP (sign the link register using SP as modifier)
  • AUTIASP ist AUTIA X30, SP (authenticate link register with SP)
  • Kombinierte Formen wie RETAA, RETAB (authenticate-and-return) oder BLRAA (authenticate & branch) existieren in ARM-Erweiterungen / Compiler-Unterstützung.
  • Auch Zero-Modifier-Varianten: PACIZA / PACIZB, bei denen der modifier implizit null ist, etc.

Modifiers

Das Hauptziel des modifiers ist, die PAC an einen spezifischen Kontext zu binden, sodass derselbe adressierte Wert in verschiedenen Kontexten unterschiedliche PACs erhält. Das ist wie das Hinzufügen eines Salts zu einem Hash.

Daher:

  • Der modifier ist ein Kontextwert (ein anderes Register), der in die PAC-Berechnung mit einfließt. Typische Auswahl: der Stack Pointer (SP), ein Frame Pointer oder eine Objekt-ID.
  • Die Verwendung von SP als modifier ist üblich für Return-Address-Signing: die PAC ist an den spezifischen Stack-Frame gebunden. Versucht man, LR in einem anderen Frame wiederzuverwenden, ändert sich der modifier, sodass die PAC-Validierung fehlschlägt.
  • Derselbe Pointer-Wert, der unter verschiedenen modifiers signiert wurde, ergibt unterschiedliche PACs.
  • Der modifier muss nicht geheim sein, idealerweise ist er aber nicht vom Angreifer kontrollierbar.
  • Für Instruktionen, die Pointer signieren oder verifizieren, wo kein sinnvoller modifier existiert, verwenden manche Formen null oder eine implizite Konstante.

Apple / iOS / XNU Customizations & Observations

  • Apples PAC-Implementierung enthält per-boot diversifiers, sodass Keys oder Tweaks bei jedem Boot wechseln und eine Wiederverwendung über Boots hinweg verhindern.
  • Sie enthalten auch cross-domain Mitigations, sodass PACs, die im User-Mode signiert wurden, nicht einfach im Kernel-Mode wiederverwendet werden können, etc.
  • Auf Apple M1 / Apple Silicon zeigte Reverse Engineering, dass es neun modifier-Typen und Apple-spezifische System-Register für Key-Control gibt.
  • Apple nutzt PAC in vielen Kernel-Subsystemen: Return-Address-Signing, Pointer-Integrität in Kernel-Daten, signierte Thread-Contexts, etc.
  • Google Project Zero demonstrierte, dass man unter einer mächtigen Memory read/write-Primitive im Kernel Kernel-PACs (für A-Keys) auf A12-era Geräten fälschen konnte, aber Apple schloss viele dieser Wege.
  • In Apples System sind manche Keys global für den Kernel, während User-Prozesse per-Process Key-Randomness bekommen können.

PAC Bypasses

  1. Kernel-mode PAC: theoretical vs real bypasses
  • Da Kernel-PAC-Keys und -Logik streng kontrolliert sind (privilegierte Register, Diversifiers, Domain-Isolation), ist das Fälschen beliebiger signierter Kernel-Pointer sehr schwierig.
  • Azads 2020er “iOS Kernel PAC, One Year Later” berichtet, dass er in iOS 12–13 einige partielle Bypässe fand (signing gadgets, reuse of signed states, unprotected indirect branches), aber keinen vollständigen generischen Bypass. bazad.github.io
  • Apples “Dark Magic”-Customizations verengen die ausnutzbaren Flächen weiter (domain switching, per-key enabling bits). i.blackhat.com
  • Es gibt einen bekannten kernel PAC bypass CVE-2023-32424 auf Apple Silicon (M1/M2) gemeldet von Zecao Cai et al. i.blackhat.com
  • Diese Bypässe beruhen jedoch oft auf sehr spezifischen Gadgets oder Implementierungsfehlern; sie sind nicht allgemeingültige Workarounds.

Deshalb gilt Kernel-PAC als hochgradig robust, wenn auch nicht perfekt.

  1. User-mode / runtime PAC bypass techniques

Diese sind häufiger und nutzen Imperfektionen in der Anwendung von PAC im dynamic linking / runtime-Framework. Nachfolgend Klassen mit Beispielen.

2.1 Shared Cache / A key issues

  • Die dyld shared cache ist ein großes vor-verlinktes Blob von System-Frameworks und Libraries. Weil es so weit verbreitet geteilt wird, sind Funktionspointer innerhalb des shared cache bereits signiert und werden von vielen Prozessen benutzt. Angreifer zielen auf diese bereits-signed Pointer als “PAC oracles”.

  • Einige Bypass-Techniken versuchen, A-key signierte Pointer aus dem shared cache zu extrahieren oder wiederzuverwenden und sie in Gadgets zu nutzen.

  • Der Vortrag “No Clicks Required” beschreibt, wie man ein Oracle über die shared cache baut, um relative Adressen zu inferieren und das mit signierten Pointern zu kombinieren, um PAC zu umgehen. saelo.github.io

  • Außerdem wurden Importe von Funktionspointer aus shared libraries in Userspace als unzureichend durch PAC geschützt gefunden, was einem Angreifer erlaubt, Funktionspointer zu erhalten, ohne deren Signatur zu verändern. (Project Zero Bug Entry) bugs.chromium.org

2.2 dlsym(3) / dynamic symbol resolution

  • Ein bekannter Bypass ist, dlsym() aufzurufen, um einen bereits signierten Funktionspointer zu bekommen (mit A-Key, diversifier zero) und diesen dann zu verwenden. Weil dlsym legitim signierte Pointer zurückgibt, umgeht ihre Verwendung das Fordern, PAC zu fälschen.

  • Epsilons Blog beschreibt, wie einige Bypässe dies ausnutzen: dlsym("someSym") liefert einen signierten Pointer und kann für indirekte Aufrufe verwendet werden. blog.epsilon-sec.com

  • Synacktivs “iOS 18.4 — dlsym considered harmful” beschreibt einen Bug: einige mittels dlsym aufgelöste Symbole in iOS 18.4 geben Pointer zurück, die falsch signiert sind (oder fehlerhafte diversifiers haben), was unbeabsichtigte PAC-Bypässe ermöglicht. Synacktiv

  • Die Logik in dyld für dlsym beinhaltet: wenn result->isCode, signieren sie den zurückgegebenen Pointer mit __builtin_ptrauth_sign_unauthenticated(..., key_asia, 0), also mit Kontext null. blog.epsilon-sec.com

Damit ist dlsym ein häufiger Vektor in User-Mode PAC-Bypässen.

2.3 Other DYLD / runtime relocations

  • Der DYLD-Loader und die dynamische Relocation-Logik sind komplex und mappten manchmal temporär Pages als read/write, um Relocations vorzunehmen, und schalteten sie danach wieder auf read-only. Angreifer nutzen diese Zeitfenster aus. Synacktivs Vortrag beschreibt “Operation Triangulation”, einen timing-basierten PAC-Bypass über dynamische Relocations. Synacktiv

  • DYLD-Pages sind jetzt mit SPRR / VM_FLAGS_TPRO geschützt (einige Protections-Flags für dyld). Frühere Versionen hatten schwächere Schutzmaßnahmen. Synacktiv

  • In WebKit-Exploit-Ketten ist der DYLD-Loader oft Ziel für PAC-Bypässe. Die Slides erwähnen, dass viele PAC-Bypässe den DYLD-Loader ins Visier genommen haben (via Relocation, Interposer-Hooks). Synacktiv

2.4 NSPredicate / NSExpression / ObjC / SLOP

  • In Userland-Exploit-Ketten werden Objective-C Runtime-Methoden wie NSPredicate, NSExpression oder NSInvocation verwendet, um Control-Calls zu schmuggeln, ohne offensichtliches Pointer-Fälschen.

  • Auf älteren iOS-Versionen (vor PAC) nutzte ein Exploit fake NSInvocation-Objekte, um arbitrary selectors auf kontrolliertem Speicher aufzurufen. Mit PAC sind Modifikationen nötig. Aber die Technik SLOP (SeLector Oriented Programming) wurde auch unter PAC erweitert. Project Zero

  • Die ursprüngliche SLOP-Technik erlaubte das Verketten von ObjC-Aufrufen durch das Erzeugen gefälschter invocations; der Bypass beruht darauf, dass ISA- oder Selector-Pointer manchmal nicht vollständig PAC-geschützt sind. Project Zero

  • In Umgebungen, in denen Pointer Authentication nur partiell angewendet wird, sind Methoden / Selector- / Target-Pointer nicht immer PAC-geschützt, was Raum für Bypässe lässt.

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>
Ein buffer overflow überschreibt eine return address auf dem stack. Der attacker schreibt die target gadget address, kann aber den korrekten PAC nicht berechnen. Wenn die Funktion zurückkehrt, löst die CPU‑Anweisung `AUTIA` einen Fault aus, weil der PAC nicht übereinstimmt. Die Kette schlägt fehl.
Project Zero’s Analyse des A12 (iPhone XS) zeigte, wie Apple PAC verwendet wird und welche Methoden es zum Fälschen von PACs gibt, wenn ein attacker ein memory read/write primitive besitzt.
</details>


### 9. **Branch Target Identification (BTI)**
**Eingeführt mit ARMv8.5 (neuere Hardware)**
BTI ist ein Hardware‑Feature, das **indirect branch targets** überprüft: Beim Ausführen von `blr` oder indirekten calls/jumps muss das Ziel mit einem **BTI landing pad** (`BTI j` oder `BTI c`) beginnen. Das Springen in gadget addresses, die kein Landing Pad haben, löst eine Exception aus.

LLVM’s Implementation beschreibt drei Varianten von BTI‑Instruktionen und wie sie auf Branch‑Typen abgebildet werden.

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

- In Code, die mit branch target enforcement kompiliert wurde, fügen Compiler eine BTI‑Instruktion (C, J oder JC) an jedem gültigen indirect‑branch target ein (Funktionsanfänge oder Blocks, die per Jump erreichbar sind), sodass indirekte Branches nur zu diesen Stellen erfolgreich sind.
- **Direct branches / calls** (d.h. fixe Adressen `B`, `BL`) werden **nicht** von BTI eingeschränkt. Die Annahme ist, dass Code‑Pages vertrauenswürdig sind und ein Angreifer sie nicht verändern kann (daher sind direkte Branches sicher).
- Außerdem sind **RET / return**‑Instruktionen im Allgemeinen nicht durch BTI eingeschränkt, da return‑Adressen via PAC oder return signing Mechanismen geschützt sind.

#### Mechanism and enforcement

- Wenn die CPU einen **indirect branch (BLR / BR)** dekodiert, der sich in einer als „guarded / BTI‑enabled“ markierten Page befindet, prüft sie, ob die erste Instruktion am Ziel ein gültiges BTI (C, J oder JC, je nach erlaubt) ist. Falls nicht, tritt eine **Branch Target Exception** auf.
- Die BTI‑Instruktionscodierung wurde so entworfen, dass sie Opcodes wiederverwendet, die zuvor für NOPs reserviert waren (in älteren ARM‑Versionen). Daher bleiben BTI‑aktivierte Binaries abwärtskompatibel: Auf Hardware ohne BTI‑Support wirken diese Instruktionen wie NOPs.
- Die Compiler‑Passes, die BTIs einfügen, tun dies nur dort, wo es nötig ist: Funktionen, die indirekt aufgerufen werden können, oder Basic Blocks, die per Jump erreichbar sind.
- Einige Patches und LLVM‑Code zeigen, dass BTI nicht für *alle* Basic Blocks eingefügt wird — nur für jene, die potenzielle branch targets sind (z. B. aus switch / jump tables).

#### BTI + PAC synergy

PAC schützt den Pointer‑Wert (die Quelle) — stellt sicher, dass die Kette von indirekten Aufrufen / Returns nicht manipuliert wurde.

BTI stellt sicher, dass selbst ein gültiger Pointer nur auf korrekt markierte Entry‑Points zeigen darf.

Kombiniert benötigt ein Angreifer sowohl einen gültigen Pointer mit korrektem PAC als auch ein Ziel, das dort ein BTI hat. Dadurch wird es deutlich schwieriger, brauchbare Exploit‑Gadgets zusammenzustellen.

#### Example


<details>
<summary>Example</summary>
Ein Exploit versucht, auf ein gadget bei `0xABCDEF` zu pivotieren, das nicht mit `BTI c` beginnt. Die CPU prüft beim Ausführen von `blr x0` das Ziel und löst einen Fault aus, weil die Instruktionsausrichtung kein gültiges Landing Pad enthält. Viele Gadgets werden dadurch unbrauchbar, sofern sie nicht mit einem BTI‑Prefix versehen sind.
</details>


### 10. **Privileged Access Never (PAN) & Privileged Execute Never (PXN)**
**Eingeführt in neueren ARMv8‑Extensions / iOS‑Support (für gehärteten kernel)**

#### PAN (Privileged Access Never)

- **PAN** ist ein Feature, eingeführt in **ARMv8.1‑A**, das verhindert, dass **privileged code** (EL1 oder EL2) **lesen oder schreiben** auf Speicher zugreift, der als **user‑accessible (EL0)** markiert ist, sofern PAN nicht explizit deaktiviert wird.
- Die Idee: Selbst wenn der kernel manipuliert oder kompromittiert wird, kann er nicht beliebig user‑space Pointer dereferenzieren, ohne zuvor PAN zu *clearen*, wodurch das Risiko von **ret2usr**‑artigen Exploits oder Missbrauch von user‑kontrollierten Buffern reduziert wird.
- Wenn PAN aktiviert ist (PSTATE.PAN = 1), führt jede privilegierte load/store‑Instruktion, die auf eine virtuelle Adresse zugreift, die „accessible at EL0“ ist, zu einem **permission fault**.
- Der kernel, wenn er legitimerweise auf user‑space memory zugreifen muss (z. B. Daten von/zu user Buffern kopieren), muss PAN **vorübergehend deaktivieren** (oder „unprivileged load/store“ Instruktionen verwenden), um diesen Zugriff zu erlauben.
- In Linux auf ARM64 wurde PAN etwa 2015 eingeführt: Kernel‑Patches fügten Feature‑Erkennung hinzu und ersetzten `get_user` / `put_user` etc. durch Varianten, die PAN rund um user memory accesses clearen.

**Wichtige Nuance / Limitierung / Bug**
- Wie von Siguza und anderen bemerkt, führt ein Spezifikationsfehler (oder eine mehrdeutige Verhalten) in ARMs Design dazu, dass **execute‑only user mappings** (`--x`) PAN **nicht auslösen** können. Anders gesagt: Wenn eine user‑Seite ausführbar, aber ohne Lese‑Permission ist, kann der kernel‑Leseversuch PAN möglicherweise umgehen, weil die Architektur „accessible at EL0“ als lesbar definiert, nicht nur ausführbar. Das führt in manchen Implementierungen zu einem PAN‑Bypass.
- Deshalb, falls iOS / XNU execute‑only user pages erlauben (wie in einigen JIT oder code‑cache Setups), könnte der kernel trotz aktiviertem PAN versehentlich von diesen Seiten lesen. Das ist ein bekanntes, subtiles, in bestimmten ARMv8+ Systemen ausnutzbares Problem.

#### PXN (Privileged eXecute Never)

- **PXN** ist ein Page‑Table‑Flag (in den PTEs, leaf oder block entries), das angibt, dass die Page **nicht ausführbar im privileged mode** ist (d.h. wenn EL1 sie ausführt).
- PXN verhindert, dass der kernel (oder jeglicher privileged code) in user‑space Pages springt oder Instruktionen daraus ausführt, selbst wenn die Kontrolle umgeleitet wird. Effektiv verhindert es kernel‑level Control‑Flow‑Redirections in user memory.
- Kombiniert mit PAN stellt das sicher:
1. Kernel kann standardmäßig nicht user‑space Daten lesen oder schreiben (PAN)
2. Kernel kann nicht user‑space Code ausführen (PXN)
- Im ARMv8 Page‑Table‑Format haben leaf entries ein `PXN` Bit (sowie `UXN` für unprivileged execute‑never) in ihren Attributbits.

Auch wenn der kernel einen korrupten Funktionszeiger hat, der auf user memory zeigt und dorthin zu springen versucht, würde das PXN‑Bit einen Fault verursachen.

#### Memory‑permission model & wie PAN und PXN zu Page‑Table Bits abgebildet werden

Um zu verstehen, wie PAN / PXN funktionieren, muss man sehen, wie ARMs Translation‑ und Permission‑Modell arbeitet (vereinfacht):

- Jede Page‑ oder Block‑Entry hat Attributfelder, einschließlich **AP[2:1]** für Access‑Permissions (read/write, privileged vs unprivileged) und **UXN / PXN** Bits für execute‑never Einschränkungen.
- Wenn PSTATE.PAN = 1 (aktiviert), erzwingt die Hardware geänderte Semantiken: privilegierte Zugriffe auf Seiten, die als „accessible by EL0“ markiert sind (d.h. user‑accessible), werden disallowed (Fault).
- Wegen des erwähnten Bugs könnten Seiten, die nur ausführbar markiert sind (keine Leseberechtigung), unter bestimmten Implementierungen nicht als „accessible by EL0“ zählen und damit PAN umgehen.
- Wenn eine Page das PXN‑Bit gesetzt hat, ist selbst bei Instruktionsfetches aus höherer Privilegstufe die Ausführung verboten.

#### Kernel‑Nutzung von PAN / PXN in einem gehärteten OS (z.B. iOS / XNU)

In einem gehärteten kernel‑Design (wie Apple es verwenden könnte):

- Der kernel aktiviert PAN standardmäßig (so dass privileged code eingeschränkt ist).
- In Pfaden, die legitimerweise user‑Buffer lesen oder schreiben müssen (z. B. syscall Buffer‑Copy, I/O, read/write user pointer), deaktiviert der kernel PAN vorübergehend oder verwendet spezielle Instruktionen, um den Zugriff zu erlauben.
- Nach Abschluss des user‑Datenzugriffs muss PAN wieder aktiviert werden.
- PXN wird über Page‑Tables durchgesetzt: user‑Pages haben PXN = 1 (sodass kernel sie nicht ausführen kann), kernel‑Pages haben PXN nicht gesetzt (sodass kernel Code ausführen kann).
- Der kernel muss sicherstellen, dass keine Code‑Pfade zur Execution in user memory Regionen führen (das würde PXN umgehen) — Exploit‑Ketten, die auf „jump into user‑controlled shellcode“ setzen, werden dadurch blockiert.

Aufgrund des beschriebenen PAN‑Bypass durch execute‑only Pages könnte ein reales System Apple dazu bringen, execute‑only user Pages zu deaktivieren oder die Spezifikationsschwäche anderweitig zu patchen.

#### Attack surfaces, bypasses, and mitigations

- **PAN bypass via execute‑only pages**: wie besprochen erlaubt die Spezifikation eine Lücke: user pages mit execute‑only (ohne read perm) könnten unter bestimmten Implementierungen nicht als „accessible at EL0“ zählen, sodass PAN kernel Reads von solchen Pages nicht blockiert. Das gibt einem Angreifer einen ungewöhnlichen Pfad, Daten über „execute‑only“ Abschnitte einzuschleusen.
- **Temporal window exploit**: wenn der kernel PAN für ein größeres Zeitfenster deaktiviert als nötig, könnte ein Race oder ein bösartiger Pfad dieses Fenster ausnutzen, um ungewollte user memory Zugriffe durchzuführen.
- **Forgotten re‑enable**: werden Pfade vergessen, PAN wieder zu aktivieren, können nachfolgende kernel‑Operationen fälschlicherweise user memory zugreifen.
- **Misconfiguration of PXN**: wenn Page‑Tables PXN auf user Pages nicht setzen oder user code Pages falsch mappen, kann der kernel dazu gebracht werden, user‑space Code auszuführen.
- **Speculation / side‑channels**: ähnlich zu spekulativen Bypasses können mikroarchitektonische Seiteneffekte transient PAN / PXN Checks verletzen (obwohl solche Angriffe stark von CPU‑Design abhängig sind).
- **Complex interactions**: In fortgeschrittenen Features (z. B. JIT, shared memory, just‑in‑time code regions) benötigt der kernel feingranulare Kontrolle, um bestimmte memory accesses oder Ausführung in user‑gemappten Regionen zu erlauben; diese sicher unter PAN/PXN zu designen ist nicht trivial.

#### Example

<details>
<summary>Code Example</summary>
Here are illustrative pseudo-assembly sequences showing enabling/disabling PAN around user memory access, and how a fault might occur.

// 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

Wenn der Kernel PXN auf dieser user page **nicht** gesetzt hätte, könnte der branch erfolgreich sein — was unsicher wäre.

Wenn der Kernel vergisst, PAN nach dem Zugriff auf user memory wieder zu aktivieren, entsteht ein Zeitfenster, in dem weitere Kernel-Logik versehentlich beliebiges user memory lesen oder schreiben könnte.

Wenn der user pointer in eine execute-only page zeigt (user page mit nur execute-Berechtigung, kein read/write), könnte unter dem PAN-Spec-Bug `ldr W2, [X1]` **nicht** faulten, selbst bei aktiviertem PAN, was je nach Implementierung einen Bypass-Exploit ermöglichen würde.

</details>

<details>
<summary>Example</summary>
A kernel vulnerability tries to take a user-provided function pointer and call it in kernel context (i.e. `call user_buffer`). Under PAN/PXN, that operation is disallowed or faults.
</details>

---

### 11. **Top Byte Ignore (TBI) / Pointer Tagging**
**Introduced in ARMv8.5 / newer (or optional extension)**
TBI means the top byte (most-significant byte) of a 64-bit pointer is ignored by address translation. This lets OS or hardware embed **tag bits** in the pointer’s top byte without affecting the actual address.

- TBI stands for **Top Byte Ignore** (sometimes called *Address Tagging*). It is a hardware feature (available in many ARMv8+ implementations) that **ignores the top 8 bits** (bits 63:56) of a 64-bit pointer when performing **address translation / load/store / instruction fetch**.
- In effect, the CPU treats a pointer `0xTTxxxx_xxxx_xxxx` (where `TT` = top byte) as `0x00xxxx_xxxx_xxxx` for the purposes of address translation, ignoring (masking off) the top byte. The top byte can be used by software to store **metadata / tag bits**.
- This gives software “free” in-band space to embed a byte of tag in each pointer without altering which memory location it refers to.
- The architecture ensures that loads, stores, and instruction fetch treat the pointer with its top byte masked (i.e. tag stripped off) before performing the actual memory access.

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

#### Why TBI: Use cases and motivation

- **Pointer tagging / metadata**: You can store extra metadata (e.g. object type, version, bounds, integrity tags) in that top byte. When you later use the pointer, the tag is ignored at hardware level, so you don’t need to strip manually for the memory access.
- **Memory tagging / MTE (Memory Tagging Extension)**: TBI is the base hardware mechanism that MTE builds on. In ARMv8.5, the **Memory Tagging Extension** uses bits 59:56 of the pointer as a **logical tag** and checks it against an **allocation tag** stored in memory.
- **Enhanced security & integrity**: By combining TBI with pointer authentication (PAC) or runtime checks, you can force not just the pointer value but also the tag to be correct. An attacker overwriting a pointer without the correct tag will produce a mismatched tag.
- **Compatibility**: Because TBI is optional and tag bits are ignored by hardware, existing untagged code continues to operate normally. The tag bits effectively become “don’t care” bits for legacy code.

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

---

### 12. **Page Protection Layer (PPL)**
**Introduced in late iOS / modern hardware (iOS ~17 / Apple silicon / high-end models)** (some reports show PPL circa macOS / Apple silicon, but Apple is bringing analogous protections to iOS)

- PPL is designed as an **intra-kernel protection boundary**: even if the kernel (EL1) is compromised and has read/write capabilities, **it should not be able to freely modify** certain **sensitive pages** (especially page tables, code-signing metadata, kernel code pages, entitlements, trust caches, etc.).
- It effectively creates a **“kernel within the kernel”** — a smaller trusted component (PPL) with **elevated privileges** that alone can modify protected pages. Other kernel code must call into PPL routines to effect changes.
- This reduces the attack surface for kernel exploits: even with full arbitrary R/W/execute in kernel mode, exploit code must also somehow get into the PPL domain (or bypass PPL) to modify critical structures.
- On newer Apple silicon (A15+ / M2+), Apple is transitioning to **SPTM (Secure Page Table Monitor)**, which in many cases replaces PPL for page-table protection on those platforms.

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

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

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

#### Gate / Entry & Exit

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

#### Protected page categories

The pages that PPL typically protects include:

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

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


#### Known Bypasses & Vulnerabilities

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

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

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

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

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

2. **Other potential hazards & constraints**

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



#### Example
<details>
<summary>Code Example</summary>
Here’s a simplified pseudocode / logic showing how a kernel might call into PPL to modify protected pages:
```c
// In kernel (outside PPL domain)
function kernel_modify_pptable(pt_addr, new_entry) {
// validate arguments, etc.
return ppl_call_modify(pt_addr, new_entry)  // call PPL wrapper
}

// In PPL (trusted domain)
function ppl_call_modify(pt_addr, new_entry) {
// temporarily enable write access to protected pages (via APRR adjustments)
aprr_set_index_for_write(PPL_INDEX)
// perform the modification
*pt_addr = new_entry
// restore permissions (make pages read-only again)
aprr_restore_default()
return success
}

// If kernel code outside PPL does:
*pt_addr = new_entry  // a direct write
// It will fault because APRR mapping for non-PPL domain disallows write to that page

Der Kernel kann viele normale Operationen ausführen, aber nur über die ppl_call_*-Routinen kann er geschützte Mappings ändern oder Code patchen.

Beispiel Ein kernel-Exploit versucht, die entitlement table zu überschreiben oder die code-sign-Enforcement zu deaktivieren, indem er ein kernel signature blob modifiziert. Da diese Seite PPL-protected ist, wird der Schreibzugriff blockiert, es sei denn, er erfolgt über das PPL-Interface. Selbst bei kernel code execution kann man also code-sign-Beschränkungen nicht umgehen oder credential-Daten beliebig ändern. Auf iOS 17+ verwenden bestimmte Geräte SPTM, um PPL-managed pages weiter zu isolieren.

PPL → SPTM / Ersatz / Zukunft

  • Auf Apples modernen SoCs (A15 oder neuer, M2 oder neuer) unterstützt Apple SPTM (Secure Page Table Monitor), das PPL für den Schutz von Seitentabellen ersetzt.
  • Apple stellt in der Dokumentation fest: “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.”
  • Die SPTM-Architektur verlagert wahrscheinlich mehr Policy-Durchsetzung in einen höher privilegierten Monitor außerhalb der Kernel-Kontrolle und reduziert so die Vertrauensgrenze weiter.

MTE | EMTE | MIE

Hier eine übergeordnete Beschreibung, wie EMTE im Rahmen von Apples MIE-Setup funktioniert:

  1. Tag assignment
  • Wenn Speicher zugewiesen wird (z. B. im Kernel oder im user space über secure allocators), wird dem Block ein secret tag zugewiesen.
  • Der an den User oder Kernel zurückgegebene Pointer enthält dieses Tag in seinen oberen Bits (unter Verwendung von TBI / top byte ignore Mechanismen).
  1. Tag checking on access
  • Wann immer ein Load oder Store mit einem Pointer ausgeführt wird, prüft die Hardware, ob das Tag des Pointers mit dem Tag des Speicherblocks (allocation tag) übereinstimmt. Bei Nichtübereinstimmung tritt sofort ein Fault auf (da synchron).
  • Weil es synchron ist, gibt es kein Fenster für „verzögerte Erkennung“.
  1. Retagging on free / reuse
  • Wenn Speicher freigegeben wird, ändert der Allocator das Tag des Blocks (so dass ältere Pointer mit alten Tags nicht mehr übereinstimmen).
  • Ein use-after-free Pointer hätte daher ein veraltetes Tag und würde bei Zugriff nicht übereinstimmen.
  1. Neighbor-tag differentiation to catch overflows
  • Benachbarte Allokationen erhalten unterschiedliche Tags. Wenn ein buffer overflow in den Speicher des Nachbarn überläuft, verursacht der Tag-Mismatch einen Fault.
  • Das ist besonders effektiv, um kleine Overflows zu erkennen, die eine Grenze überschreiten.
  1. Tag confidentiality enforcement
  • Apple muss verhindern, dass Tag-Werte leaked werden (denn wenn ein Angreifer das Tag kennt, könnte er Pointer mit korrekten Tags erstellen).
  • Sie fügen Protektionen hinzu (microarchitectural / speculative controls), um side-channel leakage von Tag-Bits zu vermeiden.
  1. Kernel and user-space integration
  • Apple verwendet EMTE nicht nur im user-space, sondern auch in Kernel / OS-kritischen Komponenten (um den Kernel gegen memory corruption zu schützen).
  • Hardware/OS stellen sicher, dass die Tag-Regeln gelten, selbst wenn der Kernel im Auftrag des user space ausgeführt wird.
Beispiel ``` 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>

#### Einschränkungen & Herausforderungen

- **Intrablock overflows**: Wenn ein Overflow innerhalb derselben Allocation verbleibt (überschreitet die Grenze nicht) und das tag gleich bleibt, fängt ein tag mismatch das nicht ab.
- **Tag width limitation**: Es stehen nur wenige Bits (z. B. 4 bits, oder eine kleine Domain) für Tags zur Verfügung — begrenzter Namespace.
- **Side-channel leaks**: Falls Tag-Bits geleakt werden können (via cache / speculative execution), kann ein Angreifer gültige Tags lernen und umgehen. Apples Tag Confidentiality Enforcement soll dem entgegenwirken.
- **Performance overhead**: Tag-Checks bei jedem Load/Store verursachen Kosten; Apple muss die Hardware optimieren, um den Overhead gering zu halten.
- **Compatibility & fallback**: Auf älterer Hardware oder Teilen, die EMTE nicht unterstützen, muss ein Fallback existieren. Apple behauptet, MIE sei nur auf Geräten mit Support aktiviert.
- **Complex allocator logic**: Der Allocator muss Tags verwalten, retaggen, Grenzen ausrichten und Kollisionen vermeiden. Bugs in der Allocator-Logik könnten neue Verwundbarkeiten einführen.
- **Mixed memory / hybrid areas**: Einige Speicherbereiche können untagged bleiben (legacy), was die Interoperabilität erschwert.
- **Speculative / transient attacks**: Wie bei vielen mikroarchitektonischen Protektionen könnten speculative Ausführungen oder micro-op Fusions Checks transient umgehen oder Tag-Bits leaken.
- **Limited to supported regions**: Apple könnte EMTE nur in selektiven, hochriskanten Bereichen (Kernel, security-kritische Subsysteme) durchsetzen, nicht flächendeckend.



---

## Wichtige Verbesserungen / Unterschiede im Vergleich zu standard MTE

Hier sind die Verbesserungen und Änderungen, die Apple hervorhebt:

| Feature | Original MTE | EMTE (Apple’s enhanced) / MIE |
|---|---|---|
| **Check mode** | Supports synchronous and asynchronous modes. In async, tag mismatches are reported later (delayed) | Apple besteht standardmäßig auf **synchronous mode** — tag mismatches werden sofort erkannt, keine Verzögerungs-/Race-Fenster erlaubt. |
| **Coverage of non-tagged memory** | Accesses to non-tagged memory (e.g. globals) may bypass checks in some implementations | EMTE verlangt, dass Zugriffe von einem tagged Bereich auf non-tagged memory ebenfalls Kenntnisse über das tag validieren, was das Umgehen durch Mixing von Allocations erschwert. |
| **Tag confidentiality / secrecy** | Tags might be observable or leaked via side channels | Apple fügt **Tag Confidentiality Enforcement** hinzu, das versucht, das Leaken von Tag-Werten zu verhindern (via speculative side-channels etc.). |
| **Allocator integration & retagging** | MTE leaves much of allocator logic to software | Apples secure typed allocators (kalloc_type, xzone malloc, etc.) integrieren sich mit EMTE: Beim Alloc/Free werden Tags fein granular verwaltet. |
| **Always-on by default** | In many platforms, MTE is optional or off by default | Apple aktiviert EMTE / MIE standardmäßig auf unterstützter Hardware (z. B. iPhone 17 / A19) für Kernel und viele User-Prozesse. |

Da Apple sowohl Hardware als auch Software-Stack kontrolliert, kann es EMTE streng durchsetzen, Performance-Probleme vermeiden und Side-Channel-Lücken schließen.

---

## Wie EMTE in der Praxis arbeitet (Apple / MIE)

Hier eine höherstufige Beschreibung, wie EMTE unter Apples MIE-Setup arbeitet:

1. **Tag assignment**
- Wenn Speicher alloziert wird (z. B. im Kernel oder User-Space via secure allocators), wird diesem Block ein **secret tag** zugewiesen.
- Der zurückgegebene Pointer an User oder Kernel enthält dieses Tag in seinen High-Bits (unter Verwendung von TBI / top byte ignore mechanisms).

2. **Tag checking on access**
- Wann immer ein Load oder Store mit einem Pointer ausgeführt wird, prüft die Hardware, dass das Pointer-Tag mit dem Tag des Speicherblocks (allocation tag) übereinstimmt. Bei Mismatch wird sofort ein Fault ausgelöst (da synchronous).
- Weil es synchronous ist, gibt es kein "delayed detection" Window.

3. **Retagging on free / reuse**
- Wenn Speicher freigegeben wird, ändert der Allocator den Tag des Blocks (ältere Pointer mit alten Tags stimmen dann nicht mehr überein).
- Ein use-after-free Pointer hätte daher ein veraltetes Tag und würde bei Zugriffen mismatchen.

4. **Neighbor-tag differentiation to catch overflows**
- Benachbarte Allocations erhalten unterschiedliche Tags. Wenn ein Buffer Overflow in den Nachbar-Speicher schreibt, führt ein tag mismatch zu einem Fault.
- Das ist besonders effektiv beim Erkennen kleiner Overflows, die Grenzen überschreiten.

5. **Tag confidentiality enforcement**
- Apple muss verhindern, dass Tag-Werte geleakt werden (denn wenn ein Angreifer das Tag kennt, könnte er Pointer mit korrekten Tags erzeugen).
- Sie implementieren Schutzmaßnahmen (mikroarchitektonisch / speculative Controls), um Side-Channel-Leaks von Tag-Bits zu vermeiden.

6. **Kernel and user-space integration**
- Apple nutzt EMTE nicht nur im User-Space, sondern auch im Kernel / in OS-kritischen Komponenten (um den Kernel gegen memory corruption zu schützen).
- Die Hardware/OS stellen sicher, dass Tag-Regeln auch gelten, wenn der Kernel im Auftrag von User-Space ausgeführt wird.

Weil EMTE in MIE eingebettet ist, verwendet Apple EMTE im synchronous mode über wichtige Angriffsflächen hinweg, nicht als opt-in oder Debugging-Mode.



---

## Exception-Handling in XNU

Wenn eine **Exception** auftritt (z. B. `EXC_BAD_ACCESS`, `EXC_BAD_INSTRUCTION`, `EXC_CRASH`, `EXC_ARM_PAC`, etc.), ist die **Mach layer** des XNU-Kernels dafür verantwortlich, sie abzufangen, bevor sie zu einem UNIX-ähnlichen **Signal** (wie `SIGSEGV`, `SIGBUS`, `SIGILL`, ...) wird.

Dieser Prozess umfasst mehrere Ebenen der Exception-Propagation und -Behandlung, bevor er den User-Space erreicht oder in ein BSD-Signal umgewandelt wird.


### Exception Flow (High-Level)

1.  **CPU triggers a synchronous exception** (z. B. ungültiger Pointer-Dereferenz, PAC-Failure, illegal instruction, etc.).

2.  **Low-level trap handler** läuft (`trap.c`, `exception.c` im XNU-Source).

3.  Der Trap-Handler ruft **`exception_triage()`** auf, den Kern der Mach-Exception-Behandlung.

4.  `exception_triage()` entscheidet, wie die Exception geroutet wird:

-   Zuerst an das **thread's exception port**.

-   Dann an das **task's exception port**.

-   Dann an das **host's exception port** (oft `launchd` oder `ReportCrash`).

Wenn keiner dieser Ports die Exception behandelt, kann der Kernel:

-   **Sie in ein BSD-Signal umwandeln** (für User-Space-Prozesse).

-   **Panic** auslösen (für Kernel-Space-Exceptions).


### Kernfunktion: `exception_triage()`

Die Funktion `exception_triage()` leitet Mach-Exceptions die Kette möglicher Handler hinauf, bis einer sie behandelt oder bis sie schließlich fatal ist. Sie ist definiert in `osfmk/kern/exception.c`.
```c
void exception_triage(exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt);

Typischer Ablauf:

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

Wenn alle fehlschlagen → wird von bsd_exception() behandelt → in ein Signal wie SIGSEGV übersetzt.

Exception-Ports

Jedes Mach-Objekt (thread, task, host) kann Exception-Ports registrieren, an die Exception-Nachrichten gesendet werden.

Sie werden durch die API definiert:

task_set_exception_ports()
thread_set_exception_ports()
host_set_exception_ports()

Jeder exception port hat:

  • Eine Maske (welche Exceptions er empfangen möchte)
  • Einen port name (Mach-Port, der Nachrichten empfängt)
  • Ein behavior (wie der Kernel die Nachricht sendet)
  • Einen flavor (welchen thread state er einschließt)

Debuggers and Exception Handling

Ein debugger (z. B. LLDB) setzt einen exception port auf die Ziel-task oder den thread, üblicherweise mittels task_set_exception_ports().

Wenn eine Exception auftritt:

  • Die Mach-Nachricht wird an den debugger-Prozess gesendet.
  • Der debugger kann entscheiden, die Exception zu handlen (resume, Register ändern, Instruction überspringen) oder nicht zu handlen.
  • Wenn der debugger sie nicht handlet, propagiert die Exception zur nächsten Ebene (task → host).

Flow of EXC_BAD_ACCESS

  1. Thread dereferenziert einen ungültigen Pointer → CPU löst Data Abort aus.

  2. Kernel trap handler ruft exception_triage(EXC_BAD_ACCESS, ...) auf.

  3. Nachricht wird gesendet an:

  • Thread port → (debugger kann breakpoint abfangen).

  • Wenn debugger ignoriert → Task port → (process-level handler).

  • Wenn ignoriert → Host port (meist ReportCrash).

  1. Wenn niemand handlet → bsd_exception() übersetzt das in SIGSEGV.

PAC Exceptions

Wenn Pointer Authentication (PAC) fehlschlägt (Signature mismatch), wird eine spezielle Mach exception ausgelöst:

  • EXC_ARM_PAC (Typ)
  • Codes können Details enthalten (z. B. key type, pointer type).

Wenn das Binary das Flag TFRO_PAC_EXC_FATAL gesetzt hat, behandelt der Kernel PAC-Fehler als fatal und umgeht die debugger-Interzeption. Das verhindert, dass Angreifer Debugger benutzen, um PAC-Checks zu umgehen, und ist für platform binaries aktiviert.

Software Breakpoints

Ein Software-breakpoint (int3 auf x86, brk auf ARM64) wird durch absichtliches Herbeiführen eines Faults implementiert.
Der debugger fängt das über den exception port ab:

  • Modifiziert instruction pointer oder Memory.
  • Stellt die originale Instruction wieder her.
  • Setzt die Ausführung fort.

Dieser Mechanismus erlaubt es auch, eine PAC-Exception “abzufangen” — es sei denn TFRO_PAC_EXC_FATAL ist gesetzt, dann erreicht sie nie den debugger.

Conversion to BSD Signals

Wenn kein Handler die Exception akzeptiert:

  • Kernel ruft task_exception_notify() → bsd_exception() auf.

  • Das mappt Mach-Exceptions auf Signale:

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 → Kern von exception_triage(), exception_deliver_*().

  • bsd/kern/kern_sig.c → Signal-Delivery-Logik.

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

  • osfmk/mach/exc.h → Exception-Codes und Strukturen.

  • osfmk/kern/task.c → Setup von Task exception ports.


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

Der Kernel verwendete einen zone allocator (kalloc), aufgeteilt in feste “zones” nach Größe. Jede zone speicherte nur Allocations einer einzelnen size class.

Aus dem Screenshot:

Zone NameElement SizeBeispielverwendung
default.kalloc.1616 bytesSehr kleine Kernel-Strukturen, Pointer.
default.kalloc.3232 bytesKleine Strukturen, Objekt-Header.
default.kalloc.6464 bytesIPC messages, winzige Kernel-Buffer.
default.kalloc.128128 bytesMittelgroße Objekte wie Teile von OSObject.
default.kalloc.12801280 bytesGroße Strukturen, IOSurface/graphics-Metadaten.

Funktionsweise:

  • Jede Allocation wird auf die nächsthöhere zone-Größe aufgerundet. (z. B. landet eine 50-Byte-Anfrage in der kalloc.64-zone).
  • Speicher in jeder zone wurde in einer freelist gehalten — von Kernel freigegebene Chunks gingen in diese zone zurück.
  • Wenn du einen 64-Byte-Buffer overflowst, überschreibst du das nächste Objekt in derselben zone.

Deshalb war heap spraying / feng shui so effektiv: du konntest Nachbarn vorhersagen, indem du Allocations derselben size class gesprayed hast.

The freelist

Innerhalb jeder kalloc-zone wurden freed objects nicht direkt an das System zurückgegeben — sie kamen in eine freelist, eine verkettete Liste verfügbarer Chunks.

  • Wenn ein Chunk freigegeben wurde, schrieb der Kernel einen Pointer an den Anfang dieses Chunks → die Adresse des nächsten freien Chunks in derselben zone.

  • Die zone hielt einen HEAD-Pointer auf das erste freie Chunk.

  • Allocation nutzte immer den aktuellen HEAD:

  1. Pop HEAD (gibt diesen Speicher an den Caller zurück).

  2. Update HEAD = HEAD->next (gespeichert im Header des freed Chunks).

  • Freeing pusht Chunks zurück:

  • freed_chunk->next = HEAD

  • HEAD = freed_chunk

Die freelist war also einfach eine verkettete Liste, aufgebaut im freigegebenen Speicher selbst.

Normaler Zustand:

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)

Ausnutzen der freelist

Weil die ersten 8 Bytes eines free chunk = freelist pointer, könnte ein Angreifer diesen korrumpieren:

  1. Heap overflow in einen angrenzenden freed chunk → überschreibe dessen “next” pointer.

  2. Use-after-free: Schreibe in ein freed object → überschreibe dessen “next” pointer.

Dann, bei der nächsten Allocation dieser Größe:

  • Der allocator poppt das korrumpierte chunk.
  • Der allocator folgt dem vom Angreifer bereitgestellten “next” pointer.
  • Gibt einen Pointer auf beliebigen Speicher zurück, wodurch fake object primitives oder targeted overwrite möglich werden.

Visuelles Beispiel für 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 machte Exploitation vor dem Hardening sehr effektiv: predictable neighbors from heap sprays, raw pointer freelist links und keine Typtrennung erlaubten es Angreifern, UAF/overflow-Bugs in die willkürliche Kontrolle des Kernel-Speichers zu eskalieren.

Heap Grooming / Feng Shui

Das Ziel von heap grooming ist, das heap layout so zu formen, dass, wenn ein Angreifer einen overflow oder use-after-free auslöst, das Zielobjekt (victim) direkt neben einem vom Angreifer kontrollierten Objekt liegt.
Auf diese Weise kann der Angreifer bei einer Speicherkorruption zuverlässig das victim-Objekt mit kontrollierten Daten überschreiben.

Schritte:

  1. Spray allocations (fill the holes)
  • Im Laufe der Zeit fragmentiert der kernel heap: einige Zonen haben Lücken, in denen alte Objekte freed wurden.
  • Der Angreifer erzeugt zuerst viele Dummy-allocations, um diese Lücken aufzufüllen, sodass der Heap „gepackt“ und vorhersehbar wird.
  1. Force new pages
  • Sobald die Lücken gefüllt sind, müssen neue Allocations aus neuen Seiten kommen, die der Zone hinzugefügt werden.
  • Frische Pages bedeuten, dass Objekte zusammengeclustert werden, statt über altes fragmentiertes Memory verstreut zu sein.
  • Das gibt dem Angreifer deutlich bessere Kontrolle über Nachbarn.
  1. Place attacker objects
  • Der Angreifer sprayt jetzt erneut und erstellt viele vom Angreifer kontrollierte Objekte in diesen neuen Pages.
  • Diese Objekte sind in Größe und Platzierung vorhersehbar (da sie alle zur selben Zone gehören).
  1. Free a controlled object (make a gap)
  • Der Angreifer freed bewusst eines seiner eigenen Objekte.
  • Das erzeugt ein „Loch“ im Heap, das der Allocator später für die nächste Allocation dieser Größe wiederverwenden wird.
  1. Victim object lands in the hole
  • Der Angreifer löst das Kernel aus, das victim-Objekt zu allocaten (das Objekt, das er korrumpieren möchte).
  • Da das Loch der erste verfügbare Slot in der freelist ist, wird das victim genau dort platziert, wo der Angreifer sein Objekt freed hat.
  1. Overflow / UAF into victim
  • Nun hat der Angreifer vom Angreifer-kontrollierte Objekte rund um das victim.
  • Durch ein Overflow aus einem der eigenen Objekte (oder durch Reuse eines freed Objekts) kann er zuverlässig die Memory-Felder des victims mit gewählten Werten überschreiben.

Warum es funktioniert:

  • Zone allocator predictability: Allocations der gleichen Größe stammen immer aus derselben Zone.
  • Freelist behavior: Neue Allocations reuse den most recently freed Chunk zuerst.
  • Heap sprays: Der Angreifer füllt Memory mit vorhersehbarem Content und kontrolliert das Layout.
  • Endresultat: Der Angreifer kontrolliert, wo das victim-Objekt landet und welche Daten daneben liegen.

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

Apple hat den Allocator gehärtet und heap grooming deutlich erschwert:

1. From Classic kalloc to kalloc_type

  • Before: Für jede Größenklasse (16, 32, 64, … 1280, etc.) existierte eine einzelne kalloc.<size> Zone. Jedes Objekt dieser Größe wurde dort platziert → Angreifer-Objekte konnten neben privilegierten Kernel-Objekten liegen.
  • Now:
  • Kernel-Objekte werden aus typed zones (kalloc_type) alloziert.
  • Jeder Objekttyp (z. B. ipc_port_t, task_t, OSString, OSData) hat seine eigene dedizierte Zone, selbst wenn sie die gleiche Größe haben.
  • Die Zuordnung zwischen Objekttyp ↔ Zone wird zur Compile-Zeit vom kalloc_type system generiert.

Ein Angreifer kann nicht mehr garantieren, dass kontrollierte Daten (OSData) neben sensitiven Kernel-Objekten (task_t) derselben Größe landen.

2. Slabs and Per-CPU Caches

  • Der Heap ist in slabs (Pages, in fixed-size Chunks für diese Zone) unterteilt.
  • Jede Zone hat einen per-CPU cache, um Contention zu reduzieren.
  • Allocation-Pfad:
  1. Versuche per-CPU cache.
  2. Wenn leer, ziehe aus der global freelist.
  3. Wenn freelist leer, allocate eine neue slab (eine oder mehrere Pages).
  • Vorteil: Diese Dezentralisierung macht heap sprays weniger deterministisch, da Allocations von unterschiedlichen CPUs’ Caches bedient werden können.

3. Randomization inside zones

  • Innerhalb einer Zone werden freed Elemente nicht in einfacher FIFO/LIFO-Reihenfolge zurückgegeben.
  • Modernes XNU verwendet encoded freelist pointers (safe-linking-ähnlich wie Linux, eingeführt ~iOS 14).
  • Jeder freelist pointer ist XOR-encoded mit einem pro-Zone secret cookie.
  • Das verhindert, dass Angreifer einen gefälschten freelist pointer erstellen, selbst wenn sie eine Schreibprimitive haben.
  • Manche Allocations werden in ihrer Platzierung innerhalb einer slab randomisiert, sodass ein Spray keine Nachbarschaft garantiert.

4. Guarded Allocations

  • Bestimmte kritische Kernel-Objekte (z. B. credentials, task-Strukturen) werden in guarded zones alloziert.
  • Diese Zonen fügen guard pages (unmapped Memory) zwischen Slabs ein oder verwenden redzones um Objekte.
  • Jedes Overflow in die guard page löst einen Fault aus → sofortiger Panic statt stiller Korruption.

5. Page Protection Layer (PPL) and SPTM

  • Selbst wenn man ein freed Objekt kontrolliert, kann man nicht den gesamten Kernel-Speicher modifizieren:
  • PPL (Page Protection Layer) erzwingt, dass bestimmte Regionen (z. B. code signing data, entitlements) read-only sind, sogar für den Kernel selbst.
  • Auf A15/M2+ devices wird diese Rolle durch SPTM (Secure Page Table Monitor) + TXM (Trusted Execution Monitor) ersetzt/ergänzt.
  • Diese hardware-erzwungenen Layer bedeuten, dass Angreifer nicht von einer einzelnen Heap-Korruption zu einem willkürlichen Patchen kritischer Sicherheitsstrukturen eskalieren können.
  • (Added / Enhanced): außerdem wird PAC (Pointer Authentication Codes) im Kernel verwendet, um Pointer (insbesondere function pointers, vtables) zu schützen, sodass das Fälschen oder Korrumpieren schwieriger wird.
  • (Added / Enhanced): Zonen können zone_require / zone enforcement durchsetzen, d. h. dass ein freed Objekt nur durch seine korrekte typed zone zurückgegeben werden kann; ungültige Cross-Zone frees können panics verursachen oder abgelehnt werden. (Apple deutet darauf in ihren Memory-Safety-Posts hin)

6. Large Allocations

  • Nicht alle Allocations laufen über kalloc_type.
  • Sehr große Anfragen (oberhalb von ~16 KB) umgehen typed zones und werden direkt aus dem kernel VM (kmem) über Page-Allocations bedient.
  • Diese sind weniger vorhersehbar, aber auch weniger ausnutzbar, da sie Slabs nicht mit anderen Objekten teilen.

7. Allocation Patterns Attackers Target

Auch mit diesen Schutzmaßnahmen suchen Angreifer weiterhin nach:

  • Reference count objects: Wenn man retain/release-Zähler manipulieren kann, kann das zu use-after-free führen.
  • Objects with function pointers (vtables): Das Korrumpieren eines solchen Objekts bietet weiterhin Control Flow.
  • Shared memory objects (IOSurface, Mach ports): Diese sind weiterhin Ziele, weil sie User ↔ Kernel überbrücken.

Aber — anders als früher — kann man nicht einfach OSData sprayen und erwarten, dass es neben einem task_t landet. Man braucht type-spezifische Bugs oder info leaks, um erfolgreich zu sein.

Example: Allocation Flow in Modern Heap

Angenommen, Userspace ruft IOKit auf, um ein OSData-Objekt zu allocaten:

  1. Type lookupOSData mappt auf die kalloc_type_osdata zone (Größe 64 bytes).
  2. Prüfe per-CPU cache auf freie Elemente.
  • Falls gefunden → gebe eines zurück.
  • Falls leer → gehe zur global freelist.
  • Falls freelist leer → allocate eine neue slab (Page von 4KB → 64 Chunks à 64 bytes).
  1. Gebe Chunk an Aufrufer zurück.

Freelist pointer protection:

  • Jeder freed Chunk speichert die Adresse des next free Chunk, aber encoded mit einem secret key.
  • Das Überschreiben dieses Feldes mit Angreifer-Daten funktioniert nicht, sofern man den Key nicht kennt.

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 neueren Apple-OS-Versionen (insbesondere iOS 17+) führte Apple einen sichereren Userland-Allocator ein, xzone malloc (XZM). Dies ist das User-Space-Pendant zum Kernel-kalloc_type und wendet Type-Awareness, Metadaten-Isolation und Memory-Tagging-Sicherheiten an.

Goals & Design Principles

  • Type segregation / type awareness: Gruppiere Allocations nach Typ oder Verwendung (Pointer vs Data), um type confusion und Cross-Type-Reuse zu verhindern.
  • Metadata isolation: Trenne Heap-Metadaten (z. B. free lists, size/state bits) vom Objekt-Payload, sodass Out-of-Bounds-Schreibvorgänge weniger wahrscheinlich Metadaten korruptieren.
  • Guard pages / redzones: Füge unmapped Pages oder Padding um Allocations ein, um Overflows zu erkennen.
  • Memory tagging (EMTE / MIE): Arbeitet zusammen mit Hardware-Tagging, um Use-After-Free, Out-of-Bounds und ungültige Zugriffe zu erkennen.
  • Scalable performance: Behalte niedrigen Overhead, vermeide übermäßige Fragmentierung und unterstütze viele Allocations pro Sekunde mit niedriger Latenz.

Architecture & Components

Nachfolgend die Hauptelemente im xzone-Allocator:

Segment Groups & Zones

  • Segment groups partitionieren den Adressraum nach Verwendungskategorien: z. B. data, pointer_xzones, data_large, pointer_large.
  • Jede Segment-Gruppe enthält segments (VM-Bereiche), die Allocations für diese Kategorie hosten.
  • Zu jedem Segment gehört ein metadata slab (separater VM-Bereich), das Metadaten speichert (z. B. free/used bits, Größenklassen) für dieses Segment. Diese out-of-line (OOL) metadata stellt sicher, dass Metadaten nicht mit Objekt-Payloads vermischt sind und mindert Korruption durch Overflows.
  • Segments werden in chunks (Slices) unterteilt, die wiederum in blocks (Allocation-Einheiten) geschnitten werden. Ein Chunk ist an eine bestimmte Größenklasse und Segment-Gruppe gebunden (d. h. alle Blocks in einem Chunk teilen dieselbe Größe & Kategorie).
  • Für kleine/mittlere Allocations werden feste Chunk-Größen verwendet; für große/huge kann separat gemappt werden.

Chunks & Blocks

  • Ein chunk ist ein Bereich (oft mehrere Pages), der einer Größenklasse innerhalb einer Gruppe gewidmet ist.
  • Innerhalb eines Chunks sind blocks Slots für Allocations. Freed Blocks werden über die metadata slab getrackt — z. B. per Bitmaps oder Free-Lists, die out-of-line gespeichert sind.
  • Zwischen Chunks (oder innerhalb) können guard slices / guard pages eingefügt werden (z. B. unmapped Slices), um Out-of-Bounds-Schreibvorgänge zu erkennen.

Type / Type ID

  • Jede Allocation-Site (oder Aufruf von malloc, calloc, etc.) ist mit einer type identifier (malloc_type_id_t) verbunden, die kodiert, welche Art von Objekt alloziert wird. Diese Type ID wird an den Allocator übergeben, der sie benutzt, um die richtige Zone / Segment auszuwählen.
  • Dadurch können zwei Allocations gleicher Größe in völlig unterschiedlichen Zonen landen, wenn ihre Typen unterschiedlich sind.
  • In frühen iOS-17-Versionen waren nicht alle APIs (z. B. CFAllocator) vollständig type-aware; Apple hat einige dieser Schwächen in iOS 18 adressiert.

Allocation & Freeing Workflow

Hier ein High-Level-Flow, wie Allocation und Deallocation in xzone funktionieren:

  1. malloc / calloc / realloc / typed alloc wird mit einer Größe und Type ID aufgerufen.
  2. Der Allocator benutzt die Type ID, um die korrekte Segment-Gruppe / Zone auszuwählen.
  3. Innerhalb dieser Zone/Segment sucht er nach einem Chunk mit freien Blocks der gewünschten Größe.
  • Er kann lokale Caches / per-thread Pools oder free block lists aus den Metadaten konsultieren.
  • Wenn kein freier Block verfügbar ist, kann er einen neuen Chunk in dieser Zone allocaten.
  1. Die metadata slab wird aktualisiert (free bit wird gelöscht, Buchführung).
  2. Falls Memory-Tagging (EMTE) aktiv ist, bekommt der zurückgegebene Block ein Tag zugewiesen und die Metadaten werden auf „live“ gesetzt.
  3. Bei free():
  • Der Block wird in den Metadaten als freed markiert (via OOL slab).
  • Der Block kann in eine free list gelegt oder zum Reuse gepoolt werden.
  • Optional werden Block-Inhalte gecleared oder ge-poisoned, um Data-Leaks oder Use-After-Free-Exploitation zu reduzieren.
  • Der Hardware-Tag des Blocks kann invalidiert oder neu getaggt werden.
  • Wenn ein ganzer Chunk frei wird (alle Blocks freed), kann der Allocator diesen Chunk reclaimen (unmap oder an das OS zurückgeben) bei Memory-Pressure.

Security Features & Hardening

Dies sind die Abwehrmaßnahmen im modernen Userland-xzone:

FeatureZweckHinweise
Metadata decouplingVerhindert, dass Overflows Metadaten korruptierenMetadaten leben in separatem VM-Bereich (metadata slab)
Guard pages / unmapped slicesErkennen von Out-of-Bounds-WritesHilft, Buffer Overflows zu erkennen, statt angrenzende Blocks still zu korruptieren
Type-based segregationVerhindert Cross-Type-Reuse & type confusionAuch gleichgroße Allocations aus unterschiedlichen Typen gehen in verschiedene Zonen
Memory Tagging (EMTE / MIE)Erkennen ungültiger Zugriffe, stale references, OOB, UAFxzone arbeitet mit Hardware-EMTE im synchronen Modus („Memory Integrity Enforcement“) zusammen
Delayed reuse / poisoning / zapReduziert Chance auf Use-After-Free-ExploitationFreed Blocks können ge-poisoned, geloescht oder in Quarantäne gehalten werden vor Reuse
Chunk reclamation / dynamic unmappingReduziert Memory-Waste und FragmentierungGanze Chunks können ungemappt werden, wenn sie ungenutzt sind
Randomization / placement variationVerhindert deterministische NachbarschaftenBlocks in einem Chunk und die Chunk-Auswahl können randomisierte Aspekte haben
Segregation of “data-only” allocationsTrennung von Allocations, die keine Pointer speichernVerringert Angreifer-Kontrolle über Metadaten oder Kontrollfelder

Interaction with Memory Integrity Enforcement (MIE / EMTE)

  • Apples MIE (Memory Integrity Enforcement) ist das Hardware- + OS-Framework, das Enhanced Memory Tagging Extension (EMTE) in einen always-on, synchronen Modus über große Angriffsflächen bringt.
  • Der xzone-Allocator ist eine fundamentale Grundlage von MIE im User-Space: Allocations, die via xzone erfolgen, bekommen Tags, und Zugriffe werden von der Hardware geprüft.
  • In MIE sind Allocator, Tag-Zuweisung, Metadaten-Management und Tag-Confidentiality-Enforcement integriert, um sicherzustellen, dass Memory-Fehler (z. B. stale reads, OOB, UAF) sofort erkannt werden und nicht später ausgenutzt werden.

Wenn du magst, kann ich auch ein Cheat-Sheet oder Diagramm der xzone-Interna für dein Buch erzeugen. Möchtest du, dass ich das als Nächstes erstelle?
:contentReference[oai:20]{index=20}

(Old) Physical Use-After-Free via IOSurface

ios Physical UAF - IOSurface


Ghidra Install BinDiff

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

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

Using BinDiff with Kernel versions

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

macOS Kernel Extensions & Kernelcache

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

Finding the right XNU version

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

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

JSKit-Based Safari Chains and PREYHUNTER Stagers

Renderer RCE abstraction with JSKit

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

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

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

PREYHUNTER helper & watcher modules

  • Watcher anti-analysis: Eine dedizierte Watcher-Binary profiliert das Gerät kontinuierlich und bricht die Kill-Chain ab, wenn eine Analyseumgebung erkannt wird. Sie prüft security.mac.amfi.developer_mode_status, das Vorhandensein einer diagnosticd-Konsole, die Locale US oder IL, Jailbreak-Spuren wie Cydia, Prozesse wie bash, tcpdump, frida, sshd oder checkrain, mobile AV-Apps (McAfee, AvastMobileSecurity, NortonMobileSecurity), benutzerdefinierte HTTP-Proxy-Einstellungen und benutzerdefinierte Root-CAs. Wird eine Prüfung ausgelöst, wird die weitere Payload-Zustellung blockiert.
  • Helper surveillance hooks: Der Helper-Komponent kommuniziert mit anderen Stages über /tmp/helper.sock und lädt dann Hook-Sets namens DMHooker und UMHooker. Diese Hooks greifen VOIP-Audio-Pfade ab (Aufnahmen landen unter /private/var/tmp/l/voip_%lu_%u_PART.m4a), implementieren einen systemweiten keylogger, nehmen Fotos ohne UI auf und hooken SpringBoard, um Benachrichtigungen zu unterdrücken, die diese Aktionen normalerweise auslösen würden. Der Helper fungiert daher als unauffällige Validierungs- und leichte Überwachungs-Schicht, bevor schwerere Implants wie Predator installiert werden.

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

Referenzen

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks