当前位置: 首页 > news >正文

基于AFLFast的fuzz自动化漏洞挖掘(1)

基于AFLFast的fuzz自动化漏洞挖掘(1)

前言:无意间了解到这样的一个课题,这里算是一个前瞻性的基础知识积累,后面也会陆续把涉及到的论文一并讲解了,还有涉及到的代码、环境这些。挺杂的,总之开始吧。

遗传算法(Genetic Algorithm, GA)

最最开始之前,要了解几个基本知识:遗传算法、fuzz、现行传统自动化漏洞挖掘的问题等等。简单补充一个前提性的东西。

算法核心:

顾名思义,遗传算法来自生物进化——遗传、变异、自然选择。遗传算法的目标也很简单:从一群解(个体)中“进化”出最优(或者近似最优)。

说白了,就是怎么去寻找最优解。

核心组成:

这里我们将简单说说遗传算法的5个核心元素:

元素含义示例
个体(Individual)一个解,用数组、字符串等形式编码[1, 0, 1, 1, 0]
种群(Population)一群个体(解)多个 [1,0,...]
适应度函数(Fitness)衡量个体好坏的函数函数最大值、路径最短等
选择(Selection)按适应度选择“好个体”进入下一代轮盘赌、锦标赛等方法
变异 / 交叉(Mutation / Crossover)用于产生新个体,增加多样性随机翻转、交叉组合等

我的理解是:前两个元素构成了基本种群,而适应度函数类似选择规则,选择实现了种群的迭代,而变异和交叉则为出现新的最优解提供了可能。

基本的GA流程大致如下:

[初始化种群] → [评估适应度] → [选择父代] → [交叉/变异产生子代] → [替换进入新一代] → 循环直到停止

最简单的算法实例:

目标:求最大化 f(x) = x²,x ∈ [0, 31](用 5-bit 二进制编码)

import randomdef decode(individual):return int("".join(str(bit) for bit in individual), 2)def fitness(individual):x = decode(individual)return x ** 2def select(population):population.sort(key=fitness, reverse=True)return population[:2]  # 保留适应度最高的两个个体def crossover(parent1, parent2):point = random.randint(1, len(parent1)-2)return parent1[:point] + parent2[point:], parent2[:point] + parent1[point:]def mutate(individual, rate=0.1):return [bit if random.random() > rate else 1-bit for bit in individual]# 初始化
POP_SIZE = 6
GENE_LENGTH = 5
population = [[random.randint(0, 1) for _ in range(GENE_LENGTH)] for _ in range(POP_SIZE)]# 进化
for generation in range(10):population = sorted(population, key=fitness, reverse=True)print(f"第{generation}代 最佳 = {decode(population[0])}, f(x) = {fitness(population[0])}")parents = select(population)offspring = []while len(offspring) < POP_SIZE:p1, p2 = random.choice(parents), random.choice(parents)c1, c2 = crossover(p1, p2)offspring.append(mutate(c1))if len(offspring) < POP_SIZE:offspring.append(mutate(c2))population = offspring

根据我们的分析我们可以很容易得知 f(x) = x²这个函数在给定的区间上是单调递增的,但是如何使用我们的遗传算法实现呢?

结合我们一开始说的基本要素还有流程简单说说:

1、定义种群:

population = [[random.randint(0, 1) for _ in range(GENE_LENGTH)] for _ in range(POP_SIZE)]

我们用基因长度表示每个解,这个种群中有6个个体,每个是一个5位长度的基因组成的。

population = [[0, 1, 0, 1, 1],  # → 11[1, 0, 0, 0, 1],  # → 17...
]
#这样的数组有五个

2、适应度函数评估:

def decode(individual):return int("".join(str(bit) for bit in individual), 2)def fitness(individual):x = decode(individual)return x ** 2

decode处理五位基因长度实现。fitness完成运算

3、选择:

很显然,在这个任务下我们的选择判断很简单:

def select(population):population.sort(key=fitness, reverse=True)return population[:2]

排序,选择最优秀的两个个体作为父代(精英选择的思想)

4、交叉:

def crossover(parent1, parent2):point = random.randint(1, len(parent1)-2)return parent1[:point] + parent2[point:], parent2[:point] + parent1[point:]

随机选择一个交叉点,将两个父代的基因交换,产生两个子代

5、变异:

def mutate(individual, rate=0.1):return [bit if random.random() > rate else 1-bit for bit in individual]
  • 每一位基因有 10% 概率发生变异(翻转 0 ↔ 1);
  • 模拟生物“突变”行为,增加基因多样性;
  • 防止陷入局部最优。

6、迭代与传递:

offspring = []
while len(offspring) < POP_SIZE:p1, p2 = random.choice(parents), random.choice(parents)c1, c2 = crossover(p1, p2)offspring.append(mutate(c1))if len(offspring) < POP_SIZE:offspring.append(mutate(c2))population = offspring
  • 从精英父代中随机选两人交叉、变异,生成子代;
  • 用这些子代构成新一代种群(取代旧的种群);
  • 整个进化进入下一轮。

依次迭代直到满足条件为止。

Fuzzing(模糊测试)

前面我们已经简单说完了遗传算法的事情,现在来说说fuzzing。

什么是fuzzing?

Fuzzing(模糊测试) 是一种自动化测试技术,它的核心思路是:

向目标程序输入大量随机或变异生成的数据,以触发异常行为(如崩溃、挂起、越界等),从而发现漏洞或缺陷。

核心:触发bug/崩溃/安全漏洞,探索更多的代码路径。

基本流程与组成核心:

        +----------------+| 初始输入种子 S |+----------------+|[变异器 Mutator]↓+----------------+|   构造输入 I    |+----------------+|[执行器 Executor]↓+---------------------+| 运行程序 F(I)        |+---------------------+↓+------------------------+| 监控:崩溃?覆盖路径? |+------------------------+
组件作用
种子(seed)起始输入,通常为合法或示例输入
变异器(mutator)修改已有输入,生成变异输入
执行器(executor)执行目标程序,收集结果
监控器(monitor)监控是否崩溃、路径是否新颖
调度器(scheduler)决定 fuzz 哪个种子、多少次(如 AFLFast 的精髓)

分类:

类型描述工具示例
黑盒 Fuzzing不知道程序内部,仅靠输入和输出Radamsa
灰盒 Fuzzing插桩获取程序路径信息(最实用)AFL, libFuzzer
白盒 Fuzzing使用符号执行等复杂技术KLEE, SAGE

AFL

接下来这里先侧重讲一下AFL,项目源地址放这里:google/AFL: 美国模糊 lop - 一种面向安全的模糊测试器 — google/AFL: american fuzzy lop - a security-oriented fuzzer

AFL 是一款开源的灰盒模糊测试器(fuzzer),由 Michal Zalewski 开发,它的目标是:

自动向程序输入变异数据,
智能记录执行路径,
引导程序触发新路径或崩溃。

                +--------------------+
[1] 种子输入集  → | 变异引擎 Mutator   |+--------------------+|↓+--------------------+| 执行目标程序 ForkServer |+--------------------+|+--------------------+| 路径覆盖反馈 Feedback  |+--------------------+↓[2] 更新种子队列、记录崩溃、添加新路径

核心机制说明:

1、灰盒:插桩记录路径

AFL 会对被测试程序进行插桩编译(用 afl-gccafl-clang):

  • 编译后,AFL 能记录程序运行时走过的代码路径;
  • 它使用一个 共享内存 bitmap(路径覆盖图),记录执行边(edge coverage);
  • 每当 fuzz 出一个“新路径”,就会保存对应的输入。

AFL 的“灰盒”本质:程序内部状态可感知,但只看路径,不需要源码理解能力。

2、变异器(Mutator)
AFL 的输入变异策略分为多个阶段:

bit/byte 翻转(如翻转第 1 位)

字节插入/删除

整数覆盖(插入 0、1、INT_MAX)

Havoc 模式:随机多个操作叠加

Splice 模式:组合两个种子的部分数据(像交叉)

每个种子会根据这些策略生成大量变异输入,尝试触发不同路径或崩溃。

3、执行器(Executor)
AFL 使用一个名为 fork server 的技术,快速地对目标程序进行重复执行:

程序被加载后常驻内存;

每次新输入仅 fork 子进程执行;

大幅提升 fuzz 执行速度(数百倍)

4、路径反馈(Feedback)
核心是 AFL 的路径覆盖表(map[64k]):

每次执行都会将程序运行的代码路径(通过插桩)哈希后映射到这个表;

新的路径(bitmap 中新的位) → 保留对应输入 → 加入种子队列;

AFL 只保留那些“走新路径”的输入,剔除冗余样本。

5、种子调度器(Scheduler)
AFL 会根据每个种子的“重要性”分配不同的 fuzz 能量(次数):

路径稀有 → 分配更多 fuzz 次数;

高速种子优先(执行时间短);

后续 AFLFast 正是优化了这一步(低频路径优先、能量调度更合理)

6、崩溃检测与记录
每当程序运行崩溃(SIGSEGV、SIGABRT):

AFL 自动记录引发崩溃的输入样本;

保存在 crashes/ 文件夹;

可进一步用作漏洞验证或 PoC(Proof of Concept)

简单总结一下:

概念含义
插桩在程序中添加记录代码路径的语句
插桩编译自动为源代码添加监控逻辑再编译
AFL 的插桩编译器替代 gcc,为每个代码块自动插入路径记录逻辑
作用让 AFL 能检测“当前输入触发了新路径”

思考:原始的AFL中有GA嘛?

严格的说,是没有的。类似GA但是并非:

对比项遗传算法(GA)原始 AFL
是否有显式种群(population)?✅ 有✅ 有种子队列(输入集合)
是否使用适应度函数?✅ 明确定义适应度打分❌ 没有明确“评分函数”,仅用路径新颖性判断是否保留输入
是否进行父代选择?✅ 有选择算法(如轮盘赌、锦标赛)❌ 只用简单轮询 / 静态调度策略,AFLFast 才引入调度优化
是否有交叉操作?✅ 有明确 crossover 算子⚠️ 有类似行为(splice 模式),但不是系统性遗传操作
是否有变异操作?✅ 有突变算子✅ 丰富的 bit/byte 变异器
是否进化目标函数?✅ 明确的最优化目标⚠️ 模糊目标:尽量多探索路径,发现崩溃
是否有一代一代进化?✅ 明确的“代数”❌ 不分代,基于种子队列循环变异执行
终止条件?✅ 迭代轮数、误差阈值等❌ 只看时间/崩溃条件等外部限制

(搬运gpt)

为什么说AFL像GA?

GA 特性AFL 类比行为
个体(染色体)一个输入样本(种子)
种群种子队列(输入池)
突变(mutation)多种 bit/byte 变异策略
交叉(crossover)splice() 操作组合两个种子
适应度选择如果输入带来新路径 → 被保留
生存竞争新路径被选中,冗余路径淘汰

AFLFast

项目地址:https://github.com/mboehme/aflfast

AFLFast,它是对原始 AFL 的一个重要增强版本,主要目标是提高路径探索效率,加快发现漏洞的速度。

AFLFast(Coverage-based Greybox Fuzzing as Markov Chain) 是一种对 AFL 的优化版,它用更聪明的方式选择种子和分配能量,让 fuzz 更快地覆盖更多代码路径。

简单来说也就一句话:“不是所有种子都值得花一样多精力去 fuzz!”

原始AFL的问题:

默认的种子调度是轮询式,每个 seed 轮流 fuzz;

结果:

  • 高频路径被反复 fuzz,浪费时间
  • 低频路径(隐藏 bug 的地方)很少被探索到
  • 整体路径增长变慢,bug 发现效率低。

改进思想:

1. 马尔可夫链建模

AFLFast 把模糊测试过程抽象成马尔可夫链(Markov Chain)

  • 每个路径视为一个状态 π_i
  • 每次种子变异是从 π_i → π_j 的状态转移,转移概率为 p_ij
  • 目标是以最小代价探索所有状态(路径);
  • 因此应优先 fuzz 从未或少 fuzz 过的路径(低频路径 = 潜在新分支)。

2. 频率反比调度(Power Schedule)

根据路径频率分配“能量”(即变异次数):

  • 频率高 → 能量低(不再浪费时间);
  • 频率低 → 能量高(值得深度挖掘);
  • 多种调度策略被提出,例如:
策略说明
EXPLOIT优先 fuzz 覆盖更多新路径的种子
EXPLORE优先 fuzz 低频路径的种子
FAST路径频率越高,fuzz 次数指数级减少
COE混合策略(Covariance-based Exponential)

AFLFast 默认用的是 FAST 策略。


3. 搜索策略(Search Strategy)增强

AFLFast 修改了 choose_next() 逻辑:

  • 种子不是轮询选择,而是按优先级排序;
  • 优先选择:
    • 执行时间短的(加快迭代);
    • 路径深的(更可能通向新分支);
    • 低频路径对应的种子。

4. 能量调度公式示意(FAST 示例)

AFL 中的种子能量 = 变异次数,原始是固定值;

在 AFLFast 中:

energy=baseenergy/(1+freq(seed.path))energy = base_energy / (1 + freq(seed.path)) energy=baseenergy/(1+freq(seed.path))

或指数形式:

energy=baseenergy∗exp(−α∗freq)energy = base_energy * exp(-α * freq) energy=baseenergy∗exp(−α∗freq)

  • freq 表示种子对应路径在历史上被 fuzz 过的频率;
  • α 是可调参数(控制频率影响强度);

这样设计能确保新奇的路径被更充分地探索

   ┌──────────────────┐              ┌──────────────────┐│  AFL (baseline)  │              │    AFLFast       │└──────────────────┘              └──────────────────┘│                                  │[轮询选择种子]                    [基于路径频率调度种子]↓                                  ↓[固定能量分配]                   [能量与路径频率成反比]↓                                  ↓[路径覆盖慢]                     [路径覆盖快、bug 快速暴露]
思考:关于能量的问题:

fuzz 测试资源(时间、次数)应该花在谁身上更划算

一、抽象理解:什么是“能量”?

在 AFL / AFLFast 中,能量就是你给一个种子分配的变异次数(fuzz 次数)

也可以理解为:

能量 = 种子被选择后,被 fuzz 的强度(数量)

例如:

  • 给一个 seed 分配了 100 能量,就会对它变异生成 100 个输入;
  • 给另一个只有 10 能量 → 只生成 10 个变异输入。

所以,“谁的能量高”,谁就被 fuzz 得更深入、更频繁。

二、频率反比调度的核心思想

⚠️ 问题:如果你总是 fuzz 那些常见路径,探索不到新的代码分支,浪费资源。

解决思路:应该给 “探索价值大” 的种子更多能量,即:

触发“稀有路径”的种子 → 应该多 fuzz!
走“高频路径”的种子 → fuzz 少点!

三、公式抽象(以 AFLFast 的 FAST 模型为例)
image-20250716171022377

其中:

  • base_energy: 默认能量基准值
  • freq: 表示 seed 所对应路径的历史命中次数
  • α: 衰减系数
  • energy: 分配给该 seed 的变异次数

效果:

  • 路径频率 freq 越小 → energy 越大
  • 路径频率 freq 越高 → energy 越小

四、伪代码:

for seed in seed_queue:path_id = hash_path(seed)  # 路径标识freq = path_counter.get(path_id, 1)energy = BASE / freq       # 或 BASE * exp(-alpha * freq)for i in range(int(energy)):new_input = mutate(seed)run_target(new_input)update_path_counter()

关键点:

  • 每个路径有自己的访问频率计数;
  • seed 的路径频率越低,它对应的能量越高;
  • 动态更新 freq → 调整资源分配。

五、实际 fuzz 中哪些 seed 更“值得”投入能量?

✅ 1. 触发了新路径的种子(Rare Path Seed)

  • 对应的路径在 bitmap 中覆盖稀少;
  • fuzz 它更有可能“挖到新逻辑”。

👉 AFLFast 会优先调度这类种子 + 提高能量

✅ 2. 执行时间短的种子

  • 目标是单位时间内尝试更多变异;
  • 短种子 → 快执行 → 高性价比;

✅ 3. 路径深、复杂逻辑的种子

  • 触发路径较深层函数 → 更容易挖出逻辑漏洞;
  • 有时需要结合 coverage depth(路径深度)做调度;

✅ 4. 带有未初始化数据或边界输入的种子

  • 比如触发边界检查或稀有错误分支的输入;
  • 虽然路径频率可能还不低,但 fuzz 潜力大;
  • 在一些高级调度策略中也会被优先处理。
http://www.lryc.cn/news/602385.html

相关文章:

  • 全新AI工具小程序源码 全开源
  • 时序数据库选型指南:工业大数据场景下基于Apache IoTDB技术价值与实践路径
  • Verilog简易的按键消抖模块
  • css 实现虚线效果的多种方式
  • Kubernetes 存储入门
  • 【自动化运维神器Ansible】Ansible常用模块之unarchive模块详解
  • 快速入门Linux操作系统(二)
  • 腾讯AI IDE
  • 《红色脉络:一部PLMN在中国的演进史诗 (1G-6G)》 第3篇 | 2G:GSM一统江湖?——移动、联通的“分家”与双轨并行
  • windows平台计划任务批处理实现定时任务
  • 零基础学习性能测试第九章:全链路追踪-系统中间件节点监控
  • DDD领域驱动中瘦模型与富态模型的核心区别
  • FastGPT本地构建工作流高级编排(最新4.11.0)
  • 火狐浏览器中国特供版关闭,如何下载 Firefox 国际版?如何备份数据?
  • App Inventor 2 使用 MaterialIcons 图标字体,快捷展示专业图标
  • NAS远程访问新解法:OMV与cpolar的技术协同价值
  • CentOS7 安装和配置教程
  • nvim tagbar安装
  • VUE2 学习笔记11 脚手架
  • 架构实战——互联网架构模板(“存储层”技术)
  • 黑马商城微服务-下
  • 云服务器以域名形式访问机房Kubernetes集群服务之解决方案
  • 国产化PDF处理控件Spire.PDF教程:Java 提取 PDF 图片,高质量提取与图片过滤技巧
  • 【设计模式】状态模式 (状态对象(Objects for States))
  • Spring AI 1.0 提供简单的 AI 系统和服务
  • claude code
  • LeetCode 85. 最大矩形
  • 剑指“CPU飙高”问题
  • FFmpeg 安装与使用
  • kafka开启Kerberos使用方式