Translated ['', 'src/binary-exploitation/ios-exploiting/ios-physical-uaf

This commit is contained in:
Translator 2025-09-29 08:59:55 +00:00
parent ed1f0c3612
commit 024372bb4f

View File

@ -1,99 +1,99 @@
# iOS Physical Use-After-Free via IOSurface # iOS Physical Use After Free via IOSurface
{{#include ../../banners/hacktricks-training.md}} {{#include ../../banners/hacktricks-training.md}}
## Physical use-after-free ## Physical use-after-free
これは [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html) の投稿の要約です。さらに、この手法を使ったエクスプロイトの詳細は [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd) にあります。 これは [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html) の投稿を要約したものです。さらにこの手法を使ったエクスプロイトの詳細は [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd) にあります。
### Memory management in XNU <a href="#memory-management-in-xnu" id="memory-management-in-xnu"></a> ### Memory management in XNU <a href="#memory-management-in-xnu" id="memory-management-in-xnu"></a>
iOS のユーザープロセス向けの仮想メモリ空間は **0x0 0x8000000000** までです。ただし、これらのアドレスは物理メモリに直接対応しているわけではありません。代わりに、カーネルはページテーブルを使って仮想アドレスを実際の物理アドレスに変換します。 iOSのユーザプロセス向けの virtual memory address space は **0x0 から 0x8000000000** までです。ただし、これらのアドレスは physical memory に直接対応しているわけではありません。代わりに、**kernel** が **page tables** を使って virtual addresses を実際の **physical addresses** に変換します。
#### Levels of Page Tables in iOS #### Levels of Page Tables in iOS
ページテーブルは階層構造になっており、3 段階で構成されています: Page tables は階層化されており、3つのレベルで構成されます:
1. **L1 Page Table (Level 1)**: 1. **L1 Page Table (Level 1)**:
* ここにある各エントリは仮想メモリの大きな範囲を表します。 * ここでの各エントリは仮想メモリの大きな範囲を表します。
* 仮想メモリの **0x1000000000 バイト**= **256 GB**をカバーします。 * **0x1000000000 bytes****256 GB**)の仮想メモリをカバーします。
2. **L2 Page Table (Level 2)**: 2. **L2 Page Table (Level 2)**:
* ここでのエントリはより小さな領域、具体的には **0x2000000 バイト**32 MBを表します。 * ここでのエントリはより小さな領域、具体的には **0x2000000 bytes**32 MBを表します。
* L1 のエントリがその全範囲を直接マップできない場合、L2 テーブルへのポインタを指すことがあります。 * L1 エントリがその領域全体を直接マップできない場合、L2 テーブルへのポインタを指すことがあります。
3. **L3 Page Table (Level 3)**: 3. **L3 Page Table (Level 3)**:
* もっとも細かいレベルで、各エントリは単一の **4 KB** ページをマップします。 * これは最も細かいレベルで、各エントリが単一の **4 KB** ページをマップします。
* より詳細な制御が必要な場合、L2 エントリは L3 テーブルを指すことがあります。 * より細かい制御が必要な場合、L2 エントリは L3 テーブルを指すことがあります。
#### Mapping Virtual to Physical Memory #### Mapping Virtual to Physical Memory
* **Direct Mapping (Block Mapping)**: * **Direct Mapping (Block Mapping)**:
* ページテーブルの一部のエントリは、仮想アドレスの範囲を物理アドレスの連続領域に直接マップします(ショートカットのようなものです)。 * page table のいくつかのエントリは、仮想アドレスの範囲を連続する physical addresses に直接 **map** します(ショートカットのようなものです)。
* **Pointer to Child Page Table**: * **Pointer to Child Page Table**:
* より細かい制御が必要な場合、あるレベルのエントリ(例: L1は次のレベルの子ページテーブル(例: L2へのポインタを持ちます * より細かい制御が必要な場合、あるレベルのエントリ(例: L1は次のレベルの **child page table** を指すことができます(例: L2
#### Example: Mapping a Virtual Address #### Example: Mapping a Virtual Address
仮に仮想アドレス **0x1000000000** にアクセスしようとすると: 仮に仮想アドレス **0x1000000000** にアクセスしようとすると:
1. **L1 Table**: 1. **L1 Table**:
* カーネルはこの仮想アドレスに対応する L1 エントリをチェックします。もし L2 ページテーブルへのポインタがあれば、その L2 テーブルに移動します。 * kernel はこの仮想アドレスに対応する L1 page table エントリをチェックします。もし **pointer to an L2 page table** があれば、L2 テーブルへ進みます。
2. **L2 Table**: 2. **L2 Table**:
* カーネルはより詳細なマッピングのために L2 を確認します。もしこのエントリが L3 ページテーブルを指していれば、さらに進みます。 * kernel はより詳細なマッピングのために L2 page table をチェックします。もしこのエントリが **pointer to an L3 page table** を指していれば、さらに L3 へ進みます。
3. **L3 Table**: 3. **L3 Table**:
* 最終的に L3 エントリを参照し、実際のページの **物理アドレス** を得ます。 * kernel は最終的な L3 エントリを参照し、それが実際のメモリページの **physical address** を指します。
#### Example of Address Mapping #### Example of Address Mapping
もし L2 テーブルの最初のインデックスに物理アドレス **0x800004000** を書き込んだ場合: もし L2 テーブルの最初のインデックスに physical address **0x800004000** を書き込んだ場合:
* 仮想アドレス **0x1000000000** **0x1002000000** は、物理アドレス **0x800004000** **0x802004000**マップされます。 * 仮想アドレス **0x1000000000** から **0x1002000000** は物理アドレス **0x800004000** から **0x802004000**マップされます。
* これは L2 レベルでの **ブロックマッピング** です。 * これは L2 レベルでの **block mapping** です。
一方で、L2 エントリが L3 テーブルを指している場合: また、L2 エントリが L3 テーブルを指す場合:
* 仮想アドレス範囲 **0x1000000000 -> 0x1002000000** 内の各 4 KB ページは L3 の個別エントリによってマッピングされます。 * 仮想アドレス範囲 **0x1000000000 -> 0x1002000000** の各 4 KB ページは L3 テーブルの個別エントリでマップされます。
### Physical use-after-free ### Physical use-after-free
physical use-after-freeUAFは次のような状況で発生します: A **physical use-after-free** (UAF) は次のような状況で発生します:
1. プロセスが読み書き可能なメモリを割り当てる。 1. プロセスが読み書き可能としていくつかのメモリを **allocates**る。
2. ページテーブルが更新され、そのメモリがプロセスからアクセス可能な特定の物理アドレスにマップされる。 2. **page tables** が更新され、そのメモリがプロセスからアクセス可能な特定の physical address にマップされる。
3. プロセスがそのメモリを解放freeする。 3. プロセスがそのメモリを **deallocates (free)** する。
4. しかしバグにより、カーネルはページテーブルからそのマッピングを削除するのを忘れてしまい、対応する物理メモリは「free」としてマークされる。 4. しかし、**bug** のために kernel は page tables からその mapping を削除し忘れ、対応する physical メモリは free としてマークされる。
5. カーネルはその「解放された」物理メモリを別用途(例: カーネルデータ)に再割り当てする可能性がある。 5. kernel はその「free」になった physical memory を他の用途(例: **kernel data**)のために **reallocate**る。
6. マッピングが削除されていないため、プロセスは依然としてその物理メモリの読み書きが可能である。 6. mapping が削除されていないため、プロセスはその physical memory を引き続き **read and write** できる。
これにより、プロセスはカーネルメモリのページにアクセスできるようになり、機密データや構造体を含む可能性があるため、攻撃者がカーネルメモリを操作できてしまう恐れがあります。 つまり、プロセスは **kernel memory のページ** にアクセスできるようになり、そこには機密データや構造体が含まれている可能性があるため、攻撃者が **kernel memory を操作** できる恐れがあります。
### IOSurface Heap Spray ### IOSurface Heap Spray
攻撃者は、解放された物理メモリがどのカーネルページに割り当てられるかを制御できないため、heap spray と呼ばれる手法を使います: 攻撃者は freed メモリにどの kernel ページが割り当てられるかを制御できないため、次のような **heap spray**法を使います:
1. 攻撃者はカーネルメモリ上に多数の IOSurface オブジェクトを作成する。 1. 攻撃者は kernel memory 内に大量の IOSurface objects を **create** する。
2. 各 IOSurface オブジェクトは、識別しやすいフィールドに magic value を含む 2. 各 IOSurface object はそのフィールドの1つに **magic value** を含め、特定しやすくします
3. 彼らは解放されたページをスキャンして、これらの IOSurface オブジェクトが解放ページ上に割り当てられていないか確認する 3. 彼らは freed pages を **scan** して、その中にこれらの IOSurface objects が入っているかどうかを確認します
4. 解放ページ上に IOSurface オブジェクトを見つけた場合、それを使ってカーネルメモリの読み書きを行える 4. freed page 上に IOSurface object を見つけた場合、それを使って **kernel memory の read/write** を行えます
詳細は [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups) を参照してください。 詳細は [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups) を参照してください。
> [!TIP] > [!TIP]
> iOS 16+A12+デバイスはハードウェア緩和策PPL や SPTM などを導入しており、physical UAF 技術は実用性が大きく低下しています。 > iOS 16+ (A12+) デバイスはハードウェア緩和策PPL や SPTM などを導入しており、physical UAF 技術の有効性を大幅に低下させています。
> PPL はコード署名、entitlements、機密性の高いカーネルデータに関連するページに対して厳格な MMU 保護を強制するため、ページが再利用されたとしても userland からの書き込みや侵害されたカーネルコードからの書き込みは PPL 保護ページに対してブロックされます。 > PPL は code signing、entitlements、敏感な kernel data に関連するページに対して厳格な MMU 保護を課し、ページが再利用されても userland や compromised kernel code からの書き込みをブロックします。
> Secure Page Table Monitor (SPTM) は PPL を拡張してページテーブルの更新自体を強化します。これにより、権限を持つカーネルコードであっても、secure なチェックを経ずに解放ページを黙って再マップしたりマッピングを改ざんしたりすることはできなくなります。 > Secure Page Table Monitor (SPTM) は PPL を拡張し、page table の更新自体を強化します。これにより、特権を持つ kernel コードであっても secure checks を経ずに freed pages を再マップしたりマッピングを改竄したりすることができなくなります。
> KTRR (Kernel Text Read-Only Region) はブート後にカーネルのコード領域を読み取り専用にロックダウンします。これによりランタイムでのカーネルコード改変を防ぎ、physical UAF エクスプロイトが依存する主要な攻撃ベクターを塞ぎます。 > KTRR (Kernel Text Read-Only Region) はブート後に kernel のコード領域を読み取り専用に固定します。これにより runtime による kernel code の変更が防がれ、physical UAF エクスプロイトが頼りにする主要な攻撃ベクトルが閉じられます。
> さらに、IOSurface の割り当ては予測しにくくなり、ユーザがアクセス可能な領域にマッピングされにくくなっているため、「magic value スキャン」トリックの信頼性は低下しています。また、IOSurface は現在 entitlements や sandbox 制限によって保護されています。 > さらに、`IOSurface` の割り当ては予測しにくくなり、user-accessible な領域へマッピングするのが難しくなっているため、magic value をスキャンするトリックの信頼性が低下しています。`IOSurface` は現在 entitlements や sandbox 制限でも保護されています。
### Step-by-Step Heap Spray Process ### Step-by-Step Heap Spray Process
1. **Spray IOSurface Objects**: 攻撃者は特定の識別子("magic value")を持つ多数の IOSurface オブジェクトを生成します。 1. **Spray IOSurface Objects**: 攻撃者は特別な識別子("magic value")を持つ多くの IOSurface objects を作成します。
2. **Scan Freed Pages**: これらのオブジェクトのうち、解放されたページに割り当てられたものがないか確認します。 2. **Scan Freed Pages**: それらのオブジェクトのいずれかが freed page に割り当てられているかを確認します。
3. **Read/Write Kernel Memory**: IOSurface オブジェクトのフィールドを操作することで、カーネルメモリに対する arbitrary reads and writes を実現します。これにより: 3. **Read/Write Kernel Memory**: IOSurface object のフィールドを操作することで、**arbitrary reads and writes** を kernel memory に対して実行できるようになります。これにより:
* あるフィールドを使ってカーネルメモリ内の任意の 32-bit 値を読み出せる。 * あるフィールドを使って kernel memory の任意の 32-bit 値を **read** できる。
* 別のフィールドを使って 64-bit 値を書き込み、安定した kernel read/write primitive を確立できる。 * 別のフィールドを使って 64-bit 値を **write** でき、安定した **kernel read/write primitive** を実現できる。
Generate IOSurface objects with the magic value IOSURFACE\_MAGIC to later search for: Generate IOSurface objects with the magic value IOSURFACE_MAGIC to later search for:
```c ```c
void spray_iosurface(io_connect_t client, int nSurfaces, io_connect_t **clients, int *nClients) { void spray_iosurface(io_connect_t client, int nSurfaces, io_connect_t **clients, int *nClients) {
if (*nClients >= 0x4000) return; if (*nClients >= 0x4000) return;
@ -114,7 +114,7 @@ io_connect_t id = result.surface_id;
} }
} }
``` ```
解放された物理ページ1つ内で**`IOSurface`**オブジェクトを検索する: 1つの解放された物理ページ内で **`IOSurface`** オブジェクトを検索する:
```c ```c
int iosurface_krw(io_connect_t client, uint64_t *puafPages, int nPages, uint64_t *self_task, uint64_t *puafPage) { int iosurface_krw(io_connect_t client, uint64_t *puafPages, int nPages, uint64_t *self_task, uint64_t *puafPage) {
io_connect_t *surfaceIDs = malloc(sizeof(io_connect_t) * 0x4000); io_connect_t *surfaceIDs = malloc(sizeof(io_connect_t) * 0x4000);
@ -148,25 +148,25 @@ free(surfaceIDs);
return 0; return 0;
} }
``` ```
### IOSurface を使った Kernel Read/Write の取得 ### IOSurface を使ったカーネル読み書きの実現
kernel memory 内の IOSurface オブジェクトuserspace からアクセス可能な解放済み物理ページにマップされている)の制御を得た後、これを使って **arbitrary kernel read and write operations** を実行できます カーネルメモリ上の IOSurface オブジェクトを制御できるようになった後(ユーザースペースからアクセス可能な解放済み物理ページにマップされている)、これを使って**任意のカーネル読み書き操作**が可能になる
**IOSurface の重要なフィールド** **IOSurface の重要なフィールド**
The IOSurface object には重要なフィールドが2つあります: IOSurface オブジェクトには2つの重要なフィールドがある:
1. **Use Count Pointer**: **32-bit read** を可能にします 1. **Use Count Pointer**: **32-bit の読み取り**を可能にする
2. **Indexed Timestamp Pointer**: **64-bit write** を可能にします 2. **Indexed Timestamp Pointer**: **64-bit の書き込み**を可能にする
これらのポインタを上書きすることで、kernel memory の任意のアドレスを指すようにリダイレクトし、read/write 機能を実現できます これらのポインタを書き換えることで、カーネルメモリ内の任意のアドレスへリダイレクトでき、読み書きが可能になる
#### 32-Bit Kernel Read #### 32-Bit Kernel Read
読み取りを行うには: 読み取りを行うには:
1. **use count pointer** を上書きして、target address から 0x14 バイト引いたオフセットを指すようにします 1. **use count pointer** を上書きし、対象アドレスから 0x14 バイトを引いた位置を指すようにする
2. `get_use_count` メソッドを使って、そのアドレスの値を読み取ります 2. `get_use_count` メソッドを使い、そのアドレスの値を読み取る
```c ```c
uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) { uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) {
uint64_t args[1] = {surfaceID}; uint64_t args[1] = {surfaceID};
@ -186,10 +186,10 @@ return value;
``` ```
#### 64ビットカーネル書き込み #### 64ビットカーネル書き込み
書き込みを行うには 書き込みを行うには:
1. ターゲットアドレスに**indexed timestamp pointer**を上書きする。 1. ターゲットアドレスに **インデックス付きタイムスタンプポインタ** を上書きする。
2. `set_indexed_timestamp` メソッドを使用して64ビット値を書き込む。 2. `set_indexed_timestamp` メソッドを使て64ビット値を書き込む。
```c ```c
void set_indexed_timestamp(io_connect_t client, uint32_t surfaceID, uint64_t value) { void set_indexed_timestamp(io_connect_t client, uint32_t surfaceID, uint64_t value) {
uint64_t args[3] = {surfaceID, 0, value}; uint64_t args[3] = {surfaceID, 0, value};
@ -203,13 +203,13 @@ set_indexed_timestamp(info.client, info.surface, value);
iosurface_set_indexed_timestamp_pointer(info.object, orig); iosurface_set_indexed_timestamp_pointer(info.object, orig);
} }
``` ```
#### エクスプロイトフローの要約 #### Exploit Flow Recap
1. **Trigger Physical Use-After-Free**: 解放されたページが再利用可能になる。 1. **Trigger Physical Use-After-Free**: 解放されたページが再利用可能になる。
2. **Spray IOSurface Objects**: カーネルメモリにユニークな "magic value" を持つ多数の IOSurface オブジェクトを割り当てる。 2. **Spray IOSurface Objects**: カーネルメモリに一意の "magic value" を持つ多数の IOSurface オブジェクトを割り当てる。
3. **Identify Accessible IOSurface**: 自分が制御する解放済みページ上の IOSurface を見つける。 3. **Identify Accessible IOSurface**: 制御下の解放ページ上の IOSurface を特定する。
4. **Abuse Use-After-Free**: IOSurface オブジェクト内のポインタを改変し、IOSurface メソッド経由で任意の **kernel read/write** を可能にする。 4. **Abuse Use-After-Free**: IOSurface オブジェクト内のポインタを改変し、IOSurface methods 経由で任意の **kernel read/write** を可能にする。
これらのプリミティブにより、エクスプロイトはカーネルメモリへの制御された **32-bit reads****64-bit writes** を提供する。さらなる jailbreak 手順は、より安定した read/write プリミティブを必要とする場合があり、追加の保護(例: 新しい arm64e デバイス上の PPLを回避する必要があるかもしれない これらのプリミティブにより、エクスプロイトはカーネルメモリに対する制御された **32-bit reads****64-bit writes** を提供する。さらなる jailbreak 手順は、より安定した read/write プリミティブが必要になり、追加の保護(例: 新しい arm64e デバイス上の PPLを回避する必要がある場合がある
{{#include ../../banners/hacktricks-training.md}} {{#include ../../banners/hacktricks-training.md}}