欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

编写wireshark lua插件 解析私有协议

程序员文章站 2022-07-08 20:07:16
...

wireshark 解析私有协议

1:本文很多写法并不合理,因为我不知道wireshark框架,也从来没写过lua。。纯属瞎捉摸出的结果。
2:文本以解析websocket协议为例。

准备lua文件

我的wireshark安装目录在:C:\Program Files (x86)\Wireshark\ ,所以我在plugins\2.6目录下,创建了一个websocket.lua文件。

lua插件解析框架

do

    --协议名称为mrprewebsocket,在Packet Details窗格显示为mrprewebsocket
    local p_mrprewebsocket = Proto("mrprewebsocket","mrprewebsocket")

    local data_dis = Dissector.get("data")

    --解析报文的主函数
    local function mrprewebsocket_dissector(buf, pkt, root)
        return 1
    end

    -- 每个报文都会触发调用的函数,所以我们需要必须实现这个函数
    function p_mrprewebsocket.dissector(tvb, pkt, root) 

        ret = mrprewebsocket_dissector(tvb, pkt, root)
        if ret == 1
        then
            --valid mrprewebsocket diagram
        elseif ret == 0
            --not enough data
        then
        else
            --当发现不是我的协议时,就应该调用data
            data_dis:call(tvb, pkt, root)
        end
    end


    local tcp_encap_table = DissectorTable.get("tcp.port")
    --只定义只需要解析80端口就可以了
    tcp_encap_table:add(80, p_mrprewebsocket)
end

上面框架很简单,如果大家想玩玩,就可以这么用了。但是对于想真正希望在实际的场景中使用的人来说,还不是很完善,因为我们少考虑了2点。

需要考虑的2点

1:数据分包,即应用层数据放在多个tcp负载里面,比如mss是1440,但是应用层数据是2000。
2:一个tcp负载存在多个应用层封装,比如开启了nodelay,且应用层连续发了多个帧。

所以修改框架为:

do

    --协议名称为mrprewebsocket,在Packet Details窗格显示为mrprewebsocket
    local p_mrprewebsocket = Proto("mwebsocket","mwebsocket")

    local data_dis = Dissector.get("data")

    local function mrprewebsocket_dissector(buf, pkt, root)

        local b_offset = pkt.desegment_offset or 0


        --一个tcp负载上可能有多个应用层封装,所以需要循环处理
        while true do
            --ADD 1
            total_len = 解析头部获取当前头部指定的应用层payload长度

            --tcp的负载长度小于应用层头部指定的应用层数据,即应用层数据分包
            if b_offset + total_len > buf:len() then
                message("not enough")
                --标记一下缺多少报文,不知道是否必要
                pkt.desegment_len = b_offset + total_len - buf:len()
                --标记当前的buf已经处理了多少,下次从offset开始处理
                pkt.desegment_offset = b_offset
                return 0
            end

            --ADD 2
            -- 处理


            -- 处理完成,判断剩余是否还有报文,有则继续continue
            b_offset =  b_offset + total_len

            if b_offset > buf:len()
            then
                return -1
            elseif b_offset == buf:len()
            then
                return 1
            end
        end
        return 1
    end

    function p_mrprewebsocket.dissector(tvb, pkt, root) 
        ret = mrprewebsocket_dissector(tvb, pkt, root)
        if ret == 1
        then
            --valid mrprewebsocket diagram
        elseif ret == 0
            --not enough data
        then
        else
            --当发现不是我的协议时,就应该调用data
            data_dis:call(tvb, pkt, root)
        end
    end

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

    tcp_encap_table:add(80, p_mrprewebsocket)
end

上面的框架基本涵盖了解析协议的各种可能性,只要大家在ADD 1ADD 2处,加入自己的逻辑,即可完成解析。(ADD 1处解析获获得这个应用层负载多大,ADD 2处就是解析具体协议了)

完整的解析websocket的lua文件

这里拿websoket协议解析为例,用到了多种方法,例如lua的比特位获取等,供大家参考。其次,若碰到问题需要调试,message函数可以帮助你把打印调试信息到console上,例如下面例子中使用message("plen: " .. plen.." offset: "..offset),这样,先打开wireshark的 工具->lua->console,然后单击某一条报文,就会执行下面的解析函数,调试信息就会出现在console上了。

do
    -- ProtoField 用,将对应的值变成字符串显示
    local op_text = {
    [0x0] = "Continuation",
    [0x1] = "Text",
    [0x2] = "Binary",
    [0x8] = "Close",
    [0x9] = "Ping",
    [0xa] = "Pong",
    }

    local mk_text = {
    [0x0] = "False",
    [0x1] = "True",
    }

    local len_text = {
    [126] = "2",
    [127] = "8",
    }

    --协议名称为mrprewebsocket,在Packet Details窗格显示为mrprewebsocket
    local p_mrprewebsocket = Proto("mwebsocket","mwebsocket")

    --定义协议的各个字段

    --所有可能的字段都要定义,到时没有t:add就不会显示
    -- 第一个参数是过滤器过滤中显示的字段
    -- 第二个参数是协议解析时显示的字段
    -- 第三个参数是解析成number时显示的格式,十进制显示还是16进制显示
    -- 第四个参数是解析出的结果,通过table转换成对应的值,如果不需要转换,则置为nil
    -- 第五个参数是会将待解析的入参进行与操作,取对应的bit位进行后续的解析。当一个字节中存在多个字段解析时是必要的

    local FIN = ProtoField.uint8("mrprewebsocket.fin","Fin", base.DEC, nil, 0x80)
    local RSV = ProtoField.uint8("mrprewebsocket.rsv","Rsv", base.DEC, nil, 0x70)
    local OPCODE = ProtoField.uint8("mrprewebsocket.opcode","Opcode", base.DEC, op_text, 0x0f)
    local MASKED = ProtoField.uint8("mrprewebsocket.masked","Masked", base.DEC, mk_text, 0x80)
    local LENHEAD = ProtoField.uint8("mrprewebsocket.lenhead","flowing byte", base.DEC, len_text, 0x7f)
    local LEN = ProtoField.uint8("mrprewebsocket.len","Payload length", base.DEC)
    local LENRAW = ProtoField.uint8("mrprewebsocket.lenraw","Payload length", base.DEC, nil, 0x7f)
    local KEY = ProtoField.bytes("mrprewebsocket.key","Masking-Key")
    local PAYLOAD = ProtoField.bytes("mrprewebsocket.payload","Payload")
    local MKPAYLOAD = ProtoField.bytes("mrprewebsocket.maskedpayload","Masked Payload")
    local UMKPAYLOAD = ProtoField.string("mrprewebsocket.unmaskedpayload","Unmasked Payload", base.ASCII)

    -- 需要将所有字段如下加入到fields
    p_mrprewebsocket.fields = { FIN, RSV, OPCODE, MASKED, LENHEAD, LENRAW, LEN, KEY, PAYLOAD, MKPAYLOAD, UMKPAYLOAD}

    local data_dis = Dissector.get("data")

    local function mrprewebsocket_dissector(buf, pkt, root)

        local b_offset = pkt.desegment_offset or 0
        local buf_len  = buf:len();

        --头部分包我暂时没处理,目前假设头部不分包。
        if buf_len < 4
        then
            return false
        end

        --一个tcp负载上可能有多个应用层封装,所以需要循环处理
        while true do
            --message("in while b_offset "..b_offset)

            -- 取对应字节的bit位数
            -- buf(b_offset,1)表示取buf得第b_offset开始的1字节长度的值
            -- :bitfield(1,3) 表示 从第1bit开始取,取3bit
            rsv = buf(b_offset,1):bitfield(1,3)
            op  = buf(b_offset,1):bitfield(4,4)
            mked = buf(b_offset + 1,1):bitfield(0,1)
            plen = buf(b_offset + 1,1):bitfield(1,7)


            if plen == 126
            then
                offset = 4
                plen = buf(b_offset + 2, 2):uint()
            elseif  plen >= 126
            then
                offset = 10
                plen = buf(b_offset + 2, 8):uint()
            else
                offset = 2
            end

            --message("plen: " .. plen.." offset: "..offset)

            if mked == 1            
            then
                total_len = plen + offset + 4
            else
                total_len = plen + offset
            end

            --tcp的负载长度小于应用层头部指定的应用层数据,即应用层数据分包
            if b_offset + total_len > buf:len() then
                message("not enough")
                --标记缺多少
                pkt.desegment_len = b_offset + total_len - buf:len()
                --标记当前的buf已经处理了多少,下次从offset开始处理
                pkt.desegment_offset = b_offset
                return 0
            end

            --将当头部前信息显示在wireshark中
            local t = root:add(p_mrprewebsocket, buf)
            pkt.cols.protocol = "mrprewebsocket"
            t:add(FIN, buf(b_offset,1))
            t:add(RSV, buf(b_offset,1))
            t:add(OPCODE, buf(b_offset,1))
            t:add(MASKED, buf(b_offset + 1,1))

            --显示websocket的len,len有不同形式
            plen = buf(b_offset + 1,1):bitfield(1,7)
            if plen == 126
            then
                t:add(LENHEAD, buf(b_offset + 1, 1))
                t:add(LEN, buf(b_offset + 2, 2))
                offset = 4
                plen = buf(b_offset + 2, 2):uint()
            elseif  plen >= 126
            then
                t:add(LENHEAD, buf(b_offset + 1, 1))
                t:add(LEN, buf(b_offset + 2, 8))
                offset = 10
                plen = buf(b_offset + 2, 8):uint()
            else
                t:add(LENRAW, buf(b_offset + 1, 1))
                offset = 2
            end

            --如果有masked,则进行亦或获取明文,然后显示出来
            --这里写的复杂了,可以更简单
            if mked == 1
            then
                t:add(KEY, buf(b_offset + offset, 4))
                local mk = buf(b_offset + offset, 4):bytes()
                offset = offset + 4
                t:add(MKPAYLOAD, buf(b_offset + offset, plen))

                local mkd = buf(b_offset + offset, plen):bytes()
                local nmked = ByteArray.new()
                nmked:set_size(plen)
                j = 1
                -- 亦或操作
                for i=0, mkd:len()-1 do

                    a = mkd:get_index(i)
                    b = mk:get_index(i%4)

                    r = bit32.bxor(a, b)
                    --r = 0
                    nmked:set_index(i, r)
                end


                if nmked:len()~= 0 then
                local tmptvb = ByteArray.tvb(nmked:subset(0, nmked:len()), "plaintext")
                t:add(UMKPAYLOAD, tmptvb(0, nmked:len()))
                end
            else
                --non masked
                if plen ~= 0 then
                t:add(PAYLOAD, buf(b_offset + offset, plen))
                end

                local mkd = buf(b_offset + offset, plen):bytes()
                local nmked = ByteArray.new()
                nmked:set_size(plen)
                -- 简单的拷贝
                for i=0, mkd:len()-1 do
                    nmked:set_index(i, mkd:get_index(i))
                end

                if nmked:len() ~= 0 then
                local tmptvb = ByteArray.tvb(nmked:subset(0, nmked:len()), "plaintext")
                t:add(UMKPAYLOAD, tmptvb(0, nmked:len()))
                end
            end

            --set column info                    
            info = op_text[op]

            if buf(b_offset,1):bitfield(0,1) == 1
            then
            info = info.."[FIN]"
            end

            if b_offset == 0
            then
            pkt.cols.info = info
            else
            pkt.cols.info:append(", "..info)
            end

            b_offset =  b_offset + total_len
            --message("end b_offset "..b_offset)

            if b_offset > buf:len()
            then
                return -1
            elseif b_offset == buf:len()
            then
                return 1
            end

        end
        return 1
    end

    function p_mrprewebsocket.dissector(tvb, pkt, root) 
        ret = mrprewebsocket_dissector(tvb, pkt, root)
        if ret == 1
        then
            --valid mrprewebsocket diagram
        elseif ret == 0
            --not enough data
        then
        else
            --当发现不是我的协议时,就应该调用data
            data_dis:call(tvb, pkt, root)
        end
    end

    -- register our new dummy protocol for post-dissection
    --register_postdissector(p_mrprewebsocket)
    local tcp_encap_table = DissectorTable.get("tcp.port")
    --只需要处理tcp 80端口就可以了
    tcp_encap_table:add(80, p_mrprewebsocket)
end

解析结果通过wireshark如下:
编写wireshark lua插件 解析私有协议

相关标签: wireshark