初试py-afl-fuzz

github: https://github.com/jwilk/python-afl

入门:

需要在/etc/environment中添加

1
2
export AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1
export PYTHON_AFL_PERSISTENT=1

保证正常使用, 第一个是啥忘记了, 第二个是确认开启使用持久模式. (第一个想起来了!是要执行sudo bash -c 'echo core >/proc/sys/kernel/core_pattern'

最简单的用法是py-afl-fuzz -i input_dir/ -o results_dir/ -- python3 script.py.

使用-C选项就可以实现不用生成种子输入开始随机fuzzing了,不过需要在input_dir/下生成一个只有一个空格的文件input0.txt.

其中, inputs_dir目录中给定的输入用例集合要尽可能, 而且有趣, 这里有趣是指每个输入都应该能代表一类输入, 而非类似.

results_dir目录结构如下:

results/
├── crashes/
├── fuzz_bitmap
├── fuzzer_stats
├── hangs/
├── plot_data
└── queue/

crashes目录保存fuzz so far得到的所有craches.

plot_data文件中保存fuzz过程中探索到的路径, unique crash的个数和最大深度的变化.

给个例子, 文件hack.py中有如下代码:

1
2
3
4
def hack(a, b):
if a == 234 and b == 102:
raise Exception()
return 0

我们想通过fuzz使其抛出异常, 即找到输入为a=234, b=102的情况.

1
2
3
4
5
6
7
8
9
10
11
12
import afl, os, sys, hack

afl.init()

sys.stdin.seek(0)
in_str = sys.stdin.read()
a, b = in_str.strip().split()
a = int(a)
b = int(b)
hack.hack(a, b)

os._exit(0)

inputs/中预给的输入文件input0.txt如下:

1
1 2

fuzz1.png

发现两个能引发异常的输入, 分别是\B1 2q 2, 没有找到a=234, b=102. 在有限时间内找到这个corner case很困难, 所以可以在字典中一些常用的可能引发错误的输入, 例如创建一个目录dicts, 创建dict0.dict, 内容为:

1
234 102

fuzz2.png

很快就引发了第三个unique crash.

接下来使用同样的方法对pydantic.datetime_parse下的from_unix_seconds方法进行fuzz, 很快找出三个unique crash, 分别是\B1, q, -11111111111111111111. 10分钟后发现第四个unique crash: 33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333.

1
2
3
4
5
6
7
8
9
10
import afl, os, sys, pydantic

afl.init()

sys.stdin.seek(0)
t = sys.stdin.read().strip()
t = int(t)
pydantic.datetime_parse.from_unix_seconds(t)

os._exit(0)

还有一种使用afl.loop(N)的用法,N表示每轮测试用例个数, 称为持久模式. 这种用法不用在import后写afl.init(), 最快的持久模式不应该包含可能被之前执行影响而改变的全局的状态. 这种用法可以用于将之前的执行结果重用在当前状态的场景下.