fuzz ImageMagick

前言

代码审计存在一定的局限性,一般都是静态的检测,对于复杂的算法来说,逻辑漏洞更加难以发觉,所以需要动态的代码执行技术来进行深层次的漏洞触发

fuzz 的种类

  • Generation Based :通过对目标协议或文件格式建模的方法,从零开始产生测试用例,没有先前的状态
  • Mutation Based :基于一些规则,从已有的数据样本或存在的状态变异而来
  • Evolutionary :包含了上述两种,同时会根据代码覆盖率的回馈进行变异。

AFL

模糊测试(Fuzzing)技术作为漏洞挖掘最有效的手段之一。对整体程序进行Fuzzing

AFL

1,Build:

1
2
3
4
5
6
git clone https://github.com/google/AFL.git ~/afl-src
cd ~/afl-src/qemu_mode
./build_qemu_support.sh
cd .. && make install
#开启崩溃转储,而不是交给崩溃处理程序
echo core >/proc/sys/kernel/core_pattern

2,Build test target

有两种

​ 一种是开源项目,那么直接在源码里面插桩,使用afl-clang-clang++(其实是将编译器编译出来的汇编文件进行插桩,使用了~/afl-src/afl-as,将程序的每个block都加上回调,看下一篇AFL源码解读)

-fsanitize=address 就是开启AddressSanitizer (ASAN)内存检测工具 【参考2】
-g 产生符号,方便调试

1
2
3
4
clang -g -O1 -fsanitize=fuzzer                         mytarget.c # Builds the fuzz target w/o sanitizers
clang -g -O1 -fsanitize=fuzzer,address mytarget.c # Builds the fuzz target with ASAN
clang -g -O1 -fsanitize=fuzzer,signed-integer-overflow mytarget.c # Builds the fuzz target with a part of UBSAN
clang -g -O1 -fsanitize=fuzzer,memory mytarget.c # Builds the fuzz target with MSAN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export LLVM_CONFIG=`which llvm-config-3.5`

# 考虑加上 --disable-shared,静态链接上去,方便模糊测试库,不然afl是不会去分析的
# -fprofile-arcs -ftest-coverage 第9点要用,不然又要编译一次
#-fsanitize-coverage=func for function-level coverage (very fast).
#-fsanitize-coverage=bb for basic-block-level coverage (may add up to 30% extra slowdown).
#-fsanitize-coverage=edge for edge-level coverage (up to 40% slowdown).

./configure --disable-shared CC="afl-clang" CXX="afl-clang++" CFLAGS="-g -fsanitize=fuzzer,address -fprofile-arcs -ftest-coverage" LFLAGS="-static"
# 快速编译
./configure --disable-shared CC="afl-clang-fast" CXX="afl-clang-fast++" CFLAGS="-g -fsanitize=fuzzer,address -fprofile-arcs -ftest-coverage" LFLAGS="-static"

AFL_USE_ASAN=1 make

# 可以切换llvm版本
update-alternatives


#ASAN_OPTIONS, LSAN_OPTIONS, MSAN_OPTIONS or UBSAN_OPTIONS
#可以生成sancov覆盖率文件
ASAN_OPTIONS=coverage=1 ./magick

​ 另一种就是闭源程序,必须要使用QEMU 运行时动态插桩,但是效率低了。方法就是afl-fuzz命令后加上-q

1
2
#不在源程序插桩的编译,且想效率高的方法,使用clang, 比gcc好多了
CC=clang-3.5 CXX=clang++-3.5 LLVM_CONFIG=llvm-config-3.5 make

编译好的程序在 ImageMagick/utilities/.libs/magick
看下依赖情况

1
2
3
4
5
$ ldd ./magick
linux-vdso.so.1 => (0x00007fffaef6e000)
libMagickCore-7.Q16HDRI.so.7 => not found
libMagickWand-7.Q16HDRI.so.7 => not found
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f61ceded000)

把库lnk过来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

#当然了,上面我们用的--disable-shared, 所以不需要了
~/awork/ImageMagick/utilities/ [master*] ldd ./magick
linux-vdso.so.1 => (0x00007fffcc1cc000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f759aa3a000)
libgomp.so.1 => /usr/lib/x86_64-linux-gnu/libgomp.so.1 (0x00007f759a818000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f759a50f000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f759a2f2000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f759a0ea000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f7599ee6000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f7599cd0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7599906000)
/lib64/ld-linux-x86-64.so.2 (0x00007f759ac54000)

#否则
#设置库查找目录,不然ln过来还是找不到
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
ln -s ~/awork/ImageMagick/MagickCore/.libs/libMagickCore-7.Q16HDRI.so.7.0.0 libMagickCore-7.Q16HDRI.so.7
ln -s ~/awork/ImageMagick/MagickWand/.libs/libMagickWand-7.Q16HDRI.so.7.0.0 libMagickWand-7.Q16HDRI.so.7
$ ldd ./magick
linux-vdso.so.1 => (0x00007ffd2ad5d000)
libMagickCore-7.Q16HDRI.so.7 => ./libMagickCore-7.Q16HDRI.so.7 (0x00007f72dfe5d000)
libMagickWand-7.Q16HDRI.so.7 => ./libMagickWand-7.Q16HDRI.so.7 (0x00007f72df788000)

3,开源语料库(影响很大)

AFL给出的建议是最好小于1 KB,但其实可以根据自己测试的程序权衡,这在AFL文档的perf_tips.txt中有具体说明。

4,Extract

测试文件太多建议先筛选,不然太慢了

样本多样性 AFL-CMIN 移除执行相同代码的输入文件

  人工增加样本多样性的方法中,最简单且明显的就是搜集下载样本,放进输入文件夹。这个过程是对样本进行丰富的过程,它非常重要,但这个过程也常常引入样本的冗余,降低fuzz的效率。为了解决这个问题,需要从大量的样本中筛掉无用的样本。

cmin操作的是文件集合,输出的也是文件集合。

可能几千个文件和f一个文件来fuzz都是一样的结果,如果不进行cmin,非常低效。

cmin也是成功使用afl-fuzz中必不可少的一步。

1
2
3
4
5
6
7
#把所有图放一个文件夹samples里面
afl-cmin -i ~/fuzzdata/samples/ -o output_cmin -- ./magick convert @@ /dev/null

找了10分钟错误,看了afl-cmin代码好像是分配空间不足导致,不要怕用 -m none
Error: no instrumentation output detected (perhaps crash or timeout).

[+] Narrowed down to 65 files, saved in 'output_cmin'.

样本复杂度 AFL-TMIN 减小单个输入文件的大小

  基于字长+步长的形式,逐字节删除,然后通过插装反馈得出样本改变是否导致了程序运行路径发生了变化。若没有发生变化,可以认为删去的字节是冗余的,只用于一个指定的文件。为了使每一个test case达到表示与原始测试用例相同的代码路径所需的最小值,afl-tmin遍历test case的实际字节,逐步删除很小的数据块,直到删除任意字节都会影响到代码路径表示。

tmin操作的是单个文件,输出单个文件;

对于有效地fuzzing来说,这都是很重要的步骤,也是需要理解的重要概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# instrumented mode(默认)
afl-tmin -i input_file -o output_file -- /path/to/program [params] @@
# crash mode -x 直接把非返回0的导致程序非正常退出的都作为crash。
afl-tmin -x -i input_file -o output_file -- /path/to/program [params] @@
afl-tmin -x -i output_cmin -o output_tmain -- ./magick convert @@ xx.png


# afl-tmin 接受单个文件输入,如果语料库中文件数量特别多
mkdir -p tmin_output_cmin
#速度太慢
for i in output_cmin/*; do afl-tmin -i $i -o ./tmin_$i -- ./magick convert @@ xx2.png; done;
#建议使用下面的大佬的并发bash脚本
screen ~/afl-src/afl-ptmin 8 ./queue_cmin ./output_tmin/ "./magick convert @@ xx3.png"


#0字节文件考虑:加 -m none
[!] WARNING: Down to zero bytes - check the command line and mem limit!

5,Usage:

fuzz

1
2
3
4
5
6
7
8
#下载fuzz样本集
git clone https://github.com/MozillaSecurity/fuzzdata.git ~/fuzz/fuzzdata
#@@就是拿来代替-i目录的文件路径,相当于以命令行参数方式输入magick。
#-t 防止大型程序执行过慢,被放到 total hangs【无响应】
#-m 内存大小
#/dev/null 不要输出文件
afl-fuzz -t 300000 -m none -i ~/fuzz/fuzzdata/fuzzdata/samples/png -o output -- ./magick convert @@ /dev/null
#没插桩就加上-q, 使用qemu

白盒

执行一个单例, 测试程序的插桩

1
2
3
4
5
6
7
8
9
afl-showmap -m none -o ./output.tuples -- ./magick convert ./jj.png cjj,jpg

*] Executing './magick'...

-- Program output begins --
-- Program output ends --
[+] Captured 3848 tuples in './output.tuples'.

执行程序打印捕获的元组(tuples),衡量衡量程序覆盖情况(分支信息)

黑盒

就是使用QEMU模式,加上-Q

6,加速 SCREEN

还可以这样. 转自【参考4】

image-20200413024247769

多进程

-M 参数指定一个主Fuzzer(Master Fuzzer)
-S 参数指定多个从Fuzzer(Slave Fuzzer)

Master Fuzzer进行确定性测试(deterministic )即对输入文件进行一些特殊而非随机的的变异;
Slave Fuzzer 进行完全随机的变异。

我试了下,3000ms已经足够了

1
2
3
4
5
6
$ screen afl-fuzz -m none -t 3000 -i ./output_tmin -o sync_dir/ -M master -- ./magick convert @@ xx3.png
$ screen afl-fuzz -m none -t 3000 -i ./output_tmin -o sync_dir/ -S slave1 -- ./magick convert @@ xx3.png
$ screen afl-fuzz -m none -t 3000 -i ./output_tmin -o sync_dir/ -S slave2 -- ./magick convert @@ xx3.png
$ screen afl-fuzz -m none -t 3000 -i ./output_tmin -o sync_dir/ -S slave3 -- ./magick convert @@ xx3.png
...
分别ctrl+a+d 挂起状态,放入后台
1
2
3
4
5
6
7
8
9
10
11
#查看所有会话
screen -ls
#进入会话
screen -r session_name
#进入会话
screen -x session_name
#kill
screen -X -S 28006 quit
tmux # starts a new tmux session


1
2
3
4
5
6
在Session下,使用ctrl+a(C-a) 
ctrl-b c # new tab
ctrl-b 0 # switch to tab 0
ctrl-b d # detach
tmux a # re-attach to your previous session
ctrl-b ? # for help

这并不是一个很理想的解决方案,

  因为这样的条件下,运行前期master fuzzer的deterministic进程太慢,而slave随机产生新的样本后,master进程的deterministic变异进程总是来不及处理;而各slave进程重复概率大,需要的同步开销过大,导致afl-fuzz的处理速度并不是线性增长。

  将会在sync_dir文件夹建立master、slave1、slave2三个文件夹,三个 fuzzer各使用一个CPU。在fuzz过程中,各 fuzzer在空闲时可以读取其他文件夹中的新文件,然后对自己的queue文件进行同步和更新。

afl-whatsup 工具可以查看每个fuzzer的运行状态和总体运行概况,加上-s选项只显示概况,其中的数据都是所有fuzzer的总和。

1
afl-whatsup sync_dir

afl-gotcpu工具可以查看每个核心使用状态。

AFL

【参考1】cp“ 可以看到这里的-o指定的是一个同步目录,并行测试中所有的Fuzzer将相互协作,在找到新的代码路径时,相互传递新的测试用例,如下图中以Fuzzer0的角度来看,它查看其它fuzzer的语料库,并通过比较id来同步感兴趣的测试用例。

多系统

没有多系统先8学了

官方详解

大佬方案请看【参考1】最后

7,!!! When to stop and prune !!!

image-20200412161130767

  cycles done 随着周期数不断增大,其颜色也会由洋红色,逐步变为黄色、蓝色、绿色。当其变为绿色时,继续Fuzzing下去也很难有新的发现了。如上所示,fuzz程序发生异常,但是这个crash无论如何都不能触发漏洞,fuzz为什么会把它放过来?

  一旦master fuzzer完成了它的第一个周期,我们可以继续并停止我们的afl-fuzz实例。我们需要合并和最小化每个实例的队列queue,并重新启动fuzzing。当使用多个fuzzing实例运行时,AFL将在根目录的syncdir目录里,根据传给afl-fuzz的参数(fuzzer的名称),为每个fuzzer维护一个独立的、同步目录。每个单独的fuzzer syncdir目录都包含一个队列queue目录,其中包含AFL能够生成的所有导致新的代码路径被检测出来的测试用例。

我们需要合并每个fuzz实例的队列目录,但是因为其中会有很多重叠,我们应该尽量最小化这个新的语料库。

1
2
3
4
5
6
ls sync_dir
master slave1 slave2
mkdir queue_all
cp sync_dir/slave*/queue/* queue_all
cp sync_dir/master/queue/* queue_all
afl-cmin -m none -t 3000 -i queue_all -o queue_cmin_all -- ../magick convert @@ xx3.png

  一旦我们通过afl-cmin运行生成的队列,我们需要最小化每个结果文件,以使我们不在我们不需要的字节上浪费CPU周期。
  贴一个大佬写的写的小bash脚本,称为afl-ptmin,它将afl-tmin并行化到一定数量的进程中,并证明在最小化过程中显著地提升了速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

cores=$1
inputdir=$2
outputdir=$3
pids=""
total=`ls $inputdir | wc -l`

for k in `seq 1 $cores $total`
do
for i in `seq 0 $(expr $cores - 1)`
do
file=`ls -Sr $inputdir | sed $(expr $i + $k)"q;d"`
echo $file
afl-tmin -m none -t 3000 -i $inputdir/$file -o $outputdir/$file -- $4 &
done
wait
done


chmod +x ~/afl-src/afl-ptmin
mkdir -m 777 queue
screen ~/afl-src/afl-ptmin 8 ./queue_cmin_all ./queue/ "./magick convert @@ xx3.png"

  即使有并行化,这个过程仍然需要一段时间。完成后,从sync_dirs目录中各个fuzzer目录下删除以前的队列queue目录(/syncdirs/fuzzer1/queue/) ,然后复制**/sync_dirs/queue/**文件夹以替换旧的队列文件夹。

1
2
3
4
5
6
rm -rf sync_dir/master/queue 
rm -rf sync_dir/slave1/queue
rm -rf sync_dir/slave2/queue
cp -r queue/ sync_dir/master/queue
cp -r queue/ sync_dir/slave1/queue
cp -r queue/ sync_dir/slave2/queue

使用最新的最小化队列queue,我们可以在之前离开的地方继续fuzzing。

1
2
3
4
screen afl-fuzz -m none -t 3000 -i- -o sync_dir/ -M master -- magick convert @@ xx1.png
screen afl-fuzz -m none -t 3000 -i- -o sync_dir/ -S slave1 -- magick convert @@ xx2.png
screen afl-fuzz -m none -t 3000 -i- -o sync_dir/ -S slave2 -- magick convert @@ xx3.png

一个 -i- , 这告诉AFL只使用syncdir中的queue/目录作为该fuzzer的种子目录,然后从那里重新启动。

8,crashesdump 处理测试结果

分类

1. crash exploration mode 变异

可以快速地产生很多和输入crash相关、但稍有些不同的crashes,从而判断漏洞是否足以利用。

1
afl-fuzz -m none -C -i poc -o peruvian-were-rabbit_out -- ~/src/LuPng/a.out @@ out.png

2.triage_crashes 信号量判断

11代表了SIGSEGV信号,有可能是因为缓冲区溢出导致进程引用了无效的内存;
06代表了SIGABRT信号,可能是执行了abort\assert函数或double free导致;
….后面再收集

1
~/afl-src/experimental/crash_triage/triage_crashes.sh fuzz_out program @@ 2>&1 | grep SIGNAL

3:crashwalk

#build

1
2
3
4
5
apt-get install gdb
mkdir ~/tools && mkdir ~/tools/go
git clone https://github.com/jfoote/exploitable.git ~/tools/
export GOPATH="~/tools/go" && export CW_EXPLOITABLE="~/tools/exploitable/exploitable/exploitable.py"
go get -u github.com/bnagy/crashwalk/cmd/...

#use
crashwalk支持AFL/Manual两种模式。

#Manual Mode

1
~/tools/go/bin/cwtriage -root syncdir/master/crashes/ -match id -~/program @@

#AFL Mode 读取crashes/README.txt文件获得目标的执行命令

1
~/tools/go/bin/cwtriage -root syncdir -afl

4:afl-collect

项目地址: https://github.com/rc0r/afl-utils

它是afl-utils套件中的一个工具,同样也是基于exploitable来检查crashes的可利用性。
它可以自动删除无效的crash样本、删除重复样本以及自动化样本分类

1
2
3
4
5
6
7
git clone https://github.com/rc0r/afl-utils
cd afl-utils
python3 setup.py install

看输出提示,注意设置
echo "source /usr/lib/python3.5/site-packages/exploitable-1.32_rcor-py3.5.egg/exploitable/exploitable.py" >> ~/.gdbinit

1
afl-collect -j 8 -d crashes.db -e gdb_script ./sync_dir ./collection_dir --  /path/to/target --target-opts

还有其他很多功能多留意,后面我再来补充

afl-collect
afl-cron
afl-minimize
afl-multicore
afl-multikill
afl-stats
afl-sync
afl-vcrash

5:手动看异常 (AddressSanitizer )(ASAN)

我们上面编译测试程序编译就开了ASAN,可以很好的分析展示出错误

1
2
3
4
5
6
7
8
./magick convert crash/xxxxxxx output

ERROR: AddressSanitizer: [内存漏洞分析]
--buffer-overflow [stack and heap]
--SEGV [Null point reference]
ERROR: LeakSanitizer: [内存泄漏分析]
--Direct leak
--Indirect leak

9,code coverage 代码覆盖率

  由于不能量化在二进制文件中执行可用的代码路径的程度,你会丢失很多信息。通过确定你没有到达代码库的哪些部分,你可以更好地调整你的测试用例种子,以便于达到更高的测试完整度。

  • ​ 工具之一是GCOV,它随gcc一起发布,所以不需要再单独安装,和afl-gcc插桩编译的原理一样,gcc编译时生成插桩的程序,用于在执行时生成代码覆盖率信息。
  • ​ 另外一个工具是LCOV,它是GCOV的图形前端,可以收集多个源文件的gcov数据,并创建包含使用覆盖率信息注释的源代码HTML页面。
  • ​ 最后一个工具是afl-cov,也是一个python脚本,可以快速帮助我们调用前面两个工具处理来自afl-fuzz测试用例的代码覆盖率结果。
1
2
3
$ apt-get install lcov
$ git clone https://github.com/mrash/afl-cov.git ~/afl-cov
$ ~/afl-cov/afl-cov -V

一,CFLAGS中添加"-fprofile-arcs""-ftest-coverage"选项,
--prefix指定一个新的目录。

1
2
3
4
5
6
cd ~/xxx/build/ 
make clean
./configure --prefix=./build-cov CC="clang" CXX="clang++" CFLAGS="-fprofile-arcs -ftest-coverage -g -fsanitize=address" --disable-shared
AFL_USE_ASAN=1 make

#上面加了-fprofile-arcs -ftest-coverage参数了,所以不需要

二,有了新程序,afl-cov可以将在给定输入的二进制程序中采用的代码路径与文件系统上的代码库链接起来。执行afl-cov。当afl-fuzz停止时,afl-cov将退出

1
2
3
4
5
6
7
8
9
10
11
12
#AFL_FILE和afl中的”@@”类似
#-d选项指定afl-fuzz输出目录;
#—live用于处理一个还在实时更新的AFL目录
#LD_LIBRARY_PATH 指定程序的库文件搜索路径
#–enable-branch-coverage用于开启边缘覆盖率(分支覆盖率)统计
#-c用于指定源码目录;
#-e选项用来设置要执行的程序和参数

screen ~/afl-cov/afl-cov -d ~/sync_dir/ --live --coverage-cmd --code-dir program/ -e "magick convert AFL_FILE /dev/null"

#需要链接库时
screen ~/afl-cov/afl-cov -d ~/sync_dir --live --enable-branch-coverage -c . -e "cat AFL_FILE | LD_LIBRARY_PATH=./build-cov/lib magick convert AFL_FILE /dev/null"

  完成后,afl-cov在sync_dir目录下的名为cov的目录中生成报告信息。 其中包括可以在Web浏览器中轻松查看的HTML文件,详细说明命中了哪些函数和哪行代码,以及未命中的函数和代码行。

当然使用afl去fuzz chrome, 如v8引擎的js编译基本可以放弃了,因为就简单的关键字function ,那要产生多少err才能通过啊。几乎不太可能去生成有效的js语法,会卡在语法parser那里。

参考&推荐:

  1. AFL漏洞挖掘技术漫谈(一):用AFL开始你的第一次Fuzzing

  2. Fuzzing-ImageMagick (魔鬼!不到2天光爆破一个程序就十几个cve !)

  3. fuzz

  4. AFL-FUZZ暴力效率流实践 大佬对fuzz进行了改造,值得深究嗷

  5. SanitizerCoverage clang的官方文档,讲了本节很多要了解的编译参数

这些项目必学一下,afl官方停止更新了

winafl、afl-go、WinAFL、afl-cov、kafl、android-afl

afl衍生品

下面计划学习libFuzzer