缘起

最近工作中,接触的内部协议比较多(项目历史原因),于是想编写 wireshark plugin,来辅助分析业务报文, 从中寻找包含特定请求的报文。

遇到一个问题,如何对请求和应答进行关联,我知道 wireshark 解析 ping 协议,是支持ping-pong相互跳转的。

ping

于是想自己写的协议插件,也具有这种功能,于是开始google。 在 wireshark 社区找到了sindy大神的这段回答,很受启发。原文如下:

The dissector code has no access to pinfo of any other packet than the one currently dissected.

If some transaction ID exists in modbus which allows you to match requests and responses, you may use two global tables indexed by this transaction ID and store the frame.number of the currently dissected packet to the appropriate table (request{transactionId} or response{transactionId}) during the first pass of the dissector (after loading the file, all packets are dissected in sequence). Whenever you click a packet in the packet list, it is dissected again so that the dissectoon tree could be displayed in the dissection pane; whenever you change the display filter, the packets are dissected in sequence again to be able to match the dissected fields to the filter conditions. So while dissecting a response, you may check whether a matching request exists in the capture by looking to request{transactionId}, and vice versa.

Have a look here to see how to render that information into the dissection tree nicely.

尝试使用 lua 实现

简单看了下 lua dict 的使用,然后就实现了以下工具方法:

  • 1 双 dict 缓存代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- 请求包缓存 <业务ID, number>
local tranId2IdRequest = {}
function cacheRequest(tid, valueId)
    tranId2IdRequest[tid] = valueId
end
function findRequestId(tid)
    id = tranId2IdRequest[tid]
    if id == nil then
        return -1
    end
    return id
end

-- 应答包缓存 <业务ID, number>
local tranId2idResp = {}
function cacheResp(tid, valueId)
    tranId2idResp[tid] = valueId
end
function findRespId(tid)
    id = tranId2idResp[tid]
    if id == nil then
        return -1
    end
    return id
end
  • 2 调用入口:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function show_refId_ink(is_req_packet, pinfo, seq, tree_node)
    local ref_id
    if is_req_packet then
        -- 为了保证每个报文的唯一性(一定程度上),故采用(port + req编号)作为transactionId
        local tid = (pinfo.src_port .. "_" .. seq)
        cacheRequest(tid, pinfo.number)
        -- 为什么生效了? 请参照下面的链接:
        -- https://ask.wireshark.org/question/1345/how-to-write-to-previous-packet-pinfo/
        ref_id = findRespId(tid)
        tree_node:add(fields.ref_rsp, ref_id)
    else
        local tid = (pinfo.dst_port .. "_" .. seq)
        cacheResp(tid, pinfo.number)
        -- 查询请求cache
        ref_id = findRequestId(tid)
        tree_node:add(fields.ref_req, ref_id)
    end
    return ref_id
end
  • 3 定义2个 framenum 字段

这两个字段具有超链接功能,可以跳转到指定的frame.number

1
2
fields.ref_req = ProtoField.framenum(NAME .. ".seq_num", "Request frame", base.NONE, frametype.REQUEST)
fields.ref_rsp = ProtoField.framenum(NAME .. ".seq_num", "Response frame", base.NONE, frametype.RESPONSE)

总结

基于双dict缓存,构造一次会话相关的唯一id(即transaction_id); 然后在 wireshark 中就能很方便的在req/rsp 之间来回跳转。

更上一层楼

借助于 wireshark 强大的检索能力,还可以实现各种复杂的查询需求: search

参考链接