标题: [AFL]如何恢复 fuzzing 会话 分类: Fuzzing 创建: 2022-10-21 10:55 修改: 2022-10-24 23:55 链接: http://0x2531.tech/fuzzing/202210211055.txt -------------------------------------------------------------------------------- afl-fuzz 支持两种恢复会话方式,一种是就地恢复(in-place),另一种是非就地恢复 (non-in-place)。就地恢复方式通过传参控制,非就地恢复方式通过目录控制。 下面分别介绍这两种方式,先是就地恢复方式,再是非就地恢复方式。 就地恢复会话非常简单,只需在正常 afl-fuzz 命令行基础上,将输入目录选项 -i 传入参数 -(短横 线)。afl-fuzz 会开启全局就地恢复会话标识 in_place_resume = 1 其工作机制是怎样的呢? 首先,是在初始化输出目录时(函数 setup_dirs_fds)。如果当前 fuzzing 为全新会话(即指定的输 出目录不存在),则直接报错退出。原因是输出目录中存放着 fuzzing 过程的中间数据,没有这些数据将 无法完成恢复。换句话说,输出目录代表着 fuzzer 实例(并行测试下目录名为 fuzzer ID),没有之前 的实例,恢复也就无从谈起。 如果已经存在输出目录,则进行一系列删除操作(函数 maybe_delete_out_dir)。首先,判断输出目录 是否为锁定状态,锁定意味着有其它同名的 afl-fuzz 实例正在运行,这种情况无论是正常会话(即设置l 了正常的输入目录)还是恢复会话都将中断退出。接着,尝试读取目录下的 fuzzing 状态文件(文件 fuzzer_stats),如果成功的话,尝试获取文件中 start_time 和 last_update 字段值,获取不到 则正常会话或恢复会话中断退出。如果正常获取到,在正常会话情形下会比较 start_time 和 last_update 的时间间隔,如果超过 25 分钟(默认),为了防止数据丢失,afl-fuzz 不会自动删除数 据,此时会话中断需人工介入调整命令行传参。在恢复会话情形下,则无需关心该时间间隔,然后将 _resume 目录设置为输入目录并将 queue 目录更名为 _resume(** 这里是就地恢复会话方式的核心 **)。接下来,就开始删除输出目录子目录及其下的文件,包括: - 目录 /queue/.state/deterministic_done 下前缀为 id: 的文件和目录自身 - 目录 /queue/.state/auto_extras 下前缀为 auto_ 的文件和目录自身 - 目录 /queue/.state/redundant_edges 下前缀为 id: 的文件和目录自身 - 目录 /queue/.state/variable_behavior 下前缀为 id: 的文件和目录自身 - 目录 /queue/.state - 目录 /queue 下前缀为 id: 的文件和目录自身 如果 crashes 为空,则直接删除目录。否则,将 crashes 目录更名为带时间戳后缀的目录。 if (in_place_resume && rmdir(fn)) { // rename logic } 这里利用对非空目录执行 rmdir 报错返回-1,空目录则成功执行返回0的特性,直接将 rmdir 作为条件 表达式的做法,还是挺值得借鉴的。 接着,对 hangs 目录执行同样的操作。最后,就是删除输出目录下的文件,包括: - .cur_input --- afl-fuzz 运行时测试用例文件 - fuzz_bitmap --- bitmap 文件 - plot_data --- afl-plot 绘图数据文件 需要注意的是,fuzzing 状态文件 fuzzer_stats 在就地恢复会话情形下会保留,原因继续往下看就明 白了。 至此,删除工作就已结束,下面就是初始化输出目录。这部分内容,就地恢复会话和正常会话一样,逻辑也比 较简单,就是去创建必要的目录、文件以及文件描述符(fd),不再赘述。 接下来,在为测试用例创建硬链接时(函数 pivot_inputs),通过队列中测试用例文件名特征判断当前是 否为恢复会话,如果是则设置全局恢复会话标识 resuming_fuzz = 1(包括就地恢复和非就地恢复) 并 在输出目录下的 queue 目录下为每个测试用例生成同名的文件(还记得之前将 queue 目录重命名为输入 目录的操作吗?)。同时,更新测试用例最大深度。最后,删除临时的 _resume 目录。 接下来,在自动设置超时时间时(函数 find_timeout),该函数只作用于恢复会话情形。原理是直接从状 态文件(文件 fuzzer_stats)中获取 exec_timeout 字段值作为目标程序超时时间,这样就不用再通 过内置算法去获取了。同样,在确定恢复会话起始位置时(函数 find_start_position),也是类似的操 作,只不过获取的是 cur_path 字段值。 最后,在校准测试用例时(函数 calibrate_case),恢复会话情形下的超时时间会宽松一些。 至此,就地恢复会话方式就介绍完了,下面再继续介绍非就地恢复会话方式。 非就地恢复会话通过向输入目录拷贝之前 fuzzing 会话输出目录生成的 queue 目录的方式使用,然后 afl-fuzz 在读取测试用例时(函数 read_testcases)自动检测,如果输入目录下存在 queue 目录, 则将输入目录指向该目录,这样就达到了恢复会话的目的。除了使用方法不同外,工作机制和就地恢复是一样 的。 从上述表述可知,无论是就地恢复还是非就地恢复,其得以工作的核心在于 queue 目录。queue 目录下存 储着以特定模式命名的测试用例文件,包括初始测试用例和经过变异的测试用例,这些测试用例具有唯一的执 行路径。另外,还包括一个隐藏的 .state 子目录,该目录下存储着 fuzzing 过程的中间状态文件,如 哪些测试用例文件已完成确定性变异策略测试等。 如何选择使用哪种恢复方式呢?一般原则是,在本地单机环境下 fuzzing 时,选择就地恢复方式,使用简 单。在多机器或多人协同 fuzzing 时,选择非就地恢复方式,以共享测试用例。