一文教你如何用C代碼解析一段網(wǎng)絡(luò)數(shù)據(jù)包?
本文的目的是通過隨機(jī)截取的一段網(wǎng)絡(luò)數(shù)據(jù)包,然后根據(jù)協(xié)議類型來解析出這段內(nèi)存。
學(xué)習(xí)本文需要掌握的基礎(chǔ)知識(shí):
- 網(wǎng)絡(luò)協(xié)議
- C語言
- Linux操作
- 抓包工具的使用
其中抓包工具的安裝和使用見下文:
《一文包你學(xué)會(huì)網(wǎng)絡(luò)數(shù)據(jù)抓包》
視頻教學(xué)鏈接如下:
《教你如何抓取網(wǎng)絡(luò)中的數(shù)據(jù)包!黑客必備技能》
一、截取一個(gè)網(wǎng)絡(luò)數(shù)據(jù)包
通過抓包工具,隨機(jī)抓取一個(gè)tcp數(shù)據(jù)包

科萊抓包工具解析出的數(shù)據(jù)包信息如下:

數(shù)據(jù)包的內(nèi)存信息:

數(shù)據(jù)信息可以直接拷貝出來:

二、用到的結(jié)構(gòu)體
下面,一口君就手把手教大家如何解析出這些數(shù)據(jù)包的信息。
我們可以從Linux內(nèi)核中找到協(xié)議頭的定義
以太頭:
- 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;
- };
因?yàn)閰f(xié)議頭長度都是按照標(biāo)準(zhǔn)協(xié)議來定義的,
所以以太長度是14, IP頭長度是20, tcp頭長度是20,
各個(gè)協(xié)議頭對(duì)應(yīng)的內(nèi)存空間如下:

三、解析以太頭
- #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));
- 注意,數(shù)據(jù)包中的數(shù)據(jù)是網(wǎng)絡(luò)字節(jié)序,如果要提取數(shù)據(jù)一定要注意字節(jié)序問題 ethh->h_proto 是short類型,占2個(gè)字節(jié),所以存儲(chǔ)到本地需要使用函數(shù)ntohs 其中:
- n:network 網(wǎng)絡(luò)字節(jié)序
- h:host 主機(jī)字節(jié)序
- s:short 2個(gè)字節(jié) l:
- long 4個(gè)字節(jié)
- ntohl() :4字節(jié)網(wǎng)絡(luò)字節(jié)序數(shù)據(jù)轉(zhuǎn)換成主機(jī)字節(jié)序
- htons() :2字節(jié)主機(jī)字節(jié)序數(shù)據(jù)轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序
- ntohs() :2字節(jié)網(wǎng)絡(luò)字節(jié)序數(shù)據(jù)轉(zhuǎn)換成主機(jī)字節(jié)序
- htonl() :4字節(jié)主機(jī)字節(jié)序數(shù)據(jù)轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序
當(dāng)執(zhí)行下面這條語句時(shí),
- ethh = (struct ethhdr *)p;
結(jié)構(gòu)體指針變量eth的成員對(duì)應(yīng)關(guān)系如下:

最終打印結(jié)果如下:

四、解析ip頭
解析ip頭思路很簡單,
就是從pkt頭開始偏移過以太頭長度(14字節(jié))就可以找到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
最終解析結(jié)果如下:

可以看到我們正確解析出了IP地址, 結(jié)果與抓包工具分析出的數(shù)據(jù)保持了一致。
其中protocol字段表示了ip協(xié)議后面的額協(xié)議類型,常見的值如下:

五、解析tcp頭
查找tcp頭思路很,
就是從pkt頭開始偏移過以太頭長度(14字節(jié))、和IP頭長度(20字節(jié))就可以找到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;
- }
結(jié)構(gòu)體與內(nèi)存對(duì)應(yīng)關(guān)系

打印結(jié)果如下:

六、學(xué)會(huì)用不同格式打印這塊內(nèi)存
在實(shí)際項(xiàng)目中,可能我們解析的并不是標(biāo)準(zhǔn)的TCP/IP協(xié)議數(shù)據(jù)包,
可能是我們自己的定義的協(xié)議數(shù)據(jù)包,
只要掌握了上述方法,
所有的協(xié)議分析都能夠手到擒來!
有時(shí)候我們還需要打印對(duì)方發(fā)送過來的數(shù)據(jù)幀內(nèi)容,
往往我們會(huì)以16進(jìn)制形式將所有數(shù)據(jù)打印出來,
這樣是最有利于我們分析數(shù)據(jù)內(nèi)容的。
1. 按字節(jié)打印
代碼如下:
- for(i=0;i<400;i++)
- {
- printf("%02x ",pkt[i]);
- if(i%20 == 19)
- {
- printf("\n");
- }
- }

2. 按short類型分析一段內(nèi)存
我們接收數(shù)據(jù)時(shí),雖然使用一個(gè)unsigned char型數(shù)組,
但是有時(shí)候?qū)Ψ桨l(fā)送過來的數(shù)據(jù)可能是2個(gè)字節(jié)的數(shù)組,
那我們只需要用short類型的指針,指向內(nèi)存的頭,
然后就可以通過該指針訪問到對(duì)方發(fā)送的數(shù)據(jù),
這個(gè)時(shí)候一定要注意字節(jié)序問題,
不同場景可能不一樣,所以一定要具體問題具體分析,
本例因?yàn)槭蔷W(wǎng)絡(luò)字節(jié)序數(shù)據(jù)轉(zhuǎn)換成主機(jī)字節(jié)序,
所以需要轉(zhuǎn)換字節(jié)序。
- //轉(zhuǎn)變short型字節(jié)序
- 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");
- }
- }
- ………………
- }
結(jié)果如下:

轉(zhuǎn)載本文請(qǐng)聯(lián)系一口Linux公眾號(hào)。



































