模糊测试障碍克服技术Skill fuzzing-obstacles

该技能专注于在软件模糊测试中,通过代码补丁和条件编译技术来绕过校验和、全局状态等障碍,以提高代码覆盖率和发现潜在漏洞。关键词:模糊测试、代码补丁、条件编译、覆盖率、校验和、确定性、软件开发测试。

测试 0 次安装 0 次浏览 更新于 3/24/2026

name: 模糊测试障碍克服 type: 技术 description: > 用于补丁代码以克服模糊测试障碍的技术。 当校验和、全局状态或其他障碍阻止模糊器进展时使用。

克服模糊测试障碍

代码库通常包含反模糊测试模式,阻止有效覆盖。校验和、全局状态(如时间种子伪随机数生成器)和验证检查可以阻止模糊器探索更深代码路径。该技术展示了如何在模糊测试期间补丁您正在测试的系统(SUT)以绕过这些障碍,同时保持生产行为。

概述

许多现实世界的程序在设计时未考虑模糊测试。它们可能:

  • 在处理输入前验证校验和或加密哈希
  • 依赖全局状态(例如,系统时间、环境变量)
  • 使用非确定性随机数生成器
  • 执行复杂验证,使模糊器难以生成有效输入

这些模式使模糊测试困难,因为:

  1. 校验和: 模糊器必须猜测正确哈希值(几乎不可能)
  2. 全局状态: 相同输入在不同运行中产生不同行为(破坏确定性)
  3. 复杂验证: 模糊器花费精力命中验证失败,而不是探索更深代码

解决方案是条件编译:在模糊测试构建期间修改代码行为,同时保持生产代码不变。

关键概念

概念 描述
SUT 补丁 修改正在测试的系统以使其对模糊测试友好
条件编译 基于编译时标志行为不同的代码
模糊测试构建模式 启用模糊测试特定补丁的特殊构建配置
误报 在模糊测试期间发现的无法在生产中发生的崩溃
确定性 相同输入总是产生相同行为(对模糊测试至关重要)

何时应用

应用此技术当:

  • 模糊器卡在校验和或哈希验证处
  • 覆盖报告显示大块不可达代码位于验证之后
  • 代码使用基于时间的种子或其他非确定性全局状态
  • 复杂验证使生成有效输入几乎不可能
  • 看到模糊器重复命中相同验证失败

跳过此技术当:

  • 障碍可以通过良好种子语料库或字典克服
  • 验证足够简单供模糊器学习(例如,魔术字节)
  • 正在进行基于语法或结构感知的模糊测试来处理验证
  • 跳过检查会引入太多误报
  • 代码已经对模糊测试友好

快速参考

任务 C/C++ Rust
检查是否为模糊测试构建 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION cfg!(fuzzing)
在模糊测试期间跳过检查 #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return -1; #endif if !cfg!(fuzzing) { return Err(...) }
常见障碍 校验和、伪随机数生成器、基于时间逻辑 校验和、伪随机数生成器、基于时间逻辑
支持的模糊器 libFuzzer、AFL++、LibAFL、honggfuzz cargo-fuzz、libFuzzer

分步指南

步骤1:识别障碍

运行模糊器并分析覆盖以找到不可达代码。常见模式:

  1. 寻找在更深处理前的校验和/哈希验证
  2. 检查调用 rand()time()srand() 与系统种子的情况
  3. 查找拒绝大多数输入的验证函数
  4. 识别在不同运行中不同的全局状态初始化

帮助工具:

  • 覆盖报告(见覆盖分析技术)
  • 使用 -fprofile-instr-generate 进行性能分析
  • 手动代码检查入口点

步骤2:添加条件编译

修改障碍以在模糊测试构建期间绕过它。

C/C++ 示例:

// 之前:硬障碍
if (checksum != expected_hash) {
    return -1;  // 模糊器无法通过这里
}

// 之后:条件绕过
if (checksum != expected_hash) {
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    return -1;  // 仅在生产中强制执行
#endif
}
// 模糊器现在可以探索此检查后的代码

Rust 示例:

// 之前:硬障碍
if checksum != expected_hash {
    return Err(MyError::Hash);  // 模糊器无法通过这里
}

// 之后:条件绕过
if checksum != expected_hash {
    if !cfg!(fuzzing) {
        return Err(MyError::Hash);  // 仅在生产中强制执行
    }
}
// 模糊器现在可以探索此检查后的代码

步骤3:验证覆盖改进

补丁后:

  1. 使用模糊测试工具重新构建
  2. 运行模糊器短时间
  3. 比较覆盖与未补丁版本
  4. 确认新代码路径被探索

步骤4:评估误报风险

考虑跳过检查是否会引入不可能的编程状态:

  • 检查后的代码是否假设已验证属性?
  • 跳过验证是否会导致无法在生产中发生的崩溃?
  • 是否有隐式状态依赖?

如果误报可能,考虑更具针对性的补丁(见常见模式)。

常见模式

模式:绕过校验和验证

使用案例: 哈希/校验和阻止所有模糊器进展

之前:

uint32_t computed = hash_function(data, size);
if (computed != expected_checksum) {
    return ERROR_INVALID_HASH;
}
process_data(data, size);

之后:

uint32_t computed = hash_function(data, size);
if (computed != expected_checksum) {
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    return ERROR_INVALID_HASH;
#endif
}
process_data(data, size);

误报风险: 低 - 如果数据处理不依赖于校验和正确性

模式:确定性伪随机数生成器种子

使用案例: 非确定性随机状态阻止可重现性

之前:

void initialize() {
    srand(time(NULL));  // 每次运行不同种子
}

之后:

void initialize() {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    srand(12345);  // 固定种子用于模糊测试
#else
    srand(time(NULL));
#endif
}

误报风险: 低 - 模糊器可以探索所有具有固定种子的代码路径

模式:谨慎验证跳过

使用案例: 验证必须跳过但下游代码有假设

之前(危险):

#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (!validate_config(&config)) {
    return -1;  // 确保 config.x != 0
}
#endif

int32_t result = 100 / config.x;  // 崩溃:在模糊测试中除零!

之后(安全):

#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (!validate_config(&config)) {
    return -1;
}
#else
// 在模糊测试期间,为失败验证使用安全默认值
if (!validate_config(&config)) {
    config.x = 1;  // 防止除零
    config.y = 1;
}
#endif

int32_t result = 100 / config.x;  // 在两种构建中都安全

误报风险: 缓解 - 提供安全默认值而不是跳过

模式:绕过复杂格式验证

使用案例: 多步骤验证使有效输入生成几乎不可能

Rust 示例:

// 之前:多验证阶段
pub fn parse_message(data: &[u8]) -> Result<Message, Error> {
    validate_magic_bytes(data)?;
    validate_structure(data)?;
    validate_checksums(data)?;
    validate_crypto_signature(data)?;

    deserialize_message(data)
}

// 之后:在模糊测试期间跳过昂贵验证
pub fn parse_message(data: &[u8]) -> Result<Message, Error> {
    validate_magic_bytes(data)?;  // 保留廉价检查

    if !cfg!(fuzzing) {
        validate_structure(data)?;
        validate_checksums(data)?;
        validate_crypto_signature(data)?;
    }

    deserialize_message(data)
}

误报风险: 中 - 反序列化必须优雅处理格式错误数据

高级用法

提示和技巧

提示 为什么有帮助
保留廉价验证 魔术字节和大小检查引导模糊器,成本低
使用固定种子伪随机数生成器 使行为确定性,同时探索所有代码路径
增量补丁 一次跳过一个障碍并测量覆盖影响
添加防御性默认值 当跳过验证时,提供安全后备值
记录所有补丁 未来维护者需要理解模糊测试与生产差异

现实世界示例

OpenSSL: 使用 FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION 修改加密算法行为。例如,在 crypto/cmp/cmp_vfy.c,某些签名检查在模糊测试期间放宽,以允许深入探索证书验证逻辑。

ogg crate (Rust): 使用 cfg!(fuzzing) 在模糊测试期间跳过校验和验证。这允许模糊器探索音频处理代码,无需花费精力猜测正确校验和。

测量补丁效果

应用补丁后,量化改进:

  1. 行覆盖: 使用 llvm-covcargo-cov 查看新可到达行
  2. 基本块覆盖: 比行覆盖更精细
  3. 函数覆盖: 现在可到达多少更多函数?
  4. 语料库大小: 模糊器是否生成更多样化输入?

有效补丁通常增加覆盖10-50%或更多。

与其他技术结合

障碍补丁与以下技术配合良好:

  • 语料库种子: 提供有效输入以通过初始解析
  • 字典: 帮助模糊器学习魔术字节和常见值
  • 结构感知模糊测试: 使用 protobuf 或语法定义处理复杂格式
  • 工具改进: 更好的工具有时可以完全避免障碍

反模式

反模式 问题 正确方法
全部跳过验证 创建误报和不稳定模糊测试 仅跳过阻止覆盖的特定障碍
无风险评估 误报浪费时间并隐藏真实 bug 分析下游代码假设
忘记记录补丁 未来维护者不理解差异 添加注释解释为什么补丁安全
补丁不测量 不知道是否有帮助 比较补丁前后覆盖
过度补丁 使模糊测试构建与生产差异太大 最小化构建间差异

工具特定指南

libFuzzer

libFuzzer 在编译期间自动定义 FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION

# C++ 编译
clang++ -g -fsanitize=fuzzer,address -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \
    harness.cc target.cc -o fuzzer

# 宏通常由 -fsanitize=fuzzer 自动定义
clang++ -g -fsanitize=fuzzer,address harness.cc target.cc -o fuzzer

集成提示:

  • 宏自动定义;手动定义通常不必要
  • 使用 #ifdef 检查宏
  • 与消毒器结合以检测新可到达代码中的 bug

AFL++

AFL++ 在使用其编译器包装器时也定义 FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION

# 使用 AFL++ 包装器编译
afl-clang-fast++ -g -fsanitize=address target.cc harness.cc -o fuzzer

# 宏由 afl-clang-fast 自动定义

集成提示:

  • 使用 afl-clang-fastafl-clang-lto 进行自动宏定义
  • 持久模式工具最受益于障碍补丁
  • 考虑使用 AFL_LLVM_LAF_ALL 进行额外输入到状态转换

honggfuzz

honggfuzz 在构建目标时也支持宏。

# 编译
hfuzz-clang++ -g -fsanitize=address target.cc harness.cc -o fuzzer

集成提示:

  • 使用 hfuzz-clanghfuzz-clang++ 包装器
  • 宏可用于条件编译
  • 结合 honggfuzz 的反馈驱动模糊测试

cargo-fuzz (Rust)

cargo-fuzz 在构建期间自动设置 fuzzing cfg 选项。

# 构建模糊目标(cfg!(fuzzing) 自动设置)
cargo fuzz build fuzz_target_name

# 运行模糊目标
cargo fuzz run fuzz_target_name

集成提示:

  • 使用 cfg!(fuzzing) 在生产构建中进行运行时检查
  • 使用 #[cfg(fuzzing)] 进行编译时条件编译
  • fuzzing cfg 仅在 cargo fuzz 构建期间设置,不在常规 cargo build
  • 可使用 RUSTFLAGS="--cfg fuzzing" 手动启用进行测试

LibAFL

LibAFL 支持用于用 C/C++ 编写的目标的 C/C++ 宏。

# 编译
clang++ -g -fsanitize=address -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \
    target.cc -c -o target.o

集成提示:

  • 手动定义宏或使用编译器标志
  • 与 libFuzzer 相同方式工作
  • 在构建自定义 LibAFL 基础模糊器时有用

故障排除

问题 原因 解决方案
补丁后覆盖不改善 错误识别障碍 性能分析执行以找到实际瓶颈
许多误报崩溃 下游代码有假设 添加防御性默认值或部分验证
代码编译不同 宏未在所有构建配置中定义 在所有源文件和依赖项中验证宏
模糊器在补丁代码中发现 bug 补丁引入无效状态 审查补丁状态不变性;考虑更安全方法
无法重现生产 bug 构建差异太大 最小化补丁;保留状态关键检查的验证

相关技能

使用此技术的工具

技能 如何应用
libfuzzer 自动定义 FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
aflpp 通过编译器包装器支持宏
honggfuzz 使用宏进行条件编译
cargo-fuzz 为 Rust 条件编译设置 cfg!(fuzzing)

相关技术

技能 关系
fuzz-harness-writing 更好的工具可能避免障碍;补丁允许更深探索
coverage-analysis 使用覆盖识别障碍并测量补丁效果
corpus-seeding 种子语料库可以帮助克服障碍而无需补丁
dictionary-generation 字典帮助魔术字节,但不帮助校验和或复杂验证

资源

关键外部资源

OpenSSL Fuzzing Documentation OpenSSL 的模糊测试基础设施展示了大规模使用 FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION。该项目使用此宏修改加密验证、证书解析和其他安全关键代码路径,以启用更深模糊测试,同时保持生产正确性。

LibFuzzer Documentation on Flags libFuzzer 的官方 LLVM 文档,包括模糊器如何定义编译器宏以及如何有效使用它们。涵盖与消毒器和覆盖工具集成。

Rust cfg Attribute Reference Rust 条件编译完整参考,包括 cfg!(fuzzing)cfg!(test)。解释编译时与运行时条件编译以及最佳实践。