FormatFuzzerは、バイナリ入力を高効率かつ高品質に生成・解析するためのフレームワークです。
バイナリ入力の形式を記述したバイナリテンプレートを受け取り、指定されたバイナリ形式を生成・解析する実行可能ファイルを生成します。
例えば、GIF用のバイナリテンプレートから、FormatFuzzerはGIFジェネレータ(別名GIFファザー)を生成します。
FormatFuzzerが生成するジェネレータは非常に効率的で、毎秒数千もの有効なテスト入力を生成します。これはミューテーションベースのファザーとは対照的で、ミューテーションベースでは入力の大部分が無効です。FormatFuzzerが生成する入力は、テスト対象プログラム(あるいは実際には任意のプログラム)とは独立しているため、ブラックボックス設定でも使用できます。さらに、FormatFuzzerはAFL++と統合して、最大カバレッジを目指す有効な入力を生成することもできます。私たちの実験では、この「二つの世界のベスト」アプローチが他のすべての設定を上回りました。詳細は私たちの論文をご覧ください。
FormatFuzzerが使用するバイナリテンプレートは、010 editorから提供されています。
170以上のバイナリテンプレートがあり、FormatFuzzerで直接使用するか、またはその用途に合わせて調整することができます。すぐに使える状態で、FormatFuzzerはAVI、BMP、GIF、JPG、MIDI、MP3、MP4、PCAP、PNG、WAV、ZIPなどの形式を生成します。そして、毎週このリストを拡張し続けています。
コントリビューターを歓迎します! FormatFuzzerプロジェクトページにアクセスして、アイデアや問題を報告したり、プルリクエストを追加してください。FormatFuzzerの動作方法や比較については、私たちの論文をご覧ください。
FormatFuzzerは、FormatFuzzerプロジェクトページから入手できます。最新リリースはリリースページからダウンロードして解凍できます。
最新かつ最高のものをお求めの場合は、Gitリポジトリをクローンすることもできます:
git clone https://github.com/uds-se/FormatFuzzer.git
以降の操作はすべてメインフォルダで行います:
cd FormatFuzzer
FormatFuzzerを実行するには、以下が必要です:
getopt_long())を備えたC++コンパイラ(clangやgccなど)py010parser、six、intervaltreezlibライブラリ(圧縮関数用)boostライブラリ(チェックサム関数用)ビルドおよび設定スクリプト(.ac および .am ファイル)を編集する場合は、以下も必要です:
sudo apt install git g++ make automake python3-full zlib1g-dev libboost-dev
python3 -m venv ~/fuzz
source ~/fuzz/bin/activate
pip3 install py010parser six intervaltree
xcode-select --install
brew install python3 automake boost
pip3 install py010parser six intervaltree
すべてのシステムで、pipを使用:
pip install py010parser
pip install six
pip install intervaltree
注意:すべてのビルドコマンドは、このREADMEファイルと同じフォルダにいる必要があります。このフォルダの外でファザーをビルドすることはまだサポートされていません。
すべての構築手順を自動化するbuild.shスクリプトがあります。
単に以下を実行してください:
./build.sh gif
これでGIFファザーが作成されます。
これはtemplates/内にあるすべてのファイル形式で機能します。templates/FOO.btというファイルがあれば、./build.sh FOOでファザーがビルドされます。
すべての構築手順を自動化するMakefile(ソースはMakefile.am)があります。
(GNU makeが必要です。)
最初に以下を実行します:
touch configure Makefile.in
次に:
./configure
そして:
make gif-fuzzer
これでGIFファザーが作成されます。
これはtemplates/内にあるすべてのファイル形式で機能します。templates/FOO.btというファイルがあれば、make FOO-fuzzerでファザーがビルドされます。
上記のmake方法が機能しない場合、またはより細かい制御が必要な場合は、手動で進める必要があるかもしれません。
ffcompileコンパイラを実行して、バイナリテンプレートをC++コードにコンパイルします。2つの引数を取ります:.btバイナリテンプレートと、生成される.cpp C++ファイル。
./ffcompile templates/gif.bt gif.cpp
以下のコマンドを使用して、ファザーgif-fuzzerを作成します。
まず、汎用コマンドラインドライバをコンパイルします:
g++ -c -I . -std=c++17 -g -O3 -Wall fuzzer.cpp
(-I .はbt.hファイルの場所を示します。-std=c++17はC++標準を設定します。)
次に、バイナリパーサー/コンパイラをコンパイルします:
g++ -c -I . -std=c++17 -g -O3 -Wall gif.cpp
最後に、バイナリパーサー/コンパイラをコマンドラインドライバとリンクして実行可能ファイルを取得します。追加のライブラリ(-lzなど)を使用する場合は、ここでそれらも指定してください。
g++ -O3 gif.o fuzzer.o -o gif-fuzzer -lz
FormatFuzzerは、特定の形式のスタンドアロンパーサー、ジェネレーター、またはミューテーターとして実行できます。 さらに、汎用ファザー(AFL++など)から呼び出して、それらの形式固有の機能をファジングプロセスに統合することもできます(下記のAFL++統合のセクションを参照)。
生成されたファザーは、最初の引数としてコマンドを取り、その後にそのコマンドのオプションと引数が続きます。
最も重要なコマンドはfuzzで、出力を生成します。その引数は、適切な形式で生成されるファイルです。
ジェネレーターを次のように実行します:
./gif-fuzzer fuzz output.gif
これにより、ランダムなバイナリファイルoutput.gifが作成されます。あるいは:
./gif-fuzzer fuzz out1.gif out2.gif out3.gif
これにより、3つのGIFファイルout1.gif、out2.gif、out3.gifが作成されます。
提供されているgif.btテンプレートは、有効なファイルの生成を容易にするための特別な関数で拡張されています。元の.btテンプレートファイルをそのまま使用する場合、生成中に警告が表示されたり、無効なファイルが作成されたりする可能性があります。
parseコマンドを使用して、ファザーをバイナリファイルのパーサーとして実行することもできます。これは、バイナリテンプレートの正確性をテストしたり、入力をミューテーションしたい場合に便利です(下記の「決定ファイル」を参照)。
パーサーを実行するには、次を使用します:
./gif-fuzzer parse input.gif
input.gifが正常に解析できない場合は、エラーメッセージが表示されます。
解析中に、すべての解析決定(つまり、どの解析代替が選択されたか)を決定ファイルに保存することもできます。これは、行われた決定を列挙するバイトのシーケンスです。
各バイトは1つの解析決定を表します。バイト値0は最初の代替が選択されたことを意味し、バイト値1は2番目の代替が選択されたことを意味し、以下同様です。
入力を解析するときに、そのような決定ファイルを生成できます:
./gif-fuzzer parse --decisions input.dec input.gif
ここで、input.decはinput.gifを解析するために行われた決定を保存します。
また、生成時にもそのような決定ファイルを使用できます。ファザーは、解析時に見つかったものとまったく同じ決定を下します。次のコマンドは、input.gifの解析中に決定された決定を使用して新しいGIFファイルを生成します:
./gif-fuzzer fuzz --decisions input.dec input2.gif
すべてがうまくいけば、両方のファイルは同一になるはずです:
cmp input.gif input2.gif
決定ファイルをミューテーション(例えば、個々のバイトを置き換える)することで、解析された元のファイルと類似した入力を作成できます。これは、特定のテスト戦略やAFLなどのファザーと連携するのに便利です。gif-fuzzerなどをトランスレーターとして使用し、決定ファイルをバイナリファイルに変換したり、その逆を行ったりできます。AFLは決定ファイルをミューテーションし、テスト対象プログラムは変換されたバイナリファイルで実行されます。バイナリファイルを直接ミューテーションする(AFLが通常行う方法)のとは対照的に、これにより常に有効な入力が得られるという利点があり、カバレッジに向けてより迅速に進むことができます。
gif-fuzzerのような形式固有のファザーに加えて、FormatFuzzerは形式固有の共有ライブラリ(gif.soなど)にコンパイルすることもできます(その場合は、単に./build.sh gifまたはmake gif.soを実行してください)。
これらの共有ライブラリは、AFL++などの汎用ファザーによってロードできます。
AFL++をFormatFuzzerと一緒に実行するには、私たちの修正版AFL++の指示に従ってください。 以下のようなさまざまなファジング戦略をサポートしています:
AFL+FFMut: FormatFuzzerを使用して形式固有のスマートミューテーションを提供しながらAFL++を実行します。
AFL+FFGen: FormatFuzzerを形式固有のジェネレーターとして使用し、AFL++がその決定シードをミューテーションします。
独自の.btバイナリテンプレートを作成して(これにより、その形式の高効率ファザー/パーサーを作成します)、010 Editorマニュアルのテンプレートとスクリプトの概要のセクションをお読みください。
多くの場合、探している形式(または類似の形式)のテンプレートがすでに存在する可能性があります。010 editorのバイナリテンプレートコレクションをチェックして、使用できるものやベースにできるものがあるかどうかを確認してください。
リポジトリに提供されている.btファイルは、一般的にファイルの解析を対象としています。生成にも使用できますが、多くの場合、入力のどの部分が必要かについての正確な情報が不足しています。
このセクションでは、.btファイルをFormatFuzzerでうまく動作するようにカスタマイズする方法のいくつかを説明します。
たとえば、GIF形式の場合、ファイルtemplates/gif-orig.btは元のバイナリテンプレートを示しており、解析のみを目的として設計されています。一方、templates/gif.btは、有効なGIFを生成できるように修正されたバージョンです。2つのファイルを比較すると、これを達成するために必要な変更はごくわずかであることがわかります。
make gif-fuzzerを実行するか、ffcompileツールを使用してgif-fuzzerを作成した場合、GIFジェネレーターとパーサーの実装を含むC++ファイルgif.cppがすでに取得されています。これは、バイナリテンプレートに加えた変更がどのように実行可能コードに変換されるかを確認するのに役立ちます。C++コードの詳細については、次のセクションで説明します。
GIFバイナリテンプレートは、実際に構造体フィールドに解析する前に、ファイル内の次のバイトの値を先読みするために、先読み関数ReadUByte()とReadUShort()を使用します。生成時には、これらの関数が追加の引数を受け取り、先読みするバイトに選択する既知の適切な値のセットを指定できるようにしています。さらに、ReadUByte()などの特定の先読み関数を呼び出すときに常に使用する、既知の適切な値のグローバルセットを指定することもできます。これらはReadUByteInitValuesベクターに保存されます。
デフォルトでは、私たちの変換手順ffcompileは、先読みバイトとの比較に使用された興味深い値をマイニングし、それらを既知の値のグローバルセットとして使用しようとします。./ffcompile templates/gif.bt gif.cppを実行すると、識別された先読み関数とマイニングされた興味深い値が表示されます:
Finished creating cpp generator.
Lookahead functions found:
ReadUByte
ReadUShort
Mined interesting values:
GlobalColorTableFlag: ['1']
LocalColorTableFlag: ['1']
ReadUByte: ['0x3B', '0x2C']
ReadUShort: ['0xF921', '0xFE21', '0x0121', '0xFF21']
Signature: ['"GIF"']