
iOS kernelcache リバースエンジニアリングのための Ghidra フレームワーク
このフレームワークは、Kernelcacheのリバースエンジニアリングにおける私の経験の最終成果物です。私は通常、カーネルとその拡張機能を手動で監査して脆弱性を探し、リバースのプロセスを高速化するためにGhidraで本当に欲しかったもののほとんどを自動化してきました。これは効果的であり、多くの時間を節約することが証明されています。 このフレームワークはiOS 12/13/14/15およびmacOS 11/12(kernelcacheと単一のKEXTの両方)で動作し、自分の環境を準備する苦労なしにiOSカーネルの研究を始めたい人々を助ける目的で公開されました。 私の考えでは、このフレームワーク(提供するツールセットとIOKitの基本的な知識があれば)は、Kernelcacheへのハッキングを開始するのに十分です。
このフレームワークは完全にPythonで書かれており、拡張して他のツールを構築することもできます。ほとんどすべてのプロジェクトで使用でき、冗長なマニュアルを読む時間を節約する基本的なAPIを提供しています。コア機能については、utils/ ディレクトリを自由に参照してください。
GhidraはKernelcacheの解析に優れていますが、他のリバースエンジニアリングツールと同様に手作業が必要です。ghidra_kernelcacheは、開始時やリバースエンジニアリング中に問題を修正するための良いエントリポイントを提供し、その結果、見栄えの良いデコンパイラ出力を提供します。
IDAProで@_bazadによって作成されたida_kernelcacheという類似のプロジェクトがあります。これはIDAでカーネルイメージを扱いたい研究者に良いエントリポイントを提供します。私のフレームワークはBrandonの作品と少し似ていますが、kernelcacheを扱うプロセスをより快適にするためにさらに多くの機能を提供しています。
::externalMethod() と ::getTargetAndMethodForIndex() の両方の外部メソッドのディスパッチテーブルを自動修正。これらの機能は、キーボードショートカットまたはツールバーのアイコンをクリックして実行できる個別のツールとして作成されています。
リポジトリをクローンする :```sh git clone https://github.com/0x36/ghidra_kernelcache.git
**重要な注意**: 本プロジェクトは Ghidra 10.1_PUBLIC および 10.2_DEV でテストされており、下位互換性はありません。
*`Windows → Script Manager`* に移動し、 *`script Directory`* をクリックして、ディレクトリパスリストに *`ghidra_kernelcache`* を追加します。
*`Windows → Script Manager`* に移動し、*scripts* リスト内で *`iOS→kernel`* カテゴリを探し、そこにあるプラグインをチェックすると、それらが GHIDRA ツールバーに表示されます。
[logos/](https://github.com/0x36/ghidra_kernelcache/tree/master/logos) ディレクトリには、各ツール用の独自のロゴを配置できます。
## iOS カーネルキャッシュのシンボリケーション
`ghidra_kernelcache` は最初の段階で [iometa](https://github.com/Siguza/iometa/) ([@s1guza](https://twitter.com/s1guza) 作) を必要とします。これはカーネルバイナリ内の C++ クラス情報を提供する強力なツールです。素晴らしい点は、スタンドアロンバイナリとして動作するため、出力を解析するだけでお気に入りの RE フレームワークにインポートできることです。私のフレームワークは iometa の出力を受け取り、解析してシンボリケーションと仮想テーブルの修正を行います。
### 使用方法
カーネルを展開した後、次のコマンドを実行します:```sh
$ iometa -n -A /tmp/kernel A10-legacy.txt > /tmp/kernel.txt
# if you want also to symbolicate using jtool2
$ jtool2 --analyze /tmp/kernel
Ghidra でカーネルキャッシュをロードします。バッチインポートは使用しないでください。Mach-O イメージとしてロードしてください。
カーネルキャッシュがロードされ、自動解析が完了したら、ツールバーに表示されているアイコンをクリックするか、Meta-Shift-K を押して、iometa の出力のフルパス(この例では /tmp/kernel.txt)を入力します。
jtool2 シンボルを使用したい場合は、iOS→kernel カテゴリにある jsymbol.py も使用できます。
完全な API の例は ghidra_kernelcache/kc.py にあります。
→ ここではクラスオブジェクトを操作する例をいくつか示します:```py from utils.helpers import * from utils.class import * from utils.iometa import ParseIOMeta
ff = "/Users/mg/ghidra_ios/kernel.txt" iom = ParseIOMeta(ff) Obj = iom.getObjects() kc = kernelCache(Obj)
kc.process_all_classes()
kc.process_classes_for_bundle("com.apple.iokit.IOSurface")
kc.process_classes_for_bundle("kernel")
kc.process_class("IOGraphicsAccelerator2")
kc.clear_class_structures()
kc.update_classes_vtable()
kc.explore_pac()
ご覧のとおり、kernelcache を完全または部分的にシンボル化できます。部分的なシンボル化を選択した場合、`ghidra_kernelcache` は自動的にすべてのクラス依存関係を構築してから処理を進めます。スクリプトを kernelcache 全体に対して実行すると(完全なシンボル化)、`ghidra_kernelcache` はカーネルイメージの分析に数分かかります。
完了すると、Ghidra は以下を提供します:
→ ブックマークフィルターに「iOS」という新しいカテゴリが追加されました:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image1.png" alt="image1" width="200"/>
→ IOKit クラスの仮想テーブルが「iOS」ブックマークに追加され、仮想テーブルのルックアップがより良く高速になります。検索バーに文字、単語、または kext バンドルを入力して kext やクラスを検索するだけで済みます。
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image2.png" alt="image2"/>
→ 仮想テーブルの修正:未知のコードを逆アセンブル/コンパイルし、名前空間を修正し、クラスメソッドを再シンボル化し、各メソッドに関数定義を適用します:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image3.png" alt="image3"/>
→ クラス名前空間の作成と、各メソッドをそれぞれ対応する名前空間に配置:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image4.png" alt="image4"/>
→ クラス階層を尊重したクラス構造の作成:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image5.png" alt="image5"/>
→ クラス vtable の作成。各メソッドは独自のメソッド定義を持ち、逆コンパイル出力が向上します:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image6.png" alt="image6"/>
完全な実装は [`utils/class.py`](https://github.com/0x36/ghidra_kernelcache/blob/master/utils/class.py) にあります。
以下は、`ghidra_kernelcache` を使用したシンボル化前後のスクリーンショットです:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image7.png" alt="image7"/>
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image8.png" alt="image8"/>
## macOS Kext のシンボル化
---
`ghidra_kernelcache` の macOS サポートは、ARM64e および x86_64 アーキテクチャ向けの kernelcache と単一 KEXT のシンボル化の両方に対応しています。
**重要:** 本稿執筆時点では、Ghidra は macOS の kernelcache 全体を解析できませんが、IDA にロードして初期分析を行い、その後データベース(idb から xml)を Ghidra にインポートすることが可能ですが、これは範囲外です。もしそれができた場合、`ghidra_kernelcache` が残りの処理を行います。
macOS カーネル拡張機能をシンボル化する前に、いくつかの手順を踏む必要があります。`ghidra_kernelcache` の主な目的は、クラス階層を再構築し、すべてのクラス構造を単一のデータベースに管理することであり、カーネル拡張機能だけではこれらの要件を満たしていないからです。つまり、単一の KEXT のシンボル化には、カーネルおよび依存する他のカーネル拡張機能のシンボル化が必要となるため、ここでは追加の作業が必要です。
`ghidra_kernelcache` は現在、Ghidra の強力な *DataType Project Archive* を介してクラス構造と仮想メソッド定義を管理・共有することにより、カーネルを含むカーネル拡張機能をシンボル化する強力な方法を提供しています。
### カーネル拡張機能をシンボル化する手順
- Ghidra プロジェクトに新しいフォルダを作成し、` /System/Library/Kernels/kernel.release.XXXXX` をそのフォルダにロードして、Ghidra に分析させます。
- 新しい *Project Archive* を作成します: `DataType Provider` に移動 → ウィンドウ右上の矢印をクリック → `New Project Archive` → 新しく作成したフォルダ内に配置 → 適当な名前を付けます (例: macOS_12.1)。
- 次に、`ghidra_kernelcache` を使用してカーネルをシンボル化します。このプロセスは iOS *kernelcache* のシンボル化と非常に似ています。```bash
$ iometa -n -A /System/Library/Kernels/kernel.release.t8101 > /tmp/kernel.txt
KM.pyスクリプトで見つけることができます :```pyfrom utils.helpers import * from utils.kext import * iom = ParseIOMeta("/tmp/kernel.txt") Obj = iom.getObjects() kc = Kext(Obj,shared_p="macOS_12.1") kc.process_kernel_kext()
- 完了すると、カーネルのアーカイブと`macOS_12.1`アーカイブの間にデータベース関連付けが作成されます。次に、`kernel.release.t8001`を右クリック → `Commit DataTypes To` → `macOS_12.1`を選択します。
- そして`Right Click` →`Select All` → `Commit`を選択します。
- プロジェクトアーカイブを保存します:`Right click` → `Save Archive`。
これで、すべてのカーネル拡張機能間で共有可能なプロジェクトアーカイブが作成されました。
例として、Apple Silicon用の`IOSurface` Kextを見てみましょう:```bash
$ lipo /System/Library/Extensions/IOSurface.kext/Contents/MacOS/IOSurface -thin arm64e -output /tmp/iosurface.arm64e
$ iometa -n -A /tmp/iosurface.arm64e > /tmp/iosurface.txt
macOS_12.1をロードします:データ型マネージャ → プロジェクトアーカイブを開く に進み、macOS_12.1を選択します。KM.pyにあります:```python
from utils.helpers import *
from utils.kext import *kc = Kext(Obj,shared_p="macOS_12.1")
kc.depac()
kc.process_kernel_kext()
**重要注意事項**: 場合によっては、`kc.process_kernel_kext()` が失敗することがあります。これは、Ghidra が一部の C++ シンボルをデマングルできなかったためです。これを修正するには、スクリプトマネージャーに移動して `DemangleAllScript.java` スクリプトを実行し、その後 `kc.process_kernel_kext()` を再度実行してください。
### カスタムクラス
`ghidra_kernelcache` や `iometa` ではシンボル化できない C++ クラスが存在する場合があります。そのため、これを処理するための新機能が追加されました。
`Custom()` クラスの再構築は、すべての `::vtable` シンボルを反復処理し、そのクラスが既に定義済みかどうかを確認します。定義されていない場合、クラス構造、特定された各クラスメソッドの関数定義、名前空間、および各クラスの仮想テーブルを自動的に作成します。
現在、カスタムクラスの作成は macOS のみでサポートされています。```bash
$ iometa -n -A /System/Library/Kernels/kernel.release.t8101 > /tmp/kernel.txt
$ iometa -n -A <kext_path> >> /tmp/kernel.txt
INPUT:```py
from utils.helpers import * from utils.custom_kc import *
if name == "main": default = "/tmp/kernel.txt" ff = askString("iometa symbol file","Symbol file: ",default) iom = ParseIOMeta(ff) Obj = iom.getObjects()
kc = Custom(Obj)
kc.process_all_classes()
kc.explore_pac()
## Miscellaneous scripts
---
### KDKのDwarf4のインポート
Ghidraが対応する`.dsym`ディレクトリを読み込めない場合があるため、これを修正する小さなスクリプトを作成しました。スクリプトは[こちら](https://github.com/0x36/ghidra_kernelcache/blob/master/dwarf4_fix.py)にあります。
**使い方**: KDKのパスからカーネルをロードし、Ghidraによる解析が終了したら`dwarf_fix.py`を実行します。シンボルがロードされ、処理に数分かかる場合があります。
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image12.png" alt="image12"/>
### 仮想メソッド呼び出し参照の解決
`ghidra_kernelcache`は、`kernelCache.explore_pac`または`fix_extra_refs`を使用して仮想呼び出しを解決する2つの方法を提供します。
**kernelCache.explore_pac()**
arm64eバイナリを扱っている場合、`ghidra_kernelcache`は`Pointer Authentication Code`値を探すことで仮想メソッド呼び出しを認識できます。このプロセスは簡単で、`fix_extra_refs()`とは異なり、`kernelCache.explore_pac`は`Pcode`や`varnode`の識別に依存せず、プログラム内のすべての命令を反復処理し、`MOVK`命令を検索して、第2オペランドを取得し、データベース内の対応する値を探します。
使い方 :
**kernelCache**、**Kext**、または**Custom**を介して*KernelCache*インスタンスを作成し、`explore_pac()`メソッドを呼び出します。```py
from utils.helpers import *
from utils.kext import *
if __name__ == "__main__":
default = "/tmp/kernel.txt"
ff = askString("iometa symbol file","Symbol file: ",default)
iom = ParseIOMeta(ff)
Obj = iom.getObjects()
kc = Kext(Obj)
kc.explore_pac()
fix_extra_refs()
この関数は、基本的なデータフロー解析に基づいて仮想呼び出しメソッドをすべて検出し、その実装を自動的に解決します。すべてのアーキテクチャで動作し、デコンパイラ出力からソースデータ型を認識し、関数内のすべての仮想呼び出し参照を解決する機能を持っています。これにより、ユーザーは手動で探すことなく、実装に直接前方/後方にジャンプできるようになります。
fix_extra_refs が提供する最も有用な機能は、実行ごとに参照を同期し続けることです。例えば、変数のデータ型をクラスのデータ型に変更した場合、fix_extra_refs は自動的にその変更を認識し、すべての呼び出しサイトを再帰的に辿って参照を解決し、呼び出しサイトキューが空になるまで処理を続けます。
fix_extra_refs が提供するその他の機能は以下の通りです。
_ptmf2ptf() 呼び出しを自動検出し、オフセットと完全な関数アドレスの両方について呼び出しメソッドを解決します。FUN_ で始まる関数)の名前空間を特定し、対象関数を自身の名前空間(例えば、対応するクラスの this ポインタを追加)に配置することで解決します。実装は utils/references.py にあります。fix_extra_refs は pcode 操作を解析し、CALLIND および CALL オペコードを探します。次に、その操作に関与するすべての varnodes を取得します。Varnode 定義が識別されると、その HighVariable を取得してクラスオブジェクトの型を特定します。型が不明(すなわちクラス構造のように見えない)な場合は無視します。それ以外の場合はクラス名を取得し、その仮想呼び出しテーブルを検索し、Varnode が提供するオフセットを使用して正しい仮想メソッド呼び出しを取得し、呼び出し命令に参照を追加します。```py
fix_extra_refs(toAddr(address))
以下は、`fix_extra_refs`の使用例です。
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image9.png" alt="image9"/>
手動での修正なしに、**IOService::isOpen()**、**OSArray::getNextIndexOfObject()**、**IOStream::removeBuffer()**の仮想呼び出しを正常に解決したことに注意してください。
次に、`fix_extra_refs`は**IOStream::removeBuffer()**を逆コンパイルし、このメソッドのすべてのHighVariablesを取得してから、前のメソッドと同様にこれらの参照を解決していきます。
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image10.png" alt="image10"/>
### 外部メソッドテーブルの自動修正
すべての研究者がこの部分を処理するためのスクリプトを持っていると思います。これがIOKitの主な攻撃対象であり、手動で行うのは負担であり、研究者が複数の外部メソッドテーブルを調査したいと思わせる方法で自動化する必要があります。
`ghidra_kernelcache`には、**fix_methodForIndex.py**と**fix_extMethod.py**の2つのスクリプトが用意されています。これらは上記のように他のスクリプトと同様に有効にできます。
***使用方法***: カーソルを外部ディスパッチテーブルの先頭に置き、スクリプトを実行します。ターゲットとセレクタの数を指定します。
`IOStreamUserClient::getTargetAndMethodForIndex()`の例:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image11.png" alt="image11"/>
### namespace.py : メソッド名前空間の修正…
これは、すべての遭遇したメソッドにクラスタイプを設定するための便利なスクリプトであり、`extra_refs.py`スクリプトが呼び出し先の関数を再帰的に探索し、その参照を解決するための依存関係です。
***使用方法***: 目的の関数の逆コンパイラ出力にカーソルを置き、ツールバーからスクリプトを実行するか、**Meta-Shift-N**を押します。
### シンボル名と型の伝播
`ghidra_kernelcache`は基本的なPcode操作のための型伝播機能を提供しますが、複雑なキャストを使用する変数では失敗することがあります。
もし誰かが手伝いたい、またはGhidraで低レベルの作業を始めたいと思っているなら、これがその機会です。
実装は[ghidra_kernelcache/propagate.py](https://github.com/0x36/ghidra_kernelcache/blob/master/propagate.py)にあります。
### 関数シグネチャの読み込み
GhidraでC++ヘッダファイルをパースすることはできませんが、kernelcacheに関数シグネチャを追加することで、逆コンパイラの出力を改善できます。
例えば、`virtual IOMemoryMap * map(IOOptionBits options = 0 );`を追加したとします。Ghidraは自動的に関数定義と関数シグネチャの両方で戻り値を`IOMemoryMap`ポインタに自動的に再型付けします。
任意のC++シンボルを**signatures/**ディレクトリに構文に従って追加できます。このディレクトリには定義済みの関数シグネチャもあります。```c++
// Defining an instance class method
IOMemoryDescriptor * withPersistentMemoryDescriptor(IOMemoryDescriptor *originalMD);
// Defining a virtual method, it must start with "virtual" keyword
virtual IOMemoryMap * createMappingInTask(task_t intoTask, mach_vm_address_t atAddress, IOOptionBits options, mach_vm_size_t offset = 0, mach_vm_size_t length = 0);
// Defining a structure
struct task_t;
// typedef'ing a type
typedef typedef uint IOOptionBits;
// Lines begining with '//' are ignored
使い方:カーネルをシンボリファイした後、利用可能なすべての関数シグネチャをロードするためにスクリプト load_sigatnures.py を実行することを強く推奨します。以前のツールのほとんどと同様に、このスクリプトはツールバーに追加するか、プラグインマネージャーから実行するか、Meta-Shift-S キーを押すだけで実行できます。
このスクリプトは非常に簡単で、SourceType.USER_DEFINED を持つすべての構造体、クラス、typedef、関数定義などを古いプロジェクトから新しいプロジェクトにインポートします。
使い方:同じツールで古いGhidraプロジェクトと新しいGhidraプロジェクトを開き、load_structs.py スクリプトに移動し、古いプログラム名を src_prog_string 変数に、新しいプログラム名を dst_prog_string 変数に設定してから、スクリプトを実行します。
このプロジェクトに興味を持ち、貢献したい場合は、PRを作成してください。レビューします。その間、以下の分野での貢献を期待しています: