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

这个技能涉及通过条件编译和代码修补技术来绕过模糊测试中的障碍,如校验和验证、全局状态和复杂检查,以提高软件测试覆盖率和发现潜在漏洞。适用于软件开发中的模糊测试场景,帮助开发者和测试人员更有效地进行安全测试和缺陷检测。关键词:模糊测试,障碍克服,代码修补,条件编译,软件测试,覆盖率提升,安全漏洞发现。

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

名称: 模糊测试障碍 类型: 技术 描述: > 通过修补代码来克服模糊测试中的障碍的技术。 当校验和、全局状态或其他障碍阻止模糊测试器进展时使用。

克服模糊测试障碍

代码库通常包含防模糊测试模式,这些模式阻止有效覆盖。校验和、全局状态(如时间种子的伪随机数生成器)和验证检查可能会阻止模糊测试器探索更深的代码路径。本技术展示了如何在模糊测试期间修补被测系统(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. 查找在处理更深之前进行的校验和/哈希验证
  2. 检查调用rand()time()或使用系统种子的srand()
  3. 找到拒绝大多数输入的验证函数
  4. 识别在不同运行中不同的全局状态初始化

辅助工具:

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

第二步:添加条件编译

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

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);  // 仅在生产中强制执行
    }
}
// 模糊测试器现在可以探索此检查之后的代码

第三步:验证覆盖率改进

修补后:

  1. 使用模糊测试仪器重新构建
  2. 运行模糊测试器短时间
  3. 比较与未修补版本的覆盖率
  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或语法定义处理复杂格式
  • 测试套件改进: 更好的测试套件有时可以完全避免障碍

反模式

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

工具特定指导

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检查宏
  • 结合消毒剂检测新可达代码中的错误

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的自定义模糊测试器时有用

故障排除

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

相关技能

使用此技术的工具

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

相关技术

技能 关系
模糊测试套件编写 更好的测试套件可能避免障碍;修补允许更深探索
覆盖率分析 使用覆盖率识别障碍和测量补丁效果
语料库播种 种子语料库可以帮助克服障碍而无需修补
字典生成 字典帮助魔数字节,但非校验和或复杂验证

资源

关键外部资源

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

LibFuzzer 文档标志 LLVM官方文档为libFuzzer,包括模糊测试器如何定义编译器宏以及如何有效使用它们。涵盖与消毒剂和覆盖率仪器的集成。

Rust cfg 属性参考 Rust条件编译的完整参考,包括cfg!(fuzzing)cfg!(test)。解释编译时与运行时条件编译和最佳实践。