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

一口Linux

    本文的目的是通过随机截取的一段网络数据包,然后根据协议类型来解析出这段内存。
    学习本文需要掌握的基础知识:
    网络协议C语言Linux操作抓包工具的使用
    其中抓包工具的安装和使用见下文:
    《一文包你学会网络数据抓包》
    视频教学链接如下:
    《教你如何抓取网络中的数据包!黑客必备技能》
    一、截取一个网络数据包
    通过抓包工具,随机抓取一个tcp数据包
    
    科莱抓包工具解析出的数据包信息如下:
    
    数据包的内存信息:
    
    数据信息可以直接拷贝出来:
    
    二、用到的结构体
    下面,一口君就手把手教大家如何解析出这些数据包的信息。
    我们可以从Linux内核中找到协议头的定义
    以太头:driversstagingtl8188euincludeif_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头
    includeuapilinux cp.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
    因为协议头长度都是按照标准协议来定义的,
    所以以太长度是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 ", MAC_ARG(ethh->h_dest));
     printf("h_source:%02x:%02x:%02x:%02x:%02x:%02x ", MAC_ARG(ethh->h_source));
     printf("h_proto:%04x",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",IP_ARG(q));
      q = (unsigned char *)&(iph->daddr);
      printf("dest ip:%d.%d.%d.%d",IP_ARG(q));
     }
    
    Iiph
    最终解析结果如下:
    
    可以看到我们正确解析出了IP地址,结果与抓包工具分析出的数据保持了一致。
    其中protocol字段表示了ip协议后面的额协议类型,常见的值如下:
    数值描述0保留字段,用于IPv6(跳跃点到跳跃点选项)1Internet控制消息 (ICMP)2Internet组管理 (IGMP)3网关到网关 (GGP)41P中的IP(封装)6传输控制 (TCP)7CBT8外部网关协议 (EGP)9任何私有内部网关(Cisco在它的IGRP实现中使用) (IGP)10BBNRCC监视11网络语音协议12PUP13ARGUS14EMCON15网络诊断工具16混乱(Chaos)17用户数据报文 (UDP)411Pv6581Pv6的ICMP591Pv6的无下一个报头60IPv6的信宿选项89OSPF IGP92多播传输协议94IP内部的IP封装协议95可移动网络互连控制协议96旗语通讯安全协议97IP中的以太封装98封装报头100GMTP101Ipsilon流量管理协议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 ",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("");
      }
     }
    
    2. 按short类型分析一段内存
    我们接收数据时,虽然使用一个unsigned char型数组,
    但是有时候对方发送过来的数据可能是2个字节的数组,
    那我们只需要用short类型的指针,指向内存的头,
    然后就可以通过该指针访问到对方发送的数据,
    这个时候一定要注意字节序问题,
    不同场景可能不一样,所以一定要具体问题具体分析,
    本例因为是网络字节序数据转换成主机字节序,
    所以需要转换字节序。
    //转变short型字节序
    void indian_reverse(unsigned short arr[],int num)
    {
     int i;
     unsigned short temp;
     for(i=0;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("");
      }
     }
     ………………
    }
    结果如下:
    
    好了,这个例子掌握了,那么网络就算入门了,快操练起来吧!