iOS Exploiting
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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
iOS Exploit Mitigations
1. Code Signing / Runtime Signature Verification
Introduced early (iPhone OS → iOS) これは基本的な保護の一つです: すべての実行可能コード(apps, dynamic libraries, JIT-ed code, extensions, frameworks, caches)は Apple の信頼のルートにある証明書チェーンで暗号的に署名されている必要があります。ランタイムでは、バイナリをメモリにロードする前(またはある境界を跨ぐジャンプを行う前)にシステムがその署名を検証します。コードが改変(bit-flipped、patched)されたり署名されていない場合、ロードは失敗します。
- Thwarts: エクスプロイトチェーンの「古典的なペイロード配置 + 実行」段階;任意コード注入;既存バイナリを改変して悪意あるロジックを挿入すること。
- Mechanism detail:
- Mach-O ローダ(および dynamic linker)は code pages、segments、entitlements、team IDs をチェックし、署名がファイルの内容をカバーしていることを確認する。
- JIT キャッシュや動的生成コードのようなメモリ領域については、Apple はページが署名されるか特別な API(例:
mprotectwith code-sign checks)経由で検証されることを強制する。 - 署名は entitlements や識別子を含み、OS は特定の API や特権機能が特定の entitlements を必要とすることを強制し、それらは偽造できない。
例
あるプロセスでコード実行を得た攻撃者が shellcode を heap に書き込みそこへジャンプしようとするとします。iOS では、そのページは実行可能にフラグされるだけでなく code-signature の制約を満たす必要があります。shellcode は Apple の証明書で署名されていないため、ジャンプは失敗するか、システムがそのメモリ領域を実行可能にすることを拒否します。2. CoreTrust
Introduced around iOS 14+ era (or gradually in newer devices / later iOS) CoreTrust はバイナリ(system と user バイナリを含む)をランタイムで Apple の root certificate に対して検証するサブシステムで、ユーザーランドのキャッシュされたトラストストアに頼らない仕組みです。
- Thwarts: インストール後のバイナリ改ざん、
jailbreaking技術で system libraries や user apps を差し替えやパッチ適用する試み;信頼されたバイナリを悪意あるものと入れ替えてシステムを騙すこと。 - Mechanism detail:
- ローカルのトラストデータベースや証明書キャッシュを信用する代わりに、CoreTrust は Apple の root を直接参照するか、セキュアなチェーンで中間証明書を検証する。
- 既存バイナリへの変更(例: filesystem 上の改変)が検出され拒否されることを保証する。
- entitlements、team IDs、code signing フラグ、その他のメタデータをバイナリのロード時にバイナリに紐づける。
例
jailbreak が `SpringBoard` や `libsystem` をパッチされたバージョンに置き換えて永続性を得ようとする場合、OS のローダや 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 は writable(データ用)にマークされたページを non-executable、および executable にマークされたページを non-writable にすることを強制します。単純に heap や stack に shellcode を書いて実行することはできません。
- Thwarts: 直接の shellcode 実行;古典的なバッファオーバーフロー → 注入された shellcode にジャンプする手法。
- Mechanism detail:
- MMU / memory protection flags(page tables 経由)が分離を強制する。
- writable なページを executable に変更しようとする試みはシステムチェックを引き起こし(禁止されるか code-sign 承認が必要になる)。
- 多くの場合、ページを executable にするには追加の制約やチェックを強制する OS API を通る必要がある。
例
オーバーフローで shellcode が heap に書き込まれる。攻撃者は `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 はライブラリ、heap、stack などの主要なメモリ領域のベースアドレスをプロセスごとにランダム化します。gadgets のアドレスは実行ごとに変わります。
- Thwarts: ROP/JOP 用に gadget アドレスをハードコードすること;静的なエクスプロイトチェーン;既知オフセットへ盲目的にジャンプすること。
- Mechanism detail:
- 読み込まれた各ライブラリ / dynamic module はランダム化されたオフセットで rebase される。
- stack と heap のベースポインタは(ある程度のエントロピー制限内で)ランダム化される。
- 場合によっては他の領域(例: mmap allocations)もランダム化される。
- 情報-leak 緩和と組み合わせると、攻撃者は最初にアドレスやポインタを leak してランタイムでベースアドレスを特定する必要がある。
例
ROP チェーンは `0x….lib + offset` に gadget があることを期待する。しかし `lib` は毎回異なる位置に再配置されるため、ハードコードされたチェーンは失敗する。エクスプロイトはモジュールのベースアドレスを先に leak してから gadget アドレスを計算する必要がある。5. Kernel Address Space Layout Randomization (KASLR)
Introduced in iOS ~ (iOS 5 / iOS 6 timeframe) ユーザー ASLR の類似で、KASLR は起動時に kernel text や他のカーネル構造のベースをランダム化します。
- Thwarts: カーネルレベルのエクスプロイトが kernel code や data の固定位置に依存すること;静的なカーネルエクスプロイト。
- Mechanism detail:
- 各ブートでカーネルの base address は範囲内でランダム化される。
task_structsやvm_mapのようなカーネルデータ構造も再配置またはオフセットされることがある。- 攻撃者はまず kernel ポインタを leak するか情報開示脆弱性を使ってオフセットを計算してからカーネル構造やコードを乗っ取る必要がある。
例
ローカル脆弱性が kernel の関数ポインタ(例: `vtable`)を `KERN_BASE + offset` で破壊しようとする。しかし `KERN_BASE` が不明なため、攻撃者はまずそれを leak(例: read primitive を介して)して正しいアドレスを計算する必要がある。6. Kernel Patch Protection (KPP / AMCC)
Introduced in newer iOS / A-series hardware (post around iOS 15–16 era or newer chips) KPP(別名 AMCC)は kernel text pages の整合性を(ハッシュやチェックサムで)継続的に監視します。許可されたウィンドウ外で改竄(パッチ、inline hooks、コード修正)を検出すると、カーネルパニックや再起動を引き起こします。
- Thwarts: 永続的なカーネルパッチ(カーネル命令の変更)、inline hooks、関数の静的上書き。
- Mechanism detail:
- ハードウェアまたはファームウェアのモジュールがカーネルテキスト領域を監視する。
- 定期的またはオンデマンドでページを再ハッシュし、期待値と比較する。
- 誤差が許容されないウィンドウ外で発生した場合、デバイスをパニックさせ(悪意あるパッチを避けるため)クラッシュさせる。
- 攻撃者は検出ウィンドウを避けるか、正当なパッチ経路を使う必要がある。
例
エクスプロイトがカーネル関数のプロローグ(例: `memcmp`)をパッチして呼び出しを傍受しようとする。しかし KPP はそのコードページのハッシュが期待値と一致しないことを検知し、カーネルパニックを発生させてデバイスをクラッシュさせ、パッチが安定する前に阻止する。7. Kernel Text Read‐Only Region (KTRR)
Introduced in modern SoCs (post ~A12 / newer hardware) KTRR はハードウェアによって強制される仕組みです: ブートの早い段階で kernel text がロックされると、EL1(カーネル)からも読み取り専用になり、以後のコードページへの書き込みを防ぎます。
- Thwarts: ブート後にカーネルコードを変更すること(例: パッチ、インプレースなコード注入)を EL1 権限で行う試み。
- Mechanism detail:
- ブート時(secure/bootloader ステージ)にメモリコントローラ(または secure hardware ユニット)がカーネルテキストを含む物理ページを読み取り専用としてマークする。
- エクスプロイトが完全なカーネル権限を得ても、そのページに書き込んで命令をパッチすることはできない。
- それらを変更するには、攻撃者はまずブートチェーンを侵害するか、KTRR 自体を乗っ取る必要がある。
例
権限昇格のエクスプロイトが EL1 にジャンプしてカーネル関数にトランポリンを書き込もうとする(例: `syscall` ハンドラ内)。しかし KTRR によりページが読み取り専用にロックされているため、書き込みは失敗するかフォルトを引き起こし、パッチは適用されない。8. Pointer Authentication Codes (PAC)
Introduced with ARMv8.3 (hardware), Apple beginning with A12 / iOS ~12+
- PAC は ARMv8.3-A で導入されたハードウェア機能で、pointer 値(return addresses、function pointers、特定の data pointers)の改竄を検出するために、ポインタの未使用の上位ビットに小さな暗号署名(“MAC”)を埋め込みます。
- 署名(“PAC”)はポインタ値と modifier(コンテキスト値、例: stack pointer や識別用データ)を使って計算されます。これにより同じポインタ値でも異なるコンテキストでは異なる PAC が付きます。
- 使用時に、そのポインタを逆参照したり分岐に使う前に authenticate 命令が PAC をチェックします。正しければ PAC を剥がして純粋なポインタを取得し、無効ならポインタは “poisoned” になるかフォルトが発生します。
- PAC を生成/検証する鍵は特権レジスタ(EL1, kernel)に存在し、ユーザーモードからは直接読み取れません。
- 多くのシステムでポインタのすべての 64 ビットが使われていない(例: 48-bit address space)ため、上位ビットの余りを PAC に使っても有効アドレスが変わらない。
Architectural Basis & Key Types
-
ARMv8.3 は 五つの 128-bit 鍵(各々は二つの 64-bit システムレジスタで実装)を導入します。
-
APIAKey — instruction pointers 用(ドメイン “I”, key A)
-
APIBKey — 二つ目の instruction pointer key(ドメイン “I”, key B)
-
APDAKey — data pointers 用(ドメイン “D”, key A)
-
APDBKey — data pointers 用(ドメイン “D”, key B)
-
APGAKey — “generic” key、ポインタ以外のデータや汎用用途の署名に使われる
-
これらの鍵は特権システムレジスタに格納され(EL1/EL2 等でのみアクセス可能)、ユーザーモードからはアクセスできない。
-
PAC は暗号関数(ARM は QARMA を提案)で計算され、次を用いる:
- ポインタ値(正規化された部分)
- modifier(stack pointer のようなコンテキスト値)
- 秘密鍵
- 内部の tweak ロジック もし得られた PAC がポインタ上位ビットに格納された値と一致すれば、authentication は成功する。
Instruction Families
命名規則は: PAC / AUT / XPAC、その後にドメイン文字。
PACxx命令はポインタに署名して PAC を挿入するAUTxx命令は PAC を authenticate + strip(検証して剥がす)XPACxx命令は検証せずに剥がす
ドメイン / 接尾辞:
| Mnemonic | Meaning / Domain | Key / Domain | Example Usage in Assembly |
|---|---|---|---|
| PACIA | Sign instruction pointer with APIAKey | “I, A” | PACIA X0, X1 — sign pointer in X0 using APIAKey with modifier X1 |
| PACIB | Sign instruction pointer with APIBKey | “I, B” | PACIB X2, X3 |
| PACDA | Sign data pointer with APDAKey | “D, A” | PACDA X4, X5 |
| PACDB | Sign data pointer with APDBKey | “D, B” | PACDB X6, X7 |
| PACG / PACGA | Generic (non-pointer) signing with APGAKey | “G” | PACGA X8, X9, X10 (sign X9 with modifier X10 into X8) |
| AUTIA | Authenticate APIA-signed instruction pointer & strip PAC | “I, A” | AUTIA X0, X1 — check PAC on X0 using modifier X1, then strip |
| AUTIB | Authenticate APIB domain | “I, B” | AUTIB X2, X3 |
| AUTDA | Authenticate APDA-signed data pointer | “D, A” | AUTDA X4, X5 |
| AUTDB | Authenticate APDB-signed data pointer | “D, B” | AUTDB X6, X7 |
| AUTGA | Authenticate generic / blob (APGA) | “G” | AUTGA X8, X9, X10 (validate generic) |
| XPACI | Strip PAC (instruction pointer, no validation) | “I” | XPACI X0 — remove PAC from X0 (instruction domain) |
| XPACD | Strip PAC (data pointer, no validation) | “D” | XPACD X4 — remove PAC from data pointer in X4 |
これらには専門化された / エイリアス形もある:
PACIASPはPACIA X30, SPの省略形(link register を SP を modifier にして署名する)AUTIASPはAUTIA X30, SP(link register を SP で authenticate)RETAA,RETAB(authenticate-and-return)やBLRAA(authenticate & branch)など、ARM 拡張 / コンパイラサポートで結合形が存在する。- また modifier がゼロのバリアント:
PACIZA/PACIZBのように modifier が暗黙的にゼロになるものもある。
Modifiers
modifier の主目的は PAC を特定のコンテキストに結びつけることです。同じアドレスを異なるコンテキストで署名すると異なる PAC が生成されるため、単純なポインタの再利用を防ぎます。ハッシュに salt を加えるようなものです。
したがって:
- modifier はコンテキスト値(別のレジスタ)で、PAC 計算に混ぜられます。典型的な選択は stack pointer (
SP)、frame pointer、またはオブジェクト ID など。 - Return address の署名に
SPを modifier として使うのが一般的です: PAC は特定のスタックフレームに縛られるため、異なるフレームで LR を再利用すると modifier が変わり、PAC 検証に失敗します。 - 同じポインタ値でも異なる modifier で署名されると異なる PAC になる。
- modifier は必ずしも秘密である必要はなく、理想的には攻撃者が制御できないものが望ましい。
- 意味のある modifier が存在しない場合は、ゼロや定数を使う形式もある。
Apple / iOS / XNU Customizations & Observations
- Apple の PAC 実装は per-boot diversifiers を含み、ブートごとに鍵や tweak が変わるためブートを跨いだ再利用を防ぐ。
- クロスドメイン緩和も含まれており、ユーザーモードで署名された PAC がカーネルモードで簡単に再利用されないようにしている。
- Apple Silicon(M1)ではリバースエンジニアリングで 九つの modifier タイプ と鍵制御用の Apple 固有システムレジスタがあることが示された。
- Apple は return address signing、カーネルデータのポインタ整合性、署名された thread contexts など、多くのカーネルサブシステムで PAC を使用している。
- Google Project Zero は、強力なメモリ read/write primitive があるとカーネル PAC(A keys)を forge できる場合があることを示した(A12 世代での研究)が、Apple は多くの経路を修正した。
- Apple のシステムでは一部の鍵は カーネル全体でグローバル であり、ユーザープロセスにはプロセスごとのランダム性が与えられることがある。
PAC Bypasses
- Kernel-mode PAC: theoretical vs real bypasses
- カーネル PAC 鍵とロジックは(特権レジスタ、diversifiers、ドメイン分離により)厳格に管理されているため、任意の署名済みカーネルポインタを forge するのは非常に困難です。
- Azad の 2020 年の “iOS Kernel PAC, One Year Later” は、iOS 12-13 でいくつかの部分的バイパス(署名ガジェット、署名済み状態の再利用、保護されていない間接分岐)を見つけたが、汎用的な完全バイパスは無かったと報告している。 bazad.github.io
- Apple の “Dark Magic” カスタマイズはさらに exploitable surface を狭めている(ドメイン切替、per-key enabling bits など)。 i.blackhat.com
- Apple silicon(M1/M2)での既知の kernel PAC bypass CVE-2023-32424 が Zecao Cai らにより報告されている。 i.blackhat.com
- しかしこれらのバイパスは多くの場合非常に特定の gadgets や実装バグに依存しており、汎用的な回避策ではない。
したがってカーネル PAC は 非常に堅牢 と見なされるが、完璧ではない。
- User-mode / runtime PAC bypass techniques
これらはより一般的で、PAC が動的リンクやランタイムフレームワークでどのように適用されているかの不完全さを突くものです。以下はクラスごとの説明と例です。
2.1 Shared Cache / A key issues
- dyld shared cache はシステムフレームワークやライブラリをまとめた大きな pre-linked blob です。広く共有されるため、shared cache 内の関数ポインタは “pre-signed” であり多くのプロセスで使われます。攻撃者はこれら既に署名されたポインタを “PAC oracles” として狙います。
- いくつかのバイパス手法は shared cache に存在する A-key 署名済みポインタを抽出または再利用して gadget に使うことを試みる。
- “No Clicks Required” の発表は shared cache 上のオラクルを構築して相対アドレスを推測し、署名済ポインタと組み合わせて PAC を回避する手法を説明している。 saelo.github.io
- また、ユーザー空間の共有ライブラリからの関数ポインタの import が十分に 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が不適切に署名されたポインタ(または diversifier にバグのあるもの)を返す問題を説明しており、意図しない PAC バイパスを可能にしている。 Synacktiv - dyld の
dlsymにおけるロジックには、result->isCodeのときに返されるポインタを__builtin_ptrauth_sign_unauthenticated(..., key_asia, 0)のようにコンテキストゼロで署名する処理が含まれている。 blog.epsilon-sec.com
したがって、dlsym は user-mode の PAC バイパスにおける頻繁なベクタです。
2.3 Other DYLD / runtime relocations
- DYLD ローダと動的再配置ロジックは複雑で、再配置を行うために一時的にページを read/write にマップし、その後 read-only に戻すことがあります。攻撃者はこれらのウィンドウを悪用します。Synacktiv の講演は relocation を介したタイミングベースの PAC バイパス「Operation Triangulation」を説明している。 Synacktiv
- DYLD ページは現在 SPRR / VM_FLAGS_TPRO のような保護で守られているが、以前のバージョンではガードが弱かった。 Synacktiv
- WebKit エクスプロイトチェーンでは、DYLD ローダが PAC バイパスのターゲットになることが多い。スライドでは多くの PAC バイパスが relocation や interposer hooks を通じて DYLD ローダを狙っていると述べられている。 Synacktiv
2.4 NSPredicate / NSExpression / ObjC / SLOP
- userland のエクスプロイトチェーンでは、Objective-C ランタイムのメソッド(
NSPredicate,NSExpression,NSInvocationなど)が制御呼び出しをこっそり移送するために使われる。 - PAC 導入以前の古い iOS では、fake
NSInvocationオブジェクトを使って制御下のメモリ上の任意の selector を呼び出すエクスプロイトが使われていた。PAC が導入されて以降は修正が必要になったが、SLOP(SeLector Oriented Programming)技法は PAC 下でも拡張されて使われている。 Project Zero - 元の SLOP 技法は偽の invocations を作成して ObjC コールをチェインすることを可能にしていた;このバイパスは ISA や selector ポインタが完全に PAC 保護されていない場合に依存することがある。 Project Zero
- ポインタ認証が部分的にしか適用されていない環境では、method/selector/target ポインタが常に PAC 保護されているわけではなく、回避の余地を与えることがある。
Example Flow
例: 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>
A buffer overflow がスタック上の return address を上書きします。攻撃者はターゲットの gadget address を書き込みますが、正しい PAC を計算できません。関数が return すると、CPU の `AUTIA` 命令が PAC の不一致を検出してフォルトします。チェーンは失敗します。
Project Zero の A12 (iPhone XS) に関する解析は、Apple の PAC がどのように使われているか、そして攻撃者が memory read/write primitive を持っている場合に PAC を偽造する方法を示しました。
</details>
### 9. **Branch Target Identification (BTI)**
**Introduced with ARMv8.5 (later hardware)**
BTI は間接ブランチのターゲットをチェックするハードウェア機能です:`blr` や間接 call/jump を実行する際、ターゲットは **BTI landing pad**(`BTI j` または `BTI c`)で始まる必要があります。ランディングパッドのない gadget アドレスへジャンプすると例外が発生します。
LLVM の実装は BTI 命令の三つのバリアントと、それらがどのブランチ種別に対応するかを示しています。
| BTI Variant | What it permits (which branch types) | Typical placement / use case |
|-------------|----------------------------------------|-------------------------------|
| **BTI C** | Targets of *call*-style indirect branches (e.g. `BLR`, or `BR` using X16/X17) | Put at entry of functions that may be called indirectly |
| **BTI J** | Targets of *jump*-style branches (e.g. `BR` used for tail calls) | Placed at the beginning of blocks reachable by jump tables or tail-calls |
| **BTI JC** | Acts as both C and J | Can be targeted by either call or jump branches |
- branch target enforcement でコンパイルされたコードでは、コンパイラは有効な間接ブランチターゲット(関数の先頭やジャンプで到達しうるブロック)ごとに BTI 命令(C, J, または JC)を挿入し、間接ブランチがそれらの場所にのみ成功するようにします。
- **Direct branches / calls**(固定アドレスの `B`, `BL`)は BTI によって制限されません。前提としてコードページは信頼され、攻撃者がそれらを変更できないため(direct branch は安全と見なされる)です。
- また、**RET / return** 命令は一般に BTI によって制限されません。return address は 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 がすべての基本ブロックに挿入されるわけではなく、switch/jump table などからの潜在的なブランチターゲットにのみ挿入されることを示しています。
#### BTI + PAC synergy
PAC はポインタ値(ソース)を保護し、間接呼び出しや return のチェーンが改ざんされていないことを保証します。
BTI は有効なポインタであっても、適切にマークされたエントリポイントのみをターゲットにできるようにします。
両者を組み合わせることで、攻撃者は正しい PAC を持つ有効なポインタに加え、ターゲットに BTI が設置されている必要があり、exploit gadget を構築する難易度が上がります。
#### Example
<details>
<summary>Example</summary>
あるエクスプロイトが `0xABCDEF` の gadget にピボットしようとしますが、その先頭が `BTI c` で始まっていません。CPU は `blr x0` を実行した際にターゲットをチェックし、ランディングパッドがないためフォルトします。結果として多くの gadget は 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 は明示的に無効にされない限り有効です。
- 考え方は:カーネルが騙されたり乗っ取られたとしても、カーネルはまず PAN をクリアしない限りユーザ空間ポインタを任意にデリファレンスできない、つまり `ret2usr` スタイルのエクスプロイトやユーザ制御バッファの悪用リスクを減らせる、というものです。
- PAN が有効なとき(PSTATE.PAN = 1)、“accessible at EL0” とされる仮想アドレスに対する特権ロード/ストア命令は permission fault を引き起こします。
- カーネルが正当にユーザ空間メモリへアクセスしなければならない場合(例:ユーザバッファへのデータコピー)、カーネルは一時的に PAN を無効にするか、あるいは「非特権ロード/ストア」命令を使用してそのアクセスを許可する必要があります。
- ARM64 上の Linux では、PAN サポートは 2015 年頃に導入されました:カーネルのパッチは機能の検出を追加し、`get_user` / `put_user` 等を PAN をクリアするバリアントに置き換えました。
**重要なニュアンス / 制限 / バグ**
- Siguza 等が指摘したように、ARM の設計上の仕様バグ(または曖昧さ)により、**execute-only user mappings**(`--x`)は **PAN をトリガーしない** 可能性があります。言い換えれば、ユーザページが実行可能だが読み取り権限を持たない場合、カーネルの読み取り試行が PAN をバイパスすることがあります。これはアーキテクチャが “accessible at EL0” を読み取り可能であることとみなしており、単に実行可能であるだけではないと扱うためです。これにより特定の構成で PAN バイパスが発生します。
- そのため、iOS / XNU が execute-only user pages を許可する(JIT や code-cache セットアップのように)場合、PAN 有効時でもカーネルがそれらから読み取ってしまう可能性があります。これは一部の ARMv8+ システムで知られた微妙なエクスプロイト領域です。
#### PXN (Privileged eXecute Never)
- **PXN** はページテーブルのフラグ(ページテーブルエントリ、リーフまたはブロックエントリ)で、そのページが **privileged mode(EL1)で実行不可** であることを示します。
- PXN はカーネルや特権コードがユーザ空間ページから命令を実行することを防ぎます。結果として、特権レベルでの制御フローがユーザメモリに向かうことを阻止します。
- PAN と組み合わせると次のことが保証されます:
1. カーネルはデフォルトでユーザ空間データを読み書きできない(PAN)
2. カーネルはユーザ空間のコードを実行できない(PXN)
- ARMv8 のページテーブル形式では、リーフエントリに `PXN` ビット(および非特権実行禁止のための `UXN`)が属性ビットとしてあります。
したがって、カーネルがユーザメモリを指す破損した関数ポインタを持っていてそこへ分岐しようとした場合でも、PXN ビットによりフォルトが発生します。
#### Memory-permission model & how PAN and PXN map to page table bits
PAN / PXN がどのように動作するかを理解するには、ARM の翻訳と permission モデル(簡略化)を把握する必要があります:
- 各ページまたはブロックエントリは、アクセス権(読み書き、特権 vs 非特権)を示す **AP[2:1]** や、実行禁止制限を示す **UXN / PXN** ビットなどの属性フィールドを持ちます。
- PSTATE.PAN が 1(有効)であるとき、ハードウェアは修正されたセマンティクスを強制します:EL0 によってアクセス可能とマークされたページへの特権アクセスは拒否(フォルト)されます。
- 前述のバグのため、読み取り権限を持たない実行専用ページは実装によっては “accessible by 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 = 0(カーネルコードは実行可能)に設定します。
- カーネルは実行フローがユーザメモリ領域へ入らないように注意する必要があります(PXN をバイパスするような実行は禁じられるべきです)。したがって「ユーザ制御のシェルコードへジャンプする」ようなエクスプロイトチェーンは阻止されます。
前述の execute-only ページによる PAN バイパスがあるため、実際のシステムでは Apple は execute-only user pages を無効にするか、仕様上の弱点を回避するパッチを当てる可能性があります。
#### Attack surfaces, bypasses, and mitigations
- **PAN bypass via execute-only pages**: 述べたように仕様にギャップがあり、実行専用(読み取り権限なし)のユーザページは実装によって PAN の対象にならないことがあります。これにより攻撃者は実行専用セクション経由でデータを供給する異常な経路を得ることがあります。
- **Temporal window exploit**: カーネルが PAN を必要以上に長く無効化すると、その間にレースや悪意ある経路がユーザメモリへの意図しないアクセスを行う可能性があります。
- **Forgotten re-enable**: PAN を再有効化し忘れるコードパスがあると、以降のカーネル操作が誤ってユーザメモリへアクセスすることになります。
- **Misconfiguration of PXN**: ページテーブルがユーザページに PXN を設定していなかったり、ユーザコードページを誤ってマップすると、カーネルがユーザ空間コードを実行してしまう可能性があります。
- **Speculation / side-channels**: speculative バイパスに類似して、マイクロアーキテクチャの副作用が PAN / PXN チェックの一時的違反を引き起こす可能性があります(ただしこうした攻撃は CPU 設計に強く依存します)。
- **Complex interactions**: JIT、shared memory、just-in-time code regions のような高度な機能では、カーネルはユーザマップ領域での特定のメモリアクセスや実行を許可するために精緻な制御が必要であり、PAN/PXN 制約下で安全に設計するのは容易ではありません。
#### Example
<details>
<summary>Code Example</summary>
Here are illustrative pseudo-assembly sequences showing enabling/disabling PAN around user memory access, and how a fault might occur.
// Suppose kernel entry point, PAN is enabled (privileged code cannot access user memory by default)
; Kernel receives a syscall with user pointer in X0 ; wants to read an integer from user space mov X1, X0 ; X1 = user pointer
; disable PAN to allow privileged access to user memory MSR PSTATE.PAN, #0 ; clear PAN bit, disabling the restriction
ldr W2, [X1] ; now allowed load from user address
; re-enable PAN before doing other kernel logic MSR PSTATE.PAN, #1 ; set PAN
; … further kernel work …
; Later, suppose an exploit corrupts a pointer to a user-space code page and jumps there BR X3 ; branch to X3 (which points into user memory)
; Because the target page is marked PXN = 1 for privileged execution, ; the CPU throws an exception (fault) and rejects execution
もしカーネルがそのユーザページにPXNを**設定していなかった**場合、ブランチが成功してしまう可能性があり、それは安全ではありません。
カーネルがユーザメモリアクセス後にPANを再有効化するのを忘れると、以降のカーネルロジックが誤って任意のユーザメモリを読み書きしてしまう可能性がある時間的ウィンドウが開きます。
ユーザポインタが実行専用ページ(実行のみ許可、読み/書き不可)を指している場合、PAN仕様のバグの下では、`ldr W2, [X1]`がPAN有効時でも**フォルトしない**ことがあり、実装に依存してバイパスが可能になることがあります。
</details>
<details>
<summary>例</summary>
カーネルの脆弱性がユーザ提供の関数ポインタを取り、カーネルコンテキストでそれを呼び出そうとする(例: `call user_buffer`)。PAN/PXN下では、その操作は許可されないかフォルトします。
</details>
---
### 11. **Top Byte Ignore (TBI) / Pointer Tagging**
**Introduced in ARMv8.5 / newer (or optional extension)**
TBIは64ビットポインタの最上位バイトをアドレス変換時に無視することを意味します。これにより、OSやハードウェアはポインタの最上位バイトに**tag bits**を埋め込んでも実際のアドレスに影響を与えません。
- TBIは**Top Byte Ignore**(Address Taggingとも呼ばれる)を表します。これはハードウェア機能で(多くのARMv8+実装で利用可能)、アドレス変換 / load/store / instruction fetch を行う際に64ビットポインタの上位8ビット(bits 63:56)を**無視します**。
- 実際には、CPUはポインタ `0xTTxxxx_xxxx_xxxx`(`TT` = top byte)をアドレス変換の目的では `0x00xxxx_xxxx_xxxx` と扱い、上位バイトをマスクします。上位バイトはソフトウェアが**メタデータ / tag bits**を格納するために使えます。
- これにより、ソフトウェアは各ポインタにバイト単位のタグをインラインで埋め込みつつ、参照先のメモリ位置を変えずに済みます。
- アーキテクチャは、実際のメモリアクセスを行う前にロード・ストア・命令フェッチでポインタの上位バイトをマスク(タグを除去)して扱うことを保証します。
したがって、TBIは**論理的ポインタ**(ポインタ+タグ)とメモリ操作で使われる**物理アドレス**を切り離します。
#### Why TBI: Use cases and motivation
- **Pointer tagging / metadata**: 上位バイトに追加のメタデータ(例:オブジェクト型、バージョン、境界、整合性タグ)を保存できます。後でポインタを使うとき、ハードウェアレベルでタグが無視されるため、メモリアクセスのために手動でタグを取り除く必要がありません。
- **Memory tagging / MTE (Memory Tagging Extension)**: TBIはMTEが構築するための基本的なハードウェア機構です。ARMv8.5では、Memory Tagging Extensionはポインタのbits 59:56を**論理タグ**として使い、メモリに保存された**allocation tag**と照合します。
- **Enhanced security & integrity**: TBIをpointer authentication (PAC) やランタイムチェックと組み合わせることで、ポインタ値だけでなくタグの正当性も強制できます。攻撃者がタグを正しく上書きしないと、タグの不一致が生じます。
- **Compatibility**: TBIはオプションであり、タグビットはハードウェアで無視されるため、既存のアンタグ付きコードは通常通り動作します。タグビットはレガシーコードにとって「関係ない」ビットになります。
#### 例
<details>
<summary>例</summary>
関数ポインタが上位バイトにタグ(例えば `0xAA`)を含んでいた。エクスプロイトがポインタの下位ビットを上書きしたがタグを忘れたため、カーネルが検証やサニタイズを行うとポインタは不一致として拒否される。
</details>
---
### 12. **Page Protection Layer (PPL)**
**Introduced in late iOS / modern hardware (iOS ~17 / Apple silicon / high-end models)** (一部報告ではmacOS / Apple silicon辺りでPPLが見られますが、Appleは同様の保護をiOSにも導入しています)
- PPLは**カーネル内部の保護境界**として設計されています。つまり、たとえカーネル(EL1)が読み書き能力を持って侵害されても、特定の**機微なページ**(ページテーブル、コード署名メタデータ、カーネルコードページ、entitlements、trust cachesなど)を自由に修正できないようにします。
- これは実質的に「カーネルの中の小さな信頼されたコンポーネント(PPL)」を作り、そのPPLだけが保護ページを変更できるようにするものです。他のカーネルコードは変更を行うためにPPLルーチンを呼び出さなければなりません。
- これによりカーネルエクスプロイトの攻撃面が減少します。カーネルモードで任意のR/W/executeが得られても、攻撃コードはPPLドメインに入るかPPLを回避しない限り重要な構造を変更できません。
- 新しいApple silicon(A15+ / M2+)では、Appleは多くのケースでページテーブル保護のためにPPLに代わる**SPTM (Secure Page Table Monitor)** に移行しています。
以下は公開分析に基づく、PPLがどのように動作すると考えられているかです:
#### Use of APRR / permission routing (APRR = Access Permission ReRouting)
- Appleハードウェアは**APRR (Access Permission ReRouting)** と呼ばれる機構を使用します。これによりページテーブルエントリ(PTE)はフルの許可ビットを持つ代わりに小さなインデックスを含めることができ、これらのインデックスはAPRRレジスタ経由で実際の権限にマッピングされます。これによりドメインごとに権限を動的に再マッピングできます。
- PPLはAPRRを利用してカーネルコンテキスト内の特権を分離します:PPLドメインだけがインデックスと実効権限のマッピングを更新することを許可されます。非PPLのカーネルコードがPTEを書いたり許可ビットを切り替えようとすると、APRRロジックがそれを拒否(または読み取り専用マッピングを強制)します。
- PPLコード自体は制限された領域(例: `__PPLTEXT`)で実行され、通常はエントリゲートが一時的に許可するまで実行不可または書込み不可です。カーネルは保護操作を行うためにPPLエントリポイント(「PPLルーチン」)を呼び出します。
#### Gate / Entry & Exit
- カーネルが保護ページ(例:カーネルコードページの権限変更やページテーブルの修正)を変更する必要があるとき、検証を行いPPLドメインへ遷移する**PPLラッパー**ルーチンを呼び出します。そのドメインの外では、保護ページは実質的に読み取り専用または変更不可となります。
- PPLエントリ中は、APRRマッピングが調整されてPPL領域内のメモリページがPPL内では**実行可能かつ書込み可能**に設定されます。退出時にはそれらは読み取り専用/非書込み可能に戻されます。これにより、監査済みのPPLルーチンだけが保護ページを書き換えられることが保証されます。
- PPL外では、カーネルコードがそれらの保護ページを書き込もうとすると、APRRマッピングがそのコードドメインに対して書込みを許可しないためフォルトします。
#### Protected page categories
PPLが通常保護するページには以下が含まれます:
- ページテーブル構造(翻訳テーブルエントリ、マッピングメタデータ)
- 重要なロジックを含むカーネルコードページ
- コード署名メタデータ(trust caches、署名ブロブ)
- Entitlementテーブル、署名強制テーブル
- 署名チェックや資格情報操作のバイパスを可能にするようなその他の高価値カーネル構造
考え方としては、たとえカーネルメモリが完全に制御されていても、攻撃者はこれらのページを単純にパッチや書き換えすることはできず、PPLルーチンを侵害するかPPLを回避しない限り不可能、ということです。
#### Known Bypasses & Vulnerabilities
1. **Project Zero’s PPL bypass (stale TLB trick)**
- Project Zeroによる公開された解説は、**stale TLB entries** を利用したバイパスを説明しています。
- アイデアは次のとおりです:
1. 物理ページAとBを割り当て、それらをPPLページ(保護対象)としてマークする。
2. 2つの仮想アドレスPとQをマップし、それらのL3翻訳テーブルページがAとBから来るようにする。
3. スレッドを回してQに継続的にアクセスさせ、そのTLBエントリを生かし続ける。
4. `pmap_remove_options()` を呼んでPから始まるマッピングを削除する。バグのため、コードは誤ってPとQ両方のTTEを削除するが、TLB無効化はPだけに対して行われ、Qの古い(stale)エントリが残る。
5. B(page Q のテーブル)を再利用して任意のメモリ(例えばPPL保護ページ)をマップする。古いTLBエントリがまだQの旧マッピングをマップしているため、そのコンテキストではそのマッピングが有効なまま残る。
6. これにより、攻撃者はPPLインターフェースを経由せずにPPL保護ページの書込み可能なマッピングを置くことができる。
- このエクスプロイトには物理マッピングとTLB挙動の精密な制御が必要でした。これは、TLB/マッピングの一貫性に頼るセキュリティ境界は、TLB無効化とマッピング整合性に関して非常に注意深くある必要があることを示しています。
- Project Zeroは、この種のバイパスは微妙で稀であるが複雑なシステムでは可能性があるとコメントしています。それでも彼らはPPLを有効な緩和策と見なしています。
2. **Other potential hazards & constraints**
- カーネルエクスプロイトが直接PPLルーチンに入れる場合(PPLラッパーを呼び出せる場合)、制限を回避できる可能性があります。したがって引数の検証が重要です。
- PPLコード自体のバグ(例:算術オーバーフロー、境界チェックの欠如)はPPL内部での範囲外修正を許す可能性があります。Project Zeroは `pmap_remove_options_internal()` のそのようなバグが彼らのバイパスで利用されたと観察しました。
- PPL境界はハードウェアの強制(APRR、メモリコントローラ)に不可逆的に結びついているため、ハードウェア実装の堅牢性に依存します。
#### 例
<details>
<summary>コード例</summary>
カーネルが保護ページを変更するためにPPLへ呼び出す簡略化した擬似コード/ロジックの例:
```c
// In kernel (outside PPL domain)
function kernel_modify_pptable(pt_addr, new_entry) {
// validate arguments, etc.
return ppl_call_modify(pt_addr, new_entry) // call PPL wrapper
}
// In PPL (trusted domain)
function ppl_call_modify(pt_addr, new_entry) {
// temporarily enable write access to protected pages (via APRR adjustments)
aprr_set_index_for_write(PPL_INDEX)
// perform the modification
*pt_addr = new_entry
// restore permissions (make pages read-only again)
aprr_restore_default()
return success
}
// If kernel code outside PPL does:
*pt_addr = new_entry // a direct write
// It will fault because APRR mapping for non-PPL domain disallows write to that page
kernel は多くの通常の操作を行えますが、保護されたマッピングを変更したりコードをパッチするのは ppl_call_* ルーチンを通じてのみ可能です。
例
kernel exploit が entitlement table を上書きしようとしたり、kernel signature blob を改変して code-sign enforcement を無効にしようとすることがあります。該当ページが PPL-protected のため、PPL インターフェースを経由しない書き込みはブロックされます。したがって、kernel code execution を得ても code-sign の制約を回避したり credential データを任意に改変することはできません。 On iOS 17+ の一部デバイスでは、SPTM により PPL-managed ページをさらに分離しています。PPL → SPTM / 置換 / 将来
- 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.”
- SPTM アーキテクチャは、ポリシーの適用を kernel control の外にあるより高権限のモニタへ移す傾向があり、信頼境界をさらに縮小する可能性があります。
MTE | EMTE | MIE
以下は Apple の MIE 設定下で EMTE がどのように動作するかの上位レベルの説明です:
- タグ割り当て
- メモリが割り当てられるとき(例: secure allocators を介した kernel や user space 内で)、そのブロックに 秘密タグ が割り当てられます。
- ユーザや kernel に返されるポインタは、そのタグを上位ビットに含みます(TBI / top byte ignore 機構を使用)。
- アクセス時のタグチェック
- ポインタを使って load または store が実行されるたびに、ハードウェアはポインタのタグがメモリブロックのタグ(割当タグ)と一致するかをチェックします。不一致なら即座にフォルトします(同期的なため)。
- 同期的であるため、「遅延検出」の窓はありません。
- 解放 / 再利用時のリタグ付け
- メモリが free されると、allocator はブロックのタグを変更します(古いタグを持つ古いポインタはもはや一致しなくなります)。
- したがって use-after-free ポインタは古いタグを持ち、アクセス時に不一致となります。
- 隣接タグ差分によるオーバーフロー検出
- 隣接する割当には異なるタグが付与されます。バッファオーバーフローが隣のメモリへ溢れると、タグ不一致でフォルトが発生します。
- これは境界を越える小さなオーバーフローを検出するのに特に強力です。
- タグの機密性保護
- Apple はタグ値が leak するのを防止しなければなりません(攻撃者がタグを学習すると正しいタグを持つポインタを作成できるため)。
- タグビットの side-channel leakage を避けるために、microarchitectural / speculative controls などの保護が含まれます。
- Kernel と user-space の統合
- Apple は EMTE を user-space だけでなく kernel / OS にとって重要なコンポーネントにも使用しており(kernel をメモリ破損から保護するため)、
- ハードウェア/OS は、kernel が user space のために実行している場合でもタグルールが適用されるよう保証します。
例
``` Allocate A = 0x1000, assign tag T1 Allocate B = 0x2000, assign tag T2// pointer P points into A with tag T1 P = (T1 << 56) | 0x1000
// Valid store *(P + offset) = value // tag T1 matches allocation → allowed
// Overflow attempt: P’ = P + size_of_A (into B region) *(P’ + delta) = value → pointer includes tag T1 but memory block has tag T2 → mismatch → fault
// Free A, allocator retags it to T3 free(A)
// Use-after-free: *(P) = value → pointer still has old tag T1, memory region is now T3 → mismatch → fault
</details>
#### Limitations & challenges
- **Intrablock overflows**: オーバーフローが同じallocation内に留まり(境界を越えない)、tagが同一のままの場合、tag mismatchでは検出できないことがある。
- **Tag width limitation**: tagに使えるビット数はわずか(例: 4 bits や小さなドメイン)で、名前空間が制限される。
- **Side-channel leaks**: tag bitsが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のまま残る(legacy)可能性があり、相互運用がより厄介になる。
- **Speculative / transient attacks**: 多くのマイクロアーキテクチャ保護と同様、speculative executionやmicro-op fusionはチェックを一時的にバイパスしたりtag bitsをleakしたりする可能性がある。
- **Limited to supported regions**: AppleはEMTEをカーネルやセキュリティ重要サブシステムなど選択的な高リスク領域でのみ適用し、全域には適用しない可能性がある。
---
## Key enhancements / differences compared to standard MTE
Here are the improvements and changes Apple emphasizes:
| Feature | Original MTE | EMTE (Apple’s enhanced) / MIE |
|---|---|---|
| **Check mode** | Supports synchronous and asynchronous modes. In async, tag mismatches are reported later (delayed)| Apple insists on **synchronous mode** by default—tag mismatches are caught immediately, no delay/race windows allowed.|
| **Coverage of non-tagged memory** | Accesses to non-tagged memory (e.g. globals) may bypass checks in some implementations | EMTE requires that accesses from a tagged region to non-tagged memory also validate tag knowledge, making it harder to bypass by mixing allocations.|
| **Tag confidentiality / secrecy** | Tags might be observable or leaked via side channels | Apple adds **Tag Confidentiality Enforcement**, which attempts to prevent leakage of tag values (via speculative side-channels etc.).|
| **Allocator integration & retagging** | MTE leaves much of allocator logic to software | Apple’s secure typed allocators (kalloc_type, xzone malloc, etc.) integrate with EMTE: when memory is allocated or freed, tags are managed at fine granularity.|
| **Always-on by default** | In many platforms, MTE is optional or off by default | Apple enables EMTE / MIE by default on supported hardware (e.g. iPhone 17 / A19) for kernel and many user processes.|
Appleがハードウェアとソフトウェアスタックの両方をコントロールしているため、EMTEを厳密に適用し、パフォーマンス問題を避け、side-channelの穴を塞ぐことができる。
---
## How EMTE works in practice (Apple / MIE)
Here’s a higher-level description of how EMTE operates under Apple’s MIE setup:
1. **Tag assignment**
- When memory is allocated (e.g. in kernel or user space via secure allocators), a **secret tag** is assigned to that block.
- The pointer returned to the user or kernel includes that tag in its high bits (using TBI / top byte ignore mechanisms).
2. **Tag checking on access**
- Whenever a load or store is executed using a pointer, the hardware checks that the pointer’s tag matches the memory block’s tag (allocation tag). If mismatch, it faults immediately (since synchronous).
- Because it's synchronous, there is no “delayed detection” window.
3. **Retagging on free / reuse**
- When memory is freed, the allocator changes the block’s tag (so older pointers with old tags no longer match).
- A use-after-free pointer would therefore have a stale tag and mismatch when accessed.
4. **Neighbor-tag differentiation to catch overflows**
- Adjacent allocations are given distinct tags. If a buffer overflow spills into neighbor’s memory, tag mismatch causes a fault.
- This is especially powerful in catching small overflows that cross boundary.
5. **Tag confidentiality enforcement**
- Apple must prevent tag values being leaked (because if attacker learns the tag, they could craft pointers with correct tags).
- They include protections (microarchitectural / speculative controls) to avoid side-channel leakage of tag bits.
6. **Kernel and user-space integration**
- Apple uses EMTE not just in user-space but also in kernel / OS-critical components (to guard kernel against memory corruption).
- The hardware/OS ensures tag rules apply even when kernel is executing on behalf of user space.
Because EMTE is built into MIE, Apple uses EMTE in synchronous mode across key attack surfaces, not as opt-in or debugging mode.
---
## Exception handling in XNU
When an **exception** occurs (e.g., `EXC_BAD_ACCESS`, `EXC_BAD_INSTRUCTION`, `EXC_CRASH`, `EXC_ARM_PAC`, etc.), the **Mach layer** of the XNU kernel is responsible for intercepting it before it becomes a UNIX-style **signal** (like `SIGSEGV`, `SIGBUS`, `SIGILL`, ...).
This process involves multiple layers of exception propagation and handling before reaching user space or being converted to a BSD signal.
### Exception Flow (High-Level)
1. **CPU triggers a synchronous exception** (e.g., invalid pointer dereference, PAC failure, illegal instruction, etc.).
2. **Low-level trap handler** runs (`trap.c`, `exception.c` in XNU source).
3. The trap handler calls **`exception_triage()`**, the core of the Mach exception handling.
4. `exception_triage()` decides how to route the exception:
- First to the **thread's exception port**.
- Then to the **task's exception port**.
- Then to the **host's exception port** (often `launchd` or `ReportCrash`).
If none of these ports handle the exception, the kernel may:
- **Convert it into a BSD signal** (for user-space processes).
- **Panic** (for kernel-space exceptions).
### Core Function: `exception_triage()`
The function `exception_triage()` routes Mach exceptions up the chain of possible handlers until one handles it or until it's finally fatal. It's defined in `osfmk/kern/exception.c`.
```c
void exception_triage(exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt);
典型的なコールフロー:
exception_triage() └── exception_deliver() ├── exception_deliver_thread() ├── exception_deliver_task() └── exception_deliver_host()
すべて失敗した場合 → bsd_exception() が処理し → SIGSEGV のようなシグナルに変換されます。
例外ポート
各 Mach オブジェクト (thread, task, host) は、例外メッセージが送られる exception ports を登録できます。
それらは API によって定義されています:
task_set_exception_ports()
thread_set_exception_ports()
host_set_exception_ports()
各例外ポートには次がある:
- マスク(どの例外を受け取りたいか)
- ポート名(メッセージを受け取る Mach port)
- 動作(カーネルがメッセージをどう送るか)
- フレーバ(どのスレッド状態を含めるか)
Debuggers and Exception Handling
デバッガ(例: LLDB)は、通常 task_set_exception_ports() を使ってターゲットの task または thread に 例外ポート を設定する。
例外が発生したとき:
- Mach メッセージがデバッガプロセスに送られる。
- デバッガはその例外を 処理する(resume、レジスタ変更、命令をスキップ)か 処理しない を選べる。
- デバッガが処理しない場合、例外は次のレベルに伝播する(task → host)。
Flow of EXC_BAD_ACCESS
-
スレッドが無効なポインタを参照 → CPU が Data Abort を発生させる。
-
カーネルトラップハンドラが
exception_triage(EXC_BAD_ACCESS, ...)を呼ぶ。 -
メッセージは次へ送られる:
-
Thread port → (デバッガがブレークポイントを傍受できる)。
-
デバッガが無視すると → Task port → (プロセスレベルのハンドラ)。
-
無視されると → Host port(通常は ReportCrash)。
- 誰も処理しなければ →
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)は、意図的なフォルトを発生させることで実装される。
デバッガは例外ポート経由でこれをキャッチする:
- 命令ポインタやメモリを修正する。
- 元の命令を復元する。
- 実行を再開する。
同じ仕組みで PAC 例外を「キャッチ」できる — ただし TFRO_PAC_EXC_FATAL が設定されている場合は例外がデバッガに到達しない。
Conversion to BSD Signals
誰も例外を受け取らなければ:
-
カーネルは
task_exception_notify() → bsd_exception()を呼ぶ。 -
これにより Mach 例外がシグナルにマッピングされる:
| Mach Exception | Signal |
|---|---|
| EXC_BAD_ACCESS | SIGSEGV or SIGBUS |
| EXC_BAD_INSTRUCTION | SIGILL |
| EXC_ARITHMETIC | SIGFPE |
| EXC_SOFTWARE | SIGTRAP |
| EXC_BREAKPOINT | SIGTRAP |
| EXC_CRASH | SIGKILL |
| EXC_ARM_PAC | SIGILL (on non-fatal) |
### Key Files in XNU Source
osfmk/kern/exception.c→exception_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)
カーネルは固定サイズの「ゾーン」に分かれた zone allocator(kalloc)を使っていた。各ゾーンは単一のサイズクラスの割り当てのみを格納する。
スクリーンショットから:
| Zone Name | Element Size | Example Use |
|---|---|---|
default.kalloc.16 | 16 バイト | 非常に小さなカーネル構造体、ポインタ。 |
default.kalloc.32 | 32 バイト | 小さな構造体、オブジェクトヘッダ。 |
default.kalloc.64 | 64 バイト | IPC メッセージ、微小なカーネルバッファ。 |
default.kalloc.128 | 128 バイト | OSObject の一部のような中程度のオブジェクト。 |
| … | … | … |
default.kalloc.1280 | 1280 バイト | 大きな構造体、IOSurface/graphics メタデータ。 |
動作原理:
- 各割り当て要求は最も近いゾーンサイズに 切り上げられる。(例: 50 バイトの要求は
kalloc.64ゾーンに入る) - 各ゾーン内のメモリは freelist に保持されていた — カーネルが解放したチャンクはそのゾーンに戻る。
- もし 64 バイトバッファをオーバーフローすると、そのゾーン内の 次のオブジェクトを上書き することになる。
これが heap spraying / feng shui が非常に効果的だった理由で、同じサイズクラスの割り当てをスプレーすることでオブジェクトの隣接関係を予測できた。
The freelist
各 kalloc ゾーン内では、解放されたオブジェクトは直接システムに返されず、freelist(利用可能チャンクのリンクリスト)に入れられていた。
- チャンクが解放されると、カーネルはそのチャンクの先頭にポインタを書き込む → 同じゾーン内の次の free チャンクのアドレス。
- ゾーンは最初の free チャンクへの HEAD ポインタを保持する。
- 割り当ては常に現在の HEAD を使う:
- HEAD をポップ(そのメモリを呼び出し元に返す)。
- 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 と等しいため、攻撃者はそれを改ざんできる:
- Heap overflow によって隣接する freed chunk に侵入し、“next” pointer を上書きする。
- Use-after-free による freed object への書き込み → “next” pointer を上書きする。
その後、同じサイズの次の割り当て時に:
- allocator は破損した chunk を取り出す。
- 攻撃者が指定した “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
heap grooming の目的は、オブジェクトの配置を「形作る」ことで、攻撃者が overflow や use-after-free を誘発したときにターゲット(被害)オブジェクトが攻撃者制御のオブジェクトのすぐ隣に来るようにすることです。
そうすることで、メモリ破壊が起きたときに攻撃者は被害オブジェクトを確実に制御データで上書きできます。
手順:
- Spray allocations (fill the holes)
- 時間が経つとカーネルヒープは断片化します:古いオブジェクトが free された穴が残ります。
- 攻撃者はまずダミー割当てを大量に作りこれらの隙間を埋め、ヒープを「詰めて」予測可能にします。
- Force new pages
- 穴が埋まると、次の割当ては zone に追加された新しいページから来る必要があります。
- 新しいページだとオブジェクトは散在せずクラスタ化されます。
- これにより攻撃者は隣接関係をより良く制御できます。
- Place attacker objects
- 攻撃者は再度 spray を行い、新しいページ内に大量の攻撃者制御オブジェクトを作成します。
- これらは同じ zone に属するためサイズや配置が予測可能です。
- Free a controlled object (make a gap)
- 攻撃者は意図的に自分のオブジェクトを free します。
- これによりヒープに「穴」ができ、アロケータはそのサイズの次の割当てにそのスロットを再利用します。
- Victim object lands in the hole
- 攻撃者はカーネルに被害オブジェクト(破壊したいオブジェクト)を割り当てさせます。
- その穴が freelist の最初の利用可能スロットなので、victim は攻撃者が free した正確な位置に配置されます。
- Overflow / UAF into victim
- 攻撃者は被害の周りに attacker-controlled オブジェクトを持ちます。
- 自分のオブジェクトから overflow するか freed オブジェクトを再利用することで、確実に victim のメモリフィールドを任意の値で上書きできます。
なぜ機能するか:
- Zone allocator の予測可能性:同じサイズの割当ては常に同じ zone から来る。
- Freelist 挙動:新しい割当ては最も最近 freed されたチャンクを優先して再利用する。
- Heap sprays:攻撃者は予測可能な内容でメモリを埋め、配置を制御する。
- 結果:攻撃者は victim がどこに配置されるか、隣にどんなデータがあるかを制御できる。
Modern Kernel Heap (iOS 15+/A12+ SoCs)
Apple はアロケータをハード化し、heap grooming を非常に困難にしました:
1. From Classic kalloc to kalloc_type
- Before: 各サイズクラス(16, 32, 64, … 1280 など)に対して単一の
kalloc.<size>zone が存在していました。あるサイズの任意のオブジェクトがそこに配置され → 攻撃者オブジェクトが特権カーネルオブジェクトの隣に座ることができました。 - Now:
- カーネルオブジェクトは typed zones(
kalloc_type)から割り当てられます。 - 各オブジェクト型(例:
ipc_port_t、task_t、OSString、OSData)は同サイズでも専用の zone を持ちます。 - オブジェクト型 ↔ zone のマッピングはコンパイル時に kalloc_type system から生成されます。
攻撃者はもはや制御データ(OSData)が同サイズの機密カーネルオブジェクト(task_t)の隣に来ることを保証できません。
2. Slabs and Per-CPU Caches
- ヒープは slabs(その zone 用に固定サイズチャンクに切り出されたページ群)に分割されています。
- 各 zone は競合を減らすための per-CPU cache を持ちます。
- 割当てパス:
- per-CPU cache を試す。
- 空なら global freelist から引く。
- freelist も空なら新しい slab(1 ページ以上)を割り当てる。
- 利点:この分散化により heap sprays は非決定的になり、割当てが異なる CPU のキャッシュから満たされることがあります。
3. Randomization inside zones
- zone 内では freed 要素が単純な FIFO/LIFO 順で返されません。
- 現代の XNU は encoded freelist pointers(Linux の safe-linking と類似、~iOS 14 で導入)を使用します。
- 各 freelist ポインタは per-zone のシークレット cookie と XOR-encoded されています。
- これにより書き込みプリミティブを得ても偽の freelist ポインタを作ることができません。
- いくつかの割当ては slab 内での配置がランダム化されているため、spray が隣接を保証しません。
4. Guarded Allocations
- いくつかの重要なカーネルオブジェクト(例:credentials, task structures)は guarded zones に割り当てられます。
- これらの zone は slab 間に guard pages(マップされていないメモリ)を挿入したり、オブジェクト周囲に redzones を使ったりします。
- guard page への overflow はフォルトを引き起こし → サイレントな破壊ではなく即時 panic になります。
5. Page Protection Layer (PPL) and SPTM
- freed オブジェクトを制御していても、カーネルメモリのすべてを書き換えられるわけではありません:
- PPL (Page Protection Layer) は特定領域(例:code signing data、entitlements)をカーネル自身からも read-only に保ちます。
- A15/M2+ デバイス ではこの役割は SPTM (Secure Page Table Monitor) + TXM (Trusted Execution Monitor) によって置き換え/強化されています。
- これらのハードウェア強制レイヤは単一のヒープ破壊から重要なセキュリティ構造を任意にパッチすることを防ぎます。
- (追加 / 強化):さらにカーネルではポインタを保護するために PAC (Pointer Authentication Codes) が使用され、関数ポインタや vtable を偽造・破壊するのが難しくなっています。
- (追加 / 強化):zones は zone_require / zone enforcement(つまり free されたオブジェクトは正しい typed zone を通じてのみ返されるべきで、クロスゾーンの不正な free は panic または拒否される)を強制する場合があります。(Apple はメモリ安全性に関する記事でこれをほのめかしています)
6. Large Allocations
- すべての割当てが
kalloc_typeを通るわけではありません。 - 非常に大きな要求(約 16 KB 超)は typed zones をバイパスし、ページ割当てによって直接 kernel VM (kmem) から提供されます。
- これらは予測が難しいですが、他のオブジェクトと slab を共有しないため攻撃可能性は低くなります。
7. Allocation Patterns Attackers Target
これらの保護があっても攻撃者が狙うもの:
- Reference count objects:retain/release カウンタをいじれると use-after-free を起こせる。
- Objects with function pointers (vtables):これを破壊すれば依然として制御フローが得られる。
- Shared memory objects (IOSurface, Mach ports):user ↔ kernel を橋渡しするため依然として攻撃対象。
しかし — 以前のように単に OSData を spray して task_t の隣に来るのを期待することはできません。成功させるには 型固有のバグ や info leaks が必要です。
Example: Allocation Flow in Modern Heap
userspace が IOKit を通じて OSData オブジェクトを割り当てると仮定します:
- Type lookup →
OSDataはkalloc_type_osdatazone(サイズ 64 バイト)にマップされます。 - per-CPU cache に空きがあるか確認。
- 見つかれば → それを返す。
- 空なら → global freelist に行く。
- freelist が空なら → 新しい slab を割り当てる(4KB ページ → 64 個の 64 バイトチャンク)。
- チャンクを呼び出し元に返す。
Freelist pointer protection:
- 各 freed チャンクは次の free チャンクのアドレスを格納しますが、シークレットキーでエンコードされています。
- 攻撃者データでそのフィールドを上書きしてもキーを知らなければ機能しません。
Comparison Table
| Feature | Old Heap (Pre-iOS 15) | Modern Heap (iOS 15+ / A12+) |
|---|---|---|
| Allocation granularity | Fixed size buckets (kalloc.16, kalloc.32, etc.) | Size + type-based buckets (kalloc_type) |
| Placement predictability | High (same-size objects side by side) | Low (same-type grouping + randomness) |
| Freelist management | Raw pointers in freed chunks (easy to corrupt) | Encoded pointers (safe-linking style) |
| Adjacent object control | Easy via sprays/frees (feng shui predictable) | Hard — typed zones separate attacker objects |
| Kernel data/code protections | Few hardware protections | PPL / SPTM protect page tables & code pages, and PAC protects pointers |
| Allocation reuse validation | None (freelist pointers raw) | zone_require / zone enforcement |
| Exploit reliability | High with heap sprays | Much lower, requires logic bugs or info leaks |
| Large allocations handling | All small allocations managed equally | Large ones bypass zones → handled via VM |
Modern Userland Heap (iOS, macOS — type-aware / xzone malloc)
最近の Apple OS(特に iOS 17+)では、より安全なユーザランドアロケータ xzone malloc(XZM)を導入しました。これはカーネルの kalloc_type のユーザ空間版に相当し、型認識、メタデータ隔離、メモリタグ保護を適用します。
Goals & Design Principles
- Type segregation / type awareness: 割当てを 型や用途(ポインタ対データ) ごとにグループ化して型混同やクロスタイプ再利用を防ぐ。
- Metadata isolation: ヒープメタデータ(free lists、サイズ/状態ビット等)をオブジェクトペイロードから分離して、OOB 書き込みでメタデータが破壊されにくくする。
- Guard pages / redzones: 割当て周囲に未マップページやパディングを挿入して overflow を検出する。
- Memory tagging (EMTE / MIE): ハードウェアのタグ付けと連携して use-after-free、out-of-bounds、無効アクセスを検出する。
- Scalable performance: 低オーバーヘッドを維持し、過度の断片化を避け、高速な割当てをサポートする。
Architecture & Components
以下は xzone アロケータの主な要素です:
Segment Groups & Zones
- Segment groups は使用カテゴリ(例:
data、pointer_xzones、data_large、pointer_large)でアドレス空間を分割します。 - 各 segment group はそのカテゴリの割当てをホストする segments(VM 範囲)を持ちます。
- 各 segment には metadata slab(別の VM 領域)があり、そのセグメントのメタデータ(free/used ビット、サイズクラスなど)を格納します。この out-of-line (OOL) metadata によりメタデータがオブジェクトペイロードと混在せず、overflow による破壊を軽減します。
- Segments は chunks(スライス)に切り出され、さらに blocks(割当て単位)に分割されます。チャンクは特定のサイズクラスと segment group に紐づいており(つまりチャンク内のすべてのブロックは同じサイズ・カテゴリを共有する)。
- 小~中サイズの割当てには固定サイズチャンクを使用し、大きな割当ては別にマップする場合があります。
Chunks & Blocks
- chunk はあるサイズクラスに専用された領域(通常数ページ)です。
- chunk 内では blocks が割当てスロットとして存在します。解放されたブロックは metadata slab によって追跡されます(ビットマップや out-of-line に格納された free lists など)。
- チャンク間やチャンク内には guard slices / guard pages(未マップスライスなど)が挿入され、OOB 書き込みを検出します。
Type / Type ID
- すべての割当て呼び出し(malloc, calloc など)は type identifier(
malloc_type_id_t)と関連付けられ、どの種類のオブジェクトが割り当てられているかをエンコードします。allocator はこの type ID を受け取り、どの zone / segment で割り当てるかを選択します。 - そのため、同じサイズでも型が異なれば全く別の zone に入る可能性があります。
- iOS 17 初期ではすべての API(例:CFAllocator)が完全に type-aware ではありませんでした;Apple は iOS 18 でこれらの弱点に対処しました。
Allocation & Freeing Workflow
xzone の割当てと解放のハイレベルなフローは次の通りです:
- malloc / calloc / realloc / typed alloc がサイズと type ID で呼ばれる。
- アロケータは type ID を使って正しい segment group / zone を選ぶ。
- その zone/segment 内で要求サイズの free ブロックがあるチャンクを探す。
- ローカルキャッシュ / per-thread pools や metadata の free block lists を参照することがある。
- 利用可能なブロックがなければ、その zone に新しいチャンクを割り当てる。
- metadata slab を更新(free ビットをクリア、会計処理)。
- メモリタグ(EMTE)が使われている場合、返却されるブロックに タグ が割り当てられ、メタデータはそのブロックの“live”状態を反映するよう更新される。
free()が呼ばれると:
- ブロックはメタデータで free とマークされる(OOL slab 経由)。
- ブロックは free list に入れられるか再利用のためプールされる。
- 任意でブロック内容がクリアまたは poison されてデータ漏えいや UAF 利用が減らされる。
- ブロックに紐づくハードウェアタグは無効化または再タグ付けされる。
- チャンク内のすべてのブロックが解放されると、アロケータはメモリ圧力下でそのチャンクを reclaim(アンマップまたは OS に返却)する場合がある。
Security Features & Hardening
現代のユーザランド xzone に組み込まれた防御:
| Feature | Purpose | Notes |
|---|---|---|
| Metadata decoupling | Overflow がメタデータを破壊するのを防ぐ | メタデータは別 VM 領域(metadata slab)に存在 |
| Guard pages / unmapped slices | OOB 書き込みを検出する | 隣接ブロックを静かに破壊するのではなくオーバーフローを検出する助けになる |
| Type-based segregation | クロスタイプ再利用と型混同を防ぐ | 同サイズでも異なる型の割当ては別 zone に行く |
| Memory Tagging (EMTE / MIE) | 無効アクセス、stale reference、OOB、UAF を検出 | xzone はハードウェア EMTE の同期モード(“Memory Integrity Enforcement”)と連動する |
| Delayed reuse / poisoning / zap | UAF 利用機会を減らす | Freed ブロックは poison、zero、quarantine されることがある |
| Chunk reclamation / dynamic unmapping | メモリの無駄と断片化を減らす | 未使用のチャンクはアンマップされることがある |
| Randomization / placement variation | 決定的な隣接を防ぐ | チャンク内のブロックやチャンク選択はランダム化される要素がある |
| Segregation of “data-only” allocations | ポインタを持たない割当てを分離 | メタデータや制御フィールドに対する攻撃者の影響を減らす |
Interaction with Memory Integrity Enforcement (MIE / EMTE)
- Apple の MIE (Memory Integrity Enforcement) はハードウェア+OS のフレームワークで、Enhanced Memory Tagging Extension (EMTE) を主要な攻撃面で常時オンかつ同期モードにします。
- xzone アロケータはユーザ空間における MIE の基盤です:xzone 経由の割当てはタグを受け、アクセスはハードウェアでチェックされます。
- MIE ではアロケータ、タグ割当て、メタデータ管理、タグの機密性保護が統合され、メモリエラー(stale read、OOB、UAF 等)が即座に検出され、後で悪用されるのを防ぎます。
- If you like, I can also generate a cheat-sheet or diagram of xzone internals for your book. Do you want me to do that next?
- :contentReference[oai:20]{index=20}
(Old) Physical Use-After-Free via IOSurface
Ghidra Install BinDiff
Download BinDiff DMG from https://www.zynamics.com/bindiff/manual and install it.
Open Ghidra with ghidraRun and go to File –> Install Extensions, press the add button and select the path /Applications/BinDiff/Extra/Ghidra/BinExport and click OK and isntall it even if there is a version mismatch.
Using BinDiff with Kernel versions
- Go to the page https://ipsw.me/ and download the iOS versions you want to diff. These will be
.ipswfiles. - Decompress until you get the bin format of the kernelcache of both
.ipswfiles. You have information on how to do this on:
macOS Kernel Extensions & Kernelcache
- Open Ghidra with
ghidraRun, create a new project and load the kernelcaches. - Open each kernelcache so they are automatically analyzed by Ghidra.
- Then, on the project Window of Ghidra, right click each kernelcache, select
Export, select formatBinary BinExport (v2) for BinDiffand export them. - Open BinDiff, create a new workspace and add a new diff indicating as primary file the kernelcache that contains the vulnerability and as secondary file the patched kernelcache.
Finding the right XNU version
If you want to check for vulnerabilities in a specific version of iOS, you can check which XNU release version the iOS version uses at [https://www.theiphonewiki.com/wiki/kernel]https://www.theiphonewiki.com/wiki/kernel).
For example, the versions 15.1 RC, 15.1 and 15.1.1 use the version Darwin Kernel Version 21.1.0: Wed Oct 13 19:14:48 PDT 2021; root:xnu-8019.43.1~1/RELEASE_ARM64_T8006.
JSKit-Based Safari Chains and PREYHUNTER Stagers
Renderer RCE abstraction with JSKit
- Reusable entry: Recent in-the-wild chains abused a WebKit JIT bug (patched as CVE-2023-41993) purely to gain JavaScript-level arbitrary read/write. The exploit immediately pivots into a purchased framework called JSKit, so any future Safari bug only needs to deliver the same primitive.
- Version abstraction & PAC bypasses: JSKit bundles support for a wide range of iOS releases together with multiple, selectable Pointer Authentication Code bypass modules. The framework fingerprints the target build, selects the appropriate PAC bypass logic, and verifies every step (primitive validation, shellcode launch) before progressing.
- Manual Mach-O mapping: JSKit parses Mach-O headers directly from memory, resolves the symbols it needs inside dyld-cached images, and can manually map additional Mach-O payloads without writing them to disk. This keeps the renderer process in-memory only and evades code-signature checks tied to filesystem artifacts.
- Portfolio model: Debug strings such as “exploit number 7” show that the suppliers maintain multiple interchangeable WebKit exploits. Once the JS primitive matches JSKit’s interface, the rest of the chain is unchanged across campaigns.
Kernel bridge: IPC UAF -> code-sign bypass pattern
- Kernel IPC UAF (CVE-2023-41992): The second stage, still running inside the Safari context, triggers a kernel use-after-free in IPC code, re-allocates the freed object from userland, and abuses the dangling pointers to pivot into arbitrary kernel read/write. The stage also reuses PAC bypass material previously computed by JSKit instead of re-deriving it.
- Code-signing bypass (CVE-2023-41991): With kernel R/W available, the exploit patches the trust cache / code-signing structures so unsigned payloads execute as
system. The stage then exposes a lightweight kernel R/W service to later payloads. - Composed pattern: This chain demonstrates a reusable recipe that defenders should expect going forward:
WebKit renderer RCE -> kernel IPC UAF -> kernel arbitrary R/W -> code-sign bypass -> unsigned system stager
PREYHUNTER の helper と watcher モジュール
-
Watcher anti-analysis: 専用の watcher バイナリがデバイスを継続的にプロファイルし、リサーチ環境を検出した場合はキルチェーンを中断します。
security.mac.amfi.developer_mode_status、diagnosticdコンソールの存在、ロケールUSまたはIL、Cydia のような jailbreak の痕跡、bash、tcpdump、frida、sshd、checkrainといったプロセス、モバイル AV アプリ(McAfee、AvastMobileSecurity、NortonMobileSecurity)、カスタム HTTP プロキシ設定、カスタム root CA などを検査します。いずれかのチェックに失敗すると以降のペイロード配信がブロックされます。 -
Helper surveillance hooks: helper コンポーネントは
/tmp/helper.sock経由で他のステージと通信し、DMHooker と UMHooker という名前のフックセットをロードします。これらのフックは VOIP オーディオ経路にタップ(録音は/private/var/tmp/l/voip_%lu_%u_PART.m4aに保存)、システム全体のキーロガー実装、UI なしでの写真撮影、そして SpringBoard をフックしてこれらの操作が通常上げる通知を抑制します。したがって helper は、Predator などのより重いインプラントを落とす前のステルスな検証+軽度監視レイヤとして機能します。 -
HiddenDot indicator suppression in SpringBoard: カーネルレベルのコード注入により、Predator は
SBSensorActivityDataProvider._handleNewDomainData:(センサー活動の集約ポイント)をフックします。このフックは Objective-C のselfポインタ(x0)をゼロにし、呼び出しを[nil _handleNewDomainData:newData]にしてカメラ/マイクの更新を無効化し、緑/オレンジのドットを抑制します。 -
Mach exception-based hooking flow (DMHooker): フックは
EXC_BREAKPOINT+ exception port を使って実装され、その後thread_set_stateがレジスタを変更して実行を再開します。戻り値2は「変更されたスレッド状態で続行する」を意味します。 -
PAC-aware redirection for camera access checks:
mediaserverd内で、パターンスキャン(例:memmem)によりCMCapture.framework内のFigVideoCaptureSourceCreateWithSourceInfo付近にあるプライベートルーチンを特定します。フックは戻り値3を返し、事前署名された PAC キャッシュのリターンアドレスを使ってリダイレクトし、PAC を満たしつつチェックを回避します。 -
VoIP capture pipeline in
mediaserverd:AudioConverterNewとAudioConverterConvertComplexBuffer+52をフックしてバッファをタップし、バッファサイズからサンプルレートを推定、float32 PCM → int16 に NEON で変換、4ch をステレオにダウンミックスし、ExtAudioFileWrite()で永続化します。VoIP モジュール自体はインジケータを抑制しないため、オペレータは別途 HiddenDot を有効にする必要があります。
WebKit DFG Store-Barrier UAF + ANGLE PBO OOB (iOS 26.1)
Webkit Dfg Store Barrier Uaf Angle Oob
iMessage/Media Parser Zero-Click Chains
Imessage Media Parser Zero Click Coreaudio Pac Bypass
References
- https://www.jamf.com/blog/predator-spyware-ios-recording-indicator-bypass-analysis/
- Google Threat Intelligence – Intellexa zero-day exploits continue
Tip
AWSハッキングを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。


