一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
WireShark 使用 Lua 编写脚本插件解析 RocketMQ 协议
WireShark 可谓是抓包神器,除了本身支持解析 TCP 、 UDP 、 HTTP 等各种协议,更是可以通过编写插件来解析自定义的协议。
先来看看我们最终解析出来的 RocketMQ 协议,可以看到通过 WireShark 我们可以看到协议包的具体发送内容
RocketMQ协议包说明
协议包可以划分为四段,前两段是固定 4 个字节
- 第一段( 4 字节) : 记录后面三部分的长度
- 第二段( 4 字节) : 记录第三段(Header )的长度
- 第三段 : Header 协议头( json 序列化的数据)
- 第四段 : Body 协议体(自定义二进制序列化的数据,不一定是 json 格式)
怎样去实现自定义协议的解析呢?
WireShark 官方文档有介绍,这里贴个链接,感兴趣的老哥可以去研究一波
大体思路
-
编写解析 RocketMQ 协议的 protocol_rk.lua 脚本
-
打开 Golobal configuration 目录
-
找到目录下的 init.lua 并在最后一行写入
dofile("protocol_rk.lua")
这行代码用于引入协议 lua 脚本
脚本编写
-
创建一个协议对象 Proto
Proto.new(名称,描述)
-
实现协议的解析器
实现方法 dissector(tvb, pinfo, tree)
- tvb : 代表数据包的缓冲区
- pinfo : 数据包信息,如源端口号、目标端口号、桢长度等
- tree : UI 树
-
端口号和协议解析器绑定
调用 DissectorTable.get("tcp.port"):add(dst_port, rmProtocol)
--- --- Generated by EmmyLua(https://github.com/EmmyLua) --- Created by MinXie. --- DateTime: 2022/4/27 6:02 PM --- tosee https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Proto.html#lua_class_Proto --- 创建一个Proto对象, --- Wireshark 中的新协议。协议有多种用途。主要是剖析协议,但它们也可以是用于注册偏好以用于其他目的的假人。 --- Proto.new(名称,描述) local rmProtocol = Proto("rocketmq", "Rocketmq Protocol") --- 目标端口 local dst_ports = { 9876, 10911 } --- http://dkolf.de/src/dkjson-lua.fsl/home 使用文档 local json = dofile("json.lua") --- 检索或分配。 --- tvb: 代表数据包的缓冲区 --- pinfo: 数据包信息,如源端口号、目标端口号、桢长度等 --- tree: ui树 function rmProtocol.dissector(tvb, pinfo, tree) end --- DissectorTable 特定协议的子解析器表(例如,http、smtp、sip 等 TCP 子解析器被添加到表“tcp.port”中)。 --- 有助于将更多解剖器添加到表中,以便它们出现在“解码为...”对话框中。 for _, dst_port in pairs(dst_ports) do DissectorTable.get("tcp.port"):add(dst_port, rmProtocol) end
dissector(tvb, pinfo, tree) 方法的实现
--- 检索或分配。
--- tvb: 代表数据包的缓冲区
--- pinfo: 数据包信息,如源端口号、目标端口号、桢长度等
function rmProtocol.dissector(tvb, pinfo, tree)
local subtree = tree:add(rmProtocol, tvb:range(tvb:len()))
pinfo.cols['protocol'] = rmProtocol.name
if isRequest(pinfo) then
pinfo.cols['info'] = "[Request]"
else
pinfo.cols['info'] = "[Response]"
end
--- rocketmq 包,分四段
--- 第一段(4字节): 记录后面三部分的长度
--- 第二段(4字节): 记录第三段的长度
--- 第三段: header
--- 第四段: body
local allLen = tvb(0, 4):uint()
local headerLen = tvb(4, 4):uint()
subtree:add("Len:", allLen)
subtree:add("Header Len:", headerLen)
subtree:add("Header:", tvb(8, headerLen):string())
subtree:add("Body:", tvb(8 + headerLen, allLen - headerLen - 4):string())
pcall(str2struct, subtree:add("HeaderData:", ""), tvb(8, headerLen):string())
pcall(str2struct, subtree:add("BodyData:", ""), tvb(8 + headerLen, allLen - headerLen - 4):string())
end
--- 数据包的目标端口是9876时为请求
function isRequest(pinfo)
--- pinfo.dst_port
--- 此数据包的目标端口。
local dstPort = pinfo.dst_port;
for _, dst_port in pairs(dst_ports) do
if dstPort == dst_port then
return true
end
return false
end
end
function str2struct(tree, headerStr)
local table = json.parse(headerStr, 1, "}")
handleTable(tree, table)
end
function handleTable(tree, table)
for k, v in pairs(table) do
if type(v) == "table" then
handleTable(tree:add(k .. ":", ""), v)
else
tree:add(k .. ":", v)
end
end
end
-
显示协议的名字
pinfo.cols['protocol'] = rmProtocol.name
-
区别是请求还是响应类型
这里可以通过 pinfo.dst_port 获取目标端口号进行判断
-
解析RocketMQ协议包
- 获取第一段的内容,存储的是后面三段的长度 local allLen = tvb(0, 4):uint()
- 获取第二段的内容,存储的是第三段的长度 local headerLen = tvb(4, 4):uint()
- 获取第三段内容,是协议头,json 序列化格式 tvb(8, headerLen):string()
- 获取第四段内容,是协议体,tvb(8 + headerLen, allLen - headerLen - 4):string()
- 分别加入到UI树里面进行显示,subtree:add(key , value)
-
将 Header 、 Body 解析为树的结构
调用str2struct方法
str2struct 方法实现
str2struct 方法做的是将json格式的字符串添加到 UI 树里面
里面调用了 json.parse 方法,可以将json转化为一个table
然后不断递归添加到子树上
实现Tree化结构
这样我们就可以非常直观地知道 RocketMQ 底层在通讯的时候,发送的具体内容是什么
像上图的协议头里面,我们就可以清楚地知道发送的 code 是 103 ,version 是 399 ,extFields 里面的 Broker 的参数信息,序列化的协议是 JSON 等
最后,贴一个很好用的 json 脚本库 json.lua
快动手玩起来吧,有不懂的可以在留言区留言