后厨里有一位试菜师,他的工作不是按菜谱做菜,而是故意把菜谱改得乱七八糟:把“两克盐”改成“两千克盐”,把“牛肉”换成“牛油果”,甚至往甜品里塞一把辣椒。大多数时候,厨房只是端出几道怪菜,但偶尔,某个配方会让后厨彻底炸锅——锅具迸裂、厨师晕倒、餐厅系统崩溃。试菜师会小心保存那张“致命配方”,交给人去复盘。日复一日,他用成千上万种组合挑战厨房,把深藏的隐患一个个逼出来。在软件世界里,这样的“试菜师”叫模糊测试(Fuzzing):自动生成海量非预期输入,丢给程序,观察它在哪一刻“炸锅”,从而发现漏洞。这本手册将带你从零认识这位自动化试菜师——它如何变异配方、如何靠“菜单覆盖率”指路、如何管理菜谱本,以及如何把它用在AI红队测试中。

岗位关联

掌握模糊测试,等于给AI红队测试配上一把自动化“探雷器”:你能用系统化输入探测软件和模型的盲区,把安全评估从手工抽查升级为持续侦察,这是AI安全工程师与红队测试人员的核心技能之一。

目录
  1. 可视化演示
  2. Fuzzing基础
  3. 变异策略
  4. 覆盖率引导
  5. 语料管理
  6. 崩溃分析
  7. AI红队应用
  8. 工具与流程
  9. 效果评估
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态演示Fuzzing模糊测试的核心机制,通过源头/管道/节点/终端四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Fuzzing模糊测试:Fuzz Fuzzing模糊测试基础原理 变异策略与输入生成方法 核心机制可视化 Fuzzing模糊测试整体架构与关键组件

Fuzzing基础

餐厅老板想考察新厨师的本事,不会只准备一份标准菜谱。他会安排试菜师从后厨抽屉里抽出几百张原料卡,随机组合成稀奇古怪的配方,一道道递给厨师加工。大部分配方做出来难吃得要命,可只要有一道让厨师当场崩溃——炒锅飞了、灶台炸了——这张配方就立即被保存下来,成为排查事故的宝贵线索。试菜师的智慧,是用海量配方逼出事故。

软件测试里的模糊测试(Fuzzing)正是这个思路的自动化版本。它把“配方卡”换成程序输入(文件、网络请求、命令行参数),把“厨师”换成被测程序,把“崩溃”换成异常、断言失败或内存错误。与人工评审代码不同,它不依赖人去猜哪里可能出问题,而是让程序自己吃下成千上万种“不对劲的配方”,看哪一种会撂倒它。

Fuzzing的核心过程分三步:先准备一批种子输入,再施加随机的变异操作,接着把生成的新输入交给被测程序执行并监控异常。整个过程可以用一个集合描述:

回到餐厅,试菜师最大的本事,是把“随机掀锅盖”变成一套能反复执行的流程。Fuzzing也一样:它不依赖直觉,只依赖量的积累和系统化的输入生成,这正是它在安全战场上经久不衰的原因。

C={m(s)∣s∈S, m∈M}C = \{ m(s) \mid s \in S,\ m \in M \}
公式说明:所有测试输入 C,都是“某个种子 s + 某种变异 m”的产物。没有种子则无米下锅,没有变异则永远止步于种子。
  1. C(测试输入集合)C 代表最终生成的全部测试输入,是模糊测试交付给被测程序的总“配方集”。
    C={… }C = \{ \dots \}
  2. 种子 s 与语料库 SS 是程序初始提供的有效输入集合,是产生变异的源头素材。
    s∈Ss \in S
  3. 变异操作 mm 是对种子施加的修改动作,如翻转比特、插入字节、替换片段。
    m∈Mm \in M
  4. 集合构造大括号表示收集所有经过变异产生的输入,每一个都等待着被测程序执行。
    C={m(s)∣s∈S, m∈M}C = \{ m(s) \mid s \in S,\ m \in M \}

变异策略

试菜师改配方,总有几套固定招式:把“两克盐”改成“三克盐”,是数值抖动;把“牛肉”换成“牛油果”,是食材替换;从调料柜抓一把“辣椒粉”硬塞进甜品,是字典注入。招式虽朴素,组合起来却千变万化,足以覆盖一个巨大的“口味谱系”。

模糊测试中的变异策略,就是在输入字节层面复刻这些“小动作”。最常见的有三种:位翻转、拼接替换、字典注入。位翻转把比特 0 变 1、1 变 0,是最细粒度的扰动;拼接替换改变输入的长度和内容,能突破程序对输入格式的假设;字典注入则把已知的危险词(如 password、select、协议关键字)塞进输入,专门打击按关键词分支的逻辑。

以位翻转为例,变异结果可以写成:

正如试菜师不会只会加盐,好的 Fuzzer 也要组合多种变异。变异策略越丰富,“配方库”覆盖的“口味谱系”越宽,撞出事故的概率才越高。

x′=x⊕(1≪i)x' = x \oplus (1 \ll i)
公式说明:把输入 x 的第 i 个比特取反,其余比特保持不变。这个微小的变化足以让整数边界、字符串长度等底层假设失守。
  1. x'(变异结果)x' 表示变异后的新测试输入,将被交给被测程序执行。
    x′=x⊕…x' = x \oplus \dots
  2. x(原始种子)x 是当前被选中的种子输入,来自语料库,是这次变异的起点。
    x⊕…x \oplus \dots
  3. 按位异或 ⊕异或运算让目标位取反,其余位保持不变,操作简单而精准。
    ⊕(1≪i)\oplus (1 \ll i)
  4. 左移掩码1 左移 i 位,得到只影响第 i 个比特的掩码,实现精确打击。
    1≪i1 \ll i

覆盖率引导

聪明的试菜师会随身带一本菜单,每尝过一道菜就在相应位置打个勾。下次试菜时,他专挑那些还没打勾的虚线区域下手——没吃过的区域才最可能藏着问题。这本菜单,就是试菜师的方向盘。

软件模糊测试也有类似的“菜单”,称为覆盖率(coverage)。通过插桩技术在编译时给每条代码路径编号,程序每执行一段路径,就像在菜单上勾了一笔。Fuzzer 据此判断哪个输入探索了“新菜区”,把它保留下来作为下一步变异的种子,形成一个持续向未知区域推进的反馈回路。

覆盖率定义为已执行路径占总可达路径的比例:

有了覆盖率这本“菜单”,试菜师就不再盲目乱吃,而是让每一次下厨都离未探索区域更近一步——这是 Fuzzing 从“瞎试”进化为“智能试错”的分水岭。

cov(P)=∣Evisited∣∣Etotal∣\text{cov}(P) = \dfrac{|E_{\text{visited}}|}{|E_{\text{total}}|}
公式说明:覆盖率衡量程序被测的“深度”。覆盖率越高,说明程序被探索得越充分,留给隐藏缺陷的暗角越少。
  1. cov(P)(覆盖率)cov(P) 表示被测程序 P 的覆盖程度,是引导 fuzzing 方向的核心指标。
    cov(P)=…\text{cov}(P) = \dots
  2. 已访问路径分子 E_visited 是测试过程中实际执行到的代码路径集合,反映探索广度。
    ∣Evisited∣|E_{\text{visited}}|
  3. 总路径数分母 E_total 是程序可达路径总数,由插桩统计的全部基本块或边决定。
    ∣Etotal∣|E_{\text{total}}|
  4. 比值含义覆盖率越接近 1,说明未探索区域越小,隐藏问题的空间也就越少。
    ∣Evisited∣∣Etotal∣\dfrac{|E_{\text{visited}}|}{|E_{\text{total}}|}

语料管理

试菜师的菜谱本不是越厚越好。如果本子里堆满重复的“西红柿炒蛋”变体,面对新挑战时就发挥不了作用。真正的高手会不断把新奇的配方抄进来,把被充分验证的老配方淘汰出去,让菜谱本始终“小而锋利”。

语料管理就是对种子库的持续维护。模糊测试开始时,种子库由正常输入样例构成,比如配置文件、示例文件、常见请求。运行中,每当某次变异带来了新的覆盖率,就把该输入加入种子库;为了防止库无限膨胀,还要定期修剪,只保留贡献最大的样本。

语料更新的判据可以用条件式表达:

菜谱本是试菜师的核心资产,语料库也是 Fuzzer 的核心记忆。管理好种子,就是管理好探索的方向与成本。

St+1={St∪{x′},if cov(x′)>cov(St)St,otherwiseS_{t+1} = \begin{cases} S_t \cup \{x'\}, & \text{if } \text{cov}(x') > \text{cov}(S_t) \\ S_t, & \text{otherwise} \end{cases}
公式说明:只有带来新覆盖的输入才被允许入库,种子库始终是“探索路线的精华浓缩”,而不是所有尝试过输入的大杂烩。
  1. 当前语料库 S_tS_t 是时刻 t 的种子集合,下一轮变异将从这里挑选素材。
    StS_t
  2. 新增输入 x'x' 是刚生成的测试输入,若它带来新覆盖则值得保留进库。
    St∪{x′}S_t \cup \{x'\}
  3. 覆盖率判据当新输入产生的覆盖率大于当前库整体时,才执行入库操作。
    if cov(x′)>cov(St)\text{if } \text{cov}(x') > \text{cov}(S_t)
  4. 丢弃分支否则语料库保持不变,避免无效种子膨胀拖慢后续测试。
    StS_t

崩溃分析

餐厅里顾客突然上吐下泻,试菜师不会把责任推给“某道菜”就完事。他会调出监控录像,顺着流程一步步回放:哪位厨师、哪种食材、哪个环节出了问题。监控画面就是厨房版“回溯栈”。

模糊测试每天能撞出成百上千次崩溃,但很多崩溃其实是同一个 bug 的不同表现。如果逐个处理,效率极低且容易淹没真正的新漏洞。崩溃分析要回答两个问题:这次崩溃和之前见过的是同一种吗?触发的输入能被简化以方便人工排查吗?

去重的核心是为每次崩溃计算“签名”:

监控录像看清楚了,才能把相似的“中毒事件”并案,挑出最精简的“致命配方”交给后厨整改。崩溃分析的效率,决定了一次模糊测试行动能沉淀出多少有效成果。

σ(c)=hash(backtrace(c))\sigma(c) = \text{hash}\left(\text{backtrace}(c)\right)
公式说明:崩溃时的函数调用栈被哈希成固定长度的签名。相同签名归为同一类问题,不同签名才有资格被当作新缺陷。
  1. 崩溃签名 σ(c)每条崩溃记录通过哈希生成特征码,用于区分不同根因。
    σ(c)=…\sigma(c) = \dots
  2. 回溯栈 backtrace(c)取出崩溃时的函数调用栈,刻画了出错的执行路径。
    backtrace(c)\text{backtrace}(c)
  3. 哈希指纹哈希把调用栈压缩为固定长度的整数,相同栈得到同一签名。
    hash(… )\text{hash}(\dots)
  4. 去重原理相同签名视为同一漏洞归并处理,不同签名则是潜在的新问题。
    hash(backtrace(c))\text{hash}(\text{backtrace}(c))

AI红队应用

如今后厨里来了一位 AI 大厨——图像识别、语音助手、智能客服。试菜师发现,老的招式依然有效:把图片上的一两个像素“调调味”,把文本里的几个字“换换料”,AI 大厨就可能说出危险的话。于是试菜师的工作又多了一个新战场:智能厨房的鲁棒性。

在 AI 安全领域,这种对新战场试菜的自动化过程常被称为对 AI 系统做模糊测试或对抗性测试。与传统程序测试不同,这里关注的不是“程序崩溃”,而是模型输出错误——比如把“停止”标识识别成“通行”,把合法用户拒之门外。

对抗样本是这一领域最典型的产物。对输入 x 施加一个与梯度方向一致的小扰动,就得到误导模型的样本:

当试菜对象从锅碗瓢盆变成神经网络,试菜师的手艺依然相通:用系统化扰动去试探,用错误输出去证伪,最终把 AI 大厨的“盲区”一个个逼出来。这正是 AI 红队测试的核心使命。

x′=x+ϵ⋅sign(∇xL(fθ(x),y))x' = x + \epsilon \cdot \text{sign}\left(\nabla_x \mathcal{L}(f_\theta(x), y)\right)
公式说明:沿损失函数上升方向给输入加一点扰动,足以让模型改变判断,是 AI 模糊测试生成“致命配方”的基本模板。
  1. x'(对抗样本)对输入施加微小扰动后得到的样本,用来测试模型的鲁棒性。
    x′=x+…x' = x + \dots
  2. 原始输入 xx 是模型的一个正常输入,如图像、文本、语音特征。
    x+ϵ⋅…x + \epsilon \cdot \dots
  3. 梯度符号方向对损失函数求输入梯度并取符号,得到让模型错误分类的扰动方向。
    sign(∇xL(fθ(x),y))\text{sign}\left(\nabla_x \mathcal{L}(f_\theta(x), y)\right)
  4. 扰动幅度 εε 控制扰动大小,让样本看起来几乎不变,但模型判断已经出错。
    ϵ\epsilon

工具与流程

成熟餐厅会给试菜师配齐流水线:自动配菜机、智能灶台、崩盘记录仪。试菜师只需把配方卡放进传送带,机器就会自动投喂、记录、归档。工具的介入,让测试从“体力活”升级成“自动化产线”。

软件社区的模糊测试也早已工业化:AFL(American Fuzzy Lop)、libFuzzer、Honggfuzz 都是成熟的 Fuzzer。它们内置了插桩、覆盖率跟踪、变异调度、崩溃归档等模块。使用时一般需要:对被测代码做插桩编译、写一个接收输入的 harness、再以批量模式运行并收集崩溃样本。

一个典型 Fuzzer 会维护“当前最优输入集”,并依此决定下一轮变异对象:

像后厨流水线一样,把工具、插桩和 CI 接好,Fuzzing 就能 7×24 小时运转。试菜师从“一碟一碟地端”升级为“整条流水线地管”,效率自然天差地别。

tn+1={mutate(tn),if cov(tn)>covbestreinit(tn),otherwiset_{n+1} = \begin{cases} \text{mutate}(t_n), & \text{if } \text{cov}(t_n) > \text{cov}_{\text{best}} \\ \text{reinit}(t_n), & \text{otherwise} \end{cases}
公式说明:若某个输入带来了新覆盖,就在它附近继续深耕;否则换一个出发点重新探索。AFL 等工具的核心调度逻辑与此一致。
  1. 下一轮用例 t_{n+1}Fuzzer 依据当前轮的探索结果决定下一个测试输入。
    tn+1t_{n+1}
  2. 继续变异若本轮输入带来了新高覆盖率,说明方向正确,继续在它邻近区域变异。
    mutate(tn)\text{mutate}(t_n)
  3. 覆盖率比较将 cov(t_n) 与历史最优比较,判断这次输入是否有新发现。
    cov(tn)>covbest\text{cov}(t_n) > \text{cov}_{\text{best}}
  4. 重新初始化否则说明走入死胡同,选择新种子重新开始探索,避免浪费算力。
    reinit(tn)\text{reinit}(t_n)

效果评估

餐厅高管想评估试菜团队干得好不好,不会只问“今天倒了几道菜”。要看他们尝过的菜品覆盖了菜单几成;要看发现致命问题的趋势是快是慢;还要看报告上的一堆事故里有多少是重复的老问题。这些指标,才是团队价值的真实注脚。

评价一次模糊测试行动同样需要多维度指标。最简单的是“崩溃总数”,但它容易被重复崩溃注水;更可靠的是唯一崩溃数(去重后)、覆盖率增长曲线、崩溃发现速率。覆盖率回答“探索深不深”,速率回答“产出快不快”,去重数量回答“问题多不多”。

一个经典的观察是:漏洞累计发现数随时间呈饱和增长曲线:

成绩单会说话。用覆盖率、去重崩溃数、发现速率评估测试行动,团队才知道下一轮该加大变异力度、扩充语料库,还是换一份更对症的字典——持续优化,才不辜负每一张辛苦生成的配方卡。

N(t)=Nmax⁡(1−e−kt)N(t) = N_{\max}\left(1 - e^{-kt}\right)
公式说明:前期 Fuzzer 快速发现一批新漏洞,随后逐渐饱和。k 越大说明单位时间发现速率越高,N_max 则估算漏洞上限。
  1. N(t)(累计发现数)N(t) 表示运行 t 时间后累计发现的漏洞数量。
    N(t)N(t)
  2. 上限 N_max表示被测程序隐含的可发现漏洞理论上界。
    Nmax⁡N_{\max}
  3. 指数饱和项1 减 e^{-kt} 描述前期快、后期慢的饱和式增长规律。
    (1−e−kt)\left(1 - e^{-kt}\right)
  4. 速率系数 kk 越大说明单位时间发现漏洞越多,是评估与对比工具优劣的关键参数。
    kk

AI 应用场景

Web API 安全测试

阶段:红队侦察与漏洞挖掘阶段

解决的问题:传统渗透测试依赖人工构造请求,速度慢、覆盖有限、容易遗漏边界条件。AI 辅助的模糊测试可持续生成并变异 API 请求参数,自动监测 5xx 异常、SQL 报错、超时等信号,并在发现崩溃后自动分类归档。

具体案例:某金融科技公司对线上转账接口做 Fuzz:构造包含超长字符串、特殊字符、负数和极端 JSON 结构的请求。测试第 3 分钟发现一个由参数 age=-99999 触发的内存越界,导致服务崩溃;第 20 分钟产生一批可追溯的崩溃报告,开发据此修复了 3 个边界校验缺陷。

AI 模型鲁棒性测试

阶段:模型上线前的对抗验证

解决的问题:传统测试只用正常样本评估准确率,无法暴露模型在扰动输入下的脆弱点。用模糊测试思想对输入施加像素扰动、遮挡、噪声、语义替换,追踪模型输出错误率,定位“最脆弱的样本类型”。

具体案例:自动驾驶团队用 fuzzing 方法对路况分类模型施加光照变化、雨雾叠加等扰动,发现“左转标志被贴上白色小块后 10% 概率被识别为直行”。随后在训练集中补充该扰动样本,模型鲁棒性显著提升。

Python 演示

python78 行
"""覆盖率引导的模糊测试演示: 用变异策略自动发现输入处理函数的漏洞"""import random def process_input(data):    """模拟被测程序: 依据输入内容走向不同分支, 某些输入会触发异常"""    if len(data) < 4:        return "too short"    if data.startswith("GET "):        if data == "GET /admin":            raise ValueError("权限检查失败!")        if "select" in data:            raise ValueError("SQL注入!")        return "ok"    if "password" in data and len(data) > 60:        raise ValueError("缓冲区溢出!")    if "\x00" in data:        raise ValueError("空字节处理异常!")    return "unknown" def mutate(seed):    """对种子输入实施随机的变异操作"""    chars = list(seed if seed else "GET ")    action = random.choice(["flip", "insert", "delete", "spice"])    if action == "flip" and len(chars) > 1:        index = random.randrange(len(chars))        chars[index] = chr(ord(chars[index]) ^ 1)    elif action == "insert":        chars.insert(random.randrange(len(chars) + 1),                     random.choice("GET /passwordselect\x00"))    elif action == "delete" and len(chars) > 1:        chars.pop(random.randrange(len(chars)))    elif action == "spice":        chars.append(random.choice(";' OR 1=1--"))    return "".join(chars) def collect_coverage(data):    """插桩统计: 真实 fuzzer 由编译器插桩完成, 这里用字符串特征模拟覆盖"""    features = set()    if len(data) < 4:        features.add("len < 4")    if data.startswith("GET"):        features.add("startswith GET")    if "password" in data:        features.add("contains password")    if "select" in data.lower():        features.add("contains select")    return features def run_fuzzer(rounds=1000):    """主循环: 变异输入, 执行程序, 记录覆盖, 保留有价值的种子"""    corpus = ["GET /", "ping", "hello"]    seen_features = set()    crashes = []     for iteration in range(rounds):        seed = random.choice(corpus)        test_input = mutate(seed)        features = collect_coverage(test_input)        new_features = features - seen_features        if new_features:            seen_features.update(new_features)            corpus.append(test_input)            corpus = corpus[-60:]         try:            process_input(test_input)        except ValueError as error:            crash_key = str(error)            if crash_key not in [c[1] for c in crashes]:                crashes.append((test_input, crash_key))                print(f"[#{iteration}] 发现崩溃: {test_input!r} -> {crash_key}")     print(f"测试完成: 共 {rounds} 轮, 覆盖特征 {len(seen_features)} 个, "          f"崩溃类型 {len(crashes)} 种")    return crashes if __name__ == "__main__":    run_fuzzer(2000)
目的:演示一个微型覆盖率引导模糊测试器——它从种子库出发,通过变异生成测试输入,用覆盖率反馈决定保留哪些输入,并自动去重记录崩溃。
  1. corpus = ["GET /", "ping", "hello"]初始化种子语料库,相当于试菜师的初始菜谱本
  2. test_input = mutate(seed)从语料库随机选取种子并变异,生成一条新“配方”
  3. features = collect_coverage(test_input)模拟插桩,统计本次输入到达的代码特征
  4. new_features = features - seen_features计算本次输入是否带来新覆盖
  5. corpus.append(test_input)有新增覆盖的输入进入种子库,成为后续变异的素材
  6. corpus = corpus[-60:]限制种子库规模,避免无限膨胀拖慢测试
  7. raise ValueError("SQL注入!")模拟被测程序中隐藏的缺陷,等待 fuzzer 撞上
  8. crash_key = str(error)以错误信息作为“签名”,去重同类崩溃
  9. run_fuzzer(2000)启动 2000 轮自动模糊测试,观察发现效果

常见误区

误区“用随机数生成器乱丢一堆输入,剩下全靠运气。既然是随机的,覆盖不覆盖都一样,跑久了总会撞见 bug。”

正解这并非主流形式。现代 Fuzzer 普遍采用覆盖率引导:优先保留探索到新代码路径的输入,再在其基础上精细变异。覆盖率引导的 Fuzzer 发现的 bug 数量,往往比纯随机丢输入高出几个量级。试菜师会盯着菜单上没打勾的虚线走,而不是闭着眼乱指。

为何常见:Fuzzing 字面含义就是“模糊”,早期工具确实是随机生成输入,“随机乱试”的印象因此根深蒂固。

误区“只要测到所有代码行都执行过,就等于把程序'看穿'了,不可能还有剩余漏洞。”

正解覆盖率 100% 只说明代码被执行,不代表所有输入组合都被验证。同一段代码,不同输入会产生不同行为;状态值、并发时序、内存布局都在“代码执行之外”。覆盖率是必要手段而非终点。菜单上每道菜都尝过一口,也不等于每个火候档位都试过。

为何常见:覆盖率数字直观,容易和“测试完备性”联系起来,人们本能地把它当作“完成度百分比”。

误区“模糊测试的输出只有崩溃才有价值,如果不是段错误或地址越界,都不值得看。”

正解成熟的 fuzzing 还监控断言失败、超时挂起、内存泄漏、未定义行为、类型混淆等。到了 AI 时代,模型的错误分类、敏感信息泄漏也是可观测的“异常”。fuzzing 的本质是“可监测的异常行为”,崩溃只是其中最醒目的一类。

为何常见:教科书和演示通常用 crash 展示成果,因为崩溃最直观,导致大众只记住了崩溃。

误区“AI 模型是浮点数运算,不是解析输入的 C 代码,模糊测试根本派不上用场。”

正解AI 模型的输入空间(图像像素、文本 token、语音特征)同样可以被变异和引导。可以用像素扰动、遮挡、噪声注入等方式“变异”输入,以模型的错误输出或不确定性为“崩溃信号”,再按簇去重找出盲区。TensorFuzz、DeepHunter 等工具正是这样做的。

为何常见:传统 fuzzing 围绕代码覆盖率展开,而 AI 模型没有“代码行”概念,让人误以为完全不适用。

面试题

Q1. Fuzzing模糊测试的核心思想是什么?

单选题 · easy

  • A.用海量自动生成的输入反复试探程序← 正确答案
  • B.人工阅读源码找出所有逻辑漏洞
  • C.仅依靠代码静态分析定位缺陷
  • D.让用户主动上报程序的异常现象

答案:A。Fuzzing 的核心是自动生成大量非预期输入并观察程序反应,与人工代码审计和静态分析是不同路线。选项 A 准确概括了“海量输入 + 自动试探”的本质。

Q2. 覆盖率引导的“覆盖率”主要衡量什么?

单选题 · easy

  • A.被测程序代码路径被执行的比例← 正确答案
  • B.测试用例数量占总数量的比例
  • C.测试过程消耗的总时间占比
  • D.程序运行时占用的内存百分比

答案:A。覆盖率衡量代码路径的执行广度,是引导 fuzzer 朝未探索区域推进的“菜单打分”。B、C、D 分别混淆了数量、时间和内存维度。

Q3. 下列哪一项属于常见的变异操作?

单选题 · easy

  • A.位翻转、字节替换、字典注入← 正确答案
  • B.代码编译、静态扫描、日志分析
  • C.数据加密、网络传输、结果校验
  • D.文件打包、版本控制、依赖安装

答案:A。位翻转、字节替换和字典注入是 fuzzing 三种经典变异招式,对应“试菜师改配方”的三种手法。其余选项属于开发编译、部署或测试辅助手段。

Q4. 种子语料库在模糊测试中的主要作用是什么?

单选题 · mid

  • A.提供初始输入作为变异的素材来源← 正确答案
  • B.保存程序源码供编译阶段使用
  • C.存储崩溃报告并自动生成工单
  • D.记录测试人员操作步骤的日志

答案:A。种子语料库是变异的起点,没有种子则“无米下锅”。它存储的是输入样本而非源码、报告或操作日志,后三者的描述偏离了语料库的定位。

Q5. 崩溃分析中“签名去重”的主要目的是?

单选题 · mid

  • A.合并同类崩溃,聚焦真正的独立缺陷← 正确答案
  • B.增加崩溃样本数量提升统计效果
  • C.缩短代码覆盖率路径搜索的时间
  • D.让崩溃输入在部署环境稳定复现

答案:A。签名去重把相同调用栈的崩溃合并,避免开发被大量重复报告淹没。它不会增加样本数、缩短覆盖率搜索时间,也不能保证复现。

Q6. 哪些操作属于模糊测试的变异策略?

多选题 · mid

  • A.对种子输入执行位翻转← 正确答案
  • B.向输入中插入字典关键字← 正确答案
  • C.删除输入中的随机字节段← 正确答案
  • D.对程序执行日志做格式化

答案:ABC。位翻转、字典注入和删除字节段都是变异策略的典型招式,对应“改配方”的多种手法。对日志做格式化属于测试结果的后续处理,不属于输入变异。

Q7. 覆盖率引导的Fuzzer有哪些典型行为?

多选题 · mid

  • A.保留能探索新路径的测试输入← 正确答案
  • B.在新覆盖输入周边持续深入变异← 正确答案
  • C.完全不关注代码执行路径信息
  • D.定期修剪无探索价值的种子输入← 正确答案

答案:ABD。覆盖率引导的核心是保留新覆盖输入、围绕新路径深入变异,并维护精简的语料库。完全不关注路径信息是纯随机 fuzzing 的特征,与覆盖率引导相反。

Q8. 对AI模型做模糊测试时,与传统fuzzing最本质的差别是?

单选题 · extreme

  • A.观测目标是错误输出而非代码崩溃← 正确答案
  • B.完全不需要种子输入和语料管理
  • C.输入维度一定比传统软件低
  • D.发现的问题无法进行人工复现

答案:A。AI 模糊测试的“异常信号”是模型错误输出、不确定性飙升等,而非传统意义的进程崩溃。种子与语料管理依然需要,维度一般更高而非更低,问题同样可复现。

Q9. 评价模糊测试效果时,哪些指标值得关注?

多选题 · easy

  • A.代码覆盖率增长曲线← 正确答案
  • B.去重后的唯一崩溃数量← 正确答案
  • C.运行时内存占用的波动
  • D.单位时间内崩溃发现速率← 正确答案

答案:ABD。覆盖率曲线反映探索深度,去重唯一崩溃数和发现速率反映真实产出。内存波动不是评估 fuzz 效果的主要指标。

Q10. 语料库修剪的最大意义在于?

单选题 · hard

  • A.控制种子规模,让变异聚焦更有价值的输入← 正确答案
  • B.删除旧种子以便重新开始测试
  • C.消除程序内部潜在的类型混淆
  • D.降低代码覆盖率计算的难度

答案:A。语料修剪是“菜谱本小而锋利”策略的实现:剔除冗余输入,使每次变异都围绕高价值样本展开。它不是删旧重来,更与修复类型混淆、降低覆盖率计算难度无关。