Skip to content
Wireshark Wiki 中文翻译整理专题首页原始页面

Lua/解析器

本页说明如何用 Lua 编写 Wireshark dissector,并重点提醒 TCP 重组、post-dissector 和 chained dissector 的差异。它适合已经知道 Lua 在 Wireshark 中能做什么、现在需要落到 dissector 实现细节的读者。

主题本页关注点容易混淆的点
普通 dissector注册到某个 DissectorTable,处理特定 payload只有匹配注册表或被用户 Decode As 强制指定时才会调用
TCP 重组处理 TCP 分段、多消息、缺失前序分段和截断包不能处理重组时,不应把 Proto 加到 TCP DissectorTable
post-dissector在其他 dissector 之后运行,读取已有字段并补充树节点它不是替代原协议 dissector,而是事后补充
chained dissector包装并调用原 dissector,再追加自己的分析必须保留并调用原 dissector,否则原有解析会被替换掉

普通 dissector 的工作方式

dissector 用于分析数据包数据的一部分。它们类似于用 C 编写的 dissector,但本节只讨论普通 Lua dissector;启发式 dissector 和 post-dissector 的工作方式不同。

要点说明
注册位置dissector 必须注册为处理另一种协议或原始 wiretap 类型的某种 payload
注册方式dissector 函数必须分配给一个 Proto 对象
调用参数Wireshark 调用 dissector 时会传入 Tvb 缓冲区、Pinfo 数据包信息记录、TreeItem 树根
调用条件只有数据包匹配 Proto 所设置到的 DissectorTable,或用户用 Decode As 强制使用它时,dissector 才会被调用
Decode As 限制只有 Proto 对象注册到正确类型的 DissectorTable 时,用户才能用 Decode As 强制使用;例如注册到 UDP 表的 Proto 不能用于解码链路层

TCP 重组

如果你无法处理重组,就不应该为 TCP payload 编写 dissector,也就是说,不要把你的 Proto 对象添加到 TCP 的 DissectorTable。与 C dissector 一样,Lua dissector 可以使用 Wireshark 重组 TCP 流的能力。

dissector 必须考虑的情况

情况说明处理思路
消息被分段TCP 数据包分段可能只包含你的消息第一部分解析到足以判断完整长度后,设置 pinfo.desegment_lenpinfo.desegment_offset
一个分段含多条消息TCP 数据包分段可能包含多条你的消息在 dissector 中编写自己的 while 循环,每次调用时解析多条消息
捕获从会话中间开始第一个交给 dissector 的 TCP 分段可能是完整消息的中间部分对早期字段做合理性检查;失败时返回 0,等待后续分段
数据包被截断用户可能限制了捕获数据包大小检查 Tvb 的 len()reported_len();不同则表示该包被截断,可选择不解析
组合情况上述情况可能同时出现按协议格式设计更稳健的边界检查和重组逻辑

请求更多 TCP 字节

对于只收到消息前半段的情况,dissector 必须解析到足以判断完整长度的程度:

你知道什么应设置什么
知道还需要多少字节pinfo.desegment_len 设置为相对于当前 Tvb 中已有字节还需要的额外字节数
不知道确切还需要多少字节pinfo.desegment_len 设置为 DESEGMENT_ONE_MORE_SEGMENT
希望下次从哪里继续pinfo.desegment_offset 设置为 tvbuff 中的偏移量

Wireshark 下次收到 TCP 数据包分段时,会再次调用你的 Proto dissector 函数,并传入一个 Tvb 缓冲区。该缓冲区由前一个 Tvb 中从 desegment_offset 开始的数据字节,以及另外 desegment_len 个字节组成。

dissector 返回值规则

场景返回值与注意事项
数据包不属于你的 dissector返回 0;此时不得设置 pinfo.desegment_lenpinfo.desegment_offset
需要更多字节先设置 pinfo.desegment_lenpinfo.desegment_offset;然后不返回任何内容,或返回 Tvb 长度
不需要更多字节不返回任何内容,或返回 Tvb 长度
已设置 desegment_len / desegment_offset不要返回 0
不知道确切长度使用 DESEGMENT_ONE_MORE_SEGMENT 表示不知道消息有多长
已设置正数长度一旦把 pinfo.desegment_len 设置为正数,该长度不可更改;不能之后把同一消息长度从 X 改为 Y

示例:普通 UDP dissector

text
-- trivial protocol example
-- declare our protocol
trivial_proto = Proto("trivial", "Trivial Protocol")

-- create a function to dissect it
function trivial_proto.dissector(buffer, pinfo, tree)
    pinfo.cols.protocol = "TRIVIAL"
    local subtree = tree:add(trivial_proto, buffer(), "Trivial Protocol Data")
    subtree:add(buffer(0, 2), "The first two bytes: " .. buffer(0, 2):uint())
    subtree = subtree:add(buffer(2, 2), "The next two bytes")
    subtree:add(buffer(2, 1), "The 3rd byte: " .. buffer(2, 1):uint())
    subtree:add(buffer(3, 1), "The 4th byte: " .. buffer(3, 1):uint())
end

-- load the udp.port table
udp_table = DissectorTable.get("udp.port")

-- register our protocol to handle udp port 7777
udp_table:add(7777, trivial_proto)

post-dissector

post-dissector 是一种注册后在其他所有 dissector 都已被调用之后再运行的 dissector。它很适合读取已有协议字段,并向解析树追加自己的项目。

text
-- trivial postdissector example
-- declare some Fields to be read
ip_src_f = Field.new("ip.src")
ip_dst_f = Field.new("ip.dst")
tcp_src_f = Field.new("tcp.srcport")
tcp_dst_f = Field.new("tcp.dstport")

-- declare our (pseudo) protocol
trivial_proto = Proto("trivial", "Trivial Postdissector")

-- create the fields for our "protocol"
src_F = ProtoField.string("trivial.src", "Source")
dst_F = ProtoField.string("trivial.dst", "Destination")
conv_F = ProtoField.string("trivial.conv", "Conversation", "A Conversation")

-- add the field to the protocol
trivial_proto.fields = {src_F, dst_F, conv_F}

-- create a function to "postdissect" each frame
function trivial_proto.dissector(buffer, pinfo, tree)
    -- obtain the current values the protocol fields
    local tcp_src = tcp_src_f()
    local tcp_dst = tcp_dst_f()
    local ip_src = ip_src_f()
    local ip_dst = ip_dst_f()

    if tcp_src then
        local subtree = tree:add(trivial_proto, "Trivial Protocol Data")
        local src = tostring(ip_src) .. ":" .. tostring(tcp_src)
        local dst = tostring(ip_dst) .. ":" .. tostring(tcp_dst)
        local conv = src .. "->" .. dst
        subtree:add(src_F, src)
        subtree:add(dst_F, dst)
        subtree:add(conv_F, conv)
    end
end

-- register our protocol as a postdissector
register_postdissector(trivial_proto)

chained dissector

chained dissector 允许你访问某个 dissector 的数据,但不必针对每个数据包运行。典型做法是先保存原 dissector,再让自己的 dissector 接管同一端口,并在内部调用原 dissector,随后追加自己的字段或分析结果。

text
-- works as of Wireshark v0.99.7
do
    local http_wrapper_proto = Proto("http_extra", "Extra analysis of the HTTP protocol")

    -- our new fields
    local F_newfield1 = ProtoField.uint16("http.newfield1", "Our new field, #1", base.DEC)
    local F_newfield2 = ProtoField.uint16("http.newfield2", "Our new field, #2", base.DEC)

    -- add the fields to the protocol
    -- NOT ProtoFieldArray, that stopped working a while ago
    http_wrapper_proto.fields = {F_newfield1, F_newfield2}

    -- declare the fields we need to read
    local f_set_cookie = Field.new("http.set_cookie")
    local f_referer = Field.new("http.referer")
    local original_http_dissector

    function http_wrapper_proto.dissector(tvbuffer, pinfo, treeitem)
        -- we've replaced the original http dissector in the dissector table,
        -- but we still want the original to run, especially because we need to read its data
        original_http_dissector:call(tvbuffer, pinfo, treeitem)

        if f_set_cookie() then
            -- this has two effects:
            -- 1. makes it so we can use "http_extra" as a display filter
            -- 2. displays a new header in the tree pane for our protocol
            local subtreeitem = treeitem:add(http_wrapper_proto, tvbuffer)

            field1_val = 42
            subtreeitem:add(F_newfield1, tvbuffer(), field1_val)
                :set_text("Don't panic: " .. field1_val)

            field2_val = 616
            subtreeitem:add(F_newfield2, tvbuffer(), field2_val)
                :set_text("The REAL number of the beast: " .. field2_val)
        end
    end

    local tcp_dissector_table = DissectorTable.get("tcp.port")
    original_http_dissector = tcp_dissector_table:get_dissector(80)
    tcp_dissector_table:add(80, http_wrapper_proto)
end

导入自 https://wiki.wireshark.org/Lua/Dissectors,时间为 2020-08-11 23:16:08 UTC

相关 Wireshark Wiki 页面

网络分析技术档案