Wireshark插件开发

  1. 前言
  2. Wireshark相关支持介绍
  3. lua插件
  4. wireshark安装插件和相关设置
  5. 存在的问题

前言

Wireshark可以显示客户端和服务器之间的数据包交互。对于游戏服务器来说,一般使用的都是自定义的协议,协议中包含protobuf格式的真实数据。这时想让Wireshark显示这些自定义协议就需要单独开发插件。

这里使用相对简单的lua插件的方式进行开发。开发完成后属于有用,但没那么有用,不过胜在开发成本低,还能学习到项目框架协议的编解码.

可以解决的问题

  1. 客户端到底有没有收到数据包。虽然游戏中打印了日志,但消息一般会经过多个流程,最终客户端有没有收到确实有待检查。
  2. 后台想要得知前台使用了什么协议。比如登录会发送登陆包,如果能够通过Wireshark看到登陆包,就能直接得知本次发送的协议名称和协议内容,方便查看对应的代码。
  3. 客户端or服务器发送的数据包有没有问题,省去加日志的步骤。

Wireshark相关支持介绍

  1. wireshark本身支持protobuf数据的解析,给定proto、message名称和message序列化后的数据就能以树状的形式展示在wireshark中。同时天然支持按字段进行索引。
  2. wireshark的lua插件目前没有找到合适的解密库的使用方法,所以需要关闭客户端和服务器的加密后使用。
  3. wireshark会以tcp包为单位提供数据包,所以只需要从数据包中编码后的自定义协议中拿到message的数据和message的名称就能完成解析

lua插件

lua插件基本格式

-- 自定义的协议名称
local proto_name = "tsf4g"
-- 服务器端口 wireshark通过端口选择协议来解析
local server_ports = {10000}

local proto = Proto(proto_name, proto_name)
-- 源端口的获取方式
-- 由于C->S和S->C之间的协议格式不一定是一致的 所以需要进行区分
srcport = Field.new("tcp.srcport")


-- 个性化字段定义 - 位的显示
--   最后的0xf0代表取字节的高4位
crypto_type_field_enum = {
    [1] = "unencrypted", -- 高4位是0001时 将会显示unencrypted
    [2] = "encrypted" -- 高4位是0002时 将会显示encrypted
}
crypto_type_field = ProtoField.uint8("crypto_type", "crypto_type", base.DEC, crypto_type_field_enum, 0xf0)
-- 个性化字段定义 - 指定格式的显示
--    这里是uint32格式和string两种格式
flag_field = ProtoField.uint32("flag", "flag", base.DEC, flag_field_enum)
debug1 = ProtoField.string("debug1", "debug1")

-- 注册字段
proto.fields = {
    crypto_type_field,
    flag_field,
    debug1
}

-- 算是个工具函数 从数据中以protobuf序列化后的格式读取一个数字
function read_number(tvb, begin_sub)
    local sub = begin_sub
    local number = 0
    local bit_lshift = 0
    repeat
        local num = tvb(sub, 1):uint()
        local t_num = bit32.band(num, 0x7f)
        number = bit32.bor(bit32.lshift(t_num, bit_lshift), number)
        bit_lshift = bit_lshift + 7
        sub = sub + 1
        if sub >= tvb:len() then
            break;
        end
    until (bit32.band(num, 0x80) == 0)
    return number
end

-- 这里的tvb可以理解为一个数组,tree则是wireshark左下角的树状展示所需要的数据
function proto.dissector(tvb, pinfo, tree)
    if tvb:len() > max_pack_size then
        return false
    end

    pinfo.cols.protocol = proto_name

    -- 判断是客户端发送到服务器 还是 服务器发送到客户端
    -- local srcport = srcport();
    -- svr_to_client = false
    -- for i, port in ipairs(server_ports) do
    --     if tostring(srcport) == tostring(port) then
    --         svr_to_client = true
    --     end
    -- end

    local sub = 0

    -- 从数据的开始位置读取4个字节,并作与运算 得到包长
    local pkg_size = bit32.band(tvb(sub, 4):uint(), 0x00ffffff)
    -- 在树中创建一个子树,这里的长度会影响选中后的高亮
    local subtree = tree:add(proto, tvb(0, pkg_size), "tsf4g")
    -- 在子树中继续创建子树
    local header = subtree:add(proto, tvb(), "header")

    -- 添加字段用于展示
    -- 由于在定义crypto_type_field设置了0xf0,所以这里实际展示内容为 第一个字节的高4位
    header:add(crypto_type_field, tvb(sub, 1))

    -- 字段判断,这里判断的是有无开启加密
    local crypto_type = bit32.rshift(bit32.band(tvb(sub, 4):uint(), 0xf0000000), 28)
    sub = sub + 1
    if not(crypto_type == 1) then
        return true
    end

    -- 添加字段的同时 添加注释
    local cmd = tvb(sub, 2):uint()
    header:add(cmd_field, tvb(sub, 2)):append_text(" [0 (Game Proto), other (tsf4g proto)]")
    sub = sub + 2

    -- 这里已经将头部数据解析完毕 sub开始到data_size就是protobuf数据了。
    if msg_type == 2 then
        -- 获取protobuf解释器
        local protobuf_dissector = Dissector.get("protobuf")
        -- 设置协议名称
        pinfo.private["pb_msg_type"] = "message,main.CSMsg"
        -- 给定protobuf序列化后的数据 进行解析
        pcall(Dissector.call, protobuf_dissector, tvb(sub, data_size), pinfo, tree)
        Dissector.call(protobuf_dissector, tvb(sub, data_size):tvb(), pinfo, subtree)
    end

    -- 由于一个包中可能有多条message 这里嵌套进行解析
    sub = sub + data_size
    if sub < tvb:len() then
        proto.dissector(tvb(sub):tvb(), pinfo, tree)
    end
end

local udp_port_table = DissectorTable.get("tcp.port")
for i, port in ipairs(server_ports) do
    udp_port_table:add(port,proto)
end

wireshark安装插件和相关设置

Wireshark安装后的根目录中存在init.lua文件

if not running_superuser or run_user_scripts_when_superuser then
    dofile(DATA_DIR.."console.lua")
    -- 添加自定义的lua插件
    dofile("D:\\tools\\wireshark1.0\\dict.lua")
end

存在的问题

  1. 暂不清楚wireshark如何处理的书中经常提到的tcp数据流本身没有分界线的问题。编写插件时,wireshark提供的数据包似乎不用考虑截断的问题,最多考虑下很少出现的粘包情况。
  2. 没有找到合适的加解密库的使用方式,一般协议使用aes加密,即使知道加密流程也没有库来调用进行解密。
  3. 当一个序列化后的数据包超过1460后会导致分包,这时候上述的方式无法解析,这个由于基本遇不到也就没有处理。
  4. 看过其他的插件是用的是将数据放到buff中对buff进行解析,这是更为合适的方式,不过目前直接解析也够用。