计算机网络

您所在的位置:网站首页 网络数据包分析工具的设计与开发 计算机网络

计算机网络

2024-07-10 16:59| 来源: 网络整理| 查看: 265

实验二 WINPCWP编程 班级xxx实验环境Win10 Pro 1709(64位)姓名xxx开发环境Visual Studio 2013学号xxx软件版本WinPcap 4.1.3 一、实验目的 了解WINPCAP的架构学习WINPCAP编程 二、实验原理

WinPcap是一个基于Win32平台的,用于捕获网络数据包并进行分析的开源库.

大多数网络应用程序通过被广泛使用的操作系统元件来访问网络,比如sockets。 这是一种简单的实现方式,因为操作系统已经妥善处理了底层具体实现细节(比如协议处理,封装数据包等等),并且提供了一个与读写文件类似的,令人熟悉的接口。

然而,有些时候,这种“简单的方式”并不能满足任务的需求,因为有些应用程序需要直接访问网络中的数据包。也就是说,那些应用程序需要访问原始数据包,即没有被操作系统利用网络协议处理过的数据包。

WinPcap产生的目的,就是为Win32应用程序提供这种访问方式; WinPcap提供了以下功能 :

1.捕获原始数据包,无论它是发往某台机器的,还是在其他设备(共享媒介)上进行交换的

2.在数据包发送给某应用程序前,根据用户指定的规则过滤数据包

3.将原始数据包通过网络发送出去

4.收集并统计网络流量信息

以上这些功能需要借助安装在Win32内核中的网络设备驱动程序才能实现,再加上几个动态链接库DLL。

所有这些功能都能通过一个强大的编程接口来表现出来,易于开发,并能在不同的操作系统上使用。这本手册的主要目标是在一些程序范例的帮助下,叙述这些编程接口的使用。

WinPcap可以被用来制作网络分析、监控工具。一些基于WinPcap的典型应用有:

1.网络与协议分析器 (network and protocol analyzers)

2.网络监视器 (network monitors)

3.网络流量记录器 (traffic loggers)

4.网络流量发生器 (traffic generators)

5.用户级网桥及路由 (user-level bridges and routers)

6.网络入侵检测系统 (network intrusion detection systems (NIDS))

7.网络扫描器 (network scanners)

8.安全工具 (security tools)

三、实验内容

通过学习WINPCAP架构,编写一个网络抓包程序。

四、实验过程 1.WinPcap的下载、安装与配置 访问WinPcap官网:https://www.winpcap.org/选择WinPcap的最新版,下载Installer for Windows并安装在VS中导入相应头文件和lib文件 2.获取设备列表

WinPcap提供了 pcap_findalldevs_ex() 函数来实现获取设备列表的功能,这个函数返回一个 pcap_if 结构的链表, 每个这样的结构都包含了一个适配器的详细信息。值得注意的是,数据域 name 和 description 表示一个适配器名称和一个可以让人们理解的描述。下面获取适配器列表,并在屏幕上显示出来,如果没有找到适配器,将打印错误信息。

#include #include "pcap.h" #define PCAP_SRC_IF_STRING "rpcap://" #define PCAP_SRC_FILE_STRING "file://" main() { pcap_if_t *alldevs; pcap_if_t *d; int i = 0; char errbuf[PCAP_ERRBUF_SIZE]; /* 获取本地机器设备列表 */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL /* auth is not needed */, &alldevs, errbuf) == -1) { fprintf(stderr, "Error in pcap_findalldevs_ex: %s\n", errbuf); exit(1); } /* 打印列表 */ for (d = alldevs; d != NULL; d = d->next) { printf("%d. %s", ++i, d->name); if (d->description) printf(" (%s)\n", d->description); else printf(" (No description available)\n"); } if (i == 0) { printf("\nNo interfaces found! Make sure WinPcap is installed.\n"); return; } getchar(); /* 不再需要设备列表了,释放它 */ pcap_freealldevs(alldevs); }

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5vm89BEc-1592755604311)(1.jpg)]

WinPcap获取的设备列表显示主机有四块逻辑网卡,与控制面板-网络适配器显示的一致

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qwUk9Qoa-1592755604314)(2.jpg)]

3.获取已安装设备的高级信息

上一部分的获取设备列表,获取了适配器的基本信息 (如设备的名称和描述),而WinPcap提供了其他更高级的信息。由 pcap_findalldevs_ex() 返回的每一个 pcap_if 结构体,都包含一个 pcap_addr 结构体,这个结构体由如下元素组成:

一个地址列表一个掩码列表一个广播地址列表一个目的地址列表

另外,函数 pcap_findalldevs_ex() 还能返回远程适配器信息和一个位于所给的本地文件夹的pcap文件列表。

下面的程序使用了 ifprint() 函数来打印出 pcap_if 结构体中所有的内容。程序对每一个由 pcap_findalldevs_ex() 函数返回的 pcap_if ,都调用 ifprint() 函数来实现打印。

#include "pcap.h" #include #define PCAP_SRC_IF_STRING "rpcap://" #define PCAP_SRC_FILE_STRING "file://" #ifndef WIN32 #include #include #else #include #endif // 函数原型 void ifprint(pcap_if_t *d); char *iptos(u_long in); int main() { pcap_if_t *alldevs; pcap_if_t *d; char errbuf[PCAP_ERRBUF_SIZE + 1]; char source[PCAP_ERRBUF_SIZE + 1]; printf("Enter the device you want to list:\n" "rpcap:// ==> lists interfaces in the local machine\n" "rpcap://hostname:port ==> lists interfaces in a remote machine\n" " (rpcapd daemon must be up and running\n" " and it must accept 'null' authentication)\n" "file://foldername ==> lists all pcap files in the give folder\n\n" "Enter your choice: "); fgets(source, PCAP_ERRBUF_SIZE, stdin); source[PCAP_ERRBUF_SIZE] = '\0'; /* 获得接口列表 */ if (pcap_findalldevs_ex(source, NULL, &alldevs, errbuf) == -1) { fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf); exit(1); } /* 扫描列表并打印每一项 */ for (d = alldevs; d; d = d->next) { ifprint(d); } pcap_freealldevs(alldevs); getchar(); return 1; } /* 打印所有可用信息 */ void ifprint(pcap_if_t *d) { pcap_addr_t *a; /* 设备名(Name) */ printf("%s\n", d->name); /* 设备描述(Description) */ if (d->description) printf("\tDescription: %s\n", d->description); /* Loopback Address*/ printf("\tLoopback: %s\n", (d->flags & PCAP_IF_LOOPBACK) ? "yes" : "no"); /* IP addresses */ for (a = d->addresses; a; a = a->next) { printf("\tAddress Family: #%d\n", a->addr->sa_family); switch (a->addr->sa_family) { case AF_INET: printf("\tAddress Family Name: AF_INET\n"); if (a->addr) printf("\tAddress: %s\n", iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr)); if (a->netmask) printf("\tNetmask: %s\n", iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr)); if (a->broadaddr) printf("\tBroadcast Address: %s\n", iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr)); if (a->dstaddr) printf("\tDestination Address: %s\n", iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr)); break; default: printf("\tAddress Family Name: Unknown\n"); break; } } printf("\n"); } /* 将数字类型的IP地址转换成字符串类型的 */ #define IPTOSBUFFERS 12 char *iptos(u_long in) { static char output[IPTOSBUFFERS][3 * 4 + 3 + 1]; static short which; u_char *p; p = (u_char *)∈ which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1); sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); return output[which]; }

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bmPrQbH4-1592755604317)(3.jpg)]

结果分析:

WinPcap获取了4块逻辑网卡的高级信息:包含设备描述、IP地址、子网掩码、广播地址等,如下表所示

编号备注IP地址子网掩码广播地址1Sangfor SSL VPN支持未获得未获得未获得2蓝牙网络连接未获得未获得未获得3无线网络连接10.0.0.106255.255.255.0255.255.255.2554有线网络连接10.0.0.108255.255.255.0255.255.255.255 4.打开适配器并捕获数据包

编写一个程序,将每一个通过适配器的数据包打印出来。 打开设备的函数是 pcap_open()。下面是参数 snaplen, flags 和 to_ms 的解释说明

snaplen:制定要捕获数据包中的哪些部分。 在一些操作系统中 (比如 xBSD 和 Win32), 驱动可以被配置成只捕获数据包的初始化部分: 这样可以减少应用程序间复制数据的量,从而提高捕获效率。本例中,将值定为65535,它比能遇到的最大的MTU还要大,因此总能收到完整的数据包。

flags:最最重要的flag是用来指示适配器是否要被设置成混杂模式。 一般情况下,适配器只接收发给它自己的数据包, 而那些在其他机器之间通讯的数据包,将会被丢弃。 相反,如果适配器是混杂模式,那么不管这个数据包是不是发给我的,我都会去捕获。也就是说,我会去捕获所有的数据包。 这意味着在一个共享媒介(比如总线型以太网),WinPcap能捕获其他主机的所有的数据包。 大多数用于数据捕获的应用程序都会将适配器设置成混杂模式,所以在下面的范例中,使用混杂模式。

to_ms:指定读取数据的超时时间,以毫秒计(1s=1000ms)。在适配器上进行读取操作(比如用 pcap_dispatch() 或 pcap_next_ex()) 都会在 to_ms 毫秒时间内响应,即使在网络上没有可用的数据包。 在统计模式下,to_ms 还可以用来定义统计的时间间隔。 将 to_ms 设置为0意味着没有超时,那么如果没有数据包到达的话,读操作将永远不会返回。 如果设置成-1,则情况恰好相反,无论有没有数据包到达,读操作都会立即返回。

#include "pcap.h" #define PCAP_SRC_IF_STRING "rpcap://" #define PCAP_SRC_FILE_STRING "file://" #define PCAP_OPENFLAG_PROMISCUOUS 1 //Defines if the adapter has to go in promiscuous mode. #define PCAP_OPENFLAG_DATATX_UDP 2 //Defines if the data trasfer(in case of a remote capture) has to be done with UDP protocol. #define PCAP_OPENFLAG_NOCAPTURE_RPCAP 4 //Defines if the remote probe will capture its own generated traffic. #define PCAP_OPENFLAG_NOCAPTURE_LOCAL 8 //Defines if the local adapter will capture its own generated traffic. #define PCAP_OPENFLAG_MAX_RESPONSIVENESS 16 //This flag configures the adapter for maximum responsiveness. /* packet handler 函数原型 */ void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data); main() { pcap_if_t *alldevs; pcap_if_t *d; int inum; int i = 0; pcap_t *adhandle; char errbuf[PCAP_ERRBUF_SIZE]; /* 获取本机设备列表 */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &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->description) printf(" (%s)\n", d->description); else printf(" (No description 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); if (inum i) { printf("\nInterface number out of range.\n"); /* 释放设备列表 */ pcap_freealldevs(alldevs); return -1; } /* 跳转到选中的适配器 */ for (d = alldevs, i = 0; inext, i++); /* 打开设备 */ if ((adhandle = pcap_open(d->name, // 设备名 65536, // 65535保证能捕获到不同数据链路层上的每个数据包的全部内容 PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式 1000, // 读取超时时间 NULL, // 远程机器验证 errbuf // 错误缓冲池 )) == NULL) { fprintf(stderr, "\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name); /* 释放设备列表 */ pcap_freealldevs(alldevs); return -1; } printf("\nlistening on %s...\n", d->description); /* 释放设备列表 */ pcap_freealldevs(alldevs); /* 开始捕获 */ pcap_loop(adhandle, 0, packet_handler, NULL); return 0; } /* 每次捕获到数据包时,libpcap都会自动调用这个回调函数 */ void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) { struct tm *ltime; char timestr[16]; time_t local_tv_sec; /* 将时间戳转换成可识别的格式 */ local_tv_sec = header->ts.tv_sec; ltime = localtime(&local_tv_sec); strftime(timestr, sizeof timestr, "%H:%M:%S", ltime); printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len); }

运行结果(捕获的是无线网卡的数据包)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FHqtNf4l-1592755604320)(4.jpg)]

结果分析:

适配器被打开,捕获工作就可以用 pcap_dispatch() 或 pcap_loop() 进行。 这两个函数非常的相似,区别就是 pcap_ dispatch() 当超时时间到了(timeout expires)就返回 (尽管不能保证) ,而 pcap_loop() 不会因此而返回,只有当 cnt 数据包被捕获,所以,pcap_loop() 会在一小段时间内,阻塞网络的利用。pcap_loop() 对于这个简单的范例来说,可以满足需求,不过, pcap_dispatch() 函数一般用于比较复杂的程序中。

这两个函数都有一个 回调 参数, packet_handler指向一个可以接收数据包的函数。 这个函数会在收到每个新的数据包并收到一个通用状态时被libpcap所调用 ( 与函数 pcap_loop() 和 pcap_dispatch() 中的 user 参数相似),数据包的首部一般有一些诸如时间戳,数据包长度的信息,还有包含了协议首部的实际数据。 注意:冗余校验码CRC不再支持,因为帧到达适配器,并经过校验确认以后,适配器就会将CRC删除,与此同时,大部分适配器会直接丢弃CRC错误的数据包,所以,WinPcap没法捕获到它们。

上面的程序将每一个数据包的时间戳和长度从 pcap_pkthdr 的首部解析出来,并打印在屏幕上。

5.分析数据包

捕获的数据包的协议首部,打印一些网络上传输的UDP数据的信息。

#include "pcap.h" #define PCAP_SRC_IF_STRING "rpcap://" #define PCAP_SRC_FILE_STRING "file://" #define PCAP_OPENFLAG_PROMISCUOUS 1 //Defines if the adapter has to go in promiscuous mode. #define PCAP_OPENFLAG_DATATX_UDP 2 //Defines if the data trasfer(in case of a remote capture) has to be done with UDP protocol. #define PCAP_OPENFLAG_NOCAPTURE_RPCAP 4 //Defines if the remote probe will capture its own generated traffic. #define PCAP_OPENFLAG_NOCAPTURE_LOCAL 8 //Defines if the local adapter will capture its own generated traffic. #define PCAP_OPENFLAG_MAX_RESPONSIVENESS 16 //This flag configures the adapter for maximum responsiveness. #include "pcap.h" /* 4字节的IP地址 */ typedef struct ip_address{ u_char byte1; u_char byte2; u_char byte3; u_char byte4; }ip_address; /* IPv4 首部 */ typedef struct ip_header{ u_char ver_ihl; // 版本 (4 bits) + 首部长度 (4 bits) u_char tos; // 服务类型(Type of service) u_short tlen; // 总长(Total length) u_short identification; // 标识(Identification) u_short flags_fo; // 标志位(Flags) (3 bits) + 段偏移量(Fragment offset) (13 bits) u_char ttl; // 存活时间(Time to live) u_char proto; // 协议(Protocol) u_short crc; // 首部校验和(Header checksum) ip_address saddr; // 源地址(Source address) ip_address daddr; // 目的地址(Destination address) u_int op_pad; // 选项与填充(Option + Padding) }ip_header; /* UDP 首部*/ typedef struct udp_header{ u_short sport; // 源端口(Source port) u_short dport; // 目的端口(Destination port) u_short len; // UDP数据包长度(Datagram length) u_short crc; // 校验和(Checksum) }udp_header; /* 回调函数原型 */ void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data); main() { pcap_if_t *alldevs; pcap_if_t *d; int inum; int i = 0; pcap_t *adhandle; char errbuf[PCAP_ERRBUF_SIZE]; u_int netmask; char packet_filter[] = "ip and udp"; struct bpf_program fcode; /* 获得设备列表 */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &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->description) printf(" (%s)\n", d->description); else printf(" (No description 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); if (inum i) { printf("\nInterface number out of range.\n"); /* 释放设备列表 */ pcap_freealldevs(alldevs); return -1; } /* 跳转到已选设备 */ for (d = alldevs, i = 0; inext, i++); /* 打开适配器 */ if ((adhandle = pcap_open(d->name, // 设备名 65536, // 要捕捉的数据包的部分 // 65535保证能捕获到不同数据链路层上的每个数据包的全部内容 PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式 1000, // 读取超时时间 NULL, // 远程机器验证 errbuf // 错误缓冲池 )) == 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 = 0xffffff; //编译过滤器 if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) ts.tv_sec; ltime = localtime(&local_tv_sec); strftime(timestr, sizeof timestr, "%H:%M:%S", ltime); /* 打印数据包的时间戳和长度 */ printf("%s.%.6d len:%d ", timestr, header->ts.tv_usec, header->len); /* 获得IP数据包头部的位置 */ ih = (ip_header *)(pkt_data + 14); //以太网头部长度 /* 获得UDP首部的位置 */ ip_len = (ih->ver_ihl & 0xf) * 4; uh = (udp_header *)((u_char*)ih + ip_len); /* 将网络字节序列转换成主机字节序列 */ sport = ntohs(uh->sport); dport = ntohs(uh->dport); /* 打印IP地址和UDP端口 */ printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\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); }

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-opE4yBdC-1592755604323)(5.jpg)]

首先,将过滤器设置成"ip and udp"。在这种方式下,packet_handler() 只会收到基于IPv4的UDP数据包;这将简化解析过程,提高程序的效率。其次,创建了用于描述IP首部和UDP首部的结构体。这些结构体中的各种数据会被packet_handler()合理地定位。在开始捕捉前,使用了pcap_datalink() 对MAC层进行了检测,以确保在处理一个以太网络,确保MAC首部是14位的。IP数据包的首部就位于MAC首部的后面,将从IP数据包的首部解析到源IP地址和目的IP地址。

处理UDP的首部有一些复杂,因为IP数据包的首部的长度并不是固定的,可以通过IP数据包的length域来得到它的长度。一旦知道了UDP首部的位置就能解析到源端口和目的端口。

被解析出来的值被打印在屏幕上,形式上图所示,每一行分别代表一个数据包。

五、习题与思考题

1、WINPCAP是否能实现服务质量的控制?

答:不能。WinPcap可以独立地通过主机协议发送和接受数据,如同TCP-IP。 这就意味着WinPcap不能阻止、过滤或操纵同一机器上的其他应用程序的通讯: 它仅仅能简单地"监视"在网络上传输的数据包。所以,它不能提供类似网络流量控制、服务质量调度和个人防火墙之类的支持,因而不能实现服务质量的控制。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3