Quiet
  • HOME
  • ARCHIVE
  • CATEGORIES
  • TAGS
  • LINKS
  • ABOUT

ChenSir

  • HOME
  • ARCHIVE
  • CATEGORIES
  • TAGS
  • LINKS
  • ABOUT
Quiet主题
  • UESTC

AutoSafeCoder-Fuzzing Input Generator

ChenSir
UESTC

2025-04-27 00:00:04

Chapter 4: 模糊测试输入生成器 (Fuzzing Input Generator)

欢迎回来!在上一章 第 3 章:静态安全分析器 (Static Security Analyzer) 中,我们认识了 AutoSafeCoder 的“代码安全检查员”。它像一个细心的侦探,在不运行代码的情况下,检查代码本身是否存在已知的安全漏洞。这非常有用,但还不够全面。

想象一下,我们设计了一辆新玩具车。静态安全分析器就像检查员,检查了玩具车的零件是否齐全、材料是否安全、结构是否符合设计图。但是,这辆玩具车在实际玩耍时表现如何呢?如果孩子们用奇怪的方式推它、撞它、甚至给它一些意想不到的“指令”,它会不会散架或者出现奇怪的行为?

仅仅检查代码的“设计图”是不够的,我们还需要看看代码在实际运行时,面对各种意想不到的、甚至是“捣乱”的输入数据时,表现如何。这就是模糊测试 (Fuzzing) 的用武之地,而要进行模糊测试,我们首先需要有人来制造这些“捣乱”的数据。这就是我们今天要认识的**模糊测试输入生成器 (Fuzzing Input Generator)**。

什么是模糊测试输入生成器?

模糊测试输入生成器 (Fuzzing Input Generator) 是 AutoSafeCoder 团队中的“捣蛋鬼”或“创意工匠”。它的核心任务是为模糊测试过程创建大量的、多样化的测试输入数据。

它的工作方式通常是:

  1. 从一个“种子”开始:它通常会从一个或一组已知的、有效的输入开始(这些初始输入可能由另一个专门的智能体,比如 TesterFuzzAgent,基于代码功能生成)。
  2. 进行“变异”:然后,它会对这些种子输入进行各种随机或半随机的修改和变形。这就像一个工匠,拿到一块正常的木头(原始输入),然后对其进行切割、打磨、拼接、甚至随机涂鸦,制造出各种奇形怪状的“零件”(新的测试输入)。
  3. 生成大量新输入:通过不断重复这个变异过程,它可以产生成百上千、甚至数百万个新的、可能看起来很奇怪的输入用例。

这些“奇特”的输入数据正是我们想要的!它们的目标是去挑战代码处理边缘情况和异常情况的能力。比如:

  • 给一个需要数字的函数输入一个超大的数字、一个负数、零,甚至是一段文字。
  • 给一个需要特定格式字符串的函数输入一个空字符串、一个超长字符串、或者包含特殊字符的字符串。
  • 改变数据结构,比如把列表变成字典,或者在预期的地方传入 None。

通过用这些由输入生成器制造的“捣乱”数据去运行代码,我们就能更容易地发现那些在正常使用情况下很难暴露出来的 Bug 或安全漏洞,比如程序崩溃、内存泄漏、或者意想不到的行为。

模糊测试输入生成器如何工作?(高层视角)

想象一下,我们有一个函数 calculate_discount(price, percentage),用于计算折扣。

  1. 获取初始输入: 输入生成器可能先拿到一个正常的输入,比如 {"price": 100, "percentage": 0.1}。这可能是由 TesterFuzzAgent 生成的。
  2. 应用变异策略: 输入生成器(在 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}。
  3. 输出新输入: 每一次变异都会产生一个新的测试输入,比如:
    • {"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 类)协调:

  1. 生成初始输入 (TesterFuzzAgent): 在模糊测试开始时,框架首先需要一些“种子”输入。它会调用 TesterFuzzAgent 的 generate_test_inputs 方法来完成这一步。这个智能体可能会利用 LLM 分析代码功能来生成一些合理的初始测试用例。
  2. 变异现有输入 (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: 返回新的测试输入

图解说明:

  1. 框架 (MAS) 创建 InputMutatorAgent (IMA) 实例,并调用其 mutate_inputs 方法。
  2. 输入变异智能体 (IMA) 调用 utils.py 中的 fuzz_function 函数。(根据 fuzz_agent.py 的代码,mutate_inputs 直接调用了 fuzz_function)。
  3. fuzz_function 接着调用 utils.py 中的另一个 mutate_inputs 辅助函数(注意,这里有两个同名但可能功能不同的 mutate_inputs,一个是 IMA 的方法,一个是 utils.py 的辅助函数)。
  4. utils.py 的 mutate_inputs 函数 遍历输入字典中的每一个键值对。
  5. 对于每一个值,它调用 utils.py 中的 mutate_value 函数。
  6. mutate_value 函数 是真正执行变异的地方。它会检查值的类型(是整数、字符串、列表还是其他?),然后应用适合该类型的随机修改策略(比如给整数加减一个随机数,随机改变字符串中的字符)。
  7. mutate_value 返回变异后的值。
  8. utils.py 的 mutate_inputs 将所有变异后的值重新组装成一个新的输入字典。
  9. 这个新的、变异后的输入字典最终被返回给框架,用于下一轮测试。

代码层面的深入了解:

让我们看看 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

上一篇

AutoSafeCoder-Secure Code Execution Environment

下一篇

AutoSafeCoder-Static Security Analyzer

©2025 By ChenSir. 主题:Quiet
Quiet主题