本文是对于模糊测试方面的小总结,计划三篇;第一篇概述;第二篇afl;第三篇Winafl。非大部头书,只提取关键信息。
本篇为第二篇:afl。
Part 2 总览
什么是AFL
它是如何工作的?
Fork 模式 VS 持久性(Persistent)模式
模糊测试策略
Sanitizers(消毒剂?)—— ASAN,UBSAN, MSAN,TSAN
使用AFL(练习)
- 如何编译一个简单的C程序?
- 对一个简单的C程序进行AFL模糊测试
- RCA 根本原因分析
- 崩溃(Crash)分类
使用AFL模糊测试真实世界的程序
- 使用AFL模糊测试一个开源软件
总结
什么是AFL?
- American Fuzzy Lop
- 由Michael Zelwaski创建的
- 一种由插桩后覆盖率引导的Fuzzer
- 编译后附带一套实用程序:
- afl-fuzz, afl-cmin, afl-tmin, afl-showmapetc..
- Fork server模式/Persistent mode模式
- Fork server mode —— 不断创建进程的副本
- Persistentmode —— 着重于一个程序疯狂循环
- 根据各种策略对文件进行突变
- Bitflip, byteflip, havoc, splice etc.
它是如何工作的?
在编译时使用特殊的方法插桩
它提供自带的编译器包装器(wrapper)
- afl-gcc,afl-g++, afl-clang, afl-clang++, afl-clang-fast, afl-clang-fast++
使用二进制重写技术
- 在每个基础块(BB块)上进行插桩
- 每个基本块都有一个唯一的随机ID
- 在每个基础块(BB块)上进行插桩
其插桩可以由以下伪代码所表示:
- A → B →C → D→ E 与 A → B → D → C→ E
Fork 模式 VS 持久性(Persistent)模式
Fork 模式
- 在main()函数处停止
- 使用fork创建程序的克隆
- 处理输入input 并创建另一个克隆
- 节省初始化程序的时间,从而提高速度
- Ref: https://lcamtuf.blogspot.com/2014/10/fuzzing-binaries-without-execve.html
持久性(Persistent)模式
- 不断使用fork仍然消耗很大
- 每次运行后并不需要杀死子进程
- 使用 in process 模糊测试 (类似libfuzzer?)
- 需要编写一个线束(harness)程序
模糊测试策略
- bitflip,按位翻转,1变为0,0变为1
- 1/1,2/1,4/1,8/8 ….32/8
STAGE_FLIP1
每次翻转一位(1 bit
),按一位步长从头开始。STAGE_FLIP2
每次翻转相邻两位(2 bit
),按一位步长从头开始。STAGE_FLIP4
每次翻转相邻四位(4 bit
),按一位步长从头开始。STAGE_FLIP8
每次翻转相邻八位(8 bit
),按八位步长从头开始,也就是说,每次对一个byte做翻转变化。STAGE_FLIP16
每次翻转相邻十六位(16 bit
),按八位步长从头开始,每次对一个word做翻转变化。STAGE_FLIP32
每次翻转相邻三十二位(32 bit
),按八位步长从头开始,每次对一个dword做翻转变化。
- 1/1,2/1,4/1,8/8 ….32/8
- arithmetic,整数加/减算术运算
- interest,把一些特殊内容替换到原文件中
- dictionary,把自动生成或用户提供的token替换/插入到原文件中
- havoc,中文意思是“大破坏”,此阶段会对原文件进行大量变异
- splice,中文意思是“绞接”,此阶段会将两个文件拼接起来得到一个新的文件
其中,前四项bitflip, arithmetic, interest, dictionary是非dumb mode(-d
)和主fuzzer(-M
)会进行的操作,由于其变异方式没有随机性,所以也称为deterministic fuzzing;havoc和splice则存在随机性,是所有状况的fuzzer(是否dumb mode、主从fuzzer)都会执行的变异。
Sanitizers(消毒剂?)
- 基于编译器的检测工具
- 有助于发现BUGs
- 可以发现一些正常模式不好检出的错误,例如大内存分配,堆溢出,UAF等
- 不同类型的Sanitizers
- ASAN
- MSAN
- UBSAN
- TSAN
ASAN(Address Sanitizer)
编译时指令: -fsanitize=address
可以检测各种问题,例如UAF,堆缓冲区溢出,内存泄漏等
ASAN + Fuzzer = More bugs!
有大牛表示:“AFL Fuzzing without ASAN is just a waste of CPU”
UBSAN(UndefinedBehavior Sanitizer)
编译时指令: -fsanitize=undefined
检测程序中未定义的行为
- 除零漏洞,整数溢出,非法的读取等
提升的发现错误的概率
Ref: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
Memory Sanitizer(MSAN)
- 编译时指令: -fsanitize=memory
- 检测未初始化的读取等
- Ref:https://clang.llvm.org/docs/MemorySanitizer.html
Thread Sanitizer(TSAN)
- 编译时指令: -fsanitize=thread
- 检测数据竞争等
- Ref:https://clang.llvm.org/docs/ThreadSanitizer.html
使用AFL(练习)
编译并安装AFL
- git clone https://github.com/google/AFL.git
- make
- cd llvm_mode
- make (这步可能需要clang)
- cd ..
- sudo make install
如果安的是AFL++,直接用带的脚本一键安装完事儿
如何用AFL编译一个程序?
- afl-clang : gcc的编译器包装器,它将编译并插桩二进制文件
- -fsanitize=address : 开启ASAN ( 用AFL_USE_ASAN=1也行)
- -g : 调试符号支持
- imgRead.c : 源文件
- imgReadafl :生成可被模糊测试的可执行文件
使用AFL开始模糊测试
- 生成一个初始输入
- echo “IMG” > input/1.img
- afl-fuzz –iinput –o output –m none –./imgRead@@
- afl-fuzz : fuzzer的二进制程序
- -i : 包含输入种子文件的目录
- -o : Fuzzer的输出数据的目录
- Crashes : 包含使目标程序崩溃的输入文件
- Hangs : 包含导致目标程序挂起(hang)的输入文件
- -m : 内存限制,如果为ASAN和64位,则将其设置为none
- 否则使用编译器标志–m32将其编译为32位
- 将内存限制设置为–m 800
- 查找更多crashes
- -M : 主实例Master模式(如果您具有多个CPU内核)
- -S : 从实例Slaver,可以为任意n数,具体取决于您拥有的内核数
根本原因分析(RCA)
- 使用GDB对Crashes进行分析
- 常用命令:
- Gdb
- r -> 开始运行目标程序
- s -> 步步运行程序
- Next/fi-> 一直运行到return
- b filename.c:linenumber -> 在对应filename.c中的 linenumber行数打断点(break)
- Gdb
- 在我们的例子中:
- r <output/crashes/filename>
崩溃(Crash)分类
如果遇到很多崩溃,Crashwalk是一个有用的工具,可以对崩溃进行分类
安装Crashwalk
- sudo apt-get install golang
- go get -u github.com/bnagy/crashwalk/cmd/…
- ~/go/bin
安装 exploitable
- ~/src/exploitable/exploitable/exploitable.py
- mkdir~/src
- cd ~/src
- git clone https://github.com/jfoote/exploitable.git
Cwtriage——分类崩溃的实用程序
- ASAN_OPTIONS=”abort_on_error=1:symbolize=0” Cwtriage –afl –root output
- 分析每个崩溃文件并将结果保存在crashwalk.db中
- 使用ASAN运行,否则崩溃将无法复现
Cwdump——从crashwalk.db转储崩溃信息的实用程序
- Cwdump crashwalk.db
使用AFL模糊测试一个开源软件
Fuzz tcpdump
- 先把tcpdump和libcap的源码dump下来
- git clone https://github.com/the-tcpdump-group/tcpdump.git
- cd tcpdump
- git clone https://github.com/the-tcpdump-group/libpcap.git
- cd libpcap
- 使用AFL对其进行编译
- CC=afl-gcc CFLAGS=”-g -fsanitize=address -fno-omit-frame-pointer” LDFLAGS=”-g -fsanitize=address -fno-omit-frame-pointer” ./configure
- sudo make && make install
- 语料库从哪来??
- 查看一下项目自带的test文件夹
- 收集别的符合条件的文件,比如issue页面
- 精简它们:afl-cmin–i tests –o mincorpus –m none –./tcpdump –vv –ee –nnr @@
- Fuzz 它
- afl-fuzz –I mincorpus –o fuzzoutput –m none – ./tcpdump –vv –ee –nnr @@
Fuzz libtiff
- 获取源码:
- 用AFL编译
- ./autogen.sh
- CC=afl-gcc CXX=afl-g++ CFLAGS=”-g -fsanitize=address -fno-omit-frame-pointer” CXXFLAGS=”-g -fsanitize=address -fno-omit-frame-pointer” LDFLAGS=” -g -fsanitize=address -fno-omit-frame-pointer” ./configure
- sudo make && make install
- 收集语料库
- 精简它们
- afl-cmin–itiff –o mintiff–./tiff2rgba @@ test.tiff
- Fuzz 它们
- afl-fuzz –i <*input> -o fuzzoutput –m none – tiff2rgba @@ test.tiff
总结
- 使用AFL在linux上fuzz开源软件十分简单
- 大多数的lib/开源软件的源代码都是可以获取的
- 在编译时插桩是可行的
- 在Fuzz时要用ASAN,MSAN,UBSAN
- 有时最需要花费时间的过程是项目编译
- 缺失引用的第三方库(lib)
- 编译过程中的各种错误
- 不同项目不同的编译方法与各种选项