ソースコードもデバッグシンボルもないLinuxカーネルへのコード挿入方法
このような状況を想像してみてください:モバイルデバイスや組み込みARM64ボード用の完成済みLinuxカーネルバイナリが手元にあるとします。ソースコードは利用できず、デバッグ情報はビルド時にストリップされており、システムコールを傍受したり、カーネルレベルで独自のロジックを追加したりする必要があります。
このような場合、従来の方法は長く面倒です。IDA ProやGhidraでバイナリをリバースエンジニアリングし、関数オフセットを手動で見つけ、脆弱なパッチを作成する必要があります。bmax121というニックネームの開発者がこのプロセスを簡素化することを決意し、KernelPatchプロジェクトをリリースしました。
KernelPatchが必要な理由
KernelPatchは、ストリップされたカーネルイメージ(vmlinuxやboot.imgなど)のみでLinuxカーネルにパッチを適用し、フックするためのフレームワークです。
デバッグ情報なしでビルドされたカーネルでは、kprobesのような従来のデバッグツールは追加の回避策なしには動作しません。KernelPatchは解决这个问题を 자체적으로 해결합니다。カーネルバイナリから直接シンボルテーブルを復元し、実行可能コードを変更するための便利なAPIを提供します。
このプロジェクトは、リバースエンジニア、Androidセキュリティ研究者、組み込みシステム向けの低レベルソフトウェア開発者にとって便利です。
フレームワークできること
開発者はカーネル操作のためにプロジェクトにいくつかの重要な機能を組み込んでいます:
- ソースコードなしのシンボル検索。このツールは、完成済みイメージ内の内部
kallsyms構造を解析します。外部のSystem.mapファイルやソースコードは不要です。同様のアプローチは、著者がいくつかのアイデアを借用したvmlinux-to-elfユーティリティでも使用されています。 - 2つのコード挿入モード。イメージへの静的パッチ(例えばディスク上の
boot.imgを上書き)または 런타임時の動的コードロードが可能です。 - インラインホックによる関数傍受。ARM64アーキテクチャでは、コール傍受は
android-inline-hookの作業に基づいています。フレームワークは元のコードの実行を壊さないように、相対アドレッシング命令(b、bl、adrp)を正しく処理します。 - システムコールフック。
sys_call_tableテーブル内のアドレスを置換するのは数コマンドで完了します。 - 組み込みSUモジュール。Androidデバイス向けに、プロジェクトには独自のsuperuser実装が含まれています。
内部動作の仕組み
ユーザーモジュール(KPM — Kernel Patch Modules)をオンデマンドでロードするために、フレームワークはカーネル内のメモリを必要とします。このために、KernelPatchにはコンパクトなTLSFアロケータが含まれています。カーネルアドレス空間内に直接、実行可能なメモリページ(ROX)を割り当てることができます。
インラインホックを実行する際、フレームワークはターゲット関数の最初の命令を読み取り所谓的トランポリンを作成し、ハンドラーへのジャンプを書き込みます。独自のコードの実行が完了すると、トランポリンを通じて元の関数に制御が戻ります。
+-------------------+ +---------------------+ +-------------------+
| Целевая функция | ---> | Ваш обработчик KPM | ---> | Трамплин (Orig) |
| (инструкция JMP) | | (пользовательский) | | (продолжение) |
+-------------------+ +---------------------+ +-------------------+
システム要件と制限事項
このプロジェクトには、事前に知る重要な適用範囲の境界があります:
- ARM64アーキテクチャのみサポートされています。x86_64やRISC-Vのバージョンはまだありません。
- カーネルバージョン範囲 — Linux 3.18から6.6まで。
- 主な要件は、カーネルが
CONFIG_KALLSYMS=yフラグでビルドされていることです。
ファームウェアベンダーがビルド段階で完全にCONFIG_KALLSYMSを削除した場合、KernelPatchは自動的に関数アドレスを解析できません。幸いなことに、このフラグはほとんどのAndroidおよびARMデバイスファームウェアで有効のままになっています。
実践的な使用例
テスト用Androidデバイスでexecveシステムコール介してすべてのプロセス起動を追跡する必要があるが、ベンダーのソースコードからカーネルを再ビルドしたくないとします。
従来は、完全なカーネルロード可能モジュール(LKM)をビルドし、デジタル署名検証の問題に対処し、カーネルにモジュールローダーが存在しないという問題がありました。
KernelPatchでは、プロセスが異なります:
- 傍受ロジックを含む小さなCコードを記述します。
- KPM形式にコンパイルします。
- KernelPatchユーティリティにカーネルイメージとコンパイル済みKPMモジュールを入力します。
- 変更されたカーネルイメージを取得し、fastbootでフラッシュします。
デバイスが起動した後、execveへのすべてのコールが最初にハンドラーを通過します。
プロジェクトのREADMEは非常に簡潔であり、すべての詳細なドキュメントはソースコードとsamplesフォルダ内のファイルから研究する必要があります。それでも、プロジェクトは積極的に開発中で、GitHubで14,000を超えるスターを獲得しています。
ちなみに、実験用にフレームワーク自体は不要で、代わりにそれに基づいた готовый Android用root化ソリューションが必要な場合は 著者は隣接プロジェクトであるAPatchを推奨しています。これはKernelPatchを内部で使用する、MagiskやKernelSUの上位レベルの代替手段です。
セキュリティ研究者とARM64開発者にとって、KernelPatchはクローズドソースカーネルの分析時に何十時間も節約できる優れた発見です。
関連プロジェクト