Chapter 4: 模糊测试输入生成器 (Fuzzing Input Generator)
欢迎回来!在上一章 第 3 章:静态安全分析器 (Static Security Analyzer) 中,我们认识了 AutoSafeCoder 的“代码安全检查员”。它像一个细心的侦探,在不运行代码的情况下,检查代码本身是否存在已知的安全漏洞。这非常有用,但还不够全面。
想象一下,我们设计了一辆新玩具车。静态安全分析器就像检查员,检查了玩具车的零件是否齐全、材料是否安全、结构是否符合设计图。但是,这辆玩具车在实际玩耍时表现如何呢?如果孩子们用奇怪的方式推它、撞它、甚至给它一些意想不到的“指令”,它会不会散架或者出现奇怪的行为?
仅仅检查代码的“设计图”是不够的,我们还需要看看代码在实际运行时,面对各种意想不到的、甚至是“捣乱”的输入数据时,表现如何。这就是模糊测试 (Fuzzing) 的用武之地,而要进行模糊测试,我们首先需要有人来制造这些“捣乱”的数据。这就是我们今天要认识的**模糊测试输入生成器 (Fuzzing Input Generator)**。
什么是模糊测试输入生成器?
模糊测试输入生成器 (Fuzzing Input Generator) 是 AutoSafeCoder 团队中的“捣蛋鬼”或“创意工匠”。它的核心任务是为模糊测试过程创建大量的、多样化的测试输入数据。
它的工作方式通常是:
- 从一个“种子”开始:它通常会从一个或一组已知的、有效的输入开始(这些初始输入可能由另一个专门的智能体,比如
TesterFuzzAgent
,基于代码功能生成)。 - 进行“变异”:然后,它会对这些种子输入进行各种随机或半随机的修改和变形。这就像一个工匠,拿到一块正常的木头(原始输入),然后对其进行切割、打磨、拼接、甚至随机涂鸦,制造出各种奇形怪状的“零件”(新的测试输入)。
- 生成大量新输入:通过不断重复这个变异过程,它可以产生成百上千、甚至数百万个新的、可能看起来很奇怪的输入用例。
这些“奇特”的输入数据正是我们想要的!它们的目标是去挑战代码处理边缘情况和异常情况的能力。比如:
- 给一个需要数字的函数输入一个超大的数字、一个负数、零,甚至是一段文字。
- 给一个需要特定格式字符串的函数输入一个空字符串、一个超长字符串、或者包含特殊字符的字符串。
- 改变数据结构,比如把列表变成字典,或者在预期的地方传入
None
。
通过用这些由输入生成器制造的“捣乱”数据去运行代码,我们就能更容易地发现那些在正常使用情况下很难暴露出来的 Bug 或安全漏洞,比如程序崩溃、内存泄漏、或者意想不到的行为。
模糊测试输入生成器如何工作?(高层视角)
想象一下,我们有一个函数 calculate_discount(price, percentage)
,用于计算折扣。
- 获取初始输入: 输入生成器可能先拿到一个正常的输入,比如
{"price": 100, "percentage": 0.1}
。这可能是由TesterFuzzAgent
生成的。 - 应用变异策略: 输入生成器(在 AutoSafeCoder 中由
InputMutatorAgent
负责后续变异)开始“捣乱”:- 改变数值: 把
price
改成999999999
(极大值),改成-50
(负数),改成0
。 - 改变类型: 把
percentage
从0.1
(浮点数) 改成"ten percent"
(字符串)。 - 边界值: 把
percentage
改成1.0
(100%折扣),改成0.0
,改成1.1
(超过100%)。 - 结构变异: 把输入改成
{"price": 100}
(缺少percentage
),或者{"price": 100, "percentage": None}
。
- 改变数值: 把
- 输出新输入: 每一次变异都会产生一个新的测试输入,比如:
{"price": -50, "percentage": 0.1}
{"price": 100, "percentage": "ten percent"}
{"price": 100, "percentage": 1.1}
{"price": 100}
这些新产生的输入会被传递给下一个环节——模糊测试执行器 (Fuzzing Executor),用来实际运行被测试的代码。
如何使用模糊测试输入生成器?
在 AutoSafeCoder 中,生成模糊测试输入的过程主要涉及两个部分,它们都由多智能体协作框架 (Multi-Agent Collaboration Framework)(即 main.py
中的 MultiAgentSystem
类)协调:
- 生成初始输入 (
TesterFuzzAgent
): 在模糊测试开始时,框架首先需要一些“种子”输入。它会调用TesterFuzzAgent
的generate_test_inputs
方法来完成这一步。这个智能体可能会利用 LLM 分析代码功能来生成一些合理的初始测试用例。 - 变异现有输入 (
InputMutatorAgent
): 在模糊测试的循环中,框架会使用InputMutatorAgent
来对当前的输入进行变异,产生新的测试用例。这是“生成器”概念的核心体现。
让我们看看 main.py
中框架是如何使用它们的:
1. 创建相关智能体实例
在 MultiAgentSystem
的初始化方法 __init__
中,会创建这些智能体的实例。
# main.py (片段)
from tester_fuzz_agent import TesterFuzzAgent # 用于生成初始输入
from fuzz_agent import InputMutatorAgent # 用于变异输入
# ... 其他导入 ...
class MultiAgentSystem:
def __init__(self, entry):
# ... 初始化 ProgrammerAgent 和 ExecutorStaticAgent ...
# 创建 TesterFuzzAgent 实例
self.tester_fuzz_agent = TesterFuzzAgent(entry)
# 注意: InputMutatorAgent 是在运行中根据当前输入创建的
self.code = None
self.test_inputs = None # 用来存储当前的测试输入
代码解释:
- 我们从
tester_fuzz_agent.py
导入TesterFuzzAgent
。 - 在
MultiAgentSystem
初始化时创建其实例self.tester_fuzz_agent
。 InputMutatorAgent
(负责变异的智能体)通常不是在初始化时创建,而是在模糊测试循环内部,当需要对一组特定的输入进行变异时才创建。
2. 生成初始输入
框架在 run
方法的模糊测试部分,首先调用 tester_fuzz_agent
获取初始输入。
# main.py (run 方法片段 - 简化)
class MultiAgentSystem:
# ... (init 和 静态分析部分) ...
def run(self, iterations=120):
# ... (前面的代码) ...
# === 第 3 步: Fuzzing Agent 生成初始测试输入 ===
test_inputs_list = []
# 调用 tester_fuzz_agent 的 generate_test_inputs 方法
self.test_inputs = self.tester_fuzz_agent.generate_test_inputs()
if not self.test_inputs: # 如果未能生成输入
print("未能生成初始模糊测试输入,跳过动态测试。")
# ... (记录状态并返回) ...
return
test_inputs_list.append(self.test_inputs) # 记录使用的输入
print(f"初始测试输入:\n{self.test_inputs}")
# ... 后续模糊测试循环 ...
代码解释:
self.tester_fuzz_agent.generate_test_inputs()
会与 LLM 交互,尝试根据代码的功能生成一组初始的、可能有效的输入,并将其存储在self.test_inputs
中。- 这些输入会被记录下来,并作为模糊测试的第一轮输入。
示例初始输入 (self.test_inputs
):
假设代码是 def add(a, b): return a + b
,初始输入可能是:
{'a': 1, 'b': 2}
3. 在循环中变异输入
接着,框架进入一个循环,在每次迭代中,它会执行代码,然后调用 InputMutatorAgent
来生成下一轮的输入。
# main.py (run 方法片段 - 模糊测试循环简化)
class MultiAgentSystem:
# ... (前面的代码) ...
# === 第 4 步: 执行和变异循环 ===
failed_inputs_fuzz = [] # 记录导致失败的输入
current_inputs = self.test_inputs # 从初始输入开始
functionname = None # 用于存储检测到的函数名
for iteration in range(iterations): # 进行 N 轮模糊测试
print(f"\n模糊测试迭代 {iteration + 1}")
# 4a: 执行器用当前输入运行代码 (下一章内容)
# result, passed, inputs_used, func_name = execute_fuzz(...)
# (简化:假设我们拿到了执行结果和使用的输入/函数名)
passed = True # 假设本次执行通过
inputs_used = current_inputs
# (实际代码会从 execute_fuzz 获取 functionname)
# functionname = func_name
if not passed:
# 如果执行失败,记录失败信息 (省略)
failed_inputs_fuzz.append({'inputs': inputs_used, 'result': 'some error'})
# 4b: 创建 InputMutatorAgent 并变异输入
# 需要传入当前的输入、代码和函数名 (用于某些变异策略)
mutator_agent = InputMutatorAgent(inputs_used, self.code, functionname)
current_inputs = mutator_agent.mutate_inputs() # 生成新的输入
test_inputs_list.append(current_inputs) # 记录新生成的输入
print(f"变异后的输入:\n{current_inputs}")
# === 第 5 步: 处理模糊测试结果 (如果发现错误则反馈给程序员) ===
# ... (省略) ...
代码解释:
- 在每一轮循环的末尾,框架会创建一个
InputMutatorAgent
的实例。 - 创建时需要传入当前使用的输入 (
inputs_used
)、代码 (self.code
) 和 **函数名 (functionname
)**。(代码和函数名有时可以帮助变异器做出更智能的变异决策,虽然简单变异可能用不上)。 - 调用
mutator_agent.mutate_inputs()
,这个方法会应用各种随机变异策略,生成一组新的输入。 - 这个新生成的输入
current_inputs
将在下一轮循环中被用来测试代码。 - 这个“执行 -> 变异 -> 执行 -> 变异 -> …”的过程不断重复,持续探索代码对各种输入的反应。
示例变异输入 (current_inputs
):
基于上一轮的输入 {'a': 1, 'b': 2}
,mutate_inputs()
可能会生成:
{'a': 1001, 'b': 2}
(整数变异){'a': 1, 'b': -998}
(整数变异){'a': 'hello', 'b': 2}
(类型变异,可能在utils.py
的mutate_value
实现)- …等等,每次调用都可能不同,因为它是随机的。
通过 TesterFuzzAgent
提供起点,InputMutatorAgent
不断制造“惊喜”,AutoSafeCoder 就能源源不断地产生测试数据来考验代码了。
内部实现揭秘
我们已经知道了模糊测试输入生成器(主要是变异器 InputMutatorAgent
)的作用和用法,现在稍微深入一点,看看它是如何在内部进行“变异”的。这主要涉及到 fuzz_agent.py
文件和 utils.py
文件中的辅助函数。
非代码流程图解 (输入变异):
当框架调用 InputMutatorAgent
的 mutate_inputs
方法时,内部大致发生了什么?
sequenceDiagram
participant MAS as 多智能体协作框架 (MultiAgentSystem)
participant IMA as 输入变异智能体 (InputMutatorAgent)
participant Utils as 工具函数 (utils.py)
MAS->>IMA: 创建 InputMutatorAgent(当前输入, 代码, 函数名)
MAS->>IMA: 调用 mutate_inputs()
IMA->>Utils: 调用 fuzz_function(当前输入, 代码, 函数名)
Note right of Utils: fuzz_function 内部
Utils->>Utils: 调用 mutate_inputs(当前输入)
Utils->>Utils: 对输入的每个值调用 mutate_value(值)
Note right of Utils: mutate_value 根据值的类型应用随机变异
Utils-->>Utils: 返回变异后的值
Utils-->>Utils: 组装成新的输入字典
Utils-->>IMA: 返回变异后的输入字典
IMA-->>MAS: 返回新的测试输入
图解说明:
- 框架 (MAS) 创建
InputMutatorAgent
(IMA) 实例,并调用其mutate_inputs
方法。 - 输入变异智能体 (IMA) 调用
utils.py
中的fuzz_function
函数。(根据fuzz_agent.py
的代码,mutate_inputs
直接调用了fuzz_function
)。 fuzz_function
接着调用utils.py
中的另一个mutate_inputs
辅助函数(注意,这里有两个同名但可能功能不同的mutate_inputs
,一个是 IMA 的方法,一个是utils.py
的辅助函数)。utils.py
的mutate_inputs
函数 遍历输入字典中的每一个键值对。- 对于每一个值,它调用
utils.py
中的mutate_value
函数。 mutate_value
函数 是真正执行变异的地方。它会检查值的类型(是整数、字符串、列表还是其他?),然后应用适合该类型的随机修改策略(比如给整数加减一个随机数,随机改变字符串中的字符)。mutate_value
返回变异后的值。utils.py
的mutate_inputs
将所有变异后的值重新组装成一个新的输入字典。- 这个新的、变异后的输入字典最终被返回给框架,用于下一轮测试。
代码层面的深入了解:
让我们看看 fuzz_agent.py
和 utils.py
中的关键代码。
1. 输入变异智能体 (InputMutatorAgent
)
# fuzz_agent.py (片段)
from utils import fuzz_function # 导入执行变异的函数
class InputMutatorAgent:
def __init__(self, inputs, code, funname):
# 存储当前的输入、代码和函数名
self.inputs = inputs
self.code = code
self.funname = funname
def mutate_inputs(self):
# 调用 utils.py 中的 fuzz_function 来执行变异
mutated_inputs = fuzz_function(self.inputs, self.code, self.funname)
return mutated_inputs # 返回变异后的输入
代码解释:
__init__
方法接收当前的输入inputs
、代码code
和函数名funname
并存储起来。mutate_inputs
方法的核心就是调用了utils.py
中的fuzz_function
。它将存储的输入、代码和函数名传递给这个函数,并直接返回该函数的结果(即变异后的输入)。InputMutatorAgent
本身更像是一个调用入口,实际的变异逻辑在utils.py
中。
2. 变异的核心逻辑 (utils.py
中的 fuzz_function
, mutate_inputs
, mutate_value
)
# utils.py (片段 - 简化)
import random
import string
from copy import deepcopy
# 这个函数是 InputMutatorAgent 调用的入口
def fuzz_function(inputs, code, funname, num_tests=1):
# 提取并变异输入 (这里的实现直接调用了下面的 mutate_inputs)
return mutate_inputs(inputs)
# 这个辅助函数遍历输入字典并调用 mutate_value
def mutate_inputs(inputs):
mutated_inputs = {}
try:
# 创建输入的深拷贝以避免修改原始输入
inputs_copy = deepcopy(inputs)
for key, value in inputs_copy.items():
# 对每个值调用 mutate_value 进行变异
mutated_inputs[key] = mutate_value(value)
except AttributeError:
# 处理输入不是字典的情况 (简化处理)
print("错误:输入不是字典格式。")
# 尝试将其视为列表处理 (示例性代码)
if isinstance(inputs, list):
inputs_dict = {i: item for i, item in enumerate(inputs)}
inputs_copy = deepcopy(inputs_dict)
for key, value in inputs_copy.items():
mutated_inputs[key] = mutate_value(value)
return mutated_inputs
# 这个函数根据值的类型应用具体的随机变异策略
def mutate_value(value):
"""根据值的类型变异单个值。"""
value_copy = deepcopy(value) # 操作副本
if isinstance(value_copy, bool):
# 50% 的概率翻转布尔值
return not value_copy if random.random() < 0.5 else value_copy
elif isinstance(value_copy, int):
# 整数变异:加上一个 [-1000, 1000] 范围内的随机数
return value_copy + random.randint(-1000, 1000)
elif isinstance(value_copy, float):
# 浮点数变异:加上一个 [-1000.0, 1000.0] 范围内的随机浮点数
return value_copy + random.uniform(-1000.0, 1000.0)
elif isinstance(value_copy, str):
# 字符串变异:随机选择打乱、添加或删除字符
if not value_copy: # 如果是空字符串,生成随机字符串
return ''.join(random.choices(string.ascii_letters + string.digits, k=random.randint(1, 5)))
mutation_type = random.choice(['shuffle', 'add', 'remove'])
str_len = len(value_copy)
if mutation_type == 'shuffle' and str_len > 1:
return ''.join(random.sample(value_copy, str_len))
elif mutation_type == 'add':
pos = random.randint(0, str_len)
char = random.choice(string.ascii_letters + string.digits)
return value_copy[:pos] + char + value_copy[pos:]
elif mutation_type == 'remove' and str_len > 0:
pos = random.randint(0, str_len - 1)
return value_copy[:pos] + value_copy[pos+1:]
else: # 默认返回原值或简单处理
return value_copy
elif isinstance(value_copy, list):
# 列表变异:对列表中的每个元素递归调用 mutate_value
# (简化:可能增加/删除元素,或只变异现有元素)
return [mutate_value(element) for element in value_copy]
elif isinstance(value_copy, dict):
# 字典变异:(简化) 随机变异某个值
if value_copy:
key_to_mutate = random.choice(list(value_copy.keys()))
value_copy[key_to_mutate] = mutate_value(value_copy[key_to_mutate])
return value_copy
else:
# 对于不支持的类型,返回原始值的副本
return value_copy
代码解释:
fuzz_function
目前的实现比较简单,直接调用了mutate_inputs
辅助函数。mutate_inputs
辅助函数负责遍历输入字典的每一项。它使用了deepcopy
来确保不会意外修改原始的输入对象。对于字典中的每个value
,它调用mutate_value(value)
。mutate_value
是核心的变异逻辑所在地。它使用isinstance
来判断值的类型,并根据类型执行不同的随机操作:- 布尔值: 随机翻转。
- 整数: 加上一个随机整数。
- 浮点数: 加上一个随机浮点数。
- 字符串: 随机选择一种操作(打乱顺序、添加随机字符、删除随机字符)。
- 列表: 递归地对列表中的每个元素调用
mutate_value
(更复杂的策略可能包括增删元素、改变顺序等)。 - 字典: 递归地变异字典中的值或键(这里简化为只变异一个随机选择的值)。
deepcopy
的使用很重要,确保了变异操作不会影响到原始数据结构,特别是对于列表和字典这样的可变对象。- 随机性是关键。每次调用
mutate_value
(以及内部的random
函数)都会产生不同的结果,这就是模糊测试能够探索大量不同输入的原因。
通过这种基于规则和随机性的变异,InputMutatorAgent
(借助 utils.py
中的函数)能源源不断地生成新的、可能引发问题的测试输入。
总结
在本章中,我们认识了 AutoSafeCoder 中负责制造“惊喜”的**模糊测试输入生成器 (Fuzzing Input Generator)**。我们学到了:
- 它的主要目标是生成大量多样化的、可能导致异常的输入数据,用于动态测试代码的健壮性和安全性。
- 它像一个“捣蛋工匠”,从初始输入(种子)开始,通过各种随机或半随机的变异操作(改变数值、类型、格式、结构等)来制造新的测试用例。
- 在 AutoSafeCoder 中,这个过程分为两步:
TesterFuzzAgent
负责生成初始种子输入。InputMutatorAgent
(利用utils.py
中的变异函数) 负责在模糊测试循环中对现有输入进行变异,生成后续的测试输入。
- 我们了解了框架 (
main.py
) 如何调用这两个智能体来驱动输入生成过程。 - 我们通过代码示例(特别是
utils.py
中的mutate_value
)了解了输入变异的基本原理:根据数据类型应用不同的随机修改策略。
模糊测试输入生成器为我们准备好了测试的“弹药”。但是,光有弹药还不行,我们还需要一个“发射器”来把这些弹药(测试输入)打向我们的目标(代码),并观察结果。
下一章预告: 第 5 章:模糊测试执行器 (Fuzzing Executor) - 我们将探索负责实际使用这些生成的输入来运行代码,并监控其行为的智能体。
Generated by AI Codebase Knowledge Builder