目前大部分 Fuzz 工具仅支持标准输入或命令行参数作为输入,而 IoT 设备 Fuzz 的主要对象为网络程序,需通过 Socket 进行输入输出,这构成了技术难点。
在掌握 Harness 编写技术后,可利用该方案对 IoT 设备的 Socket 通信程序进行 Fuzz 测试。
本文以 ASUS RT-N56U 设备为例进行阐述。目标 Fuzz 程序为 httpd,通过逆向分析可知,处理 HTTP 流程的代码位于 handle_request 函数中,该函数的第一个参数是 Socket 文件描述符,第二个参数是一个包含连接信息的结构体。
在 handle_request 函数中,通过 fgets 函数获取 HTTP 请求,如下所示:
1 | if ( !fgets(v95, 4096, a1) ) |
基于此,可以构建如下思路:
handle_request 所需的参数。基于上述逻辑,编写 Fuzz 函数如下:
1 | void fuzz(const char *filename) |
首先,Fuzz 函数的参数源于命令行参数。接着,编写 create_memfd_from_file 函数,将文件内容转换为可读写的文件描述符,实现逻辑如下:
1 | // 兼容封装:优先使用 memfd_create 系统调用;不可用时回退到匿名临时文件 |
随后,使用 fdopen 函数将 int 类型的文件描述符转换为 FILE* 类型。最后构造 fake_item 结构体,作为 handle_request 的第二个参数,fake_item 结构体定义如下:
1 | struct qm_trace { |
此外,在调试模式下,还需支持输出 HTTP 请求结果。
Hook main 函数的方法如下:
1 | int __uClibc_main( |
由于大多数 IoT 设备使用 uClibc 库,因此需要 Hook __uClibc_main 函数。
针对不同设备,需根据具体情况和架构进行差异化处理。在本例中,目标 httpd 程序在监听 Web 端口之前,也会执行初始化操作,如设置管理员账号密码等,因此需执行同样的初始化动作。如下所示:
1 | typedef void* (*HANDLE_RESET_LOGIN_DATA) (void); |
Harness 编写思路至此结束,后续需针对具体 IoT 程序进行差异化操作,以确保程序正常运行。
本例中,httpd 仅需解决 NVRAM 依赖问题。IoT 设备通常使用 NVRAM 存储配置信息,但运行 Fuzz 的主机通常不包含 NVRAM 驱动,不过部分操作系统可能存在 NVRAM 驱动包,可自行安装。为使 httpd 正常使用 NVRAM,存在以下三种方案:
nvram_get, nvram_set 等。接下来,需获取设备的配置文件,或导出设备上的 NVRAM 数据并导入当前机器,以实现更真实的仿真。
鉴于代码篇幅较长,此处仅展示用法,如下所示:
1 | hexdump -C /var/lib/soft_nvram.bin |tail |
通常情况下,上述 NVRAM 程序尚无法正常运行,因仍缺关键一步。该架构下的 NVRAM 驱动调用需使用 ioctl,而 QEMU 对 ioctl 调用有独立处理逻辑,并非默认使用系统调用。QEMU 默认无法识别 NVRAM 的 ioctl 调用方法。因此,需对 QEMU 进行相应修改,经研究,较为简便的修改方案如下:
1 | diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h |
然而,测试发现 QEMU 5.X 版本中 NVRAM 读写操作会因未知原因失败,而 QEMU 10.X 版本则能成功运行。鉴于 QEMU 代码库庞大,排查难度较高,因此考虑采用 QEMU 10.X 进行 AFL Fuzz。
目前公开的 QEMUAFL 支持的最高版本为 QEMU 5.X。若要使用 QEMU 10.X,需自行进行 Patch 适配。相关的 Patch 方案及过程将在后续文章中进行分享。
]]>前文分析了 AFL++ 对 QEMU 5.X 版本的补丁内容,以及在 QEMU 5.X 版本中通过 ioctl 操作 nvram 时可能出现的未知 Bug。鉴于此,计划将 qemuafl 的补丁迁移至 QEMU 的最新版本中。
QEMU 当前最新版本为:v10.1.3。
在尝试将 qemuafl 的补丁迁移至该版本时,遇到了因 QEMU 架构变更导致的严重问题。
在新版本的 QEMU 中,为了加速文件的编译速度,对代码结构进行了区分。例如,一部分代码的编译只依赖主机架构,而另一部分代码的编译则依赖目标架构。
以 accel/tcg 目录下的代码为例,在最新版的 QEMU 中,该部分被认为仅需考虑主机架构,因此只需编译一次。例如在首次编译 mips 架构的 QEMU 后,再次编译 arm 架构时,这部分代码无需重新编译,仅需编译与架构相关的代码。
然而,qemuafl 的补丁在仅需考虑主机架构的目录代码中引入了对目标架构参数的依赖,从而导致编译失败。
研究发现,自 v10.1 版本起,QEMU 已完全变更为新架构。若在此版本中添加 AFL 相关补丁,需要进行大量修改。进一步分析发现,v10.0.6 版本的 QEMU 架构尚未完全变更,若在该架构中进行相关补丁适配,可节省大量时间。
以下是需要修改的代码部分说明。
首先是 accel/tcg 目录下的代码,accel/tcg/meson.build 文件的部分代码如下所示:
1 | common_ss.add(when: 'CONFIG_TCG', if_true: files( |
common_ss 集合中的文件表明该部分代码是通用代码,无需考虑目标架构。tcg_specific_ss 集合中的代码为需要考虑目标架构的 tcg 代码。
在 tcg-runtime.h 的代码中,补丁内容大量使用了 tl 类型,如下所示:
1 | DEF_HELPER_FLAGS_2(qasan_load1, TCG_CALL_NO_RWG, void, env, tl) |
将宏展开后可以发现,tl 类型的定义位于 include/exec/helper-head.h.inc 文件中,如下所示:
1 |
dh_alias(t) => dh_alias_tl,该类型只有当存在 COMPILING_PER_TARGET 定义时,才会被声明。
通过最外层的 meson.build 代码可以看到,只有在编译需要考虑目标架构的代码时,才会设置该值,代码如下所示:
1 | ... |
因此,需要将 tcg-runtime.c 和 tcg-runtime.h 文件中 AFL 补丁的代码提取出来,放入 tcg-runtime_afl.c 和 tcg-runtime_afl.h 文件中。
在这部分的补丁代码中,受影响最大的是 TCP Helper 函数,该部分代码的实现逻辑在前文中已说明,此处不再赘述。
在 tcg-runtime_afl.c 文件中,代码主要是 Helper 函数的实现,为了防止 missing-prototypes 错误,需要添加以下头文件:
1 |
经测试,需包含的最简头文件如下所示:
1 |
最后修改 accel/tcg/meson.build,如下所示:
1 | tcg_specific_ss.add(files( |
QASAN核心代码位于tcg目录下,该部分代码也发生了变化,代码从tcg/tcg-op.c文件迁移到了tcg/tcg-op-ldst.c文件中。并且tcg目录下的代码都为通用代码,不考虑目标架构,但是AFL patch的头文件需要考虑目标架构,因此还需要对头文件进行修改。
QEMU v10.0.6 不存在以下配置,需从 build_qemu_support.sh 文件中删除:
1 | --disable-blobs |
在 qemuafl/asan-giovese-inl.h 文件中需要添加一个声明,如下所示:
1 | void queue_signal(CPUArchState *env, int sig, int si_type, |
tcg_const_tl -> tcg_constant_tltcg_const_ptr->tcg_constant_ptrtcg_constant_tl 得到的值不再需要使用 tcg_temp_free 释放,该 free 函数也已不再存在。因此需要搜索所有 tcg_temp_free 调用并将其注释。考虑到 AFL 补丁代码中该部分可能仍需释放,因此进行了以下修改:1 | // include/tcg/tcg-op.h |
针对一些编译警告的修复如下:
1 | ../accel/tcg/cpu-exec.c:586:10: warning: no previous prototype for ‘ijon_simple_hash’ [-Wmissing-prototypes] |
主要查看 QEMU V10.1.3 版本的三个配置文件,如下所示:
accel/tcg/meson.build 代码如下所示:1 | if not have_tcg |
从上述代码可以看出,在当前架构,配置文件不区分 common_ss 通用代码和 tcg_specific_ss tcg 特定架构代码。而是分成 user_ss user 模式编译的代码和 system_ss system 模型编译的代码。
accel/meson.build 代码如下所示:1 | common_ss.add(files('accel-common.c')) |
meson.build 部分代码如下所示:1 | user_ss = ss.source_set() |
在当前架构中,accel/tcg/ 目录下的代码在第一次编译的时候都会被编译到 libuser 库中,无需考虑目标架构。但是该目录下有大量 AFL 补丁的代码,且 AFL 的代码有大量需要考虑目标架构。这就导致 AFL 的补丁代码无法直接迁移到该版本中。
不过,查看accel/meson.build的编译代码可以发现,仍然存在 specific_ss 配置,只需将 AFL 需要依赖目标架构的代码,迁移到 accel 目录下,然后把文件加入到 specific_ss 当中,即可在 QEMU V10.1.3 版本中成功编译 AFL 补丁版本。
测试版本为 QEMU V10.0.6 linux-user 模式,目标架构为 mips。(V10.1.3 版本仍然存在)
成功编译 V10.0.6 版本的 qemuafl 后,开始尝试运行 afl fuzz。
前文曾提及 AFL 的 qemu 模式运行逻辑,默认情况下采用 forkserver 模式。在 fuzz 程序首次运行时,程序能正常运行,但在第二次执行到某个指令时,却会重新跳转回 main 函数,导致执行失败。
最终通过 QEMU 的 exec_tb、translate_block 日志信息定位到了问题根源。
测试脚本如下所示:
1 | #!/usr/bin/env python3 |
调试命令如下所示:
1 | QEMU_LOG_FILENAME="/tmp/debug.txt" QEMU_LOG="in_asm,out_asm" python3 afl-py.py |
出现错误的流程大致如下:
afl_wait_tsl 函数,在父进程的内存空间中同步翻译指令块。这样,在下次 fork 出的子进程执行到同样的块时,就无需再翻译一次,可以节省代码执行时间。经定位,错误代码位于 libuClibc 库的 strchr 函数中,关键代码如下所示:
1 | .text:00038FA0 03 00 43 30 andi $v1, $v0, 3 |
按照 QEMU 分块的逻辑,0x00038FAC - 0x0039000 算一个代码块。
但是在 target/mips/tcg/translate.c 文件的 mips_tr_translate_insn 函数中,有以下代码:
1 | static void mips_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) |
在上面的代码中 TARGET_PAGE_SIZE=4096=0x1000,通过注释也能知道,该部分代码是代码块按照页进行分割,一页的大小就是 0x1000。
MIPS 指令集中存在 delay slot(延迟槽)机制,即在执行跳转指令之前,会先执行下一条指令。
1 | 比如以下指令 |
但根据 QEMU 的分页机制,0x00038FAC - 0x0039000 块将会被分成:0x00038FAC - 0x0038FFC 和 0x0039000 两个代码块。
不过,QEMU 的开发者也考虑到了这种情况,因此有以下代码:
1 | static void mips_tr_tb_stop(DisasContextBase *dcbase, CPUState *cs) |
当 is_jmp == DISAS_TOO_MANY 时,首先保存上下文的 cpu 状态信息,然后设置当前块的下一跳为 delay slot 指令。
从这点看,QEMU 本身逻辑并无问题。分页是为了减少内存开销,同时也处理了被分开的代码块。
但是和 AFL 结合后,就会导致兼容性问题,产生 bug。下面将通过 QEMU 的日志信息,来展示该 BUG。
首先,第一个子进程在翻译代码块时的日志如下所示:
1 | IN(子进程): |
观察上述指令发现,QEMU 翻译的跳转目标地址出现了偏差:movl $0x402ac0, 0x80(%rbp)。0x402ac0 地址为 httpd 程序的 _start 函数起始地址。
经过详细分析与调试,明确了该 Bug 的成因。
将原本的分支指令块设为 TB_A,把被分片的 delay slot 指令块设为 TB_B。
当 QEMU 翻译完 TB_A 代码块后,将会执行 mips_tr_tb_stop 函数,然后调用 save_cpu_state 函数:代码如下所示:
1 | static inline void save_cpu_state(DisasContext *ctx, int do_save_pc) |
因为在 mips_tr_tb_stop 函数中调用的是 save_cpu_state(ctx, 0);,因此 do_save_pc=0。最终只执行了三句指令:
1 | tcg_gen_movi_i32(hflags, ctx->hflags); |
这三句指令可以对应到之前 QEMU 日志中的指令,如下所示:
1 | # 设置hflags |
当前代码块翻译完成后,接下来翻译 TB_B 块。在翻译块的开头调用 mips_tr_init_disas_context 函数进行上下文变量初始化,将会调用 restore_cpu_state 函数,恢复 btarget 值,相关代码如下所示:
1 | static inline void restore_cpu_state(CPUMIPSState *env, DisasContext *ctx) |
上述代码是导致本次 Bug 的另一个核心点,下文将详细说明。在初始化完上下文信息后,将会调用 mips_tr_translate_insn 翻译代码,首先翻译 add 指令。翻译完成后,因为 is_slot=True,所以将会调用 gen_branch(ctx, insn_bytes); 函数生成分支跳转代码。关键代码如下所示:
1 | static void mips_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) |
该部分代码可以和上面 QEMU 日志信息进行一一对应,如下所示:
1 | tcg_gen_brcondi_tl(TCG_COND_NE, bcond, 0, l1); |
至此,相关流程已梳理完毕。该 Bug 情况总体概括如下:AFL 在翻译 TB_B 代码块时,由于 ctx->btarget 的值错误地等于 0x402ac0,导致翻译出来的指令为:movl 0x402ac0, 0x80(%rbp)。在代码执行到该分支后,将会错误地跳转回程序的 _start 函数,最终导致程序崩溃。
总体梳理一下该 BUG 的成因:
TB_A 和 TB_B。delay slot 的情况,因此在翻译完 TB_A 后,会调用 save_cpu_state 函数保存跳转地址信息。save_cpu_state 函数保存 btarget 的方案是翻译成 TCG 代码(movl $0x2b506fd0, 0x1b20(%rbp))。restore_cpu_state 函数恢复 btarget 的方案是从 env->btarget 上下文中获取。上述的 3, 4 两点就产生了冲突,最终导致 BUG 的产生。
在原版的 QEMU 中,因为性能考虑,不会一次性把所有代码都翻译成 TCG 指令。而是运行到哪,翻译到哪。因此在翻译完 TB_A 指令后,将会执行 TB_A 指令。TB_A 翻译出的 TCG 指令最终会跳转到 TB_B 地址,因此下一步将会翻译 TB_B 指令。由于已经执行了 TB_A TCG 指令中的 movl $0x2b506fd0, 0x1b20(%rbp) 指令,因此 env->btarget 已经被成功设置成正确的 btarget 地址了。所以在后面翻译 TB_B 指令的流程中没有出错。
在 qemuafl 中,第一个子进程的翻译过程就是按照 QEMU 原版的逻辑来运行,因此不会出错。但是,随后父进程将会跟着开始翻译代码块,却不执行,这就导致 env->btarget 无法被正确设置,最终代码出错,程序崩溃。
简单来说,原版 QEMU 的流程为:翻译TB_A -> 执行TB_A -> 翻译TB_B -> 执行TB_B。
qemuafl 父进程的流程为:翻译TB_A -> 翻译TB_B -> ...。
随后对原版 qemuafl(QEMU V5 版本)进行的简要分析显示,该 Bug 仍然存在,如下所示:
1 | OUT: [size=114] |
0x7fe6dc01c290 地址的指令仍然出错,至于为何未能触发该 Bug,需要进一步调试分析,感兴趣的读者可自行探索。
delay slot 进行分片,patch 代码如下所示:1 | /* |
该修复方案优点在于快捷简单,缺点是可能会影响代码分片,从而对 QEMU 执行效率产生一定影响。
gen_branch函数的逻辑,如下所示:1 | gen_goto_tb(ctx, 0, ctx->btarget); |
该修复方案同样快捷简单,把所有分支跳转的btarget都修改成从内存中获取。缺点是性能开销大,这步骤需要进行内存寻址操作,性能开销远大于硬编码。mips代码中肯定会存在大量分支跳转指令,这会大大影响的程序的执行效率。
gen_branch函数的逻辑,如下所示:1 | target/mips/tcg/translate.h 中: |
该方案由AI生成,修改起来稍微复杂一点,但是性能损失小。
]]>首先,下面列出 AFL对 QEMU 的修改目录:
1 | accel/tcg/cpu-exec.c | 1582 +++++++++++++++++ |
不过上面的目录并不完全,只针对了目标架构为 mips 的情况,不同架构的以下文件不会一样:
1 | linux-user/{arch}/cpu_loop.c |
首先,qemuafl目录下的文件为 AFL 相关的头文件,包含相关全局变量结构体类型声明等等。若需查看 AFL 对 QEMU 进行了哪些修改,可以通过检查该文件代码是否包含qemuafl目录下的头文件。
由于 AFL 的 QEMU 使用的是user-mode,因此应从 linux-user 目录下的代码开始分析。
linux-user/main.c文件的代码,patch 内容如下所示:1 | diff --git a/linux-user/main.c b/linux-user/main.c |
main.c 代码中添加的内容旨在让 QEMU 支持 QASan。关于 QASan,ChatGPT 的解释如下:
1 | QASan(QEMU Address Sanitizer) |
linux-user/elfload.c文件patch 内容如下所示:1 | diff --git a/linux-user/elfload.c b/linux-user/elfload.c |
elfload.c 文件主要用于读取目标 ELF 文件结构信息。AFL 在该文件中进行全局变量的初始化,比如获取该 ELF 文件的代码段访问。还可以通过AFL_EXITPOINT环境变量设置程序fuzz 的结束地址。还可以使用AFL_ENTRYPOINT环境变量设置fuzz 的入口地址。如果没设置,默认为 ELF 程序的代码起始地址。
linux-user/mmap.c文件patch 内容如下所示:1 | diff --git a/linux-user/mmap.c b/linux-user/mmap.c |
mmap.c 文件涉及内存管理。在 persistent 模式下,AFL 需要自动跟踪目标应用的所有 mmap 内存区域。
linux-user/signal.c文件patch 内容如下所示:1 | diff --git a/linux-user/signal.c b/linux-user/signal.c |
signal.c 文件主要处理信号相关内容。AFL 主要通过信号来判断目标程序是否 crash,比如:SIGILL, SIGFPE, SIGSEGV, SIGBUS。
然而,当目标程序自行编写信号处理函数时,AFL 可能无法捕获相关信号。因此,可通过
除此之外,还有添加跟QASan相关的代码,在 fatal signals 之前调用 QASan。
linux-user/syscall.c文件patch 内容如下所示:1 | diff --git a/linux-user/syscall.c b/linux-user/syscall.c |
syscall.c 文件主要处理系统调用相关指令。该补丁主要进行了三类修改:
linux-user/mips/cpu_loop.c文件patch 内容如下所示:1 | diff --git a/linux-user/mips/cpu_loop.c b/linux-user/mips/cpu_loop.c |
linux-user/{arch}/cpu_loop.c 文件包含 QEMU 执行客户端代码的主要函数。AFL 在该文件中添加的内容为:在persistent模式下,如果遇到exit或exit_group函数,并不会真正的退出,而是跳转到afl_persistent_addr地址。
接下来是accel/tcg目录下的代码,在默认情况下,QEMU 仿真的流程为:
1 | Guest CPU 指令 → TCG IR(中间指令) → Host 机器码 → 执行 |
TCG IR 翻译为主机指令的操作即在该目录下完成。
accel/tcg/tcg-runtime.h文件patch 内容如下所示:1 | diff --git a/accel/tcg/tcg-runtime.h b/accel/tcg/tcg-runtime.h |
accel/tcg/tcg-runtime.h头文件主要是用来声明 TCG Helper函数,AFL 在该文件中新增了一些 AFL 相关的Helper 函数。
下面介绍 TCG Helper 函数的结构。
1). 在include/exec/helper-proto.h.inc函数中定义了Helper 函数声明的宏。
DEF_HELPER_FLAGS_x为Helper 函数宏,其中 x 标识有几个参数,比如:DEF_HELPER_FLAGS_2表示该函数有两个参数。
在helper-proto.h.inc文件中的宏为:
1 |
若将 DEF_HELPER_FLAGS_2(qasan_load2, TCG_CALL_NO_RWG, void, env, tl) 展开,函数声明为:void helper_qasan_load2(CPUArchState *, i32) __attribute__((noinline));。
2). 在include/exec/helper-info.h.inc函数中定义了 Helper 函数相关的结构体宏。
在helper-info.h.inc文件中的宏为:
1 |
把该宏展开,如下所示:
1 | TCGHelperInfo helper_info_qasan_load2 = { |
3). 在include/exec/helper-gen.h.inc函数中定义了 Helper 函数调用函数宏。
在helper-gen.h.inc文件中的宏为:
1 |
把该宏展开,如下所示:
1 | extern TCGHelperInfo helper_info_qasan_load2; |
最后就是HELPER 函数的实现,如下所示:
1 | void HELPER(qasan_load2)(CPUArchState *env, target_ulong addr) { |
当需要调用 helper 函数时,调用的不是helper_qasan_load2(x, x),而是gen_helper_qasan_load2(x, x)。
accel/tcg/tcg-runtime.c文件patch 内容如下所示:1 | diff --git a/accel/tcg/tcg-runtime.c b/accel/tcg/tcg-runtime.c |
tcg-runtime.c 原本用于实现 TCG Helper 函数。AFL 在该文件中增加了相关 Helper 函数的实现代码。
accel/tcg/translator.c文件patch 内容如下所示:1 | diff --git a/accel/tcg/translator.c b/accel/tcg/translator.c |
QEMU 进行 Guest CPU 指令 → TCG IR(中间指令) 翻译的主要流程代码位于 translator.c 的 translator_loop 函数中。
AFL 在该函数中添加了入口流程的代码,当翻译的地址为afl_entry_point时,则调用gen_helper_afl_entry_routine函数,而该函数就是 AFL forkserver 模式通信的核心函数。
AFL forkserver 工作模式如下:
AFL 首先 fork 出一个子进程,随后创建两个管道,一个负责输入,一个负责输出。默认情况下,这两个管道的描述符为:FORKSRV_FD, FORKSRV_FD+1。
在qemuafl/imported/config.h中定义了:#define FORKSRV_FD 198。
其中FORKSRV_FD负责 AFL->QEMU 通信,FORKSRV_FD+1负责QEMU->AFL 通信。
创建该管道的子进程使用 execve 执行 QEMU,QEMU 进程将继承这两个管道,从而实现 AFL 和 QEMU 的进程间通信。
gen_helper_afl_entry_routine函数的实现代码为:
1 | void HELPER(afl_entry_routine)(CPUArchState *env) { |
实际上调用的是 afl_forkserver 函数,该函数用于与 AFL 进行握手。若握手失败,则退出并进入原本的 QEMU 流程;若握手成功,则该进程保持与 AFL 通信,并 fork 出一个子进程以继续 QEMU 的后续流程。
由于是 fork 出的子进程,将完全拷贝一份内存数据,且不影响父进程的内存空间结构。AFL 可借此快速执行每次 fuzz 流程。
在 afl_forkserver 函数中,还会通过 afl_wait_tsl 函数接收子进程指令翻译的情况,并同步至父进程。这样父进程下次 fork 的子进程可直接执行 Host 机器码,无需重复指令翻译流程,从而大幅提升 QEMU 仿真速度。
accel/tcg/translate-all.c文件patch 内容如下所示:1 | diff --git a/accel/tcg/translate-all.c b/accel/tcg/translate-all.c |
translate-all.c 文件主要用于将 TCG IR 翻译成 Host 机器码。
该 patch 将 AFL 的 qemu-mode 插桩深度整合进 TCG 代码,使 QEMU 在翻译TB(TCG Block,存储着 TCG IR)时自动生成各种覆盖记录,并添加了 edge TB、生存期概率插桩、不稳定 block 追踪等 AFL 功能。
accel/tcg/cpu-exec.c文件patch 内容如下所示:1 | diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c |
cpu-exec.c 中的 cpu_exec_loop 函数为 QEMU 仿真的核心函数,调用流程大致如下:
cpu_exec函数,进行一些初始化后,执行到cpu_exec_setjmp函数。cpu_loop_exec_tb去执行 Host 机器码。translate_code函数将Guest 指令翻译成 TCG IR。AFL 在该文件中的 patch 是将IJON、snapshot、persistent 模式等功能集成进 QEMU 当中,如下所示:
加入覆盖率共享内存(SHM)映射
加入 AFL forkserver 支持
加入持久化模式(persistent mode)支持
加入持久化模式下 snapshot(快照)/ restore(恢复)功能
加入 IJON(高级指导型 fuzzing)支持
加入比较覆盖(CmpCov)支持
加入 AFL TB 链跟踪结构(afl_tb, afl_chain 等)
加入 sharedmem fuzzing 模式支持
加入对 shared memory / registers 的 IJON hook 支持
扫描并保存 /proc/self/maps 的内存区域
在翻译完指令后,会将翻译情况传输给父进程forkserver。
接下来是tcg目录下的代码。
tcg/tcg.c文件patch 内容如下所示:1 | diff --git a/tcg/tcg.c b/tcg/tcg.c |
该部分 AFL patch 主要实现一个简单接口,允许在 TCG 翻译过程中插入对指定函数的纯粹函数调用。
tcg/tcg-op.c文件patch 内容如下所示:1 | diff --git a/tcg/tcg-op.c b/tcg/tcg-op.c |
这段代码是 QASan实现的核心部分。
简单来说,它的作用是:在 QEMU 将 Guest(客户机)的内存读写指令翻译成中间码(TCG Ops)时,强制插入一段“检查代码”。
最后就是target/{arch}/tcg目录下的代码。
target/mips/tcg/translate.c文件patch 内容如下所示:1 | diff --git a/target/mips/tcg/translate.c b/target/mips/tcg/translate.c |
target/{arch}/tcg/translate.c 主要将目标程序的指令翻译成 TCG IR。AFL 对该文件的 patch 主要添加 AFL 持久化模式支持,包括持久化 fuzz 逻辑注入及 MIPS 完整寄存器状态的保存与恢复,使 AFL 能以高效循环方式 fuzz MIPS 程序。
由于测试案例使用 MIPS 架构,因此上述 patch 内容仅包含 MIPS 架构相关部分,其他架构暂未 patch。此外,由于暂未使用 QASAN 功能,许多 QASAN 相关 patch 亦暂未加入。
]]>