当前位置: 首页 > article >正文

Wireshark Lua 插件教程

本⽂主要介绍 Lua 脚本在 Wireshark 中的应⽤, Lua 脚本可以在 Wireshark 中完成如下功能:

  • 从⽹络包中提取数据, 或者统计⼀些数据包(Dumper)

  • 需要解析⼀种 Wireshark 不提供原⽣⽀持的协议(Dissector)

⽰例

协议解析

VREP 协议是 NOGD 框架对于 TRIP 协议的⼀种延伸和扩展. Wireshark 原⽣并不提供对于 VREP 协议的⽀持, 下图展⽰了脚本前后对⽐.

(a) VREP 原始字节流如下
VREP raw byte stream

(b) 脚本解析过后的信息
VREP with Lua script

数据流提取

Wireshark 对 RTPRTSP 均提供⽀持, 但是没提供对于 RTP over RTSP 协议的⽀持, 可以利⽤此处脚本提供的协议对此完成解析. 下图展⽰了这种差异.

(a) RTP over RTSP 原始信息
RTP over RTSP raw

(b) 脚本加强解析后的信息
RTP over RTSP with Lua

使⽤⽅法

假定你要使⽤ foo.lua 脚本

  1. 将脚本拷⻉到 Wireshark home ⽬录, 如 C:\Program\Files\Wireshark\foo.lua
  2. 修改 init.lua 脚本(C:\Program\Files\Wireshark\init.lua), 在末尾添加⼀⾏ dofile("foo.lua")
  3. 重启 Wireshark 使脚本⽣效

不同类型的脚本的使⽤⽅法:

  • Dissector 在选定的数据流中右击 -> 解码为… -> 选择脚本注册的协议, 如 RTPP.
  • Dumper 在⼯具菜单下⾯选择注册的 dumper.(如 Dump MPEG TS Packet)

解析器(Dissector)

注册新协议

注册新协议的⼀般步骤.

  1. 注册新协议

    • 基于 UDP 相对而⾔⽐较简单, 逐个解析 IP 包即可
    • 基于 TCP 解析器⽐较复杂, 需要考虑 TCP 重组(TCP Reassembly)
  2. 定义协议字段

  3. 注册协议字段

  4. 定义解析函数

  5. 注册到协议端口号

解析器代码框架

local ror = Proto("ror", "RTP over RTSP Protocol")

-- 定义协议字段
local pf_ror_magic = ProtoField.uint8("ror.magic", "Magic", base.HEX)
local pf_ror_channel = ProtoField.uint8("ror.channel", "Interleaved Channel", base.HEX)
local pf_ror_length = ProtoField.uint16("ror.length", "Length")

-- 注册协议字段
ror.fields = {
    pf_ror_magic ,
    pf_ror_channel ,
    pf_ror_length ,
}

 -- 在此处定义你精妙绝伦的解析函数
 function ror.dissector(tvbuf, pinfo, root)
    -- tvbuf: TCP segment
    -- pinfo: packet column info
    -- root: node info in the display zone
 end

-- 指定协议端⼝, 此处是tcp 端⼝
local tcp_dissector_table = DissectorTable.get("tcp.port")
tcp_dissector_table:add(554, ror)

TCP 包重组问题

作为 tcp 解析器必须要⽤能⼒处理下⾯⼏种情况

  1. TCP 数据段只含有协议数据包的前⼀部分
  2. TCP 数据段含有多个协议数据包
  3. 协议数据包在 TCP 数据段的中间部分, 因为协议包的前⼀部分可能没有被捕获到
  4. 数据包可能有被截断的情形
  5. 以上⼏种情形的任意组合

对以上问题的应对策略.

  1. 针对 4, 简单来说就是不解析切断的包(return 0)
  2. 针对 3, 解析函数必须要做⼀定的合法性检查, 如果不是属于该协议的包, 那么就丢弃该包(return 0)
  3. 针对 2, 在解析函数⾥⾯做⼀个 while 循环(return 已解析的⻓度)
  4. 针对 1, 尽最⼤可能去确定协议数据包的⻓度, 如果不能确定, 那么就返回⼀个默认值 DESEGMENT_ONE_MORE_SEGMENT

dissector 函数的返回值如下:

  1. 如果 TCP 数据段携带的数据不属于当前协议, 那么返回 0
  2. 如果需要更多的数据才能继续解析, 那么设置 desegment_len, desegment_offset, 返回值为 nil 或者已解析的⻓度都可以
  3. 如果不需要更多数据, 那么返回 nil 或者已经解析的⻓度都可以

Dumper

可以导出指定的协议字段到⽂件. 可选的字段:

  1. 来⾃预定义的字段(ip.src, tcp.port, rtsp.length)
  2. ⾃定义的字段(ror.magic)

Dumper 代码框架

-- 定义感兴趣的字段
-- wireshark 现已⽀持的协议的字段
local mpeg_pid = Field.new("mp2t.pid")
local mpeg_pkt = Field.new("mp2t")
-- ⾃定义协议的字段
local ror_channel = Field.new("ror.channel")

-- 激活对话框时候的回调
local function init_payload_dump(file, filter)
    local tap = Listener.new(nil, filter)

    -- this function is going to be called once each time our filter matches
    function tap.packet(pinfo, tvb)
        -- do some fancy work
    end

    retap_packets()

    tap:remove()
end

-- 窗⼝回调
local function begin_dialog_menu()
    new_dialog("Dump MPEG TS Packets", init_payload_dump, "Output file", ilter")
end

-- 注册窗⼝
register_menu("Dump MPEG TS Packets", begin_dialog_menu, MENU_TOOLS_UNSORTED)

样例解析

如下是⼀些解析样例.

  1. VREP Dissector
  2. RTP Over RTSP Dissector
  3. MPEG Dumper
  4. RTP over RTSP Dumper

VREP Dissector 解析样例

这个文件比较长, 请参考我的Github Repo

RTP Over RTSP Dissector 解析样例

-- 1. declare a new type of protocol
local rtpp = Proto("rtpp", "RTP over RTSP(iPanel Flavor)")

-- 2. define some field into the rtpp
local pf_rtpp_magic = ProtoField.uint8("rtpp.magic", "Magic", base.HEX)

local pf_rtpp_channel = ProtoField.uint8("rtpp.channel", "Interleaved Channel", base.HEX)
local pf_rtpp_length = ProtoField.uint16("rtpp.length", "Length")

-- data 就是⽆法识别净荷内容的情况下简单的将其设置为⼆进制数据的⽅法
local default_parser = Dissector.get("data")

-- 3. 注册新协议拥有的字段, 这些字段可作为以后抽取数据之⽤
rtpp.fields = {
    pf_rtpp_magic,
    pf_rtpp_channel,
    pf_rtpp_length,
}

-- 前向声明
local dissect_rtpp
local rtpp_min_len = 4

-- 此处处理TCP 重传的逻辑
function rtpp.dissector(tvbuf, pktinfo, root)
    local segment_length = tvbuf:len()
    local bytes_consumed = 0

    local reported_len = tvbuf:reported_length_remaining()
    if segment_length ~= reported_len then
        -- captured packets are being sliced/cut-off,
        -- so don't try to desegment/reassemble
        return 0
    end

    while bytes_consumed < segment_length do
        -- 此处会调⽤具体的解析函数
        local result = dissect_rtpp(tvbuf, pktinfo, root, bytes_consumed)
        if result == 0 then
            return 0
        elseif result > 0 then
            bytes_consumed = bytes_consumed + result
        else
            tinfo.desegment_offset = bytes_consumed
            result = -result
            pktinfo.desegment_len = result
            return segment_length
        end
    end
    return bytes_consumed
end

-- RTP over RTSP 有基本的RTSP 协议信令也有数据流,
-- 对基本的信令⽤默认的RTSP 解析器来解析
-- 对interleaved-channel 形式的按照其载荷类型来解析
dissect_rtpp = function(tvbuf, pinfo, root, offset)
    local msglen = tvbuf:len() - offset
    debug("sub_buf len=" .. msglen .. ", offset=" .. offset)
    if msglen < rtpp_min_len then
        debug("sliced packet")
        return - DESEGMENT_ONE_MORE_SEGMENT
    end

    -- 分类解析
    if tvbuf:range(offset, 1):uint() ~= 0x24 then
        -- 以普通的rtsp 消息来解析
        debug("interpret packet as normal rtsp")
        local rtsp_dissector = Dissector.get("rtsp")
        -- 此处的返回值尚不清楚, 对此的处理也⽐较幼稚
        rtsp_dissector:call(tvbuf:range(offset, msglen):tvb(), pinfo, root)
        info("ret=" .. ret)
        return msglen
    else
        -- interleaved-channel 形式
        debug("interpret packet as interleaved channel")
        local len_buf = tvbuf:range(offset + 2, 2)
        local payload_len = len_buf:uint()
        local packet_len = payload_len + 4
        debug("rtsp packet_len=" .. packet_len .. ", payload_len load=" .. pyload_len)

        -- ⾄少需要4 个字节才可以区分出该包是否属于RTP over RTSP 协议
        if msglen < packet_len then
            return -(packet_len - msglen)
        end

        -- 添加⼀些界⾯显⽰信息
        root:add(pf_rtpp_magic,   tvbuf:range(offset + 0, 1))
        root:add(pf_rtpp_channel, tvbuf:range(offset + 1, 1))
        root:add(pf_rtpp_length,  len_buf)

        offset = offset + 4
        -- 取净荷的第⼀个字节来区分mpeg 和rtp
        local probe_byte = tvbuf:range(offset, 1):uint()
        debug("probe_byte=" .. string.format( "0x%x ", probe_byte))

        if probe_byte == 0x47 then
            -- 0x47 开头的就⽤mpeg 的解析器来解析
            debug("raw mp2t packet, offset=" .. offset .. ", payload_len=" .. payload_len)
            local mpeg_dissector = Dissector.get("mp2t")
            while (offset + 188) <= packet_len do
                local mpeg_tvb = tvbuf:range(offset, packet_len - offset):tvb()
                -- 暂时不知道该函数的返回值是什么情况
                mpeg_dissector:call(mpeg_tvb, pinfo, root)
                offset = offset + 188
            end
            return offset
        elseif probe_byte == 0x80 then
            -- 0x80 开头的尝试⽤rtp 来解析
            debug("RTP packet, offset=" .. offset .. ", payload_len=" .. payload_len)
            local rtp_dissector = Dissector.get("rtp")
            local rtp_tvb = tvbuf:range(offset, payload_len):tvb()
            -- 同样也是对返回值的情况不太了解.
            rtp_dissector:call(rtp_tvb, pinfo, root)
            if msglen ~= packet_len then
                debug("length not match, payload_len + 4=" .. packet_len.. ", msglen=" .. msglen)
            end
            return packet_len
        else
            default_parser(tvbuf, pinfo, root)
            return packet_len
        end
    end
end

-- 将RTP over RTSP 协议注册到554 端⼝
-- 需要注意的是因为调⽤了原⽣RTSP 解析器, 所以我们的解析结果并不影响普通的rtsp 解析
tcp_dissector_table:add(554, rtpp)
tcp_dissector_table:add(2554, rtpp)
info("rtpp (RTP over RTSP) protocol registed at TCP port 554")

MPEG 流抽取器

if not GUI_ENABLED then
    print("mpeg_packets_dump.lua only works in Wireshark")
    return
end

-- 声明要抽取的字段
local mpeg_pid = Field.new("mp2t.pid")
local mpeg_pkt = Field.new("mp2t")

-- 窗口回调函数
local function init_payload_dump(file, filter)
    local packet_count = 0
    -- 对任意的udp 包进⾏过滤
    -- filter 为符合BPF 格式的任意过滤器
    local tap = Listener.new(nil, filter)
    local myfile = assert(io.open(file, "w+b"))

    -- 每次BPF 过滤器过滤出⼀个ip 包就会调⽤下⾯的函数
    function tap.packet(pinfo, tvb)
        -- 检查当前包⾥⾯是否有mpeg 包
        if (mpeg_pid()) then
            packet_count = packet_count + 1

            -- dump 出所有的mpeg 包
            local contents = {mpeg_pkt()}

            -- 逐个包输出到⽂件
            for i, finfo in ipairs(contents) do
                local tvbrange = finfo.range
                local subtvb = tvbrange:tvb()
                myfile:write(subtvb:raw())
                -- myfile:flush()
            end
        end
    end

    -- re-inspect all the packets that are in the current capture, thereby
    -- triggering the above tap.packet function
    retap_packets()

    -- cleanup
	myfile:flush()
    myfile:close()
    tap:remove()
    debug("Dumped mpeg packets: " .. packet_count)
end


local function begin_dialog_menu()
    new_dialog("Dump MPEG TS Packets", init_payload_dump, "Output file", "Packet filter (optional)\n\nExamples:\nip.dst == 225.1.1.4\nmp2t\nmp2t.pid == 0x300")
end

-- 注册到程序菜单
register_menu("Dump MPEG TS Packets", begin_dialog_menu, MENU_TOOLS_UNSORTED)

RTP over RTSP 负载抽取

local mpeg_pid = Field.new("mp2t.pid")
local mpeg_pkt = Field.new("mp2t")
local rtp_payload = Field.new("rtp.payload")

local function init_payload_dump(file, filter)
    local packet_count = 0
    local real_filter = "(rtpp.channel)"
    if filter ~= nil and filter ~= "" then
        -- 拼接⽤⼾输⼊的过滤参数
        real_filter = real_filter .. " and (" .. filter ..")"
    end

    local tap    = Listener.new(nil, real_filter)
    local myfile = assert(io.open(file, "w+b"))
    function tap.packet(pinfo, tvb)
        -- 检查是否有mpeg 数据包
        if (mpeg_pid()) then
            local contents = {mpeg_pkt()}
            for i, finfo in ipairs(contents) do
                local tvbrange = finfo.range
                local subtvb = tvbrange:tvb()
                myfile:write(subtvb:raw())
            end
        else
            -- 检查是否是rtp 包
            local payload = rtp_payload()
            if payload then
                local tvbrange = payload.range
                local subtvb = tvbrange:tvb()

                myfile:write(subtvb:raw())
            end
        end
    end
    retap_packets()
    myfile:flush() myfile:close() tap:remove()
end

调试脚本

在命令⾏中启⽤ Wireshark, 然后可以在当前命令⾏中看到 Lua 脚本的打印输出. debug,warn(), info() 会输出到 Lua consolestdout

Debug Lua Script

参考链接

  • Wireshark Lua
  • Lua Dissector
  • Wireshark Lua Example

http://www.kler.cn/a/565621.html

相关文章:

  • Tailwind CSS 4【实用教程】
  • Python PDF文件拆分-详解
  • React面试葵花宝典之二
  • 【博资考2】网安学院-北航网安基础部分(简洁版)
  • 【LeetCode347】前k个高频元素
  • CIDR转IP段:原理Java实现
  • SpringCloud Gateway 集成 Sentinel 详解 及实现动态监听Nacos规则配置实时更新流控规则
  • 自动化测试的价值重构:软件质量保障的效率革命与理性抉择
  • 实践教程:使用DeepSeek实现PDF转Word的高效方案
  • [Machine Learning] K-means算法
  • DeepSeek开源周Day5压轴登场:3FS与Smallpond,能否终结AI数据瓶颈之争?
  • 场景重建——Nerf场景重建
  • 【PCIe 总线及设备入门学习专栏 10.1 -- Linux PCIe 驱动框架 之 RK3399 Region1 访问】
  • Vue 3指令全解析:内置指令与自定义指令实战指南
  • Day76 补JWT
  • [c语言日寄] 指针学习情况自检题目
  • 智能指针c/c++
  • 【Groovy】流程控制
  • 三个小时学完vue3 —— 简单案例(二)
  • matlab图论分析之网络构建