Modbus Fuzzing Lab
1. 前言
针对Modbus TCP协议中的03功能码-读取保存寄存器开发的模糊测试教学实验平台,是一个非常非常非常简陋的初级版本,用于课程作业
2.Modbus-TCP 协议深度解析
2.1 协议体系结构
2.1.1 OSI 7层模型定位
Modbus-TCP 协议是工业自动化领域的一种应用层报文传输协议,它是将传统的 Modbus 序列通信协议通过 TCP/IP 网络进行封装的产物;Modbus-TCP 严格遵循 OSI 7 层模型,其运行在 TCP/IP 协议栈上,属于应用层协议。
与运行在物理层串口 RS-485/232 的 Modbus-RTU 不同,Modbus-TCP 剥离了 RTU 报文中的循环冗余校验校验字段,因为 TCP 协议与以太网链路层已经提供了完善的差错控制机制;并将 Modbus 的应用数据单元 ADU 封装在标准的 TCP/IP 协议栈中。

2.1.2 Client/Server 通信模型
Modbus-TCP 改变了传统串行通信中的主从概念(Master/Slave),转而采用以太网通用的客户端/服务器模型(Client/Server):
- 客户端:发起请求,主动建立 TCP 连接,并发送包含功能码和地址的请求报文;在本次实验中,客户端为基于 Python Boofuzz 编写的 fuzz 测试脚本
- 服务器:Modbus-TCP 服务器默认监听在 502 端口,负责接收请求、解析报文逻辑并返回响应或执行相应的输出控制;在本次实验中,Modbus-TCP 服务器为用 Node.js 编写的模拟器
这种模型的优势在于支持多连接并发,使得一个控制中心可以同时访问数十台甚至上百台 PLC 节点,但也增加了协议解析器在处理高并发畸形报文时的压力
Modbus-TCP 的核心在于 ADU 的构成,由两部分组成:MBAP 报文头与协议数据单元 PDU

2.2 MBAP 报文头格式
Modbus-TCP 协议的 MBAP 报文头固定 7 字节长,一共有 4 个字段:
2.2.1 事务标识符 (Transaction Identifier)
- 长度:2 字节
- 作用:用于标识每个 Modbus-TCP 请求和响应事务,允许客户端和服务器能够将请求和响应进行配对,确保请求和响应的正确匹配。每次发起请求时,客户端会设置一个唯一的事务标识符,而服务器在响应时会返回相同的事务标识符。
2.2.2 协议标识符 (Protocol Identifier)
- 长度:2 字节
- 作用 :用于标识 Modbus 协议的版本和类型。对于 Modbus-TCP 协议,协议标识符的值通常为
00 00
2.2.3 长度 (Length)
- 长度:2 字节
- 作用:指示后续数据的字节数,通常是数据部分的长度。该字段的值包括从 Unit ID 字段开始,直至整个报文的末尾数据的长度。
2.2.4 单元标识符 (Unit ID)
- 长度:1 字节
- 作用:单元标识符用于标识 Modbus 设备的具体单元或节点。在 Modbus-TCP 协议中,单元标识符通常用于识别请求数据的目标设备,尤其在多设备环境中,能确保数据被送到正确的目标设备。
2.3 PDU 格式
Modbus-TCP 协议的 PDU 按照字段可分为:
- 功能码 (Function Data)
- 长度:1 字节
- 作用:指示 Modbus 设备执行某个特定的操作。
- 常见功能码 :
0x01:读取线圈状态0x02:读取离散输入状态0x03:读取保持寄存器0x04:读取输入寄存器0x05:强制单个线圈0x06:强制单个寄存器0x0F:强制多个线圈0x10:预置多个寄存器
- 数据(以 0x03 功能码为例)
- 对于请求报文 :
- 长度:4 字节
- 内容:起始寄存器地址与寄存器数量各占 2 字节
- 对于响应报文 :
- 长度:不固定
- 内容 :
- 字节数:表示在这之后有多少字节的数据
- 读取值:寄存器中的值(高位 1 字节,低位 1 字节)
- 对于请求报文 :

2.4 字节序与数据处理
2.4.1 Modbus-TCP 所使用的字节序
Modbus-TCP 协议严格规定采用大端序进行数据传输;即数据的高位字节存储在低地址内存中,而低位字节存储高地址内存中。
Node.js 模拟器端的处理 :在模拟器实现代码中,利用 Node.js 原生的 Buffer 对象进行二进制解析。代码中使用 readUInt16BE() 方法,其中 "BE" 后缀明确代表大端序读取。
实现逻辑 :const declaredLen = data.readUInt16BE(4); 该行代码确保了从报文第 5、6 字节中正确提取出 Length 字段的数值。
Python Boofuzz 端的处理 :在 fuzzer.py 建模中,通过 s_word 原语定义 16 位字段,使用 endian='>' 参数来指定大端序
3. 模糊测试技术原理
3.1 模糊测试技术概述
模糊测试(Fuzzing)是一种自动化的软件漏洞挖掘技术,其核心思想是向目标系统输入大量精心构造的、非预期的畸形数据,并实时监控目标系统的运行状态(如崩溃、挂起或异常响应),从而发现潜在的安全性缺陷。
在工控安全领域,模糊测试被广泛用于协议栈的健壮性审计。相比于传统的静态代码审计和渗透测试,模糊测试具有自动化程度高、黑盒测试有效以及能够发现深层逻辑漏洞的优势。
3.2 模糊测试分类
按照数据的生成方式,可分为:
- 生成式:根据协议标准从零构造报文。本实验采用此种方式,通过 Boofuzz 建立协议模型。
- 变异式:在捕获的合法报文基础上进行位翻转或数据截断。
按照对目标的知识掌握程度,可分为:
- 黑盒模糊测试:不了解目标内部实现,仅关注输入与输出。本实验对从站模拟器进行的是此类测试。
- 灰盒/白盒模糊测试:利用代码覆盖率反馈引导变异,如 AFL 框架。
4. 基于模糊测试的Modbus-TCP协议健壮性检测系统

技术架构:
- 前端:HTML5+js
- 后端:Node.js
- 模糊测试:python3.9+Boofuzz
部署
Node.js 环境
- 安装Node.js:建议使用
version >= 18.x,实验使用v22.14.0 - 初始化依赖:
在项目根目录下执行:
shell
npm init -y
npm install express socket.io
- 启动服务:
shell
node monitor.js
此时服务将运行在http://localhost:3000
Python 环境
shell
git clone https://github.com/jtpereyda/boofuzz.git
- 创建conda虚拟环境
推荐:使用miniconda,占用空间小且完全够用
shell
conda create --name <conda_envs_name> python=3.9 -y
- 安装必要的库
python
# reqiurements.txt
argparse
boofuzz
shell
pip install -r requirements.txt
-
运行
- 提供运行参数:
shell--mode:[ "length", # 针对MBAP头的Length字段 "func", # 针对功能码字段 "payload", #针对超长载荷 "format", "bound", # 针对边界值 "all" # 全部fuzzing ] --sleep: 发送数据包的时间间隔,便于观察数据包 --ip: 目标ip --port: 目标端口
shell
python fuzzing.py --mode length --ip 127.0.0.1 --port 5020 --sleep 2