WireSharks插件编写(lua)

前言

参考:

wireshark的lua插件由于最近研究SRT的使用,发现包并不能抓到,尽管wireshark内支持SRT包,但不知为何抓不到,只能看到下层的UDP包,因此只能自己另外写了,目前插件写完已经过去两周有余,一直想总结,都没时间。。。这次特地申请了加班过来写,记录下来,以备下次编写时查阅,其中博客部分内容搬运自赵子清的博客,链接贴在上方,同时加了很多自己的例子,希望能够帮助大家

API

Proto

表示一个新的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)

ProtoField

表示协议字段,一般用于解析字段后往解析树上添加节点。根据字段类型不同,其接口可以分为两大类。

这些接口都会返回一个新的字段对象。方括号内是可选字段,花括号内是可替换的类型字段。

整型:

  • ProtoField.{type} (abbr, [name], [base], [valuestring], [mask], [desc])
    type包括:uint8, uint16, uint24, uint32, uint64, framenum

举例:

fields.time_stamp = ProtoField.uint32("bvc_srt.time_stamp", "Time Stamp", base.DEC)

其他类型

  • ProtoField.{type} (abbr, [name], [base], [valuestring], [mask], [desc])
    type包括:float, double, string, stringz, bytes, bool, ipv4, ipv6, ether,oid, guid

以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

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))

TvbRange

表示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,这个部分基本没有用到过。。我编的时候没有用到。

Pinfo

报文信息(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

表示报文解析树中的一个树节点。主要接口有:

接口 说明
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))

在这里插入图片描述

DissectorTable

表示一个具体协议的解析表,比如,协议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)

prefs

有的时候需要去指定解析某个端口,则使用prefs,prefs就是首选项,通过修改首选项可以指定解析某一个端口或者,指定解析某些包等,总之就是一个类似于偏好设置的东西,由于大部分博客,都未涉及到这一部分,只有wireshark官方手册上孤零零的一个Proto.prefs_changed,导致我压根不知道怎么写,这部分是参考github上其他人写的lua插件写的。。。。。

下面介绍一下这个首选项应该怎么用~

接口 说明
prefs:__newindex(name, pref) 创建一个新的首选项,pref参数指的是使用Pref.uint等创建的内容
Pref.uint(label, default, descr) 创建一个uint型的Pref,可以作为__newindex的参数
prefs:__index(name) 获取对应参数的默认值

使用示例

local port = 1935

-- 第一种方式
local pref = Pref.uint("SRT UDP Port", 1935, "SRT UDP Port")
srt.prefs:__newindex("srt_udp_port", pref)
Pref.uint("SRT UDP Port", srt.prefs:__index("srt_udp_port") , "SRT UDP Port")

-- 第二种方式
-- 使用[]的方式,和上面其实是一样的,只是写法不同而已
srt.prefs["srt_udp_port"] = Pref.uint("SRT UDP Port", 1935, "SRT UDP Port")

-- 第三种方式
srt.prefs.srt_udp_port = Pref.uint("SRT UDP Port", 1935, "SRT UDP Port")

-- 下面的代码加在任意一种方式后面才算完整
DissectorTable.get("udp.port"):add(port, srt)

-- prefs changed will listen at new port
function bvc_srt.prefs_changed()
	if port ~= bvc_srt.prefs.srt_udp_port then
		if port ~= 0 then
			DissectorTable.get("udp.port"):add(srt.prefs["srt_udp_port"], srt)
		end

		port = bvc_srt.prefs.srt_udp_port

		if port ~= 0 then
			DissectorTable.get("udp.port"):add(srt.prefs["srt_udp_port"], srt)
		end
	end
end

代码部分

由于代码不方便直接贴在这里,毕竟在公司写的,不能乱分享,因此只给出大概写的思路

大致框架

这部分包含了一个完整的解析器需要的部分:解析器对象,解析器函数,注册解析器到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

关于标志位的操作,lua中怎么做

上述例子中有关于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的for循环并不支持在循环体内改变循环变量,其实这么做确实不安全,有的时候会死循环,但是在有些场景下,改变循环变量更方便处理逻辑,比如:

现在有一段数据,里面包含的是丢失包的序号:

  • 有的时候,这个长度是4字节,里面只包含了这个丢失包的序号
  • 有的时候这个长度是8字节,前4个字节表示丢包的开始序号,后4字节表示丢包结束序号
  • 数据中既有第一种表示方式,也有第二种表示方式,有可能有多个这样的表示方式的数据在一起

那么我现在要处理这个数据,怎么办

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语句

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
在这里插入图片描述

插件抓包效果图

抓取SRT协议Data Packet的效果图
在这里插入图片描述

关于Post-dissector和Listener

由于自己并未使用到,因为传输的数据是视频数据,因此包的数据内容部分没有包含什么有效信息,未用到这两个,因此贴出赵子清的博客地址,以供之后方便查阅,若以后有写wireshark插件写到这部分,再来做补充

关于Post-dissector和Listener博客

发布了89 篇原创文章 · 获赞 96 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/Boring_Wednesday/article/details/99695507