OpenSSL 1.1 Fuzzing 实战指南用 LibFuzzer 与 AFL 对 SRS 内置 OpenSSL 进行健壮性测试【免费下载链接】srsSRS is a simple, high-performance, AI-driven real-time media server supporting RTMP, WebRTC, HLS, HTTP-FLV, HTTP-TS, SRT, MPEG-DASH, and GB28181, with codec support for H.264, H.265, AV1, VP9, AAC, Opus, and G.711.项目地址: https://gitcode.com/GitHub_Trending/sr/srsSRS 媒体服务器在其 trunk/3rdparty/openssl-1.1-fit 目录中内置了裁剪过的 OpenSSL 1.1 源码用于支撑 HTTPS、WebRTC/DTLS 等加密链路。本文以该内置 OpenSSL 源码树中的 fuzz/README.md 为核心完整讲解如何用 LLVM LibFuzzer 与 AFL 对 OpenSSL 的 TLS 握手、ASN.1 解析、大整数运算等高风险入口做模糊测试读完你将掌握从编译环境搭建、Fuzzer 构建、语料corpus管理到崩溃复现的完整工作流。为什么需要 Fuzz OpenSSL密码学库是安全攻防的第一线TLS 握手解析、ASN.1/DER 编码、X.509 证书解析、BIGNUM 大整数运算都是历史上漏洞高发的区域。OpenSSL 官方正是通过在源码树内提供fuzz/目录把模糊测试作为日常开发的一部分。在本仓库的 SRS 场景下OpenSSL 被用于 RTMP over HTTPS、WebRTC DTLS 等实时媒体链路一旦解析代码存在内存破坏类缺陷后果会直接波及媒体服务的可用性与安全性。OpenSSL 1.1 的 fuzz 体系同时支持两条主流工具链工具链特点触发配置开关LibFuzzerclang 自带进程内覆盖率引导速度极快enable-fuzz-libfuzzerAFLafl-clang-fast独立进程 持久模式生态成熟enable-fuzz-afl两种方式共享同一套fuzz/*.c模糊测试目标与语料目录只是驱动与编译插桩不同。下面从零开始依次演示。准备 LibFuzzer 编译环境LibFuzzer 依赖较新的 clang。官方文档给出的路径是从零搭建安装 git 并克隆 Chromium 维护的 clang 构建脚本执行更新脚本拉取预编译的 clang$ sudo apt-get install git $ mkdir git-work $ git clone https://chromium.googlesource.com/chromium/src/tools/clang $ clang/scripts/update.py提示可以定期git pull并重跑update.py以保持 clang 版本较新老版本可能无法正常工作。把编译好的 clang 加入PATH$ PATH~/third_party/llvm-build/ReleaseAsserts/bin/:$PATH获取并编译 LibFuzzer 运行时库以 svn 方式拉取 compiler-rt 中的 fuzzer 源码然后手工编译成静态库$ cd $ sudo apt-get install subversion $ mkdir svn-work $ cd svn-work $ svn co https://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/fuzzer Fuzzer $ cd Fuzzer $ clang -c -g -O2 -stdc11 *.cpp $ ar r libFuzzer.a *.o $ ranlib libFuzzer.a这一步得到的libFuzzer.a将在下一步配置 OpenSSL 时通过--with-fuzzer-lib传入。配置并用 LibFuzzer 构建 OpenSSL进入内置的 OpenSSL 源码树用 clang 作为编译器开启 fuzz 模式进行配置$ CCclang ./config enable-fuzz-libfuzzer \ --with-fuzzer-include../../svn-work/Fuzzer \ --with-fuzzer-lib../../svn-work/Fuzzer/libFuzzer.a \ -DPEDANTIC enable-asan enable-ubsan no-shared \ -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \ -fsanitize-coveragetrace-pc-guard,indirect-calls,trace-cmp \ enable-ec_nistp_64_gcc_128 -fno-sanitizealignment enable-tls1_3 \ enable-weak-ssl-ciphers enable-rc5 enable-md2 \ enable-ssl3 enable-ssl3-method enable-nextprotoneg \ --debug逐项拆解这一长串参数的作用参数作用enable-fuzz-libfuzzer启用 LibFuzzer 驱动对应 Configure 中的 fuzz 配置逻辑--with-fuzzer-include指向上面编译的 Fuzzer 头文件目录--with-fuzzer-lib指向libFuzzer.a静态库enable-asan enable-ubsan开启 AddressSanitizer 与 UndefinedBehaviorSanitizer用于捕获内存越界、未定义行为no-shared静态编译避免动态库符号与插桩冲突-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION关键宏让 client/server fuzzer 使用可预测的随机数保证语料覆盖稳定详见后文随机数与可复现性-fsanitize-coveragetrace-pc-guard,indirect-calls,trace-cmp覆盖率插桩类型LibFuzzer 据此引导变异enable-tls1_3 enable-weak-ssl-ciphers enable-rc5 enable-md2 enable-ssl3 enable-ssl3-method enable-nextprotoneg打开更多协议与弱密码族扩大可 fuzz 的代码面--debug开启调试符号与断言接着编译并指定链接器为clang因为 LibFuzzer 运行时是 C$ sudo apt-get install make $ LDCMDclang make -j启动 Fuzzer 并处理崩溃编译完成后fuzz/目录下会生成若干可执行文件如asn1、x509、client、server、bignum、bndiv、cms、conf、crl、ct、asn1parse等每个对应一个模糊测试目标。官方推荐通过辅助脚本启动$ fuzz/helper.py $FUZZER其中$FUZZER是fuzz/下的某个可执行文件名。以仓库中提供的 fuzz/helper.py 源码来看它会自动完成三件事在fuzz/corpora/$FUZZER/下创建或复用语料目录创建fuzz/corpora/$FUZZER-crash/崩溃目录若存在fuzz/corpora/$FUZZER-seed/种子目录则一并加入输入集合最后以-artifact_prefix$FUZZER-crash/参数启动 fuzzer。因此一旦发生崩溃对应的输入文件会自动落入fuzz/corpora/$FUZZER-crash/这个目录中的每个文件就是一条可复现崩溃的极小输入可以直接用于后续回归测试与缺陷定位。使用 AFL 进行模糊测试如果你偏好 AFL 工作流配置同样简洁$ sudo apt-get install afl-clang $ CCafl-clang-fast ./config enable-fuzz-afl no-shared -DPEDANTIC \ enable-tls1_3 enable-weak-ssl-ciphers enable-rc5 enable-md2 \ enable-ssl3 enable-ssl3-method enable-nextprotoneg \ enable-ec_nistp_64_gcc_128 -fno-sanitizealignment \ --debug $ make这里不再需要-fsanitize-coverage插桩由afl-clang-fast完成enable-fuzz-afl会让 fuzz/driver.c 编译为 AFL 专用入口主循环调用__AFL_LOOP(10000)走持久化模式从 stdin 读取不超过 64KBBUF_SIZE 65536的输入并喂给FuzzerTestOneInput从而避免反复 fork 的开销。此外文档还提示以下 sanitizer 可按需叠加enable-asan, enable-ubsan, enable-msan运行某个 fuzzer 的命令为$ afl-fuzz -i fuzz/corpora/$FUZZER -o fuzz/corpora/$FUZZER/out fuzz/$FUZZER-i指定输入语料目录-o指定输出目录AFL 会把发现的新路径、崩溃、挂起分别放入out/queue、out/crashes、out/hangs。复现 fuzzer 发现的缺陷当 fuzzer 产出崩溃文件后需要用*-test二进制做最小化复现与调试。每个 fuzzer 目标都配套生成一个$FUZZER-test可执行程序其主体编译自 fuzz/test-corpus.c它把命令行给出的每个文件也支持目录递归逐条读入并调用FuzzerTestOneInput任何崩溃都会如实暴露。复现命令$ fuzz/$FUZZER-test $file需要注意的要点*-test二进制不需要用 fuzz 配置编译不必设置CC、不必传enable-fuzz-*或-fsanitize-coverage但部分选项仍然建议保留例如enable-asan/enable-ubsan能精准指示崩溃发生的时机与位置对client与server这两个 TLS fuzzer复现时通常仍需要-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION以复现当初生成该输入时的随机数序列。随机数与可复现性FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION这是 OpenSSL fuzz 体系中最精妙的设计之一。TLS 握手过程中 client 和 server 会生成随机数参与密钥协商这会导致一个副作用语料覆盖随随机数变化而漂移即使代码一行未改每次运行 fuzzer 的覆盖曲线也在变甚至整个测试套件的覆盖率统计都会随 commit 抖动。为了让覆盖结果可复现、可比较client与serverfuzzer 在定义了FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION时改用可预测的伪随机数。其实现就在 fuzz/rand.incstatic int fuzz_bytes(unsigned char *buf, int num) { unsigned char val 1; while (--num 0) *buf val; return 1; }它通过FuzzerSetRand()用RAND_set_rand_method()注册一个固定填充序列的RAND_METHOD从而保证client fuzzer 每次生成完全相同的 ClientHello对应的 server 响应也可以离线预生成并固化进语料。同时文档强调这个宏不会绕过任何哈希校验——语料中存放的是与固定随机数配套的正确哈希值因此测试依然具备真实性。另一个可复现性细节同样藏在源码里client.c与server.c都重定义了time()函数返回固定值FUZZTIME1485898104避免证书有效期、时间戳校验影响握手路径的可复现性Windows 平台因链接机制差异不保证生效仅会导致覆盖略有不同。覆盖率漂移与语料维护正因为语料覆盖依赖 client/server 的默认行为默认发送的 ClientHello 内容、默认的密码套件顺序等一旦上游修改了默认行为语料覆盖就会变化此时必须同步更新语料。这是维护者需要持续跟进的工作。官方在更新语料时会针对多种配置分别生成 client/server 语料再用 libfuzzer 的 merge 能力将各配置的增量覆盖合并到最小集合上文文档记录的默认配置组合去掉enable-ec_nistp_64_gcc_128且不带--debug的配置no-asm配置关闭汇编实现覆盖 C 代码路径32 位构建默认配置外加生成 fuzzer 所需的必要选项。这种多配置生成 merge 归并的语料管理方式可以理解为对同一组模糊目标做交叉验证显著提升覆盖的完备性。仓库中的各 Fuzzer 目标源码解析fuzz/目录下的每个.c文件都实现统一的FuzzerInitialize/FuzzerTestOneInput/FuzzerCleanup接口声明见 fuzz/fuzzer.h按测试面可归为三类TLS 握手类client 与 serverfuzz/client.c创建SSLv23_method()上下文设置ALL:eNULL:SECLEVEL0密码套件、SNIlocalhost把模糊输入直接写入内存 BIO 作为服务端发来的握手数据驱动SSL_do_handshake握手成功后继续SSL_read消费应用数据直到 EOF 或出错。fuzz/server.c更复杂一些。它内嵌了内置的 RSA 证书/私钥、ECDSA 与 DSA 的 PEM 证书/私钥以 DER/PEM 字节数组形式硬编码在源码中加载进SSL_CTX后把模糊输入作为客户端握手指向的数据。输入的最后一个字节被当作选项位opt 0x01时先尝试SSL_read_early_data处理 TLS 1.3 early data再走完整握手。这使单个 fuzzer 能同时覆盖普通握手与 0-RTT 两条路径。解析类asn1、x509、cms、crl、conf、ct、asn1parsefuzz/asn1.c维护一张巨大的ASN1_ITEM表涵盖X509、PKCS7/PKCS12、OCSP全系、RSA/DSA/EC参数、INT64/UINT64等数百种结构对同一份输入逐一遍历依次执行ASN1_item_d2i→ASN1_item_print→ASN1_item_i2d→ASN1_item_free的完整生命周期并额外用d2i_*/i2d_*直接测试 DH、DSA、RSA、EC、SSL_SESSION 等类型的编解码。fuzz/x509.cd2i_X509解析后调用X509_print会连带加载并打印公钥与扩展、X509_issuer_and_serial_hash、再i2d_X509回写覆盖证书解析、打印、哈希与重编码路径。其余如cms.c、crl.c、conf.c、ct.c、asn1parse.c分别针对 CMS 消息、CRL 吊销列表、OpenSSL 配置文件、证书透明度、ASN.1 通用解析器等入口机制一致。大整数运算类bignum 与 bndivfuzz/bignum.c用输入前两个字节切分三段长度、第三个字节的低位决定符号构造出三个大整数b1、b2、b3然后对比BN_mod_exp优化实现与BN_mod_exp_simple朴素实现的结果是否一致——这是一种差分测试两个实现结果不一致即视为失败用于捕获模幂运算的隐藏缺陷。fuzz/bndiv.c对输入切分为被除数与除数验证除法恒等式b3*b2 b4 b1以及商/余数的符号规则sign(d) sign(a)0 r b并限制输入上限 256KB 以避免超时。当被除数为 0 时校验商与余数均为 0。三种驱动的协作关系小结最后用一张逻辑图梳理 OpenSSL fuzz 体系的运行架构不需要截图理解即可模糊输入 ──▶ FuzzerTestOneInput(buf, len) │ ├─ LibFuzzer 模式driver.cLLVMFuzzerTestOneInput 包装覆盖率引导 进程内变异 ├─ AFL 模式driver.c__AFL_LOOP 持久循环 stdin 喂入 └─ test 模式test-corpus.c逐文件回放用于复现 fuzz/corpora/$FUZZER-crash/同一份模糊目标代码fuzz/*.c通过 fuzz/driver.c 中的条件编译适配三种运行方式定义OPENSSL_NO_FUZZ_LIBFUZZER与否分别走 LibFuzzer 入口或 AFL 入口语料与崩溃产物统一落在fuzz/corpora/下helper.py负责目录编排与参数注入确定性由FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION 固定随机数 固定时间共同保证覆盖数据才具有跨 commit 的可比性。对 SRS 这类把 OpenSSL 1.1 作为第三方依赖内置的媒体服务器项目而言理解这套 fuzz 工作流的价值在于你可以随时在内置的 fuzz/README.md 指导下针对自己实际启用的协议组合TLS、DTLS 相关配置重新生成语料、运行 fuzzer并将发现的崩溃回灌到*-test复现与修复从而在把 OpenSSL 引入生产媒体服务之前先把它摩擦得足够健壮。【免费下载链接】srsSRS is a simple, high-performance, AI-driven real-time media server supporting RTMP, WebRTC, HLS, HTTP-FLV, HTTP-TS, SRT, MPEG-DASH, and GB28181, with codec support for H.264, H.265, AV1, VP9, AAC, Opus, and G.711.项目地址: https://gitcode.com/GitHub_Trending/sr/srs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考