网络嗅探器设计实验
一、实验环境:
(1)Windows 10 系统平台;
(2)在VS环境下进行C++编程。
二、课程目的:
(1)参照有关书籍,掌握有关网络通信基本原理;
(2)学习网络嗅探器的概念及其工作原理;
(3)掌握嗅探器的程序设计。
三、基本原理:
- 网络嗅探器又称为网络监听器,简称为Sniffer子系统,放置于网络节点处,对网络中的数据帧进行捕获的一种被动监听手段,是一种常用的收集有用数据的方法,这些数据可以是用户的账号和密码,可以是一些商用机密数据等等。Sniffer是利用计算机的网络接口截获目的地为其他计算机的数据报文的一种工具。Sniffer的正当用处主要是分析网络的流量,以便找出所关心的网络中潜在的问题。嗅探器可以理解为一个安装在计算机上的窃听设备它可以用来窃听计算机在网络上所产生的众多的信息。
- 计算机直接所传送的数据,事实上是大量的二进制数据。因此一个网络窃听程序必须也使用特定的网络协议来分解嗅探到的数据,嗅探器也就必须能够识别出那个协议对应于这个数据片断,只有这样才能够进行正确的解码。很多计算机网络采用的是“共享媒体",几乎可以在任何连接着的网络上直接窃听到你同一掩码范围内的计算机网络数据,我们称这种窃听方式为“基于混杂模式的嗅探”。尽管如此,这种“共享”的技术发展的很快,慢慢转向“交换”技术,这种技术会长期内会继续使用下去,它可以实现有目的选择的收发数据。
- 在网络嗅探技术中所使用的包捕获机制的方法,大致可归纳为两类:一类是由操作系统内核提供的捕获机制;另一类是由应用软件或系统开发包通过安装包捕获驱动程序提供的捕获机制,该机制主要用于Win32平台下的开发。操作系统提供的捕获机制主要有四种:BPF,DLPI,NIT, Sock Packet类型套接口。BPF由基于BSD的Unix系统内核所实现;DLPI是Solaris(和其它System V Unix)系统的内嵌子系统。从性能上看,Sock Packet最弱。Windows操作系统没有提供内置的包捕获机制。它只提供了数量很少并且功能有限的API调用。WinPcap是Win32上的第一个用来捕获数据包的开放系统软件包,它是一种新提出的强有力并且可扩展的框架结构。
- 以太网的数据传输是基于“共享”原理的,所有的同一本地网范围内的计算机共同接收到相同的数据包。这意味着计算机直接的通讯都是透明可见的。正是因为这样的原因,以太网卡都构造了硬件的“过滤器”这个过滤器将忽略掉一切和自己无关的网络信息。事实上是忽略掉了与自身MAC地址不符合的信息。嗅探程序正是利用了这个特点,它主动的关闭了这个嗅探器,也就是前面提到的设置网卡“混杂模式”。因此,嗅探程序就能够接收到整个以太网内的网络数据信息。
四、方法概述:
- 嗅探器实质就是从网络上获取数据包的一种工具,它可以捕捉流经本地网卡的所有数据包。抓取网络数据包进行分析有很多用处,如分析网络是否有网络病毒等异常数据,通信协议的分析(数据链路层协议、TCP、IP、UDP,甚至各种应用层协议),敏感数据的捕捉等。
程序在执行过程中有两个核心算法设计,一是调用Winpcap函数库实现下层抓包,二是对抓到的包文进行分析。对于抓包算法过程的实现:
(1)初始化Winpcap开发库;
(2)获取当前的网卡列表,同时要求用户指定要操作的网卡;
(3)获得当前的过滤规则;
(4)调用库函数pcap_loop(),同时指定其回调函数,即为数据包分析过程。 - 本实验需要电脑上已经安装过winpcap,最主要的部分是数据包的解析与信息的获取。
- 首先是配置使用pcap.h这个库,只需要在链接上加入wpcap.lib等几个相关联的动态库即可,其中拥有着功能更加强大的函数,现列举几个本嗅探器设计中所使用的函数于下:
- pcap_findalldevs_ex()函数用于获取当前主机的设备列表,使用者可以根据该函数给出的设备列表选定需要进行嗅探的设备究竟是哪一个,例如是以太网口还是无线网卡之类的设备。最后使用pcap_freealldevs()清空即可,否则会一直占用影响下次使用。
- pcap_open()函数则是另一个较为关键的函数,它主要用于打开与连接设备器,后面跟随的参数分别为:设备名 ;要捕捉的数据包的部分,往往用65535保证能捕获到不同数据链路层上的每个数据包的全部内容 ;‘PCAP_OPENFLAG_PROMISCUOUS’用作将其置于混杂模式 ; 读取超时时间 ;远程机器验证(NULL) ;错误缓冲池这几个参数,这也是确保嗅探器开始正确嗅探的一步。
- 当嗅探器正确收集到数据包的时候则需要对数据包进行进一步的解析,以IP结构头为例,iphVerLen就代表了IP头中版本号和头长度(各占4位)的信息,ipSource与ipDestination则是我们所关心的源地址与目标地址信息,通过对获取到的数据包的结构体中这些信息的解析与输出,例如ip_hd->ipDestination便提取出了投中的目标地址,完成到了基本的嗅探操作,同时也可以获得数据包以数据或ASCII码等多种形式予以输出。
- Winpcap提供了pcap_findalldevs_ex()函数,这个函数返回一个指向pcap_if结构的链表,其中的每一项都包含了一个己经绑定的适配器的全部信息。取得网卡列表后就在屏幕上显示出来,如果网卡没有被发现就显示有关错误。pcap_ findalldevs()同其他的libpcap函数一样有一个errbuf参数,当有异常情况发生时,这个参数会被pcap填充为某个特定错误字串。随后,获得网卡的信息后就可以按数据捕获的要求打开网卡,打开网卡的功能是通过pcap_open_live()来实现的。在正常情况下网卡只接受去往它的包而去往其他主机的数据包则被忽略,相反当网卡处于混杂模式时它将接收所有的流经它的数据包。
- 之后,使用pcap_next_ex()从网络接口中读取一个数据包,该函数第一个参数是接口句柄,后两个参数由函数返回,分别为数据包的相关信息和数据包本身。函数返回1表示正常接收一个数据包,每捕获到一个数据包就调用PacketHandler()函数对数据包进行后续解析处理。之后对捕获的数据包按照数据链路层、网络层、传输层和应用层的层次结构自底向上进行解析,最后将解析结果显示输出。
五、实验设计流程:
六、实验结果:
该实验使用了pcap.h这个库文件,实现了更加强大的监听功能,不仅能对TCP进行监听,亦可以对网络中流传的UDP协议通讯进行监听。运行程序后,首先检测2号网卡是否打开,经过监听一段时间发现并没有任何回应,即可说明该网卡端口并没有连接,详见下图:
随后输入网卡号3,同时输出获取的报文,且不对其输出长度做任何限制。这次成功监听到端口信息,详见下图:
七、代码:
1 #define HAVE_REMOTE 2 #define LINE_LEN 16 3 #include "winsock.h" 4 #include <string.h> 5 #include "pcap.h" 6 /////////////////////////////////////////////// 7 typedef struct ip_address 8 { //ip地址 9 u_char b1; 10 u_char b2; 11 u_char b3; 12 u_char b4; 13 } ip_address; 14 15 typedef struct mac_address 16 {//mac地址 17 u_char b1; 18 u_char b2; 19 u_char b3; 20 u_char b4; 21 u_char b5; 22 u_char b6; 23 } mac_address; 24 25 typedef struct ethe_header 26 { //mac帧首部 27 mac_address mac_dest_address; 28 mac_address mac_source_address; 29 u_short ether_type; 30 } ethe_header; 31 32 typedef struct ip_header 33 { //ip地址首部 34 u_char ver_ihl; 35 u_char tos; 36 u_short tlen; 37 u_short identification; 38 u_short flags_fo; 39 u_char ttl; 40 u_char proto; 41 u_short crc; 42 ip_address saddr; 43 ip_address daddr; 44 u_int op_pad; 45 } ip_header; 46 47 typedef struct udp_header 48 { //UPD首部 49 u_short sport; 50 u_short dport; 51 u_short len; 52 u_short crc; 53 } udp_header; 54 55 typedef struct tcp_header 56 { //TCP首部 57 u_short sport; 58 u_short dport; 59 u_int num; 60 u_int ack; 61 u_short sum; 62 u_short windonw; 63 u_short crc; 64 u_short ugr; 65 } tcp_header; 66 67 void packet_handler(u_char * param, const struct pcap_pkthdr * header, const u_char *pkt_data); 68 char judge; 69 int length; 70 int main() 71 { 72 pcap_if_t * alldevs, *device; 73 int i = 0; 74 int iNum; 75 u_int netmask; 76 struct bpf_program fcode; 77 pcap_t * adhandle; 78 char errbuf[PCAP_ERRBUF_SIZE]; 79 //修改这里可以更改捕获的数据包使用的协议类型 80 char packet_filter[] = "(ip and udp) or (ip and tcp) or (ip and icmp)"; 81 82 if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) 83 { //获取设备列表 84 fprintf(stderr,"无法打开网络设备:%s\n", errbuf); 85 return 1; 86 } 87 for (device = alldevs; device != NULL; device = device->next) 88 { //打印列表 89 if (i == 0) 90 { 91 printf("请按CTRL + C退出!\n\n"); 92 printf("网络设备如下:\n"); 93 } 94 printf("%d. %s\n", ++i, device -> name); 95 if (device->description) 96 printf(" (%s)\n", device->description); 97 else 98 printf("没有设备描述信息!"); 99 } 100 if (i == 0) 101 { 102 printf("\n请先安装WinPcap!"); 103 return -1; 104 } 105 printf("请选择网络设备接口:(1 - %d):", i); 106 scanf("%d", &iNum); 107 getchar(); 108 if (iNum < 1 || iNum > i) 109 { 110 printf("设备不存在!\n"); 111 pcap_freealldevs(alldevs); 112 return -1; 113 } 114 //跳转到已选设备 115 for (device = alldevs, i = 0;i < iNum -1 ; device = device -> next,i++); 116 117 // 打开适配器 118 if ( (adhandle= pcap_open(device->name, // 设备名 119 65536, // 要捕捉的数据包的部分 120 // 65535保证能捕获到不同数据链路层上的每个数据包的全部内容 121 PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式 122 1000, // 读取超时时间 123 NULL, // 远程机器验证 124 errbuf // 错误缓冲池 125 ) ) == NULL) 126 { 127 fprintf(stderr,"\n不能打开适配器!\n"); 128 /* 释放设备列表 */ 129 pcap_freealldevs(alldevs); 130 return -1; 131 } 132 133 if (pcap_datalink(adhandle) != DLT_EN10MB) 134 { //检查数据链路层,为了简单,只考虑以太网 135 fprintf(stderr, "\n系统网卡链路出错!\n"); 136 pcap_freealldevs(alldevs); //释放设备列表 137 return -1; 138 } 139 140 if (device->addresses != NULL) //获得接口第一个地址的掩码 141 netmask = ((struct sockaddr_in *)(device->addresses->netmask))->sin_addr.S_un.S_addr; 142 else //如果接口没有地址,那么我们假设一个C类的掩码 143 netmask = 0xffff00; 144 if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) < 0) 145 { //编译过滤器 146 fprintf(stderr, "不能监听过滤该数据报!\n"); 147 pcap_freealldevs(alldevs); 148 return -1; 149 } 150 151 if (pcap_setfilter(adhandle, &fcode) < 0) 152 { //设置过滤器 153 fprintf(stderr, "过滤设置错误!\n"); 154 pcap_freealldevs(alldevs); 155 return -1; 156 } 157 printf("请输入是否要输出捕捉到的报文信息(y/n) : "); 158 scanf("%c",&judge); 159 if (judge!='n') 160 { 161 printf("请输入要限制要输出报文信息长度(-1不限制) : "); 162 scanf("%d",&length); 163 } 164 printf("\n正在监听通过%s的数据报...\n", device->description); 165 pcap_freealldevs(alldevs); //释放设备列表 166 pcap_loop(adhandle, 0, packet_handler, NULL); //开始捕捉 167 168 return 0 ; 169 } 170 171 void packet_handler(u_char *dumpfile, const struct pcap_pkthdr *header, const u_char *pkt_data) 172 { //回调函数,当收到每一个数据包时会被libpcap所调用 173 if(header->caplen>400) return; 174 int len; 175 struct tm *ltime; 176 char timestr[16]; 177 ip_header * ip_hd; 178 udp_header * udp_hd; 179 tcp_header * tcp_hd; 180 ethe_header * ethe_hd; 181 int ip_len,tcp_len,start; 182 u_short sport,dport; 183 184 printf("\n"); 185 ltime=localtime(&header->ts.tv_sec); //将时间戳转换为可读字符 186 strftime( timestr, sizeof timestr, "%H:%M:%S", ltime); 187 printf("时间:%s\n",timestr); 188 189 ethe_hd = (ethe_header *)pkt_data; 190 ip_hd = (ip_header *)(pkt_data + 14); 191 ip_len = (ip_hd ->ver_ihl & 0xf) * 4; //ip首部长度 192 udp_hd = (udp_header *)((u_char *)ip_hd + ip_len); 193 sport = ntohs(udp_hd->sport); 194 dport = ntohs(udp_hd->dport); 195 if(ip_hd->proto==17) 196 { 197 printf("协议:UDP"); 198 start=ip_len+8; 199 } 200 else if(ip_hd->proto==6) 201 { 202 printf("协议:TCP"); 203 tcp_hd = (tcp_header *)((u_char *)ip_hd + ip_len); 204 tcp_len=ntohs(tcp_hd->sum)>>12; 205 start=ip_len+tcp_len*4; 206 } 207 else if(ip_hd->proto==1) 208 { 209 printf("协议:ICMP"); 210 start=ip_len+23; 211 } 212 else printf("协议:其他"); 213 //printf("start=%d\n",start); 214 printf(" 数据报的长度:%d\n",header->caplen); 215 printf("IP头的长度:%d IP包存活时间:%d\n",ip_hd->tlen,ip_hd->ttl); 216 printf("源IP地址: %d.%d.%d.%d:%d 目的IP地址:%d.%d.%d.%d:%d\n源端口:%d 目的端口:%d\n源物理地址: %x-%x-%x-%x-%x-%x 目的物理地址:%x-%x-%x-%x-%x-%x\n", 217 ip_hd->saddr.b1, ip_hd->saddr.b2, ip_hd->saddr.b3, ip_hd->saddr.b4, 218 ip_hd->daddr.b1, ip_hd->daddr.b2, ip_hd->daddr.b3, ip_hd->daddr.b4, sport, dport, 219 ethe_hd->mac_source_address.b1, ethe_hd->mac_source_address.b2, ethe_hd->mac_source_address.b3, 220 ethe_hd->mac_source_address.b4, ethe_hd->mac_source_address.b5, ethe_hd->mac_source_address.b6, 221 ethe_hd->mac_dest_address.b1, ethe_hd->mac_dest_address.b2, ethe_hd->mac_dest_address.b3, 222 ethe_hd->mac_dest_address.b4, ethe_hd->mac_dest_address.b5, ethe_hd->mac_dest_address.b6); 223 //输出数据部分 224 if (judge=='y') 225 { 226 printf("数据部分内容为:\n"); 227 if(length==-1) len=(header->caplen) + 1 ; 228 else len=(length>header->caplen + 1-start)?(header->caplen+1)-start:length; 229 for (int i=start; (i < start + len ) ; i++) 230 { 231 printf("%.2x ", pkt_data[i-1]); //也可以改为 %c 以 ascii码形式输出。 232 if ( (i % LINE_LEN) == 0) printf("\n"); 233 } 234 printf("\n\n"); 235 } 236 }