news 2026/9/26 7:22:06

渗透测试Fuzz实战:从底层逻辑到环境搭建与进阶玩法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
渗透测试Fuzz实战:从底层逻辑到环境搭建与进阶玩法

1. 渗透测试与Fuzz的底层逻辑拆解

1.1 从一次真实项目说起:为什么要用Fuzz

去年接手一个物联网设备的固件审计项目,设备通过MQTT协议与云端通信,固件里跑着一个用C写的协议解析模块。手工构造了几十个畸形报文,测了两天,只发现了两个边界溢出,效率低得让人抓狂。后来换了个思路,写了个简单的变异脚本,把协议报文里的长度字段、类型字段、载荷内容做随机翻转和替换,一晚上跑了十几万条用例,第二天早上看日志,直接定位到三个可复现的崩溃点。这就是Fuzz的威力——它不靠人的直觉去猜漏洞在哪,而是用自动化的方式去“撞”,撞到程序扛不住的地方,漏洞就浮出来了。

很多人第一次听到“渗透测试fuzz”这个词,会把它和“漏洞扫描”混为一谈。其实两者差别很大。漏洞扫描器像是拿着一份已知问题的清单去逐条核对,比如“你有没有装这个版本的组件”“这个端口有没有开弱口令”,它依赖的是已知特征库。而Fuzz是主动向目标程序输入大量非预期的数据,观察程序是否出现异常行为——崩溃、挂起、内存泄漏、断言失败等等。它不预设漏洞长什么样,而是通过制造异常来暴露问题。在渗透测试的语境下,Fuzz通常被归类为“动态测试”的一种,属于主动探测手段,和静态代码审计形成互补。

1.2 Fuzz在渗透测试流程中的位置

一个完整的渗透测试流程,大致会经过信息收集、威胁建模、漏洞发现、漏洞利用、后渗透、报告输出这几个阶段。Fuzz主要活跃在“漏洞发现”这个环节,但它并不是孤立存在的。信息收集阶段拿到的协议格式、接口定义、文件格式样本,都是Fuzz的输入素材;威胁建模阶段确定的重点目标,决定了Fuzz的资源往哪里倾斜;而Fuzz跑出来的崩溃样本,又需要漏洞利用阶段去进一步验证是否可利用、危害等级多高。

我习惯把Fuzz看作渗透测试里的“放大器”。手工测试能覆盖的输入空间非常有限,一个人的精力再旺盛,一天能构造并验证的用例也就几百条。但Fuzz可以把用例数量拉到百万甚至千万级别,把那些藏在深层逻辑里的边界条件、类型混淆、状态机异常统统翻出来。尤其是在面对闭源目标、没有源码可审计的时候,Fuzz几乎是发现内存破坏类漏洞最有效的手段之一。

1.3 常见Fuzz类型与适用场景对照

Fuzz的分类方式很多,按输入生成策略分,有基于变异的、基于生成的、基于语法的;按目标类型分,有文件格式Fuzz、网络协议Fuzz、API接口Fuzz、内核系统调用Fuzz等等。下面这张表是我在实际项目中总结的对照关系,方便你快速判断什么场景该用什么路子。

Fuzz类型核心思路典型目标工具举例上手难度
基于变异对已有样本做随机翻转、截断、拼接文件解析器、图片解码器AFL、honggfuzz低
基于生成按语法规则从零构造输入协议实现、编译器Peach、boofuzz中
基于覆盖率根据代码覆盖反馈引导变异方向二进制程序、内核AFL、libFuzzer中高
基于协议状态按状态机流转构造多阶段交互网络服务、车载中控boofuzz、自定义脚本高
基于API调用序列按接口依赖关系编排调用链Web API、移动应用RESTler、自定义框架中

选择哪种类型,取决于你手头有什么素材、目标是什么形态、你能投入多少时间。如果目标是一个处理图片的库,你手上有大量正常图片样本,那基于变异的AFL就是最快出结果的路子。如果目标是车载中控的CAN总线通信,协议规范你拿到了,但样本很少,那就得走基于生成的路线,用boofuzz之类的框架把协议状态机描述出来。

2. Fuzz核心机制与实操要点解析

2.1 变异引擎:Fuzz的心脏怎么跳

变异引擎决定了Fuzz能不能在有限时间内触达更深的代码路径。最朴素的变异就是随机翻转比特位,但这种方式效率极低,因为大部分翻转都会让输入在解析早期就被拒绝,根本走不到深层逻辑。真正有效的变异引擎会结合多种策略:位翻转、字节替换、算术增减、块复制、块删除、字典插入等等。AFL在这方面做得非常成熟,它维护了一个变异调度队列,会根据每个变异操作的历史命中率动态调整权重。

我在实际使用中有一个体会:字典的质量直接决定Fuzz的命中率。比如目标是一个JSON解析器,你在字典里放上{、}、"、:、null、true、false这些token,变异出来的输入就更容易通过语法检查,进入语义处理阶段。如果目标是一个二进制协议,字典里放上常见的魔数、长度字段的边界值(0、1、255、256、65535、65536),效果会立竿见影。AFL支持通过-x参数加载字典文件,格式就是每行一个token,支持\x转义。

# AFL字典文件示例 dict.txt "GET" "POST" "HTTP/1.1" "\x00\x00\x00\x00" "\xff\xff\xff\xff" "Content-Length:"

2.2 覆盖率反馈:怎么知道Fuzz有没有“跑偏”

覆盖率反馈是现代化Fuzz工具的核心竞争力。简单说,工具在编译目标程序时插桩,记录每次执行走了哪些代码分支,然后优先保留那些触发了新分支的输入样本,用它们作为下一轮变异的种子。这样Fuzz就会像爬山一样,一步步往代码更深处走,而不是在浅层逻辑里原地打转。

AFL的插桩方式有两种:编译时插桩(afl-gcc、afl-clang-fast)和二进制插桩(QEMU模式)。编译时插桩效率高,但需要源码;二进制插桩不需要源码,但性能损耗大,通常是编译时插桩的5到10倍。如果目标程序有源码,强烈建议用编译时插桩。如果只有二进制,QEMU模式也能用,但要有心理准备,跑得慢。

注意:覆盖率反馈不是万能的。如果目标程序有大量并行分支或者状态依赖,单纯的边覆盖率可能引导Fuzz走进死胡同。这时候可以考虑用上下文敏感的插桩,或者结合符号执行来突破。

2.3 种子样本:垃圾进,垃圾出

种子样本的质量对Fuzz效率的影响,怎么强调都不为过。一个精简、多样、覆盖了主要功能路径的种子集,能让Fuzz在几小时内就触达深层逻辑;而一堆冗余、格式错误的种子,只会让Fuzz在入口处反复碰壁。

我通常这样准备种子集:先从目标程序的文档、测试用例、公开样本库里收集一批正常输入;然后用afl-cmin做去重,把那些触发相同覆盖率的样本剔除掉;再用afl-tmin对每个样本做最小化,在保持覆盖率不变的前提下把文件体积压到最小。经过这两步处理,种子集通常能从几百个缩减到几十个,但Fuzz效率反而更高。

# 种子去重 afl-cmin -i seeds_raw -o seeds_min -- ./target @@ # 单样本最小化 afl-tmin -i seeds_min/input1 -o seeds_tmin/input1 -- ./target @@

2.4 崩溃去重与分类:别被假阳性淹没

Fuzz跑起来之后,最让人头疼的不是没崩溃,而是崩溃太多。同一个漏洞可能触发几十上百次崩溃,如果不去重,你会在日志里看到一片红,根本分不清哪些是真正独立的漏洞。AFL自带了一个afl-collect脚本,可以根据崩溃时的执行路径做去重。更精细的做法是用exploitable这个GDB插件,它会自动分析崩溃的可利用性,给出“EXPLOITABLE”“PROBABLY_EXPLOITABLE”“NOT_EXPLOITABLE”等评级。

我在项目里通常会搭一个简单的流水线:Fuzz跑出崩溃样本后,先用afl-collect做初步去重,然后用exploitable做可利用性分级,最后人工复核那些评级高的样本。这样能把精力集中在真正有价值的崩溃上,而不是被一堆空指针解引用浪费时间。

3. 从零搭建一个Fuzz实战环境

3.1 环境准备与工具链安装

这里以AFL为例,走一遍完整的搭建流程。操作系统用Ubuntu 22.04,这是目前兼容性最好的选择。先装基础依赖:

sudo apt update sudo apt install -y build-essential clang llvm gdb python3-pip

然后从源码编译安装AFL:

wget https://github.com/google/AFL/archive/refs/tags/v2.57b.tar.gz tar -xzf v2.57b.tar.gz cd AFL-2.57b make sudo make install

安装完成后,afl-fuzz、afl-gcc、afl-clang-fast这些命令应该都在PATH里了。可以用afl-fuzz -h验证一下。

提示:如果目标程序是64位且需要大量内存,记得调整系统的核心转储模式。echo core > /proc/sys/kernel/core_pattern可以避免AFL因为核心转储问题报错。

3.2 目标程序编译与插桩

假设我们要Fuzz一个简单的C程序,它从文件读取数据并解析。先用afl-clang-fast编译:

afl-clang-fast -o target target.c

编译完成后,可以用afl-showmap验证插桩是否生效:

afl-showmap -o /dev/null -- ./target input_sample

如果输出里有-- Program output begins --之类的信息,说明插桩成功了。

3.3 种子准备与Fuzz启动

准备一个种子目录,放几个正常输入样本:

mkdir -p seeds cp sample1.bin seeds/ cp sample2.bin seeds/

然后启动Fuzz:

afl-fuzz -i seeds -o findings -- ./target @@

-i指定种子目录,-o指定输出目录,@@是AFL替换输入文件路径的占位符。启动后你会看到一个彩色的终端界面,显示执行速度、覆盖率、崩溃数量等信息。

3.4 结果分析与崩溃复现

Fuzz跑了一段时间后,findings目录下会有crashes、hangs、queue等子目录。crashes里就是触发崩溃的样本。用GDB复现:

gdb --args ./target findings/crashes/id:000000,sig:11,src:000000,op:havoc,rep:4

在GDB里跑起来,看崩溃时的调用栈,定位到具体的代码行。如果崩溃原因是内存破坏,还需要进一步分析是否可被利用,比如能否控制EIP、能否写入任意地址等。

3.5 持续化与并行化:让Fuzz跑得更久更广

单实例Fuzz的效率有限,AFL支持多实例并行。主实例用-M,从实例用-S:

# 主实例 afl-fuzz -i seeds -o findings -M master -- ./target @@ # 从实例 afl-fuzz -i seeds -o findings -S slave1 -- ./target @@

多个实例会共享findings目录,互相同步新发现的种子。如果机器核多,可以开十几个从实例,把CPU吃满。另外,AFL支持断点续跑,如果中途停了,用-i-参数可以恢复:

afl-fuzz -i- -o findings -- ./target @@

4. 常见问题与排查技巧实录

4.1 Fuzz跑不起来?先查这几个地方

新手最常遇到的问题就是AFL启动后报错退出。根据我的经验,九成以上的问题出在三个地方:核心转储模式没设对、CPU频率调节器没关、目标程序没有插桩。核心转储的问题前面提过了,echo core > /proc/sys/kernel/core_pattern能解决。CPU频率调节器可以用cpupower frequency-set -g performance关掉。插桩问题可以用afl-showmap验证,如果输出为空,说明编译时没插桩成功,检查一下是不是用了普通的gcc而不是afl-gcc。

还有一个坑是目标程序需要从标准输入读取数据,但AFL默认是用文件参数。这时候可以用@@占位符,或者用-f参数指定一个固定文件路径。如果目标程序需要网络交互,那就得用afl-fuzz的-n模式(非插桩模式),或者换用支持网络协议的Fuzz框架。

4.2 覆盖率上不去?试试这几招

Fuzz跑了一段时间,覆盖率曲线变平了,说明遇到了瓶颈。这时候可以尝试几个方向:一是丰富字典,把目标程序里出现的魔法字符串、关键字都加进去;二是调整变异策略,AFL的-p参数可以切换调度策略,explore偏向探索新路径,fast偏向快速变异,coe偏向稀有路径;三是换用更强大的插桩方式,比如afl-clang-lto,它比afl-clang-fast的插桩粒度更细,能发现更多边。

如果目标程序有复杂的校验逻辑,比如CRC校验、加密签名,那Fuzz很难绕过。这时候要么用符号执行辅助求解,要么直接patch掉校验逻辑,让Fuzz能进入深层代码。patch的时候要注意,别把漏洞也patch没了。

4.3 崩溃太多怎么筛?分级处理

前面提过用exploitable做分级,这里补充一下具体用法。先装GDB插件:

pip3 install exploitable

然后在GDB里加载:

gdb -ex "source /usr/share/gdb/auto-load/usr/lib/exploitable/exploitable.py" ./target

跑崩溃样本,exploitable会自动输出评级。评级为EXPLOITABLE的优先看,PROBABLY_EXPLOITABLE的次之,NOT_EXPLOITABLE的基本可以忽略。但要注意,exploitable的判断基于启发式规则,不是百分之百准确,最终还是要人工确认。

4.4 常见问题速查表

问题现象可能原因排查方法解决方案
AFL启动即退出核心转储模式不对查看报错信息echo core > /proc/sys/kernel/core_pattern
覆盖率长期不变种子质量差或字典缺失检查queue目录补充字典、优化种子
执行速度极慢用了QEMU模式或目标程序太重看exec speed改用编译时插桩、精简目标
崩溃全是同一类去重没做好看崩溃地址用afl-collect去重
目标程序需要网络AFL不支持网络输入看目标行为用boofuzz或自定义harness
Fuzz跑一段时间后卡死内存泄漏或死循环看hangs目录设置超时参数-t

4.5 几个让我印象深刻的踩坑经历

有一次Fuzz一个协议解析器,跑了一整天一个崩溃都没有。后来发现是种子样本里的长度字段和实际载荷长度不匹配,程序在解析早期就返回错误了,根本没走到深层逻辑。把种子样本修正后,两小时就出了三个崩溃。这件事让我明白,种子样本的“合法性”比“数量”重要得多。

还有一次Fuzz一个车载中控的蓝牙协议栈,用AFL跑了很久没结果。后来换了个思路,用boofuzz把蓝牙的配对、连接、数据传输这几个状态描述出来,按状态机流转构造输入,很快就发现了配对过程中的一个缓冲区溢出。这让我意识到,对于有状态协议,基于生成的Fuzz比基于变异的Fuzz更有效。

另外,Fuzz环境的稳定性很重要。我有一次跑Fuzz跑了三天,结果机器重启了,所有进度都没了。后来学乖了,用screen或tmux把Fuzz会话挂起来,再配合-i-断点续跑,再也不怕意外中断了。

5. Fuzz在渗透测试中的进阶玩法

5.1 结合AI的Fuzz:让变异更聪明

传统的Fuzz变异是盲目的,虽然覆盖率反馈能引导方向,但效率仍然有限。最近几年,用机器学习模型来指导Fuzz变异成为一个热门方向。核心思路是训练一个模型,让它学习什么样的输入更可能触发新路径或崩溃,然后用模型来生成变异。比如用RNN或Transformer对种子样本建模,生成符合语法的新样本;或者用强化学习来优化变异策略的选择。

我在一个Web API的Fuzz项目里试过用简单的序列模型来生成JSON请求体,效果比纯随机变异好不少,尤其是对于嵌套结构比较深的接口。但这类方法也有局限,模型训练需要大量样本,而且泛化能力不一定好。目前来看,AI辅助Fuzz还处于探索阶段,适合作为传统方法的补充,而不是替代。

5.2 内核Fuzz:从系统调用入手

内核Fuzz是Fuzz领域的一个硬骨头,也是高价值目标。内核的输入面主要是系统调用,但系统调用数量多、参数复杂、状态依赖强,直接Fuzz效率很低。目前比较成熟的方案是syzkaller,它用一套描述语言把系统调用的参数类型、依赖关系描述出来,然后自动生成调用序列。syzkaller还支持覆盖率反馈,能引导Fuzz往未覆盖的内核代码走。

跑syzkaller需要一台独立的机器或者虚拟机,因为它会把内核跑崩。配置过程比较繁琐,需要编译内核、配置SSH、设置镜像等等。但一旦跑起来,它能发现很多深层次的内核漏洞。我在一个项目里用syzkaller跑了两周,发现了四个内核崩溃,其中两个是可提权的。

5.3 Web Fuzz:接口层面的自动化测试

Web应用的Fuzz和二进制程序的Fuzz思路不太一样。Web接口的输入是HTTP请求,参数是结构化的(JSON、表单、XML),所以基于生成的Fuzz更合适。可以用OpenAPI/Swagger文档自动生成请求,然后对参数值做变异。RESTler是微软开源的一个工具,它能根据OpenAPI文档自动推断接口依赖关系,生成有意义的调用序列,然后对参数做Fuzz。

我在一个微服务架构的项目里用RESTler跑过一轮,发现了好几个参数校验缺失的问题,比如某个接口对数组长度没有限制,传一个超大数组直接导致服务OOM。这类问题手工测试很容易漏掉,但Fuzz能稳定复现。

5.4 车载渗透测试中的Fuzz实践

车载系统的Fuzz和传统IT系统有很大不同。车载网络以CAN总线为主,报文格式固定,但状态机复杂。而且车载ECU对实时性要求高,Fuzz的时候如果发太多报文,可能导致总线拥塞,影响车辆正常功能。所以车载Fuzz通常要在台架上做,不能直接在实车上跑。

我参与过一个车载中控的渗透测试项目,目标是中控与仪表之间的通信。我们用CANoe模拟仪表发送报文,然后用自定义脚本对中控的响应做变异。重点测了诊断服务(UDS)的几个关键服务,比如0x27安全访问、0x2E写数据、0x31例程控制。在0x2E服务里发现了一个长度校验漏洞,传超长数据会导致中控重启。这个漏洞如果被利用,可以在车辆行驶中让中控黑屏,影响驾驶安全。

5.5 Fuzz与渗透测试智能体的结合

最近“渗透测试智能体”这个概念很火,核心思路是用大模型来编排渗透测试流程,自动决定下一步做什么。Fuzz可以作为智能体的一个工具,智能体根据当前掌握的信息决定要不要启动Fuzz、Fuzz哪个目标、用什么策略。比如智能体发现了一个文件上传接口,它可以自动下载一些样本文件,启动Fuzz,分析崩溃结果,然后决定是否进一步利用。

目前这类智能体还处于早期阶段,实际项目里更多是辅助角色,比如帮渗透测试工程师生成Fuzz脚本、分析崩溃日志、推荐变异策略。但方向是明确的,未来Fuzz的自动化程度会越来越高,人的角色会更多转向策略制定和结果研判。

6. 学习路线与资源推荐

6.1 从零到一的学习路径

如果你刚接触Fuzz,我建议按这个顺序来:先理解Fuzz的基本概念和分类,知道什么场景用什么工具;然后动手跑通一个最简单的例子,比如用AFL Fuzz一个开源的图片解析库;接着学习如何编写harness,也就是把目标程序包装成Fuzz能调用的形式;再深入学覆盖率反馈的原理和插桩方式;最后根据你的工作方向,选择专攻二进制Fuzz、协议Fuzz还是Web Fuzz。

整个过程大概需要两到三个月,前提是每天能投入一两个小时。不要一上来就啃syzkaller或者AFL的源码,那样容易劝退。先从用起来开始,遇到问题再往深里挖。

6.2 值得反复看的资料

AFL的官方文档和源码是必看的,尤其是afl-fuzz.c里的变异调度逻辑,看懂了会对Fuzz有更深的理解。libFuzzer的文档也值得看,它和AFL的思路不同,是基于进程内的Fuzz,适合Fuzz库函数。boofuzz的文档里有大量协议Fuzz的例子,适合做网络协议方向的人。syzkaller的文档比较硬核,但如果你想做内核Fuzz,这是绕不过去的。

另外,Google的fuzzing仓库里有很多教程和示例,质量很高。Fuzzing 101这个系列视频也值得看,它用AFL和libFuzzer演示了十几个真实目标的Fuzz过程,跟着做一遍收获很大。

6.3 面试中常被问到的Fuzz问题

渗透测试岗位的面试里,Fuzz相关的问题通常不会问得太深,但有几个点是高频的:Fuzz和漏洞扫描的区别是什么;AFL的覆盖率反馈是怎么实现的;种子样本怎么准备;崩溃怎么去重和分级;有没有实际用Fuzz发现过漏洞。如果你能结合一个具体项目讲清楚从种子准备到崩溃复现的完整流程,基本就能过关。

如果面试的是更偏研究性质的岗位,可能会问得更细,比如AFL的变异调度算法、QEMU模式的工作原理、如何绕过Fuzz中的校验逻辑等等。这些问题就需要你对Fuzz的内部机制有比较深的理解了。

6.4 我个人的一些经验之谈

Fuzz这件事,工具只是手段,核心还是对目标的理解。你越了解目标程序的输入格式、处理逻辑、状态流转,就越能设计出高效的Fuzz方案。我见过很多人一上来就afl-fuzz跑起来,然后就不管了,跑了一周没结果就放弃了。其实Fuzz是一个需要不断调优的过程,种子要调、字典要调、变异策略要调、超时参数要调。每一次调整都可能带来效率的成倍提升。

另外,Fuzz发现的崩溃不等于漏洞。很多崩溃是不可利用的,比如空指针解引用、断言失败。真正有价值的崩溃是那些能控制程序执行流的,比如栈溢出、堆溢出、类型混淆。所以Fuzz之后的崩溃分析同样重要,甚至比Fuzz本身更重要。学会用GDB、学会看汇编、学会判断可利用性,这些技能和Fuzz本身一样值得投入时间。

最后,Fuzz是一个需要耐心的活。有时候跑几天没结果,有时候一晚上出好几个崩溃。保持耐心,持续优化,结果总会来的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 7:21:46

深度学习医学影像分割实战:U-Net选型、数据预处理与训练避坑

简介:这是一份面向医学影像分割入门与课程设计的深度学习实战资源,基于Python语言开发,以U-Net、V-Net等经典卷积网络为核心,覆盖从医学影像数据准备、模型搭建、训练优化到批量预测与结果查看的完整流程,适用于课程作…

作者头像 李华
网站建设 2026/9/26 7:21:42

把AI当结对程序员:五个Prompt模板搞定代码生成、排错与审查

1. 先聊清楚:我把 AI 当结对程序员,而不是搜索引擎我先说个结论:AI 写代码这件事,用得好的人跟用得差的人,差距根本不在模型选哪个,而在输入方式。大部分人把 AI 当搜索引擎用——"帮我写个登录功能&q…

作者头像 李华
网站建设 2026/9/26 7:20:59

Spring Boot项目创建的5种方式:从Initializr到手工Maven全解析

我平时最常被问到的 Spring Boot 项目创建方式,不是“SpingBoot 怎么写接口”,而是“项目到底怎么建出来”。尤其当你同时要开新微服务、给同事搭演示工程、或者准备自动化批量样板代码的时候,创建方式选对了,能省下的时间是以小时…

作者头像 李华
网站建设 2026/9/26 7:19:36

AI获客系统不占本地配置?深度实测云端SaaS与本地部署的成本真相

1. "不占本地配置"这句话的三种正确解读——先别急着下单,搞懂它省略了什么前阵子一位做制造业的朋友被某AI获客系统的销售缠了一个星期,对方反复强调"这套系统完全不用买服务器,你们现有电脑就能跑,真不占本地配置…

作者头像 李华
网站建设 2026/9/26 7:19:11

SSVEP空间滤波全解析:从CCA到TRCA的脑机接口实战指南

简介:面向脑机接口与脑电信号处理研究者的SSVEP空间滤波算法包,集中实现多种典型相关分析及其变体。资源覆盖标准典型相关分析、扩展典型相关分析、多重刺激典型相关分析、多通道典型相关分析、L1正则化多通道典型相关分析以及多数据集典型相关分析等主流…

作者头像 李华
网站建设 2026/9/26 7:18:54

Java开发者转型AI Agent工程师:Spring AI进阶路线与15个实战方向

1. 从Java开发者到AI Agent工程师:这条路到底该怎么走这两年跟不少做Java的朋友聊过,大家普遍有个焦虑:AI这波浪潮来了,Python阵营的人好像天然占优势,写Java的是不是要被落下了?我一开始也有这个担心&…

作者头像 李华