wireshark插件 解析自定义协议

·

Wireshark内置了Lua脚本编写插件,无需配置额外的环境,使用起来吧比较便。

使用Lua编写Wireshark协议解析插件,有几个比较重要的概念:

  1. Dissector,解剖器,用来解析包的类,我们要编写的,也是一个Dissector。
  2. DissectorTable,解析器表是Wireshark中解析器的组织形式,是某一种协议的子解析器的一个列表,其作用是把所有的解析器组织成一种树状结构,便于Wireshark在解析包的时候自动选择对应的解析器。例如TCP协议的子解析器 http, smtp, sip等都被加在了"tcp.port"这个解析器表中,可以根据抓到的包的不同的tcp端口号,自动选择对应的解析器。

基础

主要接口

-- create a new protocol
local proto_name = "PNASKCP"
local proto_desc = "Private NAS Protocol (KCP)"
local proto_obj = Proto(proto_name, proto_desc)
local proto_port = 5067

--register this dissector add Packet Details
DissectorTable.get("udp.port"):add(proto_port, proto_obj)

ProtoField

表示协议字段,一般用于解析字段后往解析树上添加节点。根据字段类型不同,其接口可以分为两大类。 这些接口都会返回一个新的字段对象。方括号内是可选字段,花括号内是可替换的类型字段。

ProtoField.{type}(abbr, [name], [base], [valuestring], [mask], [desc])
·type包括:uint8, uint16, uint24, uint32, uint64, framenum, float, double, string, stringz, bytes, bool, ipv4, ipv6, ether,oid, guid

参数

  • abbr 字段的缩写名称(过滤器中使用的字符串)。
  • name (optional) 字段的实际名称(出现在树中的字符串)。
  • base (optional) base.DEC,base.HEX或base.OCT,base.DEC_HEX,base.HEX_DEC,base.UNIT_STRING或base.RANGE_STRING。
  • valuestring (optional) 包含与值对应的文本的表,或包含与值 ({min, max, “string”}) 对应的范围字符串值表的表(如果基数为 )base.RANGE_STRING,或包含单位名称的表如果 base 是base.UNIT_STRING.
  • mask (optional) 此字段的整数掩码。
  • desc (optional) 字段说明。
fields.tlv_value_int16 = ProtoField.uint16(proto_name .. ".tlv_value_int", "PNAS TLV VALUE", base.HEX)
fields.uri_ipv4 = ProtoField.ipv4(proto_name .. ".uri_ipv4", "IP ADDRESS")

tvb

Tvb(Testy Virtual Buffer)表示报文缓存,也就是实际的报文数据,可以通过下面介绍的TvbRange从报文数据中解出信息。主要接口有: tvb 接口

-- tvb(offset, 4)表示从offset开始之后的4个字节
subtree:add_le(fields.peer_ipaddr, tvb(offset, 4))

pinfo

报文信息(packet information)。主要接口有:

-- show protocol name
pinfo.cols.protocol = "PNASKCP"
-- show in info
pinfo.cols.info:set("Private NAS Protocol (CALL_REQUEST)") 

TreeItem

表示报文解析树中的一个树节点。主要接口有: 还有注意一下网络字节序的问题,如果是网络字节序需要用add_le添加节点

-- kcp header
local subtree = tree:add(proto_obj, tvb(offset, lmmh_len), "KCP")	
--conv
subtree:add_le(fields.conv, tvb(offset, 4))
offset = offset + 4
--cmd
subtree:add_le(fields.cmd, tvb(offset, 1))
offset = offset + 1
--frg
subtree:add_le(fields.frg, tvb(offset, 1))
offset = offset + 1
--wnd
subtree:add_le(fields.wnd, tvb(offset, 2))
offset = offset + 2

脚本模板

创建编译 packet-demo.lua 文件

do

-- 创建一个新的协议: 协议名称为 fuzz_proto,在Packet Details窗格显示为 Fuzz Custom Protocol
fuzz_protocol = Proto("fuzz_proto", "Fuzz Custom Protocol")

-- 定义协议字段
--[[ 自定义字段
protocol_type(2) -- 自定义协议编号
base_protocol(1) -- 下层协议
connection_index(1) -- 收发状态
round_index(4) -- 本轮次序号
--]]
local f_protocol_type = ProtoField.uint16("fuzz_proto.protocol_type", "Custom Protocol ID", base.DEC)
local f_base_protocol = ProtoField.uint8("fuzz_proto.base_protocol", "Base Protocol", base.DEC)
local f_connection_index = ProtoField.uint8("fuzz_proto.connection_index", "Connection ID", base.DEC)
local f_round_index = ProtoField.uint32("fuzz_proto.round_index", "Round Index", base.DEC)
-- 将字段添加到协议中
fuzz_protocol.fields = { f_protocol_type, f_base_protocol, f_connection_index, f_round_index } 
local data_dis = Dissector.get("data")

-- 定义协议的解析函数
function FUZZ_dissector(buffer, pinfo, tree)
    local length = buffer:len()
    -- 确保报文有足够的长度供解析
    if length < 8 then
        return false
    end
    --验证一下identifier这个字段是不是0x12,如果不是的话,认为不是我要解析的packet
    local v_identifier = buffer(0, 1)
    if (v_identifier:uint() ~= 0x12) then 
        return false 
    end
    --取出其他字段的值
    local v_length = buffer(1, 1)
    v_length = tonumber(tostring(v_length),16)
    local v_data = buffer(2,v_length)

    -- 显示协议名称 --在主窗口的 Protocol 字段显示的名称为 XX_Protobuf
    -- pinfo.cols.protocol:set("XX_Protobuf")
    pinfo.cols.protocol = "fuzz_proto"

    --现在知道是我的协议了,放心大胆添加Packet Details
    -- 在树形结构中添加Headers
    local subtree = tree:add(fuzz_protocol, buffer(0, 7), "Fuzz Header")
    -- 解析协议字段并添加到树中
    subtree:add(f_protocol_type, buffer(0, 2))

    local d_base_protocol = buffer(2, 1):uint()
    local d_base_protocol_map = {
        [0] = "UnSet",
        [1] = "Ethernet",
        [2] = "IP",
        [3] = "TCP",
        [4] = "UDP",
        [5] = "TLS",
        [6] = "HTTP"
    }
    local d_base_protocol = d_base_protocol_map[d_base_protocol] or "Unknown"
    subtree:add(f_base_protocol, buffer(2, 1)):append_text(" (" .. d_base_protocol .. ")")

    local d_connection_index = (buffer(3, 1):uint() < 127) and "Send" or "Receive"
    subtree:add(f_connection_index, buffer(3, 1)):append_text(" (" .. d_connection_index .. ")")

    -- 本轮次序号, 取值为十六进制,需要将其转换为十进制 
    local d_round_index = string.format("%d", buffer(4, 4):uint()) 
    subtree:add(f_round_index, buffer(4, 4)):append_text(" (" .. d_round_index .. ")") 

    -- 解析下层协议
    local payload = buffer(8, length - 8)
    local subtree_payload = tree:add(fuzz_protocol, payload, "Current Custom Protocol")
    subtree_payload:add("Payload", payload):append_text(" (" .. payload:len() .. " bytes)")
end

function fuzz_protocol.dissector(buffer, pinfo, tree)
    if FUZZ_dissector(buffer, pinfo, tree) then
        -- valid Fuzz diagram
    else
        -- data这个dissector几乎是必不可少的;当发现不是我的协议时,就应该调用data
        data_dis.call(buffer, pinfo, tree)
    end
end

-- 注册 dissector 到对应的 DLT_USER3 (150)
-- local wtap_encap_table = DissectorTable.get("wtap_encap")
-- wtap_encap_table:add(150, fuzz_protocol)
local tcp_encap_table = DissectorTable.get("tcp.port")
--因为我们的自定义协议的接受端口是1080,所以这里只需要添加到"tcp.port"这个DissectorTable里即可。
tcp_encap_table:add(1080, fuzz_protocol)
end

配置lua脚本

lua脚本位置

如图, 在/usr/share/wireshark/init.lua文件中,添加:

enable_lua = true
dofile("/home/peter/.config/wireshark/packet-demo.lua")

然后重新启动Wireshark或者点击【分析】-【重新载入Lua插件】,就可以启用你自己的lua插件了。

新版本wireshark4.2 中的位置有所改变

如图 init.lua需要 放置在 /usr/lib/x86_64-linux-gnu/wireshark/plugins/init.lua 位置 并添加

dofile(DATA_DIR.."/packet-demo.lua")

高级一点的玩法

如果我们协议的PayLoad里封装的其实是以太网包,能不能让Wireshark在我们的插件执行完之后,继续按照以太网格式解析其他部分呢?

这里,我们重新构造一下需要继续解析的数据,然后获取出一个以太网解析器就继续做下去

local raw_data = buf(8, buf:len() - 8)
Dissector.get("eth_maybefcs"):call(raw_data:tvb(), pinfo, tree)

替换上面代码的-- 解析下层协议即可 需要在注意,

  1. 获取的解析器名称应该是 eth_maybefcs,因为DissectorTable里写的eth,但是提示找不到。 eth_maybefcs,意思是可能带有fcs的eth帧
  2. raw_data需要调用一下tvb()函数,不然会提示你这个是userdata,不能使用。tvb的全称应该是Testy Virtual Buffer,用来存储Packet buffer的,要处理必须先转成这个。

调用eth_maybefcs解析器的时候,这些解析器会给协议栏赋值,覆盖掉我们之前写的wfuzz protocol。为了区分,我们可以在上面的代码之后加上:

pkt.cols.protocol:append("-FuzzProtocol")

就是不管协议栏被改成了什么,都在后面加上-FuzzProtocol,这样ARP、ICMP等就会变成 ARP-Fuzz、ICMP-Fuzz了,一眼就可以跟那些普通的ARP和ICMP区分出来。

参考

wireshark插件开发
自定义协议
wireshark lua api 协议解析