
スナップショットベースのカバレッジガイド型Windowsカーネルファザー
Rewind は、Windows カーネルコンポーネントを対象としたスナップショットベースのカバレッジガイドファザーです。
アイデアは、稼働中のシステムのスナップショットから開始することです。このスナップショットは、物理メモリページと CPU の状態で構成されています。
この状態は、仮想 CPU の初期状態を設定するために使用されます。オンデマンドページングを利用することで、ターゲット関数の実行に必要なページだけがスナップショットから読み込まれます。
ターゲット関数の実行に有用な物理メモリページのみを持つ専用の仮想マシンを使用するため、スナップショットの復元は高速です。
現在、2 つのバックエンドが利用可能です:
WHVP バックエンドは、WHVP (Windows Hypervisor Platform) API を利用して Hyper-V パーティションへのアクセスを提供します。詳細は https://docs.microsoft.com/en-us/virtualization/api/hypervisor-platform/hypervisor-platform を参照してください。Bochs バックエンドは Bochs エミュレータを利用します (https://bochs.sourceforge.io/)KVM バックエンドは開発中で、すぐに利用可能になるはずです。
Rewind は 2 つの主要機能を提供します:
また、ファジングに関する有用な情報を報告する基本的な TUI (ターミナルユーザーインターフェース) も提供します。
Windows と Linux でテストされています (現時点では Linux では bochs バックエンドのみ)。
私は常に、特に Windows カーネルにおけるカーネル脆弱性研究を楽しんでいました。そのプロセスには常に静的解析と動的解析の組み合わせが含まれます。動的解析を行うと、すぐに退屈になることがあります。デバッグ / クラッシュ / 再起動 / 全ブレークポイントのリセットというサイクルは遅くて苦痛です。ファジングを行いたい場合、多くの場合、1 台または複数の仮想マシンとカーネルデバッガーをセットアップし、クラッシュ検出を処理するための場当たり的なスクリプトを作成する必要があります...
仮想マシンでスナップショットを取ると役立ちますが、遅いです。
2018 年、Microsoft は Windows Hypervisor Platform (WHVP) という新しい API セットを導入しました。これらの API を使用すると、いくつかの仮想プロセッサを備えたパーティション (Hyper-V の用語では VM) をセットアップし、仮想マシンで発生する VM イグジットを制御できます。これは、ユーザーランドに独自の VM イグジットハンドラーを持つようなものです。便利なことを行うのに非常に役立ちます。例えば、Simpleator や applepie などです。
そこで WHVP で遊び始め、Hyper-V パーティション内でシェルコードを実行できる最初の PoC を作成しました。それは Python で書かれており、かなり遅かったです。この最初の PoC はすぐに、ある種のスナップショットベースのトレーサーへと進化しました。私は仮想 CPU をブートストラップし、セットアップが非常に簡単なものが欲しいと考えていました。すでにターゲットで遊ぶためにカーネルデバッガーを使用していたので、WinDbg で作成したカーネルダンプをスナップショットとして使用することにしました。これにより、仮想 CPU を備えたパーティションをセットアップするだけで済みました。仮想 CPU コンテキストは、ダンプから取得したコンテキストで設定されます。仮想 CPU が物理ページを必要とするときはいつでも、ダンプのページを使用します。
これにより、ダンプの状態をパーティションにフォークしてから実行を再開することができました。これにより、ターゲット関数の実行を簡単にトレースできました。引数を変更してパーティションのメモリ状態を元に戻すことで、ターゲットをファズすることも非常に簡単でした。
この研究は SSTIC カンファレンス 2020 で発表され、github で公開されました。
このツールは、カバレッジを取得するための 2 つの方法を実装しています。1 つ目は、古典的な TF (トラップフラグ) を利用して、すべての命令で INT1 割り込みを発生させる方法です。これはターゲットの変更が必要で、遅いです。私は MONITOR トラップフラグを使用したかったのですが、WHVP はこの可能性を提供していません。
適切なパフォーマンス (ファジングに必要) を得るために、カバレッジの精度を下げ、命令が初めて実行されたときだけを把握するモードを追加することにしました。
そのために、スナップショットから取得したページを 0xcc バイトでパッチします (実行可能なページのみ)。CPU がこれらのパッチされた命令を実行すると、ハイパーバイザーが例外をトラップし、命令を元のコードで書き換えます。
これは、すべての命令に個別のソフトウェアブレークポイントが設定されているようなものです。95% の確率で機能しますが、特定のコード (たとえばジャンプテーブルを含むコード) では、データが置き換えられるため失敗します。
これを克服する 1 つの方法は、コードをマッピングする前に逆アセンブルし、必要なものだけをパッチすることです (次回の課題かもしれません)。
実験中、WHVP を使用する際にいくつかの制限に遭遇しました。それは遅い、本当に遅いです。VirtualBox のソースコードにはいくつか興味深いコメントがあります :)
したがって、適切なパフォーマンスを得るには VM イグジットを制限する必要があり、Hyper-V をトレーシングハイパーバイザーとして使用したい場合には互換性がありません (多くの VM イグジットが必要になるため)。
同じ頃、ツールで得られたトレースが正しいかどうかを確認するために bochs (特にインストルメンテーション部分) を使い始めました。Bochs は、トレースに分岐がないかを確認するための一種のオラクルでした。
Bochs は、フルトレースを行う場合 WHVP よりも高速で、メモリアクセスやその他の便利な機能も利用できます。
bochs を別のバックエンドとして追加することにしました。whvp という名前はもはや適切ではなく、rewind に落ち着きました。

rewind は、Windows プラットフォーム上のカーネルドライバーに対するセキュリティ評価を実施する際の、私自身のワークフローを中心に設計されました。
最初のステップは、仮想マシン内にターゲットソフトウェアをインストールすることです。静的解析と動的解析を組み合わせて使用しているため、カーネルデバッガーもセットアップします。
IDA でいくつかのランダムなドライバーを開いた後、すぐにいくつかの関数をターゲットにし始めます。そのために、通常 windbg でブレークポイントを設定し、ret-sync と組み合わせて遊び始めることができます。
そこで rewind の出番です。メモリ内のランダムなバッファを編集してシングルステップ実行し、IDB に注釈を付けて何が起こっているのかをおおまかに把握する代わりに、windbg でスナップショットを取得し、代わりに rewind を使用します。
これにより、プロセスが大幅に楽になります。スナップショットを持つことには多くの利点があります。すべてが決定的です。関数呼び出しを飽きるほど何度でもリプレイできます。ターゲット関数が面白そうならファザーを起動できます。もう必要ないので VM を閉じることさえできます。
言うまでもなく Rust が必要です (Windows と Linux で Rust 1.50 を使用したインストールをテスト済み)。また、いくつかの依存関係では CMake も必要です。
まずリポジトリをクローンします:
$ git clone [email protected]:quarkslab/rewind.git
bochs バックエンドのインストールに進みます
vendor ディレクトリに bochscpu (https://github.com/yrp604/bochscpu) リポジトリをクローンします:
$ cd vendor
$ git clone https://github.com/yrp604/bochscpu
bochscpu-build (https://github.com/yrp604/bochscpu-build) からビルド済みの bochs アーティファクトをダウンロードします
$ curl.exe -L --output bochs-x64-win.zip [artifact_url]
lib フォルダと bochs フォルダを bochscpu のチェックアウトに展開します。
$ Expand-Archive -Path .\bochs-x64-win.zip -DestinationPath .\
$ copy -Recurse .\bochs-x64-win\msvc\* .\bochscpu\
Windows では、WHVP もバックエンドとしてビルドされます。
昇格した powershell セッションで、次のコマンドを使用して WHVP が有効かどうかを確認します:
Get-WindowsOptionalFeature -FeatureName HypervisorPlatform -Online
FeatureName : HypervisorPlatform
DisplayName : Windows Hypervisor Platform
Description : Enables virtualization software to run on the Windows hypervisor
RestartRequired : Possible
State : Enabled
CustomProperties :
有効になっていない場合は、Set-WindowsOptionalFeature コマンドレットを使用して有効にできます。また、Hyper-V を有効にする必要もあります。
また、Windows SDK (10.0.19041.0) がインストールされている必要があります。https://developer.microsoft.com/fr-fr/windows/downloads/windows-10-sdk/ からダウンロードできます。
LLVM をインストールし、LIBCLANG_PATH 環境変数を設定する必要があります (bindgen に必要)。詳細な説明は https://rust-lang.github.io/rust-bindgen/requirements.html を参照してください。
$ $env:LIBCLANG_PATH="C:\Program Files\LLVM\bin"
そこから rewind をビルドできるはずです (bochscpu クレートの unwind_attributes のため nightly が必要です):
$ cd rewind_cli
$ cargo +nightly build --release
rewind バイナリは target/release ディレクトリで利用可能になります。
cargo を使用してローカルにインストールすることもできます:
$ cd rewind_cli
$ cargo +nightly install --path .
> error: failed to run custom build command for `zydis v3.1.1`
whvp-sys のビルドに失敗しますCVE-2020-17087 を利用した基本的なチュートリアルが examples ディレクトリに用意されています
TODO.md を参照してください
hit カバレッジモードを使用する場合、トレーサーは一部の関数で誤動作します (一部のスイッチテーブルがその場合です)。その理由は、各バイトがソフトウェアブレークポイントに置き換えられるためです (実行可能ページにデータが存在する場合はデータも含まれます)。これを行うより良い方法は、たとえば逆アセンブラからすべての基本ブロックのリストを取得することです。このツールは現在、Apache 2.0 ライセンスの下で Quarkslab によって開発およびスポンサーされています。
@yrp604、@0vercl0k、Alexandre Gazet の各氏に感謝します。彼らの助け、フィードバック、そして考えに感謝します。Quarkslab のすべての同僚にも感謝します!