本文的目的是通過隨機截取的一段網(wǎng)絡數(shù)據(jù)包,然后根據(jù)協(xié)議類型來解析出這段內存。
學習本文需要掌握的基礎知識:
- 網(wǎng)絡協(xié)議C語言Linux操作抓包工具的使用
視頻教學鏈接如下:
《教你如何抓取網(wǎng)絡中的數(shù)據(jù)包!黑客必備技能》
一、截取一個網(wǎng)絡數(shù)據(jù)包
通過抓包工具,隨機抓取一個tcp數(shù)據(jù)包
科萊抓包工具解析出的數(shù)據(jù)包信息如下:
數(shù)據(jù)包的內存信息:
數(shù)據(jù)信息可以直接拷貝出來:
二、用到的結構體
下面,一口君就手把手教大家如何解析出這些數(shù)據(jù)包的信息。
我們可以從Linux內核中找到協(xié)議頭的定義
- 以太頭:
driversstagingrtl8188euincludeif_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頭
includeuapilinuxip.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頭
includeuapilinuxtcp.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;
};
因為協(xié)議頭長度都是按照標準協(xié)議來定義的,所以以太長度是14, IP頭長度是20, tcp頭長度是20,各個協(xié)議頭對應的內存空間如下:
三、解析以太頭
#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:%04xn",ntohs(ethh->h_proto));
注意,數(shù)據(jù)包中的數(shù)據(jù)是網(wǎng)絡字節(jié)序,如果要提取數(shù)據(jù)一定要注意字節(jié)序問題 ethh->h_proto 是short類型,占2個字節(jié),所以存儲到本地需要使用函數(shù)ntohs 其中:n:network 網(wǎng)絡字節(jié)序 h:host 主機字節(jié)序 s:short 2個字節(jié) l:long 4個字節(jié) ntohl() :4字節(jié)網(wǎng)絡字節(jié)序數(shù)據(jù)轉換成主機字節(jié)序 htons() :2字節(jié)主機字節(jié)序數(shù)據(jù)轉換成網(wǎng)絡字節(jié)序 ntohs() :2字節(jié)網(wǎng)絡字節(jié)序數(shù)據(jù)轉換成主機字節(jié)序 htonl() :4字節(jié)主機字節(jié)序數(shù)據(jù)轉換成網(wǎng)絡字節(jié)序
當執(zhí)行下面這條語句時,
ethh = (struct ethhdr *)p;
結構體指針變量eth的成員對應關系如下:
最終打印結果如下:
四、解析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.%dn",IP_ARG(q));
q = (unsigned char *)&(iph->daddr);
printf("dest ip:%d.%d.%d.%dn",IP_ARG(q));
}
Iiph
最終解析結果如下:
可以看到我們正確解析出了IP地址, 結果與抓包工具分析出的數(shù)據(jù)保持了一致。
其中protocol字段表示了ip協(xié)議后面的額協(xié)議類型,常見的值如下:
數(shù)值 | 描述 |
---|---|
0 | 保留字段,用于IPv6(跳躍點到跳躍點選項) |
1 | Internet控制消息 (ICMP) |
2 | Internet組管理 (IGMP) |
3 | 網(wǎng)關到網(wǎng)關 (GGP) |
4 | 1P中的IP(封裝) |
6 | 傳輸控制 (TCP) |
7 | CBT |
8 | 外部網(wǎng)關協(xié)議 (EGP) |
9 | 任何私有內部網(wǎng)關(Cisco在它的IGRP實現(xiàn)中使用) (IGP) |
10 | BBNRCC監(jiān)視 |
11 | 網(wǎng)絡語音協(xié)議 |
12 | PUP |
13 | ARGUS |
14 | EMCON |
15 | 網(wǎng)絡診斷工具 |
16 | 混亂(Chaos) |
17 | 用戶數(shù)據(jù)報文 (UDP) |
41 | 1Pv6 |
58 | 1Pv6的ICMP |
59 | 1Pv6的無下一個報頭 |
60 | IPv6的信宿選項 |
89 | OSPF IGP |
92 | 多播傳輸協(xié)議 |
94 | IP內部的IP封裝協(xié)議 |
95 | 可移動網(wǎng)絡互連控制協(xié)議 |
96 | 旗語通訊安全協(xié)議 |
97 | IP中的以太封裝 |
98 | 封裝報頭 |
100 | GMTP |
101 | Ipsilon流量管理協(xié)議 |
133~254 | 未分配 |
255 | 保留 |
五、解析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;
}
結構體與內存對應關系
打印結果如下:
六、學會用不同格式打印這塊內存
在實際項目中,可能我們解析的并不是標準的TCP/IP協(xié)議數(shù)據(jù)包,可能是我們自己的定義的協(xié)議數(shù)據(jù)包,只要掌握了上述方法,所有的協(xié)議分析都能夠手到擒來!
有時候我們還需要打印對方發(fā)送過來的數(shù)據(jù)幀內容,往往我們會以16進制形式將所有數(shù)據(jù)打印出來,這樣是最有利于我們分析數(shù)據(jù)內容的。
1. 按字節(jié)打印
代碼如下:
for(i=0;i<400;i++)
{
printf("%02x ",pkt[i]);
if(i%20 == 19)
{
printf("n");
}
}
2. 按short類型分析一段內存
我們接收數(shù)據(jù)時,雖然使用一個unsigned char型數(shù)組,但是有時候對方發(fā)送過來的數(shù)據(jù)可能是2個字節(jié)的數(shù)組,那我們只需要用short類型的指針,指向內存的頭,然后就可以通過該指針訪問到對方發(fā)送的數(shù)據(jù),這個時候一定要注意字節(jié)序問題,不同場景可能不一樣,所以一定要具體問題具體分析,本例因為是網(wǎng)絡字節(jié)序數(shù)據(jù)轉換成主機字節(jié)序,所以需要轉換字節(jié)序。
//轉變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");
}
}
………………
}
結果如下:
好了,這個例子掌握了,那么網(wǎng)絡就算入門了,快操練起來吧!