はじめに
基礎技術研究部の伊藤です。本記事では、Windows 上で動作するプログラムを高速にファジングする、オープンソースのファジングツール what the fuzz (wtf) を紹介します。ファジングとは何か、wtf はどのようなファジングツールなのか、といった話に触れたあと、その具体的な使い方を見ていきます。
少なくとも研究領域においては、Windows 上で動作するプログラムをターゲットとするファジングツールはあまり多くありません。ファジングの、特にパフォーマンスに関する最先端の研究は、Linux をプラットフォームとして行われるケースが非常に多いです。そのため、Windows 上で動作するプログラムをファジングしたい場合、wtf は強力な選択肢の 1 つとなります。
本記事の一部では wtf の実装について言及します。その対応するコミットハッシュ (wtf のバージョン) は e93236e です。また、本記事中で wtf のソースコードファイルに言及する場合は、全て /src/wtf のようにソースコードリポジトリのルートからの絶対パスで表記します。
- はじめに
- ファジングとは
- what the fuzz (wtf) とは
- wtf でファジングするために必要なもの
- wtf によるファジングの基本的な流れ
- ハーネス作成に利用できる便利機能
- おわりに
- エンジニア募集
ファジングとは
ファジングは、プログラムのバグや脆弱性を探すための技術です。簡単に言うと、以下の工程をひたすら繰り返す手法です。
- プログラムに与える新たな入力を作る
- 作った入力をプログラムに与える
- 与えた入力でプログラムがクラッシュしたか確かめる
非常に単純な手法のように思えますが、実際、皆さんが 1 度は聞いたことがあるであろう数々の著名なソフトウェアでも、膨大な数のバグや脆弱性がファジングで検出されています。例えば Linux カーネルに対しては、ファジングツール syzkaller による継続的なファジングが行われており、検出されたバグをメーリングリストへ自動で投稿する仕組み syzbot があります。
特にここ 10 年ほど、アカデミア・産業界を問わず、ファジングに関する研究はとても盛んに行われてきました。例えば、以下のようなことを今も多くの研究者が考えています。
- より速く、より多くのバグや脆弱性をファジングで見つけるにはどうすればよいか
- より様々なターゲットにファジングを適用するにはどうすればよいか
- ファジングで見つけたバグや脆弱性の根本原因を自動で特定し、修正できないか
今回紹介する wtf は学術研究の成果として生み出されたファジングツールではありませんが、Windows 上で動作する多くのプログラムに高度なファジングを適用可能にするものです。その点において wtf は、上に挙げた「より様々なターゲットにファジングを適用できるようにするにはどうすればよいか」という課題に貢献したファジングツールの 1 つといえます。
what the fuzz (wtf) とは
wtf は、Axel Souchet 氏により開発された、オープンソースのファジングツールです。はじめに、wtf によるファジングの概略図を示します。
wtf はさまざまな特徴のあるツールですが、ここでは 3 つ挙げてご紹介します。
- カバレッジに基づくグレーボックスファジングを行う
- スナップショットベースのファジングツールである
- Windows 上で動作するプログラムをターゲットとしている
1 つ目の特徴として、wtf は、カバレッジ*1に基づくグレーボックスファジング (coverage-guided grey-box fuzzing) を行います。ここでいうカバレッジは、簡単に言うと「ファジングによってターゲットの振る舞いがどの程度網羅されたか」を表す指標です。ファジングには様々な手法があり、特に、ファジングを行いながら得られるフィードバックを入力生成に利用する手法を、グレーボックスファジングといいます。カバレッジに基づくグレーボックスファジングでは、ファジングツールがカバレッジを管理しています。カバレッジを向上させた入力を変異させて新たな入力を生成することで、さらなるカバレッジの向上と、新たなバグや脆弱性の検出を目指します。wtf は、機械語命令レベルのコードカバレッジと、基本ブロック間遷移のエッジカバレッジ*2をサポートしています。
2 つ目の特徴として、wtf は、スナップショットベースのファジングツールです。ファジングでは、発生したクラッシュを検証しやすくする*3ため、新たな入力を与える前にターゲットの状態をリセットします。スナップショットは、リセットを実現する手段の 1 つです。ターゲットを仮想マシン (VM) 上で実行し、新たな入力を与えるたびにスナップショットを書き戻し、ターゲットの状態をリセットします。先の概略図では、この VM を ターゲット VM として示しています。ターゲット VM は、ファジングの実行時に wtf が作成する非常に簡素な VM であり、CPU とメモリ以外のハードウェアが存在しません。そのため、wtf におけるスナップショットは、ターゲットを実行している CPU レジスタとメモリのダンプのみで構成されています。なお、wtf はターゲット VM のバックエンド (仮想化プラットフォーム) として、以下のものをサポートしています。
3 つ目の特徴として、wtf は、Windows 上で動作するプログラムをターゲットとしています。ユーザーモードとカーネルモード、いずれで動作するプログラムに対してもファジングを実行できます。つまり、電卓やメモ帳のようなアプリケーションも、OS の一部として動作するデバイスドライバーも、wtf でファジングできます。
wtf でファジングするために必要なもの
wtf によるファジングの実行に必要な環境は、以下の通りです。1 台の物理マシンで全て構築できます。
- wtf をビルドする環境
- ビルドした wtf を動かす環境
- ターゲットを実行可能な Hyper-V VM
まず、wtf をビルドできる環境が必要です。wtf で独自のターゲットに対しファジングを実行する際は、ターゲットごとに作成したハーネスを wtf に組み込む必要があり、その際に wtf をビルドします。ハーネスについては後ほど ハーネスの作成 で述べます。この環境に Windows を使用する場合は、C++ によるネイティブアプリ開発の可能な Visual Studio 2022 が入っていれば十分です。
次に、ビルドした wtf を動かせる環境が必要です。この環境内で、先の概略図中に示したもの (wtf および wtf が作成するターゲット VM) が全て動きます。この環境に Windows を使う場合は、実行時に dbgeng.dll などのデバッガ用 DLL を利用するため、Debugging Tools for Windows のインストールが必要です。wtf 自体は Linux 上でも実行可能ですが、その場合は内部でデバッガを利用しないため、ハーネスでフックを設定する際、シンボル名が利用できないといった制約が生じます。
最後に、ターゲットを実行可能な Hyper-V VM が必要です。この VM ではターゲットを動作させ、ホストから WinDbg などのカーネルデバッガをアタッチして、スナップショットを取得します。原理的には VMware や VirtualBox など他の仮想化ソフトウェアでも可能ですが、スナップショットの取得が不安定らしく、推奨されていません。また、VM に関して下記のような推奨事項があります。
- 仮想 CPU の数: 1
- 仮想 RAM のサイズ: 4 GB
- カーネルデバッガとの通信方式: KDNET
この VM は概略図中のターゲット VM とは異なることに注意してください。この VM は wtf によるファジングに必要なスナップショットを取得するための VM であり、ユーザーが用意・操作しなければなりません。
wtf によるファジングの基本的な流れ
ここからは、wtf で独自のターゲットにファジングを行う際の流れを見ていきます。主な項目は以下の通りです。
スナップショットの取得
ターゲットを Hyper-V VM 上で動作させ、KD や WinDbg などのカーネルデバッガを用いて、wtf によるファジングに必要なスナップショットを取得します。what the fuzz (wtf) とは で述べた通り、wtf におけるスナップショットは、メモリおよび CPU レジスタのダンプを指します。
VM を作成する際は、wtf でファジングするために必要なもの で述べた推奨事項に注意してください。また、Hyper-V VM における KDNET を用いたカーネルデバッグのセットアップ方法については、Setting Up Network Debugging of a Virtual Machine - KDNET を参照してください。
ファジングを開始したい点にブレークポイントをセットするなどして実行を止め、そこでスナップショットを取得します。スナップショットの取得には、wtf と同じく Souchet 氏が開発した WinDbg の拡張機能 snapshot を使うと、簡単に CPU レジスタやメモリのダンプを取得できます。拡張機能 snapshot は、指定したディレクトリにスナップショットを保存します。保存されるスナップショットの実体は、mem.dmp と regs.json という 2 つのファイルです。名前の通り、mem.dmp はメモリダンプであり、regs.json は各種 CPU レジスタの値を記録した JSON ファイルです。出力されたこれらのファイルは、ターゲットディレクトリ内の state/ に配置します。ターゲットディレクトリについては ターゲットディレクトリの作成 を参照してください。
ハーネスの作成
wtf でファジングを行うには、wtf が特定のターゲットに対してファジングを行う仕組みを作成しなければなりません。本記事ではこの仕組みを ハーネス と呼びます。wtf のコードベースには、ハーネス作成に利用できる C++ の API が含まれており、それらを用いた C++ のコードとしてハーネスを作成します。作成したハーネスは wtf の一部としてビルドされ、生成される実行ファイル wtf.exe に組み込まれます。そのため、wtf で独自のターゲットにファジングを行う際は、wtf をビルドできなければなりません。
残念ながら、ハーネスの作り方に関する詳細なドキュメントは本記事の執筆時点 (2025 年 11 月) では存在しません。/src/wtf/ 以下にある既存ハーネス fuzzer_* のコードや、それらのコードの中で呼び出されている API のコードを読みながら、新たなハーネスを作成します。
ハーネスに関する全ては本記事でカバーしきれないため、基本的な事項のみ記します。また、本記事の後半では ハーネス作成に利用できる便利機能 も紹介します。
ハーネスの最も重要な役割は、以下のような Target_t 構造体のインスタンスを定義することです。
struct Target_t { using Init_t = bool (*)(const Options_t &, const CpuState_t &); using InsertTestcase_t = bool (*)(const uint8_t *, const size_t); using Restore_t = bool (*)(); using CreateMutator_t = std::unique_ptr<Mutator_t> (*)(std::mt19937_64 &, const size_t); explicit Target_t( const std::string &_Name, const Init_t _Init, const InsertTestcase_t _InsertTestcase, const Restore_t _Restore = []() { return true; }, const CreateMutator_t _CreateMutator = LibfuzzerMutator_t::Create); std::string Name; Init_t Init = nullptr; InsertTestcase_t InsertTestcase = nullptr; Restore_t Restore = nullptr; CreateMutator_t CreateMutator = nullptr; };
Target_t 構造体のコンストラクタ宣言を見てみましょう。コンストラクタは、引数として 4 つの関数ポインタを受け取っています。後者 2 つはデフォルト引数が与えられているので、インスタンス生成時に明示的に与えなければならないのは Init と InsertTestcase ですね。
wtf によるファジングでは基本的に、ターゲットの正常終了と異常終了 (クラッシュ) を定義し、それらを検知する仕組みをハーネスで実装しなければなりません。例えば、実行中の関数から return したら正常終了、何かしらの例外が発生したら異常終了と定義し、wtf が提供する API を使ってそれらを検知する仕組みをハーネス内に実装します。また、ターゲット VM において、ハードウェアは CPU とメモリしか存在しません。ストレージすら存在しないため、ファイルやレジストリなどへのアクセスはハーネスで適切に処理しなければなりません。
Init として与える関数は、wtf によるターゲットのファジングが開始される時に、1 度だけ呼ばれます。既存のハーネスでは上で述べた仕組みの多くを、特定のメモリアドレスへの到達や、関数呼出しに対するフック*4として実装しています。wtf は、以下に示すフック設定用の API SetBreakpoint を提供しています。これらを Init 内で呼び、特定アドレスへの到達や関数呼出しが発生した際に実行する処理を設定します。
// 仮想アドレスへの到達をフックする bool SetBreakpoint( const Gva_t Gva, // 到達をフックしたい仮想アドレス const BreakpointHandler_t Handler // Gva 到達時に実行する関数へのポインタ ); // シンボル(例:関数名)への到達をフックする bool SetBreakpoint( const char *Symbol, // 到達をフックしたいシンボル const BreakpointHandler_t Handler // Symbol 到達時に実行する関数へのポインタ );
InsertTestcase として与える関数は、新たなテストケース (入力) による実行を開始する際に毎回呼ばれます。この関数に求められるのは、wtf が生成した入力を、CPU レジスタやメモリ上に適切に配置することです。CPU レジスタやメモリを操作するための API は wtf が提供しています。
作成したハーネスのコードは、既存のハーネスと同様に /src/wtf/ に配置します。配置後に wtf をビルドすると、作成したハーネスは wtf.exe に組み込まれます。
ターゲットディレクトリの作成
wtf によるファジングを実行する環境において、以下の構造をもつディレクトリを作成します。名称は何でも構いません。本記事では、このディレクトリを ターゲットディレクトリ と呼びます。
<target>/ ├── inputs/ // 【要配置】初期シードの置き場 ├── outputs/ // wtf が生成したテストケースの置き場 ├── coverage/ // 【バックエンド次第で要配置】.cov ファイル置き場 ├── crashes/ // クラッシュしたテストケースの置き場 └── state/ // 【要配置】スナップショット置き場
上で 【要配置】 としたディレクトリには、ファジングを行う前に適切なデータを置かなければなりません。inputs/ には、初期シードを配置します。初期シードとは、既存の入力を変異させて新たな入力を生成するファジングにおいて、ファジングツールが最初に生成する入力の変異元となるデータです。ターゲットが正常に処理可能ないくつかの入力サンプルを与えるとよいでしょう。state/ には、取得したスナップショットのファイルを配置します。snapshot 拡張機能を使い取得した場合は、mem.dmp と regs.json がこれに該当します。
また、バックエンドとして bochscpu 以外を用いる場合は、coverage/ に .cov ファイルを配置しなければなりません。bochscpu 以外のバックエンドは、ソフトウェアブレークポイントを用いてカバレッジ情報を収集します。しかし、各バックエンドはブレークポイントをセットすべきアドレスを知らないため、その情報を .cov ファイルとして与えなければなりません。.cov ファイルは自前で作成してもよいですが、IDA Pro や Binary Ninja、Ghidra を使用して作成するスクリプトが /scripts に用意されています。.cov ファイルの実体は、以下のような内容の JSON ファイルです。
{
"name": "target.exe",
"addresses": [4096, 4112, ..., 558339]
}
文字列 name と整数配列 addresses が記述されています。name は、この .cov ファイルに記述するイメージ名です。addresses は、当該イメージにおいてカバレッジ情報収集のためにブレークポイントを張るアドレスの配列です。アドレスの形式は、イメージベースに対する RVA (相対仮想アドレス) です。指定するアドレスは任意ですが、基本的にはイメージを構成する各基本ブロックの先頭アドレスを指定します。
ファジングの実行
以上の準備を終えたら、コマンドプロンプトや PowerShell などのコマンドラインインターフェースから wtf を起動します。wtf によるファジングの実行には少なくとも 2 つの wtf.exe プロセスを起動しなければなりません。一方のプロセスを server node といい、他方のプロセスを fuzzing node といいます。wtf.exe はサブコマンドをとり、サブコマンドとして master を与えると server node を、fuzz を与えると fuzzing node を起動できます。server node は入力の生成やカバレッジの管理を、fuzzing node は server node が生成した入力によるターゲットの実行を担います。
Server Node の起動
server node は、wtf を起動するコマンドライン文字列にサブコマンド master をつけて起動します。wtf.exe master で利用可能なオプションを以下に示します。
C:\Users\User\Desktop\wtf\wtf>wtf.exe master --help
Master options
Usage: wtf.exe master [OPTIONS]
Options:
-h,--help Print this help message and exit
--help-all Expand all help
--address TEXT=tcp://localhost:31337
Which address to listen in
--runs UINT=18446744073709551615
Number of mutations done.
--max_len UINT REQUIRED Maximum size of a generated testcase.
--name TEXT REQUIRED Name of the target fuzzer.
--target TEXT Target directory
--inputs TEXT Input corpus
--outputs TEXT Outputs path
--crashes TEXT Crashes path
--seed UINT Override the seed used to initialize RNG.
上記のオプション一覧において REQUIRED となっているオプションは必須です。--max-len では、wtf により生成されるテストケースの最大サイズを与えます。--name に与える文字列は、ハーネスの作成 で作成したハーネスにおいて、Target_t 構造体のインスタンスを生成する際にコンストラクタへ第 1 引数で与えた文字列と一致させます。例えば、サンプルターゲット HEVD のハーネス /src/wtf/fuzzer_hevd.cc には、Target_t 構造体のインスタンス Hevd が以下のように定義されています。第 1 引数には文字列 hevd が与えられているため、--name にも hevd を指定します。
Target_t Hevd("hevd", Init, InsertTestcase);
これは、wtf.exe が全てのハーネスのコードを含んでおり、--name で与えられる文字列で、使用するハーネスを決めているためです。具体的には、/src/wtf/wtf.cc にて、使用するハーネスを以下のように決定しています。
Targets_t &Targets = Targets_t::Instance(); const Target_t *Target = Targets.Get(Opts.TargetName); if (Target == nullptr) { Targets.DisplayRegisteredTargets(); return EXIT_FAILURE; }
定義済みターゲットの一覧を Targets として持っており、--name で指定された文字列で、対応する Target_t 構造体を取り出しています。
Fuzzing Node の起動
fuzzing node は、wtf を起動するコマンドライン文字列にサブコマンド fuzz をつけて起動します。wtf.exe fuzz で利用可能なオプションを以下に示します。
C:\Users\User\Desktop\wtf\wtf>wtf.exe fuzz --help
Fuzzing options
Usage: wtf.exe fuzz [OPTIONS]
Options:
-h,--help Print this help message and exit
--help-all Expand all help
--backend ENUM:value in {bochscpu->0,whv->1,bxcpu->0} OR {0,1,0}
Execution backend.
--edges=0 Turn on edge coverage (bxcpu only).
--name TEXT REQUIRED Name of the target fuzzer.
--target TEXT Target directory which contains state/ inputs/ outputs/ folders.
--limit UINT Limit per testcase (instruction count for bochscpu, time in second for whv).
--state TEXT:DIR State directory which contains memory and cpu state.
--guest-files TEXT:DIR Directory where all the guest files are stored in.
--seed UINT Override the seed used to initialize RNGs.
--address TEXT=tcp://localhost:31337/
Connect to the master node.
上記のオプション一覧において、REQUIRED となっているオプションは必須です。server node として wtf を起動した場合と同じく、少なくとも --name は与える必要があります。
結果の確認
ファジングの結果は ターゲットディレクトリの作成 で作成したターゲットディレクトリに格納されます。ファジング中にカバレッジを向上させたテストケースは outputs に、クラッシュを発生させたテストケースは crashes に配置されます。outputs に配置されるテストケースのファイル名は、基本的にその内容から算出されるハッシュ値となりますが、特殊なテストケースには接頭辞がつけられます。具体的には以下のようなケースです。
timeout-*: 当該テストケースを与えた際の実行がタイムアウトしたcrash-*: 当該テストケースを与えた際の実行がクラッシュしたcr3-*: 当該テストケースを与えた際の実行で、コンテキストスイッチが発生した
各テストケースの個別実行
クラッシュしたテストケースなどは、再度与えてターゲットがどのような動きをしたのか確認が必要です。wtf.exe は、各テストケースを個別に実行するためのサブコマンド run を提供しています。wtf.exe run で利用可能なオプションを以下に示します。
C:\Users\User\Desktop\wtf\hevd>..\wtf\wtf.exe run --help
Run and trace options
Usage: ..\wtf\wtf.exe run [OPTIONS]
Options:
-h,--help Print this help message and exit
--help-all Expand all help
--name TEXT REQUIRED Name of the target fuzzer.
--backend ENUM:value in {bochscpu->0,whv->1,bxcpu->0} OR {0,1,0}
Execution backend.
--state TEXT:DIR State directory which contains memory and cpu state.
--guest-files TEXT:DIR Directory where all the guest files are stored in.
--input TEXT:(FILE) OR (DIR) REQUIRED
Input file or input folders to run.
--limit UINT Limit per testcase (instruction count for bochscpu, time in second for whv).
--coverage TEXT:DIR Directory where all the coverage files are stored in.
--edges=0 Turn on edge coverage (bxcpu only).
--runs UINT=1 Number of mutations done.
[Option Group: trace]
Describe the type of trace and where to store it
[At most 2 of the following options are allowed]
Options:
--trace-path TEXT:DIR Base folder where to output traces
--trace-type ENUM:value in {rip->1,cov->2,tenet->3} OR {1,2,3}
Type of trace to generate.
上記のオプション一覧において REQUIRED となっているのは、--name と --input です。--name には他のサブコマンド master や fuzz と同様に、ターゲットに対応する Target_t 構造体の名称を指定します。--input には、実行したいテストケース、または、実行したいテストケース群を含むディレクトリを指定します。
実行トレースの生成
wtf.exe のサブコマンド run では、実行トレースの生成も可能です。--trace-path にトレースファイルの出力先ディレクトリを指定し、--trace-type に出力するトレースのタイプを指定します。トレースのタイプには以下の 3 種類があります。
rip: 実行中に観測された RIP レジスタ (プログラムカウンタ) の値を時系列順に並べたトレースcov: 実行中に観測されたユニークな RIP レジスタの値を記録したトレースtenet: IDA Pro の tenet プラグインで読み込み可能な形式のトレース
rip および cov タイプのトレースは、Souchet 氏開発の symbolizer-rs でトレースをシンボル化 (絶対アドレスではなく、シンボルからの相対位置表記に変換) できます。シンボル化した cov タイプのトレースは、IDA Pro の lighthouse プラグインで読み込めます。
ハーネス作成に利用できる便利機能
ここでは、ハーネスの作成 で述べたハーネスを作成するために利用できる便利な既存機能を 2 つ紹介します。
ファイルシステムフック
ハーネスの作成 で述べた通り、wtf によるファジングでは、ターゲットは CPU とメモリしか存在しないターゲット VM で実行されます。当然ながらこの VM では、CPU とメモリ以外のハードウェアに対する全てのアクセスが失敗します。
この問題に対する解決策の 1 つは、VM に存在しないデバイスへアクセスする関数の呼び出しをフックし、実際のアクセスの代わりに、その振る舞いを模倣する処理を実行することです。しかし、該当するあらゆる関数の振る舞いを模倣するのは困難です。
wtf では、ファイルアクセスを生じる以下の関数についてのみ、それらをフックし、その振る舞いを模倣する仕組みが提供されています。本記事ではその仕組みを ファイルシステムフック と呼びます。ハーネス内で /src/wtf/fshook.h を include し、初期化関数内で SetupFilesystemHooks を呼ぶと、ファイルシステムフックが有効になります。
ntdll!NtClosentdll!NtQueryAttributesFilentdll!NtCreateFilent!NtOpenFilent!NtQueryVolumeInformationFilent!NtQueryInformationFilent!NtSetInformationFilent!NtWriteFilent!NtReadFile
ファイルシステムフックは、ファイルの情報を全て /src/wtf/fshandle_table.cc で定義されている g_FsHandleTable で管理しています。g_FsHandleTable は、FsTableHandle_t クラスのインスタンスです。g_FsTableHandle の内容は基本的にファイルシステムフックの動作によって実行時に更新されるものですが、ハーネスからその内容を触るための API も提供されています。例えば API MapGuestFileStream は以下のように実装されています。
void FsHandleTable_t::MapGuestFileStream(const char16_t *GuestPath, // VM 上でのファイルパス const uint8_t *Buffer, // ファイルの中身を保持しているバッファ const size_t BufferSize, // バッファのサイズ const bool AlreadyExisted, // 既存のファイルとしてマークするか? const bool AllowWrites // ファイルに対する書き込みを許すか? ) { FsDebugPrint( "Mapping {} guest file {} with filestream({}) {}\n", (AlreadyExisted ? "already existing" : "previously non existing"), u16stringToString(GuestPath), BufferSize, (AllowWrites ? "with writes allowed" : "")); auto GuestFile = new GuestFile_t(GuestPath, Buffer, BufferSize, AlreadyExisted, AllowWrites); TrackedGuestFiles_.emplace(GuestPath, GuestFile); }
第 2 引数に与えたバッファの内容をもつ GuestFile_t オブジェクト (仮想ファイル) が作成され、FsTableHandle_t クラスのインスタンスに紐づけられています。この API を InsertTestcase で呼び出し、wtf が生成したテストケースを保持するバッファを第 2 引数で渡すと、テストケースの内容をデータにもつ仮想ファイルを作成できます。ターゲットが実行中にアクセスするファイルをこのように作成しておくと、ターゲットに対するファイル経由の入力も可能となります。
クラッシュ検知
ハーネスの作成 で述べた通り、wtf では、ターゲットのクラッシュを検知する仕組みをハーネス内で実装しなければなりません。クラッシュの定義や検知方法をゼロから考えるのは大変ですが、wtf では、ユーザーモードプログラムにおける汎用的なクラッシュ検知用のフックを一括で設定できる API が用意されています。
ハーネス内で /src/wtf/crash_detection_umode.h を include し、初期化関数内で SetupUsermodeCrashDetectionHooks を呼ぶと、クラッシュ検知フックが有効になります。具体的には、以下に示す関数の呼び出しがフックされ、クラッシュと判定されます。
nt!KeBugCheck2ntdll!RtlDispatchExceptionnt!KiFastFailDispatchnt!KiProcessControlProtectionverifier!VerifierStopMessage
おわりに
本記事では、オープンソースのファジングツールである wtf を紹介しました。ファジングとは何か、wtf はどのようなファジングツールなのか、といった話に触れたあと、必要に応じてその内部を掘り下げながら、使い方を見てきました。
スナップショットの取得やハーネスの作成が必要となるなど、wtf はファジングツールの中でも使いこなすのが難しい部類に入ります。一方で非常に柔軟性が高く、習熟すれば Windows 上で動く任意のプログラムを自由自在にファジングできるポテンシャルを秘めています。また、発展的な内容のため本記事では言及しませんでしたが、異なるマシン上で server node と複数の fuzzing node を動かし、分散環境でファジングを行うこともできます。本記事を読んで興味を持たれた方は、ぜひ実際にご自身で wtf を使ったファジングを試してみてください。
エンジニア募集
FFRIセキュリティではサイバーセキュリティに関する興味関心を持つエンジニアを募集しています。 採用に関しては 採用ページ をご覧ください。
*1: 一般には、何らかの「網羅率」を指す言葉です。ソフトウェアテストの文脈では、テストを評価する定量的指標として、ラインカバレッジやブランチカバレッジといった、コードに関するカバレッジがしばしば登場します。 ↩
*2: コードカバレッジの一種です。プログラムにおける処理の流れはしばしば制御フローグラフで表されますが、グラフにおける辺 (エッジ) の網羅率を、エッジカバレッジといいます。 ↩
*3: ファジングでは多様な入力をターゲットに与えますが、与えた入力によりターゲットの状態が変化するかもしれません。ターゲットの状態をリセットせず複数の入力を連続して与えていると、クラッシュした際に、どの入力が原因だったのかが分かりづらくなってしまいます。 ↩
*4: プログラムの実行中に独自の処理を割り込ませる仕組み全般を指す言葉です。 ↩
