DIE1は、SSLabによって開発されたJavaScriptエンジン用のファザーです。ファジングでJavaScriptエンジンを扱う際には次のような問題点が挙げられます。
- 文法的に正しいJavaScriptを生成する必要がある。
- なるべく実行時エラーを起こさないJavaScriptを生成したい。
- 従来のファザーは1文ごとにtry-catchで囲むことにより実行時エラーを防いでいたが、try-catchを含む関数はJITコンパイラにより最適化されないため、JITのバグが見つかりにくいという問題がある。
このような問題に対応したState-of-the-Artのファザーとしてはfuzzilliなどが挙げられます。DIEも同様にこれらの問題を解決しているのですが、加えてaspect-preservingという特徴を持ちます。
DIEは「過去に見つかったバグに近いバグ」を優先的に見つけることを目的としています。そのために、既存のテストケースの関数呼び出しやfor文といった、基本的なスクリプトの構造を維持した状態でミューテーションします。このようなミューテーションをaspect-preservingと呼び、これによりJITによる最適化など、元のテストケースが持つ「意図」をミューテーションを通して高い確率で保持できます。
fuzzufでは、外部のミューテーションエンジンを利用する例としてDIEモードが実装されています。
DIEを実行するには以下のツールが必要です。各自でインストールしてください。(Ubuntu 14.04/18.04/20.04でテスト済みです)
- Node.js (2022年1月17日現在で最新のLTS(16.13)を使用した)
- npm (バージョン6以降を推奨)
- node (バージョン10以降を推奨)
- Python 3 (3.6以降を推奨)
fuzzufはDIEを利用するためのスクリプトを提供します。上述の「要件」を満たし、fuzzufを ビルドした 後、次のコマンドを実行するとセットアップが自動で実行されます。
cmake --build build --target die上記コマンド実行後、以下のメッセージが表示されればインストール成功です。次々節の「シードの用意」に進んでください。
[+] DIE successfully setup!
何らかの理由で自動セットアップを選択しない場合、以下の手順に沿って手動でセットアップしてください。
まず、DIEを公式リポジトリからクローンします。
ここでは2022年1月17日現在で最新のコミットIDf1ab180c18ea4096d6c3336a7dc3e00af897549dのDIEを使用しています。
git clone https://github.com/sslab-gatech/DIE/次にパッケージをインストールします。
cd DIE/fuzz/TS
npm installDIEはTypeScriptを利用しているため、次のコマンドを実行しJavaScriptにトランスパイルする必要があります。
node_module/.bin/tscこれでfuzzufでDIEを使う準備は完了です。
DIEはミューテーションに利用する初期シードを必要とします。
シードは拡張子が.jsで終わるファイル名のJavaScriptファイルで、必ず1つ以上のシードを用意してください。DIEのミューテーションの性質上、入力テストケースは過去に見つかったバグのPoCなどを用意することが望ましいです。
なお、DIEの論文で利用されているシードはGitHubで公開されています。
入力ディレクトリ中のJavaScriptファイルは、ファジングループ開始前に計装され、型情報が集められます。型情報は元のファイル名に拡張子.tが加えられた名前で保存され、このファイルが存在する場合、以降ファジングを再実行した際に、そのファイルの型情報収集はスキップされます。(そのため、入力ディレクトリ中のスクリプトのファイル名を同じまま中身を入れ替える場合は、拡張子.tのファイルも削除してください。)
DIEは次のコマンド例の要領で実行できます。例では ./target/d8 をPUTとしてファジングキャンペーンを開始しています。
fuzzuf die --in_dir=input --out_dir=output -- ./target/d8 @@--in_dirで指定するディレクトリには1つ以上のJavaScriptファイルを用意してください。また、検査対象のJavaScriptエンジンはafl-gccでコンパイルしておく必要があります。
あるいは、コンパイル済みのQuickJS実行ファイルがput_binariesディレクトリ以下にあるため、そちらを検査対象とすることも可能です。
基本的なオプションはAFLと同じですが、DIEに指定可能あるいは必須のオプションがいくつか存在します。
--die_dir: クローンしたDIEのディレクトリパスを渡します(デフォルト:tools/die/DIE)--typer:tools/die/typer.pyのパスを渡します(デフォルト:tools/die/typer.py)--node: JavaScriptを実行するコマンド(デフォルト:node)--d8: d8のパス(指定しない場合、PUTがJSエンジンとして利用されます。)--d8_flags:--d8で指定したJSエンジンに渡すフラグ(デフォルト:空)--mut_cnt: 1回のミューテーションで何個のスクリプトを生成するか(デフォルト:100)
DIEのファジングループは、「シードの選択」、「ミューテーション」、「実行」、「カバレッジ情報の収集」のサイクルで表されます。 また、初期入力テストケースに対して計装・実行することで、スクリプト中の変数の型を動的に調べ、ミューテーションの際に利用します。
DIEは1つ以上のJavaScriptファイルを入力として受け取り、それらに変更を加えていくmutationalなファザーです。 DIEでは、実行時エラーを減らすために入力テストケースの型情報を動的に収集しています。 例えば次のようなコードをミューテーションすることを考えます。
let s = "Hello World";
let v = s.split(" ");もしこれを次のようにミューテーションすると、2行目のsplitメソッドの呼び出しで実行時エラーが発生します。
let s = function(v) { return v; };
let v = s.split(" ");このようなミューテーションを防ぐために、DIEでは事前に各変数の型情報を保持します。 まず入力テストケースに対して、オブジェクト定義後とオブジェクト利用前のすべての箇所に、次のように型情報を出力する計装を加えます。
let s = "Hello World";
console.log("1行目,sの型は"+typeof(s));
console.log("2行目,sの型は"+typeof(s));
console.log("2行目,s.splitの型は"+typeof(s.split));
let v = s.split(" ");
console.log("2行目,vの型は"+typeof(v));このように計装したコードを実行することでDIEは動的にスクリプト中の各変数の型情報を収集します。 なお、同じ名前の変数に対してもすべての箇所で型を調べているのは、JavaScriptが動的型付け言語だからです。
DIEは事前に収集した型情報を用いて、なるべく同じ型の変数とのみミューテーションするように設計されています。 同じ箇所でも実行に応じて複数の型を持ち得る変数や、プロパティを持つオブジェクト型などにも対応しています。
DIEのミューテーション戦略は、大きく分けて3つです。
- 型付きASTのミューテーション
- 新しい文の挿入
- 新しい変数の作成
いずれもスクリプトの構造を破壊しない形で実行されます。
ミューテーションの回数や新しい文の挿入の確率などはDIE/fuzz/TS/esfuzz.tsで定義されています。
オリジナルのDIEの実装では、AFLと同様にカバレッジ情報を収集します。 AFLのコードカバレッジが各エッジに対して1バイトのヒットカウントを利用しています。しかし、DIEではヒットカウントを利用せず、各エッジに1ビットのデータを用意し、そこを通ったかどうかのみを記録します。 なぜなら、JavaScriptの場合、例えばループのカウンタを変更するだけでヒットカウントが増加し、そのような変化は意味がないからです。fuzzufではこのようなJaveScriptのファザー向けのカバレッジは今後実装される予定です。
DIEではAFLと同じスケジューリングを利用しています。
DIEではredisサーバーと通信することで、複数台のマシンで分散してファジングが実行できます。この際カバレッジの情報も共有されます。 ただし、この機能はfuzzufにおいては未実装です。
Footnotes
-
S. Park, W. Xu, I. Yun, D. Jang and T. Kim, "Fuzzing JavaScript Engines with Aspect-preserving Mutation," 2020 IEEE Symposium on Security and Privacy (SP), 2020, pp. 1629-1642, doi: 10.1109/SP40000.2020.00067. ↩