はじめに
IPAのファジング実践資料(AFL編)を参考に、AFLでファジングしてみました。
AFLとは
AFL (American Fuzzy Lop) は、最も著名なファジングツールの1つです。
やってみた
環境構築
仮想サーバ起動
仮想サーバ上でファジングをやりましょう。
Ubuntuの仮想サーバを起動します。
$ git clone git@github.com:kannkyo/boilerplate-vagrant.git
$ cd vagrant-sandbox/ubuntu-focal64-20201117.0.0/
$ vagrant up
$ vagrant ssh
AFL インストール
Ubuntuのオフィシャルレポジトリは、AFL派生版のafl++
を提供しています。
apt-get
で簡単にインストールできるのでafl++
をインストールしましょう。
$ sudo apt update
$ sudo apt install afl++ afl++-clang afl++-doc
RAMディスク作成
afl
の開発コミュニティは、RAMディスク上でafl
を実行することを推奨しています。
afl
はファイルI/Oが多く、HDDの寿命を縮める恐れが有ります。
RAMディスク /tmp/afl-ramdisk/
を作成しましょう。
$ mkdir -p /tmp/afl-ramdisk && chmod 777 /tmp/afl-ramdisk/
$ sudo mount -t tmpfs -o size=512M tmpfs /tmp/afl-ramdisk/
$ cd /tmp/afl-ramdisk/
ソースコードの準備
まず、ファジングの対象とするソースコードを作成します。
$ cat <<EOF >example.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char** argv)
{
char buf[8];
if(read(0, buf, 8) < 1)
{
exit(1);
}
printf(buf);
exit(0);
}
EOF
標準入力から文字列を読み取るプログラムです。
コンパイル
afl-gcc
を使って、ソースコードをコンパイルします。
afl-gcc
はgcc
のラッパーです。
コード内に測定用コードを埋め込んだあとにコンパイルします。
測定コードは、ファジングにより発見された不具合の発生箇所の特定、カバレッジの測定などを可能にします。
afl-gccのマニュアルを確認します。
$ afl-gcc
afl-cc++2.59d by Michal Zalewski
[!] NOTE: afl-gcc is deprecated, llvm_mode is much faster and has more options
This is a helper application for afl-fuzz. It serves as a drop-in replacement
for gcc or clang, letting you recompile third-party code with the required
runtime instrumentation. A common use pattern would be one of the following:
CC=/usr/bin/afl-gcc ./configure
CXX=/usr/bin/afl-g++ ./configure
You can specify custom next-stage toolchain via AFL_CC, AFL_CXX, and AFL_AS.
Setting AFL_HARDEN enables hardening optimizations in the compiled code.
afl-gcc
は廃止deprecated
されていて、中身はafl+cc++
に書き換わっているようです。
コンパイルを実行します。
$ afl-gcc -o example example.c
afl-cc++2.59d by Michal Zalewski
[!] NOTE: afl-gcc is deprecated, llvm_mode is much faster and has more options
example.c: In function ‘main’:
example.c:14:10: warning: format not a string literal and no format arguments [-Wformat-security]
14 | printf(buf);
| ^~~
afl-as++2.59d by Michal Zalewski
[+] Instrumented 2 locations (64-bit, non-hardened mode, ratio 100%).
ファジング
afl-fuzz
コマンドを使って、ファジングを実行します。
afl-fuzz
のマニュアルを確認します。
$ afl-fuzz
afl-fuzz++2.59d based on afl by Michal Zalewski and a big online community
afl-fuzz [ options ] -- /path/to/fuzzed_app [ ... ]
Required parameters:
-i dir - input directory with test cases
-o dir - output directory for fuzzer findings
Execution control settings:
-p schedule - power schedules recompute a seed's performance score.
<explore (default), fast, coe, lin, quad, or exploit>
see docs/power_schedules.txt
-f file - location read by the fuzzed program (stdin)
-t msec - timeout for each run (auto-scaled, 50-1000 ms)
-m megs - memory limit for child process (50 MB)
-Q - use binary-only instrumentation (QEMU mode)
-U - use unicorn-based instrumentation (Unicorn mode)
-W - use qemu-based instrumentation with Wine (Wine mode)
Mutator settings:
-R[R] - add Radamsa as mutator, add another -R to exclusivly run it
-L minutes - use MOpt(imize) mode and set the limit time for entering the
pacemaker mode (minutes of no new paths, 0 = immediately).
a recommended value is 10-60. see docs/README.MOpt
Fuzzing behavior settings:
-N - do not unlink the fuzzing input file
-d - quick & dirty mode (skips deterministic steps)
-n - fuzz without instrumentation (dumb mode)
-x dir - optional fuzzer dictionary (see README, its really good!)
Testing settings:
-s seed - use a fixed seed for the RNG
-V seconds - fuzz for a maximum total time of seconds then terminate
-E execs - fuzz for a maximum number of total executions then terminate
Note: -V/-E are not precise, they are checked after a queue entry is done
which can be many minutes/execs later
Other stuff:
-T text - text banner to show on the screen
-M / -S id - distributed mode (see parallel_fuzzing.txt)
-I command - execute this command/script when a new crash is found
-B bitmap.txt - mutate a specific test case, use the out/fuzz_bitmap file
-C - crash exploration mode (the peruvian rabbit thing)
-e ext - File extension for the temporarily generated test case
For additional tips, please consult /usr/share/doc/afl++-doc/README
次に、ファジング用のサンプル・テストケースをダウンロードして、ファジングを実行します。
$ wget -O - https://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz | tar zxvf -
$ afl-fuzz -i afl-2.52b/testcases/others/text/ -o out/ ./example
afl-fuzz++2.59d based on afl by Michal Zalewski and a big online community
[+] afl++ is maintained by Marc "van Hauser" Heuse, Heiko "hexcoder" Eissfeldt and Andrea Fioraldi
[+] afl++ is open source, get it at https://github.com/vanhauser-thc/AFLplusplus
[+] Power schedules from github.com/mboehme/aflfast
[+] Python Mutator and llvm_mode whitelisting from github.com/choller/afl
[+] afl-tmin fork server patch from github.com/nccgroup/TriforceAFL
[+] MOpt Mutator from github.com/puppet-meteor/MOpt-AFL
[*] Getting to work...
[+] Using exploration-based constant power schedule (EXPLORE)
[+] You have 1 CPU core and 1 runnable tasks (utilization: 100%).
[*] Checking core_pattern...
[!] WARNING: Could not check CPU scaling governor
[*] Setting up output directories...
[+] Output directory exists but deemed OK to reuse.
[*] Deleting old session data...
[+] Output dir cleanup successful.
[*] Scanning 'afl-2.52b/testcases/others/text/'...
[+] No auto-generated dictionary tokens to reuse.
[*] Creating hard links for all input files...
[*] Validating target binary...
[*] Attempting dry run with 'id:000000,time:0,orig:hello_world.txt'...
[*] Spinning up the fork server...
[+] All right - fork server is up.
len = 6, map size = 2, exec speed = 242 us
[+] All test cases processed.
[+] Here are some useful stats:
Test case count : 1 favored, 0 variable, 1 total
Bitmap range : 2 to 2 bits (average: 2.00 bits)
Exec timing : 242 to 242 us (average: 242 us)
[*] No -t option specified, so I'll use exec timeout of 20 ms.
[+] All set and ready to roll!
american fuzzy lop ++2.59d (example) [explore] {-1}
┌─ process timing ────────────────────────────────────┬─ overall results ────┐
│ run time : 0 days, 0 hrs, 4 min, 5 sec │ cycles done : 4789 │
│ last new path : none yet (odd, check syntax!) │ total paths : 1 │
│ last uniq crash : 0 days, 0 hrs, 4 min, 3 sec │ uniq crashes : 1 │
│ last uniq hang : none seen yet │ uniq hangs : 0 │
├─ cycle progress ───────────────────┬─ map coverage ─┴──────────────────────┤
│ now processing : 0.4789 (0.0%) │ map density : 0.00% / 0.00% │
│ paths timed out : 0 (0.00%) │ count coverage : 1.00 bits/tuple │
├─ stage progress ───────────────────┼─ findings in depth ───────────────────┤
│ now trying : havoc │ favored paths : 1 (100.00%) │
│ stage execs : 255/256 (99.61%) │ new edges on : 1 (100.00%) │
│ total execs : 1.23M │ total crashes : 31 (1 unique) │
│ exec speed : 5054/sec │ total tmouts : 0 (0 unique) │
├─ fuzzing strategy yields ──────────┴───────────────┬─ path geometry ───────┤
│ bit flips : 0/32, 0/31, 0/29 │ levels : 1 │
│ byte flips : 0/4, 0/3, 0/1 │ pending : 0 │
│ arithmetics : 0/224, 0/0, 0/0 │ pend fav : 0 │
│ known ints : 0/23, 0/84, 0/44 │ own finds : 0 │
│ dictionary : 0/0, 0/0, 0/0 │ imported : n/a │
│ havoc/rad : 1/1.23M, 0/0, 0/0 │ stability : 100.00% │
│ py/custom : 0/0, 0/0 ├───────────────────────┘
│ trim : 33.33%/1, 0.00% │ [cpu:199%]
└────────────────────────────────────────────────────┘^C
+++ Testing aborted by user +++
[+] We're done here. Have a nice day!
ファジング実行中は実行状況のサマリーがカラフルに表示されます。
結果の確認
ファジングの結果を確認します。
まず、出力ファイルを確認します。
$ ll out/
total 80
drwx------ 5 root root 200 Aug 30 15:30 ./
drwxrwxrwt 4 root root 140 Aug 30 15:29 ../
-rw------- 1 root root 2 Aug 30 15:34 .cur_input
-rw------- 1 root root 10 Aug 30 15:30 cmdline
drwx------ 2 root root 80 Aug 30 15:30 crashes/
-rw------- 1 root root 65536 Aug 30 15:30 fuzz_bitmap
-rw------- 1 root root 802 Aug 30 15:34 fuzzer_stats
drwx------ 2 root root 40 Aug 30 15:30 hangs/
-rw------- 1 root root 2835 Aug 30 15:34 plot_data
drwx------ 3 root root 80 Aug 30 15:30 queue/
$ ll out/crashes/
total 8
drwx------ 2 root root 80 Aug 30 15:30 ./
drwx------ 5 root root 200 Aug 30 15:30 ../
-rw------- 1 root root 586 Aug 30 15:30 README.txt
-rw------- 1 root root 6 Aug 30 15:30 id:000000,sig:11,src:000000,time:2487,op:havoc,rep:32
$ hexdump -C out/crashes/id\:000000\,sig\:11\,src\:000000\,time\:2487\,op\:havoc\,rep\:32
00000000 40 c0 72 40 25 53 |@.r@%S|
00000006
out/
フォルダに出力結果のファイルが格納されているようですが、バイナリファイルなどもあってよくわかりません。
afl-plot
コマンドで出力結果を可視化してみます。
まず、afl-plot
に必要なgnuplot
をインストールします。
$ sudo apt install gnuplot -y
次に、コマンドを実行します。
$ afl-plot out/ graph/
graph/
ファルダには下図のようなグラフの画像が格納されています。
1つ目のグラフは、時系列で実行パス数などをプロットしたグラフです。
プロットされる項目は以下の5つです。
- total paths:トータルパス
- current path:現在のパス
- pending paths:ペンディングされているパス
- pending favs:ペンディングされているfavs
- cycles done:サイクル終了数
2つ目のグラフは、時系列で発見された不具合を種類別にプロットしたグラフです。
以下の3つの項目がプロットされます。
- uniq crashes:一意なクラッシュ数
- uniq hangs:一意なハング数
- levels:レベル
3つ目のグラフは、時系列で実行速度が表示されるグラフです。
以下の1つの項目がプロットされます。
- execs/sec:1秒あたりの実行数