尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

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

Vibe Coding实战:快速定位FFmpeg除零漏洞

Vibe Coding实战:快速定位FFmpeg除零漏洞 前几天我在本地一个临时沙箱里跑模糊测试目标不是新东西还是 FFmpeg。一条非常奇怪的小文件触发了崩溃日志停在Floating point exception。最初我以为是环境问题因为文件很小输入参数也不复杂。但当我把同一段输入反复跑了几次每次都稳定复现后我开始意识到这不是偶发而是一个真实的除零问题。更让我在意的不是漏洞本身而是我用的这个 fuzzer并不是花几天手工搭的而是基于 vibe coding 的方式用自然语言描述需求后快速迭代出来的。换句话说漏洞确实是 FFmpeg 的但发现漏洞的工具生产方式已经和过去很不一样了。这篇文章想记录的不只是一次除零漏洞的定位过程更是我对“用 vibe 写安全工具”这件事的重新理解。1. 一次“不意外”的崩溃反而让我认真想了一下工具链的关系1.1 崩溃日志长什么样崩溃发生的现场并不复杂。我写了一个很小的 Python 脚本不断生成变异后的媒体文件再让 FFmpeg 去解析返回码一旦异常就记录下来。某一次运行之后脚本抓到了这样的结果returncode: -8 signal: SIGFPE (floating point exception) cmd: ffmpeg -v error -i ./mutants/mut_0087.bin -f null -SIGFPE在 Linux 上通常意味着进程收到了浮点异常而最常见的触发原因之一就是整数除以零。FFmpeg 这种底层媒体处理库大量代码是 C/C一旦某个分母被外部输入控制成了 0崩溃就会在不知不觉间出现。我先把这条样本单独拿出来用完整信息跑了一次ffmpeg -v debug -i mut_0087.bin -f null -输出里能看到解析器在很早的阶段就停住了连正常的媒体信息都没有打印完整。更关键的是同样的命令换了三个不同的 FFmpeg 构建有两个都能稳定复现其中一个构建甚至直接在解封装阶段就崩了。看到这里我的第一反应已经不是“修一个 crash”而是“为什么我能在这么短的时间内用一段 AI 帮忙生成的脚本找到这种问题”。这个疑问比崩溃本身更值得写下来。1.2 这个发现为什么值得写出来FFmpeg 是历史非常悠久、使用面极广的多媒体处理库。它处理的是大量不可信的外部输入文件格式复杂内部解码路径又多所以一直是模糊测试的重点目标。到现在为止FFmpeg 的崩溃报告和补丁数量都相当可观发现一个新的除零漏洞从安全研究角度来说并不算特别罕见。但这次发现的过程有一个明显不同的地方工具链起点不是一段我手写的 C harness也不是一个现成框架而是一个“基于 vibe coding 的模糊测试工具”。我说的 vibe coding不是指某种自动化漏洞挖掘平台而是指一种编程方式我用自然语言描述我要做什么AI 生成初版代码我再通过运行结果和日志反推下一步怎么改。这种工作方式真正的价值不是让 AI 替我发现了漏洞而是让“发现漏洞”这件事从“需要完整工程能力”变成了“需要清晰判断力”。过去我如果要给 FFmpeg 写一个 fuzzer光是搭编译环境、写变异器、处理崩溃样本就要花掉大半天。现在AI 可以快速给我一个能跑的骨架我只需要判断它跑得对不对、怎么改更好。当然这套流程也有自己的坑。AI 生成的代码不一定安全不一定高效甚至不一定真的在做模糊测试。它可能只是把文件随机改了几个字节然后调用 FFmpeg根本没有考虑覆盖率。所以整个过程中人的角色不是变轻了而是换了从“写代码的人”变成“验收代码和修正方向的人”。2. “vibe coding”和模糊测试严格说补的是同一块短板2.1 模糊测试一直以来的真正门槛不是写 fuzzer很多人以为模糊测试最难的是写一个 fuzzer。其实网上有大量现成工具AFL、libFuzzer、honggfuzz 都是很好的起点。真正难的是确定你的测试对象边界、构造合适的输入种子、读懂崩溃堆栈、复现并最小化样本、判断漏洞根因、和上游沟通修复。这些环节里写变异循环只是最表层的工作。传统做法里一个新手要能完整跑通这套流程往往要先花很多时间解决编译选项、环境依赖和工具链匹配问题。等到把所有东西配好精力已经消耗了一半。我这次的做法很不一样。我没有从零写代码而是先用一句话描述需求让 AI 生成一个“用 Python 调用 ffmpeg 命令、对种子文件做变异、监控异常返回码”的脚本。第一版代码很快就出来了虽然不完美但至少能跑。之后我根据实际报错反复修改提示词让它加入超时控制、崩溃样本保存和返回值分类。这就是 vibe coding 对模糊测试的最大意义它把“从零到能跑”的时间压缩到了极致让人能把精力放在更重要的判断上。2.2 vibe coding 的本质是“把意图翻译成脚手架”我会这样理解 vibe coding它本质上是一个“意图翻译器”。你告诉它边界条件、输入输出、约束和偏好它把这段话翻译成代码。但你不会因为代码能跑就把它当成正确实现。比如我让 AI 写一个变异器它可能默认用“逐字节随机翻转”。这个策略对某些格式有效但对 FFmpeg 这种有复杂结构的目标来说纯随机变异很容易产生大量在早期就被拒绝的输入根本走不到深层解析逻辑。于是我就需要补充先保留文件头部结构只变异后面的媒体数据块或者先尝试最小样本再逐步恢复字段。这个补充过程本身就是一个人在做设计决策的过程。AI 只是帮我快速实现判断还是要我来下。所以vibe coding 补的不是“思考能力”而是“把思考快速变成可运行代码”的能力。2.3 两者结合后真正被改变的是迭代速度模糊测试本质上是一个迭代过程跑一批输入发现问题理解问题调整输入生成策略再跑。以前每轮迭代的瓶颈往往在代码修改上。现在我可以让 AI 基于上一次的日志快速改出一版新代码我再运行、记录、继续改。这种速度提升带来的实际效果很明显。我在半天内就从“什么都没有”走到了“一个持续监控 FFmpeg 命令行进程的变异器”并开始抓到了第一批异常样本。如果没有 vibe coding这个流程可能需要一天或更久前提还是我足够熟悉所有相关工具链。不过速度提升也有代价。代码生成得快问题出现得也快。AI 可能生成一个没有处理 timeout 的脚本导致某个 FFmpeg 进程卡住也可能在捕获 stderr 时忽略了大段错误信息。每一个问题都是一次新的判断是让 AI 修还是自己动手改。3. 从想法到可运行 fuzzer我们用的四步流程如果你也想用类似方式做一次针对 FFmpeg 或其它多媒体框架的模糊测试可以参考我这次走通的四步流程。3.1 先定目标为什么选 FFmpegFFmpeg 适合做模糊测试目标有几个原因输入来源不可信经常要解码各种来源的媒体文件。格式解析路径很复杂容器、编码、解码层层嵌套。处理的是二进制数据字段稍有不符就可能触发边界问题。社区对崩溃报告和补丁的响应流程相对成熟。选目标时不要贪多。我这次只聚焦在“命令行工具在解析输入文件时会不会崩溃”没有直接深入某个具体解码库。这样可以先用最外层命令来验证流程等发现问题再往下钻。3.2 生成初版语言、运行方式、监控策略我没有用 C 语言重写一个 libFuzzer harness而是选了 Python 包一层命令行调用。原因很简单C 语言环境配置复杂Python 可以更快地验证想法而且 FFmpeg 命令本身就是一个很清晰的观察窗口。第一版提示词大致长这样帮我写一个模糊测试脚本目标是 ffmpeg 命令行。 要求 1. 读取一个种子目录里的媒体文件。 2. 对每个种子做随机变异生成新文件。 3. 用 subprocess 调用 ffmpeg 解析这个文件参数是 -v error -i 输入文件 -f null -。 4. 捕获运行超时和返回码异常。 5. 遇到崩溃时把变异后的文件保存到 crash 目录。AI 生成的代码里有几个小问题比如没有做文件大小限制也没有处理 FFmpeg 在某些情况下会正常返回非零码比如格式不支持。我很快就发现如果不把这些误报过滤掉整个 crash 目录会被大量无意义样本淹没。于是我加了几个规则返回值是负数才认为可能是信号崩溃文件大小限制在 1MB 内单条命令执行超过 5 秒就杀掉。这个调整很有必要因为 FFmpeg 对某些畸形输入会触发慢路径而不是直接崩溃。3.3 用一条样本验证输入和输出边界在正式大规模变异前一定要先拿一条正常样本跑通整个流程。我准备了一个很小的音频文件用下面的命令测试ffmpeg -v error -i normal_sample.mp3 -f null -正常情况下这条命令应该没有任何输出返回码为 0。然后我再拿变异后的文件做一次相同调用确认脚本能把崩溃时间、返回码和测试文件都记录下来。这一步看起来简单但非常关键。如果连“正常样本”都不能稳定通过后面所有崩溃结果都无法判断是真实漏洞还是脚本问题。尤其是当你让 AI 生成脚本时更要在这一步确认它是否真的把错误码判断正确是否把文件写入路径搞错了是否把崩溃样本命名为同一个名字导致覆盖3.4 循环变异从单条测试到持续任务验证完基本流程后我开始扩大变异策略。最简单的是随机覆盖几个字节但纯随机覆盖对 FFmpeg 这种解析器来说效率不高。我又让 AI 生成了“保持文件头部不变只变异尾部数据”的模式同时加入了一些位翻转和整段删除的操作。实际跑起来的循环大概是这样import subprocess import os import random SEED_DIR ./seeds OUT_DIR ./mutants CRASH_DIR ./crashes def mutate(seed_bytes): data bytearray(seed_bytes) mode random.choice([flip, bitflip, trim]) if mode flip: for _ in range(random.randint(1, 8)): pos random.randrange(len(data)) data[pos] random.randrange(256) elif mode bitflip: for _ in range(random.randint(1, 4)): pos random.randrange(len(data)) data[pos] ^ 1 random.randrange(8) elif mode trim: if len(data) 64: data data[:random.randrange(32, len(data))] return bytes(data) def run_one(mutated, index): path os.path.join(OUT_DIR, fmut_{index}) with open(path, wb) as f: f.write(mutated) try: result subprocess.run( [ffmpeg, -v, error, -i, path, -f, null, -], capture_outputTrue, timeout5 ) if result.returncode 0: crash_name os.path.join(CRASH_DIR, fcrash_{index}_ret{abs(result.returncode)}) with open(crash_name, wb) as f: f.write(mutated) return result.returncode, result.stderr except subprocess.TimeoutExpired: pass return None, None这个示例结构只是为了说明流程不是完整生产脚本。真实使用中还需要处理并发、日志轮转、磁盘空间、样本去重等问题。当我看到第一个returncode -8时第一反应不是庆祝而是先怀疑会不会是文件太小导致某个读取越界会不会是我的变异策略引入了某种非文件条件这些疑问都需要在下一环节里逐步排除。4. 除以零漏洞的定位过程从浮点异常到根因发现崩溃只是第一步真正有价值的是搞清楚它为什么崩。4.1 第一现场Floating point exception在 Linux 上SIGFPE的常见原因是整数除以零。C/C 里整数除以零是未定义行为运行时通常会触发这个信号。FFmpeg 内部有大量数学计算尤其是在音频重采样、时间戳换算、帧率计算等地方如果某个分母来自文件头部的字段而该字段未做校验就可能在特定输入下变成 0。我第一次看到这个信号时马上想的不是“哦有除以零漏洞”而是“这个崩溃是否稳定”。因为模糊测试里偶尔会有一些和环境相关的信号比如内存不足、栈溢出甚至编译器的 UB 优化行为。只有稳定复现才值得进一步分析。4.2 复现三步固定输入、固定构建、固定参数我整理了一个简单的排查顺序固定输入把触发崩溃的mut_0087.bin单独保存不经过变异直接用原始字节反复测试。固定构建确认当前用的 FFmpeg 版本和编译选项。如果是自己编译的要保留编译配置。固定参数确认不是命令行参数差异导致。比如-v error和-v debug虽然不会改变解析逻辑但会影响输出可能会影响某些缓冲区的初始化。在这个案例里我用原始文件跑了 20 次每次都在同一位置崩溃稳定复现。然后我把 FFmpeg 编译配置换成最常用的 release 配置也能复现。这一步基本可以排除“随机变异导致的偶然内存状态”这一可能。然后我继续做小样本化。用二进制编辑工具把文件不断截短看能不能在更小的文件上触发。最后我发现一个只有几十字节的文件也能触发只是此时很多头字段已经残缺了。这个最小化过程很有用它说明崩溃点不依赖庞大的媒体数据而更可能发生在文件头字段的解析和计算阶段。4.3 根因分母从哪来为什么是零通过 gdb 或核心转储可以拿到崩溃现场的调用栈。这里的细节我不想编造具体函数名因为不同版本代码路径不同。但在通用分析思路上这类除零通常有几种来源文件头里的采样率、帧率、通道数等字段为 0。某个时长字段在解析时先被当作分母使用。不同解码器之间对同一字段的解释不一致。某些条件分支只检查了分子没检查分母。我当时的判断是崩溃位置离具体解码器还有一段距离更像是在某个通用结构解析阶段。那个位置的代码默认“正常文件都会包含有效的采样率”但畸形文件可以把它设置为 0于是除法直接崩了。要确认这一点不能只看崩溃栈。我还需要跟踪输入文件里哪个字段起了作用。常见的做法是用xxd查看文件头部的字节再对照格式文档找出对应字段。这一步通常需要手动分析AI 可以帮你整理思路但很难替你完成因为格式细节需要验证。4.4 确认“值得报告”的判断标准发现一个崩溃后我会问自己几个问题判断项判断标准本次结果是否稳定复现相同输入至少 3 次崩溃20 次全部稳定是否只影响特定构建换编译配置是否仍存在两个构建均可复现是否属于预期行为畸形输入是否应被拒绝而非崩溃解析阶段崩溃不是用户主动需求是否有远程触发可能输入文件是否可被对方控制媒体文件本身就是不可信输入是否已有已知补丁能否在最新版本复现需要去上游版本验证根据这几个标准我认为这更像是一个真实的健壮性缺陷而不是环境问题。不过我也清楚单独一个除零漏洞的危害面通常有限它在解码不可信媒体时可能导致程序崩溃造成拒绝服务但一般不会直接变成远程代码执行。所以处理优先级可以按实际场景来定。5. 漏洞后面真正有价值的是“可复现、可追踪、可修复”发现一个除零漏洞本身不难难的是把它变成一个能被上游理解和修复的问题。5.1 补丁验证和回归用例如果上游能提供一个修复版本或者我们本地自己能打补丁验证过程就非常关键。我一般会这样做在修复前版本上跑一次崩溃样本确认必现。切换到修复版本再次跑同一个样本确认不再崩溃。再跑一遍正常样本集确保没有引入新的行为变化。把崩溃样本保存为回归用例方便以后在 CI 或本地持续测试。这个流程不复杂但很多人会跳过第 3 步。其实第 3 步很重要因为如果修复只是简单加一个“如果值为 0 就返回”可能不会影响常规路径但如果修复是在更深层改变了计算流程就可能影响很多正常解码路径。一定要做回归验证。5.2 量化影响面不是所有除以零都同样危险对普通开发者和安全工程师来说影响面分析要更细一些。我会从四个角度评估触发前提是不是所有用户都可能遇到还是只有解析特定容器格式时会触发使用场景是 CLI 批量转换场景还是服务端自动转码场景可用性影响崩溃是否容易导致服务中断是否需要重启附加条件是否需要特定的编译选项、平台或解码器在这次案例里触发条件是“让 FFmpeg 解析一个畸形文件”这在很多视频处理服务里是可能发生的。但危害主要还是可用性不是机密性。真要部署安全基线还需要把日志、退出码和异常监控结合起来而不能只盯着漏洞本身。5.3 从一次性发现到可持续的安全基线一次模糊测试发现漏洞不能说明这个项目是安全的。真正有价值的是把这段经历沉淀成一个可重复的检查流程。我后来把崩溃样本、复现命令、分析记录都整理成了一个目录里面包含cves/divide-by-zero-ffmpeg/ ├── poc.bin ├── run.sh ├── README.md └── analysis_notes.mdrun.sh的命令大概是#!/bin/bash ffmpeg -v error -i poc.bin -f null -这个脚本看起来很简单但它能把“一次分析”变成“一条回归命令”。下次版本更新后只要跑一遍这个脚本就能知道问题有没有被修好或者是否又在新版本里出现。这个习惯比发现一个漏洞更重要。6. 如果让我重新做一次我会更早做这几件事回头看这次用 vibe coding 做模糊测试的体验总体很顺但也有一些可以优化的地方。如果再来一次我会把下面几件事提前。6.1 给 vibe 写代码加上“验收清单”AI 生成的代码不能拿来就用。我会在跑测试前先给代码做一次人工检查重点看有没有设置超时控制。有没有对临时文件做清理。有没有保存崩溃样本。有没有错误处理子进程调用。有没有限制变异后的文件大小。这次第一版脚本就没做文件大小限制导致一个变异文件膨胀到几百 MB把磁盘塞满。这个坑如果提前设好验收清单完全可以避免。6.2 先建立最小可复现环境再上变异我在最初跑的时候环境里同时存在多个 FFmpeg 构建导致一开始没意识到崩溃和构建版本有关。后来我把测试环境固定下来才确认它只在其中某一个版本上稳定复现。更合理的做法是一开始就建立一个最小环境只保留一个 FFmpeg 二进制固定编译参数然后把所有种子和脚本放在同一个目录里。这样能在后续分析中省掉大量“是不是环境不一致”的排障时间。6.3 长期使用前必须补的工程化组件如果只是临时跑一跑一个 Python 脚本就够了。但如果你想长期对 FFmpeg 或类似项目做持续模糊测试就需要补这几个组件日志记录保存每一次运行的输入文件、返回码、stderr、耗时。样本去重不要对同一个崩溃样本反复记录按哈希去重。资源限制限制子进程内存、CPU 时间、输出大小。并发调度可以同时跑多个 FFmpeg 进程但要避免相互干扰。回归机制每次出现崩溃样本后自动加入下一次测试集。这些组件不一定要自己写很多现成框架已经支持。但如果你是用 vibe coding 快速生成脚本那么你很可能要从零补齐。越早意识到这一点后期越省力。6.4 这种工作方式的适用边界vibe coding 适合生成模糊测试工具吗我的答案是适合但有边界。适合的地方在于它能快速把“模糊测试流程”变成可运行的代码。你可以让 AI 生成变异器、崩溃监控器、样本保存器甚至让它辅助分析崩溃栈。对于一次性的、探索性的、小规模验证来说效率非常高。不适合的地方也很明显它不能代替真正深入的漏洞分析。AI 可能知道很多常见模式但它不理解某个特定 FFmpeg 版本内部的设计约束。碰到复杂二次漏洞、内存损坏、需要跨函数跟踪的根因分析时最后还是得靠人来读代码、做实验、验证假设。所以我更愿意把这种组合理解成一个“放大器”它能放大你的迭代速度也能放大你的判断失误。如果你对模糊测试流程本身没有基本概念AI 生成出来的脚本可能只是在“看起来像模糊测试”实际上并没有真正覆盖到目标代码路径。6.5 给同样想尝试的人一个朴素建议如果你也想拿 FFmpeg 练手建议按这个顺序来先准备一个固定版本的 FFmpeg 二进制。准备 3 到 5 个正常的小样本文件。手动跑通ffmpeg -v error -i sample -f null -确认正常返回。让 AI 生成一个最简单的变异脚本不要一上来就加太多功能。加入超时、异常返回码和崩溃样本保存。跑一段时间再根据崩溃样本逐步增加变异策略。这样会比较容易获得正反馈也不会一上来就被工具链问题淹没。等你积累了一批样本再尝试用 libFuzzer 或 AFL 做更深层的覆盖率引导测试也不迟。说到底FFmpeg 里是否还有除零漏洞肯定有。任何一个存在了几十年的 C 项目都会有类似问题。但真正值得关注的是我们能不能用新的工具生产方式更快地发现它们、理解它们、修复它们。vibe coding 给不了全部答案但它确实让“从想法到第一个崩溃样本”这个过程变得比过去快了很多。
返回列表