afl-fuzz.c 的代码阅读。
calculate_score
classify_counts
common_fuzz_stuff
这个函数会处理输入,将输入写入文件并执行,在执行后处理错误情况。如果出错返回 1。
如果有后处理逻辑则处理输入:
将输入写入文件并调用 run_target 执行:
处理错误情况:
- 如果程序停了就返回 1;
- 如果错误代码是
FAULT_TMOUT
(超时错误):
subseq_tmouts
(连续超时数量)加一;
- 如果连续超时数量大于
TMOUT_LIMIT
(默认为 250),将 cur_skipped_paths
加一并返回 1。
- 如果没有超时,就将
subseq_tmouts
设置为 0;
skip_requested
参数在 handle_skipreq 中设置,用户向程序发送 SIGUSR1 信号时触发,如果触发了 SIGUSR1 信号,跳过后续的处理并对 cur_skipped_paths
加一,之后返回 1。
调用 save_if_interesting 继续处理其他错误,如果输入发现了新路径,就对 queued_discovered
加一。
更新界面。
fuzz_one
见 fuzz_one
handle_skipreq
处理 SIGUSR1 信号
handle_timeout
处理 SIGALRM 信号,如果是以子进程的方式启动的,就 kill 掉子进程;如果是以 fork server 形式启动的,则 kill 掉 forkserver 启动的进程。
init_forkserver
maybe_add_auto
添加自动识别的 tokens。
- 跳过相同字节的情况;
- 跳过和内置有趣值相同的情况;
- 跳过和 extra 数组中已有 token 相同的情况;
如果收集到了足够的 token 或者用户不想自动识别就不收集;
跳过相同字节的情况:如果所有的字节都相同就跳过;
跳过和内置有趣值相同的情况,包括内置有趣值取反的情况,仅比较 len=2
或 len=4
的情况:
检查 extra 数组,跳过与 extra 数组中已有 token 相同的情况。
因为 extra 数组中的值是按照长度从小到大排序的,因此可以快速判断
检查 a_extra 数组,如果发现这个 token 已经被发现过了,就给这个 token 的计数器加一,并跳转到排序逻辑:
在跳过了众多可能重复的情况后,这里到达了添加新 token 的逻辑。如果发现的 token 太多,就随机从列表的后半部分替换掉一个 token。
最后对自动发现的 token 数据先按照命中次数排序,再按照 token 长度排序。在这里的细节是:
- 对于命中次数排序,会对
a_extras_cnt
数量的 token 进行排序,默认 a_extras_cnt
小于 500(USE_AUTO_EXTRAS*10
);
- 对于 token 长度排序,会对
MIN(USE_AUTO_EXTRAS, a_extras_cnt)
数量的 token 排序,默认 USE_AUTO_EXTRAS
值为 50,因此最多对前 50 个 token 按照长度排序。
这就是为什么在发现 token 的时候会随机驱逐 a_extras
后半部分的 token,事实上前 50 个 token 才是最有用的。
run_target
清空共享内存,并设置内存屏障:
如果在 dumb 模式下或没有开启 forkserver,就 fork 出一个子进程,然后让子进程 execv 目标程序。这一段代码和 init_forkserver 中的代码有一些重复:
如果执行失败,就将 trace_bits
标记为 EXEC_FAIL_SIG:
如果不在 dumb 模式,向 fsrv_ctl_fd
(控制管道)写入 prev_timed_out
的值,接下来从 fsrv_st_fd
(状态管道)读子进程的 pid:
配置超时,如果超时了,那么 AFL 会发送 SIGALRM 信号,进入到 handle_timeout 的处理逻辑。
等待子进程结束:
如果不在 dumb 模式且是 forkserver,会从状态管道读取程序的退出原因:
如果子进程未停止,则将子进程 pid 设置为 0。
取消定时器:
执行次数加一:
设置内存屏障,处理覆盖率信息,这里通过 WORD_SIZE_64
调用处理不同架构宽度下的 classify_counts 函数:
设置 prev_timed_out
的值为 child_timed_out
:
根据 status 的值向调用者返回结果:
WIFSIGNALED(status)
若为异常结束子进程返回的状态,则为真
WTERMSIG(status)
取得子进程因信号而中止的信号代码
- 如果child_timed_out为1,且状态码为
SIGKILL
,则返回FAULT_TMOUT
- 否则返回
FAULT_CRASH
处理使用 MSAN 时会遇到的特殊情况:
- 如果是
dumb_mode
,且 trace_bits
为 EXEC_FAIL_SIG
,就返回 FAULT_ERROR
- 如果
timeout
小于等于 exec_tmout
,且 slowest_exec_ms
小于 exec_ms
,设置 slowest_exec_ms
等于 exec_ms
save_if_interesting
trim_case
参考资料