软件安全实验 SEEDlabs 模糊测试技术实验(基于AFL)
Task1:初探AFL
Task 1.1:安装 AFL
我们从源码进行构建,克隆最新版本的源码。
1 | |
随后进入 AFL文件夹,在文件夹下打开命令行输入:
1 | |
安装完成后,AFL安装在/usr/local/bin目录下,ls查看文件判断是否安装成功
1 | |

Task1.2:初步尝试
首先准备一段待测试的代码test.c,此代码中模拟了4个漏洞(有些漏洞并非真实安全问题,仅用 作演示)。

首先,使用afl-gcc编译待测试的test.c文件
实质上,afl-xxx系列命令是对编译器如gcc、g++或clang的包装,其使用的参数选项与这些编译 器相同。
对于C++源代码,应使用afl-g++进行编译。 接下来,创建两个目录fuzz_in和fuzz_out,分别用于存放模糊测试的输入和输出。在fuzz_in目 录中至少需要准备一个测试用例。
执行以下命令进行模糊测试
1 | |
fuzz_out 目录记录了模糊测试的运行结果。通过分析此目录,我们可以识别出导致程序崩溃(crash) 的具体测试用例(case)。
crashes 子目录包含了触发程序崩溃的测试用例,hangs子目录包含了导致程序超时的测试用例,而 queue 子目录存储了所有探索到的不同执行路径的测试用例。如果获得了所需的崩溃信息或长时间无法获 取新的有趣输出,应使用Ctrl-C命令退出模糊测试过程。
例如,可使用xxd命令查看触发的具体崩溃实例,分析test.c可知crashcase3是触发了第三个漏 洞,即输入长度过长导致栈溢出,程序将异常退出。


任务:请使用 xxd查看你触发crash的cases,并判断分别对应 test.c中哪种漏洞,并解释漏洞产生 的原因。注意,你的crashcases应覆盖所有4个漏洞。
特定字符串触发异常 (Logic Flaw - “FAS” + Length 6)

现象:分析 AFL 生成的崩溃样本 id:000007,通过 xxd 命令查看其二进制内容。样本内容以十六进制 46 41 53 开头(即 ASCII 码 “FAS”),且文件总长度为 6 字节。
成因:对应代码 test.c 第 14-20 行。函数 vuln_test 检查输入字符串:若首字符为 ‘F’ 且长度为 6,随后继续检查第二字符是否为 ‘A’、第三字符是否为 ‘S’。一旦匹配字符串 “FAS…”,程序调用 raise(SIGSEGV) 导致崩溃。
栈缓冲区溢出 (Stack Buffer Overflow)

现象:AFL 生成了大量长文件样本(例如 id:000002),其长度(如 26698 字节)远超缓冲区大小。
成因:对应代码 test.c 第 34 行 gets(buff)。变量 buff 定义为 char buff[80],但 gets() 函数未对输入长度进行限制。当输入长度超过 80 字节时,多余的数据覆盖了栈上的关键信息(如返回地址),导致函数返回时跳转到非法地址,引发段错误。
格式化字符串漏洞 (Format String Vulnerability)

现象:通过输入包含格式化字符(如 %s%s%s)的样本 manual_vuln4_fmt,程序在未发生栈溢出的情况下崩溃。
成因:对应代码 test.c 第 35 行 printf(buff)。程序直接将用户输入作为 printf 的格式化字符串参数,而未提供后续参数。当输入包含 %s、%n 等格式说明符时,printf 尝试从栈中读取非法内存地址或向非法地址写入数据,导致访问违规(Access Violation)并崩溃。
特定条件触发异常 (Logic Flaw - ‘C’ + Length 30)

现象:通过构造首字符为 ‘C’ 且总长度为 30 字节的输入样本 manual_vuln1_c_30,程序运行后发生段错误(Segmentation Fault)。
成因:代码 test.c 第 10-12 行存在逻辑判断 if(str[0] == ‘C’ && len == 30)。当输入满足此条件时,程序主动调用 raise(SIGSEGV) 触发崩溃。AFL 在测试中倾向于生成长字符串触发栈溢出,因此该精确长度路径较难命中,通过手动验证确认了漏洞存在。
Task2: 基于AFL++测试Xpdf
AFL++ 是在原始AFL框架基础上演进的覆盖引导模糊测试工具,对性能和可用性进行了多项优化, 尤其在大型项目模糊测试场景下往往具有更好的执行效率。尽管如此,AFL++在整体架构、使用方式和 核心工作流程上与AFL基本保持一致。在本任务中,我们将使用AFL++对大型开源PDF处理组件Xpdf 进行模糊测试实践。
AFL++ 安装
本实验采用自源码编译的方式安装最新版本的AFL++2。在此之前,需要安装若干构建依赖包
1 | |
系统提示LLVM版本过旧 如果在执行AFL++相关命令时收到“LLVM版本过旧”的提示,需要安装更新版 本的LLVM/Clang。以下示例以安装LLVM15为例:
1 | |
构建测试环境
首先,为本次Xpdf模糊测试创建单独工作目录:
1 | |
下载并解压Xpdf3.02版本源码:
1 | |
使用系统默认编译器对Xpdf进行常规构建与安装(用于功能验证)
1 | |
随后,准备若干PDF示例文件用于测试:
1 | |
可以通过如下命令调用pdfinfo对示例文件进行解析,检查Xpdf是否安装成功并可正常工作:
1 | |
若能看到正确的PDF元数据输出,则说明Xpdf已成功构建和安装。

执行模糊测试
在使用AFL++重新构建前,先清理已有的安装目录和编译产物:
1 | |
与afl-gcc 类似,afl-clang-fast 是对 clang 的封装,通过LLVMPass对目标程序进行插桩,从 而在性能与插桩灵活性方面具备优势(原版AFL也支持LLVMMode,有兴趣可进一步自行探索)。 使用afl-clang-fast 和afl-clang-fast++ 作为编译器,对Xpdf进行重新配置与构建:
1 | |
在编译输出中可以看到来自AFL++的插桩提示,表明目标程序已被插入覆盖率收集代码。随后,可 以使用如下命令启动模糊测试:
1 | |
参数含义如下:
•-i:指定初始输入(种子)文件目录。
•-o:指定AFL++输出目录,用于保存变异样本、崩溃用例等。
•-s:设置静态随机种子。AFL默认采用非确定性测试流程,使用固定种子(如-s 123)有助于复现 实验结果。
• @@:命令行参数占位符,AFL++会在运行时将其替换为当前输入样本的文件名。

复现Crash
本实验中触发的崩溃对应的已知安全问题被分配为CVE-2019-13288。请阅读相关公开信息,接下来我们将使用gdb分析导致程序崩溃的具体原因。
首先,需要重新编译Xpdf,引入调试信息以获得完整的符号和栈回溯信息:
1 | |
然后,使用gdb对触发崩溃的输入样本进行调试(将替换为实际Crash文件名)
1 | |
在gdb中依次执行:
1 | |
run 启动程序,崩溃发生后通过bt查看栈回溯信息,一般可以看到因段错误(SegmentationFault) 导致的异常退出。在实验报告中,请结合回溯信息说明程序崩溃的大致位置与原因。
进一步分析可以发现,在解析PDF过程中,多次调用了parser.cc中的相关函数。初步怀疑问题与 getObj 和 fetch 等函数的调用逻辑有关。为验证该假设,可以在Parser::getObj()处设置断点,并重 新运行程序:
任务:请依据上述步骤,结合gdb进行Crash的复现与分析。在实验报告中给出调试过程的关键截图, 描述你的观察结果,并解释该漏洞产生的根本原因(例如错误的边界检查、异常输入未被正确处理等)。





崩溃位置:EmbedStream::getChar() (Frame #0),由 Gfx::opBeginImage (Frame #1) 调用。
成因分析:
这是一个**内存访问违规(Memory Corruption / Segmentation Fault)**漏洞。
通过 GDB 调试发现,程序在处理 PDF 中的图像对象(opBeginImage)时崩溃。结合命令行输出的大量 Error: Dictionary key… 提示,说明 AFL++ 生成的畸形 PDF 文件破坏了对象的字典结构。当程序试图通过 EmbedStream::getChar 读取图像数据流时,由于结构损坏(可能是空指针引用或越界读取),访问了非法的内存地址,导致操作系统抛出 SIGSEGV 信号终止程序。这证实了 Xpdf 在解析畸形 PDF 对象时存在输入验证缺陷。
Task3: 使用QEMU模式执行模糊测试(无程序源码)
在Task1和Task2中,我们主要针对具有源代码的程序开展模糊测试。而在真实应用场景中,测试目 标往往仅以二进制形式提供,无法获得源码。为支持此类“黑盒”目标,AFL++提供了QEMU模式,使得在 缺乏源码的情况下仍然可以进行覆盖引导的模糊测试。启用QEMU模式非常简单,只需在原有afl-fuzz 命令行基础上增加-Q参数。
QEMU模式是AFL++的高级特性之一。它基于QEMU(QuickEMUlator,官方网站:https://www.qemu.org/)这一全系统模拟器,在运行时对目标程序进行动态二进制翻译与插桩:即由QEMU逐块读取 目标程序的机器指令,将其翻译为宿主平台等价的指令序列,并在翻译过程中自动插入额外的监控代码, 用于记录执行路径和代码覆盖情况,而无需重新编译或修改原始可执行文件。通过这种方式,即便只有二 进制文件,也能实现覆盖引导的模糊测试。
安装QEMU模式(在AFLplusplus目录下)
首先,在AFLplusplus源码目录中为QEMU模式安装所需依赖并构建支持模块:
1 | |
若安装过程中出现”cannot find”类错误,说明系统缺失某些必需软件包,请根据错误提示使用 sudo apt install 安装缺失依赖。
完成构建后,返回上级目录并重新安装AFL++,使QEMU相关组件正确注册到系统路径中:
1 | |
随后可以检查afl-qemu-trace是否已安装到/usr/local/bin等路径下:
1 | |

使用QEMU模式执行模糊测试
根据任务提供的环境,我们使用已编译并安装好的exif程序4作为模糊测试目标,输入样本来自 exif-samples 文件夹中的JPEG文件。
由于exif依赖特定版本的libc,需要先更新系统中的libc库。可以通过添加新的软件源来获取合 适版本的libc:
1 | |
保存后,更新软件源并安装libc6:
1 | |
系统提示Meson版本过旧 如果AFL++在构建或使用过程中提示Meson版本过旧,可以通过卸载发行 版自带的旧版本并使用PyPI安装新版本来解决,示例命令如下:
1 | |
然后配置编译环境,使AFL++及目标程序优先使用新安装的LLVM/Clang工具链(示例仍以LLVM 15 为例):
1 | |
依赖准备完成后,将提供的exif程序移动到下面路径,即可使用QEMU模式启动模糊测试:
1 | |
其中,-Q表示启用QEMU模式,其他参数含义与前一任务类似。需要注意的是,QEMU模式通过动 态二进制翻译实现插桩,通常比基于源码的插桩模式更慢,因此模糊测试需要更长时间,请耐心等待。

分析得到的Crash
当AFL++在QEMU模式下发现崩溃样本后,可以使用GDB对相应输入进行调试,分析程序异常的根 本原因。例如:
1 | |
任务:请结合网上公开信息研究exif中已知的CVE-2009-3895或CVE-2012-2836漏洞。参考Task2中 的分析流程,利用afl-fuzz-Q获得崩溃样本,并通过gdb进行调试,找出至少一个漏洞的触发路径与 成因。在实验报告中详细说明该漏洞是如何产生的,以及恶意输入如何导致程序崩溃或异常行为(需配合 调试截图说明)




调试现象:
程序运行后中止并抛出 SIGABRT 信号。
GDB 输出显示错误信息 realloc(): invalid next size。这表明 glibc 的内存分配器在执行 realloc 操作时,检测到当前堆块(Chunk)的元数据(如头部的大小字段)已被覆盖或损坏,从而触发了安全机制主动终止程序。
漏洞定位:
通过 bt(栈回溯)发现,崩溃发生在 exif_entry_fix 函数(exif-entry.c:253)调用 exif_entry_realloc 的过程中。
成因分析 (Root Cause):
这是一个典型的堆溢出导致的堆破坏 (Heap Corruption triggered by Heap Overflow)。
根据调用栈分析,libexif 在处理畸形的 EXIF 标签时,调用了 exif_entry_fix 试图修正条目大小。由于输入文件中的标签长度字段被恶意篡改(例如伪造了一个巨大的长度),导致程序在之前的数据写入操作中越界,覆盖了堆内存中相邻块的头部信息(Metadata)。
当程序随后调用 realloc 试图调整内存大小时,分配器检查发现堆块的 next size 字段非法(invalid next size),从而判定堆结构已损坏并终止运行。这与 CVE-2009-3895(整数溢出导致堆溢出)的特征完全一致。