protobuf是google团队开发的用于高效存储和读取结构化数据的工具。相比于json和xml,protobuf会把数据压缩得更小,大约是json格式的1/10,xml格式的1/20。正因如此,protobuf编码后的数据,不能像json、xml那样直观地呈现数据。本文将介绍如何解析利用wireshark自定义插件,解析protobuf数据包。
wireshark的插件可以使用c或是lua脚本进行开发,考虑到灵活性,推荐使用lua脚本。
我理解wireshark插件的执行过程就是用插件去解析每一个数据包,解析过程中如果出错,则继续由其他插件尝试解析;如果解析正确,则表示由该协议解析并显示结果。
值得注意的是,当插件只处理了部分数据时,未处理部分的数据切片要继续交由其他插件尝试解析。例如一个基于TCP协议的RTSP数据包依次由Ethernet、Internet、TCP、RTSP插件解析,Ethernet协议解析该数据包的0 - 13byte,Internet协议解析该数据包的14 - 33byte,TCP协议解析了34 - 65byte,RTSP协议解析剩余的274 byte,至此数据包解析完成。
#环境是ubuntu 64位,windows开发笔者比较弱鸡
$sudo apt-get update
$sudo apt-get install wireshark
wireshark中lua版本是5.2,所以需要先安装lua5.2
$sudo apt-get update
$sudo apt=get install lua5.2
克隆lua-protobuf项目并编译
$git clone https://github.com/starwing/lua-protobuf.git
$gcc -O2 -fPIC -I/usr/include/lua5.2 -c pb.c -o pb.o
$gcc -shared -o pb.so -L/usr/local/lib pb.o
我们通过一个例子来说明如何创建一个插件:
message Person{
required string name = 1;
required int32 age = 2;
optional string email = 3;
enum PhoneType{
HOME = 1;
MOBILE = 2;
WORK = 3;
}
message Phone{
required int64 id = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated Phone phoneNum = 4;
}
大体思路如下:
1.创建一个wireshark插件,完成我们自定义数据包的识别,并提取协议标识、消息ID、数据长度、以及protobuf数据;
2.使用protobuf lua兼容库解析步骤1中提取出的protobuf数据,并显示;
wireshark的自定义插件在\usr\lib\x86_64-linux-gnu\wireshark\plugins目录下,在该文件下创建一个xx.lua文件。
一个典型的wireshark插件代码结果如下:
do
local struct = Struct
local data_dis = Dissector.get("data")
-- 协议名称为 meteoric_proto,在Packet Details窗格显示为 XXX Protocol
-- 注意这里的Proto与protobuf一点关系也没有。
local m_MeteoricProto = Proto("meteoric_proto","XXX Protocol")
-- 添加协议字段ident、id、length和data
local f_ident= ProtoField.bytes("meteoric_proto.ident","ident")
local f_id= ProtoField.int32("meteoric_proto.id","ID")
local f_len= ProtoField.int32("meteoric_proto.len","length")
local f_data = ProtoField.bytes("meteoric_proto.data","Data")
m_MeteoricProto .fields = { f_ident, f_id, f_len, f_data }
--定义协议解析函数,解析正确则返回true,反之返回false
local function Meteoric_dissector(buffer, pinfo, tree)
--取得数据长度
local buf_len = buffer:len()
--长度检测
if buf_len < 8 then return false end
--头检测
if (buffer(0,1):uint()~=0x00 or
buffer(1,1):uint()~=0x00 or
buffer(2,1):uint()~=0x00 or
buffer(3,1):uint()~=0x01) then
return false
end
--字段解析
tree:add(f_ident, buffer(0,4))
tree:add(f_id , buffer(4, 6):uint())
tree:add(f_len , buffer(6, 8):uint())
--这里只提取出了protobuf数据,并没有开始解析protobuf。
tree:add(f_data , buffer(8, buf_len))
--设置协议显示
pinfo.cols.protocol:set("XX_Protobuf")
return true
end
function m_MeteoricProto.dissector(buffer, pinfo, tree)
--在主窗口的 Protocol 字段显示的名称为 XX_Protobuf
if Meteoric_dissector(buffer, pinfo, tree) then
else
-- data 这个 dissector 几乎是必不可少的; 当发现不是我的协议时, 交由其他协议尝试解析
data_dis:call(buffer, pinfo, tree)
end
end
--将该协议对象添加到tcp 7200端口上
DissectorTable.get("tcp.port"):add(7200, m_MeteoricProto)
end
首先生成将Person.proto生成.pb文件备用:
$protoc -o Person.pb Person.proto
--加载pb对象
local pb = require "pb"
--载入pb文件 ,可以载入多个文件
pb.loadfile "Person.pb"
--反序列化,结果类型为table,
msgtable = pb.decode("Person", buffer:string())
--结果递归遍历显示
AddTreeNode(f_data, msgtable)
local function AddTreeNode(nodeTree, msgTable)
for k,v in pairs(msgTable) do
if type(v) == "table" then
AddTreeNode(nodeTree:add(k), v)
else
nodeTree:add(k..":", v)
end
end
end
do
local struct = Struct
local data_dis = Dissector.get("data")
-- 协议名称为 meteoric_proto,在Packet Details窗格显示为 XXX Protocol
-- 注意这里的Proto与protobuf一点关系也没有。
local m_MeteoricProto = Proto("meteoric_proto","XXX Protocol")
-- 添加协议字段ident、id、length和data
local f_ident= ProtoField.bytes("meteoric_proto.ident","ident")
local f_id= ProtoField.int32("meteoric_proto.id","ID")
local f_len= ProtoField.int32("meteoric_proto.len","length")
local f_data = ProtoField.bytes("meteoric_proto.data","Data")
m_MeteoricProto.fields = { f_ident, f_id, f_len, f_data }
-- protobuf解析结果递归显示
local function AddTreeNode(nodeTree, msgTable)
for k,v in pairs(msgTable) do
if type(v) == "table" then
AddTreeNode(nodeTree:add(k), v)
else
nodeTree:add(k..":", v)
end
end
end
--定义协议解析函数,解析正确则返回true,反之返回false
local function Meteoric_dissector(buffer, pinfo, tree)
--取得数据长度
local buf_len = buffer:len()
--长度检测
if buf_len < 8 then return false end
--头检测
if (buffer(0,1):uint()~=0x00 or
buffer(1,1):uint()~=0x00 or
buffer(2,1):uint()~=0x00 or
buffer(3,1):uint()~=0x01) then
return false
end
--字段解析
tree:add(f_ident, buffer(0,4))
tree:add(f_id , buffer(4, 6):uint())
tree:add(f_len , buffer(6, 8):uint())
tree:add(f_data , buffer(8, buf_len))
--加载pb对象
local pb = require "pb"
--载入pb文件 ,可以载入多个文件
pb.loadfile "Person.pb"
--反序列化,结果类型为table,
msgtable = pb.decode("Person", buffer(8, buf_len):string())
--结果递归遍历显示
AddTreeNode(f_data, msgtable)
--设置协议显示
pinfo.cols.protocol:set("XX_Protobuf")
return true
end
function m_MeteoricProto.dissector(buffer, pinfo, tree)
--在主窗口的 Protocol 字段显示的名称为 XX_Protobuf
if Meteoric_dissector(buffer, pinfo, tree) then
else
-- data 这个 dissector 几乎是必不可少的; 当发现不是我的协议时, 交由其他协议尝试解析
data_dis:call(buffer, pinfo, tree)
end
end
--将该协议对象添加到tcp 7200端口上
DissectorTable.get("tcp.port"):add(7200, m_MeteoricProto)
end
https://cloud.tencent.com/developer/article/1365481
https://www.jianshu.com/p/1e2f63a484d6