WinPcap分析网络数据包
《网络程序设计》上机实验报告五
WinPcap分析网络数据包
[实验目的]
了解WinPcap如何实现网络数据包的捕获
首先了解WinPcap的相关知识:比如下载安装等。
利用捕获的数据包和所学知识完成数据包的分析
1.了解相关头文件pcap.h,里面有WinPcap提供的相关函数及数据结构
[实验要求]
给出实验中主要的程序代码以及运行结果,并把编译、运行过程中出现的问题以及解决方法填入实验报告中,按时上交。
[实验学时] 2 学时。
[实验内容]
Winpcap环境调试实验截图:
首先在预处理器中添加两个定义:
然后再在包含目录和库目录下找到安装的winpcap路径添加。
最后再添加上依赖项wpcap.lib、ws2_32.lib、Packet.lib即可。
WinPcap 是 BPF 模型和 Libpcap 函数库在 Windows 平台下网络数据包捕获和网络状态分析的一种体系结构,这个体系结构是由一个核心的包过滤驱动程序,一个底层的动态连接库 Packet.dll 和一个高层的独立于系统的函数库 Libpcap 组成。
WinPcap 包括三个部分:
第一个模块NPF(Netgroup Packet Filter),是一个虚拟设备驱动程序文件。第二个模块packet.dll为win32平台提供了一个公共的接口。Packet.dll用于解决这些不同。第三个模块 Wpcap.dll是不依赖于操作系统的。
packet.dll和Wpcap.dll:packet.dll直接映射了内核的调用。 Wpcap.dll提供了更加友好、功能更加强大的函数调用。WinPcap的优势提供了一套标准的抓包接口对于NPF内核层次上的过滤器支持,支持内核态的统计模式,提供了发送数据包的能力。
利用winpcap进行网络数据包的捕获和过滤的设计步骤
1)打开网卡,并设为混杂模式。
2)回调函数 Network Tap 在得到监听命令后,从网络设备驱动程序处收集数据包把监听到的数据包负责传送给过滤程序。
3)当 Packet filter 监听到有数据包到达时,NDIS 中间驱动程序首先调用分组驱动程序,该程序将数据传递给每一个参与进程的分组过滤程序。
4)然后由 Packet filter 过滤程序决定哪些数据包应该丢弃,哪些数据包应该接收,是否需要将接收到的数据拷贝到相应的应用程序。
5)通过分组过滤器后,将数据未过滤掉的数据包提交给核心缓冲区。然后等待系统缓冲区满后,再将数据包拷贝到用户缓冲区。监听程序可以直接从用户缓冲区中读取捕获的数据包。
6)关闭网卡。
混杂模式(Promiscuous Model):工作在混杂模式下的网卡接收所有的流过网卡的帧,信包捕获程序就是在这种模式下运行的。网卡的缺省工作模式包含广播模式和直接模式,即它只接收广播帧和发给自己的帧。如果采用混杂模式,一个站点的网卡将接受同一网络内所有站点所发送的数据包这样就可以到达对于网络信息监视捕获的目的。
IP数据包头字段说明
版本号(Version):长度4比特。标识目前采用的IP协议的版本号。一般的值为0100(IPv4),0110(IPv6)
IP包头长度(HeaderLength):长度4比特。这个字段的作用是为了描述IP包头的长度,因为在IP包头中有变长的可选部分。该部分占4个bit位,单位为32bit(4个字节),即本区域值= IP头部长度(单位为bit)/(8*4),因此,一个IP包头的长度最长为“1111”,即15*4=60个字节。IP包头最小长度为20字节。
服务类型(Type of Service):长度8比特。
IP包总长(Total Length):长度16比特。
标识符(Identifier)(数据报ID):长度16比特。
标记(Flags):长度3比特。
片偏移(Fragment Offset):长度13比特。
生存时间(TTL):长度8比特。当IP包进行传送时,先会对该字段赋予某个特定的值。当IP包经过每一个沿途的路由器的时候,每个沿途的路由器会将IP包的TTL值减少1。如果TTL减少为0,则该IP包会被丢弃。这个字段可以防止由于路由环路而导致IP包在网络中不停被转发。
协议(Protocol):长度8比特。标识了上层所使用的协议。
以下是比较常用的协议号:
ICMP IGMP
6 TCP
17 UDP
88 IGRP
89 OSPF
头部校验(Header Checksum):长度16位。用来做IP头部的正确性检测,但不包含数据部分。 因为每个路由器要改变TTL的值,所以路由器会为每个通过的数据包重新计算这个值。
起源和目标地址(Source and Destination Addresses):这两个地段都是32比特。标识了这个IP包的起源和目标地址。要注意除非使用NAT,否则整个传输的过程中,这两个地址不会改变。
打开适配器并捕获数据包源代码:
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <Winsock2.h>
#include<iostream>
#include "pcap.h"
#include "stdio.h"
#include<time.h>
#include <string>
#include <fstream> //文件的输入输出;
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"wpcap.lib")
using namespace std;
//以太网的协议格式
struct ethernet_header
{
u_int8_t ether_dhost[6]; //目的以太地址
u_int8_t ether_shost[6]; //源以太网地址
u_int16_t ether_type; //以太网类型
};
//ip地址格式
typedef u_int32_t in_addr_t;
struct ip_header
{
#ifdef WORKS_BIGENDIAN
u_int8_t ip_version : 4, //version:4
ip_header_length : 4; //IP协议首部长度Header Length
#else
u_int8_t ip_header_length : 4,
ip_version : 4;
#endif
u_int8_t ip_tos; //服务类型Differentiated Services Field
u_int16_t ip_length; //总长度Total Length
u_int16_t ip_id; //标识identification
u_int16_t ip_off; //片偏移
u_int8_t ip_ttl; //生存时间Time To Live
u_int8_t ip_protocol; //协议类型(TCP或者UDP协议)
u_int16_t ip_checksum; //首部检验和
struct in_addr ip_source_address; //源IP
struct in_addr ip_destination_address; //目的IP
};
//tcp头部定义
struct tcp_header
{
u_int16_t tcp_source_port; //源端口号
u_int16_t tcp_destination_port; //目的端口号
u_int32_t tcp_acknowledgement; //序号
u_int32_t tcp_ack; //确认号字段
#ifdef WORDS_BIGENDIAN
u_int8_t tcp_offset : 4,
tcp_reserved : 4;
#else
u_int8_t tcp_reserved : 4,
tcp_offset : 4;
#endif
u_int8_t tcp_flags;
u_int16_t tcp_windows; //窗口字段
u_int16_t tcp_checksum; //检验和
u_int16_t tcp_urgent_pointer; //紧急指针字段
};
//tcp数据包分析的函数定义tcp_protocol_packet_callback
void tcp_protocol_packet_callback(u_char* argument, const struct pcap_pkthdr*
packet_header, const u_char* packet_content)
{
struct tcp_header* tcp_protocol; //tcp协议变量
u_char flags; //标记
int header_length; //头长度
u_short source_port; //源端口
u_short destination_port; //目的端口
u_short windows; //窗口大小
u_short urgent_pointer; //紧急指针
u_int sequence; //序列号
u_int acknowledgement; //确认号
u_int16_t checksum; //检验和
tcp_protocol = (struct tcp_header*)(packet_content + 14 + 20); //获得tcp首部内容
source_port = ntohs(tcp_protocol->tcp_source_port); //获得源端口号
destination_port = ntohs(tcp_protocol->tcp_destination_port); //获得目的端口号
header_length = tcp_protocol->tcp_offset * 4; //获得首部长度
sequence = ntohl(tcp_protocol->tcp_acknowledgement); //获得序列号
acknowledgement = ntohl(tcp_protocol->tcp_ack);
windows = ntohs(tcp_protocol->tcp_windows);
urgent_pointer = ntohs(tcp_protocol->tcp_urgent_pointer);
flags = tcp_protocol->tcp_flags;
checksum = ntohs(tcp_protocol->tcp_checksum);
printf("\n===运输层(TCP协议) ====\n");
printf("源端口:\t %d\n", source_port);
printf("目的端口:\t %d\n", destination_port);
int min = (destination_port < source_port) ? destination_port : source_port;
cout << "应用层协议是:\t";
switch (min)
{
case 80:printf(" http (HTTP)");
break;
case 21:printf(" ftp 文件传输协议(FTP)");
break;
case 23:printf(" telnet Telnet 服务 ");
break;
case 25:printf(" smtp 简单邮件传输协议(SMTP)");
break;
case 110:printf(" pop3 邮局协议版本3 ");
break;
case 443:printf(" https (HTTPs) ");
break;
default:printf("其他类型");
break;
}
cout << endl;
printf("序列号:\t %u \n", sequence);
printf("确认号:\t%u \n", acknowledgement);
printf("首部长度:\t%d \n", header_length);
printf("保留字段:\t%d \n", tcp_protocol->tcp_reserved);
printf("控制位:");
if (flags & 0x08) printf("\t推送 PSH");
if (flags & 0x10) printf("\t确认 ACK");
if (flags & 0x02) printf("\t同步 SYN");
if (flags & 0x20) printf("\t紧急 URG");
if (flags & 0x01) printf("\t终止 FIN");
if (flags & 0x04) printf("\t复位 RST");
printf("\n");
printf("检验和 :\t%d\n", checksum);
printf("紧急指针 :\t%d\n", urgent_pointer);
}
void ip_protocol_packet_callback(u_char* argument, const struct pcap_pkthdr*
packet_header, const u_char* packet_content)
{
struct ip_header* ip_protocol; //ip协议变量
u_int header_length; //头长度
u_int offset; //片偏移
u_char tos; //服务类型
u_int16_t checksum; //首部检验和
ip_protocol = (struct ip_header*)(packet_content + 14); //获得ip数据包的内容去掉以太头部
checksum = ntohs(ip_protocol->ip_checksum); //获得校验和
header_length = ip_protocol->ip_header_length * 4; //获得头长度
tos = ip_protocol->ip_tos; //获得tos
offset = ntohs(ip_protocol->ip_off); //获得偏移量
printf("\n###网络层(IP协议)####\n");
printf("IP版本:\t\tIPv%d\n", ip_protocol->ip_version);
printf("IP协议首部长度:\t%d\n", header_length);
printf("服务类型:\t%d\n", tos);
printf("总长度:\t\t%d\n", ntohs(ip_protocol->ip_length));//获得总长度
printf("标识:\t\t%d\n", ntohs(ip_protocol->ip_id)); //获得标识
printf("片偏移:\t\t%d\n", (offset & 0x1fff) * 8); //获得片偏移
printf("生存时间:\t%d\n", ip_protocol->ip_ttl); //获得ttl
printf("首部检验和:\t%d\n", checksum); //获得校验和
printf("源IP:\t%s\n", inet_ntoa(ip_protocol->ip_source_address)); //获得源ip地址
printf("目的IP:\t%s\n", inet_ntoa(ip_protocol->ip_destination_address)); //获得目的ip地址
printf("协议号:\t%d\n", ip_protocol->ip_protocol); //获得协议类型
cout << "\n传输层协议是:\t";
switch (ip_protocol->ip_protocol)
{
case 6:
printf("TCP\n");
tcp_protocol_packet_callback(argument, packet_header, packet_content);
break;
case 17:
printf("UDP\n");
break;
case 1:
printf("ICMP\n");
break;
case 2:
printf("IGMP\n");
break;
default:break;
}
}
//IP数据包分析的函数定义ethernet_protocol_packet_callback
void ethernet_protocol_packet_callback(u_char* argument, const struct pcap_pkthdr* packet_header, const u_char* packet_content)
{
u_short ethernet_type; //以太网协议类型
struct ethernet_header* ethernet_protocol; //以太网协议变量
u_char* mac_string;
static int packet_number = 1;
printf("\n*************************************\n");
printf("\t第%d个IP数据包被捕获!\n", packet_number);
printf("\n====链路层(以太网协议) ====\n");
ethernet_protocol = (struct ethernet_header*)packet_content; //获得一太网协议数据内容
printf("以太网类型为 :\t");
ethernet_type = ntohs(ethernet_protocol->ether_type); //获得以太网类型
printf("%04x\n", ethernet_type);
switch (ethernet_type) //判断以太网类型的值
{
case 0x0800:
printf("网络层是:\tIPv4协议\n"); break;
case 0x0806:
printf("网络层是:\tARP协议\n"); break;
case 0x8035:
printf("网络层是:\tRARP 协议\n"); break;
default: break;
}
//获得Mac源地址
printf("Mac源地址:\t");
mac_string = ethernet_protocol->ether_shost;
printf("%02x:%02x:%02x:%02x:%02x:%02x:\n", *mac_string, *(mac_string + 1), *(mac_string + 2), *(mac_string + 3), *(mac_string + 4), *(mac_string + 5));
//获得Mac目的地址
printf("Mac目的地址:\t");
mac_string = ethernet_protocol->ether_dhost;
printf("%02x:%02x:%02x:%02x:%02x:%02x:\n", *mac_string, *(mac_string + 1), *(mac_string + 2), *(mac_string + 3), *(mac_string + 4), *(mac_string + 5));
switch (ethernet_type)
{
case 0x0800:
/*如果上层是IPv4ip协议,就调用分析ip协议的函数对ip包进行贩治*/
ip_protocol_packet_callback(argument, packet_header, packet_content);
break;
default:break;
}
packet_number++;
}
main()
{
pcap_if_t* alldevs;
pcap_if_t* d;
int inum = 0;
int i = 0;
pcap_t* adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
// 获得网卡的列表
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->description)
printf(" (%s)\n", d->description);
else
printf(" (No description available)\n");
}
if (i == 0)
{
printf("\n没有发现接口\n");
return -1;
}
printf("\n输入要选择打开的网卡号 (1-%d):\t", i);
scanf("%d", &inum); //输入要选择打开的网卡号
if (inum < 1 || inum > i) //判断号的合法性
{
printf("\n网卡号超出范围.\n");
//释放设备列表
pcap_freealldevs(alldevs);
return -1;
}
// 找到要选择的网卡结构
for (d = alldevs, i = 0; i < inum - 1; d = d->next, i++);
// 打开选择的网卡
if ((adhandle = pcap_open_live(d->name, //设备名称
65536, //最大值.65536允许整个包在所有mac电脑上被捕获.
1, /* 混杂模式 混杂模式是指一台主机能够接受所有经过它的数据流,不论这个数据流的目的地址是不是它,它都会接受这个数据包。
也就是说,混杂模式下,网卡会把所有的发往它的包全部都接收。在这种情况下,可以接收同一集线器局域网的所有数据。*/
1000, // 读超时为1秒
errbuf // error buffer
)) == NULL)
{
fprintf(stderr, "\n无法打开适配器\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
printf("\n正在监听中 %s...\n", d->description);
//释放设备列表
pcap_freealldevs(alldevs);
int cnt = -1;
cout << "\n将要捕获数据包的个数:\t\t";
cin >> cnt;
/* 开始以回调的方式捕获包
函数名称:int pcap_loop(pcap_t * p,int cnt, pcap_handler callback, uchar * user);
函数功能:捕获数据包,不会响应pcap_open_live()函数设置的超时时间
*/
pcap_loop(adhandle, cnt, ethernet_protocol_packet_callback, NULL);
cout << "\n\t解析IP数据包结束\n";
return 0;
}
实验截图:
实验中利用winpack编程获取和分析接收到的内容,利用wireshark验证分析内容是否正确。
[实验总结]
WinPcap 包括三个部分:
第一个模块NPF(Netgroup Packet Filter),是一个虚拟设备驱动程序文件。它的功能是过滤数据包,并把这些数据包原封不动地传给用户态模块,这个过程中包括了一些操作系统特有的代码。
第二个模块packet.dll为win32平台提供了一个公共的接口。不同版本的Windows系统都有自己的内核模块和用户层模块。Packet.dll用于解决这些不同。调用Packet.dll的程序可以运行在不同版本的Windows平台上,而无需重新编译。
第三个模块 Wpcap.dll是不依赖于操作系统的。它提供了更加高层、抽象的函数。packet.dll和Wpcap.dll:packet.dll直接映射了内核的调用。 Wpcap.dll提供了更加友好、功能更加强大的函数调用。WinPcap的优势提供了一套标准的抓包接口,与libpcap兼容,可使得原来许多UNIX平台下的网络分析工具快速移植过来便于开发各种网络分析工具,充分考虑了各种性能和效率的优化,包括对于NPF内核层次上的过滤器支持,支持内核态的统计模式,提供了发送数据包的能力。
在这次实验中,安装了winpcap开发包,用pcap.h头文件,和WinPcap提供的各种函数和数据结构。编写了WinPcap捕获网络数据包程序,成功地识别了网络设备和其信息(网卡)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧