前言
最近因任务需要,要求这方面的知识基础,因此打算开一个系列来自己学习巩固一下,充实自己。。。。
Modbus协议是什么?
Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。
Modbus作为一种通信协议,它是一种应用层的报文传输协议,它既可以在物理层面上选择串口进行简单的串行通信,也可以使用TCP的方式进行传输。
modbus是一种主从协议,主设备的一方向从设备的一方下达指令,从设备的一方根据指令做出反应并回复主设备,主设备可以有多个从设备。具体来说,工作人员的计算机可认为是master,而PLC(可编程逻辑控制器)之类的具体设备就是slave了。每个设备有自己的“代号”,主设备通过“代号”来找到某一个对应的设备,当然也可以使用广播的方式,代号0即为广播。
Modbus有自己的数据链路层定义,其实主要是对于传输数据格式和校验等方面的规定。具体来说,modbus定义了自己的数据单元,功能码与具体的数据组成了PDU(协议数据单元 Protocol Data Unit),所谓的功能码也就是代表了主向从下达的指令是什么,数据也就是这次指令要用到的“参数”。
Modbus功能码
功能码主设备能够对从设备下达指令,功能码有效范围在1~255之间。其中大部分都是保留的,如128-255为异常响应保留,例如:
- 01 读线圈状态
- 02 读离散输入状态
- 03 读保持寄存器
- 04 读输入寄存器
- 05 写单个线圈
- 15 写多个线圈
- 16 写多个保持寄存器
- 20 读文件记录
- 21 写文件记录
- 08 获取异常状态
更多功能码可去查询手册来获取具体信息。。。
modbus可以说是将读写指令分为了两大类,一类是离散的,也就是位操作,非1即0;第二类是模拟的,也就是数字,可以叫做字操作。而每一类下面都有输出和输出之分,于是就有了下面四种说法:
- DO(digital output数字量输出),所谓线圈就是离散的输出状态,01即读一个离散的输出状态,举个不恰当的栗子,你家灯泡接到某个控制器上(实际上并不会存在这种情况……),我们可以通过01加上数据,比如1,让他亮,加上0,让他灭。
- DI (digital input数字量输入),所谓的离散输入就是它,还是上面的栗子,我们想知道灯的开关是咋样的呢?就用02指令看看,如果是1,哦,按下去了,如果是0就是没按。通过这个不恰当的栗子我们大概也可以猜到,这是不可写的(如果你随便一个指令把开关给按死了,那我这灯不是彻底开不了了?),可以理解为外部对工控系统所带来的“开关”影响。
- AO(AnalogOutput模拟输出),保持寄存器的功能,和DO最大的不同就是它不再是0或1,可以是一个数值,比如,我们设定的PID运行参数,或者是温度的上下限等等
- AI(Analog Input 模拟输入),也就是输入寄存器,和DI一样,可读但不可写,可以理解为外部对于系统的多位输入。
Modbus TCP
我们可以通过wireshark对Modbus的流量包进行抓取进而观察Modbus TCP的数据格式
Transaction identifier : 事务标识符
Protocol identifier : 默认为0
Length : 数据的长度
Unit identifier : 从机地址,因为使用了TCP/IP所以用ip地址来标识从机,所以该位可忽视,或者做进一步分发
Function code : modbus的功能码
Data :具体的数据
可以看到在遵从TCP/IP的基础上Modbus加了自己的修改,主要有以下三个部分:
- 由于TCP/IP本身具有数据校验部分,所以ADU的差错校验没有了
- 实用ip可以确定从机,ADU的附加地址也不再有效。但是目标可以继续是一个主机,再向其他从机发送数据,这时ADU的附加地址可以作为下一个主机分发数据包时的地址。
- 增加了TCP/IP的头部,比如length、协议标识符等。
实例
黑客通过外网进入一家工厂的控制网络,之后对工控网络中的操作员站系统进行了攻击,最终通过工控协议破坏了正常的业务。我们得到了操作员站在攻击前后的网络流量数据包,我们需要分析流量中的蛛丝马迹,找到FLAG,flag形式为 flag{}。
使用wireshark打开流量包,限定范围为modbus协议的数据包,根据功能码来判断异常,筛选功能码为16的数据包进行ASCII码转换即可发现flag。
利用payload:
import pyshark
func_code = [1,2,3,4]
def find_flag():
pcap = pyshark.FileCapture("q1.pcap")
for c in pcap:
for pkt in c:
if pkt.layer_name == "modbus":
temp = int(pkt.func_code)
if temp not in func_code:
payload = str(c["TCP"].payload).replace(":", "")
print("content[*] is " + payload)
解析:整体上没有什么异常,接下来就该考虑是否有数据的写入,flag很有可能就是写入的数据。那么首先排除12345的功能码,因为flag既不可能是位操作,也不可能是字操作,都太短了,所以聚焦的就应该是包长度大的,或者是类似16功能码那样写多个字的指令。