fuzz
模糊测试 - 维基百科,自由的百科全书 (wikipedia.org)
定义
- 变异测试(mutation-based),变异测试通过改变已有的数据样本去生成测试数据
- 生成测试(generation-based),生成测试则通过对程序输入的建模来生成新的测试数据
目前主要的三种模糊测试技术
- 黑盒随机模糊:对正确格式的输入数据进行随机变异,然后用这些变异的输入运行程序,看是否能够触发异常
- 基于语法的模糊:是模糊复杂格式输入的替代方法,需要指定输入格式的输入语法,还指定哪些输入部分要进行模糊化以及如何模糊化。基于语法的模糊生成器会生成许多新的输入,每个输入满足语法编码的约束。基于语法的fuzzing通过模糊生成器的用户的创造力和专业知识来指导fuzzing
- 白盒模糊处理:动态地执行测试下的程序,并从执行过程中遇到的条件分支收集输入约束。然后,系统地逐个否定所有这些约束,并使用约束求解器求解,其解被映射到执行不同程序执行路径的新输入。使用系统搜索技术重复这个过程,试图扫描程序的所有可行的执行路径。与黑盒随机模糊相比,白盒模糊通常更精确,可以运行更多的代码,从而发现更多的bug
测试用例
随机数据的类型有:超长字符串、格式化字符串、浮点数、超大数、特殊字符、unicode编码……
测试用例构造
- 单项测试用例
- 多项测试用例
- 同类字符不必区分法则:如前所述0-9这类数字,a-z这类字母都是同类,不是很有必要测了一个再去测其他
- 长度不必过细法则:选几个有代表性的就行没必要长度100来一个测试用例,长度101来一个测试用例。
一般测试(不管理普通测试还是渗透测试)是不会强行把软件撕开一个口子去测试的,测试就是就着目标系统提供的接口对接口中的各项值进行修改以此生成测试用例去进行测试。
测试过程
初始种子->选择种子->变异数据是否符合要求->符合的注入目标程序->目标程序是否发生异常->输出日志
模糊生成器fuzzer
- fuzzer根据code coverage的结果,调整corpus数据,以达到code coverage最大化
- fuzzer监控一些指令和函数,比如比较指令、switch/case指令、memcmp/strcmp函数等,智能的修改数据
- 支持多线程、多进程
- 不能有随机性,不能受全局状态的影响,否则效率会大幅度降低
- 配合Sanitize,监控程序运行情况:AddressSanitizer和UndefinedBehaviorSanitizer
工具
American Fuzzy Lop(AFL)
安装
tar zxvf afl-latest.tgz make sudo install make
讯享网
测试-有源码程序
(1条消息) AFL (American fuzzy lop) 二进制程序模糊测试工具学习___lifanxin的博客-CSDN博客
讯享网#include <stdio.h> int main() {
char buf[100] = {
0}; gets(buf); // stack overflow if (buf[0] == 'A') printf("hello\n"); else printf("NO A\n"); return 0; }
编译。编译c程序使用afl-gcc,编译c++程序使用afl-g++
afl-gcc test.c -o test # 编译 mkdir fuzz_in # 创建输入的文件夹 echo "hello" > fuzz_in/testcase # 给一个输入样例 afl-fuzz -i fuzz_in -o fuzz_out ./test # 开始测试
使用afl-fuzz进行测试,-i指明测试用例的目录,-o指明测试结果的存放目录。对于直接从终端获取输入的程序来说,我们需要在testcase_dir目录下新建一个文件,文件的内容就是程序的输入,文件命名不唯一。fuzz_out会自动生成。如果afl-fuzz出现报错时,修改一下即可
讯享网sudo su echo core >/proc/sys/kernel/core_pattern
执行afl-fuzz后会出现下面的界面,正在进行模糊测试。这是跑了五分钟的结果,可以看到已经有两个crash了。不想跑了就ctrl+c退出
afl界面参数说明
process timing:fuzzing测试的时间消耗
overall results:汇总了fuzzing测试的执行结果

- cycles done:fuzzing的轮数,品红色表示处于the first pass。如果在the first pass后有新发现,进入子过程,会变黄色。所有子过程完成后会变成蓝色,最后变成绿色表面长时间无新动作,可以ctrl+c关闭了
cycle progress:展示当前队列中fuzzer的位置,以及放弃了的超时输入
map coverage:程序的覆盖率
stage progress:进一步展示fuzzer的执行过程细节
- now trying指明当前所用的变异输入方法
- calibration:在fuzzing测试前的阶段,主要检查执行路径检测异常,建立基线执行速度
- trim L/S:fuzzing测试前的阶段,修建测试用例使其更短,但保证裁剪后仍能达到相同的执行路径。L表示length长度,S表示stepover步距,其值与文件大小是相关的
- bitflip L/S:确定性的比特位翻转。以S为增量,L长度的bit数被翻转。有几种变型模式:1/1, 2/1, 4/1, 8/8, 16/8, 32/8
- arith L/8:确定性的算术运算。AFL会尝试去减去或者加上一些整数使其为8bit/16bit/32bit的值,步距永远是8bits
- interest L/8:确定性的值覆盖。AFL自身保留了一些Interesting的8bit/16bit/32bit的值,用这些值去覆盖原有的测试用例,步距永远是8bits
- extras:确定性的字典注入。AFL自身有一个字典,也可以用-x选项来指明使用用户提供的字典
- havoc:固定长度的堆叠随机扭曲。该阶段会尝试位翻转,用随机数或者Interesting的整数去覆盖,块删除,块复制,以及字典的相关操作
- splice:最后一种策略。在上述策略都执行完后将会执行该策略,它和havoc差不多,不过它会首先将队列中的两个随机输入先拼接在一起
- sync – 这个是并行执行的策略选项,通过-M或者-S选项进行指定。该策略并不会涉及到真正的fuzzing,会导入从另一个fuzzer得到的输出和测试用例
- exec speed:测试用例的执行速度,正常不低于500 exec/sec,长时间低于100的话可以查看perf_tips.txt来寻求优化
findings in depth:一些路径、crash信息
fuzzing strategy yields:进一步展示AFL所作的工作,采用的策略情况
path geometry:路径测试的相关信息
- levels:测试等级
- pending:还没有经过fuzzing的输入数量
- pend fav:fuzzer感兴趣的输入数量
- own finds:在fuzzing过程中新找到的,或者是并行测试从另一个实列导入的
- imported:n/a表示不可用,即没有导入
- stability:相同输入是否产生相同的行为,一般是100%。如果低于100%且变红,需要查官方文档寻找解决方法
输出文件
分析一下fuzz_out里的文件,先看下结构
ubuntu@ubuntu:~/Desktop/pwn/fuzz$ tree fuzz_out fuzz_out ├── crashes │ ├── id:000000,sig:06,src:000000,op:havoc,rep:128 │ ├── id:000001,sig:06,src:000001,op:havoc,rep:128 │ ├── id:000002,sig:11,src:000000,op:havoc,rep:64 │ └── README.txt ├── fuzz_bitmap ├── fuzzer_stats ├── hangs ├── plot_data └── queue ├── id:000000,orig:testcase └── id:000001,src:000000,op:havoc,rep:32,+cov
queue:存放fuzzer生成的所有不同执行路径的测试用例+自己一开始构造的测试用例
crashes:存放造成程序崩溃的测试用例,根据产生的信号不同进行分类
hangs:存放造成程序超时的测试用例
剩下的文件记录fuzzer工作的一些信息
测试-无源码程序
AFL依赖QEMU实现了这个功能,qemu是一个仿真器。下载AFL的源码后可以很方便的通过其自带脚本完成安装,如下命令所示,执行该命令后会去下载指定版本的qemu然后安装
讯享网cd qemu_mode ./build_qemu_support.sh
操作和前面一样,不过编译用gcc,afl-fuzz加上参数-Q
mkdir fuzz_in echo "hello" > fuzz_in/testcase gcc test.c -o test afl-fuzz -i fuzz_in -o fuzz_out -Q ./test
基本框架结构
Fuzzbook系列(3):Fuzz的基本框架结构 - FreeBuf网络安全行业门户
Runner类
使用给定的输入来执行某些特定的程序(要接受测试的某些程序或函数)
讯享网import subprocess class Runner(object): # Test outcomes PASS = "PASS" FAIL = "FAIL" UNRESOLVED = "UNRESOLVED" def __init__(self): """Initialize""" pass def run(self, inp): """Run the runner with the given input""" return (inp, Runner.UNRESOLVED) class PrintRunner(Runner): def run(self, inp): """Print the given input""" print(inp) return (inp, Runner.UNRESOLVED) class ProgramRunner(Runner): def __init__(self, program): """Initialize. `program` is a program spec as passed to `subprocess.run()`""" self.program = program def run_process(self, inp=""): """Run the program with `inp` as input. Return result of `subprocess.run()`.""" return subprocess.run(self.program, input=inp, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) def run(self, inp=""): """Run the program with `inp` as input. Return test outcome based on result of `subprocess.run()`.""" result = self.run_process(inp) if result.returncode == 0: outcome = self.PASS elif result.returncode < 0: outcome = self.FAIL else: outcome = self.UNRESOLVED return (result, outcome) class BinaryProgramRunner(ProgramRunner): def run_process(self, inp=""): """Run the program with `inp` as input. Return result of `subprocess.run()`.""" return subprocess.run(self.program, input=inp.encode(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
cat = ProgramRunner(program="cat") print (cat.run("hello"))
输出
讯享网ubuntu@ubuntu:~/Desktop/pwn/fuzz$ python3 test1.py (CompletedProcess(args='cat', returncode=0, stdout='hello', stderr=''), 'PASS'
Fuzzer类
fuzzer生成数据并送至runner
class Fuzzer(object): def __init__(self): pass def fuzz(self): """Return fuzz input""" return "" def run(self, runner=Runner()): """Run `runner` with fuzz input""" return runner.run(self.fuzz()) def runs(self, runner=PrintRunner(), trials=10): """Run `runner` with fuzz input, `trials` times""" # Note: the list comprehension below does not invoke self.run() for subclasses # return [self.run(runner) for i in range(trials)] outcomes = [] for i in range(trials): outcomes.append(self.run(runner)) return outcomes class RandomFuzzer(Fuzzer): def __init__(self, min_length=10, max_length=100, char_start=32, char_range=32): """Produce strings of `min_length` to `max_length` characters in the range [`char_start`, `char_start` + `char_range`]""" self.min_length = min_length self.max_length = max_length self.char_start = char_start self.char_range = char_range def fuzz(self): string_length = random.randrange(self.min_length, self.max_length + 1) out = "" for i in range(0, string_length): out += chr(random.randrange(self.char_start, self.char_start + self.char_range)) return out
创建一个模糊器
讯享网random_fuzzer = RandomFuzzer(min_length=20, max_length=20) for i in range(10): print(random_fuzzer.fuzz())
ubuntu@ubuntu:~/Desktop/pwn/fuzz$ python3 test2.py #+;"09*81.))9&-+ #3< *?2!*>$5,379 511' %? (6)="4 -0*/.,<8)'#(& -))+7,/;43>?#);70/&. #05? 42%!=*9#) $* 0&2?5(%#6 8:)$4-1 <4== (*9(2<=(+#:-,)$ '>97(,'.3;-,/%27+!1' 1'04>1/>9*.?/0>+%4(% *5*;$:6-9?8$61+9$4/!
以cat为例,生成输入发送的cat中
讯享网random_fuzzer = RandomFuzzer(min_length=20, max_length=20) for i in range(10): inp = random_fuzzer.fuzz() result, outcome = cat.run(inp) assert result.stdout == inp assert outcome == Runner.PASS print(random_fuzzer.run(cat))
ubuntu@ubuntu:~/Desktop/pwn/fuzz$ python3 test2.py (CompletedProcess(args='cat', returncode=0, stdout='(92: ;;!80;6+3.8?;<;', stderr=''), 'PASS')
使用runs可以重复执行模糊测试多次
讯享网print(random_fuzzer.runs(cat,10))
ubuntu@ubuntu:~/Desktop/pwn/fuzz$ python3 test2.py [(CompletedProcess(args='cat', returncode=0, stdout='5;?9+"=,0-6>\'6 ?,#1:', stderr=''), 'PASS'), (CompletedProcess(args='cat', returncode=0, stdout="%(0;7-+*07=,934=3 '9", stderr=''), 'PASS'), (CompletedProcess(args='cat', returncode=0, stdout='$>>7&-<6-$+(> <0=+;"', stderr=''), 'PASS'), (CompletedProcess(args='cat', returncode=0, stdout='-(,;9!$!:&,3=03 >85)', stderr=''), 'PASS'), (CompletedProcess(args='cat', returncode=0, stdout='*#:/"7(/+?00:!1;$"?#', stderr=''), 'PASS'), (CompletedProcess(args='cat', returncode=0, stdout='8-/*987/#5%0.#1<"5:5', stderr=''), 'PASS'), (CompletedProcess(args='cat', returncode=0, stdout="47;!)'!447;<29'>> 6!", stderr=''), 'PASS'), (CompletedProcess(args='cat', returncode=0, stdout='1(:$/7)10*;/%-$ :$99', stderr=''), 'PASS'), (CompletedProcess(args='cat', returncode=0, stdout='&?8);"!;!4&.762/)$0-', stderr=''), 'PASS'), (CompletedProcess(args='cat', returncode=0, stdout='\'9%"=.162\')313)1(8+-', stderr=''), 'PASS')]

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/122978.html