zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

一文教你如何用C代码解析一段网络数据包?

2023-03-07 09:48:31 时间

本文的目的是通过随机截取的一段网络数据包,然后根据协议类型来解析出这段内存。

学习本文需要掌握的基础知识:

  1. 网络协议
  2. C语言
  3. Linux操作
  4. 抓包工具的使用

其中抓包工具的安装和使用见下文:

一文包你学会网络数据抓包

视频教学链接如下:

教你如何抓取网络中的数据包!黑客必备技能

一、截取一个网络数据包

通过抓包工具,随机抓取一个tcp数据包

一文教你如何用C代码解析一段网络数据包?「含代码」

科莱抓包工具解析出的数据包信息如下:

一文教你如何用C代码解析一段网络数据包?「含代码」

数据包的内存信息:

一文教你如何用C代码解析一段网络数据包?「含代码」

数据信息可以直接拷贝出来:

一文教你如何用C代码解析一段网络数据包?「含代码」

二、用到的结构体

下面,一口君就手把手教大家如何解析出这些数据包的信息。

我们可以从Linux内核中找到协议头的定义

以太头:

  1. drivers\staging\rtl8188eu\include\if_ether.h  
  1. struct ethhdr { 
  2.  unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ 
  3.  unsigned char h_source[ETH_ALEN]; /* source ether addr */ 
  4.  unsigned short h_proto;  /* packet type ID field */ 
  5. }; 

IP头

  1. include\uapi\linux\ip.h  
  1. struct iphdr { 
  2. #if defined(__LITTLE_ENDIAN_BITFIELD)  //小端模式 
  3.  __u8 ihl:4, 
  4.   version:4; 
  5. #elif defined(__BIG_ENDIAN_BITFIELD)    //大端模式 
  6.  __u8 version:4, 
  7.   ihl:4; 
  8. #endif 
  9.  __u8 tos; 
  10.  __u16 tot_len; 
  11.  __u16 id; 
  12.  __u16 frag_off; 
  13.  __u8 ttl; 
  14.  __u8 protocol; 
  15.  __u16 check
  16.  __u32 saddr; 
  17.  __u32 daddr; 
  18.  /*The options start here. */ 
  19. }; 

tcp头

  1. include\uapi\linux\tcp.h 
  1. struct tcphdr { 
  2.  __be16 source; 
  3.  __be16 dest; 
  4.  __be32 seq; 
  5.  __be32 ack_seq; 
  6. #if defined(__LITTLE_ENDIAN_BITFIELD) 
  7.  __u16 res1:4, 
  8.   doff:4, 
  9.   fin:1, 
  10.   syn:1, 
  11.   rst:1, 
  12.   psh:1, 
  13.   ack:1, 
  14.   urg:1, 
  15.   ece:1, 
  16.   cwr:1; 
  17. #elif defined(__BIG_ENDIAN_BITFIELD) 
  18.  __u16 doff:4, 
  19.   res1:4, 
  20.   cwr:1, 
  21.   ece:1, 
  22.   urg:1, 
  23.   ack:1, 
  24.   psh:1, 
  25.   rst:1, 
  26.   syn:1, 
  27.   fin:1; 
  28. #else 
  29. #error "Adjust your <asm/byteorder.h> defines" 
  30. #endif  
  31.  __be16 window; 
  32.  __sum16 check
  33.  __be16 urg_ptr; 
  34. }; 

因为协议头长度都是按照标准协议来定义的,

所以以太长度是14, IP头长度是20, tcp头长度是20,

各个协议头对应的内存空间如下:

一文教你如何用C代码解析一段网络数据包?「含代码」

三、解析以太头

  1. #define MAC_ARG(p) p[0],p[1],p[2],p[3],p[4],p[5] 
  1. struct ethhdr *ethh; 
  2.  unsigned char *p = pkt; 
  3.   
  4.  ethh = (struct ethhdr *)p; 
  5.  
  6.  printf("h_dest:%02x:%02x:%02x:%02x:%02x:%02x \n", MAC_ARG(ethh->h_dest)); 
  7.  printf("h_source:%02x:%02x:%02x:%02x:%02x:%02x \n", MAC_ARG(ethh->h_source)); 
  8.  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字节主机字节序数据转换成网络字节序

当执行下面这条语句时,

  1. ethh = (struct ethhdr *)p; 

结构体指针变量eth的成员对应关系如下:

一文教你如何用C代码解析一段网络数据包?「含代码」

最终打印结果如下:

一文教你如何用C代码解析一段网络数据包?「含代码」

四、解析ip头

解析ip头思路很简单,

就是从pkt头开始偏移过以太头长度(14字节)就可以找到IP头,

解析代码如下:

  1. #define IP_ARG(p)  p[0],p[1],p[2],p[3] 
  1. /* 
  2.  解析IP头 
  3. */ 
  4. if(ntohs(ethh->h_proto) == 0x0800) 
  5.  
  6.  iph = (struct iphdr *)(p + sizeof(struct ethhdr)); 
  7.  
  8.  q = (unsigned char *)&(iph->saddr); 
  9.  printf("src ip:%d.%d.%d.%d\n",IP_ARG(q)); 
  10.  
  11.  q = (unsigned char *)&(iph->daddr); 
  12.  printf("dest ip:%d.%d.%d.%d\n",IP_ARG(q)); 
一文教你如何用C代码解析一段网络数据包?「含代码」

Iiph

最终解析结果如下:

一文教你如何用C代码解析一段网络数据包?「含代码」

可以看到我们正确解析出了IP地址, 结果与抓包工具分析出的数据保持了一致。

其中protocol字段表示了ip协议后面的额协议类型,常见的值如下:

一文教你如何用C代码解析一段网络数据包?「含代码」

五、解析tcp头

查找tcp头思路很,

就是从pkt头开始偏移过以太头长度(14字节)、和IP头长度(20字节)就可以找到tcp头,

  1. switch(iph->protocol) 
  2.  { 
  3.   case 0x1: 
  4.    //icmp 
  5.    break; 
  6.   case 0x6: 
  7.    //tcp     
  8.    tcph = (struct tcphdr *)(p + sizeof(struct ethhdr) + sizeof(struct iphdr)); 
  9.    printf("source:%d dest:%d \n",ntohs(tcph->source),ntohs(tcph->dest);  
  10.  
  11.    break; 
  12.   case 0x11: 
  13.    //udp 
  14.     
  15.    break; 
  16.  } 

结构体与内存对应关系

一文教你如何用C代码解析一段网络数据包?「含代码」

打印结果如下:

一文教你如何用C代码解析一段网络数据包?「含代码」

六、学会用不同格式打印这块内存

在实际项目中,可能我们解析的并不是标准的TCP/IP协议数据包,

可能是我们自己的定义的协议数据包,

只要掌握了上述方法,

所有的协议分析都能够手到擒来!

有时候我们还需要打印对方发送过来的数据帧内容,

往往我们会以16进制形式将所有数据打印出来,

这样是最有利于我们分析数据内容的。

1. 按字节打印

代码如下:

  1. for(i=0;i<400;i++) 
  2.  printf("%02x ",pkt[i]); 
  3.  if(i%20 == 19) 
  4.  { 
  5.   printf("\n"); 
  6.  } 
一文教你如何用C代码解析一段网络数据包?「含代码」

2. 按short类型分析一段内存

我们接收数据时,虽然使用一个unsigned char型数组,

但是有时候对方发送过来的数据可能是2个字节的数组,

那我们只需要用short类型的指针,指向内存的头,

然后就可以通过该指针访问到对方发送的数据,

这个时候一定要注意字节序问题,

不同场景可能不一样,所以一定要具体问题具体分析,

本例因为是网络字节序数据转换成主机字节序,

所以需要转换字节序。

  1. //转变short型字节序 
  2. void indian_reverse(unsigned short arr[],int num) 
  3.  int i; 
  4.  unsigned short temp
  5.  
  6.  for(i=0;i<num;i++) 
  7.  { 
  8.   temp = 0; 
  9.  
  10.   temp = (arr[i]&0xff00)>>8; 
  11.   temp |= (arr[i]&0xff)<<8; 
  12.   arr[i] = temp
  13.  } 
  14. main() 
  15.  unsigned short spkt[200]; 
  16.   
  17.  ……………… 
  18.  memcpy(spkt,pkt,sizeof(pkt)); 
  19.  
  20.  indian_reverse(spkt,ARRAY_SIZE(spkt)); 
  21.   
  22.  for(i=0;i<200;i++) 
  23.  { 
  24.   printf("%04x ",spkt[i]); 
  25.   if(i%10 == 9) 
  26.   { 
  27.    printf("\n"); 
  28.   } 
  29.  } 
  30.  ……………… 

结果如下:

一文教你如何用C代码解析一段网络数据包?「含代码」

转载本文请联系一口Linux公众号。