在分析工控设备流量时,经过Wireshark内置的协议解码插件能够解析一些开放协议的数据格式,可是不少厂家考虑到安全性和产品独特性并不会公开私有的报文格式。这就须要经过逆向工程或者查阅相关文档来了解通信协议的数据格式,从而编写Wireshark协议解码插件来解析未知的工业网络通信数据报文格式。安全
本文中,咱们以一个国产某工控设备的上位机流量做为例子,以下:微信
使用Wireshark抓取到的流量以下,能够看到该数据报文是基于TCP协议的,下位机的端口为500。网络
因为上位机是C#写的,能够用dnSpy快速定位到其协议解析的逻辑代码,经过静态分析和动态调试的手段来分析出上位机和下位设备之间通讯的报文格式。tcp
该报文简单格式以下表格,这里只分析到报文格式的头部,不过对于工业网络通信数据分析来讲,识别出该报文对应的业务操做已经足够了。ide
目前对于Wireshark来讲,C/C++语言和Lua脚本是编写插件的主流语言,对于C/C++语言这类语言来讲,不能否认它具备很是高的性能,可是其编译配置较为麻烦,每次修改都要从新编译,而Lua脚本虽然解析效率没前者那么高,可是它的语法和修改都很是简单,能够提升了开发效率。因此,这里选择了Lua做为编写插件的语言。函数
Wireshark软件是否支持Lua插件脚本的检查方法:启动Wireshark,依次点击”帮助”,”关于 Wireshark“菜单,在打开的对话框中的”Wireshark”标签页上观察版本信息,若是以下图同样显示With Lua,说明此版本支持Lua插件。性能
在这里,能够将不一样的命令解释显示出来,从而增长可读性,以下:ui
--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命令,用于读取设备状态。lua
以后每次修改脚本后,能够经过快捷键“Ctrl+Shift+L”从新加载脚本,不须要每次重启Wireshark软件。
本文简单介绍了Wireshark插件的编写方法,经过编写插件来解析第三方的私有协议,帮助咱们更好理解工控上位机和下位机的交互。固然,有些工控流量可能比本文的例子相对复杂一些,可能包含加密和签名等字段,这就须要更长的逆向分析时间,只要把协议数据的报文格式了解清楚了,那么编写插件也是水到渠成的事情。
转载请注明来自:工业互联网安全应急响应中心(微信号ICSCERT)