
指向型グレイボックスファジング(DGF)(例:AFLGo)は、事前に選択された潜在的に脆弱なターゲット位置に対してストレステストを実行することを目的としており、さまざまなセキュリティコンテキストに応用されます:(1)バグ再現、(2)パッチテスト、または(3)静的解析レポートの検証。最近では、指向型ファジングの有効性と効率を向上させた研究がさらに行われています(awesome-directed-fuzzing を参照)。
我々は UAFuzz を提案します。これは、Use-After-Free(UAF)バグに特化したバイナリレベルの指向型ファザーであり、指向型ファジングの主要コンポーネントをこのバグクラスの特定の特性に合わせて慎重に調整しています。UAFバグは、ヒープ要素が解放された後に使用されると発生します。UAFバグの検出は困難です:(1)複雑さ:Proof-of-Concept(PoC)入力が同じメモリ位置で割り当て(alloc)、解放(free)、使用(use)の3つのイベントのシーケンスをトリガーする必要があり、テスト対象プログラムの複数の関数にまたがるため。(2)静寂性:セグメンテーションフォールトが発生しない。
全体的に、UAFuzzは指向型ファザーと同様のワークフローを持ち、下図に示すようにファジングプロセス全体にわたって我々の変更箇所がオレンジ色で強調されています。我々は(1)バグ再現と(2)パッチテストアプリケーションに焦点を当てているため、すべてのメモリ関連UAFイベントの*(ほぼ)完全なスタックトレースを持っている可能性が高くなります。既存の一般的な指向型アプローチではターゲットを独立して選択できるのとは異なり、我々はターゲット間の関係*(例:UAFにとって不可欠な順序)を考慮して指向性を向上させます。まず、UAFuzzの静的プリコンピュテーションはバイナリレベルで高速です。次に、実行時にファザーをターゲットに導く新しい順序認識入力メトリクスを導入します。最後に、期待されるトレース内のすべてのターゲットをカバーする潜在的な入力のみをトリアージし、バグをトリガーする可能性が低い解放(free)入力の事前フィルタリングを行います。
詳細は、RAID'20での論文と、Black Hat USA'20での講演をご覧ください。また、Sébastien Bardin、Matthieu Lemerre、Roland Groz教授、そして特にRichard Bonichon(@rbonichon)氏のOcamlに関するご助力に感謝します。
テスト環境はUbuntu 16.04 64-bitです。
# OPAMを介してBINSEC用のOcamlと前提パッケージをインストール
sudo apt update
sudo apt update
sudo apt install ocaml ocaml-native-compilers camlp4-extra opam emacs llvm-6.0-dev pkg-config protobuf-compiler libgmp-dev libzmq3-dev cmake valgrind
opam init
opam switch 4.05.0
opam depext conf-m4.1
opam install merlin ocp-indent caml-mode tuareg menhir ocamlgraph ocamlfind piqi zmq.5.0.0 zarith llvm.6.0.0
eval `opam config env`
# Pythonパッケージのインストール(IDAスクリプト用のPython 2)
sudo python -m pip install networkx pydot
sudo apt install graphviz
# Graph Easyのインストール
wget https://cpan.metacpan.org/authors/id/S/SH/SHLOMIF/Graph-Easy-0.76.tar.gz
tar xzf Graph-Easy-0.76.tar.gz
cd Graph-Easy-0.76
perl Makefile.PL; make test; sudo make install
export GRAPH_EASY_PATH=/usr/local/bin/graph-easy
# ソースコードのチェックアウト
git clone https://github.com/strongcourage/uafuzz.git
# 環境変数
export IDA_PATH = /path/to/ida-6.9/idaq
export GRAPH_EASY_PATH=/path/to/graph-easy
cd uafuzz; export UAFUZZ_PATH=`pwd`
# ソースコードのコンパイル
./scripts/build.sh uafuzz
# IDA/UAFuzzインターフェースのヘルプ
./binsec/src/binsec -ida-help
./binsec/src/binsec -uafuzz-help
我々のファザーは、ファジング用にQEMUモードのAFL v2.52bを基盤とし、軽量静的解析にBINSECを使用しています(uafuzz/README.mdを参照)。現在、IDA Pro v6.9を使用してテスト対象バイナリの制御フローグラフ(CFG)とコールグラフを抽出しています(ida/README.mdを参照)。
uafuzz
├── binsec/src
│ └── ida: IDAのCFGとコールグラフをインポート・処理するプラグイン
│ └── uafuzz: ファジングコード
│ │ └── afl-2.52b: AFL-QEMU上に構築されたコアファジング
│ │ └── uafuzz_*.ml(i): 静的情報を計算しAFL-QEMUと通信するプラグイン
└── scripts: ビルドとバグトリアージ用のスクリプト
まず、単純なUAFバグを考えます。AFL-QEMUや、ソースレベルでターゲットを指定した指向型ファザーAFLGoでさえ、6時間以内にこのバグを検出できませんが、UAFuzzはValgrindのUAFレポートの助けを借りて数分以内に検出できます。
# AFL-QEMUの実行
$UAFUZZ_PATH/tests/example.sh aflqemu 360
# ソースレベルでターゲットを指定したAFLGoの実行
$UAFUZZ_PATH/tests/example.sh aflgo 360
# UAFuzzの実行
$UAFUZZ_PATH/tests/example.sh uafuzz 360 $UAFUZZ_PATH/tests/example/example.valgrind
実世界のプログラムについては、評価にUAF Fuzzing Benchmarkを使用しています。
# ベンチマークのチェックアウト
git clone https://github.com/strongcourage/uafbench.git
cd uafbench; export UAFBENCH_PATH=`pwd`
readelf(Binutils)のCVE-2018-20623を例に、UAFuzzをバグ再現アプリケーションで実行する方法を詳しく示します。Valgrindで得られたこのUAFバグのスタックトレースは以下の通りです。
// 不正使用のスタックトレース
==5358== Invalid read of size 1
==5358== at 0x40A9393: vfprintf (vfprintf.c:1632)
==5358== by 0x40A9680: buffered_vfprintf (vfprintf.c:2320)
==5358== by 0x40A72E0: vfprintf (vfprintf.c:1293)
[6] ==5358== by 0x80AB881: error (elfcomm.c:43)
[5] ==5358== by 0x8086217: process_archive (readelf.c:19409)
[1] ==5358== by 0x80868EA: process_file (readelf.c:19588)
[0] ==5358== by 0x8086B01: main (readelf.c:19664)
// 解放のスタックトレース
==5358== Address 0x4221dc0 is 0 bytes inside a block of size 80 free'd
==5358== at 0x402D358: free (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
[4] ==5358== by 0x8086647: process_archive (readelf.c:19524)
[1] ==5358== by 0x80868EA: process_file (readelf.c:19588)
[0] ==5358== by 0x8086B01: main (readelf.c:19664)
// 割り当てのスタックトレース
==5358== Block was alloc'd at
==5358== at 0x402C17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
[3] ==5358== by 0x80AD97E: make_qualified_name (elfcomm.c:906)
[2] ==5358== by 0x8086350: process_archive (readelf.c:19435)
[1] ==5358== by 0x80868EA: process_file (readelf.c:19588)
[0] ==5358== by 0x8086B01: main (readelf.c:19664)
前処理スクリプトは、x86のテスト対象バイナリとValgrindのスタックトレースを入力として受け取り、以下のように(basic_block_address,function_name)形式のターゲット位置のシーケンスであるUAFバグトレースを生成します。
[0] (0x8086ae1,main) -> [1] (0x80868de,process_file) -> [2] (0x808632c,process_archive) ->
[3, alloc] (0x80ad974,make_qualified_name) -> [4, free] (0x808663a,process_archive) ->
[5] (0x808620b,process_archive) -> [6, use] (0x80ab86a,error)
いくつかの入力パラメータ(例:実行するファザー、タイムアウト(分)、事前定義されたターゲット(例:バグレポートから抽出))を指定するファジングスクリプトのテンプレートを提供します。上記の例では、スクリプトCVE-2018-20623.shを使用してUAFuzzを次のように実行します。
# タイムアウト60分でUAFuzzを実行
$UAFBENCH_PATH/CVE-2018-20623.sh uafuzz 60 $UAFBENCH_PATH/valgrind/CVE-2018-20623.valgrind
ファジングのタイムアウト後、UAFuzzは期待されるUAFバグトレースのすべてのターゲット位置を順にカバーする入力を識別できます(例:',all'で終わる入力名)。したがって、UAFuzzは、ValgrindやAddressSanitizerなどの既存のプロファイリングツールを使用して、目的のバグをトリガーする可能性が高いこれらの種類の入力のみをトリアージします。
GNU PatchのCVE-2018-6952を使用して、修復プロセスを促進するために異なる一意のバグトリガー入力を生成することの重要性を説明します。GNU Patchにはダブルフリーが存在し、開発者によって修正されました(コミット 9c98635)。しかし、CVE-2018-6952のスタックトレースを使用することで、UAFuzzは最新バージョン2.7.6(コミット 76e7758)の不完全なバグ修正 CVE-2019-20633を発見しました。バグトレースに若干の違いがあります。全体的なプロセスはバグ再現アプリケーションと似ていますが、ターゲットとなるUAFバグトレースの特定に手作業が必要な場合があります。既存のバグのPoC入力とfuzzing-corpusの有効なファイルを高品質なシードとして使用します。
# CVE-2018-6952のパッチ適用バージョンをファジング
$UAFBENCH_PATH/CVE-2019-20633.sh uafuzz 360 $UAFBENCH_PATH/valgrind/CVE-2018-6952.valgrind
可能なハイブリッドアプローチとして、UAFuzzとGUEBを組み合わせることが考えられます。GUEBは、UAF向けのOcamlで書かれた唯一のバイナリレベル静的解析ツールです。ただし、GUEBは多くの偽陽性を生成し、現在複雑なバイナリでは正常に動作できません。そのため、現在GUEBをBINSECに改良・統合し、GUEBのレポートから抽出したターゲットを使用してUAFuzzをガイドしています。続報をお待ちください!