一、两种语言开发插件的对比
- 想要开发wireshark插件,我们可以使用C语言也可以使用Lua语言。如下图,是使用两种语言开发插件的对比。
- 如上图,我最后选择使用Lua语言进行开发wireshark插件。
二、插件功能
- 目前我有一个任务,需要解析RTP包的payload,这个payload前三个字节是自定义的字节,后面的才是H264的数据、或者OPUS的数据。
- 因此我需要在RTP包的上层进行解析RTP包的payload,并且让H264的数据按照H264的格式或者OPUS的格式来显示。
本篇博文重点介绍如何基于Lua语言去编写wireshark插件。
- 具备的基础:
- Lua语言基础,可以去这个网站去学习简单的Lua语言基础。https://www.runoob.com/lua/lua-miscellaneous-operator.html
- 熟悉wireshark的使用,并且去浏览一下wireshark支持Lua语言的API。https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Proto.html
- 剩下的就只需要看一下我的这篇博文就好了
三、Lua插件API介绍
3.1 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个参数 |
- 如下的例子,创建一个新的协议
local NAME1 = "red"
local PORT = 5004
local RTP_PROTO_TYPE = 106
local red = Proto(NAME1, "Red Protocol")
3.2 ProtoField
-
表示协议字段,一般用于解析字段后往解析树上添加节点。根据字段类型不同,其接口可以分为两大类。
-
整型:
• ProtoField.{type} (abbr, [name], [desc],[base], [valuestring], [mask])
type包括:uint8, uint16, uint24, uint32, uint64, framenum -
其他类型
• ProtoField.{type} (abbr, [name], [desc])
type包括:float, double, string, stringz, bytes, bool, ipv4, ipv6, ether,oid, guid -
这些接口都会返回一个新的字段对象。方括号内是可选字段,花括号内是可替换的类型字段。
-
如下图的例子,
-- create fields of red
fields_M = ProtoField.uint8 (NAME1 .. ".M", "M", base.HEX,Payload_type,0x80)
fields_pt = ProtoField.uint8 (NAME1 .. ".PT", "PT", base.DEC,Payload_type,0x7F)
fields_seqno = ProtoField.uint16(NAME1 .. ".seqno", "Sequence number")
fields_h264bytes = ProtoField.bytes(NAME1 .. ".bytes", "H264Data")
fields_fec = ProtoField.bytes(NAME1 .. ".fec", "FEC Payload")
- 当添加这写字段后,我们看如下图的实际加载情况
3.3 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:len()”
3.4 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报文列表列(界面) |
- 取得报文列表列后,就可以设置该列的文本,比如
-- show protocol name in protocol column
pinfo.cols.protocol = red.name
- 如上图所示,我们就可以将协议名称修改为RED。
3.5 DissectorTable
- 表示一个具体协议的解析表,比如,协议TCP的解析表”tcp.port”包括http,smtp,ftp等。可以依次点击wireshark菜单“Internals”、“Dissector tables”,来查看当前的所有解析表。如下图,我们选择 rtp.pt解析表在“Integer tables”选项卡中,顾名思义,它是通过类型为整型的 rtp 端口号来识别下游协议的:
- DissectorTable的主要接口有:
- 接口说明
接口 | 说明 |
---|---|
DissectorTable.get(name) | get名为name的解析表的引用 |
dissectortable:add(pattern, dissector) | 将Proto或Dissector对象添加到解析表,即注册。pattern可以是整型值,整型值范围或字符串,这取决于当前解析表的类型 |
dissectortable:remove(pattern, dissector) | 将满足pattern的一个或一组Proto、Dissector对象从解析表中删除 |
四、代码框架
-- create a new dissector
local NAME = "red"
local PORT = 5004
local red = Proto(NAME, "Red Protocol")
-- dissect packet
function red.dissector (tvb, pinfo, tree)
end
-- register this dissector
DissectorTable.get("udp.port"):add(PORT, red)
- 如上图,就是整体的三部分式的代码框架,首先需要定义一个新协议,就是给这个新协议起一个新的名字。
- 接下来,就是对这个新协议进行解析,在 red.dissector 这个函数中写我们解析这种包的逻辑。
- 最后就是将这个新协议添加到 udp 这个已有协议的的底下,用端口号进行区分。
五、完善代码
local version_str = string.match(_VERSION, "%d+[.]%d*")
local version_num = version_str and tonumber(version_str) or 5.1
local bit = (version_num >= 5.2) and require("bit32") or require("bit")
-- create a new dissector to decode rtp private payload
local NAME1 = "red"
local PORT = 5004
local RTP_PROTO_TYPE = 106
local red = Proto(NAME1, "Red Protocol")
-- create fields of red
fields_M = ProtoField.uint8 (NAME1 .. ".M", "M", base.HEX,Payload_type,0x80)
fields_pt = ProtoField.uint8 (NAME1 .. ".PT", "PT", base.DEC,Payload_type,0x7F)
fields_seqno = ProtoField.uint16(NAME1 .. ".seqno", "Sequence number")
fields_h264bytes = ProtoField.bytes(NAME1 .. ".bytes", "H264Data")
fields_fec = ProtoField.bytes(NAME1 .. ".fec", "FEC Payload")
red.fields = { fields_M, fields_pt, fields_seqno, fields_h264bytes,fields_fec }
local RTP_dis = Dissector.get("rtp")
local H264_dis = Dissector.get("h264")
local Data_dis = Dissector.get("data")
-- dissect packet
function red.dissector(tvb, pinfo, tree)
length = tvb:len()
if length == 0 then return end
-- decode private header
local subtree = tree:add(red, tvb(0,3))
subtree:add(fields_M, tvb(0,1))
subtree:add(fields_pt, tvb(0,1))
subtree:add(fields_seqno, tvb(1,2))
-- show protocol name in protocol column
pinfo.cols.protocol = red.name
local fec_id = tvb(0,1):uint()
local fec_type = bit.band(fec_id,0x7F)
if fec_type == 109 then
tree:add(fields_fec,tvb(3))
else
H264_dis:call(tvb(3):tvb(), pinfo, tree)
end
end
--decode first layer as rtp
local udp_dissector_table = DissectorTable.get("udp.port")
udp_dissector_table:set(PORT,RTP_dis)
-- register this dissector
-- DissectorTable.get("rtp.pt"):add(PORT, red)
--decode private protocol layer 3-bytes private datas + standard h264
local rtp_dissector_table = DissectorTable.get("rtp.pt")
rtp_dissector_table:set(RTP_PROTO_TYPE,red)
- 如上的代码就是扩展代码框架来正确达成我的目的的。
- 我们可以根据不同的需求编写不同的解析代码。
六、加载到wireshark中
- 将Lua文件保存到wireshark的根目录下。比如:
- 然后在 wireshark 的根目录下找到 “init.lua” 文件。 打开它,使用记事本或者notepad++或者其他软件都可以。在这个文件的开头修改”enable_lua = true“,请参考这段代码上面的注释,不同版本可能不一样。
- 接下来在这个文件”init.lua“ 的最后将我们增加到wireshark根目录的Lua文件添加。如下图:
- 这个时候Lua文件就已经被嵌入到wireshark中了,有两种启动Lua插件的办法,要么就是重启wireshark,要么就是在wireshark的”分析“菜单下点击“重新载入Lua插件”。
七、后记
- 我的整体代码放在我githup上了,链接:https://github.com/zhangyi-13572252156/Wireshark-Plug-In
- 如果需要交流,欢迎交流。QQ:1251108673