1、 IP包的截获和解析实验报告 Q1201012 刘倩君 一.实验目的: 设计一个能够截获并解析IP数据包的程序,并根据这个程序,说明IP数据包的结构及IP协议的相关问题,从而IP层的工作原理有更好的理解和认识。 二.实验要求 : 本实验的目标是捕获网络中数据包,解析数据包的内容,将结果显示在标准输出上,并同时写入日志文件. 程序的具体要求如下: 1、能够捕获以太网数据帧,并且能够解析:数据包类型,数据包长度,源、目的MAC, 2、如果是IP数据包,则进行进一步解析。输出版本,头长度,服务类型,数据包总长度,数据包标识,分段标志,分段偏移值,生存
2、时间,上层协议类型,头校验和,源IP地址和目的IP地址等内容. 3、在指定的日志文件中写入网卡、时间、数据包解析结果等内容。当程序接收到键盘输入Ctrl+C时退出. 三.主要数据结构 各种包头部、IP地址等的结构体定义在Header.h中。 1、以太网帧格式 因此,以太网帧头部的数据结构定义为: typedef struct tagDLCHeader /*以太网数据帧头部结构*/ { u_char DesMAC[6]; /* destination HW addrress */ u_char SrcM
3、AC[6]; /* source HW addresss */ u_char Ethertype; /* ethernet type */ } DLCHEADER; 2、IP数据报格式 0 4 8 16 19 24 31 版本 报头标长 服务类型 总长度 标识 标志 片偏移 生存时间 协议 头校验和 源IP地址 目的IP地址 选项 填充域 数据部分 IP数
4、据包的第一个字段是版本字段,其度是4位,表示所使用的IP协议的版本。本程序主要针对版本是IPV4的数据包的解析。 报头标长字段为4位,它定义了以4B为一个单位的IP包的报文长度.报头中除了选项字段和填充域字段外,其他各字段是定长的。因此,IP数据包的头长度在20—40B之间,是可变的。 服务类型字段共8位,用于指示路由器如何处理该数据包。 总长度字段为2B,它定义了以字节为单位的数据包的总长度,IP数据包的最大长度为65535B。 标识字段的长度为16位,用于识别IP数据包的编号,每批数据都要有一个标识值,用于让目的主机判断新来的数据属于哪个分组。 标志字段共3位,最高位是0.禁止分
5、片标志DF(do not fragment)字段的值若为1,表示不能对数据包分片;若DF值为0,则表明可以分片。分片标志MF( more fragment)的值为1,表示接收到的不是最后一个分片;若MF值为0,表示接收到的是最后一个分片。 片偏移字段共13位,说明分片在整个数据包中的相对位置,片偏移值是以8B为单位来记数的,因此选择的分片长度应该是8B的整数倍。 生存时间(TTL)字段为8位,用来设置数据包在互联网络的传输过程的寿命,通常是用一个数据包可以经过的最多的路由器跳步数来限定的。 协议字段为8位,表示使用此IP数据包的高层协议类型,常用的协议号如下表所示. 序号 协议名称
6、 序号 协议名称 1 2 4 6 8 ICMP IGMP IP inIP TCP EGP 17 41 46 89 UDP IPV6 RSVP OSPFA 头校验和字段为16位,用于存放检查报头错误的校验码,检验的范围是整个IP包的报头。校验和为0,说明数据报报头没有出错。 地址字段包括源地址和目的地址。源地址和目的地址的长度都是32位,分别表示发送数据包的源主机和目的主机的IP地址。 选项字段的长度范围为0~40B,主要用于控制和测试。在使用选项字段的过程中,有可能出现报头部分的长度不是32位的整数倍的情况。如果出现这种情况,就需
7、要通过填充位来凑齐。 因此,IP数据报的头部定义如下: typedef struct ip_header{ u_char ver_ihl; // 4 bit 的版本信息 + 4 bits 的头长 u_char tos; // TOS 类型 u_short tlen; // 总长度 u_short identification; // Identification u_short flags_fo; // Flags (3 bits) + Fragment offset (13 bits) u_char ttl; // 生存期 u_char
8、 proto; // 后面的协议信息 u_short crc; // 校验和 ip_address saddr; // 源 IP ip_address daddr; // 目的 IP ip_address selection; // 选项 }ip_header; 3、各类报头TCP/UDP/ICMP/IP结构 当我们使用Raw Socket的时候,可以完全自定义IP包,一切形式的包都可以"制造"出来。因此,本文事先必须对TCP/IP所涉及IP包结构进行必要的交待。 目前,IPv4的报头结构为常用的ICMP报文包括ECHO-REQU
9、EST(响应请求消息)、ECHO-REPLY(响应应答消息)、Destination Unreachable(目标不可到达消息)、Time Exceeded(超时消息)、Parameter Problems(参数错误消息)、Source Quenchs(源抑制消息)、Redirects(重定向消息)、Timestamps(时间戳消息)、Timestamp Replies(时间戳响应消息)、Address Masks(地址掩码请求消息)、Address Mask Replies(地址掩码响应消息)等,是Internet上十分重要的消息。后面章节中所涉及到的ping命令、ICMP拒绝服务攻击、路由
10、欺骗都与 ICMP协议息息相关。 TCP报头结构为: 对应数据结构: typedef struct psd_hdr //定义TCP伪报头 { unsigned long saddr; //源地址 unsigned long daddr; //目的地址 char mbz; char ptcl; //协议类型 unsigned short tcpl; //TCP长度 }PSD_HEADER; typedef struct _tcphdr //定义TCP报头 { unsigned short th_sport; //16位源端口
11、 unsigned short th_dport; //16位目的端口 unsigned int th_seq; //32位序列号 unsigned int th_ack; //32位确认号 unsigned char th_lenres; //4位首部长度/4位保留字 unsigned char th_flag; //6位标志位 unsigned short th_win; //16位窗口大小 unsigned short th_sum; //16位校验和 unsigned short th_urp; //16位紧急数据偏移量 } TCP_
12、HEADER; UDP报头为: 对应的数据结构为: typedef struct _udphdr //定义UDP报头 { unsigned short uh_sport;//16位源端口 unsigned short uh_dport;//16位目的端口 unsigned short uh_len;//16位长度 unsigned short uh_sum;//16位校验和 } UDP_HEADER; ICMP协议是网络层中一个非常重要的协议,其全称为Internet Control Message Protocol(因特网控制报文协议),ICMP
13、协议弥补了IP的缺限,它使用IP协议进行信息传递,向数据包中的源端节点提供发生在网络层的错误信息 反馈。ICMP报头为: 常用的回送与或回送响应ICMP消息对应数据结构为: typedef struct _icmphdr //定义ICMP报头(回送与或回送响应) { unsigned char i_type;//8位类型 unsigned char i_code; //8位代码 unsigned short i_cksum; //16位校验和 unsigned short i_id; //识别号(一般用进程号作为识别号) unsigned sh
14、ort i_seq; //报文序列号 unsigned int timestamp;//时间戳 } ICMP_HEADER; 四.程序设计分析 为了获取网络中的IP数据包,必须对网卡进行编程,在这里我们觊觎winpcap提供的库函数进行编程。但是,在通常情况下,网络通信只能响应与自己硬件地址相匹配的数据包或是以广播形式出发的数据包。对于其他形式的数据包,如已到达网络接口但却不是发送到此地址的数据包,网络接口在验证投递地址并非自身地址之后将不引起响应,也就是说应用程序无法收取与自己无关的数据包。我们要想获取流经网络设备的所有数据包,就需要将网卡设置为混杂模式。
15、 1、获取所有存在的网络设备的链表。 Pcap_if_t *alldevs; //网络设备结构链表 Char errbuf[PCAP_ERRBUF_SIZE]; //错误信息 /*所有网络设备的信息以链表形式存在alldevs中*/ pcap_findalldevs(&alldevs,errbuf); 2、从链表中选择物理Ethernet卡后,用混杂模式打开。 if((adhandle= pcap_open_live(d->name, // 设备名称 65536, // portion of the packet to capture. 1,
16、 // 采用混杂模式 1000, // 读超时为 1 秒 errbuf // error buffer ) ) == NULL) { fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n"); pcap_freealldevs(alldevs); return -1; } 在这个函数中,第一个参数为要打开的设备名称,这里是Ethernet卡,可以从设备链表alldevs中选出。第二个参数应为捕获的数据包长度,填入65535以保证在链
17、路层的整个数据包都被捕获。第三个参数为打开模式,填入1表明用混杂模式打开网卡。最后两个参数本别为读入超时的时间和保存错误信息。最后返回一个网卡描述符adhandle. 3、捕获数据包。 res = pcap_next_ex( adhandle, &header, &pkt_data)) adhandle是由pcap_open_live()返回的所打开网卡的指,header是pcap_pkthdr型的结构体,存储时间,包的长度, pkt_data存储数据包的内容,为一个char型数组。返回值res 等于1时成功;等于0时超时;等于-1时说明发生错误。对于捕获的数据包,去掉数据链路
18、层的14B的头部后才是真正的IP包信息。 5.参考程序流程图 五.结果分析 1、选择网卡 2.捕获的IP数据包 ******************************************************************** 数据包5 抓包信息: 时间:10:03:32 编号:751530 数据包长度:67 捕获的数据包长度:67 DLC 帧分析: MAC地址:00000052506a->000000525064 以太网协议类型: 0x0800(IP包) IP 报头分析: IP 4 20 0
19、 53 23213 不分 0 1 UDP 2b65 192.168.146.1->224.0.0.252 UDP 报头分析: 端口:eb->b821 报文长:46409 校验和:96 根据IP 报头结构: IP协议的版本号|包头长度|服务类型|总长|ID|Flag|片偏移|生存时间|协议类型|包头校验和|源IP|目的IP| 可以知道这个IP数据报是从192.168.146.1(本机)发给224.0.0.252(默认网关)的,包头长度为20字节,总长度为53字节,不分片,片偏移为0,生存时间为1,协议类型为UDP,包头校验和为2b65。 还可以进一步对封装的UDP数据报进行
20、分析。 3.捕获的其他数据包 ***************************************************************************** 数据包2 抓包信息: 时间:10:03:32 编号:750418 数据包长度:87 捕获的数据包长度:87 DLC 帧分析: MAC地址:000000524f3a->000000524f34 以太网上协议类型:34304 未知包丢弃 ************************************************************************
21、
根据协议类型为34304,可知这是一个未知,丢弃。
六.主程序源代码
#include
22、winsock2.h> #pragma comment(lib,"ws2_32.lib")//链接至库 #include "pcap.h" #pragma comment(lib,"wpcap.lib")//链接至库 using namespace std; int SwitchAgreement(u_char proto,const u_char *pkt_data);//对IP数据包数据部分进行解析 DWORD WINAPI Fun1Proc( LPVOID lpParameter); int k1=1;//抓包个数 int k2=1;//一次显示的数据包个
23、数 int k3;//日志变量 time_t t;//时间变量,长整型 char* s; string p; char buffer[100]; int main() { pcap_if_t *alldevs; // 网卡描述符列表,包含各网卡信息、型号、名字、地址等的结构体 pcap_if_t *d; pcap_t *adhandle; char errbuf[PCAP_ERRBUF_SIZE];//错误信息 struct pcap_pkthdr *header; ip_header *ih; const u_char *pkt_data;
24、 int inum; int i=0; int num=0; int res; struct tm *ltime; char timestr[16]; char *logname=new char[20]; cout<<"请输入日志名称(.log):\n"; cin>>logname; if(logname==NULL) logname="默认日志.log"; ofstream ofs(logname,ios::app);//以追加方式打开日志文件 time(&t);//获取系统时间 p = ""; p +=
25、 ctime(&t);//把系统时间转换为字符串 p += "用户运行程序\n"; s=&p[0]; ofs.write(s,strlen(s));//系统时间写入日志文件 ofs.flush(); // 获得网卡的列表 if (pcap_findalldevs(&alldevs, errbuf) == -1) { fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf); exit(1); } ofs.write("获得网卡的列表\n",strlen("获得网卡的列表\n"));
26、 ofs.write("打印网卡信息\n",strlen("打印网卡信息\n")); ofs.flush(); // 打印网卡信息 cout<<"本机虚拟网卡列表:\n"; p =""; for(d=alldevs; d; d=d->next) { //网卡列表写入日志文件 i++; _itoa( i, buffer,10);//将i转换为10进制的字符串 p += buffer; p += "."; p += d->name; p += "\n"; if (d->description) { p +=
27、 "\t("; p += d->description; p += ")\n"; } else p += "(No description available)\n"; s=&p[0]; ofs.write(s,strlen(s)); ofs.flush(); cout<
28、1; } HANDLE hThread1;//线程句柄 if(hThread1) { TerminateThread(hThread1,NULL); } cout<<"输入要监听的网卡号(1-"<>inum; hThread1 = CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);//这个线程用来控制退出 CloseHandle(hThread1); p += "选择网卡号为:"; _itoa( inum, buffer,10); p += buffer;
29、p += "\n";
s=&p[0];
ofs.write(s,strlen(s));
ofs.flush();
p="";
if(inum < 1 || inum > i) //判断号的合法性
{
cout< 30、
}
else
{
p += "检测网卡号有效\n";
s=&p[0];
ofs.write(s,strlen(s));
ofs.flush();
p="";
}
// 找到要选择的网卡结构
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
p += "找到了要打开的网卡地址\n";
s = &p[0];
ofs.write(s,strlen(s));
ofs.flush();
p="";
// 打开选择的网卡
if((adhandle= pcap_open_live 31、d->name, // 设备名称
65536, // portion of the packet to capture.
1, // 采用混杂模式
1000, // 读超时为 1 秒
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n");
//Free the device list
pcap_freealldevs(alldevs);
32、 return -1;
}
if(pcap_datalink(adhandle) != DLT_EN10MB)//只考虑以太网数据
{
fprintf(stderr,"\nThis program works only on Ethernet networks.\n");
pcap_freealldevs(alldevs);
return -1;
}
p += "该网卡设置为了混乱模式\n开始捕获数据包\n";
s = &p[0];
ofs.write(s,strlen(s));
ofs.flush();
p="";
cout<< 33、endl<<"listening on "< 34、ID|Flag|片偏移|生存时间|协议类型|包头校验和|源IP|目的IP|\n";
p += "***********************************************************************************\n\n";
s = &p[0];
ofs.write(s,strlen(s));
ofs.flush();
p="";
DLCHEADER *ik;//物理帧头
while((res = pcap_next_ex( adhandle, &header, &pkt_data)) >= 0 &&k1)//开 35、始捕获数据包,eader是pcap_pkthdr结构,pkt_header是接收数据的缓冲区
{
ltime=localtime(&header->ts.tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
if(res == 0) continue; /* Timeout elapsed */
num++;
p += "********************************************************************************\n 36、数据包";
_itoa(num,buffer,10);
p += buffer;
p +="\n";
p += "抓包信息:\n";
p += " 时间:";
p += timestr;
p += " 编号:";
_itoa(header->ts.tv_usec,buffer,10);
p += buffer;
p += " 数据包长度:";
_itoa(header->len,buffer,10);
p += buffer;
p += " 捕获的数据包长度:";
_itoa(header->caplen,bu 37、ffer,10);
p += buffer;
p += "\n";
s = &p[0];
cout< 38、 p += buffer;
if(ik2!=0x0800)//判断是否为IP数据包
{
p += " 以太网上协议类型:";
if(ik2==0x8864) p += " 0x8864【以太网上的 PPP(PPP 会话阶段)(PPPoE,PPP Over Ethernet 39、001【ethernetII广播包-3Com(Bridge) XNS Sys Mgmt[XEROX]】";
else if(ik2==0x814C) p += " 0x814C【简单网络管理协议(SNMP:Simple Network Management Protocol)】";
else {sprintf(buffer, "%d ", ik2); p += buffer; p += " 未知包丢弃\n";}
s=&p[0];
ofs.write(s,strlen(s));
ofs.flush();
cout< 40、 p="";
continue;
}
else
{
p += " 以太网协议类型: 0x0800(IP包)\n";
s=&p[0];
ofs.write(s,strlen(s));
ofs.flush();
cout< 41、fication);
ih->crc = ntohs(ih->crc);
p +="IP 报头分析:\n";
p += " IP";
_itoa((ih->ver_ihl & 0xf0)/16,buffer,10);
p += buffer;
p += " ";
_itoa((ih->ver_ihl & 0xf)*4,buffer,10);
p += buffer;
p +=" ";
_itoa(ih->tos,buffer,16);
p += buffer;
p +=" ";
_itoa(ih->tlen,buff 42、er,10);
p += buffer;
p += " ";
_itoa(ih->identification,buffer,10);
p += buffer;
p +=" ";
if((ih->flags_fo & 0xE000)==0x6000) p += "分片(NoLast) ";//DF/MF
else if((ih->flags_fo & 0xE000)==0x4000) p += "分片(Last) ";
else p += "不分 ";
p +=" ";
_itoa(ih->flags_fo & 0x1FFF,buff 43、er,10);
p += buffer;
p +=" ";
_itoa(ih->ttl,buffer,10);
p += buffer;
p +=" ";
if(ih->proto==17) p += "UDP ";//协议类型
else if(ih->proto==6)p += "TCP ";
else if(ih->proto==1)p += "ICMP ";
else if(ih->proto==2)p += "IGMP ";
else p += "未知 ";
_itoa(ih->crc,buffer,16);
p 44、 buffer;
p +=" ";
_itoa(ih->saddr.byte1,buffer,10);
p += buffer;
p +=".";
_itoa(ih->saddr.byte2,buffer,10);
p += buffer;
p +=".";
_itoa(ih->saddr.byte3,buffer,10);
p += buffer;
p +=".";
_itoa(ih->saddr.byte4,buffer,10);
p += buffer;
p +="->";
_itoa(ih->dad 45、dr.byte1,buffer,10);
p += buffer;
p +=".";
_itoa(ih->daddr.byte2,buffer,10);
p += buffer;
p +=".";
_itoa(ih->daddr.byte3,buffer,10);
p += buffer;
p +=".";
_itoa(ih->daddr.byte4,buffer,10);
p += buffer;
p +="\n";
s=&p[0];
ofs.write(s,strlen(s));
ofs.flush();
46、
cout< 47、0];
ofs.write(s,strlen(s));
ofs.flush();
p="";
if(res == -1){
cout<<"Error reading the packets: "< 48、roto,const u_char *pkt_data){//对IP数据部分的内容进行解析
TCP_HEADER *th;
UDP_HEADER *uh;
ICMP_HEADER *ich;
IGMP_HEADER *igh;
switch(proto)
{
case 1:
ich= (ICMP_HEADER *)(pkt_data+sizeof(ip_header)+sizeof(DLCHEADER));
p += "ICMP 报头分析:\n ";
sprintf(buffer,"类型:%x 代码:%x 校验和:%x 识别和:%x 报文序列号:% 49、x\n",
ich->i_type,ich->i_code,ich->i_cksum,ich->i_id,ich->i_seq);
p += buffer;
s=&p[0];
break;
case 6:
th= (TCP_HEADER *)(pkt_data+sizeof(ip_header)+sizeof(DLCHEADER));
p += "TCP 报头分析:\n ";
sprintf(buffer,"端口:%x->%x 序列号:%x 确认号:%x TCP偏移量:%d\n Flags:%x 窗口大小:%d 校验和:%x 紧急指针:%d\n" 50、
th->th_sport,th->th_dport,th->th_seq,th->th_ack,(th->th_lenres & 0xE0)/16,
th->th_flag & 0x3F,th->th_win,th->th_sum,th->th_urp);
p += buffer;
s=&p[0];
break;
case 17:
uh= (UDP_HEADER *)(pkt_data+sizeof(ip_header)+sizeof(DLCHEADER));
p += "UDP 报头分析:\n ";
sprintf(buffer,"Ethertype);
sprintf(buffer,"%.12x->%.12x",ik->SrcMAC,ik->DesMAC);
p += "DLC 帧分析:\n";
p += " MAC地址:";
tlen = ntohs(ih->tlen);//网络字节序改为主机字节顺序
ih->identification = ntohs(ih->identiproto,pkt_data);//IP数据部分解析
cout<






