iOS Exploiting

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

iOS Exploit Mitigations

1. Code Signing / Runtime Signature Verification

Introduced early (iPhone OS → iOS) To jedna z podstawowych ochron: all executable code (apps, dynamic libraries, JIT-ed code, extensions, frameworks, caches) musi być kryptograficznie podpisany przez łańcuch certyfikatów zaufanych przez Apple. W czasie wykonywania, przed załadowaniem binarki do pamięci (lub przed wykonaniem skoków przez pewne granice), system sprawdza jej podpis. Jeśli code został zmodyfikowany (bit-flipped, patched) lub nie jest podpisany, załadunek się nie powiedzie.

  • Thwarts: etap „classic payload drop + execute” w chainach exploitów; arbitrary code injection; modyfikowanie istniejącej binarki w celu wstawienia złośliwej logiki.
  • Mechanism detail:
  • Mach-O loader (i dynamic linker) sprawdza strony code, segmenty, entitlements, team IDs oraz czy podpis pokrywa zawartość pliku.
  • Dla regionów pamięci takich jak JIT caches lub dynamicznie generowany code, Apple wymusza, by strony były podpisane lub walidowane przez specjalne API (np. mprotect z code-sign checks).
  • Podpis zawiera entitlements i identyfikatory; OS egzekwuje, że pewne API lub uprzywilejowane możliwości wymagają konkretnych entitlements, których nie da się sfałszować.
Example Załóżmy, że exploit uzyska wykonanie code w procesie i próbuje zapisać shellcode na heapie i skoczyć do niego. Na iOS ta strona musiałaby być oznaczona jako wykonywalna **i** spełniać constraints code-signature. Ponieważ shellcode nie jest podpisany certyfikatem Apple, skok się nie powiedzie lub system odmówi nadania temu regionowi pamięci uprawnień do wykonywania.

2. CoreTrust

Introduced around iOS 14+ era (or gradually in newer devices / later iOS) CoreTrust to subsystem wykonujący runtime signature validation binarek (w tym systemowych i użytkownika) względem Apple’s root certificate zamiast polegania na lokalnych cache’ach zaufania w userlandzie.

  • Thwarts: post-install tampering binarek, jailbreak techniques próbujące podmienić lub zpatchować systemowe biblioteki lub aplikacje użytkownika; oszukanie systemu przez zastąpienie zaufanych binarek złośliwymi odpowiednikami.
  • Mechanism detail:
  • Zamiast ufać lokalnej bazie zaufania, CoreTrust odnosi się do Apple’s root albo weryfikuje certyfikaty pośrednie w bezpiecznym łańcuchu.
  • Zapewnia wykrycie i odrzucenie modyfikacji (np. w filesystemie) istniejących binarek.
  • Wiąże entitlements, team IDs, flagi code signing i inne metadane z binarką w czasie załadunku.
Example Jailbreak mógłby próbować podmienić `SpringBoard` lub `libsystem` na poprawioną wersję by uzyskać persistencję. Gdy loader systemu lub CoreTrust sprawdza plik, zauważa mismatch podpisu (lub zmienione entitlements) i odmawia wykonania.

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

Introduced in many OSes earlier; iOS had NX-bit / w^x for a long time DEP wymusza, że strony oznaczone jako writable (dla danych) są non-executable, a strony oznaczone jako executable są non-writable. Nie można po prostu zapisać shellcode do heapu lub stacka i go wykonać.

  • Thwarts: bezpośrednie wykonywanie shellcode; klasyczny buffer-overflow → skok do wstrzykniętego shellcode.
  • Mechanism detail:
  • MMU / flagi ochrony pamięci (przez tabele stron) egzekwują separację.
  • Każda próba oznaczenia writable strony jako executable uruchamia systemową weryfikację (i jest albo zabroniona, albo wymaga zgody code-sign).
  • W wielu przypadkach nadanie stronie uprawnień do wykonania wymaga użycia API OS, które nakłada dodatkowe constraints lub checks.
Example Overflow zapisuje shellcode na heapie. Atakujący próbuje `mprotect(heap_addr, size, PROT_EXEC)` by uczynić go wykonywalnym. System jednak odmawia lub weryfikuje, że nowa strona musi przejść constraints code-sign (których shellcode nie spełnia).

4. Address Space Layout Randomization (ASLR)

Introduced in iOS ~4–5 era (roughly iOS 4–5 timeframe) ASLR losuje bazowe adresy kluczowych regionów pamięci: biblioteki, heap, stack itp. przy każdym uruchomieniu procesu. Adresy gadgetów zmieniają się między uruchomieniami.

  • Thwarts: hardcodowanie adresów gadgetów dla ROP/JOP; statyczne chainy exploitów; ślepe skakanie do znanych offsetów.
  • Mechanism detail:
  • Każda załadowana biblioteka / moduł dynamiczny jest rebased pod losowym offsetem.
  • Bazowe wskaźniki stacka i heapu są losowane (w pewnych granicach entropii).
  • Czasem inne regiony (np. mmap allocations) też są losowane.
  • W połączeniu z mitigacjami information-leak, zmusza atakującego do najpierw odczytania lub przecieku (leak) adresu/pointera by odkryć bazy adresów w czasie działania.
Example ROP chain oczekuje gadgetu w `0x….lib + offset`. Ponieważ `lib` jest przeniesiony inaczej przy każdym uruchomieniu, hardcodowany chain zawiedzie. Exploit musi najpierw leaknąć base address modułu zanim obliczy adresy gadgetów.

5. Kernel Address Space Layout Randomization (KASLR)

Introduced in iOS ~ (iOS 5 / iOS 6 timeframe) Analogicznie do user ASLR, KASLR losuje bazę kernel text i innych struktur jądra przy starcie systemu.

  • Thwarts: kernel-level exploity polegające na stałych lokalizacjach kodu lub danych jądra; statyczne exploity jądra.
  • Mechanism detail:
  • Przy każdym bootowaniu baza kernel jest losowana (w pewnym zakresie).
  • Struktury danych jądra (jak task_structs, vm_map itp.) mogą być też przemieszczone lub mieć offsety.
  • Atakujący musi najpierw leaknąć kernel pointers lub wykorzystać information disclosure, by obliczyć offsety zanim przejmie kontrolę nad strukturami lub kodem jądra.
Example Lokalna podatność chce zepsuć kernel function pointer (np. w `vtable`) na `KERN_BASE + offset`. Ponieważ `KERN_BASE` jest nieznany, atakujący musi najpierw leaknąć go (np. przez read primitive) zanim obliczy poprawny adres do korupcji.

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) ciągle monitoruje integralność kernel text pages (przez hash lub checksum). Jeśli wykryje tampering (patchy, inline hooks, modyfikacje code) poza dozwolonymi oknami, wywołuje kernel panic lub reboot.

  • Thwarts: trwałe patchowanie jądra (modyfikowanie instrukcji jądra), inline hooks, nadpisywanie funkcji statycznie.
  • Mechanism detail:
  • Moduł sprzętowy lub firmware monitoruje region kernel text.
  • Okresowo lub na żądanie re-hashuje strony i porównuje z oczekiwanymi wartościami.
  • Jeśli pojawi się mismatch poza oknem update’ów, panicuje urządzenie (by zapobiec trwałym zmianom).
  • Atakujący musi albo unikać windowów detekcji, albo użyć legalnych ścieżek patchowania.
Example Exploit próbuje zapatchować prolog funkcji jądra (np. `memcmp`) by przechwytywać wywołania. KPP zauważa, że hash strony code nie odpowiada oczekiwanej wartości i wywołuje kernel panic, powodując crash urządzenia zanim patch się utrwali.

7. Kernel Text Read‐Only Region (KTRR)

Introduced in modern SoCs (post ~A12 / newer hardware) KTRR to mechanizm wymuszony sprzętowo: gdy kernel text zostanie zablokowany wcześnie w trakcie bootu, staje się read-only z poziomu EL1 (kernel), uniemożliwiając dalsze zapisywanie stron code.

  • Thwarts: wszelkie modyfikacje kernel code po starcie (np. patchowanie, in-place code injection) na poziomie przywilejów EL1.
  • Mechanism detail:
  • Podczas bootu (w secure/bootloader stage) memory controller (lub bezpieczny moduł sprzętowy) oznacza fizyczne strony kernel text jako read-only.
  • Nawet jeśli exploit zdobędzie pełne uprawnienia kernel, nie może zapisać tych stron by zmienić instrukcje.
  • Aby je zmodyfikować, atakujący musiałby najpierw skompromitować boot chain albo podmienić sam KTRR.
Example Privilege-escalation exploit wchodzi w EL1 i próbuje zapisać trampoline w funkcji jądra (np. w handlerze syscall). Ponieważ strony są locked jako read-only przez KTRR, zapis nie powiedzie się (albo spowoduje fault), więc patch nie zostanie zastosowany.

8. Pointer Authentication Codes (PAC)

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

  • PAC to funkcja sprzętowa wprowadzona w ARMv8.3-A do wykrywania modyfikacji pointerów (adresów powrotu, wskaźników funkcji, niektórych data pointerów) poprzez osadzenie małego kryptograficznego podpisu („MAC”) w nieużywanych wysokich bitach pointera.
  • Podpis („PAC”) jest obliczany dla wartości pointera plus modifier (wartość kontekstowa, np. stack pointer lub inny rozróżniający danych). Dzięki temu ta sama wartość pointera w różnych kontekstach ma inny PAC.
  • W czasie użycia, przed dereferencją lub branchowaniem przez ten pointer, instrukcja authenticate sprawdza PAC. Jeśli poprawny, PAC jest usuwany i otrzymujemy „czysty” pointer; jeśli nie, pointer staje się „poisoned” (lub zgłaszany jest fault).
  • Klucze używane do tworzenia/walidacji PAC przechowywane są w uprzywilejowanych rejestrach (EL1, kernel) i nie są bezpośrednio dostępne z user mode.
  • Ponieważ nie wszystkie 64 bity pointera są używane w wielu systemach (np. 48-bit address space), górne bity są „wolne” i mogą przechowywać PAC bez zmiany efektywnego adresu.

Architectural Basis & Key Types

  • ARMv8.3 wprowadza pięć 128-bitowych kluczy (każdy zaimplementowany przez dwa 64-bitowe rejestry systemowe) do pointer authentication.

  • APIAKey — dla instruction pointers (domena “I”, klucz A)

  • APIBKey — drugi klucz dla instruction pointers (domena “I”, klucz B)

  • APDAKey — dla data pointers (domena “D”, klucz A)

  • APDBKey — dla data pointers (domena “D”, klucz B)

  • APGAKey — „generic” key, do podpisywania non-pointer danych lub innych zastosowań

  • Te klucze są przechowywane w uprzywilejowanych rejestrach systemowych (dostępnych tylko na EL1/EL2 itp.), nieosiągalnych z user mode.

  • PAC jest obliczany przez funkcję kryptograficzną (ARM sugeruje QARMA jako algorytm) używając:

  1. Wartości pointera (część kanoniczna)
  2. modifier (wartość kontekstowa, jak sól)
  3. Sekretnego klucza
  4. Pewnej wewnętrznej logiki tweaków Jeśli uzyskany PAC zgadza się z tym zapisanym w górnych bitach pointera, authentication przechodzi pomyślnie.

Instruction Families

Konwencja nazewnictwa: PAC / AUT / XPAC, potem litery domen.

  • PACxx instrukcje podpisują pointer i wstawiają PAC
  • AUTxx instrukcje uwierzytelniają + usuwają (walidują i usuwają PAC)
  • XPACxx instrukcje usuwają bez walidacji

Domains / suffixes:

MnemonicMeaning / DomainKey / DomainExample Usage in Assembly
PACIASign instruction pointer with APIAKey“I, A”PACIA X0, X1 — sign pointer in X0 using APIAKey with modifier X1
PACIBSign instruction pointer with APIBKey“I, B”PACIB X2, X3
PACDASign data pointer with APDAKey“D, A”PACDA X4, X5
PACDBSign data pointer with APDBKey“D, B”PACDB X6, X7
PACG / PACGAGeneric (non-pointer) signing with APGAKey“G”PACGA X8, X9, X10 (sign X9 with modifier X10 into X8)
AUTIAAuthenticate APIA-signed instruction pointer & strip PAC“I, A”AUTIA X0, X1 — check PAC on X0 using modifier X1, then strip
AUTIBAuthenticate APIB domain“I, B”AUTIB X2, X3
AUTDAAuthenticate APDA-signed data pointer“D, A”AUTDA X4, X5
AUTDBAuthenticate APDB-signed data pointer“D, B”AUTDB X6, X7
AUTGAAuthenticate generic / blob (APGA)“G”AUTGA X8, X9, X10 (validate generic)
XPACIStrip PAC (instruction pointer, no validation)“I”XPACI X0 — remove PAC from X0 (instruction domain)
XPACDStrip PAC (data pointer, no validation)“D”XPACD X4 — remove PAC from data pointer in X4

Są też formy specjalizowane / aliasy:

  • PACIASP to skrót dla PACIA X30, SP (podpisz link register używając SP jako modifier)
  • AUTIASP to AUTIA X30, SP (uwierzytelnij link register używając SP)
  • Formy łączone jak RETAA, RETAB (authenticate-and-return) albo BLRAA (authenticate & branch) istnieją w rozszerzeniach ARM / wsparciu kompilatora.
  • Są też warianty z zerowym modifierem: PACIZA / PACIZB gdzie modifier jest domyślnie zero, itd.

Modifiers

Głównym celem modifiera jest związać PAC z konkretnym kontekstem, tak by ta sama podpisana wartość w różnych kontekstach dawała inne PAC. To zapobiega prostemu reuse pointerów między frame’ami lub obiektami — działa jak sól w hash.

Zatem:

  • modifier to wartość kontekstowa (inny rejestr) mieszana do obliczeń PAC. Typowe wybory: stack pointer (SP), frame pointer, jakiś object ID.
  • Użycie SP jako modifiera jest powszechne dla podpisywania adresu powrotu: PAC jest związany z daną ramką stosu. Jeśli spróbujesz reuse’ować LR w innym frame, modifier się zmieni i walidacja PAC zawiedzie.
  • Ta sama wartość pointera podpisana z różnymi modifierami daje różne PAC.
  • modifier nie musi być tajny, ale idealnie nie powinien być kontrolowany przez atakującego.
  • Dla instrukcji, które podpisują/wywierdzają pointery tam, gdzie nie ma sensownego modifiera, niektóre formy używają zera lub stałej implicite.

Apple / iOS / XNU Customizations & Observations

  • Implementacja PAC przez Apple zawiera per-boot diversifiers, więc klucze lub tweaki zmieniają się przy każdym bootie, co uniemożliwia reuse między bootami.
  • Mają też mechanizmy cross-domain mitigations, więc PACy podpisane w user mode nie są łatwo reuse’owalne w kernel mode itd.
  • Na Apple M1 / Apple Silicon odwrócona inżynieria pokazała, że jest tam dziewięć typów modifierów i Apple-specyficzne rejestry systemowe do kontroli kluczy.
  • Apple używa PAC w wielu subsystemach jądra: podpisywanie adresów powrotu, integralność pointerów w danych jądra, podpisane konteksty wątków itd.
  • Google Project Zero pokazał, że przy silnym primitive’ie read/write w kernel można było sfałszować kernel PACs (dla A keys) na urządzeniach A12, ale Apple załatało wiele takich ścieżek.
  • W systemie Apple niektóre klucze są globalne dla jądra, podczas gdy procesy użytkownika mogą mieć per-process losowość kluczy.

PAC Bypasses

  1. Kernel-mode PAC: theoretical vs real bypasses
  • Ponieważ klucze kernel PAC i logika są ściśle kontrolowane (uprzywilejowane rejestry, diversifiers, izolacja domen), sfałszowanie dowolnych podpisanych pointerów jądra jest bardzo trudne.
  • Azad w 2020 “iOS Kernel PAC, One Year Later” raportował, że w iOS 12-13 znalazł kilka częściowych bypassów (signing gadgets, reuse signed states, niechronione indirect branches) ale nie znalazł pełnego, uniwersalnego bypassu. bazad.github.io
  • Apple’s “Dark Magic” customizations jeszcze bardziej zawęziły powierzchnie ataku (domain switching, per-key enabling bits). i.blackhat.com
  • Istnieje znany kernel PAC bypass CVE-2023-32424 na Apple silicon (M1/M2) zgłoszony przez Zecao Cai i in. i.blackhat.com
  • Jednak te bypasses często polegają na bardzo specyficznych gadgetach lub bugach implementacyjnych; nie są uniwersalne.

Dlatego kernel PAC jest uważany za bardzo solidny, choć nie idealny.

  1. User-mode / runtime PAC bypass techniques

Te są częstsze i wykorzystują niedoskonałości w stosowaniu PAC w dynamicznym ładowaniu / runtime frameworks. Poniżej klasy z przykładami.

2.1 Shared Cache / A key issues

  • dyld shared cache to duży pre-linked blob systemowych frameworków i bibliotek. Ponieważ jest współdzielony, wskaźniki funkcji wewnątrz shared cache są „pre-signed” i używane przez wiele procesów. Atakujący celuja takie już-podpisane pointery jako „PAC oracles”.

  • Niektóre techniki bypass próbują wyekstrahować lub reuse’ować A-key podpisane pointery obecne w shared cache i używać ich w gadgetach.

  • Prezentacja “No Clicks Required” opisuje budowanie oracla nad shared cache do wnioskowania względnych adresów i łączenia tego z podpisanymi pointerami by obejść PAC. saelo.github.io

  • Również importy wskaźników funkcji z shared libraries w userspace zostały uznane za niewystarczająco chronione przez PAC, co pozwalało atakującemu uzyskać wskaźniki bez zmiany ich podpisu. (Project Zero bug entry) bugs.chromium.org

2.2 dlsym(3) / dynamic symbol resolution

  • Jednym znanym bypassem jest wywołanie dlsym() żeby dostać już podpisany function pointer (signed with A-key, diversifier zero) i potem go użyć. Ponieważ dlsym zwraca legalnie podpisany pointer, jego użycie omija konieczność fałszowania PAC.

  • Blog Epsilon opisuje, jak niektóre bypasses wykorzystują to: wywołanie dlsym("someSym") zwraca podpisany pointer i może być użyty do wywołań indirect. blog.epsilon-sec.com

  • Synacktiv w “iOS 18.4 — dlsym considered harmful” opisuje bug: niektóre symbole rozwiązywane przez dlsym w iOS 18.4 zwracają pointery nieprawidłowo podpisane (lub z błędnymi diversifiers), umożliwiając niezamierzony PAC bypass. Synacktiv

  • Logika w dyld dla dlsym zawiera: gdy result->isCode, podpisują zwracany pointer przez __builtin_ptrauth_sign_unauthenticated(..., key_asia, 0), tj. kontekst zero. blog.epsilon-sec.com

Dlatego dlsym jest częstym wektorem w bypassach PAC na poziomie user-mode.

2.3 Other DYLD / runtime relocations

  • DYLD loader i logika dynamicznych relocacji są złożone i czasem tymczasowo mapują strony jako read/write by wykonać relocations, potem przywracają je do read-only. Atakujący wykorzystują te okna czasowe. Prezentacja Synacktiv opisuje “Operation Triangulation”, timing-based bypass PAC przez dynamic relocations. Synacktiv

  • Strony DYLD są teraz chronione przez SPRR / VM_FLAGS_TPRO (pewne flagi ochronne dla dyld). Ale wcześniejsze wersje miały słabsze zabezpieczenia. Synacktiv

  • W chainach exploitów WebKit, DYLD loader jest często celem PAC bypass. Slajdy wspominają, że wiele bypassów PAC celowało loader DYLD (przez relocation, interposer hooks). Synacktiv

2.4 NSPredicate / NSExpression / ObjC / SLOP

  • W userland exploit chainach runtime Objective-C takie jak NSPredicate, NSExpression czy NSInvocation są używane do przemycenia wywołań kontrolowanych bez oczywistego fałszowania pointerów.

  • Na starszych iOS (przed PAC) exploit używał fake NSInvocation obiektów do wywoływania dowolnych selectorów na kontrolowanej pamięci. Z PAC technika wymaga modyfikacji. Ale technika SLOP (SeLector Oriented Programming) została rozszerzona także pod PAC. Project Zero

  • Oryginalna technika SLOP pozwalała łączyć wywołania ObjC przez tworzenie fałszywych invocations; bypass opiera się na tym, że ISA lub selector pointery czasami nie są w pełni chronione przez PAC. Project Zero

  • W środowiskach, gdzie pointer authentication jest stosowane częściowo, metody / selectory / target pointers mogą nie być zawsze chronione, co daje przestrzeń do obejścia.

Example Flow

Przykład podpisywania i uwierzytelniania ``` ; 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>
Przepełnienie bufora nadpisuje adres powrotu na stosie. Atakujący wpisuje adres docelowego gadgetu, ale nie potrafi obliczyć poprawnego PAC. Gdy funkcja zwraca, instrukcja CPU `AUTIA` powoduje wyjątek z powodu niezgodności PAC. Łańcuch zawodzi.
Analiza Project Zero dotycząca A12 (iPhone XS) pokazała, jak Apple używa PAC i metody fałszowania PAC, jeśli atakujący ma prymityw do odczytu/zapisu pamięci.
</details>


### 9. **Branch Target Identification (BTI)**
**Introduced with ARMv8.5 (later hardware)**
BTI to cecha sprzętowa, która sprawdza **indirect branch targets**: przy wykonywaniu `blr` lub niebezpośrednich call/jumpów, cel musi zaczynać się od **BTI landing pad** (`BTI j` lub `BTI c`). Skakanie do adresów gadgetów, które nie mają landing padu, wywołuje wyjątek.

Uwagi implementacyjne LLVM opisują trzy warianty instrukcji BTI i jak one mapują się na typy skoków.

| BTI Variant | What it permits (which branch types) | Typical placement / use case |
|-------------|----------------------------------------|-------------------------------|
| **BTI C** | Cele niebezpośrednich skoków typu *call* (np. `BLR`, lub `BR` używając X16/X17) | Umieszczane na początku funkcji, które mogą być wywoływane niebezpośrednio |
| **BTI J** | Cele skoków typu *jump* (np. `BR` używane dla tail calls) | Umieszczane na początku bloków osiągalnych przez jump tables lub tail-calls |
| **BTI JC** | Działa jako oba: C i J | Może być celem zarówno call jak i jump |

- W kodzie skompilowanym z branch target enforcement, kompilatory wstawiają instrukcję BTI (C, J, lub JC) w każdym prawidłowym indirect-branch target (początki funkcji lub bloki osiągalne przez skoki), tak aby niebezpośrednie skoki odnosiły sukces tylko do tych miejsc.
- **Direct branches / calls** (tj. skoki o stałym adresie `B`, `BL`) **nie są ograniczane** przez BTI. Założenie jest takie, że strony kodu są zaufane i atakujący nie może ich zmienić (więc direct branches są bezpieczne).
- Ponadto, instrukcje **RET / return** zazwyczaj nie są objęte restrykcjami BTI, ponieważ adresy powrotu są chronione przez PAC lub mechanizmy podpisywania powrotów.

#### Mechanism and enforcement

- Gdy CPU dekoduje **indirect branch (BLR / BR)** na stronie oznaczonej jako „guarded / BTI-enabled,” sprawdza, czy pierwsza instrukcja pod docelowym adresem jest prawidłowym BTI (C, J, lub JC, w zależności od dozwolonych). Jeśli nie, następuje **Branch Target Exception**.
- Kodowanie instrukcji BTI zaprojektowano tak, aby ponownie użyć opcode’ów wcześniej zarezerwowanych dla NOPów (we wcześniejszych wersjach ARM). Dzięki temu binaria z BTI są wstecznie kompatybilne: na sprzęcie bez wsparcia BTI te instrukcje działają jako NOPy.
- Przepustki kompilatora, które dodają BTI, wstawiają je tylko tam, gdzie są potrzebne: do funkcji, które mogą być wywoływane niebezpośrednio, lub do basic blocków, które są celem skoków.
- Niektóre łatki i fragmenty kodu LLVM pokazują, że BTI nie jest wstawiany dla *wszystkich* basic blocków — tylko dla tych, które są potencjalnymi celami skoków (np. z switch / jump tables).

#### BTI + PAC synergy

PAC chroni wartość wskaźnika (źródło) — zapewnia, że łańcuch niebezpośrednich wywołań/returnów nie został zmieniony.

BTI zapewnia, że nawet poprawny wskaźnik może celować tylko w odpowiednio oznaczone punkty wejścia.

W połączeniu, atakujący potrzebuje zarówno poprawnego wskaźnika z właściwym PAC, jak i tego, aby cel miał umieszczony BTI. To zwiększa trudność konstruowania gadgetów wykorzystywanych w exploitach.

#### Example


<details>
<summary>Example</summary>
Exploit próbuje pivotować do gadgetu pod adresem `0xABCDEF`, który nie zaczyna się od `BTI c`. CPU, przy wykonywaniu `blr x0`, sprawdza cel i powoduje fault, ponieważ wyrzutnia instrukcji nie zawiera poprawnego landing padu. W efekcie wiele gadgetów staje się bezużytecznych, chyba że mają prefiks BTI.
</details>


### 10. **Privileged Access Never (PAN) & Privileged Execute Never (PXN)**
**Introduced in more recent ARMv8 extensions / iOS support (for hardened kernel)**

#### PAN (Privileged Access Never)

- **PAN** to funkcja wprowadzona w **ARMv8.1-A**, która zapobiega temu, by **kod uprzywilejowany** (EL1 lub EL2) **czytał lub zapisywał** pamięć oznaczoną jako dostępna dla użytkownika (EL0), chyba że PAN zostanie wyraźnie wyłączone.
- Idea: nawet jeśli kernel zostanie oszukany lub przejęty, nie może dowolnie dereferencjonować wskaźników użytkownika bez uprzedniego *wyłączenia* PAN, co redukuje ryzyko exploitów w stylu **`ret2usr`** lub nadużycia buforów kontrolowanych przez użytkownika.
- Gdy PAN jest włączone (PSTATE.PAN = 1), każda uprzywilejowana instrukcja load/store odwołująca się do wirtualnego adresu, który jest „accessible at EL0”, powoduje **permission fault**.
- Kernel, gdy musi legalnie uzyskać dostęp do pamięci użytkownika (np. kopiowanie danych do/z buforów użytkownika), musi **tymczasowo wyłączyć PAN** (lub użyć instrukcji „unprivileged load/store”), aby umożliwić ten dostęp.
- W Linuksie na ARM64 wsparcie PAN było wprowadzane około 2015 r.: łatki do kernela dodały wykrycie tej cechy i zastąpiły `get_user` / `put_user` itd. wariantami, które wyłączają PAN wokół dostępu do pamięci użytkownika.

**Key nuance / limitation / bug**
- Jak zauważyli Siguza i inni, błąd w specyfikacji (lub niejednoznaczne zachowanie) w projekcie ARM powoduje, że **execute-only user mappings** (`--x`) mogą **nie wywoływać PAN**. Innymi słowy, jeśli strona użytkownika jest oznaczona jako wykonywalna, ale bez prawa do odczytu, próba odczytu kernela może ominąć PAN, ponieważ architektura uznaje „accessible at EL0” za wymagające prawa do odczytu, a nie tylko execute. To prowadzi do obejścia PAN w określonych konfiguracjach.
- Z tego powodu, jeśli iOS / XNU pozwala na execute-only user pages (tak jak niektóre JIT lub code-cache), kernel może przypadkowo czytać z nich nawet przy włączonym PAN. To jest znany, subtelny, potencjalnie wykorzystywalny obszar w niektórych implementacjach ARMv8+.

#### PXN (Privileged eXecute Never)

- **PXN** to flaga w tabeli stron (w wpisach leaf lub block), która wskazuje, że strona jest **niewykonywalna podczas działania w trybie uprzywilejowanym** (tj. gdy EL1 wykonuje kod).
- PXN zapobiega temu, by kernel (lub inny kod uprzywilejowany) skakał do albo wykonywał instrukcje z stron użytkownika, nawet jeśli kontrola zostanie przekierowana. W praktyce blokuje przekierowanie kontroli kernela do pamięci użytkownika.
- W połączeniu z PAN, zapewnia to, że:
1. Kernel nie może (domyślnie) czytać ani zapisywać danych użytkownika (PAN)
2. Kernel nie może wykonywać kodu użytkownika (PXN)
- W formacie tabeli stron ARM, wpisy leaf mają bit `PXN` (i także `UXN` dla nie-wykonywalności na poziomie użytkownika) w polach atrybutów.

Nawet jeśli kernel ma skompromitowany wskaźnik funkcji wskazujący na pamięć użytkownika i spróbuje tam skoczyć, bit PXN spowoduje fault.

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

Aby zrozumieć jak działają PAN / PXN, trzeba spojrzeć na model translacji i uprawnień ARM (upraszczając):

- Każda strona lub block entry ma pola atrybutów w tym **AP[2:1]** dla uprawnień dostępu (read/write, privileged vs unprivileged) oraz bity **UXN / PXN** dla ograniczeń execute-never.
- Gdy PSTATE.PAN = 1 (włączone), sprzęt egzekwuje zmodyfikowaną semantykę: uprzywilejowane dostępny do stron oznaczonych jako „accessible by EL0” są zabronione (fault).
- Z powodu wspomnianego błędu, strony oznaczone jedynie jako wykonywalne (bez prawa do odczytu) mogą nie być uznane za „accessible by EL0” w niektórych implementacjach, co omija PAN.
- Gdy bit PXN jest ustawiony dla strony, nawet jeśli fetch instrukcji pochodzi z wyższego poziomu uprzywilejowania, wykonanie jest zabronione.

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

W projekcie hardened kernel (takim jak ten stosowany przez Apple):

- Kernel włącza PAN domyślnie (tak, by kod uprzywilejowany miał ograniczenia).
- W ścieżkach, które legalnie muszą czytać lub zapisywać bufory użytkownika (np. kopiowanie z/do syscall bufferów, I/O, read/write user pointer), kernel tymczasowo **wyłącza PAN** lub używa specjalnych instrukcji, by obejść zabezpieczenie.
- Po zakończeniu dostępu do danych użytkownika, musi ponownie włączyć PAN.
- PXN jest egzekwowane przez tabelę stron: strony użytkownika mają PXN = 1 (więc kernel nie może ich wykonywać), strony kernela nie mają PXN (więc kod kernela może być wykonywany).
- Kernel musi upewnić się, że żadne ścieżki kodu nie dopuszczają wykonania kodu w regionach pamięci użytkownika (co mogłoby obejść PXN) — więc łańcuchy exploitów polegające na „skokach do shellcode’u w pamięci użytkownika” są blokowane.

Z powodu wspomnianego obejścia PAN przez execute-only pages, w rzeczywistym systemie Apple może wyłączyć lub zabronić execute-only user pages albo załatać słabość specyfikacji.

#### Attack surfaces, bypasses, and mitigations

- **PAN bypass via execute-only pages**: jak omówiono, spec pozwala na lukę: strony użytkownika z execute-only (bez prawa do odczytu) mogą nie być traktowane jako „accessible at EL0,” więc PAN nie zablokuje odczytów kernela w niektórych implementacjach. To daje atakującemu nietypową drogę do przekazania danych przez sekcje execute-only.
- **Temporal window exploit**: jeśli kernel wyłącza PAN na okienko dłuższe niż to konieczne, wyścig lub złośliwa ścieżka może wykorzystać to okienko do niezamierzonego dostępu do pamięci użytkownika.
- **Forgotten re-enable**: jeśli ścieżki kodu zapomną ponownie włączyć PAN, kolejne operacje kernela mogą nieprawidłowo uzyskiwać dostęp do pamięci użytkownika.
- **Misconfiguration of PXN**: jeśli tabela stron nie ustawi PXN dla stron użytkownika lub błędnie zmapuje strony kodu użytkownika, kernel może zostać oszukany, by wykonać kod użytkownika.
- **Speculation / side-channels**: analogicznie do omijania przez spekulację, mogą istnieć mikroarchitektoniczne skutki uboczne powodujące przejściowe naruszenie sprawdzeń PAN / PXN (choć takie ataki są silnie zależne od projektu CPU).
- **Complex interactions**: w bardziej zaawansowanych funkcjach (np. JIT, shared memory, code-cache JIT), kernel może potrzebować drobnego sterowania, by dopuścić pewne dostępy do pamięci lub wykonanie w regionach mapowanych dla użytkownika; zaprojektowanie tego bezpiecznie w kontekście PAN/PXN jest niebanalne.


#### Example

<details>
<summary>Code Example</summary>
Poniżej znajdują się ilustrujące pseudo-assemblery pokazujące włączanie/wyłączanie PAN wokół dostępu do pamięci użytkownika oraz jak może wystąpić fault.
</details>

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

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

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

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

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

; … further kernel work …

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

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

Jeśli jądro nie ustawiłoby **PXN** na tej stronie użytkownika, to rozgałęzienie mogłoby się powieść — co byłoby niebezpieczne.

Jeśli jądro zapomni ponownie włączyć **PAN** po dostępie do pamięci użytkownika, otwiera to możliwość, że dalsza logika jądra przypadkowo odczyta/zapisze dowolną pamięć użytkownika.

Jeśli wskaźnik użytkownika wskazuje do strony tylko-wykonywalnej (strona użytkownika z tylko uprawnieniem do wykonania, bez odczytu/zapisu), to w wyniku błędu specyfikacji PAN `ldr W2, [X1]` może **nie** zgłosić wyjątku nawet przy włączonym PAN, umożliwiając exploit omijający zabezpieczenia, w zależności od implementacji.

</details>

<details>
<summary>Przykład</summary>
Luka w jądrze próbuje pobrać wskaźnik funkcji dostarczony przez użytkownika i wywołać go w kontekście jądra (np. `call user_buffer`). Przy PAN/PXN ta operacja jest zabroniona lub powoduje fault.
</details>

---

### 11. **Top Byte Ignore (TBI) / Pointer Tagging**
**Introduced in ARMv8.5 / newer (or optional extension)**
TBI oznacza, że najwyższy bajt (najbardziej znaczący bajt) 64-bitowego wskaźnika jest ignorowany podczas translacji adresu. Pozwala to OS-owi lub sprzętowi osadzać **bity tagu** w najwyższym bajcie wskaźnika bez wpływu na rzeczywisty adres.

- TBI stands for **Top Byte Ignore** (czasem nazywane *Address Tagging*). To funkcja sprzętowa (dostępna w wielu implementacjach ARMv8+), która **ignoruje najwyższe 8 bitów** (bity 63:56) 64-bitowego wskaźnika podczas wykonywania **address translation / load/store / instruction fetch**.
- W praktyce CPU traktuje wskaźnik `0xTTxxxx_xxxx_xxxx` (gdzie `TT` = najwyższy bajt) jako `0x00xxxx_xxxx_xxxx` do celów translacji adresu, ignorując (maskując) najwyższy bajt. Najwyższy bajt może być użyty przez oprogramowanie do przechowywania **metadata / bitów tagu**.
- Daje to oprogramowaniu „darmowe” miejsce in-band do osadzenia bajtu tagu w każdym wskaźniku bez zmiany, do której lokalizacji pamięci odwołuje się wskaźnik.
- Architektura zapewnia, że loady, store'y i fetch instrukcji traktują wskaźnik z zamaskowanym najwyższym bajtem (tzn. tag jest usuwany) przed wykonaniem rzeczywistego dostępu do pamięci.

W ten sposób TBI oddziela **wskaźnik logiczny** (wskaźnik + tag) od **fizycznego adresu** używanego do operacji pamięciowych.

#### Dlaczego TBI: przypadki użycia i motywacja

- **Pointer tagging / metadata**: Można przechowywać dodatkowe metadata (np. typ obiektu, wersję, granice, tagi integralności) w tym najwyższym bajcie. Gdy później użyjesz wskaźnika, tag jest ignorowany na poziomie sprzętowym, więc nie trzeba go ręcznie usuwać przed dostępem do pamięci.
- **Memory tagging / MTE (Memory Tagging Extension)**: TBI jest podstawowym mechanizmem sprzętowym, na którym opiera się MTE. W ARMv8.5 **Memory Tagging Extension** używa bitów 59:56 wskaźnika jako **logicznnego tagu** i porównuje go z **allocation tag** przechowywanym w pamięci.
- **Enhanced security & integrity**: Łącząc TBI z pointer authentication (PAC) lub kontrolami w czasie wykonywania, można wymusić poprawność nie tylko wartości wskaźnika, ale również tagu. Atakujący nadpisujący wskaźnik bez poprawnego tagu otrzyma niezgodność tagu.
- **Compatibility**: Ponieważ TBI jest opcjonalne i bity tagu są ignorowane przez sprzęt, istniejący kod bez tagów działa dalej normalnie. Bity tagu praktycznie stają się „nieistotne” dla starszego kodu.

#### Example
<details>
<summary>Przykład</summary>
Wskaźnik funkcji miał tag w najwyższym bajcie (np. `0xAA`). Exploit nadpisuje niskie bity wskaźnika, ale pomija tag, więc gdy jądro weryfikuje lub sanityzuje wskaźnik, ten nie przechodzi weryfikacji i jest odrzucany.
</details>

---

### 12. **Page Protection Layer (PPL)**
**Introduced in late iOS / modern hardware (iOS ~17 / Apple silicon / high-end models)** (niektóre raporty wskazują PPL w okolicach macOS / Apple silicon, ale Apple wprowadza analogiczne zabezpieczenia do iOS)

- PPL jest zaprojektowany jako **granica ochrony wewnątrz jądra**: nawet jeśli jądro (EL1) zostanie skompromitowane i ma możliwości odczytu/zapisu, **nie powinno ono móc dowolnie modyfikować** pewnych **wrażliwych stron** (szczególnie tablic stron, metadanych podpisów kodu, stron z kodem jądra, entitlements, trust caches itp.).
- Tworzy to w praktyce **„jądro w jądrze”** — mniejszy zaufany komponent (PPL) z **podwyższonymi przywilejami**, który samodzielnie może modyfikować chronione strony. Inny kod jądra musi wywoływać rutyny PPL, żeby wprowadzić zmiany.
- Zmniejsza to powierzchnię ataku dla exploitów jądra: nawet przy pełnym arbitralnym R/W/execute w trybie jądra, kod exploita musi w jakiś sposób wejść do domeny PPL (lub ominąć PPL), żeby zmodyfikować krytyczne struktury.
- Na nowszym Apple silicon (A15+ / M2+) Apple przechodzi do **SPTM (Secure Page Table Monitor)**, który w wielu przypadkach zastępuje PPL w ochronie tablic stron na tych platformach.

Oto jak jest sądzony sposób działania PPL na podstawie publicznych analiz:

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

- Sprzęt Apple wykorzystuje mechanizm zwany **APRR (Access Permission ReRouting)**, który pozwala wpisom tablic stron (PTE) zawierać małe indeksy zamiast pełnych bitów uprawnień. Te indeksy są mapowane przez rejestry APRR na rzeczywiste uprawnienia. Pozwala to na dynamiczne przypisywanie uprawnień per domena.
- PPL wykorzystuje APRR do separacji przywilejów w kontekście jądra: tylko domena PPL ma uprawnienie do aktualizacji mapowania między indeksami a efektywnymi uprawnieniami. To znaczy, gdy kod jądra poza PPL zapisuje PTE lub próbuje zmienić bity uprawnień, logika APRR tego zabrania (lub wymusza mapowanie tylko do odczytu).
- Sam kod PPL działa w ograniczonym obszarze (np. `__PPLTEXT`), który normalnie jest nieegzekwowalny lub niezapisywalny, aż bramki wejściowe tymczasowo na to pozwolą. Jądro wywołuje punkty wejścia PPL („PPL routines”), aby wykonać wrażliwe operacje.

#### Gate / Entry & Exit

- Gdy jądro musi zmodyfikować chronioną stronę (np. zmienić uprawnienia strony z kodu jądra lub zmodyfikować tablice stron), wywołuje procedurę **PPL wrapper**, która wykonuje walidację, a następnie przechodzi do domeny PPL. Poza tą domeną chronione strony są efektywnie tylko do odczytu lub niemodyfikowalne przez główne jądro.
- Podczas wejścia do PPL mapowania APRR są dostosowywane tak, żeby strony pamięci w regionie PPL były ustawione jako **executable & writable** wewnątrz PPL. Po wyjściu przywracane są do trybu tylko do odczytu / niezapisywalnego. Zapewnia to, że tylko starannie audytowane rutyny PPL mogą zapisywać do chronionych stron.
- Poza PPL, próby przez kod jądra zapisania do tych chronionych stron spowodują fault (brak uprawnień), ponieważ mapowanie APRR dla danej domeny kodu nie zezwala na zapis.

#### Protected page categories

Strony, które PPL zwykle chroni, obejmują:

- Struktury tablic stron (wpisy tabel translacji, metadata mapowań)
- Strony z kodem jądra, zwłaszcza te zawierające krytyczną logikę
- Metadane podpisów kodu (trust caches, bloby podpisów)
- Tabele uprawnień (entitlement), tabele egzekwowania podpisów
- Inne wysoko-wartościowe struktury jądra, gdzie łatka umożliwiłaby obejście kontroli podpisów lub manipulację poświadczeniami

Idea jest taka, że nawet jeśli pamięć jądra jest w pełni kontrolowana, atakujący nie może po prostu łatwo zapatchować lub przepisać tych stron, chyba że również skompromituje rutyny PPL lub obejdzie PPL.

#### Known Bypasses & Vulnerabilities

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

- Publiczne opracowanie Project Zero opisuje obejście wykorzystujące **stare wpisy TLB**.
- Pomysł:

1. Przydziel dwie fizyczne strony A i B, oznacz je jako strony PPL (czyli chronione).
2. Zmapuj dwa adresy wirtualne P i Q, których strony tabeli translacji L3 pochodzą z A i B.
3. Uruchom wątek, który ciągle odwołuje się do Q, utrzymując jego wpis TLB aktywny.
4. Wywołaj `pmap_remove_options()` aby usunąć mapowania zaczynające się od P; z powodu buga kod błędnie usuwa TTE dla P i Q, ale unieważnia wpis TLB tylko dla P, pozostawiając przestarzały wpis TLB dla Q aktywnym.
5. Ponownie użyj B (strony tabeli Q) do zamapowania arbitralnej pamięci (np. stron chronionych PPL). Ponieważ przestarzały wpis TLB nadal mapuje starą mappingę Q, to mapowanie pozostaje ważne dla danego kontekstu.
6. Dzięki temu atakujący może ustawić zapisywalne mapowanie stron chronionych przez PPL bez przechodzenia przez interfejs PPL.

- Ten exploit wymagał precyzyjnej kontroli mapowania fizycznego i zachowania TLB. Pokazuje, że granica bezpieczeństwa oparta na poprawności TLB / mapowania musi być bardzo ostrożna przy unieważnianiu TLB i spójności mapowań.

- Project Zero zauważył, że obejścia tego typu są subtelne i rzadkie, ale możliwe w złożonych systemach. Mimo to uważają PPL za solidne zabezpieczenie.

2. **Other potential hazards & constraints**

- Jeśli exploit jądra może bezpośrednio wejść do rutyn PPL (poprzez wywołanie PPL wrappers), może obejść ograniczenia. Dlatego walidacja argumentów jest krytyczna.
- Błędy w samym kodzie PPL (np. przepełnienia arytmetyczne, błędy sprawdzania granic) mogą pozwolić na modyfikacje poza granicami wewnątrz PPL. Project Zero zaobserwował, że taki bug w `pmap_remove_options_internal()` został wykorzystany w ich obejściu.
- Granica PPL jest nierozerwalnie związana z egzekucją sprzętową (APRR, kontroler pamięci), więc jest tak silna, jak implementacja sprzętowa.

#### Example
<details>
<summary>Przykład kodu</summary>
Oto uproszczony pseudokod / logika pokazująca, jak jądro mogłoby wywołać PPL, aby zmodyfikować chronione strony:
</details>
```c
// In kernel (outside PPL domain)
function kernel_modify_pptable(pt_addr, new_entry) {
// validate arguments, etc.
return ppl_call_modify(pt_addr, new_entry)  // call PPL wrapper
}

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

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

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

Example Eksploit jądra próbuje nadpisać tabelę uprawnień (entitlement table) lub wyłączyć wymuszanie podpisu kodu (code-sign enforcement) poprzez modyfikację blobu sygnatury jądra. Ponieważ ta strona jest chroniona przez PPL, zapis jest zablokowany, chyba że następuje przez interfejs PPL. Tak więc nawet przy wykonaniu kodu w jądrze nie można obejść ograniczeń code-sign ani dowolnie modyfikować danych uwierzytelniających. Na iOS 17+ niektóre urządzenia używają SPTM, aby dodatkowo izolować strony zarządzane przez PPL.

PPL → SPTM / Replacements / Future

  • On Apple’s modern SoCs (A15 or later, M2 or later), Apple supports SPTM (Secure Page Table Monitor), which replaces PPL for page table protections.
  • Apple calls out in documentation: “Page Protection Layer (PPL) and Secure Page Table Monitor (SPTM) enforce execution of signed and trusted code … PPL manages the page table permission overrides … Secure Page Table Monitor replaces PPL on supported platforms.”
  • The SPTM architecture likely shifts more policy enforcement into a higher-privileged monitor outside kernel control, further reducing the trust boundary.

MTE | EMTE | MIE

Oto opis na wyższym poziomie, jak działa EMTE w konfiguracji MIE Apple’a:

  1. Przypisywanie tagu
  • Gdy pamięć jest alokowana (np. w jądro lub user-space przez bezpieczne alokatory), do bloku przypisywany jest tajny tag.
  • Wskaźnik zwracany do użytkownika lub jądra zawiera ten tag w swoich wysokich bitach (przy użyciu mechanizmu TBI / top byte ignore).
  1. Sprawdzanie taga podczas dostępu
  • Za każdym razem, gdy wykonywany jest load lub store przy użyciu wskaźnika, sprzęt sprawdza, czy tag wskaźnika pasuje do taga bloku pamięci (allocation tag). Jeśli nie pasuje, następuje natychmiastowy fault (ponieważ jest to operacja synchroniczna).
  • Ponieważ jest synchroniczny, nie ma okna „delayed detection”.
  1. Retagging przy free / reuse
  • Kiedy pamięć jest zwalniana, alokator zmienia tag bloku (tak że starsze wskaźniki z poprzednimi tagami nie będą już pasować).
  • Wskaźnik use-after-free będzie więc miał przestarzały tag i spowoduje mismatch przy dostępie.
  1. Różnicowanie tagów sąsiadów w celu wykrywania buffer overflow
  • Sąsiednie alokacje otrzymują różne tagi. Jeśli buffer overflow przeleje się do pamięci sąsiada, mismatch tagów spowoduje fault.
  • Jest to szczególnie skuteczne w wykrywaniu małych overflowów, które przekraczają granicę.
  1. Wymuszanie poufności tagów
  • Apple musi zapobiegać leaked wartości tagów (ponieważ jeśli atakujący pozna tag, mógłby sporządzić wskaźniki z poprawnymi tagami).
  • Zawierają zabezpieczenia (microarchitectural / speculative controls), aby uniknąć side-channel leakage bitów taga.
  1. Integracja jądra i przestrzeni użytkownika
  • Apple używa EMTE nie tylko w user-space, ale także w kernel / komponentach krytycznych dla OS (aby chronić jądro przed korupcją pamięci).
  • Sprzęt/OS zapewniają, że reguły tagów obowiązują nawet gdy jądro wykonuje kod w imieniu przestrzeni użytkownika.
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>

#### Ograniczenia i wyzwania

- **Intrablock overflows**: Jeśli overflow pozostaje w tym samym alokacie (nie przekracza granicy) i tag pozostaje ten sam, tag mismatch tego nie wykryje.
- **Tag width limitation**: Dostępnych jest tylko kilka bitów (np. 4 bity, lub mała domena) na tag — ograniczona przestrzeń nazw.
- **Side-channel leaks**: Jeśli bity taga mogą być wyciekane (przez cache / speculative execution), atakujący może poznać ważne tagi i obejść ochronę. Apple’s tag confidentiality enforcement ma na celu zminimalizowanie tego.
- **Performance overhead**: Sprawdzania tagów przy każdym load/store dodają koszt; Apple musi zoptymalizować hardware, aby zredukować narzut.
- **Compatibility & fallback**: Na starszym sprzęcie lub w częściach, które nie wspierają EMTE, musi istnieć fallback. Apple twierdzi, że MIE jest włączone tylko na urządzeniach z obsługą.
- **Complex allocator logic**: Allocator musi zarządzać tagami, retaggingiem, wyrównywaniem granic i unikać kolizji tagów. Błędy w logice allocatora mogą wprowadzić podatności.
- **Mixed memory / hybrid areas**: Część pamięci może pozostać bez tagów (legacy), co utrudnia interoperacyjność.
- **Speculative / transient attacks**: Jak przy wielu mikroarchitektonicznych protekcjach, speculative execution lub micro-op fusion mogą chwilowo obejść sprawdzenia lub wyciekować bity taga.
- **Limited to supported regions**: Apple może egzekwować EMTE tylko w wybranych, wysokiego ryzyka obszarach (kernel, security-critical subsystems), nie globalnie.

---

## Kluczowe usprawnienia / różnice w porównaniu do standardowego MTE

Oto ulepszenia i zmiany, które Apple podkreśla:

| Feature | Original MTE | EMTE (Apple’s enhanced) / MIE |
|---|---|---|
| **Check mode** | Supports synchronous and asynchronous modes. In async, tag mismatches are reported later (delayed)| Apple insists on **synchronous mode** by default—tag mismatches are caught immediately, no delay/race windows allowed.|
| **Coverage of non-tagged memory** | Accesses to non-tagged memory (e.g. globals) may bypass checks in some implementations | EMTE requires that accesses from a tagged region to non-tagged memory also validate tag knowledge, making it harder to bypass by mixing allocations.|
| **Tag confidentiality / secrecy** | Tags might be observable or leaked via side channels | Apple adds **Tag Confidentiality Enforcement**, which attempts to prevent leakage of tag values (via speculative side-channels etc.).|
| **Allocator integration & retagging** | MTE leaves much of allocator logic to software | Apple’s secure typed allocators (kalloc_type, xzone malloc, etc.) integrate with EMTE: when memory is allocated or freed, tags are managed at fine granularity.|
| **Always-on by default** | In many platforms, MTE is optional or off by default | Apple enables EMTE / MIE by default on supported hardware (e.g. iPhone 17 / A19) for kernel and many user processes.|

Dzięki kontroli nad hardware i software stackiem, Apple może ściśle egzekwować EMTE, unikać problemów z wydajnością i zamykać luki side-channel.

---

## Jak EMTE działa w praktyce (Apple / MIE)

Poniżej opis na wyższym poziomie, jak działa EMTE w konfiguracji Apple / MIE:

1. **Tag assignment**
- Gdy pamięć jest alokowana (np. w kernelu lub user space przez secure allocators), blokowi przypisywany jest **secret tag**.
- Wskaźnik zwrócony do użytkownika lub kernela zawiera ten tag w górnych bitach (korzystając z mechanizmów TBI / top byte ignore).

2. **Tag checking on access**
- Kiedy wykonywany jest load lub store z użyciem wskaźnika, hardware sprawdza, czy tag wskaźnika pasuje do taga bloku pamięci (allocation tag). Jeśli nie pasuje — następuje fault natychmiast (ponieważ synchronous).
- Ponieważ jest to synchronous, nie ma „opóźnionego wykrywania” ani okienka race.

3. **Retagging on free / reuse**
- Gdy pamięć jest zwalniana, allocator zmienia tag bloku (stare wskaźniki z przestarzałymi tagami przestaną pasować).
- Use-after-free wskaźnik będzie więc miał przestarzały tag i spowoduje mismatch przy dostępie.

4. **Neighbor-tag differentiation to catch overflows**
- Sąsiednim alokacjom przypisywane są różne tagi. Jeśli buffer overflow przeleje się do pamięci sąsiada, tag mismatch spowoduje fault.
- To szczególnie skuteczne w wykrywaniu małych overflowów przekraczających granicę.

5. **Tag confidentiality enforcement**
- Apple musi zapobiegać wyciekowi wartości tagów (bo jeśli atakujący pozna tag, może sfałszować wskaźniki z poprawnymi tagami).
- Wprowadzili zabezpieczenia (mikroarchitekturalne / speculative controls), aby uniknąć side-channel leakage bitów taga.

6. **Kernel and user-space integration**
- Apple używa EMTE nie tylko w user-space, ale też w kernelu / krytycznych komponentach OS (aby chronić kernel przed memory corruption).
- Hardware/OS zapewnia, że reguły tagów obowiązują nawet gdy kernel działa w imieniu user space.

Ponieważ EMTE jest zintegrowane z MIE, Apple stosuje EMTE w synchronous mode na kluczowych powierzchniach ataku, a nie jako opcję czy tryb debugowania.

---

## Exception handling in XNU

Gdy wystąpi **exception** (np. `EXC_BAD_ACCESS`, `EXC_BAD_INSTRUCTION`, `EXC_CRASH`, `EXC_ARM_PAC`, itd.), warstwa **Mach** jądra XNU odpowiada za przechwycenie jej zanim zostanie przekształcona w UNIX-owy **signal** (jak `SIGSEGV`, `SIGBUS`, `SIGILL`, ...).

Proces ten obejmuje kilka warstw propagacji i obsługi wyjątków zanim trafi do przestrzeni użytkownika lub zostanie skonwertowany na BSD signal.

### Exception Flow (High-Level)

1.  CPU triggers a synchronous exception (e.g., invalid pointer dereference, PAC failure, illegal instruction, etc.).
2.  Low-level trap handler runs (`trap.c`, `exception.c` in XNU source).
3.  The trap handler calls **`exception_triage()`**, the core of the Mach exception handling.
4.  `exception_triage()` decides how to route the exception:

-   First to the **thread's exception port**.
-   Then to the **task's exception port**.
-   Then to the **host's exception port** (often `launchd` or `ReportCrash`).

If none of these ports handle the exception, the kernel may:

-   **Convert it into a BSD signal** (for user-space processes).
-   **Panic** (for kernel-space exceptions).

### Core Function: `exception_triage()`

Funkcja `exception_triage()` kieruje Mach exceptions w górę łańcucha możliwych handlerów, aż któryś je obsłuży lub aż staną się one fatalne. Jest zdefiniowana w `osfmk/kern/exception.c`.
```c
void exception_triage(exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt);

Typowy przebieg wywołań:

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

Jeśli wszystkie zawiodą → obsługiwane przez bsd_exception() → przetłumaczone na sygnał, np. SIGSEGV.

Porty wyjątków

Każdy obiekt Mach (thread, task, host) może zarejestrować exception ports, na które wysyłane są komunikaty o wyjątkach.

task_set_exception_ports()
thread_set_exception_ports()
host_set_exception_ports()

Każdy exception port ma:

  • Maskę (które wyjątki chce otrzymywać)
  • Nazwę portu (Mach port do odbioru wiadomości)
  • Zachowanie (jak kernel wysyła wiadomość)
  • Flavor (które stany wątku mają być dołączone)

Debuggery i obsługa wyjątków

Debugger (np. LLDB) ustawia exception port na docelowym zadaniu lub wątku, zazwyczaj używając task_set_exception_ports().

Kiedy wystąpi wyjątek:

  • Wiadomość Mach jest wysyłana do procesu debuggera.
  • Debugger może zdecydować się obsłużyć (wznowić, zmodyfikować rejestry, pominąć instrukcję) lub nie obsłużyć wyjątku.
  • Jeśli debugger nie obsłuży wyjątku, ten propaguje się na następny poziom (task → host).

Przepływ EXC_BAD_ACCESS

  1. Wątek dereferencuje nieprawidłowy wskaźnik → CPU zgłasza Data Abort.

  2. Handler trapów jądra wywołuje exception_triage(EXC_BAD_ACCESS, ...).

  3. Wiadomość wysyłana do:

  • Thread port → (debugger może przechwycić breakpoint).

  • Jeśli debugger zignoruje → Task port → (handler na poziomie procesu).

  • Jeśli zignorowano → Host port (zazwyczaj ReportCrash).

  1. Jeśli nikt nie obsłuży → bsd_exception() tłumaczy to na SIGSEGV.

PAC Exceptions

Gdy Pointer Authentication (PAC) zawiedzie (niezgodność sygnatury), zgłaszany jest specjalny wyjątek Mach:

  • EXC_ARM_PAC (typ)
  • Kody mogą zawierać szczegóły (np. typ klucza, typ wskaźnika).

Jeśli binarka ma flagę TFRO_PAC_EXC_FATAL, kernel traktuje błędy PAC jako fatalne, omijając przechwycenie przez debugger. Ma to zapobiec wykorzystaniu debuggerów do obchodzenia kontroli PAC i jest włączone dla platform binaries.

Software Breakpoints

A software breakpoint (int3 on x86, brk on ARM64) jest zaimplementowany przez wywołanie celowego błędu.
Debugger przechwytuje to przez exception port:

  • Modyfikuje wskaźnik instrukcji lub pamięć.
  • Przywraca oryginalną instrukcję.
  • Wznawia wykonanie.

Ten sam mechanizm pozwala „przechwycić” wyjątek PAC — chyba że TFRO_PAC_EXC_FATAL jest ustawiony, wówczas nigdy nie trafia on do debuggera.

Konwersja na sygnały BSD

Jeśli żaden handler nie zaakceptuje wyjątku:

  • Kernel wywołuje task_exception_notify() → bsd_exception().

  • To mapuje Mach exceptions na sygnały:

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

Kluczowe pliki w źródłach XNU

  • osfmk/kern/exception.c → Rdzeń exception_triage(), exception_deliver_*().

  • bsd/kern/kern_sig.c → Logika dostarczania sygnałów.

  • osfmk/arm64/trap.c → Niskopoziomowe handlery trapów.

  • osfmk/mach/exc.h → Kody wyjątków i struktury.

  • osfmk/kern/task.c → Ustawianie task exception port.


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

Kernel używał zone allocator (kalloc) podzielonego na strefy o stałym rozmiarze.
Każda strefa przechowywała tylko alokacje jednej klasy rozmiaru.

Ze zrzutu ekranu:

Zone NameElement SizeExample Use
default.kalloc.1616 bytesBardzo małe struktury jądra, wskaźniki.
default.kalloc.3232 bytesMałe struktury, nagłówki obiektów.
default.kalloc.6464 bytesIPC messages, malutkie bufory jądra.
default.kalloc.128128 bytesObiekty średniej wielkości, części OSObject.
default.kalloc.12801280 bytesDuże struktury, metadane IOSurface/graphics.

Jak to działało:

  • Każde żądanie alokacji było zaokrąglane w górę do najbliższego rozmiaru strefy. (Np. żądanie 50 bajtów trafiało do strefy kalloc.64).
  • Pamięć w każdej strefie była przechowywana w freelist — kawałki zwalniane przez kernel wracały do tej strefy.
  • Jeśli przepełniłeś bufor 64-bajtowy, nadpisałeś następny obiekt w tej samej strefie.

Dlatego heap spraying / feng shui był tak skuteczny: można było przewidzieć sąsiadów obiektów, spryskując alokacje tej samej klasy rozmiaru.

The freelist

Wewnątrz każdej strefy kalloc, zwolnione obiekty nie były zwracane bezpośrednio do systemu — trafiały do freelist, powiązanej listy dostępnych kawałków.

  • Gdy kawałek był zwalniany, kernel zapisywał wskaźnik na początku tego kawałka → adres następnego wolnego kawałka w tej samej strefie.

  • Strefa przechowywała wskaźnik HEAD do pierwszego wolnego kawałka.

  • Alokacja zawsze używała obecnego HEAD:

  1. Pop HEAD (zwróć tę pamięć callerowi).

  2. Aktualizuj HEAD = HEAD->next (przechowywane w nagłówku zwolnionego kawałka).

  • Zwalnianie wkładało kawałki z powrotem:

  • freed_chunk->next = HEAD

  • HEAD = freed_chunk

Czyli freelist był po prostu listą powiązaną zbudowaną wewnątrz samej zwolnionej pamięci.

Stan normalny:

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)

Wykorzystywanie freelist

Ponieważ pierwsze 8 bajtów wolnego chunku = freelist pointer, atakujący mógłby go uszkodzić:

  1. Heap overflow into an adjacent freed chunk → overwrite its “next” pointer.
  2. Use-after-free write into a freed object → overwrite its “next” pointer.

Następnie, przy następnym alokowaniu tej wielkości:

  • Alokator wyjmuje uszkodzony chunk.
  • Podąża za dostarczonym przez atakującego “next” pointer.
  • Zwraca wskaźnik do dowolnej pamięci, umożliwiając fake object primitives lub celowane nadpisanie.

Wizualny przykład freelist poisoning:

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

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

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

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

Heap Grooming / Feng Shui

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

Steps:

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

Why it works:

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

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

Apple hardened the allocator and made heap grooming much harder:

1. From Classic kalloc to kalloc_type

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

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

2. Slabs and Per-CPU Caches

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

3. Randomization inside zones

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

4. Guarded Allocations

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

5. Page Protection Layer (PPL) and SPTM

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

6. Large Allocations

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

7. Allocation Patterns Attackers Target

Even with these protections, attackers still look for:

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

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

Example: Allocation Flow in Modern Heap

Suppose userspace calls into IOKit to allocate an OSData object:

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

Freelist pointer protection:

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

Comparison Table

FeatureOld Heap (Pre-iOS 15)Modern Heap (iOS 15+ / A12+)
Allocation granularityFixed size buckets (kalloc.16, kalloc.32, etc.)Size + type-based buckets (kalloc_type)
Placement predictabilityHigh (same-size objects side by side)Low (same-type grouping + randomness)
Freelist managementRaw pointers in freed chunks (easy to corrupt)Encoded pointers (safe-linking style)
Adjacent object controlEasy via sprays/frees (feng shui predictable)Hard — typed zones separate attacker objects
Kernel data/code protectionsFew hardware protectionsPPL / SPTM protect page tables & code pages, and PAC protects pointers
Allocation reuse validationNone (freelist pointers raw)zone_require / zone enforcement
Exploit reliabilityHigh with heap spraysMuch lower, requires logic bugs or info leaks
Large allocations handlingAll small allocations managed equallyLarge ones bypass zones → handled via VM

Modern Userland Heap (iOS, macOS — type-aware / xzone malloc)

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

Goals & Design Principles

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

Architecture & Components

Below are the main elements in the xzone allocator:

Segment Groups & Zones

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

Chunks & Blocks

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

Type / Type ID

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

Allocation & Freeing Workflow

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

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

Security Features & Hardening

These are the defenses built into modern userland xzone:

FeaturePurposeNotes
Metadata decouplingPrevent overflow from corrupting metadataMetadata lives in separate VM region (metadata slab)
Guard pages / unmapped slicesCatch out-of-bounds writesHelps detect buffer overflows rather than silently corrupting adjacent blocks
Type-based segregationPrevent cross-type reuse & type confusionEven same-size allocations from different types go to different zones
Memory Tagging (EMTE / MIE)Detect invalid access, stale references, OOB, UAFxzone works in concert with hardware EMTE in synchronous mode (“Memory Integrity Enforcement”)
Delayed reuse / poisoning / zapReduce chance of use-after-free exploitationFreed blocks may be poisoned, zeroed, or quarantined before reuse
Chunk reclamation / dynamic unmappingReduce memory waste and fragmentationEntire chunks may be unmapped when unused
Randomization / placement variationPrevent deterministic adjacencyBlocks in a chunk and chunk selection may have randomized aspects
Segregation of “data-only” allocationsSeparate allocations that don’t store pointersReduces attacker control over metadata or control fields

Interaction with Memory Integrity Enforcement (MIE / EMTE)

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

Jeśli chcesz, mogę też wygenerować cheat-sheet lub diagram wnętrza xzone do Twojej książki. Chcesz, żebym zrobił to następnie?
: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

Moduły helper i watcher PREYHUNTER

  • Watcher anti-analysis: Dedykowany binarny watcher cały czas profiluje urządzenie i przerywa kill-chain, gdy wykryte zostanie środowisko badawcze. Sprawdza security.mac.amfi.developer_mode_status, obecność konsoli diagnosticd, lokalizacje US lub IL, ślady jailbreaka takie jak Cydia, procesy takie jak bash, tcpdump, frida, sshd, lub checkrain, mobilne AV (McAfee, AvastMobileSecurity, NortonMobileSecurity), niestandardowe ustawienia HTTP proxy oraz niestandardowe root CA. Niepowodzenie dowolnej kontroli blokuje dalszą dostawę payloadu.

  • Helper surveillance hooks: Komponent helper komunikuje się z innymi etapami przez /tmp/helper.sock, a następnie ładuje zestawy hooków o nazwach DMHooker i UMHooker. Te hooki przechwytują ścieżki audio VOIP (nagrania trafiają do /private/var/tmp/l/voip_%lu_%u_PART.m4a), implementują systemowy keylogger, robią zdjęcia bez UI i hookują SpringBoard, aby stłumić powiadomienia, które te akcje normalnie by wywołały. Komponent helper działa więc jako ukryta warstwa walidacji i lekkiego nadzoru przed zrzuceniem cięższych implantów, takich jak Predator.

  • HiddenDot indicator suppression in SpringBoard: Przy wstrzykiwaniu kodu na poziomie jądra, Predator hookuje SBSensorActivityDataProvider._handleNewDomainData: (punkt agregacji aktywności sensorów). Hook zeruje wskaźnik Objective-C self (x0), więc wywołanie staje się [nil _handleNewDomainData:newData], powodując pominięcie aktualizacji kamery/mikrofonu i tłumiąc zarówno zielone, jak i pomarańczowe kropki.

  • Mach exception-based hooking flow (DMHooker): Hooki są realizowane przez EXC_BREAKPOINT + exception ports, następnie thread_set_state modyfikuje rejestry i wykonanie jest wznawiane. Kod zwrotny 2 oznacza “kontynuuj ze zmodyfikowanym stanem wątku.”

  • PAC-aware redirection for camera access checks: W mediaserverd skan wzorca (np. memmem) znajduje prywatną funkcję w pobliżu FigVideoCaptureSourceCreateWithSourceInfo w CMCapture.framework. Hook zwraca 3, aby przekierować używając pre-signed PAC cached return address, spełniając PAC przy jednoczesnym ominięciu kontroli.

  • VoIP capture pipeline in mediaserverd: Hooki AudioConverterNew i AudioConverterConvertComplexBuffer+52 przechwytują bufory, wywnioskowują sample rate z rozmiarów buforów, konwertują float32 PCM → int16 za pomocą NEON, downmixują 4-kanałowe do stereo i zapisują przez ExtAudioFileWrite(). Sam moduł VoIP nie tłumi wskaźników, więc operatorzy muszą osobno włączyć HiddenDot.

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

Webkit Dfg Store Barrier Uaf Angle Oob

Łańcuchy Zero-Click iMessage/Media Parser

Imessage Media Parser Zero Click Coreaudio Pac Bypass

Referencje

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks