前言

在分析工控设备流量时,通过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插件,首先简单定义协议名称和协议说明。

  1. 根据不同的报文字段类型添加每个字段的名称。


在这里,可以将不同的命令解释显示出来,从而增加可读性,如下:

  1. 构建一个解析器函数,根据每个字段的偏移和大小,对每个字段进行显示。

  1. 最后将编写好的解析器添加到TCP 500端口上。

  1. 最后的插件代码如下:
--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) endEcho_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)    endendlocal 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软件。

总结

本文简单介绍了Wireshark插件的编写方法,通过编写插件来解析第三方的私有协议,帮助我们更好理解工控上位机和下位机的交互。当然,有些工控流量可能比本文的例子相对复杂一些,可能包含加密和签名等字段,这就需要更长的逆向分析时间,只要把协议数据的报文格式了解清楚了,那么编写插件也是水到渠成的事情。

转载请注明来自:工业互联网安全应急响应中心(微信号ICSCERT)

©著作权归作者所有:来自51CTO博客作者mob604756ef5a44的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. 2、AP上线的那些事儿(1)capwap建立过程、设备初始化以及二层上线
  2. 前端技巧:如何使用nodejs实现举牌人表情包?
  3. TCP/IP之TCP报文简介
  4. 1.3 Ansible 整体架构图
  5. 轻量化 Jenkins 最佳实践
  6. 粉丝福利 | 秒 get 支付宝同款扫码组件
  7. 社区版本idea查看继承关系的骚操作
  8. 多厂商***系列之十九:***技术中的MTU问题总结【部署***常遇到的现
  9. thinkphp6.0 开启多应用

随机推荐

  1. Android自动化测试工具——Monkey
  2. android点滴
  3. android 布局实例解析 柱状图效果
  4. 开源终端Android Terminal Emulator
  5. 系出名门Android(2) - 布局(Layout)和菜
  6. Freeline 一款 Android平台上的秒级编译
  7. Android下使用Socket连接网络电脑
  8. LinearLayout 内部控件居中
  9. android系统权限关机重启
  10. 【Android】Android(安卓)签名相关问题