一文教你如何用C代碼解析一段網絡數據包?【含代碼】


本文的目的是通過隨機截取的一段網絡數據包,然后根據協議類型來解析出這段內存。

學習本文需要掌握的基礎知識:

  1. 網絡協議
  2. C語言
  3. Linux操作
  4. 抓包工具的使用

其中抓包工具的安裝和使用見下文:

一文包你學會網絡數據抓包

視頻教學鏈接如下:

教你如何抓取網絡中的數據包!黑客必備技能

一、截取一個網絡數據包

通過抓包工具,隨機抓取一個tcp數據包


科萊抓包工具解析出的數據包信息如下:

數據包的內存信息:

數據信息可以直接拷貝出來:

二、用到的結構體

下面,一口君就手把手教大家如何解析出這些數據包的信息。

我們可以從Linux內核中找到協議頭的定義

  • 以太頭:
drivers\staging\rtl8188eu\include\if_ether.h	
struct ethhdr {
	unsigned char	h_dest[ETH_ALEN];	/* destination eth addr	*/
	unsigned char	h_source[ETH_ALEN];	/* source ether addr	*/
	unsigned short	h_proto;		/* packet type ID field	*/
};
  • IP頭
	include\uapi\linux\ip.h	
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)  //小端模式
	__u8	ihl:4,
		version:4;
#elif defined(__BIG_ENDIAN_BITFIELD)    //大端模式
	__u8	version:4,
		ihl:4;
#endif
	__u8	tos;
	__u16	tot_len;
	__u16	id;
	__u16	frag_off;
	__u8	ttl;
	__u8	protocol;
	__u16	check;
	__u32	saddr;
	__u32	daddr;
	/*The options start here. */
};

tcp頭

include\uapi\linux\tcp.h
struct tcphdr {
	__be16	source;
	__be16	dest;
	__be32	seq;
	__be32	ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
	__u16	res1:4,
		doff:4,
		fin:1,
		syn:1,
		rst:1,
		psh:1,
		ack:1,
		urg:1,
		ece:1,
		cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
	__u16	doff:4,
		res1:4,
		cwr:1,
		ece:1,
		urg:1,
		ack:1,
		psh:1,
		rst:1,
		syn:1,
		fin:1;
#else
#error	"Adjust your <asm/byteorder.h> defines"
#endif	
	__be16	window;
	__sum16	check;
	__be16	urg_ptr;
};

因為協議頭長度都是按照標准協議來定義的,

所以以太長度是14,
IP頭長度是20,
tcp頭長度是20,

各個協議頭對應的內存空間如下:

三、解析以太頭

#define MAC_ARG(p) p[0],p[1],p[2],p[3],p[4],p[5]
	struct ethhdr *ethh;
	unsigned char *p = pkt;
	
	ethh = (struct ethhdr *)p;

	printf("h_dest:%02x:%02x:%02x:%02x:%02x:%02x \n", MAC_ARG(ethh->h_dest));
	printf("h_source:%02x:%02x:%02x:%02x:%02x:%02x \n", MAC_ARG(ethh->h_source));
	printf("h_proto:%04x\n",ntohs(ethh->h_proto));

注意,數據包中的數據是網絡字節序,如果要提取數據一定要注意字節序問題
ethh->h_proto 是short類型,占2個字節,所以存儲到本地需要使用函數ntohs
其中:
n:network 網絡字節序
h:host 主機字節序
s:short 2個字節
l:long 4個字節
ntohl() :4字節網絡字節序數據轉換成主機字節序
htons() :2字節主機字節序數據轉換成網絡字節序
ntohs() :2字節網絡字節序數據轉換成主機字節序
htonl() :4字節主機字節序數據轉換成網絡字節序

當執行下面這條語句時,

ethh = (struct ethhdr *)p;

結構體指針變量eth的成員對應關系如下:

以太頭
最終打印結果如下:

四、解析ip頭

解析ip頭思路很簡單,

就是從pkt頭開始偏移過以太頭長度(14字節)就可以找到IP頭,

解析代碼如下:

#define IP_ARG(p)  p[0],p[1],p[2],p[3]
	/*
		解析IP頭
	*/
	if(ntohs(ethh->h_proto) == 0x0800)
	{
	
		iph = (struct iphdr *)(p + sizeof(struct ethhdr));

		q = (unsigned char *)&(iph->saddr);
		printf("src ip:%d.%d.%d.%d\n",IP_ARG(q));

		q = (unsigned char *)&(iph->daddr);
		printf("dest ip:%d.%d.%d.%d\n",IP_ARG(q));
	}

Iiph

最終解析結果如下:

 可以看到我們正確解析出了IP地址,
結果與抓包工具分析出的數據保持了一致。

其中protocol字段表示了ip協議后面的額協議類型,常見的值如下:

數值 描述
0 保留字段,用於IPv6(跳躍點到跳躍點選項)
1 Internet控制消息 (ICMP)
2 Internet組管理 (IGMP)
3 網關到網關 (GGP)
4 1P中的IP(封裝)
6 傳輸控制 (TCP)
7 CBT
8 外部網關協議 (EGP)
9 任何私有內部網關(Cisco在它的IGRP實現中使用) (IGP)
10 BBNRCC監視
11 網絡語音協議
12 PUP
13 ARGUS
14 EMCON
15 網絡診斷工具
16 混亂(Chaos)
17 用戶數據報文 (UDP)
41 1Pv6
58 1Pv6的ICMP
59 1Pv6的無下一個報頭
60 IPv6的信宿選項
89 OSPF IGP
92 多播傳輸協議
94 IP內部的IP封裝協議
95 可移動網絡互連控制協議
96 旗語通訊安全協議
97 IP中的以太封裝
98 封裝報頭
100 GMTP
101 Ipsilon流量管理協議
133~254 未分配
255 保留

五、解析tcp頭

查找tcp頭思路很,

就是從pkt頭開始偏移過以太頭長度(14字節)、和IP頭長度(20字節)就可以找到tcp頭,

	switch(iph->protocol)
		{
			case 0x1:
				//icmp
				break;
			case 0x6:
				//tcp				
				tcph = (struct tcphdr *)(p + sizeof(struct ethhdr) + sizeof(struct iphdr));
				printf("source:%d dest:%d \n",ntohs(tcph->source),ntohs(tcph->dest);	

				break;
			case 0x11:
				//udp
				
				break;
		}

結構體與內存對應關系

打印結果如下:

六、學會用不同格式打印這塊內存

在實際項目中,可能我們解析的並不是標准的TCP/IP協議數據包,

可能是我們自己的定義的協議數據包,

只要掌握了上述方法,

所有的協議分析都能夠手到擒來!

有時候我們還需要打印對方發送過來的數據幀內容,

往往我們會以16進制形式將所有數據打印出來,

這樣是最有利於我們分析數據內容的。

1. 按字節打印

代碼如下:

	for(i=0;i<400;i++)
	{
		printf("%02x ",pkt[i]);
		if(i%20 == 19)
		{
			printf("\n");
		}
	}

2. 按short類型分析一段內存

我們接收數據時,雖然使用一個unsigned char型數組,

但是有時候對方發送過來的數據可能是2個字節的數組,

那我們只需要用short類型的指針,指向內存的頭,

然后就可以通過該指針訪問到對方發送的數據,

這個時候一定要注意字節序問題,

不同場景可能不一樣,所以一定要具體問題具體分析,

本例因為是網絡字節序數據轉換成主機字節序,

所以需要轉換字節序。

//轉變short型字節序
void indian_reverse(unsigned short arr[],int num)
{
	int i;
	unsigned short temp;

	for(i=0;i<num;i++)
	{
		temp = 0;

		temp = (arr[i]&0xff00)>>8;
		temp |= (arr[i]&0xff)<<8;
		arr[i] = temp;
	}
}
main()
{
	unsigned short spkt[200];
	
	………………
	memcpy(spkt,pkt,sizeof(pkt));

	indian_reverse(spkt,ARRAY_SIZE(spkt));
	
	for(i=0;i<200;i++)
	{
		printf("%04x ",spkt[i]);
		if(i%10 == 9)
		{
			printf("\n");
		}
	}
	………………
}

結果如下:

完整代碼請關注公眾號:一口Linux,回復:數據包解析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM