前言

今天中午,已经可以对wiresharkUI元素进行操作了。

刚开始做试验的时候,将所有协议都当作foo协议来解析,因为数据和逻辑都是假的,整的没意思。
后来想接管wireshark ICMP的协议分析, 这样协议又简单,数据又真实,又不用自己做简单协议程序,又可以学到经典协议的细节实现。

开始研究foo插件分析ICMP协议的实现细节。

今天收获不小,会用自产的wireshark插件来分析真实的通讯协议了. wireshark插件这层窗户纸已经捅破了:)

看wireshark文档,也有开发规范, 不能这个,不能那个。如果不向官方贡献代码,就不用管wireshark的开发规范。自己用起来爽才是真的好。

wireshark官方代码复杂度还是蛮高的,不是指协议分析有多难,而是必须用wireshark的API来做事,这个对于我这样的业余选手来说,有点多余了。只要掌握怎么向wirsharkUI上添加子树,如何在子树上添加子树,如何在子树上增加文本类型的数据,这就够了。任何数据都可以转成文本类型的buf来显示。任何挎包的处理都可以自己来做。

等有时间还要再研究一下,程序退出时,在插件的那个回调上能收到通知,要不对于挎包开辟的缓冲区,就存在内存泄漏。开始看官方文档,好像看到过,现在想找,一时半会还找不出来,已经忘了在哪看到的。等以后有时间再玩。

ICMP协议下载点

Unit5_ICMP.pdf

运行效果

wireshark geoip插件 wireshark实用插件_wireshark geoip插件

第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
}