iOS Exploiting

Reading time: 79 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

iOS Exploit Mitigations

1. Code Signing / Runtime Signature Verification

Introduced early (iPhone OS → iOS) これは基本的な保護の一つです: すべての実行可能コード(apps, dynamic libraries, JIT-ed code, extensions, frameworks, caches)は Apple の信頼に根ざした証明書チェーンで暗号的に署名されていなければなりません。ランタイムでは、バイナリをメモリにロードする前(または特定の境界を越えてジャンプする前)にシステムが署名を検証します。コードが改変(ビット反転、パッチ適用)されているか未署名であれば、ロードは失敗します。

  • 防ぐもの: エクスプロイトチェーンにおける「クラシックなペイロード配布+実行」段階;任意のコード注入;既存バイナリを書き換えて悪意あるロジックを挿入すること。
  • 仕組みの詳細:
  • Mach-O loader(および dynamic linker)はコードページ、セグメント、entitlements、team IDs、および署名がファイルの内容をカバーしているかをチェックします。
  • JIT キャッシュや動的生成コードのようなメモリ領域については、Apple はページを署名するか特別な API(例: mprotect with code-sign checks)で検証することを要求します。
  • 署名には entitlements や識別子が含まれ、OS は特定の API や特権機能が偽造できない特定の entitlements を必要とすることを強制します。
Example あるプロセスでコード実行を得て、ヒープに shellcode を書き込みそこにジャンプしようとしたとします。iOS ではそのページは実行可能にフラグされるだけでなく code-signature の制約を満たす必要があります。shellcode が Apple の証明書で署名されていないため、ジャンプは失敗するか、システムがそのメモリ領域を実行可能にすることを拒否します。

2. CoreTrust

Introduced around iOS 14+ era (or gradually in newer devices / later iOS) CoreTrust はバイナリ(システムおよびユーザーバイナリを含む)のランタイム署名検証を行うサブシステムで、ローカルのユーザーランドの信頼ストアに頼るのではなく Apple のルート証明書 に対して検証します。

  • 防ぐもの: インストール後のバイナリ改ざん、jailbreaking がシステムライブラリやユーザーアプリを差し替え・パッチしようとする手法;信頼されたバイナリを悪意あるもので置き換えてシステムを騙すこと。
  • 仕組みの詳細:
  • ローカルの信頼データベースや証明書キャッシュを信頼する代わりに、CoreTrust は Apple のルートを直接参照するか、セキュアなチェーンで中間証明書を検証します。
  • 既存バイナリに対する変更(例: ファイルシステム上の改変)が検出され拒否されることを保証します。
  • entitlements、team IDs、code signing フラグ、その他メタデータをロード時にバイナリに紐付けます。
Example jailbreak は `SpringBoard` や `libsystem` をパッチしたバージョンに置き換えて永続性を得ようとするかもしれません。しかし OS の loader や CoreTrust がチェックすると、署名不一致(または entitlements の変更)を検出し実行を拒否します。

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

Introduced in many OSes earlier; iOS had NX-bit / w^x for a long time DEP は書き込み可能(data)にマークされたページを実行不可に、実行可能にマークされたページを書き込み不可にすることを強制します。ヒープやスタック領域に単純に shellcode を書いて実行することはできません。

  • 防ぐもの: 直接的な shellcode 実行;クラシックなバッファオーバーフロー → 注入された shellcode へジャンプする手法。
  • 仕組みの詳細:
  • MMU / メモリ保護フラグ(ページテーブル経由)が分離を強制します。
  • 書き込み可能なページを実行可能にしようとする試みはシステムチェックを引き起こし(許可されないか code-sign 承認が必要になります)。
  • 多くの場合、ページを実行可能にするには追加の制約やチェックを強制する OS API を通す必要があります。
Example オーバーフローで shellcode をヒープに書き込んだ攻撃者が `mprotect(heap_addr, size, PROT_EXEC)` を試みると、システムは拒否するか新しいページが code-sign 制約を満たすことを検証します(shellcode では通らない)。

4. Address Space Layout Randomization (ASLR)

Introduced in iOS ~4–5 era (roughly iOS 4–5 timeframe) ASLR はライブラリ、ヒープ、スタック等の主要なメモリ領域のベースアドレスを各プロセス起動ごとにランダム化します。gadgets のアドレスは実行ごとに移動します。

  • 防ぐもの: ROP/JOP のために gadget アドレスをハードコーディングすること;静的なエクスプロイトチェーン;既知のオフセットへ盲目的にジャンプすること。
  • 仕組みの詳細:
  • 読み込まれる各ライブラリ / dynamic module はランダム化されたオフセットへ rebase されます。
  • スタックやヒープのベースポインタは(あるエントロピーの範囲で)ランダム化されます。
  • 場合によっては他の領域(例: mmap 割当)もランダム化されます。
  • 情報漏洩対策と組み合わせることで、攻撃者はまずアドレスやポインタを leak して実行時にベースアドレスを発見する必要があります。
Example ROP チェーンが `0x….lib + offset` に gadget を期待しているとします。しかし `lib` は毎回異なる場所に再配置されるため、ハードコードされたチェーンは失敗します。エクスプロイトは module のベースアドレスを最初に leak して gadget アドレスを計算する必要があります。

5. Kernel Address Space Layout Randomization (KASLR)

Introduced in iOS ~ (iOS 5 / iOS 6 timeframe) ユーザー ASLR に類似して、KASLR はブート時にカーネルテキストやその他のカーネル構造のベースをランダム化します。

  • 防ぐもの: カーネルレベルのエクスプロイトでカーネルコードやデータの固定位置に依存するもの;静的なカーネルエクスプロイト。
  • 仕組みの詳細:
  • 各ブートでカーネルのベースアドレスがランダム化されます(ある範囲内で)。
  • task_structvm_map といったカーネルデータ構造も再配置・オフセットされることがあります。
  • 攻撃者はオフセットを計算するためにまずカーネルポインタを leak するか情報開示の脆弱性を使う必要があります。
Example ローカル脆弱性がカーネルの関数ポインタ(例: `vtable` 内)を `KERN_BASE + offset` で破壊しようとする場合、`KERN_BASE` が不明なため攻撃者はまずそれを leak(例: 読み取りプリミティブ)して正しいアドレスを計算する必要があります。

6. Kernel Patch Protection (KPP / AMCC)

Introduced in newer iOS / A-series hardware (post around iOS 15–16 era or newer chips) KPP(別名 AMCC)はカーネルテキストページの整合性を(ハッシュやチェックサムで)継続的に監視します。許可されていないタイミングで改ざん(パッチ、インラインフック、コード変更)を検出すると、カーネルパニックや再起動を引き起こします。

  • 防ぐもの: 永続的なカーネルパッチ(カーネル命令の書き換え)、インラインフック、関数の静的上書き。
  • 仕組みの詳細:
  • ハードウェアまたはファームウェアのモジュールがカーネルテキスト領域を監視します。
  • ページを定期的または要求に応じて再ハッシュし期待値と比較します。
  • 正当な更新ウィンドウ外で不一致が発生した場合、デバイスをパニックにしてクラッシュさせます(永続的な悪意あるパッチを防ぐため)。
  • 攻撃者は検出ウィンドウを回避するか、正当なパッチ経路を使う必要があります。
Example エクスプロイトがカーネル関数のプロローグ(例: `memcmp`)をパッチして呼び出しを傍受しようとします。しかし KPP はコードページのハッシュが期待値と一致しないことを検出し、カーネルパニックを引き起こしてデバイスをクラッシュさせ、パッチが安定する前に阻止します。

7. Kernel Text Read‐Only Region (KTRR)

Introduced in modern SoCs (post ~A12 / newer hardware) KTRR はハードウェアで強制される仕組みです: ブートの早い段階でカーネルテキストがロックされると、それ以降 EL1(カーネル)からは読み取り専用になり、コードページへの追記が禁止されます。

  • 防ぐもの: ブート後のカーネルコードの改変(例: パッチ適用、インプレースのコード注入)を EL1 権限レベルで行うこと。
  • 仕組みの詳細:
  • ブート時(secure/bootloader ステージ)にメモリコントローラやセキュアハードウェアユニットがカーネルテキストを含む物理ページを読み取り専用としてマークします。
  • エクスプロイトが完全なカーネル権限を得ても、そのページを書き換えることはできません。
  • それらを変更するには、攻撃者はまずブートチェーンを破るか KTRR 自体を破る必要があります。
Example 権限昇格のエクスプロイトが EL1 にジャンプしてカーネル関数にトランポリンを書き込もうとします(例: syscall ハンドラ)。しかし KTRR によってページが読み取り専用にロックされているため、書き込みは失敗するかフォルトを引き起こしパッチは適用されません。

8. Pointer Authentication Codes (PAC)

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

  • PAC は ARMv8.3-A で導入されたハードウェア機能で、ポインタ値(リターンアドレス、関数ポインタ、特定のデータポインタ)の改ざんを検出するために、ポインタの未使用の上位ビットに小さな暗号的署名(“MAC”)を埋め込みます。
  • 署名(“PAC”)はポインタ値と modifier(コンテキスト値、例: stack pointer や識別データ)を元に計算されます。これにより同じポインタ値でもコンテキストが異なれば異なる PAC になります。
  • 使用時に、そのポインタを逆参照または分岐に使う前に authenticate 命令が PAC をチェックします。正しければ PAC は剥がされ純粋なポインタが得られ、無効であればポインタは “poisoned” になるかフォルトが発生します。
  • PAC を生成/検証するための鍵は特権レジスタ(EL1, kernel)に格納され、ユーザーモードから直接読み取ることはできません。
  • 多くのシステムで 64 ビットポインタのすべてのビットが使われているわけではないため(例: 48 ビットアドレス空間)、上位ビットは“余り”として PAC を保持できます。

Architectural Basis & Key Types

  • ARMv8.3 は 5 つの 128-bit 鍵(各鍵は 2 つの 64-bit システムレジスタで実装)を導入します。

  • APIAKey — instruction pointers 用(ドメイン “I”, key A)

  • APIBKey — 2 番目の instruction pointer key(ドメイン “I”, key B)

  • APDAKey — data pointers 用(ドメイン “D”, key A)

  • APDBKey — data pointers 用(ドメイン “D”, key B)

  • APGAKey — generic 鍵、非ポインタデータやその他の汎用用途向け

  • これらの鍵は特権システムレジスタに保存され(EL1/EL2 等でのみアクセス可能)、ユーザーモードからはアクセスできません。

  • PAC は暗号関数(ARM は QARMA を推奨)で計算され、以下を入力とします:

  1. ポインタ値(正規化部分)
  2. modifier(コンテキスト値、例: salt)
  3. 秘密鍵
  4. 内部のチューキングロジック 結果の PAC がポインタの上位ビットに格納された値と一致すれば認証は成功します。

Instruction Families

命名規則は: PAC / AUT / XPAC、その後にドメイン文字です。

  • PACxx 命令はポインタに署名して PAC を挿入します
  • AUTxx 命令は認証して PAC を剥がします(検証+剥離)
  • XPACxx 命令は検証せずに PAC を剥がします

ドメイン / サフィックス:

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

これらには専門化された/エイリアス形式もあります:

  • PACIASPPACIA X30, SP の省略形(link register を SP を modifier として署名)
  • AUTIASPAUTIA X30, SP(link register を SP で認証)
  • RETAARETAB(authenticate-and-return)や BLRAA(authenticate & branch) のような結合形式は ARM の拡張 / コンパイラサポートで存在します。
  • また modifier が暗黙的にゼロとなる PACIZA / PACIZB のようなゼロモディファイア変種もあります。

Modifiers

modifier の主目的は PAC を特定のコンテキストに結び付けることで、同じアドレスを異なるコンテキストで再利用することを防ぐことです。ハッシュに salt を追加するようなものです。

したがって:

  • modifier はコンテキスト値(別のレジスタ)で、PAC 計算に混ぜられます。典型的な選択肢は stack pointer (SP)、frame pointer、あるいはオブジェクト ID です。
  • SP を modifier に使うのはリターンアドレス署名で一般的です: PAC は特定のスタックフレームに結び付けられます。異なるフレームで LR を再利用しようとすると modifier が変わり、PAC 検証は失敗します。
  • 同じポインタ値を異なる modifier で署名すると異なる PAC が生成されます。
  • modifier は必ずしも秘密である必要はありませんが、理想的には攻撃者にコントロールされないことが望ましいです。
  • 意味のある modifier が存在しない場合、いくつかの形式はゼロや暗黙の定数を使います。

Apple / iOS / XNU Customizations & Observations

  • Apple の PAC 実装には per-boot diversifiers が含まれており、鍵やチューンがブートごとに変わるためブートを跨いだ再利用を防ぎます。
  • クロスドメイン緩和策もあり、ユーザーモードで署名された PAC がカーネルモードで簡単に再利用できないようになっています。
  • Apple Silicon(M1 など)ではリバースエンジニアリングにより 9 種類の modifier type や鍵制御用の Apple 固有のシステムレジスタが存在することが示されました。
  • Apple は多くのカーネルサブシステムで PAC を使用しています: リターンアドレス署名、カーネルデータのポインタ整合性、署名されたスレッドコンテキストなど。
  • Google Project Zero は、強力なカーネルの読み書きプリミティブがあるとカーネル PAC(A 鍵)の偽造が可能になる経路を示しました(A12 世代デバイスでの例)が、Apple は多くのパスを修正しました。
  • Apple のシステムでは一部の鍵が カーネル全体でグローバル である一方、ユーザープロセスはプロセス毎のランダム性を得ることがあります。

PAC Bypasses

  1. Kernel-mode PAC: theoretical vs real bypasses
  • カーネル PAC の鍵とロジックは厳密に制御されている(特権レジスタ、diversifiers、ドメイン分離)ため、任意の署名済みカーネルポインタを偽造するのは非常に困難です。
  • Azad の 2020 年の "iOS Kernel PAC, One Year Later" は iOS 12–13 でいくつかの部分的なバイパス(署名ガジェット、署名済み状態の再利用、保護されていない間接分岐)を報告しましたが、汎用的な完全バイパスはなかったと報告しています。 bazad.github.io
  • Apple のカスタマイズ("Dark Magic" 等)は exploitable surface をさらに狭めています(ドメイン切替、per-key 有効化ビット)。 i.blackhat.com
  • Apple silicon(M1/M2)に関する既知の kernel PAC bypass CVE-2023-32424 が Zecao Cai らにより報告されています。 i.blackhat.com
  • しかしこれらのバイパスは特定のガジェットや実装バグに依存していることが多く、汎用的な回避手段ではありません。

したがってカーネル PAC は 非常に堅牢 と考えられますが、完璧ではありません。

  1. User-mode / runtime PAC bypass techniques

これらはより一般的で、PAC が動的リンク/ランタイムフレームワークでどう適用されているかの不完全さを突きます。以下はクラス別の例です。

2.1 Shared Cache / A key issues

  • dyld shared cache はシステムフレームワークやライブラリの大きなプリリンク済みのブロブです。非常に広く共有されるため、shared cache 内の関数ポインタは既に署名されており、多くのプロセスで使われます。攻撃者はこれら既署名ポインタを "PAC oracle" として狙います。
  • 一部のバイパス手法は shared cache に存在する A-key 署名済みポインタを抽出・再利用して PAC を迂回しようとします。
  • "No Clicks Required" の講演では shared cache 上のオラクルを構築して相対アドレスを推測し、署名済みポインタと組み合わせて PAC を回避する手法が説明されています。 saelo.github.io
  • また、ユーザー空間の共有ライブラリからの関数ポインタのインポートが PAC によって十分に保護されていないと判断され、攻撃者が署名を変更せずに関数ポインタを取得できる事例が報告されています(Project Zero のバグエントリ)。 bugs.chromium.org

2.2 dlsym(3) / dynamic symbol resolution

  • 既知のバイパスの一つは dlsym() を呼び出して「既に署名された」関数ポインタ(A-key 署名、diversifier がゼロのことがある)を取得し、それを利用することです。dlsym は正当に署名されたポインタを返すため、それを使うことで PAC 偽造の必要性を回避できます。
  • Epsilon のブログはこの種のバイパスを詳述しており、dlsym("someSym") が署名済みポインタを返し間接呼び出しに利用できる事例を示しています。 blog.epsilon-sec.com
  • Synacktiv の "iOS 18.4 --- dlsym considered harmful" では、iOS 18.4 のあるバグにより dlsym で解決された一部のシンボルが誤って署名されたポインタ(または buggy diversifiers)を返し、意図しない PAC バイパスを可能にしていたことが説明されています。 Synacktiv
  • dyld のロジックでは、result->isCode の場合に __builtin_ptrauth_sign_unauthenticated(..., key_asia, 0) のように戻り値のポインタに context zero で署名することがあります。 blog.epsilon-sec.com

したがって dlsym はユーザーモードの PAC バイパスで頻繁に利用されるベクターです。

2.3 Other DYLD / runtime relocations

  • DYLD loader と動的再配置ロジックは複雑で、一時的にページを read/write にマップして再配置を行い、その後 read-only に戻すことがあります。攻撃者はこれらのウィンドウを悪用します。Synacktiv の講演は dynamic relocations を用いたタイミングベースの PAC バイパス("Operation Triangulation")を説明しています。 Synacktiv
  • DYLD ページは現在 SPRR / VM_FLAGS_TPRO のような保護で守られていますが、以前のバージョンではガードが弱かったことがあります。 Synacktiv
  • WebKit のエクスプロイトチェーンでは、DYLD loader は PAC バイパスのターゲットになることが多く、再配置や interposer hook を通じて攻撃されてきました。 Synacktiv

2.4 NSPredicate / NSExpression / ObjC / SLOP

  • ユーザーランドのエクスプロイトチェーンでは、Objective-C ランタイムのメソッド(NSPredicateNSExpressionNSInvocation など)がコントロールフローを隠して呼び出しを行うのに利用されます。
  • 古い iOS(PAC 導入前)では、fake NSInvocation オブジェクトを使って制御されたメモリ上の任意のセレクタを呼び出すエクスプロイトがありました。PAC が導入されてからは修正が必要になりましたが、SLOP(SeLector Oriented Programming)技術は PAC 下でも拡張され利用されています。 Project Zero
  • 元の SLOP 技術は偽の invocation を作って ObjC 呼び出しを連鎖させるものでした;バイパスは ISA や selector ポインタが常に完全に PAC 保護されているわけではない点を突きます。 Project Zero
  • ポインタ認証が部分的にしか適用されていない環境では、method / selector / target ポインタが常に PAC 保護されていないことがあり、バイパスの余地を与えます。

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>
バッファオーバーフローがスタック上のリターンアドレスを上書きする。攻撃者はターゲットとなるガジェットのアドレスを書き込むが、正しい PAC を計算できない。関数がリターンすると、CPU の `AUTIA` 命令が PAC の不一致によりフォルトを起こす。チェインは失敗する。
Project Zero の A12 (iPhone XS) に関する解析は、Apple の PAC がどのように使われているか、そして攻撃者がメモリの read/write プリミティブを持つ場合に PAC を偽造する方法を示した。
</details>


### 9. **Branch Target Identification (BTI)**
**Introduced with ARMv8.5 (later hardware)**
BTI は間接分岐ターゲットをチェックするハードウェア機能で、`blr` や間接的な call/jump を実行する際に、ターゲットが **BTI landing pad**(`BTI j` または `BTI c`)で始まっている必要がある。landing pad のないガジェットアドレスにジャンプすると例外が発生する。

LLVM の実装ノートは BTI 命令の三つのバリアントと、それらがどの分岐タイプにマップされるかを示している。

| BTI Variant | What it permits (which branch types) | Typical placement / use case |
|-------------|----------------------------------------|-------------------------------|
| **BTI C** | *call* スタイルの間接分岐のターゲット(例: `BLR`、または `BR` が X16/X17 を使う場合) | 間接的に呼ばれる可能性のある関数のエントリに置く |
| **BTI J** | *jump* スタイルの分岐のターゲット(例: tail call に使われる `BR`) | ジャンプテーブルや tail-call で到達可能なブロックの先頭に置く |
| **BTI JC** | C と J の両方として働く | call でも jump でもターゲットになりうる場所に使える |

- branch target enforcement でコンパイルされたコードでは、コンパイラは有効な間接分岐ターゲット(関数の先頭やジャンプで到達可能なブロック)ごとに BTI 命令(C、J、または JC)を挿入し、間接分岐がそれらの場所にしか成功しないようにする。
- **Direct branches / calls**(つまり固定アドレスの `B`, `BL`)は BTI による制限を受けない。これはコードページが信頼されており攻撃者がそれらを変更できないという前提に基づいている(したがって direct branches は安全と見なされる)。
- また、**RET / return** 命令は一般に BTI の制限を受けない。返り先アドレスは PAC や return signing 機構で保護されているためである。

#### Mechanism and enforcement

- CPU が “guarded / BTI-enabled” とマークされたページ内で **間接分岐 (`BLR` / `BR`)** をデコードすると、ターゲットアドレスの最初の命令が許可された BTI(C、J、または JC)であるかをチェックする。そうでない場合は **Branch Target Exception** が発生する。
- BTI 命令のエンコーディングは、以前の ARM バージョンで NOP に予約されていたオペコードを再利用するよう設計されている。したがって BTI 対応バイナリは後方互換性を保ち、BTI 非対応ハードウェアではそれらの命令が NOP として機能する。
- BTI を追加するコンパイラパスは、必要な場所にのみ挿入する:間接的に呼ばれる可能性のある関数、あるいはジャンプでターゲットになり得る基本ブロック。
- いくつかのパッチや LLVM のコードは、BTI が *すべての* 基本ブロックに挿入されるわけではなく、スイッチやジャンプテーブルなどからの潜在的な分岐先となるものだけに挿入されることを示している。

#### BTI + PAC synergy

PAC はポインタ値(ソース)を保護し、間接呼び出し/リターンのチェインが改竄されていないことを保証する。

BTI は有効なポインタであっても、適切にマークされたエントリポイントのみをターゲットにできるようにする。

これらを組み合わせると、攻撃者は正しい PAC を持つ有効なポインタに加え、ターゲットに BTI が配置されている必要があり、ガジェット構築の難易度が上がる。

#### Example


<details>
<summary>Example</summary>
あるエクスプロイトが `0xABCDEF` にある、`BTI c` で始まらないガジェットにピボットしようとする。CPU は `blr x0` を実行した際にターゲットをチェックし、最初の命令が有効な landing pad を含まないためフォルトする。多くのガジェットは 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** は **ARMv8.1-A** で導入された機能で、**privileged code**(EL1 または EL2)が **user-accessible (EL0)** とマークされたメモリを **読み書き** することを防ぐ(PAN を明示的に無効にしない限り)。
- 目的は:カーネルが騙されたり妥協されたりしても、カーネルがユーザ空間のポインタを任意に逆参照できないようにして、**ret2usr** スタイルのエクスプロイトやユーザ制御バッファの悪用を減らすこと。
- PAN が有効な場合(PSTATE.PAN = 1)、“accessible at EL0” とされる仮想アドレスに対する特権モードの load/store 命令はパーミッションフォルトを引き起こす。
- カーネルが正当にユーザ空間メモリへアクセスする必要がある場合(例: ユーザバッファへのデータコピー)、カーネルは一時的に PAN を無効化するか(または“unprivileged load/store” 命令を使う)してアクセスを許可する必要がある。
- Linux on ARM64 では、PAN サポートは約 2015 年に導入され、カーネルのパッチはこの機能の検出を追加し、`get_user` / `put_user` 等を PAN をクリアするバリアントに置き換えた。

**重要なニュアンス / 制限 / バグ**
- Siguza らが指摘したように、ARM の設計における仕様上のバグ(あるいは曖昧な挙動)により、**execute-only user mappings**(`--x`)は **PAN をトリガーしない場合がある**。つまり、ユーザページが実行可能で読み取り許可がない場合、カーネルの読み取り試行が PAN をバイパスする可能性がある。これはアーキテクチャが “accessible at EL0” を判定する際に読み取り可能性を要求しており、実行可能性だけでは不十分と見なすためである。これにより、特定の構成では PAN を回避できる。
- そのため、もし iOS / XNU が execute-only なユーザページを許可している(JIT やコードキャッシュ設定などで)なら、カーネルは PAN が有効でもそれらから読み取ってしまう可能性がある。これは一部の ARMv8+ システムで知られている微妙な攻撃対象である。

#### PXN (Privileged eXecute Never)

- **PXN** はページテーブルのフラグ(leaf / block エントリ内)で、ページが **特権モードで実行不可** であることを示す(つまり EL1 実行時にそのページは実行できない)。
- PXN は、カーネルや他の特権コードがユーザ空間ページからのジャンプや命令実行を行うのを防ぐ。結果として、privileged レベルでの制御フローをユーザメモリに逸らすことを止める。
- PAN と組み合わせると次が保証される:
  1. カーネルはデフォルトでユーザ空間データを読み書きできない(PAN)
  2. カーネルはユーザ空間コードを実行できない(PXN)
- ARMv8 のページテーブルフォーマットでは、leaf エントリに `PXN` ビット(および unprivileged 用の `UXN`)が属性ビットとして存在する。

したがって、カーネルにユーザメモリを指す破損した関数ポインタがあっても、ジャンプしようとすれば PXN ビットによりフォルトが発生する。

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

PAN / PXN の動作を理解するには、ARM の翻訳とパーミッションモデルがどのように機能するかを見る必要がある(簡略化):

- 各ページまたはブロックエントリは、読み書きや特権/非特権区別のための **AP[2:1]** と、実行禁止制限のための **UXN / PXN** ビットなどの属性フィールドを持つ。
- PSTATE.PAN が 1(有効)であるとき、ハードウェアは意味論を修正して強制する:EL0 により “accessible” とマークされたページへの特権アクセスは拒否(フォルト)される。
- 前述のバグのため、読み取り権がない実行専用ページは、実装によっては “accessible at EL0” と見なされず、PAN を迂回してカーネルがそれらから読み取れる場合がある。
- ページに PXN ビットが設定されていると、たとえ高い特権レベルからの命令フェッチであっても実行が禁止される。

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

ハード化されたカーネル設計(Apple が採用している可能性のあるもの)では:

- カーネルはデフォルトで PAN を有効にする(よって特権コードは制約される)。
- 正当にユーザバッファを読み書きする経路(syscall のデータコピー、I/O、ユーザポインタの read/write など)では、カーネルは一時的に **PAN を無効化** するか、ユーザメモリアクセスを許す特別な命令を使う。
- ユーザデータアクセスが終わったら、PAN を再度有効にしなければならない。
- PXN はページテーブル経由で強制される:ユーザページには PXN = 1 が設定され(カーネルはそこを実行できない)、カーネルページには PXN が設定されない(カーネルコードは実行可能)。
- カーネルは、処理のどの経路でも実行フローがユーザメモリ領域へ移ってしまわないように注意する必要がある(そうでなければ PXN をバイパスすることになる)。したがって「ユーザ制御のシェルコードにジャンプする」ことを前提にしたエクスプロイトチェインは阻止される。

前述の execute-only ページによる PAN バイパスのため、実際のシステムでは Apple は execute-only user pages を無効にするか、仕様上の弱点を回避するための修正を行っている可能性がある。

#### Attack surfaces, bypasses, and mitigations

- **PAN bypass via execute-only pages**: 前述のように、仕様の隙間により execute-only(読み取り許可なし、実行のみ)になっているユーザページは PAN をトリガーしない場合があり、これにより攻撃者は通常とは異なる経路でデータを渡すことができる。
- **Temporal window exploit**: カーネルが必要以上に長い間 PAN を無効にする場合、そのウィンドウを狙ったレースや悪意ある経路が不正なユーザメモリアクセスを誘発する可能性がある。
- **Forgotten re-enable**: PAN を再有効化し忘れるコードパスがあると、以降のカーネル操作が誤ってユーザメモリにアクセスしてしまう。
- **Misconfiguration of PXN**: ページテーブルがユーザページに PXN を設定していなかったり、ユーザコードページを誤ってマップしていると、カーネルがユーザ空間コードを実行させられてしまう可能性がある。
- **Speculation / side-channels**: 推測実行に起因するバイパスと同様に、PAN / PXN チェックの一時的な違反を引き起こすマイクロアーキテクチャ的な副作用が存在するかもしれない(ただしこうした攻撃は CPU 設計に大きく依存する)。
- **Complex interactions**: JIT、shared memory、just-in-time code regions のような高度な機能では、カーネルは特定のメモリアクセスやユーザマップ領域での実行を細かく許可する必要があり、PAN/PXN 制約下でそれらを安全に設計するのは難しい。

#### Example

<details>
<summary>Code Example</summary>
以下は、ユーザメモリアクセスのために PAN を有効/無効化する擬似アセンブリ列と、どのようにフォルトが発生しうるかを示す例である。
</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

If the kernel had **not** set PXN on that user page, then the branch might succeed — which would be insecure.

If the kernel forgets to re-enable PAN after user memory access, it opens a window where further kernel logic might accidentally read/write arbitrary user memory.

If the user pointer is into an execute-only page (user page with only execute permission, no read/write), under the PAN spec bug, `ldr W2, [X1]` might **not** fault even with PAN enabled, enabling a bypass exploit, depending on implementation.

</details>

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

---

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

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

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

#### Why TBI: ユースケースと動機

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

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

---

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

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

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

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

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

#### Gate / Entry & Exit

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

#### Protected page categories

The pages that PPL typically protects include:

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

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


#### Known Bypasses & Vulnerabilities

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

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

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

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

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

2. **Other potential hazards & constraints**

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



#### Example
<details>
<summary>コード例</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

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

Example A kernel exploit tries to overwrite the entitlement table, or disable code-sign enforcement by modifying a kernel signature blob. Because that page is PPL-protected, the write is blocked unless going through the PPL interface. So even with kernel code execution, you cannot bypass code-sign constraints or modify credential data arbitrarily. On iOS 17+ certain devices use SPTM to further isolate PPL-managed pages.

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

Here’s a higher-level description of how EMTE operates under Apple’s MIE setup:

  1. Tag assignment
  • When memory is allocated (e.g. in kernel or user space via secure allocators), a secret tag is assigned to that block.
  • The pointer returned to the user or kernel includes that tag in its high bits (using TBI / top byte ignore mechanisms).
  1. Tag checking on access
  • Whenever a load or store is executed using a pointer, the hardware checks that the pointer’s tag matches the memory block’s tag (allocation tag). If mismatch, it faults immediately (since synchronous).
  • Because it's synchronous, there is no “delayed detection” window.
  1. Retagging on free / reuse
  • When memory is freed, the allocator changes the block’s tag (so older pointers with old tags no longer match).
  • A use-after-free pointer would therefore have a stale tag and mismatch when accessed.
  1. Neighbor-tag differentiation to catch overflows
  • Adjacent allocations are given distinct tags. If a buffer overflow spills into neighbor’s memory, tag mismatch causes a fault.
  • This is especially powerful in catching small overflows that cross boundary.
  1. Tag confidentiality enforcement
  • Apple must prevent tag values being leaked (because if attacker learns the tag, they could craft pointers with correct tags).
  • They include protections (microarchitectural / speculative controls) to avoid side-channel leakage of tag bits.
  1. Kernel and user-space integration
  • Apple uses EMTE not just in user-space but also in kernel / OS-critical components (to guard kernel against memory corruption).
  • The hardware/OS ensures tag rules apply even when kernel is executing on behalf of user space.
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>

#### 制約と課題

- **Intrablock overflows**: オーバーフローが同一の割り当て内に留まり(境界を越えない)、tag が同じままの場合、tag mismatch は検出できない。
- **Tag width limitation**: tag に使えるビット数は限られている(例:4 ビットや小さなドメイン)—名前空間が制約される。
- **Side-channel leaks**: tag ビットが(cache / speculative execution 経由で)leaked できる場合、攻撃者は有効な tags を学び回避できる可能性がある。Apple の tag confidentiality enforcement はこれを緩和することを目的としている。
- **Performance overhead**: 各 load/store ごとの tag チェックはコストを追加する。Apple はハードウェア最適化でオーバーヘッドを低く抑える必要がある。
- **Compatibility & fallback**: 古いハードウェアや EMTE をサポートしない部分ではフォールバックが必要になる。Apple は MIE がサポートされるデバイスでのみ有効になると主張している。
- **Complex allocator logic**: allocator は tags の管理、retagging、境界の整列、mis-tag 衝突回避を行う必要がある。allocator ロジックのバグは新たな脆弱性を生む可能性がある。
- **Mixed memory / hybrid areas**: 一部のメモリは untagged(レガシー)のまま残る可能性があり、相互運用がより複雑になる。
- **Speculative / transient attacks**: 多くのマイクロアーキテクチャ保護と同様に、speculative execution や micro-op fusion によりチェックを一時的に回避したり tag ビットを leak したりする可能性がある。
- **Limited to supported regions**: Apple は EMTE をカーネルやセキュリティ重要サブシステムなど選択的で高リスクな領域にのみ適用する可能性があり、全域には及ばない場合がある。



---

## Key enhancements / differences compared to standard MTE

以下は Apple が強調する改善点と変更点:

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

Apple はハードウェアとソフトウェアスタックの両方を制御しているため、EMTE を厳密に適用し、パフォーマンス上の問題を避け、side-channel の穴を閉じることができる。

---

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

以下は Apple の MIE セットアップ下で EMTE がどのように動作するかの上位レベルの説明:

1. **Tag assignment**
- メモリが割り当てられるとき(カーネルや secure allocators を介したユーザ空間など)、そのブロックに**secret tag** が割り当てられる。
- ユーザやカーネルに返されるポインタはその tag を上位ビットに含む(TBI / top byte ignore メカニズムを使用)。

2. **Tag checking on access**
- ポインタを使って load/store が実行されるたびに、ハードウェアはポインタの tag がメモリブロックの tag(allocation tag)と一致するかをチェックする。ミスマッチなら即座にフォルトする(同期的であるため)。
- 同期的であるため、検出の遅延ウィンドウは存在しない。

3. **Retagging on free / reuse**
- メモリが free されると、allocator はブロックの tag を変更する(古いポインタの tag はもはや一致しない)。
- use-after-free ポインタは古い tag を持っているため、アクセス時にミスマッチとなる。

4. **Neighbor-tag differentiation to catch overflows**
- 隣接する割り当てには異なる tags が付与される。バッファオーバーフローが隣のメモリに流出すると tag mismatch によりフォルトが発生する。
- これは境界を越える小さなオーバーフローを検出するのに特に有効である。

5. **Tag confidentiality enforcement**
- 攻撃者が tag を学習して正しい tag を持つポインタを作成できないように、Apple は tag 値の leaked を防ぐ必要がある。
- そのために(speculative side-channels 等を通じた)情報漏洩を避けるための保護を含めている。

6. **Kernel and user-space integration**
- Apple は EMTE をユーザ空間だけでなくカーネル / OS 重要コンポーネントにも適用している(カーネルのメモリ破壊から保護するため)。
- ハードウェア / OS は、カーネルがユーザ空間のために実行している場合でも tag ルールが適用されるように保証する。

EMTE は MIE に組み込まれているため、Apple は主要な攻撃面に対して EMTE を同期モードで有効にしており、オプトインやデバッグモードとしてではなく常時有効にしている。

---

## Exception handling in XNU

例外(例:`EXC_BAD_ACCESS`, `EXC_BAD_INSTRUCTION`, `EXC_CRASH`, `EXC_ARM_PAC`, など)が発生すると、XNU カーネルの Mach レイヤがそれを UNIX スタイルの signal(`SIGSEGV`, `SIGBUS`, `SIGILL`, ... のような)に変換される前に介入する責任を負う。

このプロセスは、ユーザ空間に到達するか BSD signal に変換されるまでに複数の例外伝播と処理レイヤを経る。

### Exception Flow (High-Level)

1.  **CPU triggers a synchronous exception**(例:無効なポインタ参照、PAC failure、illegal instruction 等)。

2.  **Low-level trap handler** が動作する(XNU ソースの `trap.c`, `exception.c`)。

3.  トラップハンドラは **`exception_triage()`** を呼び出す。これは Mach 例外処理の中核である。

4.  `exception_triage()` は例外をどのようにルーティングするかを決定する:

-   最初に **thread の exception port** へ。

-   次に **task の exception port** へ。

-   次に **host の exception port**(多くの場合 `launchd` や `ReportCrash`)。

これらのポートのいずれも例外を処理しない場合、カーネルは:

-   **それを BSD signal に変換する**(ユーザ空間プロセス向け)。

-   **panic** を引き起こす(カーネル空間の例外の場合)。

### Core Function: `exception_triage()`

関数 `exception_triage()` は Mach 例外を処理するハンドラのチェーンに沿ってルーティングし、いずれかが処理するか最終的に致命的になるまで進める。これは `osfmk/kern/exception.c` に定義されている。
<div class="codeblock_filename_container"><span class="codeblock_filename_inner hljs">c</span></div>

```c
void exception_triage(exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt);

典型的なコールフロー:

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

If all fail → handled by bsd_exception() → translated into a signal like SIGSEGV.

Exception Ports

各 Mach オブジェクト (thread, task, host) は、例外メッセージが送られる exception ports を登録できます。

これらは API によって定義されています:

task_set_exception_ports()
thread_set_exception_ports()
host_set_exception_ports()

各例外ポートは次を持つ:

  • mask(どの例外を受け取りたいか)
  • port name(メッセージを受け取る Mach port)
  • behavior(カーネルがメッセージを送る方法)
  • flavor(含めるスレッド状態の種類)

デバッガと例外処理

debugger(例: LLDB)はターゲットのタスクやスレッドに exception port を設定する。通常は task_set_exception_ports() を使用する。

例外が発生した場合:

  • Mach メッセージがデバッガプロセスに送られる。
  • デバッガは例外を handle(再開、レジスタ変更、命令をスキップ)するか、handle しない かを決められる。
  • デバッガが処理しなければ、例外は次のレベルに伝播する(thread → task → host)。

Flow of EXC_BAD_ACCESS

  1. スレッドが無効なポインタをデリファレンスする → CPU が Data Abort を発生させる。

  2. カーネルトラップハンドラが exception_triage(EXC_BAD_ACCESS, ...) を呼ぶ。

  3. メッセージは以下に送られる:

  • Thread port → (デバッガがブレークポイントを傍受できる)。

  • デバッガが無視すると → Task port → (プロセスレベルのハンドラ)。

  • 無視されると → Host port(通常は ReportCrash)。

  1. 誰も処理しなければ → bsd_exception()SIGSEGV に変換する。

PAC Exceptions

Pointer Authentication (PAC) が失敗(署名不一致)すると、特別な Mach 例外が発生する:

  • EXC_ARM_PAC(タイプ)
  • コードには詳細(例: key type、pointer type)が含まれることがある。

バイナリにフラグ TFRO_PAC_EXC_FATAL が設定されていると、カーネルは PAC 失敗を致命的と扱い、デバッガの傍受をバイパスする。これは攻撃者がデバッガを使って PAC チェックを回避するのを防ぐためで、platform binaries に対して有効になる。

Software Breakpoints

ソフトウェアブレークポイント(x86 の int3、ARM64 の brk)は意図的なフォルトを発生させることで実装される。
デバッガはこれを exception port 経由でキャッチする:

  • 命令ポインタやメモリを変更する。
  • 元の命令を復元する。
  • 実行を再開する。

同じ仕組みで PAC 例外を「キャッチ」することができる --- ただし TFRO_PAC_EXC_FATAL が設定されている場合は例外は決してデバッガに到達しない

Conversion to BSD Signals

誰も例外を受け入れない場合:

  • カーネルは task_exception_notify() → bsd_exception() を呼ぶ。

  • これが Mach 例外をシグナルにマッピングする:

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

Key Files in XNU Source

  • osfmk/kern/exception.cexception_triage()exception_deliver_*() のコア。
  • bsd/kern/kern_sig.c → シグナル配送ロジック。
  • osfmk/arm64/trap.c → 低レベルのトラップハンドラ。
  • osfmk/mach/exc.h → 例外コードと構造体。
  • osfmk/kern/task.c → タスクの例外ポート設定。

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

カーネルは固定サイズの「zones」に分かれた zone allocatorkalloc)を使用していた。各 zone は単一のサイズクラスの割り当てのみを格納する。

スクリーンショットから:

Zone NameElement SizeExample Use
default.kalloc.1616 bytes非常に小さいカーネル構造体、ポインタ。
default.kalloc.3232 bytes小さな構造体、オブジェクトヘッダ。
default.kalloc.6464 bytesIPC メッセージ、小さなカーネルバッファ。
default.kalloc.128128 bytesOSObject の一部など中程度のオブジェクト。
default.kalloc.12801280 bytes大きな構造体、IOSurface/グラフィックスのメタデータ。

動作の仕組み:

  • 各割り当て要求は最も近いゾーンサイズに切り上げられる。 (例: 50 バイトの要求は kalloc.64 ゾーンに入る)。
  • 各ゾーンのメモリは freelist で管理されていた — カーネルが解放したチャンクはそのゾーンに戻された。
  • もし 64 バイトバッファをオーバーフローすると、同じゾーン内の次のオブジェクトを上書きしてしまう。

このため heap spraying / feng shui が非常に効果的だった: 同じサイズクラスの割り当てをスプレーすれば、オブジェクトの隣接関係を予測できた。

The freelist

各 kalloc ゾーン内では、解放されたオブジェクトはシステムに直接返されず、freelist(利用可能チャンクの連結リスト)に入れられていた。

  • チャンクが解放されると、カーネルはそのチャンクの先頭にポインタを書き込む → 同じゾーンの次のフリーチャンクのアドレス。

  • ゾーンは最初のフリーチャンクを指す HEAD ポインタを保持する。

  • 割り当ては常に現在の HEAD を使う:

  1. HEAD を pop(そのメモリを呼び出し元に返す)。

  2. HEAD = HEAD->next(解放済みチャンクのヘッダに格納されている)。

  • 解放はチャンクをプッシュする:

  • freed_chunk->next = HEAD

  • HEAD = freed_chunk

つまり freelist は、解放されたメモリ自身の内部に構築された単なる連結リストだった。

Normal state:

Zone page (64-byte chunks for example):
[ A ] [ F ] [ F ] [ A ] [ F ] [ A ] [ F ]

Freelist view:
HEAD ──► [ F ] ──► [ F ] ──► [ F ] ──► [ F ] ──► NULL
(next ptrs stored at start of freed chunks)

freelistの悪用

free chunkの最初の8バイトが freelist pointer であるため、攻撃者はそれを改ざんできます:

  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.

Then, on the next allocation of that size:

  • アロケータは破損したチャンクをポップする。
  • 攻撃者が提供した “next” pointer を辿る。
  • 任意のメモリへのポインタを返し、fake object primitives や targeted overwrite を可能にする。

Visual example of 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.

この freelist 設計はハードニング前における悪用を非常に効果的にしていました:heap sprays による予測可能な隣接、raw pointer freelist links、そしてタイプ分離の欠如により、攻撃者は UAF/overflow バグを任意のカーネルメモリ制御へエスカレートできました。

Heap Grooming / Feng Shui

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

Steps:

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

Why it works:

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

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

Apple hardened the allocator and made heap grooming much harder:

1. From Classic kalloc to kalloc_type

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

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

2. Slabs and Per-CPU Caches

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

3. Randomization inside zones

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

4. Guarded Allocations

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

5. Page Protection Layer (PPL) and SPTM

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

6. Large Allocations

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

7. Allocation Patterns Attackers Target

Even with these protections, attackers still look for:

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

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

Example: Allocation Flow in Modern Heap

Suppose userspace calls into IOKit to allocate an OSData object:

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

Freelist pointer protection:

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

Comparison Table

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

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

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

Goals & Design Principles

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

Architecture & Components

Below are the main elements in the xzone allocator:

Segment Groups & Zones

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

Chunks & Blocks

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

Type / Type ID

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

Allocation & Freeing Workflow

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

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

Security Features & Hardening

These are the defenses built into modern userland xzone:

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

Interaction with Memory Integrity Enforcement (MIE / EMTE)

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

If you like, I can also generate a cheat-sheet or diagram of xzone internals for your book. Do you want me to do that next? ::contentReference[oai:20]{index=20}


(Old) Physical Use-After-Free via IOSurface

ios Physical UAF - IOSurface


Ghidra Install BinDiff

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

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

Using BinDiff with Kernel versions

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

macOS Kernel Extensions & Kernelcache

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

Finding the right XNU version

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

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

iMessage/Media Parser Zero-Click Chains

Imessage Media Parser Zero Click Coreaudio Pac Bypass

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする