转载

Modbus TCP流量分析

Modbus协议

Modbus是全球第一个真正用于工业现场的总线协议,ModBus采用主/从(Master/Slave)方式通信。最大可支持247个从属控制器,但实际所支持的从属控制器数还得由所用通信设备决定,且每个从属控制器都有一个专属的slave ID。同时Modbus也是一个不经过身份验证的明文协议。

虽然最初是为了进行串行通信才设计出它,但是目前用的最多的还是TCP,其他版本的Modbus(用于串行通信)有Modbus RTU和Modbus ASCII。对于串行通信来说Modbus ASCII与Modbus RTU是不兼容的(也就是说在同一个网络两者不可能同时存在)

每种Modbus协议都必须选择一种帧格式:

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

首先我们需要设置ModusPal来模拟Modbus从属控制器,下载ModbusPal之后运行:

java –jar ModbusPal.jar

增加一个从属控制器,编辑从属控制器并添加一些线圈。

Modbus TCP流量分析

理论上来说你也可以更改一些线圈的值,记住他们都是布尔值,所以这些值不是0便是1。单击运行,启动从属控制器。

MBTGET

切换到安装好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可以读取保持寄存器。

Modbus中查询线圈

第一次流量抓取是在从属控制器中查询线圈,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 TCP流量分析

让我们看看Modbus数据包,Wireshark有一个Modbus编码器方便观察数据。从抓取到的数据中我们可以看出在unit id 1(-u 1)中从线圈(-r1)中读取8字节(-n 8)的请求:

Modbus TCP流量分析

下面一个是Modbus应答的数据包,在应答数据包中有一个跟上面数据包相同的Transaction Identifier(36710)。仔细研究后发现,这是Modbus同步通信的方式。应答数据包中也包含请求函数(F1 – read coils)和单元标识符(1)。最有趣的还得数payload中的数据。

Modbus TCP流量分析

Data 14是16进制数,线圈值是布尔值或者二进制值。所以我们需要将14转换为二进制:

1 = 0001 4 = 0100

转换为2进制为00010100

这个值与我们之前在ModbusPal设置的线圈一致,第三个和第五个寄存器设置为1。

在Modbus中检索保持寄存器

接下来设置3个保持寄存器的值:

Modbus TCP流量分析

然后查询从属控制器中的值:

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 TCP流量分析

Modbus应答请求函数function call (3),以及8个(从0开始)寄存器:

Modbus TCP流量分析

在Modbus写保持寄存器

如果写一个保持寄存器,看看会发生什么?

mbtget -w6 333 -u 1 -a 8 192.168.171.182 word write ok

抓取到的数据包显示了一个熟悉的输出,3次握手之后的Modbus数据包中包含Write Single Register请求函数,引用号码(8)以及payload (data, 014d)。

Modbus TCP流量分析

这响应数据包又包含了请求的函数(6)以及提交的payload:

Modbus TCP流量分析

Nmap modbus-discover

你可以使用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的诊断函数,如果你打开一个抓取的数据包你可以看到相同的请求。

Modbus TCP流量分析

从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 TCP流量并不难,最大的挑战可能就是抓取流量这个过程,特别是这个串行通信。Modbus串行通信和Modbus TCP通信差不多,所以一旦你抓取到数据使用Wireshark就能很简单的进行分析了

*原文地址: vanimpe ,编译/FB小编鸢尾,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

正文到此结束
Loading...