欢迎光临
我们一直在努力

AFL(American Fuzzy Lop)源码详细解读(1)

最近在读AFL的源码,主要是主文件 agl-fuzz.c, 里面还是有很多细节需要注意,对一些重要的函数做了详细解读,部分函数大概知道是做什么的就可以了。其中还有些小问题没有理解,欢迎有兴趣的同学纠正以及讨论。
由于有些重要的函数在不同阶段有不同的作用,所以是分阶段记录的,本篇只是一部分,后续还会更新。

  • -i:设置输入文件夹, 如果in_dir = “-”, 设置 in_place_resume = 1。
  • -o: 设置输出文件夹。
  • -M: 用于并行fuzz,设置主fuzzer的sync_id。
  • -S: 用于并行fuzz,设置从fuzzer的sync_id。
  • -f: 模糊程序读取case的位置,out_file变量在这一步被赋值。
  • -x: 设置自定义的token,token就是一些容易触发漏洞的输入,(比如边界值,很大的数等),用于后面变异过程中的替换和插入,extras_dir被赋值。
  • -t: 设置被测程序的运行时间限制,exec_tmout被赋值为时间;如果后缀为’+‘ ,timeout_given=2,否则timeout_given=1,表示已经设置了运行时间限制。
  • -m:设置被测程序的内存空间大小,单位为M,mem_limit被赋值为内存大小;mem_limit_given = 1,表示已经设置了运行内存。
  • -b:应该是设置绑定CPU核心数量?基本用不到。
  • -d:跳过变异时的确定性变异阶段,skip_deterministic = 1; use_splicing = 1(重新组合输入文件)。
  • -B:也是基本用不到,大概就是发现了一个有趣的测试用例,并且想要单独对它变异进行测试。紧跟的输入参数是一个位图,in_bitmap被赋值。
  • -C:将一个导致crash测试用例作为afl-fuzz的输入,可以快速地产生很多和输入crash相关、但稍有些不同的crashes。crash_mode = 2。
  • -n:非插桩模式 dumb_mode = 1。
  • -T:还没注意到这个参数有什么用,基本上用不到。
  • -Q:QEMU模式,qemu_mode = 1,在此模式下,如果输入参数没有设置被测程序的内存空间大小,则设置mem_limit = 200。
  • -V:版本信息。
  • default:usage() 函数显示使用提示,各个参数的作用。

setup_signal_handlers();某些情况下(如 stop、timeout)会终止子进程和forkserver。

check_asan_opts(); 读取环境变量 ASAN_OPTIONS 和 MSAN_OPTIONS,做一些必要性检查。ASAN是一个快速的内存错误检测工具。

  • no_forkserver = 0

  • no_cpu_meter_red = 0

  • no_arith = 0

  • shuffle_queue = 0

  • fast_cal = 0

orig_cmdline 被赋值为保存命令行参数的内存首地址。

如:命令行参数为:afl-fuzz -i ./in -o ./out ./test

  • argc = 6
  • argv[0] = afl-fuzz
  • argv[1] = -i
  • argv[2] = ./in
  • argv[3] = -o
  • argv[4] = ./out
  • argv[5] = ./test

use_banner 被赋值,长度不超过40。

如输入的命令行参数为:afl-fuzz -i ./in -o ./out ./test,则use_banner = test。

(目前还不知道use_banner有啥用)。

not_on_tty = 1时不运行UI界面,但是一般not_on_tty = 0

没细看,知道是干啥的就行了。

如果系统配置为将核心转储文件(core)通知发送到外部程序,会导致将崩溃信息发送到Fuzzer之间的延迟增大,进而可能将崩溃被误报为超时,所以我们得临时修改core_pattern文件。

就是第一次运行时报错让你去执行的那句话(echo core > /proc/sys/kernel/core_pattern)就是因为这个函数。

检查一些环境变量,具体没细看,不是核心函数

目前看来没啥用。

  • trace_bits 记录当前用例的路径信息
  • virgin_bits 记录总的路径信息
  • virgin_tmout 记录超时用例的路径信息
  • virgin_crash 记录崩溃用例的路径信息

将virgin_bits、virgin_tmout、virgin_crash 每个数组的所有位全部置为1。

为trace_bits 注册一块共享内存(共享内存创建后,每次执行目标程序前会清0),并注册结束处理函数 atexit(remove_shm) 程序结束时,用于删除共享内存。

将共享内存的标志符会保存到环境变量中,从而之后fork()得到的子进程可以通过该环境变量,得到这块共享内存的标志符

使用变量trace_bits来保存共享内存的地址

规整规则如下,

[0] = 0,
[1] = 1,
[2] = 2,
[3] = 4,
[4 … 7] = 8,
[8 … 15] = 16,
[16 … 31] = 32,
[32 … 127] = 64,
[128 … 255] = 128

trace_bits是用一个字节来记录是否到达这个路径,和这个路径被命中了多少次的,即 count_class_lookup8[256]。

而在实际的规整过程中是一次规整两个字节, 即count_class_lookup16[65536]。

  • out_dir/queue/
    • out_dir/queue/.state/
      • out_dir/queue/.state/deterministic_done/
      • out_dir/queue/.state/auto_extras/
      • out_dir/queue/.state/redundant_edges/
      • out_dir/queue/.state/variable_behavior/
  • out_dir/testcases
  • out_dir/crashes
  • out_dir/hangs
  • out_dir/plot_data
  • 尝试访问 in_dir/queue 文件夹,如果存在就重新设置in_dir 为 in_dir/queue,直白点讲就是你是将自己准备的种子用例直接放在了 in_dir 下,还是放在了 indir/queue 下。

  • shuffle_queue = 0, 不执行这个判断代码块。

  • 通过文件属性过滤掉 . 和 … 以及readme 和 空文件。

  • 检查文件大小,不能超过1M。

  • 判断是否经过确定性模糊测试。如果 out/queue/.state/deterministic_done/ 文件夹下已经存在了该文件,即已经经过确定性模糊测试,直接跳过。

  • 最后判断如果queued_paths = 0,说明没有可用的种子。

  • 设置 last_path_time = 0 该全局记录最新路径的时间,在add_to_queue 中被置为当前用例加入队列中的时间。在这里应该是由于是将初始种子读到队列中,还未开始执行以及发现路径,所以置 0;而后续再加入队列中的种子是提前判断发现了新路径的。

  • queued_at_start = queued_paths 记录初始种子的数量。

  • 创建queue_entry 结构体,设置name,len,depth,passed_det,全局变量max_depth。
  • 如果队列为空,即第一个添加的用例,q_prev100 = queue = queue_top 都指向当前用例;否则,直接追加到队列中。
  • 更新全局变量 queued_paths(当前队列中的用例数量)和 pending_not_fuzzed (待测试的用例数量)。
  • cycles_wo_finds = 0,目前还不知道这个全局变量的作用。
  • 队列中每100个用例做个标记 ,如当前队列中有230个用例,则第一个的 1.next_100 指向 101,101.nex_100 指向 201, 201.next_100 = null。
  • last_path_time = get_cur_time()

token就是一些容易触发漏洞的输入,比如边界值等等,除了用户自己可以准备token外,AFL也会自动生成一些token

out_dir/.state/auto_extras/auto_i 该文件不存在,所以fd < 0,直接break了。

  • 遍历队列,将队列中的所有用例都在 out_dir/queue/ 下创建硬链接。准备阶段队列中只有 in_dir 下的种子用例。
    • nfn, rsl 都赋值为文件名
    • 判断 rsl 的前三个字符是否为 id: ,并且取出来的orig_id是否等于 id,如果满足条件,说明当前在resume阶段,置resuming_fuzz = 1。查看queue文件夹下的文件,可以看到命名为id:000001,src:000000,op:havoc,rep:32+cov (这个if代码块好像从头到尾都不执行,其中一个 resuming_fuzz 变量就一直不会被赋值为1,这个变量的大概意思应该是把之前fuzz过程中变异生成的用例当作本次fuzz的种子用例,此时会执行这个代码块,resuming_fuzz才会被赋值为1,不确定这里理解的对不对)。
      • 接着取出文件的src_id ,找到其父亲用例,即该用例是在谁的基础上演变来的。更新当前用例的深度以及最大深度
    • 如果是在准备阶段, use_name 赋值为文件名, nfn = out_dir/queue/id:…,orig:use_name
    • 调用 link_or_copy ,尝试创建链接,失败则直接拷贝。
    • 修改q->fname 为nfn,如 case 改为 id:xxxxxx,orig:case
    • 如果要跳过确定性变异阶段,则调用mark_as_det_done 在 out_dir/queue/.state/deterministic_done/ 下创建相应的文件,只是创建空文件。
  • 如果in_place_resume 不为0,即输入命令行参数时 in_idr = “-”, 则不保留 out_dir/_resume/.state/ 各个文件夹下对应的文件。

将用户提供的token读取到 extra_data 结构体中保存,并且按照从大到小排序,每个token不能超过128字节,extras_cnt记录数量。

命令行参数没有给-t,并且是在resume阶段,设置exec_tmout,但是由于resuming_fuzz = 0 ,这个函数直接return了,所以timeout_given还是等于0。

  • 获取当前工作目录。
  • 进入循环,查看每一个参数,其实正常情况下这里就传过来了一个参数 “@@”。
    • 查找当前参数中是否有@@,如果有
      • 如果输入命令行参数没有-f,则out_file = null, 所以这里out_file 会被赋一个默认值, out_file = out_dir/.cur_input
      • 不管是绝对路径还是相对路径,都规整成绝对路径
      • argv[0] = “” , aa_loc + 2 = “” , aa_subst = /home/…/./out_dir/.cur_input(…是自己所在的文件夹)
      • 构造新的参数并赋值 @@ -> /home/…/./out_dir/.cur_input(…是自己所在的文件夹)
      • 将aa_loc 在 改成 ‘@’ ,不知道这一步有什么用
    • 否则,查看下一个参数

如果没有入命令行参数没有-f, 并且也没有@@,执行该函数

在out_dir 下创建 .cur_input 文件,并将文件描述符赋值给 out_fd, 执行完这个函数后,out_file 还是null

检查指定路径要执行的程序是否存在,是否为shell脚本,同时检查elf文件头是否合法及程序是否被插桩

  • 海报
海报图正在生成中...
赞(0) 打赏
声明:
1、本博客不从事任何主机及服务器租赁业务,不参与任何交易,也绝非中介。博客内容仅记录博主个人感兴趣的服务器测评结果及一些服务器相关的优惠活动,信息均摘自网络或来自服务商主动提供;所以对本博客提及的内容不作直接、间接、法定、约定的保证,博客内容也不具备任何参考价值及引导作用,访问者需自行甄别。
2、访问本博客请务必遵守有关互联网的相关法律、规定与规则;不能利用本博客所提及的内容从事任何违法、违规操作;否则造成的一切后果由访问者自行承担。
3、未成年人及不能独立承担法律责任的个人及群体请勿访问本博客。
4、一旦您访问本博客,即表示您已经知晓并接受了以上声明通告。
文章名称:《AFL(American Fuzzy Lop)源码详细解读(1)》
文章链接:https://www.456zj.com/18961.html
本站资源仅供个人学习交流,请于下载后24小时内删除,不允许用于商业用途,否则法律问题自行承担。

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址