参考:
赵子清博客
wireshark官方文档
wireshark的lua插件由于最近研究SRT的使用,发现包并不能抓到,尽管wireshark内支持SRT包,但不知为何抓不到,只能看到下层的UDP包,因此只能自己另外写了,目前插件写完已经过去两周有余,一直想总结,都没时间。。。这次特地申请了加班过来写,记录下来,以备下次编写时查阅,其中博客部分内容搬运自赵子清的博客,链接贴在上方,同时加了很多自己的例子,希望能够帮助大家
表示一个新的Protocol,在Wireshark中Protocol对象有很多用处,解析器是其中主要的一个。主要接口有:
接口 | 说明 |
---|---|
proto:__call (name,desc) | 创建Proto对象。name和desc分别是对象的名称和描述,前者可用于过滤器等 |
proto.name | get名称 |
proto.fields | get/set字段 |
proto.prefs | get配置项 |
proto.init | 初始化,无参数 |
proto.dissector | 解析函数,3个参数tvb,pinfo,tree,分别是报文内容,报文信息和解析树结构 |
proto:register_heuristic (listname, func) | 为Proto注册一个启发式解析器,被调用时,参数func将被传入与dissector方法相同的3个参数 |
Proto举例:
local NAME = "bvc_srt"
local bvc_srt = Proto(NAME, "BVC_SRT Protocol")
-- 注册解析器
DissectorTable.get("udp.port"):add(PORT, bvc_srt)
表示协议字段,一般用于解析字段后往解析树上添加节点。根据字段类型不同,其接口可以分为两大类。
这些接口都会返回一个新的字段对象。方括号内是可选字段,花括号内是可替换的类型字段。
整型:
举例:
fields.time_stamp = ProtoField.uint32("bvc_srt.time_stamp", "Time Stamp", base.DEC)
其他类型
以IP地址的方式显示举例:
fields.peer_ipaddr = ProtoField.ipv4("bvc_srt.peer_ipaddr", "Peer IP address")
有的时候需要按位去显示某一些标志位,还有一个需求满足bvc_srt.FF_state== "[Middle packet]"
这样的查找方式,那就需要给FF_state加一个表去映射
-- FF这个标志位涉及第一个字节最高两个位
-- 最高两位对应二进制1100 0000 -> 0xc0
local FF_state_select = {
[0] = "[Middle packet]",
[1] = "[Last packet]",
[2] = "[First packet]",
[3] = "[Single packet]"
}
fields.FF_state = ProtoField.uint8("bvc_srt.FF_state", "FF state", base.HEX, FF_state_select, 0xC0)
-- 解析函数中直接这么处理就可以了
data_flag_info_tree:add(fields.FF_state, tvb(offset, 1))
Tvb(Testy Virtual Buffer)表示报文缓存,也就是实际的报文数据,可以通过下面介绍的TvbRange从报文数据中解出信息。主要接口有:
接口 | 说明 |
---|---|
tvb:__tostring() | 将报文数据转化为字符串,可用于调试 |
tvb:reported_len() | get tvb的(not captured)长度 |
tvb:len() | get tvb的(captured)长度 |
tvb:reported_length_remaining() | 获取当前tvb的剩余长度,如果偏移值大于报文长度,则返回-1 |
tvb:offset() | 返回原始偏移 |
用法举例
-- tvb(offset, 4)表示从offset开始之后的4个字节
subtree:add_le(fields.peer_ipaddr, tvb(offset, 4))
表示Tvb的可用范围,常用来从Tvb中解出信息。主要接口有
接口 | 说明 |
---|---|
tvb:range([offset], [length]) | 从tvb创建TvbRange,可选参数分别是偏移和长度,默认值分别是0和总长度 |
tvbrange:{type}() | 将tvbrange所表示范围内的数据转换成type类型的值,type包括但不限于:uint,uint64,int,int64,float,ipv4,ether,nstime,string,ustring,bytes,bitfield等,其中某些类型的方法可以带一些参数 |
emmm,这个部分基本没有用到过。。我编的时候没有用到。
报文信息(packet information)。主要接口有:
接口 | 说明 |
---|---|
pinfo.len pinfo.caplen | get报文长度 |
pinfo.abs_ts | get报文捕获时间 |
pinfo.number | get报文编号 |
pinfo.src pinfo.dst | get/set报文的源地址、目的地址 |
pinfo.columns pinfo.cols | get报文列表列(界面) |
使用举例
-- 修改协议名称(效果见下图)
pinfo.cols.protocol = bvc_srt.name
-- 为报文的信息尾部添加字符串(效果见下图)
pinfo.cols.info:append(" [ACK]")
-- 还有一种便是直接覆盖
pinfo.cols.info = "[ACK]"
表示报文解析树中的一个树节点。主要接口有:
接口 | 说明 |
---|---|
treeitem:add([protofield], [tvbrange], [value], [label]) | 向当前树节点添加一个子节点 |
treeitem:set_text(text) | 设置当前树节点的文本 |
treeitem:prepend_text(text) | 在当前树节点文本的前面加上text |
treeitem:append_text(text) | 在当前树节点文本的后面加上text |
还有注意一下网络字节序的问题,如果是网络字节序需要用add_le
添加节点~
添加节点举例
subtree:add(fields.dst_sock, tvb(offset, 4))
-- 子树其实也是一个节点,因此也需要在fields里面添加字段
fields.pack_type_tree = ProtoField.uint32(NAME .. ".pack_type_tree", "Packet Type", base.HEX)
-- 创建子树
pack_type_tree = subtree:add(fields.pack_type_tree, tvb(offset, 4))
pack_type_tree:add(fields.msg_type, tvb(offset, 2))
表示一个具体协议的解析表,比如,协议TCP的解析表”tcp.port”包括http,smtp,ftp等。可以依次点击wireshark菜单栏的视图->内部->解析器表->Integer Tables
,来查看当前的所有解析表。tcp.port解析表在“Integer tables”选项卡中,顾名思义,它是通过类型为整型的tcp端口号来识别下游协议的
这次我解析的是SRT的包,因此用的是UDP包,要通过UDP的端口去识别下游协议
接口 | 说明 |
---|---|
DissectorTable.get(name) | get名为name的解析表的引用 |
dissectortable:add(pattern, dissector) | 将Proto或Dissector对象添加到解析表,即注册。pattern可以是整型值,整型值范围或字符串,这取决于当前解析表的类型 |
dissectortable:remove(pattern, dissector) | 将满足pattern的一个或一组Proto、Dissector对象从解析表中删除 |
将srt注册到udp的协议下
DissectorTable.get("udp.port"):add(PORT, bvc_srt)
由于代码不方便直接贴在这里,毕竟在公司写的,不能乱分享,因此只给出大概写的思路
这部分包含了一个完整的解析器需要的部分:解析器对象,解析器函数,注册解析器到wireshark的解析表中
-- create a new dissector
local NAME = "bvc_srt"
local PORT = 1935
local bvc_srt = Proto(NAME, "BVC_SRT Protocol")
-- create fields of bvc_srt
local fields = bvc_srt.fields
local pack_type_select = {
[0] = "Data Packet",
[1] = "Control Packet"
}
fields.pack_type_tree = ProtoField.uint32(NAME .. ".pack_type_tree", "Packet Type", base.HEX)
-- dissect packet
function bvc_srt.dissector (tvb, pinfo, tree)
-- 解析函数内部逻辑
end
-- register this dissector
DissectorTable.get("udp.port"):add(PORT, bvc_srt)
报文中的每一段数据都有自己的名称,这些名称我们都需要添加到表中,因此需要创建对象存到fields中去,以下是一些字段的例子
-- create fields of bvc_srt
local fields = bvc_srt.fields
local pack_type_select = {
[0] = "Data Packet",
[1] = "Control Packet"
}
fields.pack_type_tree = ProtoField.uint32(NAME .. ".pack_type_tree", "Packet Type", base.HEX)
fields.pack_type = ProtoField.uint16("bvc_srt.pack_type", "Packet Type", base.HEX, pack_type_select, 0x8000)
fields.reserve = ProtoField.uint16("bvc_srt.reserve", "Reserve", base.DEC)
给出一部分解析Data Packet的代码,这段代码可以解析一部分的data的报文,其中包括了怎么在节点后添加信息,怎么创建子树,并在子树添加节点
-- dissect packet
function bvc_srt.dissector (tvb, pinfo, tree)
-- 解析函数内部逻辑
-- 0 -> Data Packet
pack_type_tree:add(fields.pack_type, tvb(offset, 2))
pack_type_tree:append_text(" (Data Packet)")
local seq_num = tvb(offset, 4):uint()
pinfo.cols.info:append(" (Data Packet)(Seq Num:" .. seq_num .. ")")
-- Data Packet,则前4字节为包序号
subtree:add(fields.seq_num, tvb(offset, 4))
offset = offset + 4
data_flag_info_tree = subtree:add(fields.data_flag_info_tree, tvb(offset, 1))
-- 处理FF标志位
local FF_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0xC0), 6)
if FF_state == 0 then
data_flag_info_tree:append_text(" [Middle packet]")
elseif FF_state == 1 then
data_flag_info_tree:append_text(" [Last packet]")
elseif FF_state == 2 then
data_flag_info_tree:append_text(" [First packet]")
else
data_flag_info_tree:append_text(" [Single packet]")
end
data_flag_info_tree:add(fields.FF_state, tvb(offset, 1))
end
上述例子中有关于FF_state的处理,bit.band
就是将给定的数与掩码进行与操作,得到的结果给了变量,然后做相关不同的处理,由于位操作比较多,因此经常用到
local FF_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0xC0), 6)
bit.rshift(a, b)
表示对a
向右移b
位bit.band()
则是对数进行按位与,参数可以有多个注意:位操作需要数据类型是整型,但是tvb中取出来的并非整型,需要用uint()
转换一下
由于lua的for循环并不支持在循环体内改变循环变量,其实这么做确实不安全,有的时候会死循环,但是在有些场景下,改变循环变量更方便处理逻辑,比如:
现在有一段数据,里面包含的是丢失包的序号:
那么我现在要处理这个数据,怎么办
lua中可以通过闭包的方式改变循环变量,即循环变量通过一个函数去改变,通过这个函数去做实际的处理,给出示例代码
local start = offset
local ending = tvb:len()
local lost_list_tree = subtree:add(fields.lost_list_tree, tvb(offset, ending - offset))
-- 每次start从function中去取,由function去控制变量
for start in function()
local first_bit = bit.rshift(tvb(start, 1):uint(), 7)
if first_bit == 1 then
local lost_pack_range_tree = lost_list_tree:add(fields.lost_pack_range_tree, tvb(start, 8))
local lost_start = bit.band(tvb(start, 4):uint(), 0x7FFFFFFF)
lost_pack_range_tree:append_text(" (" .. lost_start .. " -> " .. tvb(start + 4, 4):uint() .. ")")
lost_pack_range_tree:add(fields.lost_start, tvb(start, 4), lost_start)
start = start + 4
lost_pack_range_tree:add(fields.up_to, tvb(start, 4))
start = start + 4
else
lost_list_tree:add(fields.lost_pack_seq, tvb(start, 4))
start = start + 4
end
return start
end
do
if start == ending then
break
end
end
参考资料:lua闭包
lua中没有switch-case的方式,只能通过函数数组去搞,其实c语言中也是这么去实现的,用函数指针数组。
比如有多种type的包,每种type数据都不一样,那要么用if else
,但是我想用switch啊,那么lua中就如下面例子中这么处理~
local switch = {
[1] = function()
-- parse data
end,
[2] = function()
-- parse data
end,
[3] = function()
-- parse data
end,
[4] = function()
-- parse data
end
}
-- 处理对应的msg_type,通过switch实现
local case = switch[msg_type]
if case then
case()
else
-- default case
subtree:add(fields.msg_type, tvb(offset, 2)):append_text(" [Unknown Message Type]")
offset = offset + 4
end
插件放在wireshark安装目录的plugins/3.0下即可
我的目录是C:\Program Files\Wireshark\plugins\3.0
由于自己并未使用到,因为传输的数据是视频数据,因此包的数据内容部分没有包含什么有效信息,未用到这两个,因此贴出赵子清的博客地址,以供之后方便查阅,若以后有写wireshark插件写到这部分,再来做补充
关于Post-dissector和Listener博客