编写wireshark lua插件 解析私有协议
wireshark 解析私有协议
1:本文很多写法并不合理,因为我不知道wireshark框架,也从来没写过lua。。纯属瞎捉摸出的结果。
2:文本以解析websocket协议为例。
准备lua文件
我的wireshark安装目录在:C:\Program Files (x86)\Wireshark\
,所以我在plugins\2.6
目录下,创建了一个websocket.lua
文件。
lua插件解析框架
do
--协议名称为mrprewebsocket,在Packet Details窗格显示为mrprewebsocket
local p_mrprewebsocket = Proto("mrprewebsocket","mrprewebsocket")
local data_dis = Dissector.get("data")
--解析报文的主函数
local function mrprewebsocket_dissector(buf, pkt, root)
return 1
end
-- 每个报文都会触发调用的函数,所以我们需要必须实现这个函数
function p_mrprewebsocket.dissector(tvb, pkt, root)
ret = mrprewebsocket_dissector(tvb, pkt, root)
if ret == 1
then
--valid mrprewebsocket diagram
elseif ret == 0
--not enough data
then
else
--当发现不是我的协议时,就应该调用data
data_dis:call(tvb, pkt, root)
end
end
local tcp_encap_table = DissectorTable.get("tcp.port")
--只定义只需要解析80端口就可以了
tcp_encap_table:add(80, p_mrprewebsocket)
end
上面框架很简单,如果大家想玩玩,就可以这么用了。但是对于想真正希望在实际的场景中使用的人来说,还不是很完善,因为我们少考虑了2点。
需要考虑的2点
1:数据分包,即应用层数据放在多个tcp负载里面,比如mss是1440,但是应用层数据是2000。
2:一个tcp负载存在多个应用层封装,比如开启了nodelay,且应用层连续发了多个帧。
所以修改框架为:
do
--协议名称为mrprewebsocket,在Packet Details窗格显示为mrprewebsocket
local p_mrprewebsocket = Proto("mwebsocket","mwebsocket")
local data_dis = Dissector.get("data")
local function mrprewebsocket_dissector(buf, pkt, root)
local b_offset = pkt.desegment_offset or 0
--一个tcp负载上可能有多个应用层封装,所以需要循环处理
while true do
--ADD 1
total_len = 解析头部获取当前头部指定的应用层payload长度
--tcp的负载长度小于应用层头部指定的应用层数据,即应用层数据分包
if b_offset + total_len > buf:len() then
message("not enough")
--标记一下缺多少报文,不知道是否必要
pkt.desegment_len = b_offset + total_len - buf:len()
--标记当前的buf已经处理了多少,下次从offset开始处理
pkt.desegment_offset = b_offset
return 0
end
--ADD 2
-- 处理
-- 处理完成,判断剩余是否还有报文,有则继续continue
b_offset = b_offset + total_len
if b_offset > buf:len()
then
return -1
elseif b_offset == buf:len()
then
return 1
end
end
return 1
end
function p_mrprewebsocket.dissector(tvb, pkt, root)
ret = mrprewebsocket_dissector(tvb, pkt, root)
if ret == 1
then
--valid mrprewebsocket diagram
elseif ret == 0
--not enough data
then
else
--当发现不是我的协议时,就应该调用data
data_dis:call(tvb, pkt, root)
end
end
local tcp_encap_table = DissectorTable.get("tcp.port")
tcp_encap_table:add(80, p_mrprewebsocket)
end
上面的框架基本涵盖了解析协议的各种可能性,只要大家在ADD 1
、ADD 2
处,加入自己的逻辑,即可完成解析。(ADD 1
处解析获获得这个应用层负载多大,ADD 2
处就是解析具体协议了)
完整的解析websocket的lua文件
这里拿websoket协议解析为例,用到了多种方法,例如lua的比特位获取等,供大家参考。其次,若碰到问题需要调试,message
函数可以帮助你把打印调试信息到console
上,例如下面例子中使用message("plen: " .. plen.." offset: "..offset)
,这样,先打开wireshark的 工具->lua->console
,然后单击某一条报文,就会执行下面的解析函数,调试信息就会出现在console
上了。
do
-- ProtoField 用,将对应的值变成字符串显示
local op_text = {
[0x0] = "Continuation",
[0x1] = "Text",
[0x2] = "Binary",
[0x8] = "Close",
[0x9] = "Ping",
[0xa] = "Pong",
}
local mk_text = {
[0x0] = "False",
[0x1] = "True",
}
local len_text = {
[126] = "2",
[127] = "8",
}
--协议名称为mrprewebsocket,在Packet Details窗格显示为mrprewebsocket
local p_mrprewebsocket = Proto("mwebsocket","mwebsocket")
--定义协议的各个字段
--所有可能的字段都要定义,到时没有t:add就不会显示
-- 第一个参数是过滤器过滤中显示的字段
-- 第二个参数是协议解析时显示的字段
-- 第三个参数是解析成number时显示的格式,十进制显示还是16进制显示
-- 第四个参数是解析出的结果,通过table转换成对应的值,如果不需要转换,则置为nil
-- 第五个参数是会将待解析的入参进行与操作,取对应的bit位进行后续的解析。当一个字节中存在多个字段解析时是必要的
local FIN = ProtoField.uint8("mrprewebsocket.fin","Fin", base.DEC, nil, 0x80)
local RSV = ProtoField.uint8("mrprewebsocket.rsv","Rsv", base.DEC, nil, 0x70)
local OPCODE = ProtoField.uint8("mrprewebsocket.opcode","Opcode", base.DEC, op_text, 0x0f)
local MASKED = ProtoField.uint8("mrprewebsocket.masked","Masked", base.DEC, mk_text, 0x80)
local LENHEAD = ProtoField.uint8("mrprewebsocket.lenhead","flowing byte", base.DEC, len_text, 0x7f)
local LEN = ProtoField.uint8("mrprewebsocket.len","Payload length", base.DEC)
local LENRAW = ProtoField.uint8("mrprewebsocket.lenraw","Payload length", base.DEC, nil, 0x7f)
local KEY = ProtoField.bytes("mrprewebsocket.key","Masking-Key")
local PAYLOAD = ProtoField.bytes("mrprewebsocket.payload","Payload")
local MKPAYLOAD = ProtoField.bytes("mrprewebsocket.maskedpayload","Masked Payload")
local UMKPAYLOAD = ProtoField.string("mrprewebsocket.unmaskedpayload","Unmasked Payload", base.ASCII)
-- 需要将所有字段如下加入到fields
p_mrprewebsocket.fields = { FIN, RSV, OPCODE, MASKED, LENHEAD, LENRAW, LEN, KEY, PAYLOAD, MKPAYLOAD, UMKPAYLOAD}
local data_dis = Dissector.get("data")
local function mrprewebsocket_dissector(buf, pkt, root)
local b_offset = pkt.desegment_offset or 0
local buf_len = buf:len();
--头部分包我暂时没处理,目前假设头部不分包。
if buf_len < 4
then
return false
end
--一个tcp负载上可能有多个应用层封装,所以需要循环处理
while true do
--message("in while b_offset "..b_offset)
-- 取对应字节的bit位数
-- buf(b_offset,1)表示取buf得第b_offset开始的1字节长度的值
-- :bitfield(1,3) 表示 从第1bit开始取,取3bit
rsv = buf(b_offset,1):bitfield(1,3)
op = buf(b_offset,1):bitfield(4,4)
mked = buf(b_offset + 1,1):bitfield(0,1)
plen = buf(b_offset + 1,1):bitfield(1,7)
if plen == 126
then
offset = 4
plen = buf(b_offset + 2, 2):uint()
elseif plen >= 126
then
offset = 10
plen = buf(b_offset + 2, 8):uint()
else
offset = 2
end
--message("plen: " .. plen.." offset: "..offset)
if mked == 1
then
total_len = plen + offset + 4
else
total_len = plen + offset
end
--tcp的负载长度小于应用层头部指定的应用层数据,即应用层数据分包
if b_offset + total_len > buf:len() then
message("not enough")
--标记缺多少
pkt.desegment_len = b_offset + total_len - buf:len()
--标记当前的buf已经处理了多少,下次从offset开始处理
pkt.desegment_offset = b_offset
return 0
end
--将当头部前信息显示在wireshark中
local t = root:add(p_mrprewebsocket, buf)
pkt.cols.protocol = "mrprewebsocket"
t:add(FIN, buf(b_offset,1))
t:add(RSV, buf(b_offset,1))
t:add(OPCODE, buf(b_offset,1))
t:add(MASKED, buf(b_offset + 1,1))
--显示websocket的len,len有不同形式
plen = buf(b_offset + 1,1):bitfield(1,7)
if plen == 126
then
t:add(LENHEAD, buf(b_offset + 1, 1))
t:add(LEN, buf(b_offset + 2, 2))
offset = 4
plen = buf(b_offset + 2, 2):uint()
elseif plen >= 126
then
t:add(LENHEAD, buf(b_offset + 1, 1))
t:add(LEN, buf(b_offset + 2, 8))
offset = 10
plen = buf(b_offset + 2, 8):uint()
else
t:add(LENRAW, buf(b_offset + 1, 1))
offset = 2
end
--如果有masked,则进行亦或获取明文,然后显示出来
--这里写的复杂了,可以更简单
if mked == 1
then
t:add(KEY, buf(b_offset + offset, 4))
local mk = buf(b_offset + offset, 4):bytes()
offset = offset + 4
t:add(MKPAYLOAD, buf(b_offset + offset, plen))
local mkd = buf(b_offset + offset, plen):bytes()
local nmked = ByteArray.new()
nmked:set_size(plen)
j = 1
-- 亦或操作
for i=0, mkd:len()-1 do
a = mkd:get_index(i)
b = mk:get_index(i%4)
r = bit32.bxor(a, b)
--r = 0
nmked:set_index(i, r)
end
if nmked:len()~= 0 then
local tmptvb = ByteArray.tvb(nmked:subset(0, nmked:len()), "plaintext")
t:add(UMKPAYLOAD, tmptvb(0, nmked:len()))
end
else
--non masked
if plen ~= 0 then
t:add(PAYLOAD, buf(b_offset + offset, plen))
end
local mkd = buf(b_offset + offset, plen):bytes()
local nmked = ByteArray.new()
nmked:set_size(plen)
-- 简单的拷贝
for i=0, mkd:len()-1 do
nmked:set_index(i, mkd:get_index(i))
end
if nmked:len() ~= 0 then
local tmptvb = ByteArray.tvb(nmked:subset(0, nmked:len()), "plaintext")
t:add(UMKPAYLOAD, tmptvb(0, nmked:len()))
end
end
--set column info
info = op_text[op]
if buf(b_offset,1):bitfield(0,1) == 1
then
info = info.."[FIN]"
end
if b_offset == 0
then
pkt.cols.info = info
else
pkt.cols.info:append(", "..info)
end
b_offset = b_offset + total_len
--message("end b_offset "..b_offset)
if b_offset > buf:len()
then
return -1
elseif b_offset == buf:len()
then
return 1
end
end
return 1
end
function p_mrprewebsocket.dissector(tvb, pkt, root)
ret = mrprewebsocket_dissector(tvb, pkt, root)
if ret == 1
then
--valid mrprewebsocket diagram
elseif ret == 0
--not enough data
then
else
--当发现不是我的协议时,就应该调用data
data_dis:call(tvb, pkt, root)
end
end
-- register our new dummy protocol for post-dissection
--register_postdissector(p_mrprewebsocket)
local tcp_encap_table = DissectorTable.get("tcp.port")
--只需要处理tcp 80端口就可以了
tcp_encap_table:add(80, p_mrprewebsocket)
end
解析结果通过wireshark如下:
上一篇: 对wireshark所抓的包进行流量分析
下一篇: wireshark抓包基础学习