netsniff-dpi

netsniff-dpi

​ 引言:本文回顾一下自己半年的项目,感慨颇多。回家乘着假期就把一些项目的点以及用到的知识点做一下整理。本次代码撰写是在netsniff这个原有的轮子上加上dpi的部分,能够在嵌入式的环境下提供采集、识别恶意流量以及报警功能。

1. 项目概览

​ netsniff的代码庞大而复杂,而且功能非常丰富。所以我只把我所需要改动的代码一块进行一些提炼,其他的相关的功能我把他放在最后附录中以便查阅。下图是从流量采集到识别的整个宏观的框架。当启动netsniff-ng命令后,程序读取网卡,获取流量。首先会判断ctx中的malware标志是否是带有恶意的标记,于是分两步走;在解析恶意流量部分从开始准备到解析网络层(ipv4、ipv6)到解析传输层(tcp、udp)同时也解析ICMP协议,最后结束工作;在解析所有流量中,先识别底层的网络协议以太网,还是ieee80211协议,在以太网协议中会仿照恶意流量解析思路,解析的遍历循环中把所有的协议添加在其中,而在解析ieee80211协议只是留有一定空白,并未填写代码。
在这里插入图片描述

​ 在识别流量方面,会先初始化一个hash表,然后把tcp_ops,udp_ops等需要用到的结构体插入hash表中,然后轮循遍历整个hash表去扫描恶意流量。如何初始化hash表在下面知识点1中会有涉及,在轮循过程中会有一个for_each_hash_int函数的支撑。(arg是ctx的print_mode)

int for_each_hash_int(const struct hash_table *table, 
                      int (*fn)(void *, int),
		      		  int arg)
{
	int sum = 0;
	unsigned int i;
	unsigned int size = table->size;
	struct hash_table_entry *array = table->array;

	for (i = 0; i < size; i++) {
		void *ptr = array->ptr;

		array++;
		if (ptr) {
			int val = fn(ptr, arg);
			if (val < 0)
				return val;

			sum += val;
		}
	}

	return sum;
}

fn函数则是回调函数 dissector_set_print_type 函数是在类似下面的结构体中调用函数tcp、tcp_less,进行扫描。

struct protocol tcp_ops = {
.key = 0x06,
.print_full = tcp,
.print_less = tcp_less,
.print_malware = tcp_malware,
};

int dissector_set_print_type(void *ptr, int type)
{
	struct protocol *proto;

	for (proto = ptr; proto; proto = proto->next) {
		switch (type) {
		case PRINT_NORM:
			proto->process = proto->print_full;
			break;
		case PRINT_LESS:
			proto->process = proto->print_less;
			break;
		default:
			proto->process = NULL;
			break;
		}
	}

	return 0;
}

1.1 恶意流量识别-TCP

​ 在上面框架中概括的,我们会初始化一个hash表,并且将含有钩子函数的结构体插入hash表内,接着通过轮循整个hash表来识别流量。在轮循每一个hash表里面的函数的时候就会调用结构体内的函数,进行识别流量。下面讲一下细节的函数。

​ 在讲细节函数之前,先把前面的结构体讲解一下,这个结构体是我们函数会拿到的前期分解好的数据,里面有头部指针、数据指针和尾部指针。而这里最重要的是数据指针,所谓的数据指针(data)就是在tcp头部之后的内容的第一个位置,这个会比较关键,下面都需要用到这个data指针。

struct pkt_buff {
	/* invariant: head <= data <= tail */
	uint8_t *head;
	uint8_t *data;
	uint8_t *tail;

	struct protocol *dissector;
	uint32_t link_type;
	struct sockaddr_ll *sll;

	/* added for malware traffic identification */
	/* ip */
	bool isip4;
	uint32_t  raw_src;
	uint32_t  raw_dst;
	char src_ip[INET_ADDRSTRLEN];
	char dst_ip[INET_ADDRSTRLEN];
	bool isip6;
	uint8_t * raw_src6;
	uint8_t * raw_dst6;
	char src_ip6[INET6_ADDRSTRLEN];
	char dst_ip6[INET6_ADDRSTRLEN];	

	uint8_t proto;
	/* tcp and udp */
	uint16_t src_port;
	uint16_t dst_port;

	uint8_t *tcpheader;	// (struct tcphdr *)
	uint8_t *udpheader;	// (struct udphdr *)
	uint32_t payloadLength; // tcp or udp payload
	
	struct ctx *context;

};

下面是tcp流量识别的整体过程:
在这里插入图片描述

​ 在match函数中会先初始化resp 这个结构体,如下面代码所示:

  int nCurState;
  struct BnfaResponse resp;
  resp.matchedCount = 0;
  resp.startWithHTTP = false;
  resp.hasHTTPTitle = false;
  resp.hasContentType = false;
  resp.contentType = NULL;
  resp.lengthOfContentType = 0;
  resp.data = pczSrc;
  resp.dataLength = nLen;
  resp.pkt = pkt;
  resp.isWhiteList = false;
  resp.hasPath = false;
  resp.hasBadPath = false;
  resp.IsIotMalware = false;
  resp.host_ip = NULL;
  resp.hasHost = false;
  resp.startWithHTTP = false;
  resp.hasHTTPTitle = false;
  resp.hasPortIs80 = false;

​ 接着执行int nRet = bnfaSearch(bnfa, pczSrc, nLen, matchFound, (void *) &resp, &nCurState);调用bnfasearch函数进行遍历扫描。这里大致讲一下bnfasearch的细节,如果在扫描数据包中遇到case里面的标签值,就会return 0,这样会让循环继续扫描这个包后面的东西。

for (; T<Tend; T++)
    {
        Tchar = xlatcase[ *T ];

        /* Transition to next state index */
        sindex = _bnfa_get_next_state_csparse_nfa(transList,sindex,Tchar);

        /* Log matches in this state - if any */
        if ( sindex && (transList[sindex+1] & BNFA_SPARSE_MATCH_BIT) )
        {
            if ( sindex == last_match )
                continue;

            last_match_saved = last_match;
            last_match = sindex;

            {
                mlist = MatchList[ transList[sindex] ];
                if ( !mlist )
                    //为空
                    return nfound;

                patrn = (bnfa_pattern_t*)mlist->data;
                index = T - Tx + 1;
                nfound++;
                /* Don't do anything specific for case sensitive patterns and not,
                 * since that will be covered by the rule tree itself.  Each tree
                 * might have both case sensitive & case insensitive patterns.
                 */
                res = match(patrn->userdata, mlist->rule_option_tree, index,
                    context, mlist->neg_list);
                if ( res > 0 )
                {
                    *current_state = sindex;
                    return nfound;
                }
                else if ( res < 0 )
                {
                    last_match = last_match_saved;
                }
            }
        }
    }

static int matchFound(void *user, void *tree, int index, void *data, void *neglist)

match(patrn->userdata, mlist->rule_option_tree, index,context, mlist->neg_list);

1.2 恶意流量识别-UDP

1.3 运行打印信息解读

打印信息汇总:

< lo 85 1612246954s.455105451ns #1
[ Eth MAC (00:00:00:00:00:00 => 00:00:00:00:00:00), Proto (0x0800, [1mIPv4
[0m) ]
[ Vendor (XEROX CORPORATION => XEROX CORPORATION) ]

[ IPv4 Addr (127.0.0.1 => 127.0.0.53), Proto (17), TTL (64), TOS (0), Ver (4), IHL (5), Tlen (71), ID (13845), Res (0), NoFrag (1), MoreFrag (0), FragOff (0), CSum (0x065b) is ok ]

[ UDP Port (60655 => 53 ([1mdomain
[0m)), Len (51 Bytes, 43 Bytes Data), CSum (0xfe7a) ]
[ Chr a…ntp.ubuntu.com…)… ]
[ Hex 61 a4 01 00 00 01 00 00 00 00 00 01 03 6e 74 70 06 75 62 75 6e 74 75 03 63 6f 6d 00 00 01 00 01 00 00 29 04 b0 00 00 00 00 00 00 ]

数据链路层协议mac src-dstproto协议
Eth MAC00:00:00:00:00:00 => 00:00:00:00:00:000x0800

IP层头部属性可以参照下图可以对号入座

[ IPv4 Addr (127.0.0.1 => 127.0.0.53), Proto (17), TTL (64), TOS (0), Ver (4), IHL (5), Tlen (71), ID (13845), Res (0), NoFrag (1), MoreFrag (0), FragOff (0), CSum (0x065b) is ok ]

在这里插入图片描述

[ UDP Port (60655 => 53 ( [1mdomain

[0m)), Len (51 Bytes, 43 Bytes Data), CSum (0xfe7a) ]

udp流量是有60655端口到53端口,以及包的大小长度这是DNS需要解析的域名

[ Chr a…ntp.ubuntu.com…)… ]

下面的部分是data

[ Hex 61 a4 01 00 00 01 00 00 00 00 00 01 03 6e 74 70 06 75 62 75 6e 74 75 03 63 6f 6d 00 00 0100 01 00 00 29 04 b0 00 00 00 00 00 00 ]

2. 知识点

2.1 hash表

首先解决第一层参数问题,一共两个参数,分别来解析一下

ops是报文协议的结构体 (protocol suite)比如说:

struct protocol vlan_ops = {
.key = 0x8100,
.print_full = vlan,
.print_less = vlan_less,
};类似这种结构

table在这里的代码中都是用eth_lay2起名,其中里面有unsigned int size,nr; 结构体指针 array(int hash和int *ptr)

#define INSERT_HASH_PROTOS(ops, table)					\
	do {								\
	//insert_hash插入的哈希表的第一位参数是该型号
		void **pos = insert_hash((ops).key, &(ops), &(table));	\
		/* We already had an entry there? */			\
		if (pos) {						\
			(ops).next = *pos;				\
			*pos = &(ops);					\
		}							\
	} while (0)

插入hash表格

void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table)
{
	unsigned int nr = table->nr;

	if (nr >= table->size/2)
		grow_hash_table(table);

	return insert_hash_entry(hash, ptr, table);
}

生成hash表

static void grow_hash_table(struct hash_table *table)
{
	unsigned int i;
	unsigned int old_size = table->size, new_size;
	struct hash_table_entry *old_array = table->array, *new_array;

	new_size = alloc_nr(old_size);
	new_array = xcalloc(new_size, sizeof(struct hash_table_entry));

	table->size = new_size;
	table->array = new_array;
	table->nr = 0;

	for (i = 0; i < old_size; i++) {
		unsigned int hash = old_array[i].hash;
		void *ptr = old_array[i].ptr;

		if (ptr)
			insert_hash_entry(hash, ptr, table);
	}

	free(old_array);
}

2.2 DNS知识点

(未更,下次在更)

附录

netsniff代码其他功能解读

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值