Modbus协议
Modbus是全球第一个真正用于工业现场的总线协议,ModBus采用主/从(Master/Slave)方式通信。最大可支持247个从属控制器,但实际所支持的从属控制器数还得由所用通信设备决定,且每个从属控制器都有一个专属的slave ID。同时Modbus也是一个不经过身份验证的明文协议。
虽然最初是为了进行串行通信才设计出它,但是目前用的最多的还是TCP,其他版本的Modbus(用于串行通信)有Modbus RTU和Modbus ASCII。对于串行通信来说Modbus ASCII与Modbus RTU是不兼容的(也就是说在同一个网络两者不可能同时存在)
Modbus TCP (在低网络层中没有校验和); Modbus RTU (使用二进制编码和一个CRC错误检测); Modbus ASCII (使用ASCII字符);
在TCP中客户端通常指的是主控制器,服务端指的是从属控制器。
Modbus TCP
TCP帧格式由以下部分组成:
Transaction identifier : 设备直接进行同步通信 Protocol identifier : 对于Modbus TCP来说其值始终为0 Length field : 确定数据包剩余的长度 Unit identifier : 从属控制器的地址(因为我们已经把TCP/IP地址设置为标识符,所以这里大部分情况为255) Function code : 要执行的函数 -大多数函数运行从/到PLC进行读取/写入操作 3: 读取多个保持寄存器 1: 读取线圈 5: 写入单个线圈 … -检测函数 -其他一些函数 数据字节或者命令
这里有两个可用来存储信息的地方:线圈以及寄存器。每个数据存储类型都有两个不同的寄存器:一个是读/写寄存器,一个是只读寄存器,且每个数据存储类型都引用一个内存地址。
简单的说:
线圈用于存储简单的布尔值(1 bit).可读/写,从00001到09999; 离散输入:只读类型的布尔值,从10001到19999; 输入寄存器:只读类型的长值(16 bits),从30001到39999; 保持寄存器:读/写类型的长值(16 bits),从40001到49999;
请注意:由于硬件不同,有些寄存器是从0开始,有些是从1开始。
在大多数情况下,在Modbus单元设备中因为之前已经通过它的IP地址处理了正确单元,所以你不需要一个单元id。但有些时候你可能会遇到多台设备连接到一个IP地址,如果是这样的话,你就必须将单元ID设置为255
单元ID为0你可以将其看作一个广播地址,信息发送到0,所有的从属控制器都可以接收。如果你是设置一个Modbus客户端,记住一定不要将单元ID设置为0
Modbus流量
你可以使用 ModbusPal 来模拟Modbus从属控制器的行为,这个Java应用允许你同不同的从属控制器(寄存器和线圈)愉快玩耍。你也可以使用MBTGET(纯Perl写的modbus/TCP客户端)来查询Modbus实例。
这儿有几个备选方案,你可以使用它们来玩Modbus:
Modbus poll (主应用在Windows)
CAS Modbus Scanner (主应用在Windows)
ModScan (主应用在Windows)
modbus-tk (Linux上模拟从属控制器)
Conpot (Linux上的一个Modbus -ICS蜜罐)
目前我在Kali VM上部署ModbusPal(Slave),在Linux VM上部署MBTGET(Master)
Modbus Slave : 192.168.171.182 Modbus Master : 192.168.171.139
分析Modbus流量
在OSX上使用vmnet-sniffer获取俩不同虚拟机的流量
sudo "/Applications/VMware Fusion.app/Contents/Library/vmnet-sniffer" -w modbus.pcap vmnet8
过一会儿你就可以使用Wireshark读取pcap文件,Modbus TCP流量运行在tcp/502端口
首先我们需要设置ModusPal来模拟Modbus从属控制器,下载ModbusPal之后运行:
java –jar ModbusPal.jar
增加一个从属控制器,编辑从属控制器并添加一些线圈。
理论上来说你也可以更改一些线圈的值,记住他们都是布尔值,所以这些值不是0便是1。单击运行,启动从属控制器。
切换到安装好MBTGET的Linux客户端,MBTGET上手十分容易:
usage : mbtget [-hvdsf] [-2c] [-u unit_id] [-a address] [-n number_value] [-r[12347]] [-w5 bit_value] [-w6 word_value] [-p port] [-t timeout] serveur
你可以使用-r1读取线圈,使用-r3可以读取保持寄存器。
第一次流量抓取是在从属控制器中查询线圈,vmnet-sniffer可以完成网络流量抓取的任务,之后将获得的文件导入Wireshark,使用如下Modbus命令:
mbtget -r1 -u 1 -n 8 192.168.171.182
从192.168.171.182(slave)的unit id 1开始读取8寄存器,输出为:
values: 1 (ad 00000): 0 2 (ad 00001): 0 3 (ad 00002): 1 4 (ad 00003): 0 5 (ad 00004): 1 6 (ad 00005): 0 7 (ad 00006): 0 8 (ad 00007): 0
在Wireshark中进行筛选过滤:
tcp.port == 502
在TCP3次握手完成之后,紧接着就是Modbus数据包:
让我们看看Modbus数据包,Wireshark有一个Modbus编码器方便观察数据。从抓取到的数据中我们可以看出在unit id 1(-u 1)中从线圈(-r1)中读取8字节(-n 8)的请求:
下面一个是Modbus应答的数据包,在应答数据包中有一个跟上面数据包相同的Transaction Identifier(36710)。仔细研究后发现,这是Modbus同步通信的方式。应答数据包中也包含请求函数(F1 – read coils)和单元标识符(1)。最有趣的还得数payload中的数据。
Data 14是16进制数,线圈值是布尔值或者二进制值。所以我们需要将14转换为二进制:
1 = 0001 4 = 0100
转换为2进制为00010100
这个值与我们之前在ModbusPal设置的线圈一致,第三个和第五个寄存器设置为1。
接下来设置3个保持寄存器的值:
然后查询从属控制器中的值:
mbtget -r3 -u 1 -n 8 192.168.171.182 values: 1 (ad 00000): 0 2 (ad 00001): 5 3 (ad 00002): 0 4 (ad 00003): 10 5 (ad 00004): 0 6 (ad 00005): 20 7 (ad 00006): 0 8 (ad 00007): 0
在流量抓取中你可以看到请求的函数是读取保持寄存器(F3):
Modbus应答请求函数function call (3),以及8个(从0开始)寄存器:
如果写一个保持寄存器,看看会发生什么?
mbtget -w6 333 -u 1 -a 8 192.168.171.182 word write ok
抓取到的数据包显示了一个熟悉的输出,3次握手之后的Modbus数据包中包含Write Single Register请求函数,引用号码(8)以及payload (data, 014d)。
这响应数据包又包含了请求的函数(6)以及提交的payload:
你可以使用nmap搜索Modbus设备,而且针对Modbus设备这里还提供了一个脚本:
sudo nmap -p 502 -sV --script modbus-discover.nse 192.168.171.182
如果你看一看脚本的源代码,你可以看到它试图发现可用设备的IDs
for sid = 1, 246 do stdnse.debug3("Sending command with sid = %d", sid) local rsid = form_rsid(sid, 0x11, "")
注意0×11,十六进制0×11也就是十进制的17。Modbus function code 17是Report Slave ID的诊断函数,如果你打开一个抓取的数据包你可以看到相同的请求。
从ModbusPal返回的应答表示了不支持这个函数请求
之后NSE发现脚本发送了另外请求:
discover_device_id_recursive = function(host, port, sid, start_id, objects_table) local rsid = form_rsid(sid, 0x2B, "/x0E/x01" .. bin.pack('C', start_id)) local status, result = comm.exchange(host, port, rsid)
在注意下payload 0x2B,十六进制0x2B也就是十进制的43。Modbus function code 43同时也是Read Device Identification的诊断函数
结论
读取/阅读Modbus TCP流量并不难,最大的挑战可能就是抓取流量这个过程,特别是这个串行通信。Modbus串行通信和Modbus TCP通信差不多,所以一旦你抓取到数据使用Wireshark就能很简单的进行分析了
*原文地址: vanimpe ,编译/FB小编鸢尾,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)