当前位置: 首页 > 技术干货 > Modbus协议及其取证的学习笔记

Modbus协议及其取证的学习笔记

发表于:2026-09-09 11:25 作者: Luc1dkilL 阅读数(20人)

ModBus协议

ModBus协议是工业自动化领域非常经典的通信协议。虽然诞生于 1979 年,但截至现在仍然大量存在于 PLC、变频器、仪表、传感器、能源设备、楼宇控制和 SCADA 系统中。

Modbus 的基本通信模型

传统 Modbus 通信采用 主从(Master/Slave) 模型,也有client/server的说法。

基本结构如下:

        Master(主站)【负责发起请求】
            │
      ┌──────┼──────┐
      │     │     │
      ▼     ▼     ▼
  Slave 1 Slave 2 Slave 3 【负责响应请求- 每个 Slave 通常具有唯一的设备地址】
  从站     从站     从站

主站负责发起通信请求,从站根据请求返回数据。

例如:

PLC(主站)
  │
  │ 读取温度
  ▼
温度仪表(从站)
  │
  │ 返回 25.6℃
  ▼
PLC

Modbus 常见的通信方式主要有:

  1. Modbus RTU

  2. Modbus ASCII

  3. Modbus TCP

其中工业现场最常见的是:

Modbus RTU 和 Modbus TCP

Modbus RTU和Modbus TCP

Modbus RTU

Modbus RTU 是 Modbus 最常见的串行通信模式之一。

RTU 使用二进制方式传输数据,通常运行在:

  • RS-485

  • RS-232

等串行通信接口上。

工业现场最常见的组合是:

Modbus RTU + RS-485

例如:

PLC

│ RS-485

├───────────────┐
│               │
▼               ▼
变频器1         变频器2
Slave 1         Slave 2
  • RS-485 和 Modbus RTU 的区别

Modbus RTU 是协议通信方式,RS-485 是常用的物理接口

Modbus RTU
  ↑
通信协议 / 数据格式
  │
  ▼
RS-485
  ↑
物理通信接口

例如:

应用层:    Modbus
            │
            ▼
数据格式:   Modbus RTU
            │
            ▼
物理层:     RS-485
            │
            ▼
通信线路:   A / B

Modbus TCP

Modbus TCP 是基于以太网和 TCP/IP 协议进行通信的 Modbus 版本。

它通常使用Ethernet, TCP/IP, RJ45 网口, 工业以太网交换机进行通信。

基本结构:

PLC

│ Ethernet

工业交换机

├──────────────┐
│             │
▼             ▼
变频器         远程I/O
IP:             IP:
192.168.1.10   192.168.1.20

Modbus TCP 通常使用502作为默认通信端口。

Modbus RTU 与 Modbus TCP 对比

image.png

Modbus 数据模型

在Modbus通信中,设备数据被组织在称为寄存器的单元中。client指定目标数据区和地址并发送读写请求。然后,server处理该请求并返回读请求的值或写请求的写确认。

Modbus 主要定义了四种数据类型:

image.png


总结如下:

                Bit                Word(16 bit)
                │                     │
      ┌────────┴────────┐   ┌───────┴────────┐
      │                 │   │               │
    Coil       Discrete Input Input       Holding
    线圈           离散输入     Register     Register
      │                 │         │             │
    读/写             只读       只读         读/写
      │                 │         │             │
    控制             状态       测量值       参数/控制
  • Coil(线圈)

Coil 是一种单 bit 数据

只能表示:

0 = OFF
1 = ON

例如:

Coil 00001 = 电机启动

可能定义为:

0 → 电机停止
1 → 电机启动
  • Discrete Input(离散输入)

Discrete Input 也是 bit 类型数据,但通常是只读的。

例如:

Discrete Input = 急停状态

可能:

0 → 正常
1 → 急停
  • Input Register(输入寄存器)

Input Register 通常用于存储只读数据

例如:

30001 = 温度
30002 = 压力
30003 = 电流

例如:

30001 = 256

实际可能代表:

25.6 ℃

具体缩放方式由设备厂家定义。

  • Holding Register(保持寄存器)

Holding Register 是 Modbus 中非常常用的数据类型。

它通常用于:

  • 参数设置

  • 运行频率

  • 电机转速

  • 温度设定值

  • 启停控制

  • 状态数据

  • 报警代码

例如:

40001 = 运行频率
40002 = 目标频率
40003 = 电机电流
40004 = 电机转速

PLC 可以通过 Modbus 对这些寄存器进行读取或者写入。

Modbus功能码

常见功能码如下:

image.png

工业自动化中最常见的功能码之一是:

03:读取 Holding Register

以及:

06 / 10:写入 Holding Register

Modbus取证题学习

附件是一个pcap文件,使用wireshark打开,一堆Modbus TCP的流量

image-20260906214654249

Modbus TCP格式如下,它是作为TCP的payload的。因为有tcp的原因因此没有Modbus RTU的CRC校验,

image-20260906215037074

简单来说现在有了一些不同function code的流量,我们先统计所有的function code,使用pyshark库提取

import pyshark

# 获取功能码, 统计出现次数
def get_code():
   captures = pyshark.FileCapture(r"xxx\Modbus.pcap", tshark_path=r'D:\Wireshark\tshark.exe')
   func_codes = {}
   for pkt in captures:
       if hasattr(pkt, "modbus"):
               func_code = int(pkt.modbus.func_code)
               if func_code in func_codes:
                   func_codes[func_code] += 1
               else:
                   func_codes[func_code] = 1
   print(func_codes)

if __name__ == "__main__":
   get_code()

结果如下

{1: 702, 3: 702, 4: 702, 2: 702, 16: 2}

这里出现了非常规的16功能码,而且只有2次,很有可能是解题线索

过滤一下这些功能码为16的packet

import pyshark

# 检查function code的数据
def get_targetcode_data(target_code):
   captures = pyshark.FileCapture(r"xxx\Modbus.pcap", tshark_path=r'D:\Wireshark\tshark.exe')
   for c in captures:
       for pkt in c:
           if pkt.layer_name == "modbus":
               func_code = int(pkt.func_code)
               if func_code == target_code:
              print(c)
                   
if __name__ == "__main__":
   get_targetcode_data(16)

两个packet:

  • packet1

Packet (Length: 117)
Layer ETH
:       Destination: 52:54:00:a4:30:12
      Address: 52:54:00:a4:30:12
      .... ..1. .... .... .... .... = LG bit: Locally administered address (this
is NOT the factory default)
      .... ...0 .... .... .... .... = IG bit: Individual address (unicast)      
      Source: 52:54:00:f8:5c:21
      .... ..1. .... .... .... .... = LG bit: Locally administered address (this
is NOT the factory default)
      .... ...0 .... .... .... .... = IG bit: Individual address (unicast)      
      Type: IPv4 (0x0800)
      Address: 52:54:00:f8:5c:21
Layer IP
:       0100 .... = Version: 4
      .... 0101 = Header Length: 20 bytes (5)
      Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
      0000 00.. = Differentiated Services Codepoint: Default (0)
      .... ..00 = Explicit Congestion Notification: Not ECN-Capable Transport (0)       Total Length: 103
      Identification: 0x5f09 (24329)
      Flags: 0x40, Don't fragment
      0... .... = Reserved bit: Not set
      .1.. .... = Don't fragment: Set
      ..0. .... = More fragments: Not set
      ...0 0000 0000 0000 = Fragment Offset: 0
      Time to Live: 128
      Protocol: TCP (6)
      Header Checksum: 0x3f2f [validation disabled]
      Header checksum status: Unverified
      Source Address: 172.16.3.23
      Destination Address: 172.16.1.33
Layer TCP
:       Source Port: 1073
      Destination Port: 502
      Stream index: 2195
      Conversation completeness: Incomplete, ESTABLISHED (7)
      TCP Segment Len: 63
      Sequence Number: 3073843007
      Next Sequence Number: 3073843070
      Acknowledgment Number: 1687629905
      0101 .... = Header Length: 20 bytes (5)
      Flags: 0x018 (PSH, ACK)
      000. .... .... = Reserved: Not set
      ...0 .... .... = Nonce: Not set
      .... 0... .... = Congestion Window Reduced (CWR): Not set
      .... .0.. .... = ECN-Echo: Not set
      .... ..0. .... = Urgent: Not set
      .... ...1 .... = Acknowledgment: Set
      .... .... 1... = Push: Set
      .... .... .0.. = Reset: Not set
      .... .... ..0. = Syn: Not set
      .... .... ...0 = Fin: Not set
      TCP Flags: ·······AP···
      Window: 64240
      Calculated window size: 64240
      Window size scaling factor: -2 (no window scaling used)
      Checksum: 0xe950 [unverified]
      Checksum Status: Unverified
      Urgent Pointer: 0
      Timestamps
      Time since first frame in this TCP stream: 0.014549000 seconds
      Time since previous frame in this TCP stream: 0.007470000 seconds
      SEQ/ACK analysis
      iRTT: 0.007079000 seconds
      Bytes in flight: 63
      Bytes sent since last PSH flag: 63
      TCP payload (63 bytes)
      PDU Size: 63
Layer MBTCP
:       Transaction Identifier: 0
      Protocol Identifier: 0
      Length: 57
      Unit Identifier: 1
Layer MODBUS
:       .001 0000 = Function Code: Write Multiple Registers (16)
      Reference Number: 1
      Word Count: 25
      Byte Count: 50
      Register 1 (UINT16): 84
      Register Number: 1
      Register Value (UINT16): 84
      Register 2 (UINT16): 104
      Register 3 (UINT16): 101
      Register 4 (UINT16): 77
      Register 5 (UINT16): 111
      Register 6 (UINT16): 100
      Register 7 (UINT16): 98
      Register 8 (UINT16): 117
      Register 9 (UINT16): 115
      Register 10 (UINT16): 80
      Register 11 (UINT16): 114
      Register 12 (UINT16): 111
      Register 13 (UINT16): 116
      Register 14 (UINT16): 111
      Register 15 (UINT16): 99
      Register 16 (UINT16): 111
      Register 17 (UINT16): 108
      Register 18 (UINT16): 73
      Register 19 (UINT16): 115
      Register 20 (UINT16): 70
      Register 21 (UINT16): 117
      Register 22 (UINT16): 110
      Register 23 (UINT16): 110
      Register 24 (UINT16): 121
      Register 25 (UINT16): 33
      Register Number: 2
      Register Number: 3
      Register Number: 4
      Register Number: 5
      Register Number: 6
      Register Number: 7
      Register Number: 8
      Register Number: 9
      Register Number: 10
      Register Number: 11
      Register Number: 12
      Register Number: 13
      Register Number: 14
      Register Number: 15
      Register Number: 16
      Register Number: 17
      Register Number: 18
      Register Number: 19
      Register Number: 20
      Register Number: 21
      Register Number: 22
      Register Number: 23
      Register Number: 24
      Register Number: 25
      Register Value (UINT16): 104
      Register Value (UINT16): 101
      Register Value (UINT16): 77
      Register Value (UINT16): 111
      Register Value (UINT16): 100
      Register Value (UINT16): 98
      Register Value (UINT16): 117
      Register Value (UINT16): 115
      Register Value (UINT16): 80
      Register Value (UINT16): 114
      Register Value (UINT16): 111
      Register Value (UINT16): 116
      Register Value (UINT16): 111
      Register Value (UINT16): 99
      Register Value (UINT16): 111
      Register Value (UINT16): 108
      Register Value (UINT16): 73
      Register Value (UINT16): 115
      Register Value (UINT16): 70
      Register Value (UINT16): 117
      Register Value (UINT16): 110
      Register Value (UINT16): 110
      Register Value (UINT16): 121
      Register Value (UINT16): 33
  • packet2

Packet (Length: 63)
Layer ETH
:       Destination: 52:54:00:f8:5c:21
      Address: 52:54:00:f8:5c:21
      .... ..1. .... .... .... .... = LG bit: Locally administered address (this
is NOT the factory default)
      .... ...0 .... .... .... .... = IG bit: Individual address (unicast)      
      Source: 52:54:00:a4:30:12
      .... ..1. .... .... .... .... = LG bit: Locally administered address (this
is NOT the factory default)
      .... ...0 .... .... .... .... = IG bit: Individual address (unicast)      
      Type: IPv4 (0x0800)
      Address: 52:54:00:a4:30:12
Layer IP
:       0100 .... = Version: 4
      .... 0101 = Header Length: 20 bytes (5)
      Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
      0000 00.. = Differentiated Services Codepoint: Default (0)
      .... ..00 = Explicit Congestion Notification: Not ECN-Capable Transport (0)       Total Length: 49
      Identification: 0x1f67 (8039)
      Flags: 0x40, Don't fragment
      0... .... = Reserved bit: Not set
      .1.. .... = Don't fragment: Set
      ..0. .... = More fragments: Not set
      ...0 0000 0000 0000 = Fragment Offset: 0
      Time to Live: 64
      Protocol: TCP (6)
      Header Checksum: 0xbf07 [validation disabled]
      Header checksum status: Unverified
      Source Address: 172.16.1.33
      Destination Address: 172.16.3.23
Layer TCP
:       Source Port: 502
      Destination Port: 1073
      Stream index: 2195
      Conversation completeness: Incomplete, DATA (15)
      TCP Segment Len: 9
      Sequence Number: 1687629905
      Next Sequence Number: 1687629914
      Acknowledgment Number: 3073843070
      0101 .... = Header Length: 20 bytes (5)
      Flags: 0x018 (PSH, ACK)
      000. .... .... = Reserved: Not set
      ...0 .... .... = Nonce: Not set
      .... 0... .... = Congestion Window Reduced (CWR): Not set
      .... .0.. .... = ECN-Echo: Not set
      .... ..0. .... = Urgent: Not set
      .... ...1 .... = Acknowledgment: Set
      .... .... 1... = Push: Set
      .... .... .0.. = Reset: Not set
      .... .... ..0. = Syn: Not set
      .... .... ...0 = Fin: Not set
      TCP Flags: ·······AP···
      Window: 29200
      Calculated window size: 29200
      Window size scaling factor: -2 (no window scaling used)
      Checksum: 0x6c02 [unverified]
      Checksum Status: Unverified
      Urgent Pointer: 0
      Timestamps
      Time since first frame in this TCP stream: 0.016131000 seconds
      Time since previous frame in this TCP stream: 0.001000000 seconds
      SEQ/ACK analysis
      iRTT: 0.007079000 seconds
      Bytes in flight: 9
      Bytes sent since last PSH flag: 9
      TCP payload (9 bytes)
      PDU Size: 9
Layer MBTCP
:       Transaction Identifier: 0
      Protocol Identifier: 0
      Length: 3
      Unit Identifier: 1
Layer MODBUS
:       .001 0000 = Function Code: Write Multiple Registers (16)
      Exception Code: Illegal data address (2)

其中注意到packet2报错Exception Code: Illegal data address (2)

我们把packet1的tcp payload给提取出来(tcp payload其实就是modbus tcp的相应数据)

import pyshark

def get_targetcode_data(target_code):
   captures = pyshark.FileCapture(r"xxx\Modbus.pcap", tshark_path=r'D:\Wireshark\tshark.exe')
   for c in captures:
       for pkt in c:
           if pkt.layer_name == "modbus":
               func_code = int(pkt.func_code)
               if func_code == target_code:
                   payload = str(c["TCP"].payload)
                   print(parse_payload(payload))

def parse_payload(payload):
   data = payload.split(":")
   flag = ""
   for i in data:
       _ord = ord(bytes.fromhex(i))
       if (_ord > 0) and (_ord < 128):
           flag += (chr(_ord))
   return flag


if __name__ == "__main__":
   get_targetcode_data(16)

在register之间采用\x00字节隔断

image-20260906222247465

具体打印就是flag:TheModbusProtocolIsFunny!

 

本课程最终解释权归蚁景网安学院

本页面信息仅供参考,请扫码咨询客服了解本课程最新内容和活动

🎈网安学院推荐课程: 零基础CTF竞赛实战课 红队攻防特训班 Web安全工程师特训班 应急响应安全工程师特训班
  CTF-Reverse实战技能特训班 CTF-WEB实战技能特训班 CTF-PWN实战技能特训班 CTF-MISC实战技能特训班   Python网络安全实战班 SRC漏洞高阶实战课 HVV大师课