前言

要想用wireshark 插件做一个最简单的协议分析(不挎包),除了分析协议数据(纯C, 和wireshark无关),还要将分析的结果显示在wireshark树区和数据区, 只要能在树区添加子树,在子树上再添加子树,在子树上添加文本,这事就搞定了。

运行效果

wireshark 如何显示info列 wireshark添加info列_wireshark 如何显示info列

最简单任务要调用的wireshark API列表

看官方文档,继续做试验,完成上述任务需要调用以下下wireshark API.

proto_register_field_array

注册wireshark 子树字段,向子树添加的数据,必须和要添加的字段类型相同.

proto_register_subtree_array

注册子树,最简单的分析只要一个包有一个子树根节点就行。

proto_item_add_subtree

在节点下添加子树

proto_tree_add_string

在子树下添加字符串, 这个API最有用,添加数据,用这一个API就都搞定了

proto_tree_add_item

在子树下添加节点, 要添加的节点要实现注册好,节点有具体数据类型要求的,指定数据开始偏移和数据长度,wireshark就自己显示了. 如果刚开始玩wireshark插件,可以只用proto_tree_add_string来自己写通用的文本信息就够了。

Decode as …

同事以前给我演示,将一种协议按照另外一种协议分析时,不生效。

这段时间发现,选择的端口,必须是已经注册的协议端口,而不能是普通的未注册的端口.

wireshark 如何显示info列 wireshark添加info列_数据_02


wireshark 如何显示info列 wireshark添加info列_wireshark 如何显示info列_03

试验

// @file packet-foo.c
// @ref http://www.dgtech.com/foo/sys/www/docs/html/
//      https://www.wireshark.org/docs/wsdg_html_chunked/
//      https://www.wireshark.org/docs/wsdg_html_chunked/ChDissectAdd.html#idm1589259872

// @note how to use foo plugin
// open a pcap file or capture any packet, select a tcp frame(have payload), Decode as ... => foo => ok

#include "config.h"

#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/dissectors/packet-tcp.h>
#include "packet-foo.h"

#define PROTOCOL_FULL_NAME_FOO "foo Protocol"
#define PROTOCOL_SHORT_NAME_FOO "foo"
#define PROTOCOL_DISPLAY_FILTER_NAME_FOO PROTOCOL_SHORT_NAME_FOO

// interface declare for plugin.c (plugin dll interface plugin_register(), plugin_reg_handoff())
void proto_register_foo(void);
void proto_reg_handoff_foo(void);

#define foo_TCP_PORT 7000 /* Not IANA registed */

static dissector_handle_t dissector_handle_foo = NULL;
static int protocol_handle_foo = -1;

// hf means "header field name"
static int hf_foo_message = -1;
static int hf_foo_pdu_type = -1;

// 要注册的字段信息数组, use use proto_register_field_array to register
static hf_register_info hf[] = {
    { &hf_foo_message,
        {
            "FOO message", // field name
            "foo.msg", // field short name
            FT_STRING, // field type, see ftypes.h
            BASE_NONE, // data base type, see proto.h field_display_e
            NULL, // value_string
            0x0, // bitmask
            NULL, // Brief description of field
            HFILL // info fill by proto routines
        }
    },

    { &hf_foo_pdu_type,
        {
            "FOO PDU Type", // field name
            "foo.type", // field short name
            FT_UINT8, // field type, see ftypes.h
            BASE_DEC, // data base type, see proto.h field_display_e
            NULL, // value_string
            0x0, // bitmask
            NULL, // Brief description of field
            HFILL // info fill by proto routines
        }
    }
};

static gint ett_foo = -1;

// ett means "protocol subtree array"
static gint *ett[] = {
    &ett_foo
};

static int dissect_proc_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_);

void proto_register_foo(void)
{
    // first entry proto_register_foo
    // then entry proto_reg_handoff_foo

    // 注册协议
    // 执行了proto_register_protocol, 就这一句, 在显示过滤器中输入foo, 就显示绿色
    // 说明foo协议已经注册
    // 参数1是协议的完整名称
    // 参数2是协议的短名称, 在Decode as对话框中的协议名称列表中可以看到
    // 参数3是显示过滤器中的协议名称
    protocol_handle_foo = proto_register_protocol(
        PROTOCOL_FULL_NAME_FOO, 
        PROTOCOL_SHORT_NAME_FOO, 
        PROTOCOL_DISPLAY_FILTER_NAME_FOO);

    proto_register_field_array(protocol_handle_foo, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));
}

void proto_reg_handoff_foo(void)
{
    // 建立解析器
    // 指定协议使用的解析器处理函数
    dissector_handle_foo = create_dissector_handle(dissect_proc_foo, protocol_handle_foo);

    // 绑定端口
    // 指定要处理哪个端口的载荷
    dissector_add_uint("tcp.port", foo_TCP_PORT, dissector_handle_foo);
}

static int dissect_proc_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_)
{
    // process folw
    // parse data + set ui content
    // return data length was process

    // wireshark 主显示区的列头信息
    // col.1 = "No."
    // col.2 = "Time"
    // col.3 = "Soruce"
    // col.4 = "Destination"
    // col.5 = "Protocol"
    // col.6 = "Length"
    // col.7 = "Info"

    char sz_buf[4096] = { '\0' };
    proto_item* item = NULL;
    proto_tree* sub_tree_foo = NULL;

    guint reported_length = tvb_reported_length(tvb);
    guint captured_length = tvb_captured_length(tvb);
    guint data_len_left = captured_length; // 要处理的数据长度

    // set col.5 = "Protocol" 's content to 'foo'
    col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTOCOL_SHORT_NAME_FOO);

    // set col.7 = "Info" 's content to empty, else will display by tcp info
    col_clear(pinfo->cinfo, COL_INFO);

    // 在树显示区增加数节点, 节点名称为"foo Protocol"
    // 在数据显示区,显示的数据为全部载荷的长度(点击树节点"foo Protocol", 可以将载荷数据都用阴影圈起来)
    // 分配给这个节点的数据为tvb数据开始偏移(param 4),数据长度(param 5), 数据长度 = -1, 代表全部数据
    // 如果建立树节点, 再建立子数节点, 每个子树再负责展现不同数量的载荷, 那样数据的展现就很专业了
    item = proto_tree_add_item(tree, protocol_handle_foo, tvb, 0, -1, ENC_NA);

    do {
        // 在节点下增加子树, 此时子树还没有显示出来
        sub_tree_foo = proto_item_add_subtree(item, ett_foo);

        // 在子树上添加节点
        if (data_len_left < 999999) {
            proto_tree_add_string(sub_tree_foo, hf_foo_message, tvb, 0, 4, "error, data len too short");
            proto_tree_add_string(sub_tree_foo, hf_foo_message, tvb, 4, 12, "don't process left data, will be break");

            sprintf_s(sz_buf, sizeof(sz_buf), 
                "reported_length = %d, captured_length = %d, data_len_left = %d", 
                reported_length, 
                captured_length, 
                data_len_left);

            proto_tree_add_string(sub_tree_foo, hf_foo_message, tvb, 0, -1, sz_buf);

            // 每调用一次proto_tree_add_string,就在树下增加一个数据
            // 感觉有了这个hf_foo_message字符串字段, 如果想做个最简单的wireshark插件(如果不挎包), 可以就此打住了.
            // 已经可以干活了
            /*
            foo Protocol
                FOO message: error, data len too short
                FOO message: don't process left data, will be break
                FOO message: reported_length = 38, captured_length = 38, data_len_left = 38
            */
            break;
        }

        proto_tree_add_item(sub_tree_foo, hf_foo_pdu_type, tvb, 0, 1, ENC_BIG_ENDIAN);
        data_len_left -= 1;

        // 此时, "foo Protocol"节点有折叠的子树了
        /*
        foo Protocol
            FOO PDU Type : 0
        */


    } while (0);

    return captured_length; // return data length by process
}

向子树上添加子树

用 proto_tree_add_subtree 来向子树添加子树
用 proto_item_set_len 修改已有 item负责的数据长度
用 proto_item_set_text 改变已有item上的文本说明,这个有用,原始添加的item上都有字段名称,用这个API修改后,看到的就是实际设置的文本,去掉了字段名称的显示。

运行效果

wireshark 如何显示info列 wireshark添加info列_数据_04

demo实现

// @file packet-foo.c
// @ref http://www.dgtech.com/foo/sys/www/docs/html/
//      https://www.wireshark.org/docs/wsdg_html_chunked/
//      https://www.wireshark.org/docs/wsdg_html_chunked/ChDissectAdd.html#idm1589259872

// @note how to use foo plugin
// open a pcap file or capture any packet, select a tcp frame(have payload), Decode as ... => foo => ok

#include "config.h"

#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/dissectors/packet-tcp.h>
#include "packet-foo.h"

#define PROTOCOL_FULL_NAME_FOO "foo Protocol"
#define PROTOCOL_SHORT_NAME_FOO "foo"
#define PROTOCOL_DISPLAY_FILTER_NAME_FOO PROTOCOL_SHORT_NAME_FOO

// interface declare for plugin.c (plugin dll interface plugin_register(), plugin_reg_handoff())
void proto_register_foo(void);
void proto_reg_handoff_foo(void);

#define foo_TCP_PORT 7000 /* Not IANA registed */

static dissector_handle_t dissector_handle_foo = NULL;
static int protocol_handle_foo = -1;

// hf means "header field name"
static int hf_foo_message = -1;
static int hf_foo_pdu_type = -1;

// 要注册的字段信息数组, use use proto_register_field_array to register
static hf_register_info hf[] = {
    { &hf_foo_message,
        {
            "FOO message", // field name
            "foo.msg", // field short name
            FT_STRING, // field type, see ftypes.h
            BASE_NONE, // data base type, see proto.h field_display_e
            NULL, // value_string
            0x0, // bitmask
            NULL, // Brief description of field
            HFILL // info fill by proto routines
        }
    },

    { &hf_foo_pdu_type,
        {
            "FOO PDU Type", // field name
            "foo.type", // field short name
            FT_UINT8, // field type, see ftypes.h
            BASE_DEC, // data base type, see proto.h field_display_e
            NULL, // value_string
            0x0, // bitmask
            NULL, // Brief description of field
            HFILL // info fill by proto routines
        }
    }
};

static gint ett_foo = -1;
static gint ett_foo_subtree_1 = -1;

// ett means "protocol subtree array"
static gint *ett[] = {
    &ett_foo,
    &ett_foo_subtree_1
};

static int dissect_proc_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_);

void proto_register_foo(void)
{
    // first entry proto_register_foo
    // then entry proto_reg_handoff_foo

    // 注册协议
    // 执行了proto_register_protocol, 就这一句, 在显示过滤器中输入foo, 就显示绿色
    // 说明foo协议已经注册
    // 参数1是协议的完整名称
    // 参数2是协议的短名称, 在Decode as对话框中的协议名称列表中可以看到
    // 参数3是显示过滤器中的协议名称
    protocol_handle_foo = proto_register_protocol(
        PROTOCOL_FULL_NAME_FOO, 
        PROTOCOL_SHORT_NAME_FOO, 
        PROTOCOL_DISPLAY_FILTER_NAME_FOO);

    proto_register_field_array(protocol_handle_foo, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));
}

void proto_reg_handoff_foo(void)
{
    // 建立解析器
    // 指定协议使用的解析器处理函数
    dissector_handle_foo = create_dissector_handle(dissect_proc_foo, protocol_handle_foo);

    // 绑定端口
    // 指定要处理哪个端口的载荷
    dissector_add_uint("tcp.port", foo_TCP_PORT, dissector_handle_foo);
}

static int dissect_proc_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_)
{
    // process folw
    // parse data + set ui content
    // return data length was process

    // wireshark 主显示区的列头信息
    // col.1 = "No."
    // col.2 = "Time"
    // col.3 = "Soruce"
    // col.4 = "Destination"
    // col.5 = "Protocol"
    // col.6 = "Length"
    // col.7 = "Info"

    char sz_buf[4096] = { '\0' };
    proto_item* item = NULL;
    proto_item* item_sub1 = NULL;
    proto_item* item_sub2 = NULL;
    proto_tree* sub_tree_foo = NULL;
    proto_tree* sub_tree_foo_sub1 = NULL;

    guint reported_length = tvb_reported_length(tvb);
    guint captured_length = tvb_captured_length(tvb);
    guint data_len_left = captured_length; // 要处理的数据长度

    // set col.5 = "Protocol" 's content to 'foo'
    col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTOCOL_SHORT_NAME_FOO);

    // set col.7 = "Info" 's content to empty, else will display by tcp info
    col_clear(pinfo->cinfo, COL_INFO);

    // 在树显示区增加数节点, 节点名称为"foo Protocol"
    // 在数据显示区,显示的数据为全部载荷的长度(点击树节点"foo Protocol", 可以将载荷数据都用阴影圈起来)
    // 分配给这个节点的数据为tvb数据开始偏移(param 4),数据长度(param 5), 数据长度 = -1, 代表全部数据
    // 如果建立树节点, 再建立子数节点, 每个子树再负责展现不同数量的载荷, 那样数据的展现就很专业了
    item = proto_tree_add_item(tree, protocol_handle_foo, tvb, 0, -1, ENC_NA);

    do {
        // 在节点下增加子树, 此时子树还没有显示出来
        sub_tree_foo = proto_item_add_subtree(item, ett_foo);

        // 在子树上添加节点
        if (data_len_left < 999999) {
            proto_tree_add_string(sub_tree_foo, hf_foo_message, tvb, 0, 4, "error, data len too short");
            proto_tree_add_string(sub_tree_foo, hf_foo_message, tvb, 4, 12, "don't process left data, will be break");

            sprintf_s(sz_buf, sizeof(sz_buf), 
                "reported_length = %d, captured_length = %d, data_len_left = %d", 
                reported_length, 
                captured_length, 
                data_len_left);

            proto_tree_add_string(sub_tree_foo, hf_foo_message, tvb, 0, -1, sz_buf);

            // 每调用一次proto_tree_add_string,就在树下增加一个数据
            // 感觉有了这个hf_foo_message字符串字段, 如果想做个最简单的wireshark插件(如果不挎包), 可以就此打住了.
            // 已经可以干活了
            /*
            foo Protocol
                FOO message: error, data len too short
                FOO message: don't process left data, will be break
                FOO message: reported_length = 38, captured_length = 38, data_len_left = 38
            */

            // 向树中添加格式化字符串的方便方法
            proto_tree_add_string_format_value(sub_tree_foo, hf_foo_message, tvb, 8, 4,
                "message", // 可选
                "*reported_length = %d, *captured_length = %d, *data_len_left = %d",
                reported_length,
                captured_length,
                data_len_left);

            // 给子树添加子树
            sub_tree_foo_sub1 = proto_tree_add_subtree(sub_tree_foo, tvb, 0, 0, ett_foo_subtree_1, NULL, "my sub tree 1");
            item_sub1 = proto_tree_add_string(sub_tree_foo_sub1, hf_foo_message, tvb, 0, 4, "error, data len too short");
            proto_item_set_len(item_sub1, 8);

            item_sub2 = proto_tree_add_string(sub_tree_foo_sub1, hf_foo_message, tvb, 4, 12, "don't process left data, will be break");
            proto_item_set_text(item_sub2, "change item text");

            /*
            foo Protocol
                FOO message: error, data len too short
                FOO message: don't process left data, will be break
                FOO message: reported_length = 38, captured_length = 38, data_len_left = 38
                FOO message: *reported_length = 38, *captured_length = 38, *data_len_left = 38
                my sub tree 1
                    FOO message: error, data len too short
                    change item text
            */

            break;
        }

        proto_tree_add_item(sub_tree_foo, hf_foo_pdu_type, tvb, 0, 1, ENC_BIG_ENDIAN);
        data_len_left -= 1;

        // 此时, "foo Protocol"节点有折叠的子树了
        /*
        foo Protocol
            FOO PDU Type : 0
        */


    } while (0);

    return captured_length; // return data length by process
}