0、前言
在ICT人员用于网络分析的兵器库中,wireshark无疑是倚天剑,虽历史悠久,其锋利程度丝毫不减,由于开源,便于用户二次开发,这就使得此剑的颜值、功能都近乎完美。如果能够熟练的使用此剑,对于行走江湖也是百利而无一害。
对于当下的主流协议wireshark都有自带解析插件,如IP、ARP、TCP、UDP、HTTP、DHCP等。但是实际应用中,这些协议通常只是我们传输数据过程的载体,有不少软件之间的通信协议都是私有的,如游戏客户端和服务器之间的交互协议通常都是私有的,Wireshark无法具体解析出各种字段之间的含义,只能显示接收到的二进制数据,给协议的分析和问题的排查带来了一定的困难,尤其是协议内容比较复杂时。
本文从一个自定义的简单消息协议入手,分析如何通过lua来编写wireshark插件来解析自定义消息协议。欢迎大神指正,赐教
1、消息结构
假设需要通过TCP来传输的数据结构MSG如下:
#define DATA_LEN_MAX 1000
#define LISTEN_MAX 1024
#define SERV_PORT 8001
#define LOCAL_PORT (SERV_PORT + 1)
#define MSG_BIT1_SYN 0
#define MSG_BIT1_ACK 1
#define MSG_BIT1_ERR 2
#define MSG_BIT1_UNKOWN 3
#define MSG_BIT2_RUN 1
#define MSG_BIT2_STOP 0
#define MSG_BIT2_SHUTDOWN 2
#define MSG_BIT2_REBOOT 3
#define MSG_BIT2_IDEL 4
#define MSG_BIT2_ERROR 7
#define MSG_BIT4_TRUE 1
#define MSG_BIT4_FALSE 0
typedef struct APP_MSG_HEADER{
unsigned char msg_no;
unsigned char msg_version;
}MSG_HEADER;
typedef struct APP_MSG{
MSG_HEADER msg_header;
unsigned char msg_len;
unsigned char msg_bit1:2;
unsigned char msg_bit2:3;
unsigned char msg_bit3:2;
unsigned char msg_bit4:1;
unsigned int local_id;
unsigned int remote_id;
}MSG;
该消息MSG(12Byte)+业务数据(988Byte)一起传输,抓取的报文如下:
通过wireshark可以知道每一个字段的内容,但如果消息结构复杂,需要计算偏移方可获取到指定字段值;再者,如果消息结构中含有位域字段则更加复杂,伤眼睛;最后,无法使用消息字段中的指定字段进行报文过滤操作。基于以上几点原因,为了帮助分析,编写插件脚本分析势在必行。
2、插件编写
通过lua语言可以编写插件实现对MSG的解析,具体脚本msg_analysis.lua如下:
-- @brief Implementation of custom protocol parsing through Lua script programming
-- @author wang quan
-- @date 2019.09.06
local VALS_BIT_1_3 = {[0x0] = "SYN", [0x1] = "ACK", [0x2] ="ERR", [0x3] = "UNKOWN"}
local VALS_BIT_2 = {[0x0] = "STOP", [0x1] = "RUN", [0x2] ="SHUTDOWN", [0x3] = "REBOOT",[0x4] = "IDLE",[0x7] = "ERR"}
local VALS_BIT_4 = {[0] = "False", [1] = "True"}
-- 1. 创建解析器对象
local NAME = "CUST_MESS_PROTO" --自定义的协议名
local MsgProto = Proto(NAME, "Custom Message over TCP Protocol")
-- MsgProto 定义协议的解析字段
local fields = MsgProto.fields
fields.msg_no = ProtoField.uint8(NAME .. "MSG_NO", "msg_no", base.DEC)
fields.msg_version = ProtoField.uint8(NAME .. "MSG_VERSION", "msg_version", base.DEC)
fields.msg_len = ProtoField.uint8(NAME .. "MSG_LEN", "msg_len", base.DEC)
fields.length = ProtoField.uint32(NAME .. "LENGTH", "[Length]", base.DEC) --数据总长度说明
fields.data_length = ProtoField.uint32(NAME .. "DATA_LENGTH", "[DataLength]", base.DEC)--应用数据长度说明
fields.msg_bitx = ProtoField.uint8(NAME .. "MSG_BITX", "msg_bitx", base.HEX) -- 位域定义
fields.msg_bit1 = ProtoField.uint8("MSG_BIT1", "msg_bit1", base.DEC,VALS_BIT_1_3,0x3)
fields.msg_bit2 = ProtoField.uint8("MSG_BIT2", "msg_bit2", base.DEC,VALS_BIT_2,0x1C)
fields.msg_bit3 = ProtoField.uint8("MSG_BIT3", "msg_bit3", base.DEC,VALS_BIT_1_3,0x60)
fields.msg_bit4 = ProtoField.uint8("MSG_BIT4", "msg_bit4", base.DEC,VALS_BIT_4,0x80)
fields.local_id = ProtoField.uint32("LOCAL_ID", "local_id", base.DEC)
fields.remote_id = ProtoField.uint32("REMOTE_ID", "remote_id", base.DEC)
local data_dis = Dissector.get("data")
-- 2. 解析器函数 dissect packet
--[[
下面定义 foo 解析器的主函数,这个函数由 wireshark调用
第一个参数是 tvb 类型,表示的是需要此解析器解析的数据
第二个参数是 Pinfo 类型,是协议解析树上的信息,包括 UI 上的显示
第三个参数是 TreeItem 类型,表示上一级解析树
--]]
function MsgProto.dissector (tvb, pinfo, tree)
--从根树上创建一个子树打印解析的消息数据
local subtree = tree:add(MsgProto, tvb())
subtree:append_text(", msg_no: " .. tvb(0, 1):uint())
-- 分组详情中协议行显示的协议名
pinfo.cols.protocol = MsgProto.name
tvb_length = tvb:len()
-- dissect field one by one, and add to protocol tree
--消息中包括一个消息头,继续创建树解析
local msg_head_tree = subtree:add(MsgProto, tvb(0,2),"MSG_HEADER") --"MSG_HEADER"参数会取代协议名显示
msg_head_tree:add(fields.msg_no, tvb(0, 1))--表示从0开始1个字节
msg_head_tree:add(fields.msg_version, tvb(1, 1))
subtree:add(fields.msg_len, tvb(2,1))
subtree:add(fields.length, tvb_length) --显示数据片长度信息,不从片内存从取数据
subtree:add(fields.data_length, tvb_length-8)
-- 位域继续创建树解析
local msg_bitx_tree = subtree:add( fields.msg_bitx, tvb(3,1) ) -- bitfield
msg_bitx_tree:add(fields.msg_bit1,tvb(3,1))
msg_bitx_tree:add(fields.msg_bit2,tvb(3,1))
msg_bitx_tree:add(fields.msg_bit3,tvb(3,1))
msg_bitx_tree:add(fields.msg_bit4,tvb(3,1))
subtree:add_le(fields.local_id,tvb(4,4))
subtree:add_le(fields.remote_id,tvb(8,4))
data_dis:call(tvb(12):tvb(), pinfo, tree) --解析数据流中除消息结构后面的数据,值得注意的是call的参数名必须为tvb,???,希望有大佬不吝赐教一下
end
-- 3 将解析器注册到wireshark解析表 register this dissector
local udp_port_table = DissectorTable.get("tcp.port")
--添加解析的TCP端口,根据端口号识别协议
for i,port in ipairs{8001,8002} do
udp_port_table:add(port,MsgProto)
end
3、插件使用
lua语言为弱语言,无需编译。1、直接将该脚本文件在wireshark安装目录中,2、再在当前路径下找到init.lua文件,在其中结尾处添加dofile(DATA_DIR.."msg_analysis.lua")语句,3、重启wireshark。语句具体如下:
4、结果展示
4.1、CUST_MESS_PROTO解析显示
通过加载脚本插件后可以看出原来的传输数据已经被解析成自定义协议CUST_MESS_PROTO
4.2、CUST_MESS_PROTO树状显示
4.3、报文过滤显示
通过消息结构中的字段MSG_BIT3 == 1进行报文过滤,具体如下: