AFL进阶教学——插桩、执行、覆盖率收集与反馈(解析)

  • AFL(American Fuzzy Lop)是一个面向安全的模糊测试工具,它使用编译时插桩技术和遗传算法,可以自动发现触发目标二进程程序的测试用例,从而大大提高测试代码的功能覆盖率。
  • 本文主要讲述AFL是如何实现插桩的,如何对覆盖率进行收集的,以及如何利用覆盖率指导种子进行变异的。

一、插桩

  • 使用AFL对目标程序进行测试时,首先需要通过afl-gcc/afl-clang等工具对其进行编译,并且在这个过程中会对其进行插桩。

1、afl-gcc.c

  • 这里以afl-gcc为例。afl-gcc.c其实就是gcc的一个包装。尝试打印afl-gcc在编译文件时所执行的所有命令行参数。
    • gcc test.c -B /root/src/afl-2.52b -g -O3 -funroll-loops -D__AFL_COMPILER=1 -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1
    • 可以看出,afl-gcc调用了gcc,并定义了一些宏,设置了一些参数。
    • 其中最关键的是:-B /root/src/afl-2.52b。-B 选项用于设置编译器的搜索路径,这里设置成了 /root/src/afl-2.52b,也就是afl-as的路径。
  • 把源文件编译成二进制文件,需要经过“源代码 -> 汇编代码 -> 二进制代码”的过程。将汇编代码编译成二进制代码就需要使用到汇编器。Linux系统下常用的汇编器为as。AFL目录下也有一个as文件,其作为一个符号链接指向了afl-as。
  •  从afl-gcc.c的main中也能看出这点。
  • 所以-B 选项的设置是为了把afl-as作为汇编器来使用。而AFL插桩,就是在源代码编译为汇编代码后,由afl-as完成的。
  • 下图为gcc的编译流程,只不过在第三阶段时,将as替换为了afl-as。

2、afl-as.c

  • afl-as又是如何实现插桩的呢?阅读afl-as.c。其大致逻辑是处理汇编代码,在程序代码段中的每个基本块插入了桩代码,并最终再调用as进行真正的汇编。具体插入代码的部分如下:
    • 这里通过fprintf()将格式化字符串添加到汇编文件的相应位置。其中trampoline_fmt_64和trampoline_fmt_64是定义的汇编代码,也就是桩代码。
      •  
      • 功能:
        • 保存一些寄存器的值
        • 将生成的随机数保存在ecx/rcx中
        • 调用__afl_maybe_log()
        • 恢复寄存器
      • fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, R(MAP_SIZE));
        • 其中R(MAP_SIZE))是ecx/rcx要设置的值。MAP_SIZE定义为64K,R(x)定义为(random() % (x)) ,故R(MAP_SIZE))为0~64K的一个随机数。
      • 所以,在一个基本块中插入桩代码时,afl-as会生成一个随机数,保存在ecx/rcx中,用来标记该基本块。
2.1、基本块
  • 基本块 (BB)指一组顺序执行的指令,BB中第一条指令被执行后,后续的指令也会被全部执行,每个BB中所有指令的执行次数是相同的,也就是说一个BB必须满足以下特征:
    • 只有一个入口,BB中的指令不是任何跳转指令的目标。
    • 只有一个出口,只有最后一条指令执行完跳到另一个BB中。
  • 例如,下面代码分为4个基本块。

二、执行

  • 编译完目标程序后,就可以进行fuzzing了。(本文只讲述了第一次fuzzing)

1、afl-fuzz.c

  • 在main中,首先会对所有初始测试用例进行试运行。(本文未提及对种子的变异操作)
  • 在perform_dry_run()中,会对测试用例进行了一次测试,以检测测试用例是否有问题需要校准。
  • 校验测试用例,就需要将这个测试用例输入到目标程序中运行。首先需要初始化fork server。

2、fork server

  • 为了更高效地进行上述过程,AFL实现了一套fork server机制。其基本思路是:启动目标进程后,目标会运行一个fork server。afl-fuzz不需要负责fork子进程,只需要与这个fork server通信,并由fork server来完成fork及继续执行目标的操作。
  • 简单来说,当执行第一个基本块时会启动fork server,afl-fuzz和fork server之间通过管道通信,每当afl-fuzz生成一个测试用例,就会通知fork server去fork一个子进程,然后子进程会从fork server的位置继续往下执行并处理数据,而fork server则继续等待afl-fuzz的请求。
  • 下面讲述fork server的具体运行原理:
    • 首先,afl-fuzz执行fork()得到父进程和子进程,父进程为afl-fuzz,子进程为fork server
    • 父子进程之间,是通过管道进行通信。具体使用了2个管道,一个用于传递状态,另一个用于传递命令。
    • 对于子进程(fork server),会进行一系列设置,其中包括将上述两个管道分配到预先指定的文件描述符 (fd),并最终执行目标程序。
      • 【注】内核利用文件描述符 (fd)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。
    • 对于父进程(afl-fuzz),则会读取状态管道的信息,如果一切正常,则说明fork server创建完成。
  • 下面讲述afl-fuzz是如何与fork server进行通信的:
    • fork server的实现过程,是插桩代码中调用的_afl_maybe_log()函数(afl-as.h)实现的。
      • _afl_maybe_log:用于保护现场和检查共享内存是否以分配。
      • 如已分配,则跳转到__afl_setup。初始化共享内存指针等。
      • 如一切顺利,则进入_afl_forkserver。
        • 首先,通过写入状态管道,fork server会通知afl-fuzz,其已经准备完毕,可以开始fork了。上面则父进程等待的信息。
      • 然后,fork server进入等待状态__afl_fork_wait_loop,读取命令管道,直到afl-fuzz通知其开始fork。
      • 一旦fork server接收到afl-fuzz的信息,便调用fork(),得到一个子进程(目标进程)。
      • 子进程(目标进程)则跳转执行 __afl_fork_resume,该函数会关闭不需要的管道并恢复现场。然后执行__afl_store(该函数用于记录命中桩代码的次数,并计算覆盖率,后面再讲)。
      • 子进程(目标进程)执行期间,fork server会将子进程的pid通过状态管道发送给afl-fuzz,并执行waitpid等待子进程执行完毕。一旦子进程(目标进程)执行完毕,fork server则再通过状态管道,将其结束状态发送给afl-fuzz。之后再次进入__afl_fork_wait_loop,重新循环操作。
    • 父进程(afl-fuzz)的实现过程,就是在fork server启动完成后,一旦需要执行某个测试用例,就调用run_target()方法。
      • run_target()方法会通过命令管道,通知fork server准备fork,并通过状态管道,获取子进程(目标进程)的pid。
      • 之后,afl-fuzz再次读取状态管道,获取子进程的退出状态,并由此来判断子进程结束的原因,例如正常退出、超时、崩溃等,并进行相应的记录。

三、内存共享

  • AFL最大的特点是可以通过覆盖率来指导种子的变异。也就是AFL对目标代码插桩编译再执行时,会收集执行过程中的分支信息,即覆盖率。
  • AFL使用共享内存来实现alf-fuzz和目标程序之间的信息传递。
    • afl-fuzz.c的main中会调用setup_shm()来配置共享内存。
    • 该函数中,首先会调用shmget()分配一块大小为64KB(MAP_SIZE=64K)的共享内存。
    • 分配成功后,该共享内存的标志符会被设置到环境变量中,从而之后fork()得到的子进程可以通过该环境变量,得到这块共享内存的标志符。
    • afl-fuzz也会使用变量 trace_bits 保存该共享内存地址。
    • 并且每次执行目标进程时,首先会将该共享内存清零。
  • 接下来讲述目标进程时如何获取并使用这块共享内存的。同样也是在_afl_maybe_log()函数(afl-as.h)实现的。
    • 首先会检查共享内存是否映射完成。
      • __afl_area_ptr中保存的就是共享内存映射到目标进程的内存空间中的地址,如果非空,则保存在ebx中继续执行;否则跳转到__afl_setup。
    • __afl_setup会做一些错误检查,然后获取环境变量AFL_SHM_ENV的内容并将其转为整型。AFL_SHM_ENV存放的是之前afl-fuzz保存的共享内存的标志符。
      • ​​​​​​​
    • 最后目标进程调用shmat()函数,将这块共享内存也映射到了自己的内存空间中,之后将其地址保存在__afl_area_ptr和edx中。
    • 以上就完成了afl-fuzz与目标进程之间共享内存的设置。
    • 【注】如果是fork server模式,那么上述获取共享内存的操作,是在fork server中进行。之后fork出来的子进程(目标进程),只需直接使用这个共享内存即可。

四、覆盖率收集与反馈

1、分支信息记录

  • 上面已经讲述了afl-fuzz与子进程使用内存共享进行通信,通信的内容是执行流程和代码覆盖情况。那AFL是如何记录这些信息的呢?
  • AFL通过插桩代码捕获边 (edge)覆盖率。什么是边呢?将程序看成一个控制流图,图的每个节点表示一个基本块,而边表示在基本块之间的转跳。知道了每个基本块和跳转的执行次数,就可以知道程序中的每个语句和分支的执行次数,从而获得比记录基本块更细粒度的覆盖率信息。
  • 而AFL是利用二元组(当前基本块+前一基本块)来记录分支(边)信息的,其伪代码如下:
    • cur_location = <COMPILE_TIME_RANDOM>;  // 用一个随机数标记当前基本块
      shared_mem[cur_location ^ prev_location]++;  // 将当前块和前一块异或保存到shared_mem数组(共享内存)对应的位置
      prev_location = cur_location >> 1;  // cur_location右移1位赋给prev_location,原因看下方
  • 信息收集也是在_afl_maybe_log()函数中完成的。当afl-fuzz保存了共享内存地址并且完成了fork server的初始操作后,会调用__afl_store(32位是ecx,64位是rcx,下面以64为例)
    • 其中,rcx 开始保存的是 cur_location,然后保存的是 cur_location ^ prev_location。__afl_prev_loc 开始保存的是伪代码中的 cur_location,之后右移了1位,变成了 prev_location。
    • 回顾前面插桩时做的操作,R(MAP_SIZE) 生成了一个 0~64K 的随机数,并保存到了 rcx 中。这就实现了伪代码中的 cur_location = <COMPILE_TIME_RANDOM> 操作。
  • 【原理】
    • ​​​​​AFL为每一个代码块生成了一个随机数,用于标记其位置。之后对分支处的当前块和前一块的位置(随机数)进行异或操作,并将异或结果作为该分支的key。当该分支被执行,共享内存中的对应位置就会 +1(用1Byte储存分支的执行次数),实际就是哈希表的映射。共享内存保存的是一张哈希表,哈希表中记录的是边覆盖率和分支的执行次数。
    • 因为使用的是哈希算法,所以会存在碰撞问题。但如果目标程序不是太复杂的话,碰撞概率也不会很高。
    • 另外,为什么需要将 cur_location 右移1位后,再保存到 prev_location 中?
      • 如果存在 A -> A 或 B -> B 这样的跳转,如果不右移的话,那这两个分支异或的值都是0,无法区分。
      • 如果存在 A ->B 和 B -> A 这样的跳转,如果不右移的话,那这两个分支异或的值是相同的,也无法区分。
    • 当目标进程执行结束后,afl-fuzz便开始对这张表进行分析,从而判断代码的执行情况。

2、分支信息分析

  • 在执行目标进程的最后,会调用classify_counts()先对共享内存中的执行次数进行重新计数,传入的参数trace_bits即为共享内存的地址。
    • (这里将trace_bits强制转换为了64位的指针,也就是一次读取8字节)
    •  
    • 实际就是对分支执行次数进行分类。例如,执行了4~7次的计数为8,32~127次的计数为64。
    • 目的是处理一些微小误差,而被误判为不同执行结果的情况。例如,分支A执行了32次;对另外一个测试用例,分支A执行了33次,那么AFL就会认为这两次的代码覆盖是相同的。因为它们都被处理为了64。
    • 【注】使用 count_class_lookup16 是因为AFL在后面实际进行规整的时候,是一次读两个字节去处理的,为了提高效率,这只是出于效率的考量。
  • 目标进程执行完后,会调用hash32计算共享内存的hash值。
  • 对于某些突变的种子,如果执行没有出现崩溃等异常输出,afl-fuzz还会检查其是否新增了执行路径。而通过比较hash值,就可以判断trace _bits(共享内存)是否发生了变化,从而判断此次突变种子是否带来了新路径,为之后的fuzzing提供参考信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/306058.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

提升网络安全重要要素IP地址

在数字化时代&#xff0c;网络安全已经成为人们关注的焦点。本文将深入探讨网络安全与IP地址之间的紧密联系&#xff0c;以及IP地址在构建数字世界的前沿堡垒中的关键作用。 网络安全是当今数字社会中不可忽视的挑战之一。而IP地址&#xff0c;作为互联网通信的基础协议&#…

RT-Thread:ADC 框架应用,通过 STM32CubeMX 配置 STM32 ADC驱动

关键词&#xff1a;ADC,RT-Thread ADC,STM32 ADC应用 说明&#xff1a;本笔记是记录如何开启 RT-Thread 框架的ADC功能&#xff0c;使用系统自带的ADC函数&#xff0c;并通过 STM32CubeMX 配置 STM32 ADC驱动 。 1. 打开board.h 文件&#xff0c;找到ADC 使用配置的流程&…

Dell 机架式服务器 - 高级定制服务

Dell 机架式服务器 - 高级定制服务 1. Dell Technologies2. 机架式服务器 - 高级定制服务2.1. Servers & Storage (服务器及存储) -> Servers2.2. Rack Servers (机架式服务器)2.3. Shop2.4. PowerEdge Rack Servers (PowerEdge 机架式服务器)2.5. PowerEdge R760 Rack …

SpringBoot 注解超全详解

使用注解的优势&#xff1a; 采用纯java代码&#xff0c;不在需要配置繁杂的xml文件 在配置中也可享受面向对象带来的好处 类型安全对重构可以提供良好的支持 减少复杂配置文件的同时亦能享受到springIoC容器提供的功能 1 注解详解&#xff08;配备了完善的释义&#xff0…

数据库的导入导出以及备份

1.数据库的导出和导入 一.navicat导入导出 导入&#xff1a;右键➡运行SQL文件 导出选&#xff1a;中要导出的表➡右键➡转储SQL文件➡数据和结构 mysqldump命 1. 进入navicat安装目录的bin目录&#xff0c;cmd打开命令窗口 2. mysql -u用户名 -p ➡ 输入密码 3. creat…

Python基础知识:整理7 字典的定义及其相关操作

1 字典的定义 # 1. 字典的定义 # 定义字典的字面量 # {key: value, key: value, ......, key: value}# 定义字典变量 # my_dict {key: value, key: value, ......, key: value}# 定义空字典 # my_dict {} # my_dict dict()定义重复Key的字典 my_dict1 {"张三": …

Proxmox VE 超融合集群销毁Ceph Pool

作者&#xff1a;田逸&#xff08;formyz&#xff09; 销毁Ceph Pool的目的 一套五节点的Proxmox VE超融合集群&#xff0c;当初为有效利用资源&#xff0c;配备了Nvme高性能磁盘和大容量的SATA机械磁盘&#xff08;如图所示&#xff09;&#xff0c;高性能Nvme磁盘用于虚拟机…

图像中部分RGB矩阵可视化

图像中部分RGB可视化 今天室友有个需求就是模仿下面这张图画个示意图&#xff1a; 大致就是把图像中的一小部分区域的RGB值可视化了一下。他居然不知道该怎么画&#xff0c;我寻思这不直接秒了。 import cv2 as cv import numpy as np import matplotlib.pyplot as pltclass …

构建高效秒杀系统的设计原理及注意事项

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

OpenFegin日志增强

OpenFeign配置日志增强功能 OpenFeign提供了日志打印功能&#xff0c;我们可以通过配置来调整日恙级别&#xff0c;从而了解Feign 中 Http请求的细节。 说白了就是对Feign接口的调用情况进行监控和输出 日志级别 NONE&#xff1a;默认的&#xff0c;不显示任何日志; BASIC&…

Android平板浏览器远程Ubuntu服务器使用code-server编程写代码

文章目录 1.ubuntu本地安装code-server2. 安装cpolar内网穿透3. 创建隧道映射本地端口4. 安卓平板测试访问5.固定域名公网地址6.结语 1.ubuntu本地安装code-server 准备一台虚拟机,Ubuntu或者centos都可以&#xff0c;这里以VMwhere ubuntu系统为例 下载code server服务,浏览器…

一文快速学会Docker软件部署

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;首期文章 &#x1f4da;订阅专栏&#xff1a;Docker 希望文章对你们有所帮助 做项目的时候&#xff0c;感觉很多地方的配置都特别…

RK3568上如何使用MPP进行硬解码

目录 前言正文一、FFmpeg 拉流处理二、RK3568 mpp硬解码1、简介2、普通mpp解码流程3、核心代码 END、总结的知识与问题1、一直出现jitter buffer full 这样的问题2、如何打印帧率&#xff1f;3、分析av_packet_alloc、av_init_packet、av_packet_unref、av_packet_free、av_fra…

zookeeper 与eureka区别

CAP定理 在分布式系统的发展中&#xff0c;影响最大的莫过于CAP定理了&#xff0c;是分布式系统发展的理论基石。 2000年&#xff0c;加州大学的计算机科学家 Eric Brewer提出了CAP猜想 2002 年&#xff0c;麻省理工学院的 Seth Gilbert 和 Nancy Lynch 从理论上证明了 CAP 猜…

PTA✨C语言 组合数的和

7-5 组合数的和 分数 15 全屏浏览题目 切换布局 作者 陈越 单位 浙江大学 给定 N 个非 0 的个位数字&#xff0c;用其中任意 2 个数字都可以组合成 1 个 2 位的数字。要求所有可能组合出来的 2 位数字的和。例如给定 2、5、8&#xff0c;则可以组合出&#xff1a;25、28、5…

指针的含义、表示、规范、存储、运用

指针的含义、表示、规范、存储、运用 指针的含义指针的表示指针的规范先声明再定义声明和定义一起表示错误表示 指针的存储理解一个变量的存储过程和原理理解一个指针的存储过程和原理理解多个指针的存储过程和原理 指针的运用 指针的含义 表示某个变量或数据所在的内存地址 注…

SpringBoot 如何增强PageHelper入参的健壮性

PageHelper.startPage(int pageNum, int pageSize, boolean count) 参数为外部输入&#xff0c;故存在异常输入场景。比如 pageNum 和 pageSize 输入的值 负数 或者 0&#xff0c;所以引入PageUtils来对入参进行判断矫正&#xff0c;从而避免引入异常。 第1步&#xff1a;支持…

Copilot 插件的使用介绍:如何快速上手

GitHub Copilot 本文主要介绍如何通过脚本工具激活 GitHub Copilot 插件&#xff0c;提供安装及激活图文教程&#xff0c;大家按下面操作即可激活GitHub Copilot插件&#xff0c;免费使用Ai编码工具 一、GitHub Copilot 介绍 GitHub Copilot 是由 GitHub 和 OpenAI 共同开发的…

外包做了5个月,技术退步一大半了。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;20年通过校招进入深圳某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

苹果电脑RAW图像处理软件Capture One Pro 22 mac软件介绍

Capture One Pro 22 for mac是一款专业的RAW文件转换器和图像编辑软件&#xff0c;拥有更新的处理引擎、市场领先的性能和强大的新功能&#xff0c;可为 500 多台高端相机提供具有美丽色彩和令人难以置信的细节的终极图像质量。 Capture One Pro 22 for Mac版软件介绍 Capture…