iOS Exploiting
Physical use-after-free
これはhttps://alfiecg.uk/2024/09/24/Kernel-exploit.htmlの投稿からの要約であり、この技術を使用したエクスプロイトに関するさらなる情報はhttps://github.com/felix-pb/kfdで見つけることができます。
Memory management in XNU
iOSのユーザープロセスの仮想メモリアドレス空間は0x0から0x8000000000まで広がっています。しかし、これらのアドレスは物理メモリに直接マッピングされているわけではありません。代わりに、カーネルはページテーブルを使用して仮想アドレスを実際の物理アドレスに変換します。
Levels of Page Tables in iOS
ページテーブルは3つのレベルで階層的に整理されています:
L1 Page Table (Level 1):
ここにある各エントリは、大きな範囲の仮想メモリを表します。
0x1000000000バイト(または256 GB)の仮想メモリをカバーします。
L2 Page Table (Level 2):
ここにあるエントリは、特に0x2000000バイト(32 MB)の小さな仮想メモリ領域を表します。
L1エントリは、全体の領域を自分でマッピングできない場合、L2テーブルを指すことがあります。
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にアクセスしようとすると:
L1 Table:
カーネルはこの仮想アドレスに対応するL1ページテーブルエントリをチェックします。もしL2ページテーブルへのポインタがあれば、そのL2テーブルに進みます。
L2 Table:
カーネルはより詳細なマッピングのためにL2ページテーブルをチェックします。このエントリがL3ページテーブルを指している場合、そこに進みます。
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)は次のときに発生します:
プロセスが読み取り可能かつ書き込み可能なメモリを割り当てます。
ページテーブルがこのメモリをプロセスがアクセスできる特定の物理アドレスにマッピングするように更新されます。
プロセスがメモリを解放(フリー)します。
しかし、バグのために、カーネルはページテーブルからマッピングを削除するのを忘れ、対応する物理メモリをフリーとしてマークします。
カーネルはその後、この「解放された」物理メモリをカーネルデータなどの他の目的のために再割り当てできます。
マッピングが削除されなかったため、プロセスはこの物理メモリに読み書きを続けることができます。
これは、プロセスがカーネルメモリのページにアクセスできることを意味し、そこには機密データや構造が含まれている可能性があり、攻撃者がカーネルメモリを操作できる可能性があります。
Exploitation Strategy: Heap Spray
攻撃者は解放されたメモリにどの特定のカーネルページが割り当てられるかを制御できないため、ヒープスプレーと呼ばれる技術を使用します:
攻撃者はカーネルメモリに多数のIOSurfaceオブジェクトを作成します。
各IOSurfaceオブジェクトは、そのフィールドの1つにマジックバリューを含んでおり、識別が容易です。
攻撃者は解放されたページをスキャンして、これらのIOSurfaceオブジェクトのいずれかが解放されたページに配置されているかを確認します。
解放されたページにIOSurfaceオブジェクトを見つけると、それを使用してカーネルメモリを読み書きできます。
この詳細についてはhttps://github.com/felix-pb/kfd/tree/main/writeupsで確認できます。
Step-by-Step Heap Spray Process
Spray IOSurface Objects: 攻撃者は特別な識別子(「マジックバリュー」)を持つ多くのIOSurfaceオブジェクトを作成します。
Scan Freed Pages: 彼らは、オブジェクトのいずれかが解放されたページに割り当てられているかを確認します。
Read/Write Kernel Memory: IOSurfaceオブジェクトのフィールドを操作することで、彼らはカーネルメモリ内で任意の読み書きを行う能力を得ます。これにより、彼らは:
1つのフィールドを使用してカーネルメモリ内の任意の32ビット値を読み取ることができます。
別のフィールドを使用して64ビット値を書き込むことができ、安定したカーネルの読み書きプリミティブを実現します。
IOSURFACE_MAGICというマジックバリューを持つIOSurfaceオブジェクトを生成して、後で検索します:
IOSurface
オブジェクトを解放された物理ページの中で検索します:
カーネルの読み書きをIOSurfaceで実現する
カーネルメモリ内のIOSurfaceオブジェクトを制御できるようになると(ユーザースペースからアクセス可能な解放された物理ページにマッピングされている)、任意のカーネルの読み書き操作に利用できます。
IOSurfaceの重要なフィールド
IOSurfaceオブジェクトには2つの重要なフィールドがあります:
使用カウントポインタ: 32ビットの読み取りを許可します。
インデックス付きタイムスタンプポインタ: 64ビットの書き込みを許可します。
これらのポインタを上書きすることで、カーネルメモリ内の任意のアドレスにリダイレクトし、読み書き機能を有効にします。
32ビットカーネル読み取り
読み取りを行うには:
使用カウントポインタをターゲットアドレスから0x14バイトオフセットを引いた位置にポイントするように上書きします。
get_use_count
メソッドを使用して、そのアドレスの値を読み取ります。
64ビットカーネル書き込み
書き込みを行うには:
インデックス付きタイムスタンプポインタをターゲットアドレスに上書きします。
set_indexed_timestamp
メソッドを使用して64ビット値を書き込みます。
エクスプロイトフローの再確認
物理的な使用後の解放をトリガー: 解放されたページは再利用可能です。
IOSurfaceオブジェクトをスプレー: カーネルメモリにユニークな「マジックバリュー」を持つ多くのIOSurfaceオブジェクトを割り当てます。
アクセス可能なIOSurfaceを特定: 制御可能な解放されたページ上のIOSurfaceを見つけます。
使用後の解放を悪用: IOSurfaceオブジェクト内のポインタを変更して、IOSurfaceメソッドを介して任意のカーネルの読み書きを可能にします。
これらのプリミティブを使用して、エクスプロイトはカーネルメモリへの制御された32ビットの読み取りと64ビットの書き込みを提供します。さらなる脱獄手順には、追加の保護(例:新しいarm64eデバイスのPPL)をバイパスする必要があるかもしれない、より安定した読み書きプリミティブが含まれる可能性があります。
Last updated