iOS Exploiting
Reading time: 57 minutes
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
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
iOS Exploit Mitigations
1. Code Signing / Runtime Signature Verification
Introduced early (iPhone OS → iOS) Dies ist einer der grundlegenden Schutzmechanismen: alle ausführbaren Codes (Apps, dynamic libraries, JIT-ed code, extensions, frameworks, caches) müssen kryptografisch signiert sein durch eine Zertifikatkette, die in Apples Trust verwurzelt ist. Zur Laufzeit prüft das System vor dem Laden eines Binaries in den Speicher (oder vor bestimmten Sprüngen über Grenzen hinweg) dessen Signatur. Wenn der Code verändert wurde (Bit-Flip, Patch) oder unsigniert ist, schlägt das Laden fehl.
- Verhindert: die “klassische payload drop + execute”-Phase in Exploit-Chains; arbitrary code injection; das Modifizieren eines bestehenden Binaries, um bösartige Logik einzufügen.
- Funktionsweise:
- 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 erzeugten Code verlangt Apple, dass Pages signiert sind oder über spezielle APIs validiert werden (z. B.
mprotectmit code-sign Checks). - Die Signatur enthält entitlements und Identifiers; das OS erzwingt, dass bestimmte APIs oder privilegierte Fähigkeiten spezifische entitlements erfordern, die nicht gefälscht werden können.
Example
Angenommen, ein Exploit erlangt Code execution 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** code-signature constraints erfüllen. Da der shellcode nicht mit Apples Zertifikat signiert ist, schlägt der Sprung fehl oder das System verweigert es, diese Memory-Region 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-Signaturvalidierung von Binaries (inkl. System- und User-Binaries) gegen Apples Root-Zertifikat durchführt, anstatt sich auf gecachte userland trust stores zu verlassen.
- Verhindert: post-install Tampering von Binaries, jailbreak-Techniken, die versuchen, system libraries oder user apps zu swapen oder zu patchen; das Täuschen des Systems durch Ersetzen vertrauenswürdiger Binaries mit bösartigen Gegenstücken.
- Funktionsweise:
- Anstatt einer lokalen Trust-Datenbank oder einem Zertifikat-Cache zu vertrauen, holt CoreTrust direkt Apples Root oder verifiziert intermediate Zertifikate in einer sicheren Kette.
- Es stellt sicher, dass Modifikationen (z. B. im Dateisystem) an bestehenden Binaries erkannt und abgelehnt 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 Persistence zu erlangen. Wenn der OS-Loader oder CoreTrust prüft, bemerkt er jedoch die Signaturabweichung (oder geänderte entitlements) und weigert sich, auszuführen.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 Pages, die als writable (für Daten) markiert sind, nicht-executable sind, und Pages, die executable sind, nicht-writable. Man kann nicht einfach shellcode in einen heap- oder stack-Bereich schreiben und ausführen.
- Verhindert: direkte shellcode-Ausführung; klassischen buffer-overflow → Sprung zu injiziertem shellcode.
- Funktionsweise:
- Die MMU / memory protection flags (über page tables) erzwingen die Trennung.
- Jeder Versuch, eine writable Page executable zu machen, löst eine Systemprüfung aus (und ist entweder verboten oder erfordert code-sign Approval).
- In vielen Fällen erfordert das Machen von Pages executable die Nutzung 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 sie ausführbar zu machen. Aber das System verweigert dies oder validiert, dass die neue Page code-sign constraints 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 usw. bei jedem Prozessstart. Gadget-Adressen verschieben sich zwischen Läufen.
- Verhindert: das Hardcoden von gadget-Adressen für ROP/JOP; statische Exploit-Chains; blindes Springen zu bekannten Offsets.
- Funktionsweise:
- Jede geladene Library / jedes dynamische Modul wird an einem randomisierten Offset rebased.
- Stack- und Heap-Basiszeiger werden randomisiert (innerhalb bestimmter Entropie-Grenzen).
- 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 entdecken.
Example
Eine ROP-Chain erwartet ein Gadget bei `0x….lib + offset`. Da `lib` aber bei jedem Lauf anders relociert wird, schlägt die hardcodierte Chain 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 Boot.
- Verhindert: kernel-level Exploits, die auf feste Positionen von Kernel-Code oder -Daten vertrauen; statische Kernel-Exploits.
- Funktionsweise:
- Bei jedem Boot wird die Kernel-Basisadresse randomisiert (innerhalb eines Bereichs).
- Kernel-Datenstrukturen (wie
task_structs,vm_map, etc.) können ebenfalls verschoben oder versetzt werden. - Angreifer müssen zuerst kernel pointer leak-en oder information disclosure Vulnerabilities ausnutzen, um Offsets zu berechnen, bevor sie Kernel-Strukturen oder -Code hijacken.
Example
Eine lokale Vulnerability zielt darauf ab, einen Kernel-Funktionspointer (z. B. in einer `vtable`) bei `KERN_BASE + offset` zu korrumpieren. Da `KERN_BASE` unbekannt ist, muss der Angreifer ihn zuerst leak-en (z. B. über ein read-Primitive), bevor er die richtige 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 (aka AMCC) überwacht kontinuierlich die Integrität der Kernel-Text-Pages (via Hash oder Checksum). Wenn es Tampering (Patches, inline hooks, Code-Änderungen) außerhalb erlaubter Fenster entdeckt, löst es einen Kernel-Panic oder Reboot aus.
- Verhindert: persistentes Kernel-Patching (Ändern von Kernel-Instruktionen), inline hooks, statische Funktionsüberschreibungen.
- Funktionsweise:
- Ein Hardware- oder Firmware-Modul überwacht den Kernel-Textbereich.
- Es hasht die Pages periodisch oder on-demand und vergleicht sie mit erwarteten Werten.
- Wenn Abweichungen außerhalb legitimer Update-Fenster auftreten, panict es das Gerät (um persistente bösartige Änderungen zu vermeiden).
- Angreifer müssen entweder Erkennungsfenster vermeiden 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 dem erwarteten Wert entspricht, und löst einen Kernel-Panic aus, wodurch das Gerät abstürzt, bevor der Patch stabilisiert wird.7. Kernel Text Read‐Only Region (KTRR)
Introduced in modern SoCs (post ~A12 / newer hardware) KTRR ist ein hardware-implementierter Mechanismus: sobald der Kernel-Text früh im Bootprozess gesperrt wird, wird er von EL1 (dem Kernel) als read-only markiert, was weitere Writes an Code-Pages verhindert.
- Verhindert: jede Modifikation des Kernel-Codes nach dem Boot (z. B. Patching, In-Place Code Injection) auf EL1-Privilege-Level.
- Funktionsweise:
- Während des Boots (im secure/bootloader-Stage) markiert der memory controller (oder eine sichere Hardware-Einheit) die physischen Pages, die den 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 ändern, müsste der Angreifer zuerst die Boot-Kette kompromittieren oder KTRR selbst unterwandern.
Example
Ein Privilege-Escalation-Exploit springt in EL1 und schreibt einen Trampolin in eine Kernel-Funktion (z. B. im `syscall`-Handler). Aber weil die Pages durch KTRR als read-only gesperrt sind, schlägt der Write fehl (oder verursacht Fault), sodass Patches nicht angewendet werden.8. Pointer Authentication Codes (PAC)
Introduced with ARMv8.3 (hardware), Apple beginning with A12 / iOS ~12+
- PAC ist eine Hardware-Funktion, eingeführt in ARMv8.3-A, um Manipulationen an Pointer-Werten (Return-Adressen, Funktionspointer, bestimmte Datenpointer) zu erkennen, indem eine kleine kryptografische Signatur (ein “MAC”) in die unbenutzten oberen Bits des Pointers eingebettet wird.
- Die Signatur (“PAC”) wird über den Pointer-Wert plus einen modifier (einen Kontextwert, z. B. stack pointer oder anderes unterscheidendes Datum) berechnet. So erhält derselbe Pointer-Wert in unterschiedlichen Kontexten unterschiedliche PACs.
- Zur Nutzungszeit prüft eine authenticate-Instruktion die PAC. Wenn sie gültig ist, wird die PAC entfernt und der reine Pointer gewonnen; ist sie ungültig, wird der Pointer “poisoned” (oder es wird ein Fault ausgelöst).
- Die Keys, die zur Erzeugung/Validierung von PACs verwendet werden, liegen in privilegierten Registern (EL1, Kernel) und sind nicht direkt aus dem User-Mode lesbar.
- Da nicht alle 64 Bits eines Pointers in vielen Systemen genutzt werden (z. B. 48-bit Address Space), sind die oberen Bits “frei” und können die PAC tragen, 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 data oder anderen generischen Zwecken
-
Diese Keys werden in privilegierten System-Registern gespeichert (nur bei EL1/EL2 etc. zugänglich), nicht im User-Mode.
-
PAC wird durch eine kryptografische Funktion berechnet (ARM schlägt QARMA als Algorithmus vor) unter Verwendung von:
- Dem Pointer-Wert (kanonischer Teil)
- Einem modifier (ein Kontextwert, wie ein salt)
- Dem geheimen Key
- Einem internen Tweak-Logic Wenn die resultierende PAC mit den oberen Bits des Pointers übereinstimmt, gelingt die Authentifizierung.
Instruction Families
Die Namenskonvention ist: PAC / AUT / XPAC, gefolgt von Domain-Buchstaben.
PACxxInstruktionen signieren einen Pointer und fügen eine PAC einAUTxxInstruktionen authentifizieren + strippen (validieren und die PAC entfernen)XPACxxInstruktionen strippen ohne Validierung
Domains / Suffixe:
| Mnemonic | Meaning / 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 |
Es gibt spezialisierte / Alias-Formen:
PACIASPist Kurzform fürPACIA X30, SP(den link register signieren, wobei SP als modifier genutzt wird)AUTIASPistAUTIA X30, SP(link register mit SP authentifizieren)- Kombinierte Formen wie
RETAA,RETAB(authenticate-and-return) oderBLRAA(authenticate & branch) existieren in ARM-Erweiterungen / Compiler-Support. - Auch Zero-Modifier-Varianten:
PACIZA/PACIZB, bei denen der modifier implizit null ist, etc.
Modifiers
Das Hauptziel des modifier ist, die PAC an einen bestimmten Kontext zu binden, sodass derselbe address, wenn er in unterschiedlichen Kontexten signiert wurde, unterschiedliche PACs ergibt. Das verhindert einfaches Pointer-Reuse über Frames oder Objekte hinweg. Es ist wie ein Salt für einen Hash.
Daher:
- Der modifier ist ein Kontextwert (ein anderes Register), der in die PAC-Berechnung einfließt. Typische Wahl: 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 wird an den spezifischen Stack-Frame gebunden. Versucht man, LR in einem anderen Frame wiederzuverwenden, ändert sich der modifier und die PAC-Validierung schlägt fehl.
- Derselbe Pointer-Wert, signiert mit unterschiedlichen modifiers, ergibt unterschiedliche PACs.
- Der modifier muss nicht geheim sein, aber idealerweise nicht vom Angreifer kontrolliert werden.
- 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 beinhaltet per-boot diversifiers, sodass Keys oder Tweaks bei jedem Boot wechseln und 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.
- Auf Apple M1 / Apple Silicon zeigte Reverse Engineering, dass es dort neun modifier-Typen und Apple-spezifische System-Register zur Key-Kontrolle gibt.
- Apple nutzt PAC in vielen Kernel-Subsystemen: return-address signing, Pointer-Integrität in Kernel-Daten, signierte thread contexts usw.
- Google Project Zero zeigte, wie man unter einem mächtigen memory read/write-Primitive im Kernel kernel PACs (für A-Keys) auf A12-Geräten fälschen konnte, aber Apple hat viele dieser Pfade gepatcht.
- In Apples System sind einige Keys global im Kernel, während User-Prozesse per-process Key-Randomness erhalten können.
PAC Bypasses
- Kernel-mode PAC: theoretical vs real bypasses
- Da kernel PAC-Keys und Logik eng kontrolliert werden (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, Wiederverwendung signierter Zustände, ungeschützte 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), berichtet von Zecao Cai et al. i.blackhat.com
- Diese Bypässe beruhen oft auf sehr spezifischen Gadgets oder Implementierungsfehlern; sie sind kein General-Purpose-Bypass.
Daher gilt kernel PAC als sehr robust, wenn auch nicht perfekt.
- User-mode / runtime PAC bypass techniques
Diese sind häufiger und nutzen Unzulänglichkeiten darin aus, wie PAC in dynamic linking / runtime frameworks angewendet oder genutzt wird. Nachfolgend Klassen mit Beispielen.
2.1 Shared Cache / A key issues
-
Der dyld shared cache ist ein großer pre-linked Blob von System-Frameworks und Libraries. Weil er so weit geteilt wird, sind Funktionspointer innerhalb des shared cache „pre-signed“ und werden von vielen Prozessen verwendet. Angreifer zielen auf diese bereits signierten Pointer als „PAC oracles“.
-
Manche 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 den Aufbau eines Oracles über den shared cache, um relative Adressen abzuleiten und das mit signierten Pointern zu kombinieren, um PAC zu umgehen. saelo.github.io
-
Auch Imports von Funktionspiptern aus shared libraries in userspace wurden als unzureichend durch PAC geschützt gefunden, was einem Angreifer erlaubt, Funktionspointer zu bekommen, ohne ihre Signatur zu verändern. (Project Zero Bug-Entry) bugs.chromium.org
2.2 dlsym(3) / dynamic symbol resolution
-
Ein bekannter Bypass ist das Aufrufen von
dlsym()um einen bereits signierten Funktionspointer (mit A-key, diversifier null) zu erhalten und diesen dann zu verwenden. Dadlsymlegalerweise einen signierten Pointer zurückgibt, umgeht die Verwendung desselben das Bedürfnis, PAC zu fälschen. -
Epsilons Blog beschreibt, wie manche Bypässe dies ausnutzen:
dlsym("someSym")liefert einen signierten Pointer, der für indirekte Aufrufe genutzt werden kann. blog.epsilon-sec.com -
Synacktivs "iOS 18.4 --- dlsym considered harmful" beschreibt einen Bug: einige Symbole, die via
dlsymin iOS 18.4 aufgelöst werden, liefern Pointer, die falsch signiert sind (oder buggy diversifiers haben), was unbeabsichtigte PAC-Bypass-Möglichkeiten eröffnet. 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 Kontext null. blog.epsilon-sec.com
Dementsprechend ist dlsym ein häufig genutzter Vektor bei User-Mode PAC-Bypässen.
2.3 Other DYLD / runtime relocations
-
Der DYLD-Loader und die dynamic relocation-Logik sind komplex und mappen manchmal temporär Pages als read/write, um Relocations durchzuführen, und schalten sie dann 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 inzwischen mit SPRR / VM_FLAGS_TPRO geschützt (einige Schutzflags für dyld). Frühere Versionen hatten schwächere Schutzmechanismen. Synacktiv
-
In WebKit-Exploit-Chains ist der DYLD-Loader oft Ziel für PAC-Bypässe. Die Slides erwähnen, dass viele PAC-Bypässe den DYLD-Loader angegriffen haben (via relocation, interposer hooks). Synacktiv
2.4 NSPredicate / NSExpression / ObjC / SLOP
-
In userland Exploit-Chains werden Objective-C Runtime-Methoden wie
NSPredicate,NSExpressionoderNSInvocationgenutzt, um Control-Calls zu schmuggeln, ohne offensichtliches Pointer-Fälschen. -
Auf älteren iOS-Versionen (vor PAC) nutzte ein Exploit fake NSInvocation-Objekte, um beliebige Selectors auf kontrolliertem Speicher aufzurufen. Mit PAC sind Anpassungen nötig. Dennoch wurde die Technik SLOP (SeLector Oriented Programming) unter PAC weiterentwickelt. Project Zero
-
Die ursprüngliche SLOP-Technik erlaubte das Verketten von ObjC-Aufrufen durch Erzeugen gefälschter Invocations; der Bypass stützt sich darauf, dass ISA- oder selector-Pointer manchmal nicht vollständig PAC-geschützt sind. Project Zero
-
In Umgebungen, in denen Pointer Authentication nur teilweise angewendet wird, sind Methoden / selector / target pointer nicht immer PAC-geschützt, was Raum für Bypass 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 Rückkehradresse auf dem Stack. Der Angreifer schreibt die Ziel-Gadget-Adresse, kann aber den korrekten PAC nicht berechnen. Wenn die Funktion zurückkehrt, verursacht die `AUTIA`-Instruktion der CPU einen Fault, weil der PAC nicht übereinstimmt. Die Kette schlägt fehl.
Project Zero’s Analyse des A12 (iPhone XS) zeigte, wie Apples PAC verwendet wird und Methoden zum Fälschen von PACs, falls ein Angreifer ein Speicher-Lese/Schreib-Primitiv besitzt.
</details>
### 9. **Branch Target Identification (BTI)**
**Eingeführt mit ARMv8.5 (neuere Hardware)**
BTI ist eine Hardware-Funktion, die **indirekte Branch-Ziele** ü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-Adressen, die kein Landing-Pad haben, löst eine Exception aus.
LLVM’s Implementierung beschreibt drei Varianten der BTI-Instruktionen und wie sie auf Branch-Typen abgebildet werden.
| BTI Variant | What it permits (which branch types) | Typical placement / use case |
|-------------|----------------------------------------|-------------------------------|
| **BTI C** | Ziele von *call*-artigen indirekten Branches (z. B. `BLR`, oder `BR` mit X16/X17) | Am Einstieg von Funktionen platzieren, die indirekt aufgerufen werden können |
| **BTI J** | Ziele von *jump*-artigen Branches (z. B. `BR` für tail calls) | Am Beginn von Blöcken platzieren, die über Jump-Tabellen oder Tail-Calls erreichbar sind |
| **BTI JC** | Wirkt sowohl als C als auch als J | Kann von sowohl call- als auch jump-Branches adressiert werden |
- In Code, der mit branch target enforcement kompiliert wurde, fügen Compiler an jedem gültigen indirekten-Branch-Ziel (Funktionsanfänge oder Blöcke, die durch Jumps erreichbar sind) eine BTI-Instruktion (C, J oder JC) ein, sodass indirekte Branches nur zu diesen Stellen erfolgreich sind.
- **Direkte Branches / Calls** (d. h. fixe-Adress-`B`, `BL`) sind **nicht** durch BTI eingeschränkt. Die Annahme ist, dass Code-Pages vertraut werden und ein Angreifer sie nicht ändern kann (also sind direkte Branches sicher).
- Außerdem sind **RET / return**-Instruktionen in der Regel nicht durch BTI eingeschränkt, da Rücksprungadressen durch PAC oder Return-Signing-Mechanismen geschützt sind.
#### Mechanismus und Durchsetzung
- Wenn die CPU einen **indirekten Branch (BLR / BR)** dekodiert, der sich auf einer Seite befindet, die als „guarded / BTI-enabled“ markiert ist, prüft sie, ob die erste Instruktion der Zieladresse ein gültiges BTI (C, J oder JC, je nach erlaubt) ist. Falls nicht, tritt eine **Branch Target Exception** auf.
- Das BTI-Instruktions-Encoding ist so entworfen, dass Opcodes wiederverwendet werden, die zuvor als NOPs reserviert waren (in früheren ARM-Versionen). Daher bleiben BTI-aktivierte Binaries rückwärtskompatibel: Auf Hardware ohne BTI-Unterstützung wirken diese Instruktionen als 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 von Jumps adressiert werden.
- Einige Patches und LLVM-Code zeigen, dass BTI nicht für *alle* Basic Blocks eingefügt wird — nur für diejenigen, die potenzielle Branch-Ziele sind (z. B. aus switch / jump-Tabellen).
#### BTI + PAC Synergie
PAC schützt den Pointer-Wert (die Quelle) — stellt sicher, dass die Kette indirekter Aufrufe / 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. Das erhöht die Schwierigkeit, brauchbare Exploit-Gadgets zusammenzustellen.
#### Example
<details>
<summary>Example</summary>
Ein Exploit versucht, in ein Gadget bei `0xABCDEF` zu pivoten, das nicht mit `BTI c` beginnt. Die CPU prüft beim Ausführen von `blr x0` das Ziel und faultet, weil die Instruktionsausrichtung kein gültiges Landing-Pad enthält. Viele Gadgets werden somit 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-Erweiterungen / iOS-Unterstützung (für gehärteten Kernel)**
#### PAN (Privileged Access Never)
- **PAN** ist eine Funktion, eingeführt in **ARMv8.1-A**, die verhindert, dass **privilegierter Code** (EL1 oder EL2) **Lesen oder Schreiben** von Speicher durchführt, 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 Benutzerzeiger dereferenzieren, ohne zuerst PAN zu *deaktivieren*, wodurch Risiken von **ret2usr**-artigen Exploits oder Missbrauch userkontrollierter Puffer reduziert werden.
- Wenn PAN aktiviert ist (PSTATE.PAN = 1), löst jede privilegierte Load/Store-Instruktion, die auf eine virtuelle Adresse zugreift, die „accessible at EL0“ ist, einen **Permission Fault** aus.
- Der Kernel muss, wenn er berechtigterweise auf Benutzerspeicher zugreifen muss (z. B. Daten von/zu User-Buffer kopieren), PAN **vorübergehend deaktivieren** (oder „unprivileged load/store“-Instruktionen verwenden), um diesen Zugriff zu erlauben.
- In Linux auf ARM64 wurde PAN-Unterstützung etwa 2015 eingeführt: Kernel-Patches fügten die Erkennung der Funktion hinzu und ersetzten `get_user` / `put_user` etc. durch Varianten, die PAN beim Zugriff auf User-Speicher temporär löschen.
**Wichtige Nuance / Limitierung / Bug**
- Wie von Siguza und anderen angemerkt, bedeutet ein Spezifikationsfehler (oder ein mehrdeutiges Verhalten) im ARM-Design, dass **execute-only user mappings** (`--x`) PAN **möglicherweise nicht auslösen**. Anders gesagt: Wenn eine User-Page ausführbar, aber ohne Read-Permission ist, könnte der Kernel-Leseversuch PAN umgehen, weil die Architektur „accessible at EL0“ so auslegt, dass lesbare Berechtigung erforderlich ist, nicht nur ausführbar. Das führt zu einem PAN-Bypass in bestimmten Konfigurationen.
- Deshalb könnte in iOS / XNU, falls execute-only user pages erlaubt wären (wie bei manchen JIT- oder code-cache-Setups), der Kernel fälschlicherweise von ihnen lesen, selbst mit aktivem PAN. Dies ist ein bekanntes subtiles Angriffsfenster in einigen ARMv8+-Systemen.
#### PXN (Privileged eXecute Never)
- **PXN** ist ein Page-Table-Flag (in Page-Table-Einträgen, Leaf- oder Block-Einträgen), das angibt, dass die Seite **nicht ausführbar im privilegierten Modus** ist (d. h. wenn EL1 sie ausführt).
- PXN verhindert, dass der Kernel (oder anderer privilegierter Code) in User-Space-Pages springt oder Instruktionen von dort ausführt, selbst wenn die Kontrolle umgeleitet wird. Effektiv verhindert es eine Kernel-Level-Control-Flow-Umleitung in User-Speicher.
- Kombiniert mit PAN stellt dies sicher, dass:
1. Der Kernel standardmäßig nicht auf User-Space-Daten lesen oder schreiben kann (PAN)
2. Der Kernel keinen User-Space-Code ausführen kann (PXN)
- Im ARMv8 Page-Table-Format haben Leaf-Einträge ein `PXN`-Bit (und auch `UXN` für unprivileged execute-never) in ihren Attributbits.
Selbst wenn der Kernel einen korrumpierten Funktionszeiger hat, der auf User-Speicher zeigt, und er dorthin zu springen versucht, würde das PXN-Bit einen Fault verursachen.
#### Memory-Permission-Modell & wie PAN und PXN auf Page-Table-Bits abgebildet werden
Um zu verstehen, wie PAN / PXN funktionieren, müssen Sie sehen, wie ARMs Translation- und Permission-Modell arbeitet (vereinfacht):
- Jeder Page- oder Block-Eintrag hat Attributfelder, einschließlich **AP[2:1]** für Zugriffsberechtigungen (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 Semantik: privilegierte Zugriffe auf Seiten, die als „accessible by EL0“ markiert sind (d. h. user-accessible), sind nicht erlaubt (Fault).
- Wegen des erwähnten Bugs zählen Seiten, die nur ausführbar markiert sind, unter bestimmten Implementierungen möglicherweise nicht als „accessible by EL0“, wodurch PAN umgangen werden kann.
- Wenn das PXN-Bit einer Seite gesetzt ist, ist Ausführung verboten, selbst wenn der Instruction-Fetch von einem höheren Privileg-Level kommt.
#### Kernel-Nutzung von PAN / PXN in einem gehärteten OS (z. B. iOS / XNU)
In einem gehärteten Kernel-Design (wie Apple es nutzen könnte):
- Der Kernel aktiviert PAN standardmäßig (sodass privilegierter Code eingeschränkt ist).
- In Pfaden, die berechtigterweise Benutzerspeicher lesen oder schreiben müssen (z. B. Syscall-Buffer-Kopien, I/O, read/write user pointer), deaktiviert der Kernel PAN vorübergehend **oder** verwendet spezielle Instruktionen, um den Zugriff zu erlauben.
- Nach Abschluss des Zugriffs auf User-Daten muss PAN wieder aktiviert werden.
- PXN wird über Page-Tables erzwungen: User-Pages haben PXN = 1 (sodass der Kernel sie nicht ausführen kann), Kernel-Pages haben PXN nicht gesetzt (sodass Kernel-Code ausgeführt werden kann).
- Der Kernel muss sicherstellen, dass keine Code-Pfade zur Ausführung in User-Memory führen (das würde PXN umgehen) — Exploit-Ketten, die auf „Jump into user-controlled shellcode“ beruhen, werden dadurch blockiert.
Wegen des beschriebenen PAN-Bypass durch execute-only Seiten könnte ein reales System wie Apple entweder execute-only User-Pages deaktivieren/verbieten oder die Spezifikationsschwäche anderweitig patchen.
#### Angriffsflächen, Bypässe und Mitigations
- **PAN-Bypass durch execute-only pages**: wie besprochen erlaubt die Spezifikation eine Lücke: User-Pages, die execute-only sind (keine Read-Perms), werden unter bestimmten Implementierungen eventuell nicht als „accessible at EL0“ gewertet, sodass PAN Kernel-Reads von solchen Seiten nicht blockiert. Das gibt dem Angreifer einen ungewöhnlichen Pfad, Daten über „execute-only“-Abschnitte einzuschleusen.
- **Temporales Fenster-Exploit**: wenn der Kernel PAN für ein länger als nötiges Zeitfenster deaktiviert, könnte ein Race oder ein bösartiger Pfad dieses Fenster ausnutzen, um unerwünschte User-Memory-Zugriffe durchzuführen.
- **Vergessenes Wiedereinschalten**: wenn Code-Pfade vergessen, PAN wieder zu aktivieren, könnten folgende Kernel-Operationen fälschlicherweise auf User-Speicher zugreifen.
- **Fehlkonfiguration von PXN**: wenn Page-Tables PXN für User-Pages nicht setzen oder User-Code-Pages falsch mappen, könnte der Kernel dazu verleitet werden, User-Space-Code auszuführen.
- **Spekulation / Side-Channels**: analog zu spekulativen Bypässen könnte es mikroarchitekturbedingte Nebeneffekte geben, die eine transiente Verletzung von PAN / PXN-Prüfungen verursachen (solche Angriffe sind jedoch stark CPU-Design-abhängig).
- **Komplexe Interaktionen**: Bei fortgeschrittenen Features (z. B. JIT, shared memory, just-in-time code regions) benötigt der Kernel fein granulare Kontrolle, um bestimmte Speicherzugriffe oder Ausführung in user-mapped Regionen zu erlauben; diese sicher unter PAN/PXN-Constraints zu entwerfen ist nicht trivial.
#### Example
<details>
<summary>Code Example</summary>
Hier sind illustrative Pseudo-Assembly-Sequenzen, die das Aktivieren/Deaktivieren von PAN rund um Zugriffe auf Benutzerspeicher zeigen und wie ein Fault auftreten kann.
</details>
<div class="codeblock_filename_container"><span class="codeblock_filename_inner hljs"> </span></div>
// Suppose kernel entry point, PAN is enabled (privileged code cannot access user memory by default)
; Kernel receives a syscall with user pointer in X0 ; wants to read an integer from user space mov X1, X0 ; X1 = user pointer
; disable PAN to allow privileged access to user memory MSR PSTATE.PAN, #0 ; clear PAN bit, disabling the restriction
ldr W2, [X1] ; now allowed load from user address
; re-enable PAN before doing other kernel logic MSR PSTATE.PAN, #1 ; set PAN
; ... further kernel work ...
; Later, suppose an exploit corrupts a pointer to a user-space code page and jumps there BR X3 ; branch to X3 (which points into user memory)
; Because the target page is marked PXN = 1 for privileged execution, ; the CPU throws an exception (fault) and rejects execution
Wenn der Kernel PXN auf dieser Benutzerseite **nicht** gesetzt hätte, könnte der Branch erfolgreich ausgeführt werden — was unsicher wäre.
Wenn der Kernel vergisst, PAN nach dem Zugriff auf Benutzerspeicher wieder zu aktivieren, entsteht ein Zeitfenster, in dem weitere Kernel-Logik versehentlich beliebigen Benutzerspeicher lesen/schreiben könnte.
Wenn der Benutzerzeiger auf eine execute-only-Seite zeigt (Benutzerseite mit nur Ausführungsberechtigung, ohne Lese-/Schreibrechte), kann unter dem PAN-Spezifikations-Bug `ldr W2, [X1]` möglicherweise **keinen** Fault auslösen, selbst wenn PAN aktiviert ist — was je nach Implementierung einen Bypass-Exploit ermöglichen kann.
</details>
<details>
<summary>Example</summary>
Eine Kernel-Schwachstelle versucht, einen vom Benutzer bereitgestellten Funktionszeiger zu übernehmen und im Kernel-Kontext aufzurufen (z. B. `call user_buffer`). Unter PAN/PXN ist diese Operation verboten oder führt zu einem Fault.
</details>
---
### 11. **Top Byte Ignore (TBI) / Pointer Tagging**
**Eingeführt in ARMv8.5 / neuer (oder optionale Erweiterung)**
TBI bedeutet, dass das Top-Byte (das höchstwertige Byte) eines 64-Bit-Zeigers bei der Adressübersetzung ignoriert wird. Dadurch können das OS oder die Hardware Tag-Bits im Top-Byte des Zeigers einbetten, ohne die tatsächlich adressierte Adresse zu verändern.
- TBI steht für **Top Byte Ignore** (manchmal auch *Address Tagging* genannt). Es ist eine Hardware-Funktion (in vielen ARMv8+-Implementierungen verfügbar), die die **obersten 8 Bits** (Bits 63:56) eines 64-Bit-Zeigers bei der **Adressübersetzung / load/store / instruction fetch** ignoriert.
- Effektiv behandelt die CPU einen Zeiger `0xTTxxxx_xxxx_xxxx` (wobei `TT` = Top-Byte) für die Zwecke der Adressübersetzung wie `0x00xxxx_xxxx_xxxx` und maskiert das Top-Byte. Das Top-Byte kann von Software verwendet werden, um **Metadata / Tag-Bits** zu speichern.
- Das gibt der Software einen „kostenlosen“ in-band Platz, um ein Byte Tag in jeden Zeiger einzubetten, ohne zu verändern, auf welche Speicheradresse er zeigt.
- Die Architektur stellt sicher, dass loads, stores und instruction fetches den Zeiger mit maskiertem Top-Byte (d. h. ohne Tag) behandeln, bevor der eigentliche Speicherzugriff ausgeführt wird.
Damit entkoppelt TBI den **logischen Zeiger** (Zeiger + Tag) von der **physischen Adresse**, die für Speicheroperationen verwendet wird.
#### Why TBI: Use cases and motivation
- Pointer tagging / metadata: Sie können zusätzliche Metadata (z. B. Objekt-Typ, Version, Bounds, Integritäts-Tags) in diesem Top-Byte speichern. Wenn Sie den Zeiger später verwenden, wird das Tag auf Hardware-Ebene ignoriert, sodass Sie es nicht manuell vor dem Speicherzugriff entfernen müssen.
- Memory tagging / MTE (Memory Tagging Extension): TBI ist der zugrundeliegende Hardware-Mechanismus, auf dem MTE aufbaut. In ARMv8.5 nutzt die Memory Tagging Extension die Bits 59:56 des Zeigers als **logisches Tag** und vergleicht es mit einem **allocation tag**, das im Speicher abgelegt ist.
- Enhanced security & integrity: Durch Kombination von TBI mit pointer authentication (PAC) oder Laufzeitprüfungen können Sie erzwingen, dass nicht nur der Zeigerwert, sondern auch das Tag korrekt ist. Ein Angreifer, der einen Zeiger ohne korrektes Tag überschreibt, erzeugt ein Tag-Mismatch.
- Compatibility: Da TBI optional ist und Tag-Bits von der Hardware ignoriert werden, läuft existierender ungetaggter Code weiterhin normal. Die Tag-Bits werden effektiv zu „don’t care“-Bits für Legacy-Code.
#### Example
<details>
<summary>Example</summary>
Ein Funktionszeiger enthielt ein Tag im Top-Byte (z. B. `0xAA`). Ein Exploit überschreibt die niederwertigen Bits des Zeigers, vernachlässigt jedoch das Tag, sodass bei Kernel-Validierung oder Sanitization der Zeiger fehlschlägt oder verworfen wird.
</details>
---
### 12. **Page Protection Layer (PPL)**
**Eingeführt in späten iOS-Versionen / moderner Hardware (iOS ~17 / Apple silicon / High-End-Modelle)** (einige Berichte zeigen PPL in macOS / Apple silicon, aber Apple übernimmt analoge Schutzmechanismen auch für iOS)
- PPL ist als **intra-kernel Schutzgrenze** konzipiert: Selbst wenn der Kernel (EL1) kompromittiert ist und Lese-/Schreibzugriff hat, sollte er nicht frei bestimmte **sensible Seiten** (insbesondere Page Tables, Code-Signing-Metadaten, Kernel-Code-Seiten, Entitlements, trust caches usw.) verändern können.
- Es schafft effektiv ein **„Kernel im Kernel“** — eine kleinere vertrauenswürdige Komponente (PPL) mit **elevierten Rechten**, die allein geschützte Seiten modifizieren darf. Anderer Kernel-Code muss PPL-Routinen aufrufen, um Änderungen vorzunehmen.
- Das reduziert die Angriffsfläche für Kernel-Exploits: Selbst mit vollständigem arbitrary R/W/execute im Kernel-Modus muss Exploit-Code außerdem den PPL-Bereich kompromittieren (oder PPL umgehen), um kritische Strukturen zu verändern.
- Auf neuerer Apple-Hardware (A15+ / M2+) stellt Apple teilweise auf **SPTM (Secure Page Table Monitor)** um, das in vielen Fällen PPL für Page-Table-Schutz auf diesen Plattformen ersetzt.
So wird angenommen, dass PPL arbeitet, basierend auf öffentlicher Analyse:
#### Use of APRR / permission routing (APRR = Access Permission ReRouting)
- Apple-Hardware nutzt einen Mechanismus namens **APRR (Access Permission ReRouting)**, der es Page Table Entries (PTEs) erlaubt, kleine Indizes statt voller Permission-Bits zu enthalten. Diese Indizes werden über APRR-Register auf die tatsächlichen Berechtigungen abgebildet. Das erlaubt dynamisches Remapping von Berechtigungen pro Domain.
- PPL nutzt APRR, um Privilegien innerhalb des Kernel-Kontexts zu trennen: Nur die PPL-Domain darf die Abbildung zwischen Indizes und effektiven Berechtigungen aktualisieren. Wenn nicht-PPL-Kernel-Code einen PTE schreibt oder versucht, Berechtigungsbits umzuschalten, verhindert die APRR-Logik dies (oder erzwingt eine read-only-Abbildung).
- PPL-Code selbst läuft in einem eingeschränkten Bereich (z. B. `__PPLTEXT`), der außerhalb von Eintritten normalerweise nicht ausführbar oder nicht beschreibbar ist, bis Entry-Gates ihn temporär freigeben. Der Kernel ruft PPL-Einstiegspunkte („PPL routines“) auf, um sensible Operationen durchzuführen.
#### Gate / Entry & Exit
- Wenn der Kernel eine geschützte Seite modifizieren muss (z. B. die Berechtigungen einer Kernel-Code-Seite ändern oder Page Tables modifizieren), ruft er eine **PPL-Wrapper**-Routine auf, die Validierung durchführt und dann in die PPL-Domain wechselt. Außerhalb dieser Domain sind die geschützten Seiten effektiv schreibgeschützt oder nicht änderbar durch den Hauptkernel.
- Während des PPL-Eintrags werden die APRR-Mappings so angepasst, dass Speicherseiten im PPL-Bereich innerhalb von PPL **ausführbar & beschreibbar** sind. Beim Verlassen werden sie wieder auf read-only / non-writable gesetzt. Das stellt sicher, dass nur überprüfte PPL-Routinen geschützte Seiten schreiben können.
- Außerhalb von PPL führen Schreibversuche durch Kernel-Code auf diesen geschützten Seiten zu einem Fault (Permission denied), weil die APRR-Abbildung für diese Code-Domain Schreiben nicht erlaubt.
#### Protected page categories
Typische Seiten, die PPL schützt, umfassen:
- Page-table-Strukturen (Translation Table Entries, Mapping-Metadaten)
- Kernel-Code-Seiten, besonders solche mit kritischer Logik
- Code-Sign-Metadaten (trust caches, Signatur-Blobs)
- Entitlement-Tabellen, Signature-Enforcement-Tabellen
- Andere hochrelevante Kernel-Strukturen, deren Patchen ein Umgehen von Signaturchecks oder Manipulation von Credentials erlauben würde
Die Idee ist, dass selbst wenn der Kernel-Speicher komplett kontrolliert wird, der Angreifer diese Seiten nicht einfach patchen oder neu schreiben kann, es sei denn, er kompromittiert ebenfalls PPL-Routinen oder umgeht PPL.
#### Known Bypasses & Vulnerabilities
1. Project Zero’s PPL bypass (stale TLB trick)
- Ein öffentlicher Writeup von Project Zero beschreibt einen Bypass, der **stale TLB entries** ausnutzt.
- Die Idee:
1. Zwei physische Seiten A und B allozieren und als PPL-Seiten markieren (also geschützt).
2. Zwei virtuelle Adressen P und Q so mappen, dass ihre L3 translation table pages von A bzw. B stammen.
3. Einen Thread starten, der kontinuierlich Q zugreift, um dessen TLB-Eintrag lebendig zu halten.
4. `pmap_remove_options()` aufrufen, um Mappings beginnend bei P zu entfernen; aufgrund eines Bugs entfernt der Code versehentlich die TTEs für sowohl P als auch Q, invalidiert aber nur den TLB-Eintrag für P, sodass Qs stale entry weiterlebt.
5. B (die Seite der Tabelle von Q) wiederverwenden, um beliebigen Speicher zu mappen (z. B. PPL-geschützte Seiten). Weil der stale TLB-Eintrag weiterhin Qs alte Abbildung enthält, bleibt diese Abbildung in diesem Kontext gültig.
6. Auf diese Weise kann der Angreifer eine schreibbare Abbildung von PPL-geschützten Seiten herstellen, ohne über die PPL-Schnittstelle gehen zu müssen.
- Dieser Exploit erforderte feine Kontrolle über physische Mappings und TLB-Verhalten. Er zeigt, dass eine Sicherheitsgrenze, die auf TLB-/Mapping-Korrektheit beruht, sehr sorgfältig bei TLB-Invalidierungen und Mapping-Konsistenz sein muss.
- Project Zero kommentierte, dass solche Bypässe subtil und selten sind, aber in komplexen Systemen möglich. Dennoch bewerten sie PPL als eine solide Abschwächung.
2. Other potential hazards & constraints
- Wenn ein Kernel-Exploit direkt in PPL-Routinen eintreten kann (z. B. durch Aufruf der PPL-Wrapper), könnte er Einschränkungen umgehen. Daher ist Argument-Validation kritisch.
- Bugs im PPL-Code selbst (z. B. arithmetische Overflows, Boundary-Checks) können Modifikationen innerhalb von PPL erlauben. Project Zero beobachtete, dass ein solcher Bug in `pmap_remove_options_internal()` in ihrem Bypass ausgenutzt wurde.
- Die PPL-Grenze ist untrennbar an die Hardware-Enforcement (APRR, memory controller) gebunden, daher ist sie nur so stark wie die Hardware-Implementierung.
#### Example
<details>
<summary>Code Example</summary>
Here’s a simplified pseudocode / logic showing how a kernel might call into PPL to modify protected pages:
<div class="codeblock_filename_container"><span class="codeblock_filename_inner hljs">c</span></div>
```c
// In kernel (outside PPL domain)
function kernel_modify_pptable(pt_addr, new_entry) {
// validate arguments, etc.
return ppl_call_modify(pt_addr, new_entry) // call PPL wrapper
}
// In PPL (trusted domain)
function ppl_call_modify(pt_addr, new_entry) {
// temporarily enable write access to protected pages (via APRR adjustments)
aprr_set_index_for_write(PPL_INDEX)
// perform the modification
*pt_addr = new_entry
// restore permissions (make pages read-only again)
aprr_restore_default()
return success
}
// If kernel code outside PPL does:
*pt_addr = new_entry // a direct write
// It will fault because APRR mapping for non-PPL domain disallows write to that page
Der Kernel kann viele normale Operationen ausführen, aber nur über die ppl_call_*-Routinen kann er geschützte Mappings ändern oder Code patchen.
Example
Ein Kernel-Exploit versucht die entitlement table zu überschreiben oder die code-sign-Enforcement zu deaktivieren, indem er einen kernel signature blob modifiziert. Da jene Seite PPL-protected ist, wird der Schreibzugriff blockiert, sofern er nicht über die PPL-Schnittstelle erfolgt. Selbst mit Kernel-Code-Ausführung 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 / Replacements / Future
- Auf Apples modernen SoCs (A15 oder später, M2 oder später) unterstützt Apple SPTM (Secure Page Table Monitor), welches PPL ersetzt für den Schutz von page tables.
- Apple schreibt in der Dokumentation: “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 vermutlich mehr Policy-Enforcement in einen höher privilegierten Monitor außerhalb der Kernel-Kontrolle und reduziert damit die Trust-Boundary weiter.
MTE | EMTE | MIE
Hier eine übergeordnete Beschreibung, wie EMTE unter Apples MIE-Setup arbeitet:
- Tag assignment
- Wenn Speicher alloziert 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 mechanisms).
- Tag checking on access
- Wann immer ein Load oder Store mit einem Pointer ausgeführt wird, prüft die Hardware, dass das Tag des Pointers mit dem Tag des Speicherblocks (allocation tag) übereinstimmt. Bei Mismatch folgt sofort ein Fault (da synchron).
- Weil es synchron ist, gibt es kein Fenster für „delayed detection“.
- Retagging on free / reuse
- Wenn Speicher freigegeben wird, ändert der Allocator das Tag des Blocks (so dass ältere Pointer mit alten Tags nicht mehr passen).
- Ein use-after-free-Pointer hätte daher ein veraltetes Tag und würde beim Zugriff einen Mismatch erzeugen.
- Neighbor-tag differentiation to catch overflows
- Benachbarte Allokationen erhalten unterschiedliche Tags. Wenn ein Buffer-Overflow in den Speicher des Nachbarn überschreibt, verursacht der Tag-Mismatch einen Fault.
- Das ist besonders wirksam, um kleine Overflows zu entdecken, die Grenzen überschreiten.
- Tag confidentiality enforcement
- Apple muss verhindern, dass Tag-Werte leak (denn wenn ein Angreifer das Tag kennt, könnte er Pointer mit korrekten Tags konstruieren).
- Sie fügen Schutzmechanismen hinzu (microarchitectural / speculative controls), um side-channel leak von Tag-Bits zu vermeiden.
- Kernel and user-space integration
- Apple nutzt EMTE nicht nur im User-Space, sondern auch in Kernel- und OS-kritischen Komponenten (um den Kernel gegen Memory-Corruption zu schützen).
- Die Hardware/OS stellen sicher, dass die Tag-Regeln gelten, selbst wenn der Kernel im Auftrag des User-Space ausgeführt wird.
Example
``` Allocate A = 0x1000, assign tag T1 Allocate B = 0x2000, assign tag T2// pointer P points into A with tag T1 P = (T1 << 56) | 0x1000
// Valid store *(P + offset) = value // tag T1 matches allocation → allowed
// Overflow attempt: P’ = P + size_of_A (into B region) *(P' + delta) = value → pointer includes tag T1 but memory block has tag T2 → mismatch → fault
// Free A, allocator retags it to T3 free(A)
// Use-after-free: *(P) = value → pointer still has old tag T1, memory region is now T3 → mismatch → fault
</details>
#### Einschränkungen & Herausforderungen
- **Intrablock overflows**: Wenn ein Overflow innerhalb derselben Allocation bleibt (überschreitet keine Grenze) und der tag gleich bleibt, fängt tag mismatch das nicht ab.
- **Tag width limitation**: Nur wenige Bits (z. B. 4 bits oder eine kleine Domäne) stehen für Tags zur Verfügung — begrenzter Namespace.
- **Side-channel leaks**: Wenn tag bits leaked werden können (via cache / speculative execution), kann ein Angreifer gültige tags lernen und umgehen. Apples Tag Confidentiality Enforcement soll das mindern.
- **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 in Teilen, die EMTE nicht unterstützen, muss ein Fallback existieren. Apple gibt an, dass MIE nur auf Geräten mit Unterstützung aktiviert wird.
- **Complex allocator logic**: Der allocator muss Tags verwalten, retagging durchführen, Grenzen ausrichten und Tag-Kollisionen vermeiden. Bugs in der Allocator-Logik könnten neue Verwundbarkeiten einführen.
- **Mixed memory / hybrid areas**: Ein Teil des Speichers kann untagged (legacy) bleiben, was die Interoperabilität erschwert.
- **Speculative / transient attacks**: Wie bei vielen mikroarchitektonischen Protektionen könnten speculative execution oder micro-op-Fusionen Checks transient umgehen oder tag bits leaken.
- **Limited to supported regions**: Apple könnte EMTE nur in selektiven, risikoreichen Bereichen (Kernel, sicherheitskritische Subsysteme) durchsetzen, nicht überall.
---
## 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** | Unterstützt synchronous und asynchronous modes. In async werden tag mismatches später gemeldet (verzögert) | Apple besteht standardmäßig auf **synchronous mode** — tag mismatches werden sofort erkannt, keine verzögerten/race windows erlaubt. |
| **Coverage of non-tagged memory** | Zugriffe auf non-tagged memory (z. B. globals) können in manchen Implementierungen Checks umgehen | EMTE verlangt, dass Zugriffe von einem tagged Bereich auf non-tagged memory ebenfalls das Wissen über Tags validieren, was Mixing-Allokationen erschwert. |
| **Tag confidentiality / secrecy** | Tags könnten beobachtbar sein oder via side channels geleakt werden | Apple fügt **Tag Confidentiality Enforcement** hinzu, das versucht, die Leaks von Tag-Werten (z. B. via speculative side-channels) zu verhindern. |
| **Allocator integration & retagging** | MTE überlässt viel der Allocator-Logik der 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** | Auf vielen Plattformen ist MTE optional oder standardmäßig aus | 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 die Hardware als auch den Software-Stack kontrolliert, kann es EMTE strikt durchsetzen, Performance-Probleme vermeiden und Side-Channel-Lücken schließen.
---
## Wie EMTE in der Praxis funktioniert (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 diesen tag in seinen oberen Bits (mittels 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, ob der pointer’s tag mit dem memory block’s tag (allocation tag) übereinstimmt. Bei mismatch wird sofort ein Fault ausgelöst (da synchronous).
- Weil es synchronous ist, gibt es kein Fenster für “delayed detection”.
3. **Retagging on free / reuse**
- Wenn Speicher freigegeben wird, ändert der allocator den Block’s tag (ältere pointer mit alten tags stimmen dann nicht mehr überein).
- Ein use-after-free pointer hätte daher einen veralteten tag und würde beim Zugriff mismatchen.
4. **Neighbor-tag differentiation to catch overflows**
- Benachbarte Allocations erhalten unterschiedliche tags. Wenn ein Buffer overflow in den Speicher des Nachbarn schreibt, führt das zu tag mismatch und Fault.
- Das ist besonders effektiv, um kleine Overflows, die Grenzen überschreiten, zu erkennen.
5. **Tag confidentiality enforcement**
- Apple muss verhindern, dass Tag-Werte geleakt werden (denn wenn ein Angreifer den tag kennt, könnte er pointer mit korrektem tag herstellen).
- Sie implementieren Schutzmaßnahmen (mikroarchitektonische / speculative Controls), um das Leaken von tag bits zu vermeiden.
6. **Kernel and user-space integration**
- Apple nutzt EMTE nicht nur im user-space, sondern auch in Kernel / OS-kritischen Komponenten (zum Schutz des Kernels vor Memory-Corruption).
- Hardware und OS stellen sicher, dass Tag-Regeln auch gelten, wenn der Kernel im Auftrag von User-Space ausgeführt wird.
Da EMTE in MIE integriert ist, verwendet Apple EMTE im synchronous mode über wichtige Angriffsflächen hinweg, nicht nur als opt-in oder Debug-Modus.
---
## 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-Style **Signal** (wie `SIGSEGV`, `SIGBUS`, `SIGILL`, ...) wird.
Dieser Prozess umfasst mehrere Ebenen der Exception-Propagation und -Handhabung, bevor er den User-Space erreicht oder in ein BSD signal umgewandelt wird.
### Ausnahmefluss (High-Level)
1. **CPU löst eine synchrone exception aus** (z. B. ungültiger pointer Dereference, PAC failure, illegal instruction, etc.).
2. **Low-level trap handler** läuft (`trap.c`, `exception.c` in XNU source).
3. Der trap handler ruft **`exception_triage()`** auf, den Kern der Mach-Exception-Handhabung.
4. `exception_triage()` entscheidet, wie die Exception geroutet wird:
- Zuerst an den **thread's exception port**.
- Dann an den **task's exception port**.
- Dann an den **host's exception port** (häufig `launchd` oder `ReportCrash`).
Wenn keiner dieser Ports die Exception behandelt, kann der Kernel:
- **Sie in ein BSD signal konvertieren** (für User-Space Prozesse).
- **Panic** auslösen (bei Kernel-Space Exceptions).
### Kernfunktion: `exception_triage()`
Die Funktion `exception_triage()` routed 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`.
<div class="codeblock_filename_container"><span class="codeblock_filename_inner hljs">c</span></div>
```c
void exception_triage(exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt);
Typischer Aufrufablauf:
exception_triage() └── exception_deliver() ├── exception_deliver_thread() ├── exception_deliver_task() └── exception_deliver_host()
If all fail → handled by bsd_exception() → translated into a signal like SIGSEGV.
Exception Ports
Jedes Mach-Objekt (thread, task, host) kann exception ports registrieren, an die exception messages 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 mask (welche Exceptions er empfangen möchte)
- Einen port name (Mach port, um Nachrichten zu empfangen)
- Ein behavior (wie der Kernel die Nachricht sendet)
- Einen flavor (welchen Thread state einzuschließen)
Debugger und Ausnahmebehandlung
Ein debugger (z. B. LLDB) setzt einen exception port auf die Ziel-Task oder den Thread, normalerweise mit task_set_exception_ports().
Wenn eine Exception auftritt:
- Die Mach-Nachricht wird an den Debugger-Prozess gesendet.
- Der Debugger kann entscheiden, die Exception zu handle (fortsetzen, Register ändern, Instruktion überspringen) oder nicht zu handle.
- Wenn der Debugger sie nicht behandelt, wird die Exception an die nächste Ebene weitergereicht (task → host).
Ablauf von EXC_BAD_ACCESS
-
Thread dereferenziert einen ungültigen Pointer → CPU löst einen Data Abort aus.
-
Der Kernel-Trap-Handler ruft
exception_triage(EXC_BAD_ACCESS, ...)auf. -
Nachricht gesendet an:
-
Thread port → (Debugger kann Breakpoint abfangen).
-
Wenn der Debugger ignoriert → Task port → (prozessweiter Handler).
-
Wenn ignoriert → Host port (meist ReportCrash).
- Wenn niemand behandelt →
bsd_exception()übersetzt inSIGSEGV.
PAC Exceptions
Wenn Pointer Authentication (PAC) fehlschlägt (Signatur stimmt nicht überein), wird eine spezielle Mach-Exception ausgelöst:
EXC_ARM_PAC(Typ)- Codes können Details enthalten (z. B. Key-Typ, Pointer-Typ).
Wenn das Binary das Flag TFRO_PAC_EXC_FATAL gesetzt hat, behandelt der Kernel PAC-Fehler als fatal und umgeht die Abfangmöglichkeiten des Debuggers. Das verhindert, dass Angreifer Debugger verwenden, um PAC-Prüfungen zu umgehen, und ist für platform binaries aktiviert.
Software Breakpoints
Ein Software-Breakpoint (int3 auf x86, brk auf ARM64) wird durch absichtliches Hervorrufen eines Faults implementiert.
Der Debugger fängt dies über den exception port ab:
- Ändert Instruction Pointer oder Speicher.
- Stellt die originale Instruktion 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, in diesem Fall erreicht sie niemals den Debugger.
Konvertierung zu BSD-Signalen
Wenn kein Handler die Exception akzeptiert:
-
Der Kernel ruft
task_exception_notify() → bsd_exception()auf. -
Das mappt Mach-Exceptions auf Signale:
| 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 (on non-fatal) |
Wichtige Dateien im XNU-Source
osfmk/kern/exception.c→ Kern vonexception_triage(),exception_deliver_*().bsd/kern/kern_sig.c→ Signal-Delivery-Logik.osfmk/arm64/trap.c→ Low-Level Trap-Handler.osfmk/mach/exc.h→ Exception-Codes und -Strukturen.osfmk/kern/task.c→ Setup von Task-Exception-Ports.
Alter Kernel-Heap (Pre-iOS 15 / Pre-A12-Ära)
Der Kernel verwendete einen zone allocator (kalloc), aufgeteilt in feste "Zonen".
Jede Zone speichert nur Allokationen einer einzigen Größenklasse.
Aus dem Screenshot:
| Zone Name | Element Size | Beispielverwendung |
|---|---|---|
default.kalloc.16 | 16 bytes | Sehr kleine Kernel-Strukturen, Pointer. |
default.kalloc.32 | 32 bytes | Kleine Strukturen, Objekt-Header. |
default.kalloc.64 | 64 bytes | IPC-Nachrichten, winzige Kernel-Buffer. |
default.kalloc.128 | 128 bytes | Mittlere Objekte wie Teile von OSObject. |
| … | … | … |
default.kalloc.1280 | 1280 bytes | Große Strukturen, IOSurface/Graphics-Metadaten. |
Wie es funktionierte:
- Jede Allokationsanfrage wurde auf die nächstgrößere Zone gerundet.
(z. B. landet eine 50-Byte-Anfrage in der
kalloc.64-Zone). - Speicher in jeder Zone wurde in einer freelist gehalten — vom Kernel freigegebene Chunks kamen in diese Zone zurück.
- Wenn du einen 64-Byte-Buffer overflowtest, würdest du das nächste Objekt in derselben Zone überschreiben.
Deshalb waren heap spraying / feng shui so effektiv: du konntest Objekt-Nachbarn durch Spraying von Allokationen derselben Größenklasse vorhersagen.
Die freelist
Innerhalb jeder kalloc-Zone wurden freigegebene Objekte nicht direkt an das System zurückgegeben — sie gingen in eine freelist, eine verkettete Liste verfügbarer Chunks.
-
Wenn ein Chunk freigegeben wurde, schrieb der Kernel einen Pointer an den Beginn dieses Chunks → die Adresse des nächsten freien Chunks in derselben Zone.
-
Die Zone hielt einen HEAD-Pointer auf den ersten freien Chunk.
-
Die Allokation verwendete immer das aktuelle HEAD:
-
Pop HEAD (gibt diesen Speicher an den Caller zurück).
-
Update HEAD = HEAD->next (gespeichert im Header des freigegebenen Chunks).
-
Freigaben schoben Chunks zurück auf die Liste:
-
freed_chunk->next = HEAD -
HEAD = freed_chunk
Die freelist war also einfach eine verkettete Liste, aufgebaut im freigegebenen Speicher selbst.
Normalzustand:
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 freien Chunks = freelist pointer, könnte ein Angreifer diesen korrumpieren:
-
Heap overflow in einen angrenzenden freigegebenen Chunk → überschreibt dessen “next” pointer.
-
Use-after-free: Schreiben in ein freigegebenes Objekt → überschreibt dessen “next” pointer.
Dann, bei der nächsten Allocation dieser Größe:
- Der Allocator poppt den korrumpierten Chunk.
- Folgt dem vom Angreifer gelieferten “next” pointer.
- Gibt einen Pointer auf beliebigen Speicher zurück, wodurch fake object primitives oder gezieltes Überschreiben 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 Exploits vor den Hardening-Maßnahmen hocheffektiv: vorhersehbare Nachbarn durch heap sprays, raw pointer freelist links und keine Typtrennung erlaubten es Angreifern, UAF/overflow-Bugs in beliebige Kontrolle über kernel memory zu eskalieren.
Heap Grooming / Feng Shui
Das Ziel von heap grooming ist es, 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 memory corruption zuverlässig das Victim-Objekt mit kontrollierten Daten überschreiben.
Schritte:
- Spray allocations (fill the holes)
- Mit der Zeit wird der kernel heap fragmentiert: manche Zonen haben Lücken, wo alte Objekte freed wurden.
- Der Angreifer erzeugt zuerst viele Dummy-Allocations, um diese Lücken zu füllen, sodass der heap „gepackt“ und vorhersehbar wird.
- Force new pages
- Sobald die Löcher gefüllt sind, müssen die nächsten Allocations von neuen pages in die Zone kommen.
- Frische pages bedeuten, dass Objekte zusammengeclustert werden und nicht über altes fragmentiertes memory verteilt sind.
- Das gibt dem Angreifer viel bessere Kontrolle über Nachbarn.
- Place attacker objects
- Der Angreifer sprayed erneut und erzeugt viele attacker-controlled Objekte in diesen neuen pages.
- Diese Objekte sind in Größe und Platzierung vorhersehbar (da sie alle zur selben Zone gehören).
- Free a controlled object (make a gap)
- Der Angreifer freed absichtlich 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.
- Victim object lands in the hole
- Der Angreifer veranlasst den Kernel, das Victim-Objekt zu allocaten (das Objekt, das er korruptieren 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.
- Overflow / UAF into victim
- Nun hat der Angreifer attacker-controlled Objekte um das Victim herum.
- Durch Overflow von einem seiner eigenen Objekte (oder durch Wiederverwendung eines freed Objekts) kann er zuverlässig die Speicherfelder des Victims mit gewählten Werten überschreiben.
Warum das funktioniert:
- Zone allocator predictability: Allocations der gleichen Größe kommen immer aus derselben zone.
- Freelist behavior: neue Allocations nutzen zuerst das zuletzt gefreete Chunk wieder.
- Heap sprays: Der Angreifer füllt memory mit vorhersehbarem Inhalt und kontrolliert so das Layout.
- Endergebnis: Der Angreifer kontrolliert, wo das Victim landet und welche Daten daneben stehen.
Modern Kernel Heap (iOS 15+/A12+ SoCs)
Apple hat den Allocator gehärtet und machte heap grooming deutlich schwieriger:
1. From Classic kalloc to kalloc_type
- Before: Es gab eine einzelne
kalloc.<size>zone für jede Größenklasse (16, 32, 64, … 1280, etc.). Jedes Objekt dieser Größe landete dort → attacker-Objekte konnten neben privilegierten kernel-Objekten sitzen. - 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 dieselbe Größe haben. - Die Zuordnung zwischen Objekt-Typ ↔ zone wird vom kalloc_type system zur Compile-Zeit generiert.
Ein Angreifer kann nicht mehr garantieren, dass kontrollierte Daten (OSData) neben sensitiven kernel-Objekten (task_t) gleicher Größe landen.
2. Slabs and Per-CPU Caches
- Der heap ist in slabs unterteilt (pages, die in fixed-size Chunks für diese zone zerteilt sind).
- Jede zone hat einen per-CPU cache, um Contention zu reduzieren.
- Allocation-Pfad:
- Versuche den per-CPU cache.
- Falls leer, nimm von der global freelist.
- Falls freelist leer, allociere einen neuen slab (eine oder mehrere pages).
- Vorteil: Diese Dezentralisierung macht heap sprays weniger deterministisch, da Allocations von verschiedenen 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 nutzt encoded freelist pointers (safe-linking-ähnlich wie Linux, eingeführt ~iOS 14).
- Jeder freelist pointer ist mit einem pro-zone secret cookie XOR-encodiert.
- Das verhindert, dass Angreifer einen gefälschten freelist pointer erstellen können, wenn sie eine write-Primitive haben.
- Manche Allocations werden in ihrer Platzierung innerhalb eines slabs randomisiert, sodass ein Spray keine Adjazenz garantiert.
4. Guarded Allocations
- Bestimmte kritische kernel-Objekte (z. B. credentials, task structures) werden in guarded zones alloziert.
- Diese Zonen fügen guard pages (nicht gemappter Speicher) zwischen slabs ein oder verwenden redzones um Objekte.
- Jede Overflow in die guard page löst einen Fault aus → sofortiger Panic statt stiller Korruption.
5. Page Protection Layer (PPL) and SPTM
- Selbst wenn du ein freed Objekt kontrollierst, kannst du nicht allen kernel memory ändern:
- 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 einzigen heap corruption zu beliebigen Änderungen 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 deren Fälschung oder Korruption schwieriger wird.
- (Added / Enhanced): Zonen können zone_require / zone enforcement durchsetzen, d. h. ein freigesetztes Objekt darf nur durch seine korrekte typed zone zurückgegeben werden; ungültige Cross-Zone frees können panicen 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 Requests (über ~16 KB) umgehen typed zones und werden direkt aus kernel VM (kmem) via Page-Allocations bedient.
- Diese sind weniger vorhersehbar, aber auch schwerer auszunutzen, da sie keine Slabs mit anderen Objekten teilen.
7. Allocation Patterns Attackers Target
Auch mit diesen Protections suchen Angreifer weiterhin nach:
- Reference count objects: Wenn du retain/release-Zähler manipulieren kannst, kannst du UAF verursachen.
- Objects with function pointers (vtables): Die Korruption eines solchen Objekts liefert immer noch Control Flow.
- Shared memory objects (IOSurface, Mach ports): Diese sind weiterhin Angriffsziel, weil sie user ↔ kernel überbrücken.
Aber — anders als früher — kannst du nicht einfach OSData sprayen und erwarten, dass es neben einem task_t landet. Du brauchst type-specific bugs oder info leaks, um erfolgreich zu sein.
Example: Allocation Flow in Modern Heap
Angenommen, userspace ruft IOKit auf, um ein OSData-Objekt zu allozieren:
- Type lookup →
OSDatamappt zurkalloc_type_osdatazone (Größe 64 bytes). - Check per-CPU cache for free elements.
- Wenn gefunden → return one.
- Wenn leer → global freelist prüfen.
- Wenn freelist leer → neuen slab allozieren (page von 4KB → 64 Chunks à 64 bytes).
- Gib das Chunk an den Caller zurück.
Freelist pointer protection:
- Jeder freed Chunk speichert die Adresse des nächsten freien Chunks, aber encodiert mit einem secret key.
- Das Überschreiben dieses Feldes mit Angreifer-Daten funktioniert nicht, sofern du den Key nicht kennst.
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 neueren Apple-OS-Versionen (insbesondere iOS 17+) führte Apple einen sichereren userland allocator ein, xzone malloc (XZM). Das ist das user-space Gegenstück zu Kerns kalloc_type und bringt Type Awareness, Metadaten-Isolation und Memory Tagging-Sicherheiten.
Goals & Design Principles
- Type segregation / type awareness: Gruppierung von Allocations nach Typ oder Nutzung (Pointer vs Data), um type confusion und cross-type reuse zu verhindern.
- Metadata isolation: Trennung von heap-Metadaten (z. B. free lists, size/state bits) vom Objekt-Payload, sodass out-of-bounds Writes weniger wahrscheinlich Metadaten korruptieren.
- Guard pages / redzones: Einfügen von nicht gemappten pages oder Padding um Allocations, um Overflows zu erkennen.
- Memory tagging (EMTE / MIE): Arbeit zusammen mit Hardware-Tagging, um UAF, OOB und invaliden Zugriff zu erkennen.
- Scalable performance: Geringe Overhead, Vermeidung übermäßiger Fragmentation und Unterstützung vieler Allocations pro Sekunde mit niedriger Latenz.
Architecture & Components
Unten die Hauptelemente des xzone Allocators:
Segment Groups & Zones
- Segment groups partitionieren den Adressraum nach Nutzungskategorien: z. B.
data,pointer_xzones,data_large,pointer_large. - Jede segment group enthält segments (VM-Bereiche), die Allocations für diese Kategorie hosten.
- Jedem Segment ist eine metadata slab (separater VM-Bereich) zugeordnet, die Metadaten (z. B. free/used bits, size classes) für dieses Segment speichert. Diese out-of-line (OOL) metadata stellt sicher, dass Metadaten nicht mit Objekt-Payloads vermischt sind, wodurch Overflow-Korruptionen gemildert werden.
- Segments werden in chunks (Slices) unterteilt, die wiederum in blocks (Allocation-Einheiten) zerteilt sind. Ein Chunk ist an eine bestimmte size class und segment group gebunden (d. h. alle Blocks in einem Chunk teilen dieselbe Größe & Kategorie).
- Für kleine/mittlere Allocations werden fixed-size Chunks verwendet; für große/huge Allokationen kann separat gemappt werden.
Chunks & Blocks
- Ein chunk ist eine Region (meist mehrere pages), die einer Größenklasse innerhalb einer Group gewidmet ist.
- Innerhalb eines Chunks sind blocks Slots für Allocations. Freed blocks werden über die metadata slab getrackt — z. B. via Bitmaps oder free lists, die out-of-line gespeichert sind.
- Zwischen oder innerhalb von Chunks können guard slices / guard pages eingefügt werden (z. B. nicht gemappte Slices), um OOB-Writes zu erwischen.
Type / Type ID
- Jeder Allocation-Site (oder Call zu malloc, calloc, etc.) ist ein type identifier (
malloc_type_id_t) zugeordnet, der kodiert, welche Art Objekt alloziert wird. Diese type ID wird an den Allocator übergeben, der sie nutzt, um die passende zone / den passenden segment auszuwählen. - Dadurch können zwei Allocations derselben Größe in völlig unterschiedlichen zones landen, wenn ihre Typen verschieden sind.
- In frühen iOS 17-Versionen waren nicht alle APIs (z. B. CFAllocator) vollständig type-aware; Apple behob einige dieser Schwächen in iOS 18.
Allocation & Freeing Workflow
Hier ein High-Level-Flow, wie Allocation und Deallocation in xzone ablaufen:
- malloc / calloc / realloc / typed alloc wird mit Größe und type ID aufgerufen.
- Der Allocator nutzt die type ID, um die richtige segment group / zone auszuwählen.
- Innerhalb dieser zone/segment sucht er einen Chunk mit freien Blocks der angeforderten Größe.
- Er kann lokale Caches / per-thread Pools oder free block lists aus der metadata konsultieren.
- Wenn kein freier Block vorhanden ist, kann er einen neuen Chunk in dieser zone allozieren.
- Die metadata slab wird aktualisiert (Free-Bit cleared, Bookkeeping).
- Falls Memory Tagging (EMTE) aktiv ist, bekommt der zurückgegebene Block ein Tag zugewiesen und die Metadaten werden als „live“ markiert.
- Wenn
free()aufgerufen wird:
- Der Block wird in der metadata als freed markiert (via OOL slab).
- Der Block kann in eine free list oder in einen Pool zur Wiederverwendung gelegt werden.
- Optional werden Block-Inhalte gecleared oder poisoned, um data leaks oder UAF-Exploits zu reduzieren.
- Der Hardware-Tag des Blocks kann invalidiert oder neu getaggt werden.
- Wenn ein gesamter Chunk frei wird (alle Blocks freed), kann der Allocator diesen Chunk reclaimen (unmappen oder an OS zurückgeben) bei Memory Pressure.
Security Features & Hardening
Verteidigungen im modernen userland xzone:
| Feature | Purpose | Notes |
|---|---|---|
| Metadata decoupling | Prevent overflow from corrupting metadata | Metadata lebt in separatem VM-Bereich (metadata slab) |
| Guard pages / unmapped slices | Catch out-of-bounds writes | Hilft, buffer overflows zu entdecken statt stillschweigend angrenzende Blocks zu korruptieren |
| Type-based segregation | Prevent cross-type reuse & type confusion | Selbst gleichgroße Allocations verschiedener Typen gehen in unterschiedliche zones |
| Memory Tagging (EMTE / MIE) | Detect invalid access, stale references, OOB, UAF | xzone arbeitet mit Hardware-EMTE im synchronen Modus („Memory Integrity Enforcement“) zusammen |
| Delayed reuse / poisoning / zap | Reduce chance of use-after-free exploitation | Freed Blocks können vergiftet, nullgesetzt oder zwischengelagert werden |
| Chunk reclamation / dynamic unmapping | Reduce memory waste and fragmentation | Ganze Chunks können ungemappt werden, wenn ungenutzt |
| Randomization / placement variation | Prevent deterministic adjacency | Blocks in einem Chunk und die Chunk-Auswahl können randomisierte Aspekte haben |
| Segregation of “data-only” allocations | Separate allocations that don’t store pointers | Reduziert 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 immer-aktiven, synchronen Modus über große Angriffspflächen bringt.
- Der xzone Allocator ist eine fundamentale Basis für MIE im User-Space: Allocations über xzone bekommen Tags und Zugriffe werden von der Hardware geprüft.
- In MIE sind Allocator, Tag-Assignment, Metadaten-Management und Tag-Confidentiality-Policies integriert, um sicherzustellen, dass memory errors (z. B. stale reads, OOB, UAF) sofort erkannt werden und nicht später ausgenutzt werden können.
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.
iMessage/Media Parser Zero-Click Chains
Imessage Media Parser Zero Click Coreaudio Pac Bypass
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
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
HackTricks