Wireshark+Lua自动解析自定义协议数据报

9.3k 词

在平时的工作中,经常需要根据接口文档进行开发,在调试时一般都会借助WireShark抓包进行分析,但是当协议较为复杂时,需要根据字节数手动计算进行解析,费时费力。曾经打算自己写个简单的自定义协议解析工具,后来发现WireShark提供了Lua接口,可以通过Lua脚本根据协议格式自动对获取的数据报进行解析,本文将对此进行简要介绍。

自定义协议格式

#pragma pack(1)

struct MsgHead {
    int msgNo_;
    unsigned char msgType_;
    unsigned char subMsgType_;
    short dataLen_;
};

struct MsgStruct {
MsgHead msgHead_;
unsigned char data_[MaxDataSize];
};

#pragma pack()

原始抓包结果

https://AnonymousRookie.github.io/images/2018/10/201801004_01.png

编写Lua脚本

-- creates a Proto object, but doesn't register it yet
local test_proto = Proto("test", "Test Protocol")

– a table of all of our Protocol's fields
local test_fields =
{
msgNo = ProtoField.uint32("test.msgNo", "msgNo", base.DEC),
msgType = ProtoField.uint8("test.msgType", "msgType", base.HEX),
subMsgType = ProtoField.uint8("test.subMsgType", "subMsgType", base.HEX),
dataLen = ProtoField.uint16("test.dataLen", "dataLen", base.DEC),
data = ProtoField.bytes("test.data", "data"),
}

– register the ProtoFields
test_proto.fields = test_fields

– a table of our default settings - these can be changed by changing
– the preferences through the GUI or command-line; the Lua-side of that
– preference handling is at the end of this script file
local default_settings =
{
enabled = true, – whether this dissector is enabled or not
port = 5001, – default TCP port number for Test
}

--------------------------------------------------------------------------------
– The following creates the callback function for the dissector.
– It's the same as doing "test_proto.dissector = function (tvbuf,pkt,root)"
– The 'tvbuf' is a Tvb object, 'pktinfo' is a Pinfo object, and 'root' is a TreeItem object.
– Whenever Wireshark dissects a packet that our Proto is hooked into, it will call
– this function and pass it these arguments for the packet it's dissecting.
function test_proto.dissector(tvbuf, pktinfo, root)

<span class="c1">-- set the protocol column to show our protocol name</span>
<span class="n">pktinfo</span><span class="p">.</span><span class="n">cols</span><span class="p">.</span><span class="n">protocol</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;Test&#34;</span><span class="p">)</span>

<span class="c1">-- get the length of the packet buffer (Tvb).</span>
<span class="kd">local</span> <span class="n">pktlen</span> <span class="o">=</span> <span class="n">tvbuf</span><span class="p">:</span><span class="n">len</span><span class="p">()</span>

<span class="kd">local</span> <span class="n">offset</span> <span class="o">=</span> <span class="mi">0</span>

<span class="c1">-- We start by adding our protocol to the dissection display tree.</span>
<span class="kd">local</span> <span class="n">tree</span> <span class="o">=</span> <span class="n">root</span><span class="p">:</span><span class="n">add</span><span class="p">(</span><span class="n">test_proto</span><span class="p">,</span> <span class="n">tvbuf</span><span class="p">:</span><span class="n">range</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="n">pktlen</span><span class="p">))</span>

<span class="n">tree</span><span class="p">:</span><span class="n">add</span><span class="p">(</span><span class="n">test_fields</span><span class="p">.</span><span class="n">msgNo</span><span class="p">,</span> <span class="n">tvbuf</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="mi">4</span><span class="p">))</span>
<span class="n">offset</span> <span class="o">=</span> <span class="n">offset</span> <span class="o">+</span> <span class="mi">4</span>
<span class="n">tree</span><span class="p">:</span><span class="n">add</span><span class="p">(</span><span class="n">test_fields</span><span class="p">.</span><span class="n">msgType</span><span class="p">,</span> <span class="n">tvbuf</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
<span class="n">offset</span> <span class="o">=</span> <span class="n">offset</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">tree</span><span class="p">:</span><span class="n">add</span><span class="p">(</span><span class="n">test_fields</span><span class="p">.</span><span class="n">subMsgType</span><span class="p">,</span> <span class="n">tvbuf</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
<span class="n">offset</span> <span class="o">=</span> <span class="n">offset</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">tree</span><span class="p">:</span><span class="n">add</span><span class="p">(</span><span class="n">test_fields</span><span class="p">.</span><span class="n">dataLen</span><span class="p">,</span> <span class="n">tvbuf</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
<span class="n">offset</span> <span class="o">=</span> <span class="n">offset</span> <span class="o">+</span> <span class="mi">2</span>
<span class="n">tree</span><span class="p">:</span><span class="n">add</span><span class="p">(</span><span class="n">test_fields</span><span class="p">.</span><span class="n">data</span><span class="p">,</span> <span class="n">tvbuf</span><span class="p">(</span><span class="n">offset</span><span class="p">,</span> <span class="n">pktlen</span><span class="o">-</span><span class="n">offset</span><span class="p">))</span>

end

--------------------------------------------------------------------------------
– We want to have our protocol dissection invoked for a specific TCP port,
– so get the TCP dissector table and add our protocol to it.
local function enableDissector()
– using DissectorTable:set() removes existing dissector(s), whereas the
– DissectorTable:add() one adds ours before any existing ones, but
– leaves the other ones alone, which is better
DissectorTable.get("tcp.port"):add(default_settings.port, test_proto)
end

local function disableDissector()
DissectorTable.get("tcp.port"):remove(default_settings.port, test_proto)
end

– call it now
enableDissector()

完整程序详见:https://github.com/AnonymousRookie/useful-tools/tree/master/wireshark_protocol_dissector

在Wireshark中加载Lua脚本

将protocol_dissector_example.lua拷贝到Wireshark的安装目录,找到init.lua,在该脚本结尾加上:

dofile(DATA_DIR.."protocol_dissector_example.lua")

重启Wireshark,点击按钮”表达式…/(Expression…)”,在搜索框中输入”TEST”,发现”Test Protocol”,说明脚本已经加载成功。

https://AnonymousRookie.github.io/images/2018/10/201801004_02.png

在Wireshark中查看解析结果

现在就可以根据协议格式进行筛选了,并且获取到的数据报已经根据定义的格式自动进行了解析。

https://AnonymousRookie.github.io/images/2018/10/201801004_03.png

参考文献

  • [1] https://wiki.wireshark.org/Lua/Examples