评论

收藏

[C++] 一文教你如何用C代码解析一段网络数据包?【含代码】

编程语言 编程语言 发布于:2021-08-10 13:03 | 阅读数:436 | 评论:0

本文的目的是通过随机截取的一段网络数据包,然后根据协议类型来解析出这段内存。
学习本文需要掌握的基础知识:

  • 网络协议
  • C语言
  • Linux操作
  • 抓包工具的使用
其中抓包工具的安装和使用见下文:
《一文包你学会网络数据抓包》
视频教学链接如下:
《教你如何抓取网络中的数据包!***必备技能》

一、截取一个网络数据包
通过抓包工具,随机抓取一个tcp数据包
DSC0000.jpeg
科莱抓包工具解析出的数据包信息如下:
DSC0001.png
数据包的内存信息:
DSC0002.png
数据信息可以直接拷贝出来:
DSC0003.png


二、用到的结构体
下面,一口君就手把手教大家如何解析出这些数据包的信息。
我们可以从Linux内核中找到协议头的定义

  • 以太头:
drivers\staging\rtl8188eu\include\if_ether.h
struct ethhdr {
unsigned charh_dest[ETH_ALEN];/* destination eth addr*/
unsigned charh_source[ETH_ALEN];/* source ether addr*/
unsigned shorth_proto;/* packet type ID field*/
};

  • IP头
include\uapi\linux\ip.h
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)  //小端模式
__u8ihl:4,
version:4;
#elif defined(__BIG_ENDIAN_BITFIELD)  //大端模式
__u8version:4,
ihl:4;
#endif
__u8tos;
__u16tot_len;
__u16id;
__u16frag_off;
__u8ttl;
__u8protocol;
__u16check;
__u32saddr;
__u32daddr;
/*The options start here. */
};
tcp头
include\uapi\linux\tcp.h
struct tcphdr {
__be16source;
__be16dest;
__be32seq;
__be32ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u16res1:4,
doff:4,
fin:1,
syn:1,
rst:1,
psh:1,
ack:1,
urg:1,
ece:1,
cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16doff: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
__be16window;
__sum16check;
__be16urg_ptr;
};
因为协议头长度都是按照标准协议来定义的,
所以以太长度是14,
IP头长度是20,
tcp头长度是20,
各个协议头对应的内存空间如下:
DSC0004.png


三、解析以太头
#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的成员对应关系如下:
DSC0005.png
最终打印结果如下:
DSC0006.png


四、解析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));
}
DSC0007.png

最终解析结果如下:
DSC0008.png 可以看到我们正确解析出了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 \n",ntohs(tcph->source),ntohs(tcph->dest);
break;
case 0x11:
//udp
break;
}
结构体与内存对应关系
DSC0009.png
打印结果如下:
DSC00010.png


六、学会用不同格式打印这块内存
在实际项目中,可能我们解析的并不是标准的TCP/IP协议数据包,
可能是我们自己的定义的协议数据包,
只要掌握了上述方法,
所有的协议分析都能够手到擒来!
有时候我们还需要打印对方发送过来的数据帧内容,
往往我们会以16进制形式将所有数据打印出来,
这样是最有利于我们分析数据内容的。
1. 按字节打印
代码如下:
for(i=0;i<400;i++)
{
printf("%02x ",pkt[i]);
if(i%20 == 19)
{
printf("\n");
}
}
DSC00011.png


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");
}
}
………………
}
结果如下:
DSC00012.png
完整代码请关注公众号:一口Linux,回复:数据包解析



关注下面的标签,发现更多相似文章