ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用高级模糊测试武装 SRTP 安全:SRS 仓库中 libsrtp-2-fit Fuzzer 的构建、原理与实战

用高级模糊测试武装 SRTP 安全:SRS 仓库中 libsrtp-2-fit Fuzzer 的构建、原理与实战 用高级模糊测试武装 SRTP 安全SRS 仓库中 libsrtp-2-fit Fuzzer 的构建、原理与实战【免费下载链接】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本文深入剖析 SRS 仓库内trunk/3rdparty/libsrtp-2-fit/fuzzer/下这套为 libSRTP 量身打造的高级模糊测试器它如何用可移植 PRNG 保证语料可复现、如何用自定义分配器模拟内存耗尽与零尺寸分配、如何利用 32 位地址空间的极端地址检测指针算术溢出以及如何借助 ASan/MSan 验证输出缓冲区合法性。读完本文你既能亲手编译运行srtp-fuzzer也能理解这些可复用的模糊测试技巧背后的工程考量。一、背景libSRTP 在 SRS 中的角色SRSSimple Realtime Server是一款高性能实时媒体服务器WebRTC 是其核心能力之一。WebRTC 的媒体面安全依赖 SRTPSecure Real-time Transport Protocol对 RTP/RTCP 报文进行加密与完整性保护而 SRS 使用的正是仓库内定制的 libsrtp 2.x 版本trunk/3rdparty/libsrtp-2-fit/。从 SRS WebRTC 数据面代码 可以看到SRS 在 DTLS 握手完成后通过srtp_init()、srtp_create()建立 SRTP 会话并在收发方向分别调用srtp_protect()/srtp_protect_rtcp()与srtp_unprotect()/srtp_unprotect_rtcp()完成加解密见 srs_app_rtc_dtls.cpp。库的健壮性直接关系到生产环境的媒体面安全因此 libsrtp-2-fit/fuzzer 中这套由 Guido Vranken 编写的高级模糊测试器正是保障该库以及上游 libSRTP安全性的重要工具。它不止是简单的往接口里灌随机字节而是实现了几项在常规 fuzzer 中难得一见的特殊技术原文作者也鼓励将这些思路移植到各自项目的 fuzzer 中。在 SRS 的构建体系中libsrtp-2-fit 通过 trunk/auto/depends.sh 自动复制、打补丁如3rdparty/patches/srtp/gcc10-01.patch并编译安装而 fuzzer 目录则作为库的安全测试资产随仓库一同分发。二、构建与运行 srtp-fuzzer原文档给出了在 libSRTP 仓库根目录下的完整构建命令需要在 configure 阶段同时启用编译插桩与地址/未定义行为消毒器并在链接阶段注入 libFuzzerCCclang CXXclang CXXFLAGS-fsanitizefuzzer-no-link,address,undefined -g -O3 CFLAGS-fsanitizefuzzer-no-link,address,undefined -g -O3 LDFLAGS-fsanitizefuzzer-no-link,address,undefined ./configure LIBFUZZER-fsanitizefuzzer make srtp-fuzzer逐项拆解这条命令的含义参数作用CCclang/CXXclanglibFuzzer 是 clang 工具链的一部分必须用 clang 编译-fsanitizefuzzer-no-link在编译所有目标文件时插入覆盖率与消毒器探针但暂不链接 fuzzer 运行时链接发生在最后一步-fsanitizeaddress,undefined启用 AddressSanitizer检测越界、UAF、非法指针与 UndefinedBehaviorSanitizer检测整数溢出、非法移位等未定义行为-g -O3保留调试信息的同时开启优化兼顾崩溃定位与模糊测试吞吐LIBFUZZER-fsanitizefuzzer仅在链接srtp-fuzzer这一步注入 libFuzzer 运行时含 main 入口从而只让最终二进制以 fuzz 模式运行对应地fuzzer/Makefile.in 定义了三个编译单元与最终链接规则mt19937.o由mt19937.cpp以-stdc11编译导出 C11 梅森旋转算法fuzzer.o核心驱动fuzzer.c依赖fuzzer.h与testmem.htestmem.otestmem.c必须以-O0关闭优化单独编译原因见下文输出内存测试一节链接命令通过$(LIBFUZZER)变量接入 libFuzzer 运行时并与-lsrtp2库静态链接成最终二进制srtp-fuzzer。编译产物srtp-fuzzer是标准的 libFuzzer 可执行文件支持 libFuzzer 的全部命令行参数种子语料目录、-runs、-max_len等可直接将其指向存放种子输入的目录开始模糊测试。三、核心特性之一可移植 PRNGmt19937模糊测试的输入输出需要可复现某个输入若能触发崩溃开发者应当能在任何机器上重放该输入得到一致的执行行为。这要求 fuzzer 内部的所有随机决策见后文的分配失败策略、mmap 地址选择等都来自一个跨平台行为一致的随机数源。原文档指出标准库rand()是善变的——它针对给定种子产生的随机序列可能随操作系统与 libc 实现的不同而不同这会让模糊语料失去可移植性。解决方案是直接复用 C11 标准库的std::mt19937梅森旋转伪随机数生成器它由 C 标准明确定义了算法与初始序列行为在平台上高度一致。采用它既避免了自行实现 PRNG 的额外工作也规避了从其他项目导入实现可能带来的许可证不兼容风险。从源码看mt19937.cpp 仅做了极简封装extern C void fuzz_mt19937_init(uint32_t seed) { mt_rand new std::mt19937(seed); } extern C uint32_t fuzz_mt19937_get(void) { return (*mt_rand)(); } extern C void fuzz_mt19937_destroy(void) { delete mt_rand; mt_rand NULL; }用extern C导出三个函数使 C 代码fuzzer.c可以调用 C 实现的 PRNG。值得注意的细节是PRNG 的种子本身是从 fuzzer 输入的前 4 个字节中提取的见 fuzzer.c 中的EXTRACT_IF(randseed, ...)与fuzz_mt19937_init(randseed)。这意味着输入即种子——同一个输入字节串在任意平台上都会展开为完全相同的随机决策序列从而保证分配器行为、mmap 地址选择等随机环节的可复现性。四、核心特性之二Size 0 分配检测C 标准规定malloc(0)可以返回NULL也可以返回一个唯一但不可解引用的指针。实践中的问题是有些实现如 glibc会返回一个小的、看似可用的内存区域导致开发者写出分配 0 字节后向该区域写入单个字节的未定义行为代码而毫无察觉而另一些实现如 OpenBSD在写入时会直接崩溃。这类缺陷极难在常规开发环境下暴露。fuzzer 的自定义分配器 fuzz_alloc() 采用了更激进的做法当请求大小为 0 时故意返回一个非法地址0x01 (fuzz_mt19937_get() % 1024)落在 11024 区间的无效页。任何对该指针的解引用或写入都会立刻触发 ASan 崩溃从而高效暴露不健全的零尺寸分配代码。配套的 fuzz_free() 通过fuzz_is_special_pointer()识别这一指针区间直接返回而不调用free()/munmap()避免了对非法指针的释放操作。五、核心特性之三随机分配失败真实生产环境可能面临内存短缺库必须在malloc/calloc返回NULL时仍能正确报告错误而非崩溃或泄漏。为测试这种韧性自定义分配器会周期性随机返回NULL。具体实现fuzzer.c中fuzz_calloc()在初始化阶段g_post_init true之后平均每 64 次分配就有 1 次返回NULL。这一频率由 PRNG 决定而 PRNG 种子又来自 fuzzer 输入因此分配失败的时机是确定性的——同一输入在任何机器上都会在完全相同的调用点遭遇分配失败bug 可以可靠复现。同时fuzzer 内部自身的分配如从输入中解析 policy 结构所需的临时内存走的是fuzz_alloc_succeed()fuzzer.c优先尝试可能失败的fuzz_alloc()若失败则回退到 libc 的malloc/calloc兜底。这样既能保持对被测库施加分配失败压力又不会因 fuzzer 自身内存不足而中断执行。六、核心特性之四检测不充分的指针算术32 位专属这是整套方案中最精妙的一项技术仅对 32 位构建生效。其思想是将某些分配放置在虚拟地址空间的两个极端——高地址0xFFFF0000或低地址0x00010000从而放大指针算术溢出的后果。考虑如下典型代码模式if ( start n end ) { memset(start, 0, n); }其中start/end界定了一块内存区域n是某个正整数。若n足够大start n会发生指针加法溢出。例如start分配在0xFFFF0000、end为0xFFFF1000、n为0xFFFFF时if ( 0xFFFF0000 0x000FFFFF 0xFFFF1000 ) { memset(0xFFFF0000, 0, 0x000FFFF); }加法0xFFFF0000 0x000FFFFF溢出回绕为0x000EFFFF于是条件0x000EFFFF 0xFFFF1000解析为真memset违背程序员本意被执行产生段错误。虽然这属于边界情形但无法排除其出现在生产环境的可能性更重要的是分析崩溃的分析师可以通过推算n的由来构造出令n极大的特制输入使该exploit即使在普通虚拟地址下也能成功。因此 fuzzer 一方面通过mmap()在0xFFFF0000放置分配以暴露加法溢出fuzzer.c另一方面在低地址0x00010000放置分配用于检测涉及减法的非法指针算术if ( end - n start ) {源码细节还显示选择是否用 mmap用高地址还是低地址这两个随机决策即使传入--no_mmap也会照常消耗 PRNG 输出见 fuzzer.c 注释这是为了让 PRNG 输出流在不同 fuzzer 配置间保持一致维持语料的可复现性。同时由于同一时刻只支持一个并发 mmap 分配fuzz_free()会通过g_mmap_allocation记录指针并在释放时调用munmap()失败则abort()。七、核心特性之五输出内存测试testmem.c模糊测试不仅要保证输入被正确解析还要保证被测函数的输出缓冲区是合法的内存区域。testmem.c 导出的fuzz_testmem()做的事非常简单把传入的缓冲区复制到一块新分配的堆内存然后立即释放。若启用AddressSanitizer复制过程中的读写、释放后的访问都会触发 ASan 检查从而验证输入缓冲区是否为合法内存区域若启用MemorySanitizer编译时定义FUZZ_MSANfuzz_testmem()会转调fuzz_testmem_msan()把数据fwrite到/dev/null。这是让 MSan 评估数据是否含未初始化字节的巧妙技巧——任何未初始化字节流向 I/O 都会触发 MSan 崩溃。关键工程细节从优化编译器视角看fuzz_testmem()是复制即释放的无意义操作很可能被优化掉因此该文件必须用-O0关闭优化编译对应 Makefile.in 中的$(COMPILE) -O0 testmem.c -c -o testmem.o。fuzz_testmem()在 fuzzer 中应用广泛每次run_srtp_func()执行完 protect/unprotect 后都会调用它验证输出fuzzer.c自定义事件处理器fuzz_srtp_event_handler()也会用它检查事件数据结构及其session指针fuzzer.c。八、模糊输入如何驱动整个 SRTP 状态机阅读 fuzzer.c 的LLVMFuzzerTestOneInput()可以看到fuzzer 输入不只是一段待加解密的报文而是一份完整的、可编程的测试脚本通过EXTRACT_IF/EXTRACT宏定义于 fuzzer.h逐段消费。每次调用的大致流程为提取 PRNG 种子取输入前 4 字节初始化mt19937与srand提取两份 policy 链第一份用于srtp_create()初始化上下文第二份预留给srtp_update()源码中该项仍为 TODO见 fuzzer.c创建 SRTP 上下文srtp_create(srtp_ctx, policy_chain)提取流移除与 ROC 操作extract_remove_stream_ssrc()与extract_set_roc()从输入中解析一组组srtp_remove_stream()、srtp_set_stream_roc()/srtp_get_stream_roc()的参数循环执行 SRTP 函数每次从输入提取一个函数选择protect/unprotect/protect_rtcp/unprotect_rtcp以及带 MKI 的变体函数表见 fuzzer.h与一段载荷先执行一次再把输出作为第二次操作的输入形成链式调用交错状态操作在循环间隙按输入指令移除流、设置/读取 ROC驱动库的会话状态机清理释放所有 policy、SSRC 列表、上下文。policy 的提取同样值得注意fuzzer.c输入中的srtp_crypto_policy_func会被模运算映射到 fuzzer.h 中的加密策略表AES-CM-128/192/256 与 HMAC-SHA1 系列组合、空密码/空认证等其中 192 位与 AES-GCM 系列仅在#ifdef OPENSSL下启用SSRC 类型被映射到ssrc_undefined/ssrc_specific/ssrc_any_inbound/ssrc_any_outbound保证任何随机字节都能生成合法参数组合从而最大化路径覆盖率。九、可用的 fuzzer 命令行开关从LLVMFuzzerInitialize()fuzzer.c的参数解析可以看到srtp-fuzzer支持如下自定义开关开关作用适用构建--no_align关闭 4 字节对齐强制默认对 SRTP 载荷强制 4 字节对齐见 fuzzer.c全部--no_mmap禁用极端地址 mmap 分配策略PRNG 输出流保持一致仅 32 位构建FUZZ_32BIT--no_custom_event_handler不安装自定义fuzz_srtp_event_handler使用库默认事件处理全部--write_input将当前输入原样写入input.bin便于复现崩溃用例fuzzer.c全部其中 32 位构建由 fuzzer.h 根据UINTPTR_MAX自动判定0xffffffff定义FUZZ_32BIT并包含sys/mman.h头文件。十、贡献指南保持语料可移植扩展 fuzzer 时原文档给出了一条明确约束尽可能使用跨系统宽度一致的整数类型例如使用uint64_t而非unsigned long。原因在于 fuzzer 输入与 PRNG 输出流构成了一份可移植语料——若某段解析逻辑依赖unsigned long这类在 32 位与 64 位平台上宽度不同的类型同一输入在不同平台上会产生不同行为破坏语料的可复现性也使崩溃用例失去跨平台重放能力。这一点与全文反复强调的确定性可复现原则一脉相承。结语这套 libsrtp fuzzer 的价值不仅在于其被测对象SRTP 协议栈本身的重要性——在 SRS 中它直接护卫着 WebRTC 的媒体面安全——更在于其方法论上的可迁移性用输入内嵌种子实现跨平台可复现的随机决策、用非法指针主动引爆零尺寸分配的未定义行为、用极端虚拟地址放大指针算术溢出、用-O0编译的无意义函数驱动 MSan 检查未初始化字节。这些技巧远超常规 fuzzer 的范畴正如原文档所言值得被移植到各类 C/C 项目的安全测试实践中。【免费下载链接】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),仅供参考
返回列表