1. 程式人生 > >使用lua指令碼為wireshake編寫自定義協議解析器

使用lua指令碼為wireshake編寫自定義協議解析器

以下內容轉載自:轉載地址

在網路通訊應用中,我們往往需要自定義應用層通訊協議,例如基於UDP的Real-Time Transport Protocol以及基於TCP的RTP over HTTP。鑑於RTP協議的廣泛性,wireshark(ethereal)內建了對RTP協議的支援,除錯解析非常方便。RTP over HTTP作為一種擴充套件的RTP協議,尚未得到wireshark的支援。在《RTP Payload Format for Transport of MPEG-4 Elementary Streams over http》中,使用wireshark只能抓獲到原始的TCP資料包,需要我們自己解析蘊含其中的RTP報文,剝離RTP over HTTP 16位元組的報文頭,然後萃取出MP4V_ES碼流資料。

怎麼樣使wireshark支援自定義通訊協議的解析呢?基於GPL v2的wireshark強大的外掛系統支援使用者編寫自定義協議解析器外掛。wireshark使用C語言編寫而成,它支援動態連結庫形式的外掛擴充套件。除此之外,wireshark內建了Lua指令碼引擎,可以使用Lua指令碼語言編寫dissector外掛。開啟C:/Program Files/Wireshark/init.lua,確保disable_lua = false,以開啟wireshark的Lua Console。

Lua是一種功能強大的、快速的、輕量的、嵌入式的指令碼語言,使用Lua編寫wireshark dissector外掛是非常方便的。本文例解使用Lua編寫ROH(RTP over HTTP)協議解析外掛。

以下為自定義RTP over HTTP協議解析器外掛程式碼roh.lua:

do

    --[[

    Proto.new(name, desc)

        name: displayed in the column of “Protocol” in the packet list

        desc: displayed as the dissection tree root in the packet details

    --]]

    local PROTO_ROH = Proto("ROH", "Rtp Over Http")

    --[[

    ProtoField:

        to be used when adding items to the dissection tree

    --]]

    --[[

    (1)Rtp Over Http Header

    --]]

    -- rtp over http header flag(1 byte)

    local f_roh_headerflag = ProtoField.uint8("ROH.HeaderFlag", "Header Flag", base.HEX)

    -- rtp over http interleaved channel(1 byte)

    local f_roh_interleave = ProtoField.uint8("ROH.InterleavedChannel", "Interleaved Channel", base.DEC)

    -- rtp over http packet length(2 bytes)

    local f_roh_packlen = ProtoField.uint16("ROH.PacketLen", "Packet Length", base.DEC)

    --[[

    (2)RTP Over Http

    --]]

    -- rtp header(1 byte = V:2+P:1+X:1+CC:4)

    local f_rtp_header = ProtoField.uint8("ROH.Header", "Rtp Header", base.HEX)

    -- rtp payloadtype(1 byte = M:1+PT:7)

    local f_rtp_payloadtype = ProtoField.uint8("ROH.PayloadType", "Rtp Payload Type", base.HEX)

    -- rtp sequence number(2 bytes)

    local f_rtp_sequence = ProtoField.uint16("ROH.Sequence", "Rtp Sequence Number", base.DEC)

    -- rtp timestamp(4 bytes)

    local f_rtp_timestamp = ProtoField.uint32("ROH.Timestamp", "Rtp Timestamp", base.DEC)

    -- rtp synchronization source identifier(4 bytes)

    local f_rtp_ssrc = ProtoField.uint32("ROH.SSRC", "Rtp SSRC", base.DEC)

    -- define the fields table of this dissector(as a protoField array)

    PROTO_ROH.fields = {f_roh_headerflag, f_roh_interleave, f_roh_packlen, f_rtp_header, f_rtp_payloadtype, f_rtp_sequence, f_rtp_timestamp, f_rtp_ssrc}

    --[[

    Data Section

    --]]

    local data_dis = Dissector.get("data")

    --[[

    ROH Dissector Function

    --]]

    local function roh_dissector(buf, pkt, root)

        -- check buffer length

        local buf_len = buf:len()

        if buf_len < 16

        then

            return false

        end

        -- check header flag

        if buf(0,2):uint() ~= 0x2400

        then

            return false

        end

        --[[

        packet list columns

        --]]

        pkt.cols.protocol = "ROH"

        pkt.cols.info = "Rtp Over Http"

        --[[

        dissection tree in packet details

        --]]

        -- tree root

        local t = root:add(PROTO_ROH, buf(0,16))

        -- child items

        -- ROH Header

        t:add(f_roh_headerflag, buf(0,1))

        t:add(f_roh_interleave, buf(1,1))

        t:add(f_roh_packlen, buf(2,2))

        -- ROH

        -- (1)header

        t:add(f_rtp_header, buf(4,1))

        -- (2)payloadtype

        t:add(f_rtp_payloadtype, buf(5,1))

        -- (3)sequence number

        t:add(f_rtp_sequence, buf(6,2))

        -- (4)timestamp

        t:add(f_rtp_timestamp, buf(8,4))

        -- (5)ssrc

        t:add(f_rtp_ssrc, buf(12,4))

        if buf_len > 16

        then

            local data_len = buf:len()-16;

            local d = root:add(buf(16, data_len), "Data")

            d:append_text("("..data_len.." bytes)")

            d:add(buf(16, data_len), "Data: ")

            d:add(buf(16,0), "[Length: "..data_len.."]")

            local start_code = buf(16,4):uint()

            if start_code == 0x000001b0

            then

                d:add(buf(16,0), "[Stream Info: VOS]")

            elseif start_code == 0x000001b6

            then

                local frame_flag = buf(20,1):uint()

                if frame_flag<2^6

                then

                    d:add(buf(16,0), "[Stream Info: I-Frame]")

                elseif frame_flag<2^7

                then

                    d:add(buf(16,0), "[Stream Info: P-Frame]")

                else

                    d:add(buf(16,0), "[Stream Info: B-Frame]")

                end

            end

        end

        return true

    end

    --[[

    Dissect Process

    --]]

    function PROTO_ROH.dissector(buf, pkt, root)

        if roh_dissector(buf, pkt, root)

        then

        -- valid ROH diagram

        else

            data_dis:call(buf, pkt, root)

        end

    end

    --[[

    Specify Protocol Port

    --]]

    local tcp_encap_table = DissectorTable.get("tcp.port")

    tcp_encap_table:add(60151, PROTO_ROH)

end

通過選單“ToolsàLuaàEvaluate”開啟Lua除錯視窗,將以上程式碼輸入Evaluate視窗,然後點選“Evaluate”按鈕,若無錯誤提示,且末尾提示“--[[  Evaluated --]]”,則說明程式碼無語法錯誤。這時,通過選單“HelpàSupported Protocols”可以發現,wireshark已經添加了ROH協議除錯支援,在Display Filter中輸入“roh”,則可以看到具體解析結果。

將以上程式碼儲存為C:/Program Files/Wireshark/roh.lua。在C:/Program Files/Wireshark/init.lua末尾新增程式碼行:dofile("roh.lua")。這樣在以後啟動wireshark時,自動載入roh.lua。

增加roh.Lua外掛擴充套件後,Packet 294將解析為RTP over HTTP報文。最後的Data部分為剝離16位元組報文頭後的碼流。

由於尚未掌握Lua的位域(bitfield)操作,因此上面對於RTP的解析不完整,第一個位元組(V:2+P:1+X:1+CC:4)和第二位元組(M:1+PT:7)的相關位域沒有解析出來。

既然wireshark內建了對RTP協議的支援,我們在解析完RTP over HTTP的頭4個位元組後,可以將餘下的報文交由RTP協議解析器解析。以下為呼叫內建RTP協議解析器程式碼roh_rtp.lua:

do

    --[[

    Proto.new(name, desc)

        name: displayed in the column of “Protocol” in the packet list

        desc: displayed as the dissection tree root in the packet details

    --]]

    local PROTO_ROH = Proto("ROH", "Rtp Over Http")

    local PROTO_PANASONIC_PTZ = Proto("PANASONIC_PTZ", "Panasonic PTZ Protocol")

    --[[

    ProtoField:

        to be used when adding items to the dissection tree

    --]]

    --[[

    1.ROH ProtoField

    --]]

    --rtp over http header flag(1 byte)

    local f_roh_headerflag = ProtoField.uint8("ROH.HeaderFlag", "Header Flag", base.HEX)

    --rtp over http interleaved channel(1 byte)

    local f_roh_interleave = ProtoField.uint8("ROH.InterleavedChannel", "Interleaved Channel", base.DEC)

    --rtp over http packet length(2 bytes)

    local f_roh_packlen = ProtoField.uint16("ROH.PacketLen", "Packet Length", base.DEC)

    --define the fields table of this dissector(as a protoField array)

    PROTO_ROH.fields = {f_roh_headerflag, f_roh_interleave, f_roh_packlen}

    --[[

    2.PANASONIC_PTZ ProtoField

    --]]

    -- panasonic ptz header flag(32 ascii)

    local f_panasonic_ptz_flag = ProtoField.bytes("PANASONIC_PTZ", "Header Flag")

    -- panasonic ptz command(6~17 ascii)

    local f_panasonic_ptz_cmd = ProtoField.bytes("PANASONIC_PTZ", "Command")

    --define the fields table of this dissector(as a protoField array)

    PROTO_PANASONIC_PTZ.fields = {f_panasonic_ptz_flag, f_panasonic_ptz_cmd}

    --[[

    Data Section

    --]]

    local data_dis = Dissector.get("data")

    --[[

    ROH Dissector Function

    --]]

    local function roh_dissector(buf, pkt, root)

        -- check buffer length

        local buf_len = buf:len()

        if buf_len < 16

        then

            return false

        end

        -- check header flag

        if buf(0,2):uint() ~= 0x2400

        then

            return false

        end

        --[[

        packet list columns

        --]]

        pkt.cols.protocol = "ROH"

        pkt.cols.info = "Rtp Over Http"

        --[[

        dissection tree in packet details

        --]]

        -- tree root

        local t = root:add(PROTO_ROH, buf(0,4))

        -- child items

        t:add(f_roh_headerflag, buf(0,1))

        t:add(f_roh_interleave, buf(1,1))

        t:add(f_roh_packlen, buf(2,2))

        return true

    end

    --[[

    PANASONIC_PTZ Dissector Function Helper

    --]]

    local function get_cmd_len(buf)

        local found=0

        for i=0,17 do

            if buf(i,1):uint() == 0x26

            then

                found = i

                break

            end

        end

        return found

    end

    --[[

    PANASONIC_PTZ Dissector Function

    --]]

    local function panasonic_ptz_dissector(buf, pkt, root)

        -- check buffer length

        local buf_len = buf:len()

        if buf_len < 32

        then

            return false

        end

        -- check header flag

        if buf(0,32):string() ~= "GET /nphControlCamera?Direction="

        then

            return false

        end

        -- check direction

        local sub_buf = buf(32, 18):tvb()

        local cmd_len = get_cmd_len(sub_buf)

        if cmd_len > 0

        then

            --[[

            packet list columns

            --]]

            pkt.cols.protocol = "PANASONIC_PTZ"

            pkt.cols.info = "Panasonic PTZ Protocol"

            --[[

            dissection tree in packet details

            --]]

            -- tree root

            local t = root:add(PROTO_PANASONIC_PTZ, buf(0,buf_len))

            -- child items

            local flag = t:add(f_panasonic_ptz_flag, buf(0,32))

            flag:add(buf(0,31), "["..buf(0,31):string().."]")

            local cmd = t:add(f_panasonic_ptz_cmd, buf(32,cmd_len))

            cmd:add(buf(32,cmd_len), "["..buf(32,cmd_len):string().."]")

        else

            return false

        end

        return true

    end

    --[[

    Dissect Process

    --]]

    function PROTO_ROH.dissector(buf, pkt, root)

        if roh_dissector(buf, pkt, root)

        then

            -- skip over rtp over http header

            local rtp_buf = buf(4,buf:len()-4):tvb()

            -- call internal rtp dissector

            local rtp_dissector = Dissector.get("rtp")

            rtp_dissector:call(rtp_buf, pkt, root)

        elseif panasonic_ptz_dissector(buf, pkt, root)

        then

            -- valid ptz datagram

        else

            data_dis:call(buf, pkt, root)

        end

    end

    --[[

    Specify Protocol Port

    --]]

    local tcp_encap_table = DissectorTable.get("tcp.port")

    tcp_encap_table:add(60151, PROTO_ROH)

end

此外,wireshark內建了對MP4V_ES碼流解析器,選擇選單“EditàPreferencesàProtocolsàMP4V_ES”,在“MP4V_ES dynamic payload type:”中填寫“96”。

將以上程式碼儲存為C:/Program Files/Wireshark/roh_rtp.lua。在C:/Program Files/Wireshark/init.lua末尾新增程式碼行:dofile("roh_rtp.lua"),在dofile("roh.lua")前新增“--”註釋,以免埠衝突(在roh.lua中tcp.port=60151)。儲存init.lua後,重啟wireshark。

自定義應用層協議基於傳輸層(UDP/TCP),自定義的協議埠號不能與wireshark內建應用層協議衝突。若將ROH協議指定為TCP 80,則需要選擇選單“AnalyzeàEnabled Protocols”勾掉HTTP,這樣TCP 80的報文將按照ROH協議解析。

在Display Filter中輸入“roh”可檢視RTP+MP4V_ES解析結果,如下圖所示:

此外,在Display Filter中輸入“panasonic_ptz”可檢視PTZ控制報文。

參考: