文章一开始发表在微信公众号

https://mp.weixin.qq.com/s?__biz=MzUyNzc4Mzk3MQ==&mid=2247486292&idx=1&sn=0e2e298881fcb7a67b170af9dc59e803&chksm=fa7b0a18cd0c830edc734f6cdf08ae4cbf4f66011289cb9d7ed7954d12e4fcbca4da7f443341&scene=21#wechat_redirect

AFL

AFL是Coverage Guided Fuzzer的代表,AFL通过在编译时插桩来获取程序执行的覆盖率,AFL可以获取基本块覆盖率和边覆盖率,下图所示是一个函数的流程图

A, B 是两个基本块, A->B 则是一条边表示程序从A基本块执行到了B基本块,边覆盖率比基本块覆盖率更能表示程序的执行状态,所以一般也推荐使用边覆盖率。

本章最开始提到过Fuzz的速度和样本集是Fuzz测试的两个重要因素,而AFL的实现机制很好的改进了这两个问题。具体而言,AFL的forkserver机制大大提升了Fuzz的测试速度,其覆盖率反馈机制则让AFL能够自动化的生成一个质量比较高的样本集。

下面我们先简单地介绍一下AFL中forkserver的实现机制

AFL通过源码插桩的方式在程序的每个基本块前面插入 _afl_maybe_log 函数,当执行第一个基本块时会启动forkserver,afl-fuzz和forkserver之间通过管道通信,每当afl-fuzz生成一个测试用例,就会通知forkserver去fork一个子进程,然后子进程会从forkserver的位置继续往下执行并处理数据,而forkserver则继续等待afl-fuzz的请求,工作示意图如下:

通过插桩,AFL可以在运行时获取到程序处理每个样本的覆盖率,AFL会把能够产生新用例的路径保存到样本队列中,这样随着Fuzzing的进行,AFL会得到一个质量比较高的样本集。

源码插桩模式

下面介绍如何用AFL来测试libredwg, 首先我们需要用 afl-gcc 把库给编译出来

CC=/path/of/afl/afl-gcc ./configure

如果是c++程序则加上

CXX=/path/of/afl/afl-g++

用afl来编译软件时会打印出

编译完后会在src/.libs/目录下生成libredwg.so,然后会在 examples/.libs 生成dwg2svg2。dwg2svg2是一个示例程序用于解析一个dwg文件, dwg2svg2依赖libredwg.so,

~/workplace/libredwg-0.9.2425/examples/.libs$ ldd dwg2svg2
linux-vdso.so.1 => (0x00007ffe0e7eb000)
libredwg.so.0 => not found
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f84b6739000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f84b636f000)
/lib64/ld-linux-x86-64.so.2 (0x00007f84b6a42000)

为了能够正常运行该程序,我们先需要设置环境变量,添加库路径到系统搜索路径中

export LD_LIBRARY_PATH=/path/of/libredwg/src/.libs/

当程序能够正常运行后,就可以使用afl-fuzz来开始Fuzz了,Fuzz的命令如下

/path/of/afl/afl-fuzz -i in/ -o out -- ./dwg2svg2 @@

其中

in: 存放初始用例
out: 存放afl测试过程中的输出
@@: 文件路径的占位符,afl fuzz时会用样本的路径替换@@, 被测程序会读取文件然后处理数据

一次Fuzzing的结果如下

二进制插桩

AFL还可以通过qemu在Linux下Fuzz二进制程序,使用qemu执行一个程序时,qemu会从被执行程序的入口点开始对基本块翻译并执行,为了提升效率,qemu还会把翻译出来的基本块存放到 cache 中,当 qemu 要执行一个基本块时首先判断基本块是否在 cache 中,如果在 cache 中则直接执行基本块,否则会翻译基本块并执行。AFL 的 qemu 模式的实现机制是在执行基本块的和翻译基本块的前面增加一些代码来获取代码覆盖率以及启动forkserver。

本节将介绍如何利用AFL qemu模式来Fuzz libredwg这个库,通过查看源码发现了一个数据处理的接口

/** dwg_read_file
* returns 0 on success.
*
* everything in dwg is cleared
* and then either read from dat, or set to a default.
*/
EXPORT int
dwg_read_file (const char *restrict filename, Dwg_Data *restrict dwg)

这个接口会读取一个dwg文件,然后把解析文件的结果存放到dwg里面。为了Fuzz该接口,首先我们要先用dlopen把libredwg.so这个库加载到内存中,然后通过dlsym获取dwg_read_file函数的地址,最后我们传入文件路径调用目标函数。

typedef int (*dwg_read_file)(char *filename, Dwg_Data *dwg);
dwg_read_file p_dwg_read_file = NULL; int main(int argc, char** argv)
{
void *handle;
Dwg_Data dwg; handle = dlopen("./libredwg.so", RTLD_LAZY);
printf("loader main address:%p\n", main);
struct link_map *lm = (struct link_map*)handle;
printf("base:%p\n", lm->l_addr);
p_dwg_read_file = dlsym(handle, "dwg_read_file");
printf("p_dwg_read_file:%p\n", p_dwg_read_file); p_dwg_read_file(argv[1], &dwg); dlclose(handle);
return EXIT_SUCCESS;
}

然后我们把程序编出来

gcc parse.c -lpng -L .libs/ -o parse

AFL qemu模式下默认会在可执行程序的入口点出初始化fork server并开始插桩基本块,我们可以通过环境变量来控制AFL的fork server的初始化位置以及基本块插桩的范围。首先我们直接运行AFL来fuzz看看, 执行的命令如下

~/workplace/AFLplusplus/afl-fuzz -Q -i in/ -o output -- ./loader @@

Fuzz一段时间后发现AFL一条新路径也没有发现,这是很不正常的

通过分析AFL的源码,发现是AFL在记录程序的执行路径时,最多只记录MAP_SIZE条边。

unsigned int afl_inst_rms = MAP_SIZE;

当执行到被测库libredwg.so时,记录的执行边的数量已经达到了最大值,导致libredwg.so里面的执行路径没有被记录,所以程序的每次执行都会得到一样的覆盖率记录,这也就是AFL一直没有发现新路径的原因。为了解决这个问题,我们可以通过环境变量来让AFL只记录libredwg.so里面的执行路径。

export AFL_CODE_START=0x400105a7b0
export AFL_CODE_END=0x400142c38d

AFL_CODE_START和AFL_CODE_END分别表示要插桩的起始位置和结束位置,在这里分别表示libredwg.so模块在内存中代码段的起始地址。为了定位这个地址,首先我们使用afl-qemu-trace来看一下libredwg.so模块和loader可执行文件的加载基地址。

$ ~/workplace/AFLplusplus/afl-qemu-trace ./loader
loader main address:0x400736
base:0x4001019000
p_dwg_read_file:0x40010db5b0

可以看到libredwg.so的加载基地址为0x4001019000,loader可执行文件的加载基地址为0x400000,然后用IDA就可以定位代码段的起始地址了。设置环境变量后再次运行afl-fuzz,可以发现AFL发现很多新路径

不过目前的测试速度很慢,速度慢的主要原因是默认情况下AFL会在程序执行的第一条指令前启动forkserver,这会导致每次Fuzz时程序都需要用dlopen加载libredwg.so,这个过程的开销比较大。为了提升测试速度,我们可以使用AFL_ENTRYPOINT来指定程序初始化forkserver的位置,我们可以设置在dlopen之后才启动forkserver,这样在之后的Fuzz过程中不需要执行dlopen了。一般而言forkserver初始化的位置越后越好,不过forkserver初始化的位置必须要在打开输入文件之前。

.text:00000000004007E0                 call    _printf
.text:00000000004007E5 mov rax, cs:p_dwg_read_file
.text:00000000004007EC mov rdx, [rbp+var_FA0]
.text:00000000004007F3 add rdx, 8
.text:00000000004007F7 mov rdx, [rdx]
.text:00000000004007FA lea rcx, [rbp+var_F80]
.text:0000000000400801 mov rsi, rcx
.text:0000000000400804 mov rdi, rdx
.text:0000000000400807 call rax ; 调用 dwg_read_file

值得注意的是在qemu中是按控制转移指令来切分基本块,比如call, jmp指令。在IDA中查看汇编代码可以找到0x4007E5是最靠近文件打开函数(dwg_read_file)的基本块,当我们设置AFL_ENTRYPOINT为这个基本块的地址时,在Fuzz开始时会在AFL_ENTRYPOINT启动forkserver,在之后的每次Fuzz时,程序都会从AFL_ENTRYPOINT处开始往下执行,这样AFL_ENTRYPOINT前的dlopen代码就只会执行一次,大大节省了时间,设置环境变量如下

export AFL_ENTRYPOINT=0x4007E5

执行截图如下,可以看到执行速度得到了很大的提升,达到了149.9 次每秒

使用内存磁盘也可以提升速度,Linux下创建内存磁盘的命令如下

sudo mkdir /mnt/ramdisk
sudo mount -t tmpfs -o size=500m tmpfs /mnt/ramdisk

执行命令后会在/mnt/ramdisk挂载内存磁盘,把初始数据拷贝到/mnt/ramdisk/in/中后启动AFL即可

~/workplace/AFLplusplus/afl-fuzz -t 200  -Q -i /mnt/ramdisk/in/ -o /mnt/ramdisk/out  -- ./loader @@

AFL分析与实战的更多相关文章

  1. 《iOS应用逆向工程:分析与实战》

    <iOS应用逆向工程:分析与实战> 基本信息 作者: 沙梓社    吴航    刘瑾 丛书名: 信息安全技术丛书 出版社:机械工业出版社 ISBN:9787111450726 上架时间:2 ...

  2. 新书《iOS应用逆向工程:分析与实战》

    前无古人!小白福音!国内第一本iOS应用逆向工程类图书<iOS应用逆向工程:分析与实战>就要空降啦~! 你是否曾因应用上线的第一天即遭破解而无奈苦恼,想要加以防范,却又束手无策? 你是否曾 ...

  3. 《深入理解Java虚拟机》调优案例分析与实战

    上节学习回顾 在上一节当中,主要学习了Sun JDK的一些命令行和可视化性能监控工具的具体使用,但性能分析的重点还是在解决问题的思路上面,没有好的思路,再好的工具也无补于事. 本节学习重点 在书本上本 ...

  4. 《深入理解Java虚拟机》-----第5章 jvm调优案例分析与实战

    案例分析 高性能硬件上的程序部署策略 例 如 ,一个15万PV/天左右的在线文档类型网站最近更换了硬件系统,新的硬件为4个CPU.16GB物理内存,操作系统为64位CentOS 5.4 , Resin ...

  5. Android指纹识别深入浅出分析到实战(6.0以下系统适配方案)

    指纹识别这个名词听起来并不陌生,但是实际开发过程中用得并不多.Google从Android6.0(api23)开始才提供标准指纹识别支持,并对外提供指纹识别相关的接口.本文除了能适配6.0及以上系统, ...

  6. 【JVM.4】调优案例分析与实战

    之前已经介绍过处理Java虚拟机内存问题的知识与工具,在处理实际项目的问题时,除了知识与工具外,经验同样是一个很重要的因素.本章会介绍一些具有代表性的案例. 本章的内容推荐还是原文全篇看完的好,实在不 ...

  7. Linux日志分析的实战专题

      来自 日志也是用户应该注意的地方之一.不要低估日志文件对网络安全的重要作用,因为日志文件能够详细记录系统每天发生的各种各样的事件.用户可以通过日志文件 检查错误产生的原因,或者在受到攻击和黑客入侵 ...

  8. Java之JVM调优案例分析与实战(5) - 服务器JVM进程奔溃

    环境:一个基于B/S的MIS系统,硬件为2个CPU.8GB内存的HP系统,服务器是WebLogic9.2(就是第二个案例中的那个系统).正常运行一段时间后,最近发现在运行期间频繁出现集群节点的虚拟机进 ...

  9. Java之JVM调优案例分析与实战(4) - 外部命令导致系统缓慢

    环境:这是一个来自网络的案例:一个数字校园应用系统,运行在一台4个CPU的Solaris 10操作系统上,中间件为ClassFish服务器.系统在进行大并发压力测试的时候,发现请求响应时间比较慢,通过 ...

  10. Java之JVM调优案例分析与实战(3) - 堆外内存导致的溢出错误

    环境:基于B\S的点子考试系统,为了发现客户端能实时地从服务端接收考试数据,系统使用了逆向AJAX技术(也称Comet或Server Side Push),选用CometD1.1.1作为服务端推送框架 ...

随机推荐

  1. C++ 指针基础

    指针 指针具有强大的能力,其本质是协助程序员完成内存的直接操作 指针: 特定类型数据在内存中的存储地址,即内存地址 指针只是一个逻辑概念,其实际应用是:指针变量 语法 * 符号有两种含义: 声明时:* ...

  2. Flutter 2.8 正式发布

    文/ Tim Sneath,Flutter & Dart 产品经理 Flutter 已经更新到 2.8 正式版,发布了多项新特性和改进以不断改善移动和 Web 端的开发体验,同时也正在将桌面端 ...

  3. 系统编程-进程-close-on-exec机制

    我的相关博文: 系统编程-进程-exec系列函数超级详解(带各种实操代码) 一般我们会调用exec执行另一个程序,此时会用全新的程序替换子进程的正文,数据,堆和栈等. 此时保存文件描述符的变量当然也不 ...

  4. 自己动手,通过源码找回 Ant-Design-Blaozr 中 Tree 组件的搜索筛选效果

    最近更新一个Blazor server的项目,顺带把用到的Ant-Design-Blazor 升级到了最新的 0.14.4,结果发现之前在 0.8.4 版本中 Tree 组件的搜索显示效果变了,从仅显 ...

  5. USB Type-C的工作原理与技术分析

    USB TYPE-C更加深入的应用,是从USB3.1开始的,这是因为从USB3.1开始,USB的功能开始变得更加丰富起来. USB 3.1基本规格 有SS字样的代表支持PD,有SS和10的USB标志代 ...

  6. ARMv8 寄存器

    本文主要介绍 Armv8/v9 指令集架构中常用部分,详细的还是要看 Arm architecture reference manual. ARMv8 架构 ARMv8 架构支持3种指令集: T32, ...

  7. C# Webapi Filter 过滤器 - 生命周期钩子函数 - Action Filter 基础

    ACTION Filter IAsyncACtionFilter 接口 : 1.注入ActionFilter // 注册过滤器 builder.Services.Configure<MvcOpt ...

  8. ajax异步请求数据还没有返回,页面时空白的如何处理

    使用骨架屏,给用户一种正在解析数据的感觉 : element-ui的骨架屏 :https://element.eleme.cn/#/zh-CN/component/skeleton

  9. day13-JavaDoc

    JavaDoc JavaDoc命令是用来生成自己API文档的 参数信息 @author 作者名 @version 版本号 @since 指明需要最早使用的jdk版本 @param 参数名 @retur ...

  10. Acrobat DC安装报错1603,Microsoft Visual C++2013(x64)失败

    之前顺利安装过Acrobat DC,但可能因为自动更新了,导致让我重新登录才能使用,无法再次破解.于是我卸载后重新安装,发现提示Microsoft Visual C++2013(x64)运行安装失败. ...