找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

325

积分

0

好友

45

主题
发表于 21 小时前 | 查看: 7| 回复: 0

AFL(American Fuzzy Lop)是由安全研究员Michał Zalewski(@lcamtuf)开发的一款基于覆盖引导(Coverage-guided)的模糊测试工具。它通过在程序插桩以记录代码覆盖率(Code Coverage),并据此调整输入样本,从而更高效地探索程序路径,提升发现漏洞的概率。

其核心工作流程可以概括为:

  1. 从源码编译程序时进行插桩,以记录代码覆盖率。
  2. 选择一些输入文件,作为初始测试集加入输入队列(queue)。
  3. 将队列中的文件按一定的策略进行“突变”。
  4. 如果变异后的文件触发了新的代码覆盖,则将其保留并添加到队列中。
  5. 上述过程会一直循环进行,期间所有触发程序崩溃(crash)的文件都会被记录下来。

AFL工作原理示意图

一、AFL安装与基础测试

1. 安装AFL

首先下载AFL源码并编译安装。对于更优的性能,可以安装llvm_mode。

下载源码: 下载AFL源码

执行编译: 执行make编译

安装llvm模式(可选): 安装llvm_mode

最后进行安装: 执行安装命令

2. AFL基础测试

我们用一个有缺陷的C程序来验证AFL是否工作正常。

首先,下载测试用的C文件: 下载测试C文件

使用AFL提供的编译器进行插桩编译:

afl-gcc -o test_afl test.c

使用afl-gcc编译

准备一个初始的种子语料库,例如一个简单的文本文件: 生成种子语料库

开始模糊测试:

afl-fuzz -i in -o out -- ./test_afl @@

开始fuzz测试

如果系统提示需要修改 /proc/sys/kernel/core_pattern,请按照提示执行相应命令(通常为 echo core | sudo tee /proc/sys/kernel/core_pattern)。

修改后再次运行afl-fuzz,即可看到正常的Fuzzing状态界面: Fuzz运行状态

出现该界面即表示AFL安装成功。如果状态栏出现 (odd, check syntax!) 提示,通常意味着初始语料库完全无法被程序解析,需要调整或提供更有效的种子文件。

使用 Ctrl+C 中断测试后,可以在输出目录(out/)中查看测试结果,包括触发的崩溃用例、覆盖路径等信息。 查看测试结果

3. 并行Fuzz测试

AFL的每个 afl-fuzz 进程会占用一个CPU核心。在多核主机上,我们可以启动多个实例进行并行测试,以提升测试效率。

首先,查看CPU核心数:

grep -c ^processor /proc/cpuinfo

查看CPU核心数

上图显示有4个核心,意味着我们可以同时运行最多4个实例。并行时,需要指定一个主实例(-M)和若干个从实例(-S)。

  • 主实例命令:afl-fuzz -M master -i in/ -o out/ -m none -- ./target @@
  • 从实例命令:afl-fuzz -S slave1 -i in/ -o out/ -m none -- ./target @@

启动主从实例

执行后,在输出目录 out/ 下会生成 masterslave1 等文件夹,分别存放各自实例的进度和发现。

如果尝试启动超过核心数的实例(例如在第5个终端启动),后续实例会因无法获取CPU资源而报错,但不影响已运行的实例。 超过核心数报错

二、实战:对libjpeg-turbo进行模糊测试

libjpeg是处理JPEG图像编解码的流行库,libjpeg-turbo 是其性能优化的分支。对其进行安全测试是检验AFL实战能力的好例子。

1. 编译插桩版本的libjpeg-turbo

首先下载 libjpeg-turbo 源码。为了进行覆盖引导的模糊测试,我们需要修改其CMakeLists.txt,在 cmake_minimum_required 命令下方添加编译器选项,强制使用AFL的编译器进行插桩编译:

# 在文件靠前位置添加,避免被覆盖
set(CMAKE_C_COMPILER /usr/local/bin/afl-gcc)
set(CMAKE_CXX_COMPILER /usr/local/bin/afl-g++)

修改CMakeLists.txt

随后,在源码目录中执行编译安装:

mkdir build && cd build
cmake ..
make
sudo make install

编译安装过程 make install过程

编译完成后,build 目录内容如下: build目录内容

为了测试库是否安装成功,并作为Fuzz目标,我们编写一个简单的测试程序(例如 test_turbo.c),调用库函数对JPEG图片进行压缩处理。

理想情况下,由于我们修改了CMakeLists.txt,库文件应该已被插桩。但在实际测试时发现,动态链接的方式可能未成功传递插桩信息。通过研究源码自带的测试项目,我们找到了静态链接的编译方法: 查看静态链接方式

据此,我们使用静态链接方式编译自己的测试程序:

afl-gcc -static -o turbo_test test_turbo.c `pkg-config --libs --static libturbojpeg`

静态链接编译

编译成功后,开始对静态链接的可执行文件进行模糊测试。我们启动了4个并行实例,总执行次数超过1亿次,这体现了覆盖率引导的高效性。测试结果表明,libjpeg-turbo 2.0版本的安全性非常 robust,未发现崩溃。 并行Fuzz状态1 并行Fuzz状态2

2. 结合内存错误检查工具(ASAN)

AddressSanitizer (ASAN) 是一种快速内存错误检测器。与AFL结合,可以在Fuzzing过程中更精准地发现内存漏洞。

首先,了解ASAN的基本用法。编译测试程序时添加 -fsanitize=address 选项:

g++ -fsanitize=address -fno-omit-frame-pointer -o test_asan vuln_demo.cpp

运行程序,ASAN会清晰地报告如 use-after-free、stack-buffer-overflow 等错误及其位置。 ASAN检测use-after-free ASAN检测stack buffer overflow

要在AFL中启用ASAN,需要在编译目标程序时设置环境变量:

AFL_USE_ASAN=1 make

AFL编译启用ASAN

注意,后续使用AFL测试该程序时,也需要在 afl-fuzz 命令中设置相应的ASAN环境变量(如 AFL_PRELOAD 指向ASAN运行时库),否则可能会报错。 ASAN环境问题报错

3. 使用与构建自定义字典

AFL内置了字典,但为特定文件格式(如JPEG)构建自定义字典,能指导变异过程更快速地生成有效语法结构,提升效率。

AFL自带的JPEG字典示例: AFL自带JPEG字典

我们可以分析JPEG文件格式,提取常见且关键的魔术字、标记符,构建自己的字典文件 jpeg.dict

FFD8 “SOI”
FFE0 “APP0”
FFDB “DQT”
FFC0 “SOF0”
FFC4 “DHT”
FFDA “SOS”
FFD9 “EOI”

自定义JPEG字典

afl-fuzz 命令中使用 -x 参数指定字典文件:

afl-fuzz -i in -o out -x ./jpeg.dict -- ./turbo_test @@

使用-x参数指定字典

4. 语料库蒸馏与精简

随着Fuzzing进行,语料库可能变得冗余。AFL提供了工具对其进行优化。

  • afl-cmin(语料库最小化):从大量输入文件中筛选出一个能保持相同代码覆盖率的最小集合。 afl-cmin运行

  • afl-tmin(单个文件精简):在不影响覆盖路径的前提下,尽力减小单个输入文件的大小。默认是instrumented模式。有时精简策略可能导致文件变为0字节,这在某些情况下是正常的。 afl-tmin精简文件 对于包含特殊字符的文件名或目录,可能需要编写脚本配合处理。 处理文件夹的脚本 使用 -x 参数可切换至crash模式,该模式会直接剔除导致程序异常退出的输入。

5. 持久模式(Persistent Mode)

当Fuzzing的目标是大型程序中的一个独立函数时,持久模式可以避免重复执行程序初始化、解析输入等开销,极大提升测试速度。

其原理是在一个进程内多次循环调用目标函数。我们需要修改源码,在目标函数周围添加AFL的持久循环宏 AFL_PERSISTENT_LOOP修改源码启用持久模式

同时,在编译时需要定义 AFL_PERSISTENT_MODE 宏。 修改CMakeLists以启用持久模式

重新编译库和测试程序后运行 afl-fuzz,可以发现执行速度相比普通模式有显著提升(有时可达数倍)。 持久模式下的fuzz速度

6. 使用afl-cov进行代码覆盖率分析

afl-cov 能方便地整合 lcov/gcov,可视化展示AFL测试用例所覆盖的代码行和分支。

  • GCOV:GCC套件的一部分,用于生成代码覆盖率数据。
  • LCOV:GCOV的图形前端,生成HTML格式的覆盖率报告。

首先安装afl-cov(建议从源码安装): 下载afl-cov源码

要生成覆盖率数据,必须在编译目标程序时加上GCC覆盖率选项 -fprofile-arcs -ftest-coverage修改CMakeLists以支持覆盖率

重新编译后,我们可以让afl-cov实时监控一个正在进行的 afl-fuzz 会话(--live 参数):

/home/user/Desktop/afl-cov/afl-cov -d out --live --enable-branch-coverage -c . -e "cat AFL_FILE | ./turbo_test" --overwrite

启动afl-cov实时监控

然后启动 afl-fuzz。当 afl-fuzz 停止后,afl-cov会生成详细的HTML报告。 生成的覆盖率报告目录

报告首页展示了总体覆盖率统计: 覆盖率报告首页

点击具体源文件,可以查看每行代码被测试用例执行的次数,这对于查漏补缺、优化测试方向非常有帮助。 源码行覆盖率详情 分支覆盖率详情

7. 使用后处理库(afl_postprocess)

后处理库允许我们自定义AFL生成的测试用例的格式。例如,可以强制所有变异出的文件都以特定的文件头开始。

参考官方示例,一个后处理库函数可能形如:

u8* afl_postprocess(const u8* in_buf, u32* len) {
    static u8 header[] = "GIF89a";
    // ... 处理逻辑,将header拼接到in_buf前 ...
    return out_buf;
}

后处理库示例代码

将其编译为动态库:

gcc -shared -Wall -O3 post_library.so.c -o post_library.so

AFL通过环境变量 AFL_POST_LIBRARY 来加载后处理库。设置环境变量后启动 afl-fuzz,AFL会显示库加载成功的提示。

export AFL_POST_LIBRARY=/path/to/post_library.so

设置AFL_POST_LIBRARY环境变量 后处理库加载成功提示

之后,生成的测试用例就会经过后处理函数的格式化。使用此功能时,需确保目标程序能够正确处理这种格式化后的输入。




上一篇:Spring SpEL表达式解析全流程:InternalSpelExpressionParser源码与AST构建实战
下一篇:TUIOS终端窗口管理器实战指南:提升命令行多任务工作效率
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区(YunPan.Plus) ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-6 23:54 , Processed in 0.069168 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 CloudStack.

快速回复 返回顶部 返回列表