内容简介:二进制分析方面主要利用技术包括:动态分析(Dynamic Analysis)、静态分析(Static Analysis)、符号化执行(Symbolic Execution)、Constraint Solving、资讯流追踪技术(Data Flow Tracking)以及自动化测试(Fuzz Testing)本指南使用的环境是 kali linux 2019.1目标程序
二进制分析方面主要利用技术包括:动态分析(Dynamic Analysis)、静态分析(Static Analysis)、符号化执行(Symbolic Execution)、Constraint Solving、资讯流追踪技术(Data Flow Tracking)以及自动化测试(Fuzz Testing)
AFL原理介绍参考:
《AFL漏洞挖掘技术漫谈(一):用AFL开始你的第一次Fuzzing》本指南使用的环境是 kali linux 2019.1
0x01 AFL的基本使用
1. 使用afl-gcc
1.1 使用AFL插桩程序
目标程序
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<signal.h> int vuln(char *str) { int len = strlen(str); if(str[0] == 'A' && len == 66) { raise(SIGSEGV); //如果输入的字符串的首字符为A并且长度为66,则异常退出 } else if(str[0] == 'F' && len == 6) { raise(SIGSEGV); //如果输入的字符串的首字符为F并且长度为6,则异常退出 } else { printf("it is good!\n"); } return 0; } int main(int argc, char *argv[]) { char buf[100]={0}; gets(buf);//存在栈溢出漏洞 printf(buf);//存在格式化字符串漏洞 vuln(buf); return 0; }
使用afl-gcc进行插桩编译
afl-gcc -g -o ./zerotest/vuln ./zerotest/vuln.c
PS:
如果目标程序中有Makefile,那么分两种情况:
- 程序是用autoconf构建,那么此时只需要执行如下即可
./configure CC="afl-gcc" CXX="afl-g++"
此外,还可以执行如下语句设置LD_LIBRARY_PATH让程序加载经过AFL插桩的.so文件,进行静态构建而不是动态链接
./configure --disable-shared CC="afl-gcc" CXX="afl-g++"
- 程序不是用autoconf构建,那么直接修改Makefile文件中的编译器为
afl-gcc/g++
。
为了后期更好的分析crash,在此处可以开启Address Sanitizer(ASAN)这个内存检测工具,此 工具 可以更好的检测出缓存区溢出、UAF 等内存漏洞,开启方法如下:
AFL_USE_ASAN=1 ./configure CC=afl-gcc CXX=afl-g++ LD=afl-gcc--disable-shared AFL_USE_ASAN=1 make
不使用 AFL 编译插桩时,可使用以下方式开启 Address Sanitizer。
./configure CC=gcc CXX=g++ CFLAGS="-g -fsanitize=address" make
1.2 开始fuzz
fuzz的语法一般情况是两种:
- 直接从stdin读取输入的目标程序
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program […params…]
- 从文件读取输入的目标程序,@@就是占位符,表示输入替换的位置
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@
此处我采用第一种方式
afl-fuzz -m 300 -i ./zerotest/fuzz_in -o ./zerotest/fuzz_out ./zerotest/vuln -f
PS: 常见参数的含义如下
- -f参数表示:testcase的内容会作为afl_test的stdin
- -m参数表示分配的内存空间
- -i 指定测试样本的路径
- -o 指定输出结果的路径
- /dev/null 使错误信息不输出到屏幕
- -t:设置程序运行超时值,单位为 ms
- -M:运行主(Master) Fuzzer
- -S:运行从属(Slave) Fuzzer
1.3 fuzz的结果
从界面上主要注意以下几点:
- last new path 如果报错那么要及时修正命令行参数,不然继续fuzz也是徒劳(因为路径是不会改变的);
- cycles done 如果变绿就说明后面及时继续fuzz,出现crash的几率也很低了,可以选择在这个时候停止
- uniq crashes 代表的是crash的数量
1.4 crash分析
PS: xxd命令的作用就是将一个文件以十六进制的形式显示出来
可以看到已经得到的几个crash文件,那么分析的话只需要将其作为之前vuln文件的输入,使用gdb调试分析就可以得到详细结果了,但是在这之前可以使用xxd看一下其中数据的内容做一个初步的判断。
分别看一下这几个crash的信息
- 可以看到应该是满足了开头是F且字符串长度为6的异常退出情况
- 看这个数据情况可能是栈溢出
- 栈溢出
- 符合首字符为A且栈溢出
- 格式化字符串?可能
- 符合首字符为A且字符串长度为66的异常退出情况
主要参考:
《初探Fuzz-AFL》1.5 语料库蒸馏(Corpus Distillation)
一般来说在进行fuzz之前构建一份有效的语料库是十分有必要的,这将作为程序开始时的种子。
语料库的信息来源主要如下:
- 使用项目自身提供的测试用例
- 目标程序bug提交页面
- 使用格式转换器,用从现有的文件格式生成一些不容易找到的文件格式:
- afl源码的testcases目录下提供了一些测试用例
- 其他开源的语料库
收集完后可以使用afl提供的工具来对语料库进行进一步的处理:
-
afl-cmin: 移除执行相同代码的输入文件
afl-cmin的核心思想是: 尝试找到与语料库全集具有相同覆盖范围的最小子集。
它一般的两种执行模式是:
afl-cmin -i input_dir -o output_dir -- /path/to/tested/program [params]
afl-cmin -i input_dir -o output_dir -- /path/to/tested/program [params] @@
-
afl-tmin: 减小单个输入文件的大小
它有两种工作模式: instrumented mode和crash mode。默认的工作方式是instrumented mode
# instrumented mode afl-tmin -i input_file -o output_file -- /path/to/tested/program [params] @@
# crash mode 将会剔除导致crash的文件 afl-tmin -x -i input_file -o output_file -- /path/to/tested/program [params] @@
由于只能针对单个目标进行使用,因此使用如下 shell 脚本进行批量处理
for i in *; do afl-tmin -i $i -o tmin-$i -- ~/path/to/tested/program [params] @@; done;
或者修改如下的 Python 脚本进行预处理
import os import sys import shutil def cmin(): command = ' -m 300 -t 5000 ./utilities/magick convert @@ /dev/null' os.system('afl-cmin -i seeds/tmin -o seeds/cmin ' + command) def tmin(): command = ' -m 300 -t 5000 ./utilities/magick convert @@ /dev/null' seed_list = os.listdir('seeds/all_format') for seed in seed_list: in_file = os.path.join('seeds/all_format', seed) out_file = os.path.join('seeds/tmin', seed) if os.path.getsize(in_file) > 1024*1: if os.path.getsize(in_file) < 1024*3 and not seed.endswith('.txt'): os.system('afl-tmin -i ' + in_file + ' -o ' + out_file + command) print('afl-tmin -i ' + in_file + ' -o ' + out_file + command) else: pass elif os.path.getsize(in_file) > 0: shutil.copyfile(in_file,out_file) else: pass def convert(origin_seeds): seed_list = os.listdir(origin_seeds) for seed in seed_list: seed_in = os.path.join(origin_seeds, seed) file_name = (os.path.splitext(seed)[0]) coder_list = os.listdir('coders') for cfile in coder_list: if cfile.endswith('.c'): extern = cfile[:cfile.find('.c')] seed_out = 'seeds/all_format/' + file_name + '.' + extern os.system('utilities/magick convert ' + seed_in + ' ' + seed_out) if __name__ == '__main__': if len(sys.argv) < 2: print 'Usage: ' + sys.argv[0] + ' origin_seeds_dir' else: origin_seeds_dir = sys.argv[1] try: os.mkdir('seeds') seeds_path = os.path.join(os.path.abspath('.'),'seeds') os.mkdir(os.path.join(seeds_path,'all_format')) os.mkdir(os.path.join(seeds_path,'cmin')) os.mkdir(os.path.join(seeds_path,'tmin')) except: print 'make dir fail!' convert(origin_seeds_dir) tmin() cmin()
预处理脚本来自: 《使用 AFL 进行模糊测试》
2. LLVM Mode模式
2.1 启用llvm
LLVM Mode模式编译程序可以获得更快的Fuzzing速度,因此针对大型项目可以考虑启用。
下载必要的安装包
wget http://releases.llvm.org/8.0.0/llvm-8.0.0.src.tar.xz wget http://releases.llvm.org/8.0.0/compiler-rt-8.0.0.src.tar.xz wget http://releases.llvm.org/8.0.0/clang-tools-extra-8.0.0.src.tar.xz wget http://releases.llvm.org/8.0.0/cfe-8.0.0.src.tar.xz
解压缩
xz -d ./* tar xvf cfe-8.0.0.src.tar tar xvf clang-tools-extra-8.0.0.src.tar tar xvf llvm-8.0.0.src.tar tar xvf compiler-rt-8.0.0.src.tar
源码合并
mv cfe-8.0.0.src clang mv clang llvm-8.0.0.src/tools mv clang-tools-extra-8.0.0.src extra mv extra llvm-8.0.0.src/tools/clang mv compiler-rt-8.0.0.src compiler-rt mv compiler-rt llvm-8.0.0.src/projects
编译安装
mkdir build-8.0 cmake ../llvm-8.0.0.src/ cmake --build . cmake --build . --target install cmake -DCMAKE_INSTALL_PREFIX=/tmp/llvm -P cmake_install.cmake
上面的编译安装对硬件配置和硬盘的空间要求比较高,所以你可以直接使用源进行安装,比如:
apt install llvm clang
编译安装afl的llvm模块
(我的使用的是kali linux 2019.1进行编译的,clang版本过高会失败,使用clang++也会失败,所以最终发现下面方法可行)
cd afl/llvm_mode export CXX=/usr/bin/g++ export CC=/usr/bin/clang-6.0 make
因为clang没有办法使用 update-alternatives
,因此我直接修改软连接
ln -s /usr/bin/clang-6.0 /usr/bin/clang ln -sb /usr/bin/clang++-6.0 /usr/bin/clang++
之后就可以正常使用afl-clang-fast了
其实以上均太费劲,还有更简单的方法,kali linux的源中包含了afl,所以可以直接apt进行安装,装好之后afl-clang-fast也就有了
apt install afl
2.2 使用LLVM Mode模式进行fuzz
编译插桩
root@kali-z ~/Desktop/fuzz/afl$ ./afl-clang-fast -g -o ./zerotest/vuln-fast ./zerotest/vuln.c
之后重复上面的方式进行fuzz即可,接下来展示一个使用此模式fuzz php内核代码的例子。
1. 下载目标代码
wget https://github.com/php/php-src/archive/php-7.2.11.tar.gz && tar xf php-7.2.11.tar.gz
2. 进行编译插桩
cd php-src-php-7.2.11 ./buildconf --force CC=afl-clang-fast CXX=afl-clang-fast++ ./configure AFL_USE_ASAN=1 make
PS: 如果报错缺失libconv,则在 Makefile
中的 EXTRA_LIBS =
添加 -liconv
3. 进行源代码的修改
未修改之前 sapi/cli/php_cli.c
修改之后 sapi/cli/php_cli.c
修改完之后执行如下进行rebuild
AFL_USE_ASAN=1 make
PS: 之所以进行这样的修改,是因为我们使用 php -r
来eval php string,因此定位到 sapi/cli/php_cli.c
进行代码的修改离开提升后期fuzz的效率。
4. 构造一个输入点
我们想在fuzz的时候从stdin进行数据的输入,因此构造如下输入点
unserialize(file_get_contents(“php://stdin”));
5. 根据上述的构造点构造输入数据
此处账户要考虑构造不同类类型的输入数据,构造如下
mkdir serialized_data && cd serialized_data ../sapi/cli/php -r 'echo serialize("a");' > string ../sapi/cli/php -r 'echo serialize(1);' > number ../sapi/cli/php -r 'echo serialize([1,2]);' > array_of_num ../sapi/cli/php -r 'echo serialize(["1","2"]);' > array_of_str ../sapi/cli/php -r 'echo serialize([["1","2"],["3","4"],[1,2]]);' > array_of_array echo 'O:6:"zeroyu":1:{s:4:"test";O:7:"npusec2":1:{s:5:"test2";s:10:"phpinfo();";}}' > class
6. 开始fuzz
为了从地址清理(ASAN)中获得有用的结果,有必要设置一个环境变量,以便 PHP 禁用其自定义内存分配器,从而使内存安全问题对ASAN可见。
USE_ZEND_ALLOC=0 screen -S zeroyu
使用screen可以随时进入查看fuzz的结果
screen -r zeroyu
使用如下命令开始fuzz
cd.. afl-fuzz -i serialized_data -o basic_fuzz -m none -- ./sapi/cli/php -r 'unserialize(file_get_contents("php://stdin"));'
7. 分析crash
用是使用如下bash脚本来寻找可能是bug的crash,因为有些是良性的crash,是由于ASAN无法分配足够的内存。这是因为ASAN需要额外的内存来跟踪所有分配,而精心编制的序列化对象可能会触发大内存分配。
for FILE in $(ls id*); do cat $FILE | ../../sapi/cli/php -r "unserialize(file_get_contents('php://stdin'));" 2>&1 | grep -E "SUMMARY|ERROR" | grep -v "LargeMmap" && echo $FILE; done
参考: 《Fuzzing PHP for Fun and Profit》
3. 黑盒测试
参考: 《AFL漏洞挖掘技术漫谈(一):用AFL开始你的第一次Fuzzing》
4. 并行测试
4.1 单系统并行
查看系统核心数
cat /proc/cpuinfo| grep "cpu cores"| uniq
afl-fuzz并行Fuzzing,一般的做法是通过-M参数指定一个主Fuzzer(Master Fuzzer)、通过-S参数指定多个从Fuzzer(Slave Fuzzer)。
$ screen afl-fuzz -i testcases/ -o sync_dir/ -M fuzzer1 -- ./program $ screen afl-fuzz -i testcases/ -o sync_dir/ -S fuzzer2 -- ./program $ screen afl-fuzz -i testcases/ -o sync_dir/ -S fuzzer3 -- ./program
PS: -o指定的是一个同步目录,并行测试中,所有的Fuzzer将相互协作,在找到新的代码路径时,相互传递新的测试用例。所以不用担心重复的问题
两个辅助工具:
afl-whatsup afl-gotcpu
4.2 多系统并行
压缩每个fuzzer实例目录中queue下的文件,通过如下SSH脚本同步分发到其他机器上解压。
#!/bin/sh # authorized_keys的方式进行ssh认证 # 所有要同步的主机 FUZZ_HOSTS='172.21.5.101 172.21.5.102' # SSH user FUZZ_USER=root # 同步目录 SYNC_DIR='/root/syncdir' # 同步间隔时间 SYNC_INTERVAL=$((30 * 60)) if [ "$AFL_ALLOW_TMP" = "" ]; then if [ "$PWD" = "/tmp" -o "$PWD" = "/var/tmp" ]; then echo "[-] Error: do not use shared /tmp or /var/tmp directories with this script." 1>&2 exit 1 fi fi rm -rf .sync_tmp 2>/dev/null mkdir .sync_tmp || exit 1 while :; do # 打包所有机器上的数据 for host in $FUZZ_HOSTS; do echo "[*] Retrieving data from${host}..." ssh -o 'passwordauthentication no' ${FUZZ_USER}@${host} \ "cd '$SYNC_DIR' && tar -czf - SESSION*" >".sync_tmp/${host}.tgz" done # 分发数据 for dst_host in $FUZZ_HOSTS; do echo "[*] Distributing data to${dst_host}..." for src_host in $FUZZ_HOSTS; do test "$src_host" = "$dst_host" && continue echo " Sending fuzzer data from${src_host}..." ssh -o 'passwordauthentication no' ${FUZZ_USER}@$dst_host \ "cd '$SYNC_DIR' && tar -xkzf - &>/dev/null" <".sync_tmp/${src_host}.tgz" done done echo "[+] Done. Sleeping for$SYNC_INTERVALseconds (Ctrl-C to quit)." sleep $SYNC_INTERVAL done
0x02 Fuzz结果分析和代码覆盖率
1. 工作状态
afl-fuzz永远不会停止,所以何时停止测试很多时候就是依靠afl-fuzz提供的状态来决定的。具体的几种方式如下所示:
cycles done afl-whatsup afl-stat afl-whatsup afl-plot pythia
2. fuzz结束判断
- 状态窗口中”cycles done”字段颜色变为绿色该字段的颜色可以作为何时停止测试的参考;
- 距上一次发现新路径(或者崩溃)已经过去很长时间了,至于具体多少时间还是需要自己把握;
- 目标程序的代码几乎被测试用例完全覆盖,这种情况好像很少见;
- pythia提供的各种数据中,path covera达到99或者correctness的值达到1e-08(含义: 从上次发现path/uniq crash到下一次发现之间大约需要1亿次执行)
3. 输出结果说明
queue:存放所有具有独特执行路径的测试用例。 crashes:导致目标接收致命signal而崩溃的独特测试用例。 crashes/README.txt:保存了目标执行这些crash文件的命令行参数。 hangs:导致目标超时的独特测试用例。 fuzzer_stats:afl-fuzz的运行状态。 plot_data:用于afl-plot绘图。
4. 对crash结果的简单分析和分类
-
crash exploration mode
afl-fuzz的一种运行模式,也称为peruvian rabbit mode,用于确定bug的可利用性,其输入的是crash的信息,之后使用
-C
启用这种模式,afl会自动探索并创造与之相关的crash来帮助你进行分析,比如判断能够控制某块内存地址的长度。
afl-fuzz -m none -C -i ./fuzz_out/crashes -o ./peruvian-were-rabbit_out -- ./vuln -f
-
triage_crashes.sh
AFL源码的experimental目录中有一个名为triage_crashes.sh的脚本,可以帮助我们触发收集到的crashes。
直接使用脚本跟参数的话,我们可以看到相关crash情况的寄存器等信息,但是如果只是大致分类的话,可以使用如下命令
/root/Desktop/fuzz/afl/experimental/crash_triage/triage_crashes.sh ./fuzz_out ./vuln 2>&1 | grep SIGNAL
效果如下,11代表了SIGSEGV信号,有可能是因为缓冲区溢出导致进程引用了无效的内存
-
crashwalk
优点:可以显示更为详细的信息
# 手动模式 ~/go/bin/cwtriage -root ./fuzz_out/crashes -match id -- ./vuln -f # afl自动化模式 ~/go/bin/cwtriage -root ./fuzz_out/crashes -afl
- afl-collect
项目地址: https://github.com/rc0r/afl-utils
afl-collect基于exploitable来检查crashes的可利用性。它可以自动删除无效的crash样本、删除重复样本以及自动化样本分类。
afl-collect -j 8 -d crashes.db -e gdb_script ./fuzz_out ./fuzz_in -- ./vuln --target-opts
效果如下
5. 代码覆盖率
原理部分参考:
《AFL漏洞挖掘技术漫谈(二):Fuzz结果分析和代码覆盖率》afl-cov的使用说明如下:
首先使用gcov重新编译源码
gcc -fprofile-arcs -ftest-coverage vuln.c -o vuln_cov
如果遇到需要make进行编译的文件,执行如下:
$ make clean $ ./configure --prefix=/root/tiff-4.0.10/build-cov CC="gcc" CXX="g++" CFLAGS="-fprofile-arcs -ftest-coverage" --disable-shared $ make $ make install
之后使用afl-cov来计算覆盖率
afl-cov -d ./fuzz_out --live --enable-branch-coverage -c . -e "cat AFL_FILE | ./vuln_cov AFL_FILE"
同时进行对插桩过的 vuln
的fuzz
afl-fuzz -i ./fuzz_in -o ./fuzz_out ./vuln -f
最终效果如下
生成的报告会保存在 /path/to/afl-fuzz-output/cov/web/lcov-web-final
路径下。
以上所述就是小编给大家介绍的《AFL使用指南》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
第二曲线:跨越“S型曲线”的二次增长
[英]查尔斯·汉迪(Charles Handy) / 苗青 / 机械工业出版社 / 2017-6 / 49.00
S型曲线是每个组织和企业在预测未来时一定会参考的工具,一切事物的发展都逃不开S型曲线(“第一曲线”)。 然而,从公司组织、企业治理、市场的变化,到个人职业发展、社会人际关系以及未来的教育与社会价值,多维度地探讨这个世界需要重新以不同的角度来思考问题,不能够总是停留在“第一曲线”的世界。 如果组织和企业能在第一曲线到达巅峰之前,找到带领企业二次腾飞的“第二曲线”,并且第二曲线必须在第一曲......一起来看看 《第二曲线:跨越“S型曲线”的二次增长》 这本书的介绍吧!