网络抓包数据详解

48 阅读4分钟

网络抓包数据详解

  • 最近在使用qt重写一个抓包软件,原因是用c#感觉获取速度有延迟(用的第三方库抓包,然后拼包)
    • C#使用sharppcap和xx第三方库,其实是对npcap库的二次封装
    • 想用c++直接调用npcap库,然后自己从网络包中提取出自己想要的tcp数据包,最后按照自定义协议拼接处业务数据包
  • 那么如何从网络数据包中提取出tcp数据包呢?
    • 按照规则解析网络数据,这里我们要分成两种情况
      1. 本地回环的数据
      2. 真实的网络数据

拆解网络数据包

以太网层

  • 一般14个字节
dest macsrc macflags
6个字节6个字节2个字节
我到哪里去我从哪里来我是干什么的
  • 用48位也就是6个字节来表示一个mac地址
  • 最后的两个字节是这个数据包的标志,我们这里只关注一个值:0800,表示 ipv4

ip层

  • 一般ip包头是20个字节,以4个字节为一组,分成5组。

  • image.png

  • 第一组:

    • 第一个字节的高4位:版本号,因为是ipv4,所以这里一般是4
    • 第一个字节的低4位:长度,一般这里是5,也就是 5 * 4 = 20个字节
    • 第二个字节:8位服务类型,tos,这个我也不是很清楚,有需要再深入了解
    • 第三四个字节:ip包的总长度:ip头+tcp头+tcp数据 = 总长度
  • 第二组:

    • 第一二个字节:标志,不清楚干嘛的
    • 第三四个字节:
      • 高三位:标志
      • 低13位:偏移量,拆包情况下,这个包在原来总包中的顺序
  • 第三组:

    • 第一个字节:生存时间 ttl,一般是32或者64,表示最多传播 n 个路由器
    • 第二个字节:8位协议,表示这个ip数据包的类型,tcp or udp,其中如果是06的话就是tcp
    • 第三四个字节:校验和
  • 第四组:

    • src ip
  • 第五组:

    • dest ip

tcp层

  • 一般tcp包头是20个字节,我们还是将它分成5组
  • image.png
  • 第一组:
    • 第一二个字节: src port
    • 第三四个字节:dest port
  • 第二组:
    • 序号
  • 第三组:
    • 确认序号
  • 第四组:
    • 第一个字节:
      • 高4位:长度,一般为5(5 * 4 = 20)
      • 低四位的前三个位:保留
      • 低四位的最后一个位: Accurate ECN
    • 第二个字节:
      • Congestion Window ReducedECN-EchoUrgentAcknowledgmentPUshRestSynFin
    • 第三四个字节:窗口
  • 第五组
    • 第一二个字节:校验和
    • 第三四个字节:紧急指针

提取Tcp数据(payload)

  • 了解了以上网络分层后,可以轻松的将tcp数据提取出来,最主要的是找到ip的len和tcp的len,随后就知道payload的偏移量啦,这里我给出一个demo
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data)
{
    uint len = header->len;
    if (len < 55) return; // tcp packet must be larger than 54

    int offset = 0;
    // 以太网
    if (pkt_data[12] == 8 && pkt_data[13] == 0)
    {
        // ipv4数据包
        offset = 14;
    }
    else {
        return;
    }

    // ip
    u_char ip_len = (pkt_data[offset] & 15) * 4;
    offset += ip_len;

    // tcp
    u_char tcp_len = (((pkt_data[offset + 12]) >> 4) & 15) * 4;
    offset += tcp_len;

    int payload_len = len - offset;

    qDebug() << QString("data len: %1").arg(payload_len);
}
  • 但是经过测试时,发现了两个问题

    1. tcp包有数据填充,导致我写的代码误判长度
    2. 本地回环没有以太网层
  • 先说第一个问题

    • image.png

    • 修改后的代码为

      int ip_pack_total_len = pkt_data[offset+2] * 256 + pkt_data[offset+3];
      u_char ip_len = (pkt_data[offset] & 15) * 4;
      offset += ip_len;
      
      // tcp
      u_char tcp_len = (((pkt_data[offset + 12]) >> 4) & 15) * 4;
      offset += tcp_len;
      
      int payload_len = ip_pack_total_len - ip_len - tcp_len;
      

本地回环的特殊性

  • 在测试过程中发现,本地回环的数据包,没有以太网层,而是以 02 00 00 00 开头,以后紧接着 ip数据包,如下图所示

    • image.png
  • 那么只需要在判断非以太网分支那里增加一个分支,具体代码如下

    if (pkt_data[0] == 2 && pkt_data[1] == 0 && pkt_data[2] == 0 && pkt_data[3] == 0)
    {
        // loop back
        offset = 4;
    }
    else
        return;
    

附录

完整代码

void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data)
{
    Q_UNUSED(param)
    Q_UNUSED(pkt_data)

    uint len = header->len;
    if (len < 55) return; // tcp packet must be larger than 54

    int offset = 0;
    // 以太网
    if (pkt_data[12] == 8 && pkt_data[13] == 0)
    {
        // ipv4数据包
        offset = 14;
    }
    else {
        if (pkt_data[0] == 2 && pkt_data[1] == 0 && pkt_data[2] == 0 && pkt_data[3] == 0)
        {
            // loop back
            offset = 4;
        }
        else
            return;
    }

    // ip
    int ip_pack_total_len = pkt_data[offset+2] * 256 + pkt_data[offset+3];
    u_char ip_len = (pkt_data[offset] & 15) * 4;
    offset += ip_len;

    // tcp
    u_char tcp_len = (((pkt_data[offset + 12]) >> 4) & 15) * 4;
    offset += tcp_len;

    int payload_len = ip_pack_total_len - ip_len - tcp_len;

    qDebug() << QString("data len: %1").arg(payload_len);
}