本文的目的是通過隨機截取的一段網絡數據包,然后根據協議類型來解析出這段內存。
學習本文需要掌握的基礎知識:
- 網絡協議
- C語言
- Linux操作
- 抓包工具的使用
其中抓包工具的安裝和使用見下文:
視頻教學鏈接如下:
一、截取一個網絡數據包
通過抓包工具,隨機抓取一個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));
}
最終解析結果如下:
可以看到我們正確解析出了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,回復:數據包解析