前言
今天中午,已经可以对wiresharkUI元素进行操作了。
刚开始做试验的时候,将所有协议都当作foo协议来解析,因为数据和逻辑都是假的,整的没意思。
后来想接管wireshark ICMP的协议分析, 这样协议又简单,数据又真实,又不用自己做简单协议程序,又可以学到经典协议的细节实现。
开始研究foo插件分析ICMP协议的实现细节。
今天收获不小,会用自产的wireshark插件来分析真实的通讯协议了. wireshark插件这层窗户纸已经捅破了:)
看wireshark文档,也有开发规范, 不能这个,不能那个。如果不向官方贡献代码,就不用管wireshark的开发规范。自己用起来爽才是真的好。
wireshark官方代码复杂度还是蛮高的,不是指协议分析有多难,而是必须用wireshark的API来做事,这个对于我这样的业余选手来说,有点多余了。只要掌握怎么向wirsharkUI上添加子树,如何在子树上添加子树,如何在子树上增加文本类型的数据,这就够了。任何数据都可以转成文本类型的buf来显示。任何挎包的处理都可以自己来做。
等有时间还要再研究一下,程序退出时,在插件的那个回调上能收到通知,要不对于挎包开辟的缓冲区,就存在内存泄漏。开始看官方文档,好像看到过,现在想找,一时半会还找不出来,已经忘了在哪看到的。等以后有时间再玩。
ICMP协议下载点
运行效果
第1个要解决的问题
如何接管ICMP的协议,打开pcap文件后,直接到自己的foo插件,不去wireshark实现的packet-icmp.c.
看了packet-icmp.c的协议注册代码,试试和他一样会如何.
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);
// 注册了ICMP的回调处理后, wireshark不分析了, 只交给插件来处理ICMP协议
dissector_add_uint("ip.proto", IP_PROTO_ICMP, dissector_handle_foo);
}
一试居然可以,打开pcap文件后,ICMP数据直接进了foo插件. ok.
第2个要解决的问题
写到累加和校验时,发现自己算的和ICMP数据中的累加和,总是差了几个数,看起来很像,高字节相同,低字节不同。
后来参考了http://www.faqs.org/rfcs/rfc1071.html, 搞定。
原来,算累加和时,要再加上carry值,再取反才是最后的累加和.
wireshark代码走读
看了wireshark的累加和实现,精致,兼容性强。还需要花时间再扒出来,以后再弄吧。
看了wireshark分析ICMP的实现,精致,有些包,我这样粗糙的分析,是不行的。不过手头的样本(win10x64下, ping 存在的公网域名)粗略的分析起来,还是效果挺好的。ICMP协议分ICMP和ICMP64两种。我手头样本属于ICMP协议。
自产wireshark插件foo主实现
官方packet-icmp.c的实现有2000行.
我这foo插件,只写了400行,写的太糙了,差距咋这么大呢…
// @file packet-foo.c
// @brief the foo plugin parse ICMP protocol
// @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/PartDevelopment.html
// 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 <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include "config.h"
#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/dissectors/packet-tcp.h>
#include <epan/ipproto.h>
#include "packet-foo.h"
#define PROTOCOL_FULL_NAME_FOO "ICMP protocol parse by foo plugin"
#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);
// 注册了ICMP的回调处理后, wireshark不分析了, 只交给插件来处理ICMP协议
dissector_add_uint("ip.proto", IP_PROTO_ICMP, dissector_handle_foo);
}
// wireshark工程应该是已经1字节对齐
typedef struct _tag_icmp_code {
char type;
char code;
}TAG_ICMP_CODE;
typedef struct _tag_icmp_code_desc {
const char* desc;
TAG_ICMP_CODE code;
}TAG_ICMP_CODE_DESC;
static TAG_ICMP_CODE_DESC icmp_code_desc_ary[] = {
//Type Code description
//0 0 echo reply (ping)
{"echo reply (ping)", { 0, 0 }},
//3 0 dest network unreachable
{"dest network unreachable", { 3, 0 }},
//3 1 dest host unreachable
{"dest host unreachable", { 3, 1 }},
//3 2 dest protocol unreachable
{ "dest protocol unreachable",{ 3, 2 } },
//3 3 dest port unreachable
{ "dest port unreachable",{ 3, 3 } },
//3 6 dest network unknown
{ "dest network unknown",{ 3, 6 } },
//3 7 dest host unknown
{ "dest host unknown",{ 3, 7 } },
//4 0 source quench (congestion control - not used)
{ "source quench (congestion control - not used)",{ 4, 0 } },
//8 0 echo request (ping)
{ "echo request (ping)",{ 8, 0 } },
//9 0 route advertisement
{ "route advertisement",{ 9, 0 } },
//10 0 router discovery
{ "router discovery",{ 10, 0 } },
//11 0 TTL expired
{ "TTL expired",{ 11, 0 } },
//12 0 bad IP header
{ "bad IP header",{ 12, 0 } },
{NULL, {-1, -1}},
};
// https://stackoverflow.com/questions/20247551/icmp-echo-checksum
// http://www.faqs.org/rfcs/rfc1071.html
static guint16 calc_check_sum(const guint8* data, int len)
{
guint32 sum = 0;
guint16 tmp = 0;
int i = 0;
do {
if ((NULL == data) || (len <= 0) || (0 != (len % 8))) {
break;
}
for (i = 0; i < len; i += 2) {
tmp = ntohs(*(guint16*)(data + i)); // BE data
if (0 == i) {
sum = tmp;
continue;
} else if (2 == i) {
// this is check sum, need skip it
continue;
}
else {
sum += tmp;
}
}
// add carry bit !
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
};
sum = ~sum;
} while (0);
return (guint16)sum;
}
static int dissect_proc_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_)
{
bool b_pasre_ok = false;
TAG_ICMP_CODE icmp_code;
char sz_buf[4096] = { '\0' };
proto_item* item = NULL;
proto_item* item_tmp = 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_offset = 0; // 要处理的数据开始偏移
guint data_len_left = captured_length; // 要处理的数据长度
const guint8* data_left = NULL; // 要处理的数据
const guint8* data_org = NULL; // 要处理的原始数据
int i = 0;
bool was_find = false;
guint16 check_sum = 0;
guint16 check_sum_by_calc = 0;
guint16 icmp_id = 0;
guint16 sequence_number = 0;
// set col.5 = "Protocol" 's content to 'foo'
col_set_str(pinfo->cinfo, COL_PROTOCOL, "ICMP");
// set col.7 = "Info" 's content to empty, else will display by tcp info
col_clear(pinfo->cinfo, COL_INFO);
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 (NULL == sub_tree_foo) {
break;
}
// data_len_left is the ICMP data len
// data_left is the ICMP data
data_org = tvb_get_ptr(tvb, 0, -1);
data_left = data_org;
// --------------------------------------------------------------------------------
// read ICMP type and code
// --------------------------------------------------------------------------------
if (data_len_left < sizeof(TAG_ICMP_CODE)) {
break;
}
memcpy(&icmp_code, data_left, sizeof(TAG_ICMP_CODE));
data_left += sizeof(TAG_ICMP_CODE);
data_len_left -= sizeof(TAG_ICMP_CODE);
// show ICMP.type
item_tmp = proto_tree_add_string_format_value(sub_tree_foo, hf_foo_message, tvb, offsetof(TAG_ICMP_CODE, type), sizeof(icmp_code.type),
NULL, // 可选
NULL,
-1);
proto_item_set_text(item_tmp, "ICMP.type = %d", icmp_code.type);
// show ICMP.code
item_tmp = proto_tree_add_string_format_value(sub_tree_foo, hf_foo_message, tvb, offsetof(TAG_ICMP_CODE, code), sizeof(icmp_code.code),
NULL, // 可选
NULL,
-1);
proto_item_set_text(item_tmp, "ICMP.code = %d", icmp_code.code);
// --------------------------------------------------------------------------------
// show ICMP desc
// --------------------------------------------------------------------------------
was_find = false;
for (i = 0; i < (int)(sizeof(icmp_code_desc_ary) / sizeof(icmp_code_desc_ary[0])); i++) {
if (NULL == icmp_code_desc_ary[i].desc) {
break;
}
if ((icmp_code_desc_ary[i].code.type == icmp_code.type)
&& (icmp_code_desc_ary[i].code.code == icmp_code.code))
{
was_find = true;
break;
}
}
// show ICMP packet is valid or not
item_tmp = proto_tree_add_string_format_value(sub_tree_foo, hf_foo_message, tvb, 0, sizeof(icmp_code),
NULL, // 可选
NULL,
-1);
if (!was_find) {
// show error msg
proto_item_set_text(item_tmp, "unknown ICMP type and code, please connect developer");
break;
}
else {
// show icmp desc for (icmp.type + icmp.code)
proto_item_set_text(item_tmp, "this is valid ICMP packet : [%s]", icmp_code_desc_ary[i].desc);
}
// update data offset
data_offset += sizeof(TAG_ICMP_CODE);
// --------------------------------------------------------------------------------
// get check sum
// --------------------------------------------------------------------------------
if (data_len_left < sizeof(check_sum)) {
break;
}
check_sum = ntohs(*(guint16*)data_left); // BE data
// calc check sum
check_sum_by_calc = calc_check_sum((guint8*)data_org, captured_length);
data_left += sizeof(check_sum);
data_len_left -= sizeof(check_sum);
// show checksum
item_tmp = proto_tree_add_string_format_value(sub_tree_foo, hf_foo_message, tvb, data_offset, sizeof(check_sum),
NULL, // 可选
NULL,
-1);
proto_item_set_text(item_tmp, "check sum %s : [0x%X]", (check_sum_by_calc == check_sum) ? "ok" : "error", check_sum);
// update data offset
data_offset += sizeof(check_sum);
// --------------------------------------------------------------------------------
// read icmp id
// --------------------------------------------------------------------------------
if (data_len_left < sizeof(check_sum)) {
break;
}
icmp_id = *(guint16*)data_left;
data_left += sizeof(icmp_id);
data_len_left -= sizeof(icmp_id);
// show icmp_id
item_tmp = proto_tree_add_string_format_value(sub_tree_foo, hf_foo_message, tvb, data_offset, sizeof(icmp_id),
NULL, // 可选
NULL,
-1);
proto_item_set_text(item_tmp, "ID : [0x%X]", icmp_id);
// update data offset
data_offset += sizeof(icmp_id);
// --------------------------------------------------------------------------------
// read sequence number
// --------------------------------------------------------------------------------
if (data_len_left < sizeof(sequence_number)) {
break;
}
sequence_number = ntohs(*(guint16*)data_left); // data is BE
data_left += sizeof(sequence_number);
data_len_left -= sizeof(sequence_number);
// show icmp_id
item_tmp = proto_tree_add_string_format_value(sub_tree_foo, hf_foo_message, tvb, data_offset, sizeof(sequence_number),
NULL, // 可选
NULL,
-1);
proto_item_set_text(item_tmp, "sequence number : [0x%X]", sequence_number);
// update data offset
data_offset += sizeof(sequence_number);
// --------------------------------------------------------------------------------
// show data
// --------------------------------------------------------------------------------
// 加一个子树来展现数据长度和数据
sub_tree_foo_sub1 = proto_tree_add_subtree(sub_tree_foo, tvb, data_offset, data_len_left, ett_foo_subtree_1, NULL, "data");
item_tmp = proto_tree_add_string_format_value(sub_tree_foo_sub1, hf_foo_message, tvb, data_offset, data_len_left,
NULL, // 可选
NULL,
-1);
proto_item_set_text(item_tmp, "data len = %d(bytes)", data_len_left);
if (data_len_left > 0) {
item_tmp = proto_tree_add_string_format_value(sub_tree_foo_sub1, hf_foo_message, tvb, data_offset, data_len_left,
NULL, // 可选
NULL,
-1);
/*
if is ping request or reply, show text data, else show binary data on data area
{ "echo request (ping)", { 8, 0 } },
{ "echo reply (ping)",{ 0, 0 } },
*/
if (((8 == icmp_code.type) && (0 == icmp_code.code))
|| ((0 == icmp_code.type) && (0 == icmp_code.code)))
{
memcpy(sz_buf, data_left, data_len_left);
sz_buf[data_len_left] = '\0';
proto_item_set_text(item_tmp, "data = [%s]", sz_buf);
}
else {
proto_item_set_text(item_tmp, "data maybe binary, please see it on data area");
}
}
b_pasre_ok = true;
} while (0);
if (NULL != sub_tree_foo) {
// show END message, parse ok or error
item_tmp = proto_tree_add_string_format_value(sub_tree_foo, hf_foo_message, tvb, 0, -1,
NULL, // 可选
NULL,
-1);
proto_item_set_text(item_tmp, b_pasre_ok ? "nice, parse ok" : "error, please connect developer");
}
return captured_length; // return data length by process
}