Modbus 协议
概述
Modbus 是一种广泛应用于工业自动化领域的串行通信协议。它由 Modicon 公司(现属施耐德电气)于 1979 年为其可编程逻辑控制器(PLC)通信而开发,如今已成为工业电子设备之间最常用的连接方式和事实上的业界标准。
Modbus 之所以被广泛采用,主要得益于它是公开发表且无版权要求的开放协议,用户可以免费使用;同时其消息格式简单明了,易于实现、部署和维护。
从 OSI 模型来看,Modbus 是一个应用层(第7层)报文传输协议,它定义了与底层物理网络无关的协议数据单元(PDU),可以运行在不同类型的总线或网络上。
核心通信模型:主从(客户端/服务器)架构
Modbus 采用 主从(Master/Slave) 或称客户端/服务器(Client/Server) 的通信模式
主设备(Master/Client):负责发起所有通信请求,通常是人机界面(HMI)、SCADA 系统、PLC 或工控机
从设备(Slave/Server):被动接收并响应主设备的请求,通常是传感器、PLC、可编程自动化控制器或执行器等
通信过程遵循 请求-响应 机制:主设备发送请求帧,从设备处理后返回响应帧。从设备之间不能直接通信。在串行网络中,只有主节点可以发起命令;在以太网上虽然任何设备都能发送命令,但通常也只有一个主节点
每个从设备都有唯一的地址(1-247),主设备通过地址来指定通信对象。地址 0 为广播地址,所有从设备都会接收并执行但不返回响应。一个 Modbus 网络理论上最多可支持 247 个从设备
三种主要协议变体
Modbus 协议有三种主要的通信方式,适应不同的物理层和网络环境:
| 协议变体 | 物理层/网络 | 特点 |
|---|---|---|
| Modbus RTU | 串行通信(RS-232 / RS-485 / RS-422) | 二进制格式,数据帧紧凑高效;使用 CRC(循环冗余校验) 进行错误检测;是工业现场最常用的版本 |
| Modbus ASCII | 串行通信(RS-232 / RS-485 / RS-422) | 每个字节以两个 ASCII 字符表示,人类可读但数据冗余较多;使用 LRC(纵向冗余校验);效率低于 RTU,但在某些环境下更可靠 |
| Modbus TCP/IP | 以太网(TCP/IP) | 基于 TCP/IP 协议,使用标准端口 502;无需校验和计算;适合远程访问和现代网络应用 |
数据模型与功能码
四种数据类型
Modbus 将可访问的数据组织在四个独立的地址空间中:
| 数据区 | 数据类型 | 访问权限 |
|---|---|---|
| 线圈(Coils) | 布尔(1位) | 可读可写 |
| 离散量输入(Discrete Inputs) | 布尔(1位) | 只读 |
| 保持寄存器(Holding Registers) | 16位无符号整型 | 可读可写 |
| 输入寄存器(Input Registers) | 16位无符号整型 | 只读 |
每个数据区最多可包含 65,536 个元素(地址范围 0 至 65,535)
功能码
功能码(Function Code) 是 Modbus 协议的核心,它告诉从设备要执行什么操作。常见功能码包括:
| 功能码 | 操作 |
|---|---|
| 01 | 读取线圈 |
| 02 | 读取离散量输入 |
| 03 | 读取保持寄存器 |
| 04 | 读取输入寄存器 |
| 05 | 写入单个线圈 |
| 06 | 写入单个寄存器 |
| 15 | 写入多个线圈 |
| 16 | 写入多个寄存器 |
消息帧结构
一个 Modbus RTU 请求帧由以下部分组成:
| 字段 | 长度 | 说明 |
|---|---|---|
| 地址码 | 1 字节 | 从设备地址(1-247) |
| 功能码 | 1 字节 | 指定操作类型 |
| 数据区 | 可变长度 | 具体的操作数据(如起始地址、寄存器数量等) |
| CRC 校验 | 2 字节 | 循环冗余校验,确保数据完整性 |
应用场景
Modbus 几乎出现在所有需要设备互联的工业与楼宇场景中:
- 工业自动化:PLC、RTU、仪表的数据采集,产线运行参数监控,设备状态管理
- 能源管理与计量:电表、水表、热量表等能耗采集与集中监测
- 楼宇自动化:HVAC 空调系统、电力仪表、照明系统、泵阀设备控制
- 电力监控:变压器、配电柜参数监测,保护装置数据采集
- 环境与安全监测:烟感、温湿度、气体监测
- 智能制造与 IoT:CNC 设备、机器人单元、工业设备数字化改造
在数据采集与监视控制系统(SCADA)中,Modbus 常用于连接监控计算机和远程终端单元(RTU)
优势与局限性
优势
- 完全开放:无需授权费,无知识产权限制
- 生态成熟:几乎所有工业与楼控厂商都提供 Modbus 支持
- 简单易实现:帧格式简单紧凑,8 位单片机即可实现
- 通信方式灵活:支持串口(RS-232/485/422)和以太网(TCP/IP)
- 抗干扰能力强:差分串行驱动适合长距离、噪声复杂的工业环境
局限性
- 无原生加密:安全性需额外保障
- 实时性有限:基于主设备轮询机制,不适合对实时性要求极高的场景
- 带宽较低:不适合大批量数据传输
- 不支持主动上报:从设备不能主动发送数据,需依赖主设备轮询
- 地址需人工管理:随网络规模扩大,管理复杂度上升
实战案例
Modbus 协议分析(Modbus 协议)
链接: https://pan.baidu.com/s/1RcC1VTPusecH9OqTvRPnPA?pwd=4wv3 提取码: 4wv3
题目:黑客通过外网进入一家工厂的控制网络,之后对工控网络中的操作员站系统进行了攻击,最终通过工控协议破坏了正常的业务。我们得到了操作员站在攻击前后的网络流量数据包,我们需要分析流量中的蛛丝马迹,找到 FLAG
首先打开流量包,数据包都是关于 Modbus/TCP 的流量,下面的流量解析
MBAP Header(报文头,共 7 字节)
Transaction ID(事务标识符,2 字节):类似序号,请求和响应的 ID 必须一致
Protocol ID(协议标识符,2 2字节):固定为
00 00,代表 Modbus 协议Length(长度,2 字节):表示后面还有多少个字节
Unit ID(单元标识符,1 字节):类似于从机地址(Slave ID)
PDU(协议数据单元):
Function Code(功能码,1 字节): 它告诉设备要做什么(读还是写)
Data(数据,可变长度):具体读写的地址、数量或者具体的数值
我们重点关注功能码字段
| 功能码 (十进制) | 功能码 (十六进制) | 功能码名称 (英文) | 作用与含义 |
|---|---|---|---|
| 1 | 0x01 | Read Coils | 读线圈状态:读取输出位的开关状态(0或1) |
| 2 | 0x02 | Read Discrete Inputs | 读离散输入状态:读取只读的输入位状态(0或1) |
| 3 | 0x03 | Read Holding Registers | 读保持寄存器:读取可读写寄存器中的二进制数据(CTF 最常用) |
| 4 | 0x04 | Read Input Registers | 读输入寄存器:读取只读寄存器中的物理测量值 |
| 5 | 0x05 | Write Single Coil | 写单个线圈:强制使能或关闭某一个输出位 |
| 6 | 0x06 | Write Single Register | 写单个寄存器:向某一个保持寄存器写入数据 |
| 7 | 0x07 | Read Exception Status | 读异常状态:快速读取设备的8个内部状态位 |
| 8 | 0x08 | Diagnostics | 诊断功能:提供一系列测试码,用于检查通信系统错误 |
| 11 | 0x0B | Get Comm Event Counter | 获取通信事件计数:从设备状态字中获取状态计数 |
| 12 | 0x0C | Get Comm Event Log | 获取通信事件日志:从设备中获取通信历史记录 |
| 15 | 0x0F | Write Multiple Coils | 写多个线圈:批量强制设置多个输出位 |
| 16 | 0x10 | Write Multiple Registers | 写多个寄存器:批量向一串寄存器写入数据(CTF 最常用) |
| 17 | 0x11 | Report Slave ID | 报告从机ID:描述设备的类型、运行状态等信息 |
| 20 | 0x14 | Read File Record | 读文件记录:读取大容量存储器中的文件型数据 |
| 21 | 0x15 | Write File Record | 写文件记录:向大容量存储器中写入文件型数据 |
| 22 | 0x16 | Mask Write Register | 屏蔽写寄存器:使用AND/OR掩码修改寄存器中的特定位 |
| 23 | 0x17 | Read/Write Multiple Registers | 读/写多个寄存器:在一个通信周期内同时进行读和写操作 |
| 24 | 0x18 | Read FIFO Queue | 读FIFO队列:读取先进先出队列中的数据 |
| 43 | 0x2B | Encapsulated Interface Transport | 封装接口传输:最常用于43/14(0x2B/0x0E)来读取设备厂商、型号等识别信息 |
WireShark 过滤语法 modbus.func_code == 16

解码拿到 flag

简单 Modbus 协议分析(Modbus 协议)

502 是 Modbus/TCP 常用端口

正常看了一圈找不到明显异常,没有别的功能码
正常情况下,以太网帧长度大致应该是:
1 | Ethernet header 14 字节 + IP total length |
下面过滤语法可以筛选出存在额外字节的流量包
1 | tcp.port == 502 && frame.len > ip.len + 14 |
frame.len:整个数据帧在物理网络上的总长度(从以太网头部开始,到数据包末尾结束)ip.len:IP 数据包的总长度(仅包含 IP 头部 + TCP 头部 + 应用层数据,不包含以太网链路层头部)+ 14:标准的 以太网 II 帧头部长度(6 字节目标 MAC + 6 字节源 MAC + 2 字节类型/长度字段)

转为 ASCII 码得到 flag{DGswTfgy1GD236fs2sfF2dskLng}
工控现场的恶意扫描(Modbus 协议)

题目说 PLC 被扫描,PLC 常见协议优先看:
Modbus/TCP -> 502 端口
S7comm -> 102 端口
EtherNet/IP -> 44818 端口
这份包里最明显的是 502 端口,也就是 Modbus/TCP
可以看到有两个主机在访问 PLC

按照长度排列找到 flag
1 | ip.src == 192.168.99.32 && ip.dst == 192.168.99.31 && tcp.dstport == 502 |

modbus(Modbus 协议)

过滤出来按照长度排列,有三个数据包长度是 81 且含有十进制的 ASCII 码

将这三个流量包的字符提取出来拼接得到 flag{18ghT1wr3}
不安全的组件(HTTP 协议 + Moudbus 协议)

这个 flag 是假的

有 HTTP 直接过滤,看到只对 modus3.pacpng 有一次请求

导出这个文件

打开可以看到基本上都是写入寄存器操作

将写入的数据拼接起来得到 flag
1 | 34330928433800018445622871195029877774318527404492633948321508979 |