read_testcases
nl_cnt
是统计inputs
目录下的目录数,然后从这些目录中读取测试用例加入
cull_queue
这个函数好像也没看懂是啥。。
先把队列中的实体favored全置为0
然后。。。
好像是精简输入数目
calculate_score
计算得分(对于输入的fuzz次数)
run_target
执行目标应用,监控timeouts。返回状态信息,调用的程序会更新
trace_bits[]
calibrate_case
从函数名直接翻译是校准用例。函数注释说的是:
校准一个新的测试用例,在处理输入字典时尽早警告有关测试用例的问题;并且在发现新路径时检测变量行为等。这个函数里面调用了
update_bitmap_score
虽然看完我还是不知道是干啥的
update_bitmap_score
当发现一个新路径时,调用这个方法判断这个路径是否比已有路径more “favorable”。这个”favorables”的意图是最小化路径的集合来触发至今已发现到的bitmap的bits
save_if_interesting
检查fuzzing期间
execve()
的结果是否interesting,如果是则保存或把输入放到队列中,返回1,反之返回0
技术细节
详见technical_details.txt
4 Culling the corpus
算法根据执行延迟和文件大小赋予队列中的每个实体一个得分;然后选择每个tuple得分最低的候选:
- 找到下一个没有在temporary working set中的tuple
- 定位这个tuple的winning queue entry
- 在working set中注册该entry中所有的tuples
- 如果还有working set中还有遗漏的tuples,则返回1.
通常用这种方法生成的”favored”种子集合比原来的集合小5-10倍。剩下的”Non-favored”不是直接弃用,而是以一定概率跳过:
- 如果有新的,yet-to-be-fuzzed的种子在队列中,那么non-favored entries以99%概率跳过该种子
- 若没有新的
- 若当前non-favored之前fuzz过,则95%的概率跳过该种子
- 否则,以75%的概率跳过该种子
实证证明,这个策略实现了队列循环速度和种子多样性的平衡
5 剪枝输入
trim不保证输入剪到最优,而且在精度和execve()
数之间找到一个均衡点,保证tracemap的checksum不受影响。
剪枝算法具体为:
- 尝试把大块的数据比特置为0(zero large blocks of data with large stepovers),在之后这可以减少执行的次数
- 通过不断减少块大小和步长尝试块删除,二进制搜索风格
- 通过统计unique characters进行alphabet normalization和尝试批量替换零值
- 最后,尝试在non-zero的字节上进行normalization
这里zero过程不用0x00,afl-min使用ASCII字符’0’。这样做的原因是修改一般不会干扰文本解析。
6 Fuzzing策略
确定性的策略包括:
- 位翻转,使用不同的长度和步长
- 翻转单个bit
- 一行翻转两个bit
- 一行翻转四个bit
- 加和减一个小整数
- 插入一个有趣的整数(0,1,INT_MAX等)
使用这些确定性变异原因是non-crashing和crashing输入间可能只有微小的差距
非确定性的步骤包括stacked位翻转,插入,删除,算数(加/减)和不同测试用例的拼接
8 crashes去重
仅仅查看faulting address可能导致完全不相关的两个issue聚类到一起了,因为fault发生在同一个公共的哭函数中(比如strcmp
,strcpy
等)
在AFL中,认为crash是unique的当下面任意一个条件成立:
- crash trace包含了一个之前没有出现过的tuple
- crash遗漏了一个之前经常出现的tuple
9 调查crashes
afl-fuzz提供crash exploration mode
对已知错误的测试输入进行fuzz
与普通的queue entries
不同,crashing inputs不会trim,他们会保持原状方便与parent比较(non-crashing entry)。
参考文献:afl-fuzz: crash exploration mode
crash exploration mode
下,可以让程序接收一个crashing test case,afl-fuzz从crashing的种子出发开始跑。然后程序就会看在保持程序crash的状态下能运行多远。让程序停止产生crash的变异会被排除。