前言
要想用wireshark 插件做一个最简单的协议分析(不挎包),除了分析协议数据(纯C, 和wireshark无关),还要将分析的结果显示在wireshark树区和数据区, 只要能在树区添加子树,在子树上再添加子树,在子树上添加文本,这事就搞定了。
运行效果
最简单任务要调用的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 …
同事以前给我演示,将一种协议按照另外一种协议分析时,不生效。
这段时间发现,选择的端口,必须是已经注册的协议端口,而不能是普通的未注册的端口.
试验
// @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修改后,看到的就是实际设置的文本,去掉了字段名称的显示。
运行效果
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
}