一次性付费进群,长期免费索取资料。
回复公众号: 微信群 可查看进群流程。
通过一个实例来演示一下TCP/IP协议数据包的解析。打开WireShark,选择激活的网卡进行抓包,在过滤筛选框中输入“oicq”来筛选出QQ软件通信数据,然后来查看QQ的数据包,从而找到QQ号码,如图1所示。
图1 WireShark筛选数据
在图1中,在WireShark的过滤栏中输入“oicq”后,在列表中只显示关于“OICQ协议”的相关信息,“OICQ协议”是WireShark中显示的,注意观察“Protocol”列。选中某一列数据,然后查看WireShark解析的数据。首先查看Frame信息,如图2所示。
图2 打开的网络接口卡标识
在图2中显示了Frame信息中一条比较关键的信息,即“Interface id”,它是在启动WireShark时选择打开的网卡设备的表示。在使用Winpcap开发包开发数据分析工具时,首先都会打开设备的,因此我们这里需要记住这个“Interface id”的值。看数据链路层的数据,如图3所示。
图3 数据链路层信息
图3是数据链路层的数据信息,它给出了目的MAC地址和源MAC地址,还给出了一个Type,这个Type表示了上层服务所使用的协议,比如该值为0x0800时表示上层数据是IP,值为0x0806时表示上层协议是ARP协议。在图3中可以看出type的值是0x0800,表示上层协议是IP协议。在WireShark中查看IP协议,如图4所示。
图4 网络层信息
图4是IP协议的数据,其中Version表示IP协议的版本号,Source是源地址、Destination表示目的地址,其中我们关心的是Protocol,这里的Protocol类似数据链路层的Type,它也表示了上层的所使用的协议,比如该值为1时表示上层协议是ICMP协议,值为6时表示上层协议是TCP协议,值为17时表示上层协议是UDP。在图4中Protocol的值为17,表示上层协议是UDP协议。看完IP协议后,来查看UDP协议,如图5所示。
展开全文
图5 传输层信息
图5是传输层的UDP协议,UDP协议非常简单,在WireShark中值显示了长度(Length)、源端口号(Source Port)、目的端口号(Destination Port)和校验和(Checksum)四个值。对于前面的数据链路层,对我们来说比较重要的是type;对于网络层,对我们来说比较重要的是Protocol,而在传输层这部分对我们重要的就是Port了。在进行抓包的时候,首先得到的是数据链路层的数据,然后通过数据链路层来判断type是否为0x0800,从而判断其上层是否为IP协议,如果是IP协议,那么就要关心它的Protocol是否为17,即传输层协议是否使用了UDP协议,如果UDP数据包中的目的端口是8000,那么就与可能该通信是QQ的通信,然后通过进一步来判断是否是“OICQ协议”来得到QQ号,在WireShark中查看,如图6所示。
图6 应用层信息
图6是WireShark对QQ通信数据包的解析,其中首先Flag表示该消息是QQ的数据包,然后Version是版本号,接着Command是消息的类型,消息的类型可以有多种,比如“Receive message(接收信息)”、“Heart Message(心跳)”、“Download group friend”等。在Command之后的Sequence是序号,接着就是QQ的号码了。QQ号码在整个数据包的第7个字节的位置处(从0开始)开始的4个字节长度。
有了上面的分析,就可以通过WinpCap完成一个通过抓包获取本地QQ号码的程序了。WinpCap是Windows packet capture的缩写,它是Windows操作系统下用于捕获网络数据包并进行分析的开源库。许多优秀的网络工具都是依赖它开发的,比如WireShark、Cain等。对于使用它也非常容易,只要下载其SDK开发包将其引入项目即可。
获取本机QQ号码的代码如下:
#include <pcap.h>#include <winsock2.h>#pragma comment (lib, "wpcap")#pragma comment (lib, "Packet")#pragma comment (lib, "ws2_32")/*4 bytes IP address */typedefstruct ip_address{u_char byte1;u_char byte2;u_char byte3;u_char byte4;}ip_address;/*IPv4 header */typedefstruct ip_header{u_char ver_ihl;u_char tos;u_short tlen;u_short identification;u_short flags_fo;u_char ttl;u_char proto;u_short crc;ip_address saddr;ip_address daddr;u_int op_pad;}ip_header;/*UDP header*/typedefstruct udp_header{u_short sport;u_short dport;u_short len;u_short crc;}udp_header;/*QQ header */#pragma pack(push,1)typedefstruct qq_header{u_char flag;u_short version;u_short command;u_short seq;u_int qq_number;}qq_header;#pragma pack(pop,1)#define UDP_SIGN 17 // UDP 协议标识#define QQ_SER_PORT 8000 // QQ 使用的端口号#define QQ_SIGN '\x02' // QQ 协议标识/*prototype of the packet handler */voidpacket_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);int_tmain(int argc, _TCHAR* argv[]){pcap_if_t *alldevs = NULL;pcap_if_t *d = NULL;int inum = 0;int i = 0;pcap_t *adhandle = NULL;char errbuf[PCAP_ERRBUF_SIZE] = { 0 };u_int netmask;char packet_filter[] = "ip and udp";struct bpf_program fcode;/*查找机器上所有可以使用的网络接口 */if( pcap_findalldevs(&alldevs, errbuf) = = -1 ){fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);exit(1);}/* 输出网络接口列表 */for( d = alldevs; d; d = d->next){printf("%d. %s", ++i, d->name);if (d->deion){printf(" (%s)\n", d->deion);}else{printf(" (No deion available)\n");}}if( i = = 0 ){printf("\nNo interfaces found! Make sure WinPcap is installed.\n");return -1;}// 选择要监听的网络接口printf("Enter the interface number (1-%d): ",i);scanf("%d", &inum);/* Check if the user specified a valid adapter */if( inum < 1 || inum > i ){printf("\nAdapter number out of range.\n");/* Free the device list */pcap_freealldevs(alldevs);return -1;}/* 获得选择的网络接口 */for( d = alldevs, i = 0; i < inum - 1; d = d->next, i++ );/* 打开网络适配器 */adhandle = pcap_open_live(d->name, // 设备名称65536, // 65535 保证能捕获到不同数据链路层上的每个数据包的全部内容1, // 混杂模式1000, // 读取超时时间errbuf);// 错误缓冲区if ( adhandle = = NULL ){fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n");/* 释放设备列表 */pcap_freealldevs(alldevs);return -1;}/* 检查数据链路层,为了简单,我们只考虑以太网 */if(pcap_datalink(adhandle) != DLT_EN10MB){fprintf(stderr,"\nThis program works only on Ethernet networks.\n");/* 释放设备列表 */pcap_freealldevs(alldevs);return -1;}if( d->addresses != NULL ){/* 获得接口第一个地址的掩码 */netmask= ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;}else{/* 如果接口没有地址,那么我们假设一个 C 类的掩码 */netmask= 0xff0000;}// 编译过滤规则if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) <0 ){fprintf(stderr,"\nUnable to compile the packet filter. Check the syntax.\n");/* 释放设备列表 */pcap_freealldevs(alldevs);return -1;}// 设置过滤规则if (pcap_setfilter(adhandle, &fcode)<0){fprintf(stderr,"\nError setting the filter.\n");/* 释放设备列表 */pcap_freealldevs(alldevs);return -1;}printf("\nlistening on %s...\n", d->deion);/* 释放设备列表 */pcap_freealldevs(alldevs);/* 开始捕获 */printf("开始捕获数据:\r\n");// packet_handler 是回调函数pcap_loop(adhandle, 0, packet_handler, NULL);return 0;}/*每次捕获到数据包时,libpcap 都会自动调用这个回调函数 */voidpacket_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data){ip_header *ih = NULL;udp_header *uh = NULL;u_int ip_len = 0;u_short sport = 0, dport = 0;qq_header *qh = NULL;PBYTE pByte = NULL;/* 获得 IP 数据包头部的位置 */ih = (ip_header *) (pkt_data + 14); // 14 是以太网头部长度/* 获得 UDP 首部的位置 */ip_len = (ih->ver_ihl & 0xf) * 4;// 判断是否为 UDP 协议if ( ih->proto = = UDP_SIGN ){uh = (udp_header *) ((u_char*)ih + ip_len);/* 将网络字节序列转换成主机字节序列 */sport = ntohs( uh->sport );dport = ntohs( uh->dport );// 判断源端口或目的端口是否为 8000if ( sport = = QQ_SER_PORT || dport == QQ_SER_PORT ){pByte = (PBYTE)ih + ip_len + sizeof(udp_header);if ( *pByte = = QQ_SIGN ){qh = (qq_header *)pByte;int n = ntohl(qh->qq_number);printf("QQ = %u, %d.%d.%d.%d:%d - > %d.%d.%d.%d:%d\r\n", n,ih->saddr.byte1,ih->saddr.byte2,ih->saddr.byte3,ih->saddr.byte4,sport,ih->daddr.byte1,ih->daddr.byte2,ih->daddr.byte3,ih->daddr.byte4,dport);}}}}
代码一共分为3部分,第一部分是定义了相关的结构体和一些常量,比如定义了关于IP协议的结构体、UDP协议的结构体和QQ的结构体,还定义了关于UDP标识和QQ标识的常量。第二部分是main函数的部分,这部分几乎是通用的,这部分可以从WinpCap手册中获得,主要是获取本机中的所有网络适配器,然后打开并设置编译过滤字符串,最后开启嗅探。第三部分是packet_handler函数,该函数是对数据包的解析。如果是开发简单的协议分析的工具基本就是这3部分,比如解析以太网、IP、TCP、ARP等,都是先定义其结构体,在主函数中打开网络接口、设置过滤规则、设置解析的回调函数,在回调函数中按照每层协议的具体格式进行解析。
示例中的代码,使用回调的方式来对数据包进行解析,也可以使用循环的方式解析,但是当解析的数据量过多的时候,用循环的方式编写显得就不那么规整了。代码在VS2012下进行编译连接,运行如图7所示。
图7 WinpCap获取结果
参考文献:C++ 黑客编程揭秘与防范(第3版)