Lua脚本编写Wireshark插件解析第三方私有协议
时间:2023-06-25 11:07:00
一、工控私有协议
在工业控制系统中,通信协议有很多标准和私人协议。如果你有使用组态软件的经验,你会发现在连接设备的第一步,除了以太网/串行,每个家庭基本上都有自己的私人通信协议。
上图为,某SCADA软件驱动配置界面。
众所周知,一般的工业控制协议在传输过程中没有加密,协议也没有认证。通常通过协议分析形成一些测试用例,支持协议的设备在特定的运行环境下达到异常运行效果。
如强制物理输出(使用)FINS协议攻击欧姆龙(Omron)PLC的物理(I/O)输出)、程序上传下载、重置设备状态等。在此过程中,除了一些官方协议文件外,如wireshark它还支持大量的工业控制协议,以便于理解协议中某些字段的功能和命令。
私有协议分类
许多公开或私有协议可分为以下几类:
标准协议:国际标准或公认的标准协议,如Modbus、DNP3、IEC104等。
私人披露:只有制造商自己的设备支持并提供官方协议文件,如Omron FINS协议、三菱Melsec协议等。
私有不公开:只有厂家自己的设备支持,官方不提供协议文件,如S7、西门子PPI协议、GE SRTP等。
二、使用Wireshark分析常见的工控协议
Wireshark它是一种强大的开源流量和协议分析工具。除了传统的网络协议解码外,它还支持许多主流和标准工业控制协议的分析和解码。为此,我整理出来了Wireshark涉及自控协议的源代码packet dissection实现代码路径供您参考和保留。
/tr>协议类型 | 简介 | 源码下载 |
Siemens S7 | 西门子PLC支持通信协议 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-s7comm.c |
MMS(IEC61850) | 输配电通信协议 | https://github.com/wireshark/wireshark/tree/master/asn1/mms |
GOOSE(IEC61850) | 输配电通信协议 | https://github.com/wireshark/wireshark/tree/master/asn1/goose |
SV(IEC61850) | 输配电通信协议 | https://github.com/wireshark/wireshark/tree/master/asn1/sv |
Modbus | 工控标准协议 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-mbtcp.c |
OPC DA | 工控标准协议 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-dcom.c |
FF HSE | 基金会现场总线以太网通信协定 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-ff.c |
IEC 104 | 输配电通讯协议 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-iec104.c |
Ethernet POWERLINK | 开放式实时以太网通信 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-epl.c |
OPC UA | OPC新一代标准 | https://github.com/wireshark/wireshark/tree/master/plugins/opcua/opcua.c |
HART-IP | 高速可寻址远程传感器协议 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-hartip.c |
CoAP | 轻量应用层协议 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-coap.c |
Omron FINS | 欧姆龙PLC支持的通讯协定 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-omron-fins.c |
openSAFETY | 开源安全应用协议 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-opensafety.c |
EGD(Ethernet Global Data) | GE Fanuc为PLC开发的通讯协定 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-egd.c |
DNP3 | 分布式网络协议,主要用于电力行业 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-dnp.c |
Sinec H1 | 西门子PLC支持的通讯协议 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-h1.c |
Profinet | 开放式的工业以太网通讯协定 | https://github.com/wireshark/wireshark/tree/master/plugins/profinet/ |
EtherCAT | 德国Beckhoff公司推动的开放式实时以太网通讯协定 | https://github.com/wireshark/wireshark/tree/master/plugins/ethercat/ |
SERCOS III | 实时以太网通讯协定 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-sercosiii.c |
RTPS | 实时流传输协议 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-rtps.c |
TTEthernet | 实时以太网通讯协定 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-tte.c |
CDT | 远动规约 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-cdt.c |
EtherNet/IP | 工业通讯协定(Industrial Protocol),是一种CIP的实现方式,由罗克韦尔自动化公司所设计 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-etherip.c |
CIP | 通用工业协定 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-cip.c |
CIP Safety | 安全通用工业协定 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-cipsafety.c |
DeviceNet | 一种CIP的实现方式,由Allen-Bradley公司所设计 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-devicenet.c |
BACnet | 楼宇自动控制网络数据通讯协议 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-bacnet.c |
KNXnet/IP | 住宅和楼宇控制标准 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-knxnetip.c |
Lontalk | 埃施朗公司的LonWorks技术所使用的通讯协议 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-lon.c |
CANopen | 控制局域网通讯协定 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-canopen.c |
SAE J1939 | 一种CAN的变种,适用在农业车辆及商用车辆 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-j1939.c |
USITT DMX512-A | 灯光控制数据传输协议 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-dmx.c |
BSSAP/BSAP | 由Bristol Babcock Inc发展的通讯协定 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-bssap.c |
Gryphon | 车用通讯协定 | https://github.com/wireshark/wireshark/tree/master/plugins/gryphon |
ZigBee | 开放式的无线通讯协定 | https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-zbee.h |
三、wireshark导入第三方插件
一道工控流量分析的CTF题,要对s7commplus的流量进行分析。发现Wireshark不支持s7comm-plus协议的解析,需要自己下载插件。
wireshark如何导入s7comm-plus插件?
s7comm-plus插件下载地址:S7comm Wireshark dissector plugin download | SourceForge.net
也可移步到:
(573条消息) wireshark解析s7comm-plus插件-网络基础文档类资源-CSDN文库
安装方式
将zip文件解压后把s7comm-plus.dll动态库拷贝到wireshark安装目录下的plugins/epan目录下,重启wireshark即可。
四、使用Wireshark解析opcDA/opcUA协议
1、Wireshark解析不到opcDA这一层。
2、Wireshark解析不到opcUA这一层。
解决方法:
编辑—— 首选项 —— 高级(也可以在protocols里找到opcua去改,在这里可以搜索方便一些)。
添加opcua的端口 —— 确认(OK)。
然后在协议首选项选择opcua就可以了。
五、Lua脚本编写Wireshark插件
在分析工控设备流量时,通过Wireshark内置的协议解码插件可以解析一些开放协议的数据格式,但是很多厂家考虑到安全性和产品独特性并不会公开私有的报文格式。这就需要通过逆向工程或者查阅相关文档来了解通讯协议的数据格式,从而编写Wireshark协议解码插件来解析未知的工业网络通讯数据报文格式。
协议分析
我们以一个国产某工控设备的上位机流量作为例子,如下:
使用Wireshark抓取到的流量如下,可以看到该数据报文是基于TCP协议的,下位机的端口为500。
由于上位机是C#写的,可以用dnSpy快速定位到其协议解析的逻辑代码,通过静态分析和动态调试的手段来分析出上位机和下位设备之间通信的报文格式。
该报文简单格式如下表格,这里只分析到报文格式的头部,不过对于工业网络通讯数据分析来说,识别出该报文对应的业务操作已经足够了。
编写插件
Lua脚本
目前对于Wireshark来说,C/C语言和Lua脚本是编写插件的主流语言,对于C/C语言这类语言来说,不可否认它具有非常高的性能,但是其编译配置较为麻烦,每次修改都要重新编译,而Lua脚本虽然解析效率没前者那么高,但是它的语法和修改都非常简单,可以提高了开发效率。所以,这里选择了Lua作为编写插件的语言。
Wireshark软件是否支持Lua插件脚本的检查方法:启动Wireshark,依次点击”帮助”,”关于 Wireshark“菜单,在打开的对话框中的”Wireshark”标签页上观察版本信息,如果如下图一样显示With Lua,说明此版本支持Lua插件。
编写插件
1. 开始编写Lua插件,首先简单定义协议名称和协议说明。
Echo_protocol=Proto("Echo_500","Echo_500 protocol")
2. 根据不同的报文字段类型添加每个字段的名称。
pktid = ProtoField.uint32("Echo_500.ID", "ID", base.DEC)
pktlen = ProtoField.uint32("Echo_500.length", "length", base.DEC)
station=ProtoField.uint32("Echo_500.station", "station", base.DEC)
cmd = ProtoField.uint32("Echo_500.cmd", "cmd", base.DEC,cmd_desc)
address = ProtoField.uint32("Echo_500.address", "address", base.DEC)
subcmd = ProtoField.uint32("Echo_500.subcmd", "subcmd", base.DEC,subcmd_desc)
operate = ProtoField.uint32("Echo_500.operate", "operate", base.DEC,operate_desc)
subaddress = ProtoField.uint32("Echo_500.subaddress", "subaddress", base.DEC)
datalen = ProtoField.uint32("Echo_500.datalen", "datalen", base.DEC)
data = ProtoField.bytes("Echo_500.Data", "Data")
3. 构建一个解析器函数,根据每个字段的偏移和大小,对每个字段进行显示。
function Echo_protocol.dissector(buffer,pinfo,tree)
local length=buffer:len()
if length<14 then return end
pinfo.cols.protocol=Echo_protocol.name
local subtree=tree:add(Echo_protocol,buffer(),"Echo Protocol Data")
subtree:add(pktid,buffer(0,4))
subtree:add(pktlen,buffer(4,2))
subtree:add(station,buffer(6,1))
subtree:add(cmd,buffer(7,1))
subtree:add(address,buffer(8,1))
subtree:add(subcmd,buffer(9,1))
sub_cmd=buffer(9,1):uint()
subtree:add(operate,buffer(10,1))
Operate=buffer(10,1):uint()
subtree:add(subaddress,buffer(11,2))
subtree:add(datalen,buffer(13,1))
if length>14 then
local databuf=buffer(14,length-14)
local data_subtree=subtree:add(data,databuf)
end
end
4. 最后将编写好的解析器添加到TCP 500端口上。
local tcp_port=DissectorTable.get("tcp.port")
tcp_port:add(500, Echo_protocol)
插件代码如下:
--Etrol plugin
local subcmd_desc={
[1]="SYSCOM",
[2]="SCANCOM",
[3]="EVENCOM",
[4]="PIDCOM",
[5]="PIDPARA",
[6]="SYSMSG",
[7]="HARTCOM",
[8]="SYSRESET",
[17]="DNP3DBCOM",
[18]="DNP3CHN0COM",
[19]="ZIGBEECOM",
[20]="DNP3OTHER",
}
local operate_desc={
[0]="CLRMOD",
[1]="READCOM",
[2]="WRITECOM",
[4]="WRITEFLASH",
}
local cmd_desc={
[137]="read/write",
[144]="PROG_update",
}
Echo_protocol=Proto("Echo_500","Echo_500 protocol")
pktid = ProtoField.uint32("Echo_500.ID", "ID", base.DEC)
pktlen = ProtoField.uint32("Echo_500.length", "length", base.DEC)
station=ProtoField.uint32("Echo_500.station", "station", base.DEC)
cmd = ProtoField.uint32("Echo_500.cmd", "cmd", base.DEC,cmd_desc)
address = ProtoField.uint32("Echo_500.address", "address", base.DEC)
subcmd = ProtoField.uint32("Echo_500.subcmd", "subcmd", base.DEC,subcmd_desc)
operate = ProtoField.uint32("Echo_500.operate", "operate", base.DEC,operate_desc)
subaddress = ProtoField.uint32("Echo_500.subaddress", "subaddress", base.DEC)
datalen = ProtoField.uint32("Echo_500.datalen", "datalen", base.DEC)
data = ProtoField.bytes("Echo_500.Data", "Data")
hex=function(num) return string.format("%#x",num) end
Echo_protocol.fields={pktid,pktlen,station,cmd,address,subcmd,operate,subaddress,datalen,data}
function Echo_protocol.dissector(buffer,pinfo,tree)
local length=buffer:len()
if length<14 then return end
pinfo.cols.protocol=Echo_protocol.name
local subtree=tree:add(Echo_protocol,buffer(),"Echo Protocol Data")
subtree:add(pktid,buffer(0,4))
subtree:add(pktlen,buffer(4,2))
subtree:add(station,buffer(6,1))
subtree:add(cmd,buffer(7,1))
subtree:add(address,buffer(8,1))
subtree:add(subcmd,buffer(9,1))
sub_cmd=buffer(9,1):uint()
subtree:add(operate,buffer(10,1))
Operate=buffer(10,1):uint()
subtree:add(subaddress,buffer(11,2))
subtree:add(datalen,buffer(13,1))
if length>14 then
local databuf=buffer(14,length-14)
local data_subtree=subtree:add(data,databuf)
end
end
local tcp_port=DissectorTable.get("tcp.port")
tcp_port:add(500, Echo_protocol)
运行插件
为了Wireshark加载编写好的插件,需要打开Wireshark主目录下的init.lua文件,确保disable_lua的值为false,即开启了lua。同时将编写好的插件保存为echo_500.lua放到Wireshark主目录,在init.lua文件最后一行添加dofile(DATA_DIR…“echo_500.lua”),通过这样的方式加载自定义的插件。
重启Wireshark,重新抓取数据包,可以看到协议已经解析成功。这里识别出来这是一个SYSMSG命令,用于读取设备状态。
之后每次修改脚本后,可以通过快捷键“Ctrl+Shift+L”重新加载脚本,不需要每次重启Wireshark软件。
总结
通过编写插件来解析第三方的私有协议,帮助我们更好理解工控上位机和下位机的交互。当然,有些工控流量可能比本文的例子相对复杂一些,可能包含加密和签名等字段,这就需要更长的逆向分析时间,只要把协议数据的报文格式了解清楚了,那么编写插件也是水到渠成的事情。