ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

OpenSSL Fuzzing 实战指南:使用 LibFuzzer 与 AFL 对 TLS/密码学库进行模糊测试

OpenSSL Fuzzing 实战指南:使用 LibFuzzer 与 AFL 对 TLS/密码学库进行模糊测试 OpenSSL Fuzzing 实战指南使用 LibFuzzer 与 AFL 对 TLS/密码学库进行模糊测试【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/opensslOpenSSL 作为通用 TLS 与密码学库其解析逻辑覆盖 ASN.1、X.509 证书、PKCS#12、CMS、TLS 握手等海量复杂输入格式是模糊测试Fuzzing的典型高价值目标。本文以 fuzz/README.md 为骨架完整讲解在 Linux 环境下从零配置 LibFuzzer 与 AFL 两种模糊测试工具、编译 fuzz 目标、运行语料库、复现崩溃、理解随机数打桩机制以及维护语料库corpus的全流程。读完本文你将能够在本仓库中构建出可实际运行的 OpenSSL fuzzer并把产出崩溃样本接入make test回归测试体系。OpenSSL Fuzz 基础设施概览OpenSSL 仓库在 fuzz/ 目录下维护了一套完整的模糊测试框架核心部件包括fuzz 目标fuzzer targets每个.c文件对应一个 fuzz 入口如 asn1.c、client.c、server.c、cms.c、x509.c、pem.c 等仓库当前还包含 ml-dsa、ml-kem、slh-dsa、quic-client、quic-server 等较新目标的模糊测试。统一驱动fuzzer.h 声明了三个必须实现的接口——FuzzerInitialize()初始化、FuzzerTestOneInput()对单条输入执行测试核心入口、FuzzerCleanup()清理。随机数打桩fuzz_rand.c 通过 provider 机制提供确定性随机数保证语料覆盖稳定可复现。运行辅助helper.py 自动创建语料与崩溃目录并启动 fuzzertest-corpus.c 提供离线回放模式即*-test二进制。语料库fuzz/corpora/ 存放各目标的种子输入seed与崩溃样本。从源码结构看FuzzerTestOneInput的返回值约定为 0真正的失败由崩溃配合 ASan/UBSan体现这与 LibFuzzer/AFL 的判活机制一致——只要进程异常退出即视为发现缺陷。使用 LibFuzzer 进行模糊测试LibFuzzer 是 LLVM 内置的进程内覆盖率引导模糊引擎从 clang 6.0 起随编译器一同发布无需额外安装。OpenSSL 通过enable-fuzz-libfuzzer配置项接入。方案一使用软件包管理器安装的 clang推荐以 Ubuntu 为例安装 clang 后即可获得配套的 libFuzzer 运行库sudo apt-get install clang配置 OpenSSL 进行模糊测试构建。配置时需要通过--with-fuzzer-lib显式传入 libFuzzer 库文件路径下文以$PATH_TO_LIBFUZZER表示典型值为/usr/lib/llvm-7/lib/clang/7.0.1/lib/linux/libclang_rt.fuzzer-x86_64.a完整配置命令如下CCclang ./config enable-fuzz-libfuzzer \ --with-fuzzer-lib$PATH_TO_LIBFUZZER \ -DPEDANTIC enable-asan enable-ubsan no-shared \ -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \ -fsanitizefuzzer-no-link \ enable-ec_nistp_64_gcc_128 -fno-sanitizealignment \ enable-weak-ssl-ciphers enable-rc5 enable-md2 \ enable-nextprotoneg \ --debug逐项解读这些选项的实际作用选项作用enable-fuzz-libfuzzer开启 LibFuzzer 模糊测试构建模式编译出 fuzz 目标可执行文件--with-fuzzer-lib$PATH_TO_LIBFUZZER链接指定的libclang_rt.fuzzer-*.a运行库enable-asan enable-ubsan开启 AddressSanitizer内存错误检测与 UndefinedBehaviorSanitizer未定义行为检测是捕捉崩溃与越界的核心手段no-shared静态链接便于 fuzz 目标直接携带全部被测代码-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION关键宏将 TLS 客户端/服务端的随机数替换为可预测序列保证语料覆盖稳定详见下文随机数打桩一节-fsanitizefuzzer-no-link对所有编译单元注入覆盖率插桩但不链接 fuzzer 入口入口由 fuzz 目标单独链接enable-ec_nistp_64_gcc_128 -fno-sanitizealignment启用 64 位 NIST P 曲线优化实现并关闭对齐 sanitizer 以避免该实现产生误报enable-weak-ssl-ciphers enable-rc5 enable-md2 enable-nextprotoneg开启弱密码套件、RC5、MD2、NPN 等旧特性扩大被测面--debug调试构建保留符号信息便于崩溃定位clang 在 Ubuntu 上复用 gcc 的 libstdc因此必须保证 clang 所选用的 gcc 版本配套安装了 C 标准库。可通过clang --verbose确认所选 gcc 版本例如 clang 14 选择了 gcc 12Ubuntu clang version 14.0.0-1ubuntu1.1 Target: x86_64-pc-linux-gnu ... Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/12 Selected GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/12随后确认对应版本的 libstdc 文件齐全$ ls /usr/lib/gcc/x86_64-linux-gnu/12 | grep stdc libstdc.a libstdcfs.a libstdc.so若缺失需安装对应的 dev 包Ubuntu 上 gcc-12 对应libstdc-12-devsudo apt-get install libstdc-12-dev编译与运行sudo apt-get install make make clean LDCMDclang make -j4注意此处使用LDCMDclang指定链接器为 clang因为最终链接 libFuzzer 需要 C 运行库。运行模糊测试fuzz/helper.py $FUZZER其中$FUZZER是 fuzz/ 下的目标名如asn1、client、server、cms程序将持续运行直到手动停止。从 helper.py 的实现可以看到它会自动完成三件事在fuzz/corpora/下创建fuzzer语料目录与fuzzer-crash崩溃目录若不存在若存在fuzzer-seed种子目录则一并加入输入集以-artifact_prefixfuzzer-crash/启动 fuzz 目标使触发崩溃的输入文件自动落盘到崩溃目录。若发生崩溃可在以下目录找到对应的输入文件fuzz/corpora/$FUZZER-crash/方案二使用源码编译或预构建的 clang若需使用 LLVM 官方发布站下载的预构建二进制或自行从源码编译 clang则除--with-fuzzer-lib外还需通过--with-fuzzer-include指定预构建 fuzzer 库所在的父目录即头文件FuzzerDefs.h等所在位置以$PATH_TO_LIBFUZZER_DIR表示CCclang ./config enable-fuzz-libfuzzer \ --with-fuzzer-include$PATH_TO_LIBFUZZER_DIR \ --with-fuzzer-lib$PATH_TO_LIBFUZZER \ -DPEDANTIC enable-asan enable-ubsan no-shared \ -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \ -fsanitizefuzzer-no-link \ enable-ec_nistp_64_gcc_128 -fno-sanitizealignment \ enable-weak-ssl-ciphers enable-rc5 enable-md2 \ enable-nextprotoneg --debug其余编译与运行步骤与方案一完全一致。使用 AFL 进行模糊测试AFLAmerican Fuzzy Lop是 LibFuzzer 之外的另一条成熟路径。OpenSSL 通过enable-fuzz-afl配置项接入与 LibFuzzer 二选一。配置sudo apt-get install afl-clang CCafl-clang-fast ./config enable-fuzz-afl no-shared no-module \ -DPEDANTIC enable-tls1_3 enable-weak-ssl-ciphers enable-rc5 \ enable-md2 enable-nextprotoneg enable-ec_nistp_64_gcc_128 \ -fno-sanitizealignment --debug make clean makeAFL 模式下不再需要--with-fuzzer-lib因为 AFL 的插桩和驱动由afl-clang-fast编译器与运行时注入。可选的 sanitizer 选项还包括enable-asan、enable-ubsan、enable-msan内存消毒器可根据需要追加。从 driver.c 的 AFL 分支可以看到 AFL 模式的驱动形态fuzz 目标是一个从标准输入循环读取数据、并用__AFL_LOOP(10000)包裹的独立main()——这正是 AFL 持久化模式persistent mode的典型写法__AFL_LOOP使同一进程复用避免反复 fork 带来的开销。运行某个 fuzzerafl-fuzz -i fuzz/corpora/$FUZZER -o fuzz/corpora/$FUZZER/out fuzz/$FUZZER其中-i指定输入种子目录-o指定 AFL 输出目录其中会包含crashes/子目录存放崩溃样本。复现崩溃与回归测试fuzzer 产出的输入往往无法直观判断问题归属OpenSSL 提供了*-test回放二进制用于离线复现。这些二进制不需要以模糊测试模式构建——无需设置CC也无需enable-fuzz-*或-fsanitize-coverage选项但建议开启enable-asan或enable-ubsan以在问题发生时给出明确报错。对于 client 与 server 这两个 fuzzer复现时可能需要-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION宏以复现生成随机数的路径。单个文件复现fuzz/$FUZZER-test $file例如对 asn1 目标执行其全部语料fuzz/asn1-test fuzz/corpora/asn1这与make test体系是等价的test/recipes/99-test_fuzz_asn1.t 展示了测试注册方式每个 fuzzer 对应一个99-test_fuzz_name.t测试脚本内部调用fuzz_ok($fuzzer)驱动*-test二进制跑完整个语料目录。因此也可以用 test 目标直接运行make test TESTSfuzz_test_asn1运行多个 fuzz 测试make test TESTStest_fuzz_cmp test_fuzz_cms运行全部 fuzz 测试make test TESTStest_fuzz_*从 test-corpus.c 的实现可以看到回放逻辑它递归遍历命令行传入的每个目录或直接读取文件对每个文件调用FuzzerTestOneInput并支持通过 test/helpers 中的 mfail 机制做内存分配失败注入run_mfail分支从而在复现崩溃的同时还能探索内存分配失败路径下的健壮性。随机数打桩保证覆盖可复现TLS 客户端与服务端在握手阶段通常需要生成随机数这会导致两个问题同一份语料在不同运行中的覆盖率随随机数漂移整个测试套件的覆盖率在每次提交后都会波动即使没有任何代码变更难以做覆盖率的增量对比。为了最大化语料覆盖的稳定性client 与 server fuzzer 改用可预测数字替代真随机数这一行为由FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION宏控制。实现层面fuzz_rand.c 的FuzzerSetRand()通过 OpenSSL provider 机制实现确定性 DRBG注册一个名为fuzz-rand的内置 provider通过RAND_set_DRBG_type(NULL, fuzz, NULL, NULL, NULL)将全局 DRBG 类型切换为fuzzfuzz_rand_generate的随机输出是一个从 1 开始递增的字节序列见 fuzz_rand.c因此对同一输入握手过程中生成的随机数完全一致。值得注意的是这种确定性不以跳过哈希校验为代价——语料中的样本内嵌了与这些固定随机数匹配的正确哈希值。例如 client fuzzer 总是以相同的随机数生成相同的 ClientHelloserver 端由语料文件模拟据此生成的响应也可以预先计算并固化。其结果是覆盖仅取决于输入内容与运行时机无关。此外client 与 server fuzzer 还通过time()打桩固定时间两个文件中均有#define FUZZTIME 1485898104并重定义了time()见 client.c使会话时间戳等依赖当前时间的路径也保持确定。该打桩在 Windows 上因链接器行为不可靠而不生效会导致覆盖有轻微差异属于已知限制。语料库维护更新、合并与最小化覆盖变化与语料更新语料库的覆盖与 client/server 的默认行为绑定一旦默认发送的内容发生变化例如新增扩展、调整优先级语料就需要同步更新否则新代码路径无法被触达。这解释了为什么 OpenSSL 的 client/server 语料需要跨多种配置联合生成。client 与 server 的语料由多种配置联合生成上文文档中记载的配置选项不带enable-ec_nistp_64_gcc_128且不带--debug的配置no-asm配置禁用汇编实现覆盖 C 语言实现路径32 位构建默认配置加上生成 fuzzer 所需的最小选项集。各配置跑出的覆盖通过libfuzzer 的 merge 选项合并到同一最小集合中从而在不丢失覆盖率的前提下压缩语料体积。最小化语料当从多次 fuzz 运行收集了冗余语料或希望精简某个语料子目录fuzz/corpora/DIR时可以利用 LibFuzzer 内置的 merge 模式做最小化mkdir fuzz/corpora/NEWDIR fuzz/$FUZZER -merge1 fuzz/corpora/NEWDIR fuzz/corpora/DIR该命令将DIR中的样本逐一回放仅把能带来新增覆盖的样本写入NEWDIR丢弃冗余输入得到覆盖等价的最小语料集。面向仓库贡献者的实践要点将以上知识整合进日常开发验证流程可以总结为四条可执行建议构建层面把 LibFuzzer 与 AFL 配置命令保存为脚本make clean后一键重建client/server 必须带-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION其余目标可选。验证层面任何涉及输入解析ASN.1、X.509、CMS、PEM、TLS 消息的改动提交前至少跑一次make test TESTStest_fuzz_*把 fuzz 目标纳入常规回归。复现层面CI 或本地收到崩溃样本后用fuzz/$FUZZER-test $file配 ASan/UBSan快速定位崩溃点与首次出错位置。覆盖层面若修改了 client/server 的默认握手内容需按上文多配置流程重新生成语料并最小化同时把新增的有效样本提交进fuzz/corpora/对应目录作为后续回归的种子。总结OpenSSL 的 fuzz 框架在 fuzz/ 目录下实现了统一接口FuzzerTestOneInput 双引擎LibFuzzer/AFL 确定性随机数 语料回放与最小化的完整闭环enable-fuzz-libfuzzer/enable-fuzz-afl控制构建形态helper.py管理运行目录*-test二进制与make test TESTStest_fuzz_*打通离线复现与回归测试FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION与fuzz_rand.c中的确定性 DRBG 保证覆盖可复现merge 模式维持语料精简。这套体系既是 OpenSSL 自身安全质量保障的关键一环也为研究 TLS/密码学解析器安全性的开发者提供了一个可直接复用的模板。【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表