尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Ruffle Fuzzing 实战指南:用 libFuzzer 与 AFL++ 挖掘 SWF 解析与媒体解码缺陷

Ruffle Fuzzing 实战指南:用 libFuzzer 与 AFL++ 挖掘 SWF 解析与媒体解码缺陷 Ruffle Fuzzing 实战指南用 libFuzzer 与 AFL 挖掘 SWF 解析与媒体解码缺陷【免费下载链接】ruffleA Flash Player emulator written in Rust项目地址: https://gitcode.com/GitHub_Trending/ru/ruffleRuffle 需要解析来自互联网的、不可信的 SWF 二进制文件与媒体数据这类输入是典型的模糊测试fuzzing目标。本文基于仓库文档 docs/fuzzing.md 与fuzz/、tools/fuzzer/目录的真实实现讲解 Ruffle 的模糊测试体系为什么需要 fuzzing、fuzz target 的目录结构与双引擎设计、parse_swf目标的源码级实现以及cargo fuzzer助手工具的完整用法、语料准备机制与崩溃产物artifact的处理闭环。读完本文你可以独立运行 Ruffle 的 fuzz targets、理解其底层调用链并知道如何按仓库规范添加一个新的 fuzz target。一、什么是 FuzzingRuffle 为什么需要它Fuzzing模糊测试是一种向程序投喂大量自动生成的、经过变异的输入的技术目的是寻找会导致程序崩溃、挂起或其他异常行为如 panic、越界读取、死循环、内存占用激增的输入。一个典型的 fuzzer 从一组种子输入corpus出发对其进行变异并利用代码覆盖率反馈来引导搜索使其逼近新的、有代表性的程序状态。当 fuzzer 发现某个触发崩溃或挂起的输入时它会将其保存为产物artifact以便后续复现、最小化并最终转化为回归测试。Ruffle 需要 fuzzing 的根本原因在于其输入面Ruffle 负责解析并执行 SWF 文件以及其他媒体数据这些是不可信的、结构复杂的二进制格式可能来自网络上的任何地方。通过模糊测试可以找到并修复现有测试未覆盖的输入所导致的缺陷——不仅包括格式损坏的文件还包括各种罕见的边界情况甚至是那些从未被测试过的普通真实世界 SWF 文件。根据文档Ruffle 中从 fuzzing 受益最大的领域包括SWF 与 tag 解析swfcrate——解压缩、文件头解析以及将大量 tag 类型解码为 Ruffle 内部表示ActionScript 字节码——AVM1 与 AVM2 字节码的解码与解释执行包括损坏的或越界的常量池、跳转与操作码媒体解码器——图像JPEG、PNG、GIF、视频与音频解码损坏的数据是内存安全缺陷的常见来源正则表达式——Ruffle 的 regex 引擎被 ActionScript 的RegExp使用可能暴露于不可信的模式字符串与输入字符串。Ruffle 的 fuzz targets 与配套工具位于 fuzz/ 目录下文将深入这一目录的实际实现。二、fuzz/ 目录布局与双引擎设计fuzz/README.md 说明了 fuzz 体系的组织方式。fuzz/被组织成一个独立的 Cargo workspace这样做的目的是避免污染主 workspace 的 lock 文件。每个 fuzz target 同时支持两种 fuzzing 引擎引擎工具链前提条件libFuzzercargo-fuzz需要 nightly Rust 工具链AFLcargo-afl需要安装 cargo-afl目录结构如下引自 fuzz/README.mdfuzz_targets/—— libFuzzer 入口点每个 target 一个.rs文件fuzz_targets_afl/—— AFL 入口点与上述 target 一一对应src/—— 两种引擎共享的实际 fuzzing 逻辑corpus/—— fuzzer 使用的种子输入按 fuzz/corpuses.toml 组织fuzz/corpuses.toml —— 将每个 target 映射到其语料子目录out/、artifacts/、target/—— 生成的输出已被 git 忽略。从 fuzz/Cargo.toml 可以看到这套双引擎共享逻辑的实现方式包名ruffle-fuzz将libfuzzer-sys与afl都声明为可选依赖并通过libfuzzer/afl两个 feature 控制。每个 target 注册为两个二进制[[bin]] name parse_swf path fuzz_targets/parse_swf.rs required-features [libfuzzer] [[bin]] name parse_swf_afl path fuzz_targets_afl/parse_swf.rs required-features [afl]语料映射文件当前只有一行把parse_swf目标映射到swf语料子目录# fuzz/corpuses.toml parse_swf swf三、源码深潜parse_swf 目标的实现parse_swf是仓库中目前唯一注册的 fuzz target它的核心逻辑只有 5 行位于 fuzz/src/parse_swf.rs#[inline(always)] pub fn parse_swf(data: [u8]) { let Ok(swf_buf) swf::decompress_swf(data) else { return; }; swf::parse_swf(swf_buf).ok(); }这段代码揭示了一个关键设计原则fuzz 逻辑只关心 panic不关心常规错误。swf::decompress_swf负责识别 SWF 头中的压缩标记如 ZLIB/ LZMA并解压缩实现见 swf/src/read.rs解压缩失败属于正常拒绝直接 return不视为缺陷swf::parse_swf完成整个 SWF 文件与 tag 流的解析实现见 swf/src/read.rs末尾的.ok()刻意丢弃解析错误——格式损坏导致的Err是被预期处理的路径只有解析过程中的 panic例如越界访问、解引用失败才代表真正的内存安全或逻辑缺陷需要被 fuzzer 捕获。两个引擎入口分别只是极薄的封装。libFuzzer 入口 fuzz/fuzz_targets/parse_swf.rs#![no_main] libfuzzer_sys::fuzz_target!(|data: [u8]| { ruffle_fuzz::parse_swf::parse_swf(data); });AFL 入口 fuzz/fuzz_targets_afl/parse_swf.rsfn main() { afl::fuzz!(|data: [u8]| { ruffle_fuzz::parse_swf::parse_swf(data); }); }#![no_main]是因为 libFuzzer 自行接管了进程入口。这种共享逻辑放src/、入口按引擎薄封装的布局使得新增一个引擎或修改被测逻辑时只需要同步改动一处核心函数。四、使用 cargo fuzzer 运行 fuzz 测试最常用的交互方式是仓库根目录下的cargo fuzzer别名。该别名在 .cargo/config.toml 中定义[alias] fuzzer run --package ruffle_fuzzer --即它等价于运行tools/fuzzer包因此从仓库根目录执行cargo fuzzer --help即可看到全部子命令。文档给出的最简用法是cargo fuzzer run parse_swfcargo fuzzer对崩溃或挂起敏感发现后它会报告数量并以非零状态码退出error: fuzzer found 1 crash(es) and 0 hang(s), check the artifacts directory产物artifacts写入fuzz/out/ruffle_fuzzer-target/engine/目录engine为afl或libfuzzer。绝大多数时候你不需要手动运行 fuzzer——它们会在 CI 中自动运行。4.1 子命令与参数cargo fuzzer工具实现于 tools/fuzzer/src/main.rs基于 clap 构建提供四个子命令子命令作用关键参数list列出可用 fuzz targets--long显示每个 target 支持的引擎、--fuzzer按引擎过滤默认全部prepare准备 fuzzing 语料无build构建 fuzz targets可为单个或全部target、--fuzzerrun准备语料、构建并运行 fuzz targetstarget、--no-prepare、--no-build、--time、--fuzzer其中run的参数值得注意target为位置参数缺省时运行全部targets--time使用humantime解析时长例如cargo fuzzer run parse_swf --time 5m支持30s、5m、2h等形式--fuzzer可以限定引擎afl或libfuzzer默认两者都运行。工具自身的帮助信息给出了典型用法cargo fuzzer run parse_swf --time 5m --fuzzer afl --fuzzer libfuzzer4.2 运行前的环境检查cargo fuzzer在执行前会调用check_fuzzerstools/fuzzer/src/lib.rs验证环境若选择 libFuzzer会执行rustc --version检查当前工具链是否为nightlylibFuzzer 经由cargo-fuzz依赖 nightly 特性对每个引擎通过cargo fuzz|afl --help探测cargo-fuzz/cargo-afl是否已安装缺失时会给出cargo install建议并提示可以用--fuzzer切换到另一引擎。此外.cargo/config.toml的[env]段预设了两个环境变量工具通过它们定位目录LOCAL_RUFFLE_TESTS_SWFS_DIR { value tests/tests/swfs, relative true } LOCAL_RUFFLE_FUZZ_WORKSPACE_DIR { value fuzz, relative true }4.3 语料准备把回归测试 SWF 变成种子run除非指定--no-prepare与prepare子命令会执行prepare_swf_tests_corpustools/fuzzer/src/lib.rs。它的工作是递归遍历LOCAL_RUFFLE_TESTS_SWFS_DIR指向的 tests/tests/swfs 目录即 Ruffle 庞大的 SWF 回归测试语料库把所有.swf文件扁平化复制到fuzz/corpus/swf/swf_tests/——原路径中的目录层级用_连接例如avm1/globals/foo.swf变为avm1_globals_foo.swf保证文件名唯一若目标目录已存在则先整体重建保证语料与测试树同步。这正是文档所述普通真实世界 SWF 的处理也值得被 fuzz的落地把真实回归测试用的 SWF 直接作为变异起点使 fuzzer 在已知良好结构附近探索提高触发深层解析路径的概率。语料目录本身由 fuzz/corpuses.toml 的映射确定corpus_for()读取parse_swf swf最终指向fuzz/corpus/swf/。4.4 底层进程编排两个引擎如何被拉起run_fuzz_targets会为每个 target × 每个引擎的组合各启动一个子进程tools/fuzzer/src/lib.rs输出目录统一为fuzz/out/ruffle_fuzzer-name/。spawn_fuzz_process中两条实际命令值得对照阅读tools/fuzzer/src/lib.rsAFLcargo afl fuzz -i corpus -o fuzz/out/ruffle_fuzzer-name/afl -V 秒 -- target/debug/name_afllibFuzzercargo fuzz run name corpus -- -fork1 -ignore_crashes1 -ignore_ooms1 -ignore_timeouts1 -artifact_prefixout/--time转换为-max_total_time秒。注意 libFuzzer 侧的三个ignore_*标志这使单个崩溃、OOM 或超时不会终止 fuzzer 进程而是把触发输入落盘为 artifact 后继续运行。这与先跑完一整轮、最后统一汇总的设计一致。4.5 结果统计与退出码运行结束后工具按引擎不同扫描输出目录统计tools/fuzzer/src/lib.rsAFL统计输出目录下crashes/与hangs/子目录中的文件数libFuzzer统计crash-*与oom-*前缀的文件为 crashtimeout-*前缀的文件为 hang。两者汇总为RunStats { crashes, hangs }只要非零main就打印文档中展示的报错信息并以状态码 1 退出tools/fuzzer/src/main.rs。单 target 单引擎时子进程输出直接透传到终端多进程并发时每个进程的 stdout/stderr 被重定向到fuzz/out/ruffle_fuzzer-name/logs/bin.log便于事后排查。五、添加新的 fuzz targetfuzz/README.md 给出了四步规范与 fuzz/Cargo.toml 的实际注册方式一一对应在src/中新增 fuzzing 逻辑函数并从 fuzz/src/lib.rs 导出当前为pub mod parse_swf;在fuzz_targets/name.rslibFuzzer和/或fuzz_targets_afl/name.rsAFL添加入口调用该函数——可参照 fuzz/fuzz_targets/parse_swf.rs 的fuzz_target!与fuzz_targets_afl下的afl::fuzz!两种写法在Cargo.toml的[[bin]]段注册二进制AFL 入口按约定命名name_afl并设置对应required-features在 fuzz/corpuses.toml 中为该 target 添加语料映射并在fuzz/corpus/子目录/放置种子输入。完成这四步后cargo fuzzer list即会自动发现新 target——target 发现机制就是扫描fuzz_targets/与fuzz_targets_afl/两个目录下的.rs文件名tools/fuzzer/src/lib.rs无需修改工具代码。六、产物处理与 CI 闭环文档给出的工作流程是完整闭环发现fuzzer本地cargo fuzzer run或 CI 自动运行发现崩溃/挂起输入保存触发输入被写入fuzz/out/ruffle_fuzzer-target/engine/下的 artifact 文件libFuzzer 侧由-artifact_prefix控制前缀复现与最小化将 artifact 直接回喂给 fuzz 二进制即可复现再用最小化工具缩减输入回归化把最小化输入转入tests/tests/swfs/一类的回归测试语料——由于prepare_swf_tests_corpus会把整个测试 SWF 树作为 fuzz 种子回归语料库与 fuzz 种子库天然共享同一来源形成发现缺陷 → 修复 → 语料反哺 fuzzer的循环。适用前提小结仓库根目录执行cargo fuzzer它依赖 .cargo/config.toml 中的别名与环境变量预设libFuzzer 路径要求 nightly 工具链与cargo-fuzzAFL 路径要求cargo-afl可用--fuzzer二选一fuzz/是独立 workspace构建产物与输出位于fuzz/out/、fuzz/target/git 忽略当前仓库中实际注册的 target 只有parse_swf对应 fuzz/Cargo.toml 的两个[[bin]]文档中提到的 ActionScript 字节码、媒体解码器与正则引擎属于文档规划的受益领域可在其对应 crate 中按第五节流程扩展新 target。综上Ruffle 的 fuzzing 体系把面向不可信二进制的健壮性需求落成了可操作的工具链共享 fuzz 逻辑与双引擎入口分离、基于真实回归测试 SWF 的自动语料准备、崩溃容忍的批量运行与统一的产物统计。开发者只需cargo fuzzer run parse_swf一条命令即可参与这一过程而 CI 则保证了 fuzz targets 在无人值守下持续运行。【免费下载链接】ruffleA Flash Player emulator written in Rust项目地址: https://gitcode.com/GitHub_Trending/ru/ruffle创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表