IP数据报捕获与分析(利用NPcap编程捕获数据包)

目录

一、实验内容说明

二、实验准备

1、了解NPcap的架构

2、学习NPcap的设备列表获取方法、网卡设备打开方法,以及数据包捕获方法。

(1) 设备列表获取方法

(2) 网卡设备打开方法

(3) 数据包捕获方法

3、环境配置

(1) 实验环境

(2) 添加pcap.h包含文件

(3) 添加包含文件目录

(4) 添加库文件目录

(5) 添加链接时使用的库文件

三、实验过程

1、程序设计流程图

2、获取网络设备接口列表

(1) 判断是否能成功获取设备接口列表

(2) 打印本机接口列表函数

3、选择网络设备接口

(1) 判断是否存在该序号接口

(2) 判断是否能打开该接口

4、打印捕获到的数据包

(1) 创建线程开始抓包

(2) 帧首部处理函数

(3) IP协议首部处理函数

5、程序源码

四、程序运行截图

1、打印设备列表

2、选择设备并捕获网络数据包

(1) 输入错误的接口序号

(2) 输入正确的接口序号


一、实验内容说明

IP数据报捕获与分析编程实验,要求如下:

1、了解NPcap的架构。

2、学习NPcap的设备列表获取方法、网卡设备打开方法,以及数据包捕获方法。

3、通过NPcap编程,实现本机的IP数据报捕获,显示捕获数据帧的源MAC地址和目的MAC地址,以及类型/长度字段的值。

4、捕获的数据报不要求硬盘存储,但应以简单明了的方式在屏幕上显示。必显字段包括源MAC地址、目的MAC地址和类型/长度字段的值。

5、编写的程序应结构清晰,具有较好的可读性。

二、实验准备

1、了解NPcap的架构

摘自 NMap官网 Download the Free Nmap Security Scanner for Linux/Mac/Windows

Npcap 是 Nmap 项目的 Microsoft Windows 数据包捕获(和发送)库。它使用自定义的 Windows 内核驱动程序以及我们的优秀 libpcap 库的 Windows 构建来实现开放的 Pcap API。这允许 Windows 软件使用简单、可移植的 API 捕获原始网络流量(包括无线网络、有线以太网、本地主机流量和许多 VPN)。 Npcap 也允许发送原始数据包。 Mac 和 Linux 系统已经包含 Pcap API,因此 Npcap 允许流行的软件(如 Nmap 和 Wireshark)使用单个代码库在所有这些平台(以及更多平台)上运行。 Npcap 始于 2013 年,当时是对(现已停产的)WinPcap 库的一些改进,但此后经过大量重写,发布了数百个版本,提高了 Npcap 的速度、可移植性、安全性和效率。特别是,Npcap 现在提供:

  • 环回数据包捕获和注入:Npcap 能够通过使用 Windows 过滤平台 (WFP) 来嗅探环回数据包(同一台机器上的服务之间的传输)。安装后,Npcap 提供了一个名为 NPF_Loopback 的接口,描述为“Adapter for loopback capture”。 Wireshark 用户可以选择此适配器以与其他非环回适配器相同的方式捕获所有环回流量。数据包注入也适用于 pcap_inject() 函数。

  • 支持所有当前的 Windows 版本:Npcap 支持 Microsoft 自己仍然支持的所有版本的 Windows 和 Windows Server。为避免将自己局限于受支持的最旧 Windows 版本的功能和 API,我们为每个主要平台生成并发布驱动程序。这样我们就可以在我们的 Win10 驱动器中使用 Microsoft 的所有最新技术,同时仍然支持旧系统。 Npcap 通过使用 NDIS 6 轻型过滤器 (LWF) API 在 Windows 7 及更高版本上运行。它比 WinPcap 使用的已弃用的 NDIS 5 API 更快。此外,该驱动程序使用我们的 EV 证书进行签名并由 Microsoft 会签,因此即使在 Windows 10 施加的更严格的驱动程序签名要求下也能正常工作。我们不知道 Microsoft 何时会删除 NDIS 5 或停止继承较旧的不太安全的版本驱动程序签名,但发生这种情况时 WinPcap 将停止工作。

  • Libpcap API:Npcap 使用优秀的 Libpcap 库,使 Windows 应用程序能够使用 Linux 和 MacOS 也支持的便携式数据包捕获 API。虽然 WinPcap 基于 2009 年的 LibPcap 1.0.0,但 Npcap 包含最新的 Libpcap 版本以及我们为上游贡献的所有改进。

  • 支持所有 Windows 架构(x86、x86-64 和 ARM):Npcap 一直支持 Windows 64 位和 32 位 Intel x86 平台。但是从 1.50 版开始,我们也支持新的 Windows on ARM 架构!这允许 PC 使用与智能手机相同的节能移动芯片组,以实现全天电池寿命和始终在线的 LTE 连接。用户现在可以在微软 Surface Pro X 平板电脑和三星 Galaxy Book Go 笔记本电脑等新一代设备上运行 Nmap 和 Wireshark 等应用程序。

  • 额外的安全性:可以(可选)限制 Npcap,以便只有管理员可以嗅探数据包。如果非管理员用户尝试通过 Nmap 或 Wireshark 等软件使用 Npcap,则用户必须通过用户帐户控制 (UAC) 对话框才能使用驱动程序。这在概念上类似于 UNIX,其中通常需要 root 访问权限来捕获数据包。我们还启用了 Windows ASLR 和 DEP 安全功能,并对驱动程序、DLL 和可执行文件进行了签名以防止篡改。

  • WinPcap 兼容性:为 WinPcap 编写的软件通常与 WinPcap 的源代码兼容,因此只需使用 Npcap SDK 重新编译即可获得 Npcap 的所有性能、兼容性和安全优势。事实上,甚至还有一些二进制兼容性——使用 WinPcap SDK 编译的软件通常仍然适用于现代 Npcap。然而,我们不建议依赖它,因为自 2013 年上一次 WinPcap SDK 发布以来,编译器和其他堆栈技术发生了巨大变化。在将旧版 WinPcap 软件移植到 Npcap 时,我们建议进行一些小的更改,主要是为了确保您的软件在安装了这两个库的系统上,使用 Npcap 优先于 WinPcap。默认情况下,Npcap 会用自己的驱动程序替换任何旧的 WinPcap 软件安装,但您可以通过取消选中 Npcap 的“WinPcap 兼容模式”来安装两者。安装程序选项。

  • 原始(监控模式)802.11 无线捕获:Npcap 可配置为读取原始 802.11 流量,包括 radiotap 标头详细信息,Wireshark 直接支持此功能。

2、学习NPcap的设备列表获取方法、网卡设备打开方法,以及数据包捕获方法。

(1) 设备列表获取方法

int pcap_findalldevs_ex(
		char *source,
		struct	pcap_rmtauth auth,
		pcap_if_t **alldevs,
		char *errbuf
);

Typedef struct pcap_if pcap_if_t;
struct pcap_if {
		struct pcap_if *next; 
		char *name;     
		char *description;  
		struct pcap_addr *addresses; 
		u_int flags;        
};

struct pcap_addr {
		struct pcap_addr *next; 
		struct sockaddr *addr;      
		struct sockaddr *netmask;   
		struct sockaddr *broadaddr; 
		struct sockaddr *dstaddr;   
};

pcap_if_t	*alldevs; 	               //指向设备链表首部的指针
pcap_if_t	*d; 
pcap_addr_t	*a; 
char		errbuf[PCAP_ERRBUF_SIZE];	//错误信息缓冲区
//获得本机的设备列表
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, 	//获取本机的接口设备
		NULL,			       //无需认证
		&alldevs, 		       //指向设备列表首部
		errbuf			      //出错信息保存缓存区
		) == -1)
{	……	//错误处理      }
    
for(d= alldevs; d != NULL; d= d->next)      //显示接口列表
{
	……	//利用d->name获取该网络接口设备的名字
	……	//利用d->description获取该网络接口设备的描述信息
	//获取该网络接口设备的IP地址信息
	for(a=d->addresses; a!=NULL; a=addr->next)
		if (a->addr->sa_family==AF_INET)  //判断该地址是否IP地址
		{
			……	//利用a->addr获取IP地址
			……	//利用a->netmask获取网络掩码
			……	//利用a->broadaddr获取广播地址
			……	//利用a->dstaddr)获取目的地址
		}
}
 
//释放设备列表
pcap_freealldevs(alldevs); 

(2) 网卡设备打开方法

pcap_t* pcap_open(
    const char *source,
    int snaplen,
    int flags,
    int read_timeout,
    struct pcap_rmtauth *auth,
    char *errbuf
};

(3) 数据包捕获方法

//利用回调函数捕获
pcap_dispatch():read_timeout到时返回
pcap_loop():捕获到cnt个数据包后返回
//直接捕获
pcap_next_ex():read_timeout到时返回

3、环境配置

(1) 实验环境

C++、Visual Studio 2022

(2) 添加pcap.h包含文件

所有使用Npcap函数的源文件中都需添加pcap.h包含文件:#include "pcap.h

(3) 添加包含文件目录

项目 - 属性 - 配置属性 - C/C++ - 常规 - 附加包含目录

(4) 添加库文件目录

项目 - 属性 - 配置属性 - 连接器 - 常规 - 附加库目录

(5) 添加链接时使用的库文件

三、实验过程

通过NPcap编程,实现本机的IP数据报捕获,显示捕获数据帧的源MAC地址和目的MAC地址,以及类型/长度字段的值。

在本次实验中,希望程序可以打印出的信息包括:

打印出的接口列表信息包括:

接口序号、接口设备名称、接口描述信息、网络接口是否是虚拟接口、网络接口设备的IP地址信息(包括地址族、该地址是否是ip地址、网络掩码、广播地址、目的地址)等。

打印的出的数据包的信息包括:

  • 该包是抓到的第几个数据包、抓包的时间、抓到的包的总长度、数据包的具体信息。
  • 该包的以太网协议的内容,包括以太网类型、上层协议、MAC帧源地址、MAC帧目的地址等。
  • 如果该包的上层协议是IP协议,则继续打印IP协议的具体内容,包括版本号、首部长度、服务类型、总长度、标识、偏移、生存时间、协议类型、上层协议、检验和、源IP地址、目的IP地址等。

1、程序设计流程图

2、获取网络设备接口列表

(1) 判断是否能成功获取设备接口列表

如果无法成功获得网络设备接口列表,则异常退出程序。如果可以则调用 ifprint() 函数打印网络设备接口列表

//取得列表
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,//获取本机的接口设备
	NULL,//无需认证
	&alldevs,//指向设备列表首部
	errbuf//出错信息保存缓冲区
) == -1) 
{//如果无法获取设备本机接口
	printf("Error in pcap_findalldevs_ex!%s\n");//错误处理
	return -1;
}

//输出列表
for (d = alldevs; d != NULL; d = d->next)
{
	ifprint(d);//输出设备接口列表的函数
}
if (i == 0)
{
	printf("\nNo interfaces found!\n");
	return -1;
}

(2) 打印本机接口列表函数

打印接口列表的函数为 ifprint() 。打印网络设备接口列表的信息包括:接口序号、接口设备名称、接口描述信息、网络接口是否是虚拟接口、网络接口设备的IP地址信息(IP地址信息包括地址族、该地址是否是ip地址、网络掩码、广播地址、目的地址)等。

在打印 IP地址、网络掩码、广播地址、目的地址 时,为了将获取到的信息转化为 0.0.0.0 的ip地址格式,需要调用 iptos() 函数。

/*IP格式转化函数*/
char* iptos(u_long in)
{
	static char output[IPTOSBUFFERS][3 * 4 + 3 + 1];
	static short which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
	u_char* p = (u_char*)∈
	sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
	return output[which];
}

/*打印本机接口列表*/
void ifprint(pcap_if_t* d)
{
	pcap_addr_t* a;//表示接口地址指针
	printf("%d.%s\n", ++i, d->name);//打印网络接口设备的名字
	if (d->description)
	{
		printf("\tDescription:(%s)\n", d->description);//打印描述信息
	}
	else {
		printf("\t(No description available)\n");
	}
	printf("\tLoopback:%s\n", (d->flags & PCAP_IF_LOOPBACK) ? "yes" : "no");//是否是虚拟接口

	//获取该网络接口设备的IP地址信息
	for (a = d->addresses; a != NULL; a = a->next)
	{
		printf("\tAddress Family:#%d\n", a->addr->sa_family);
		switch (a->addr->sa_family)
		{
		case AF_INET://判断该地址是否IP地址
			printf("\tAddress Family Name:AF_INET\n");
			if (a->addr)//ip地址
			{
				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("\tAddressFamilyName:Unknown\n");
			break;
		}
	}
}

3、选择网络设备接口

(1) 判断是否存在该序号接口

如果不存在该序号的接口,则可以选择重新输入新的序号或者退出程序。

LOOP:
	printf("\nEnter the interface number (1-%d):", i);//首先输入想要选择的网络接口的序号
	scanf("%d", &num);
	if (num <1 || num >i)//如果该序号不合法
	{
		printf("\nInterface number out of range.\n");
		printf("Continue or Quit [c/q]:");//选择是退出程序还是重新输入序号
		char a;
		cin >> a;
		if (a == 'c' || a == 'C') 
        {
			goto LOOP;//重新输入序号
		}
		else if (a == 'q' || a == 'Q') 
        {
			pcap_freealldevs(alldevs);//释放接口列表,退出程序
			return 0;
		}
	}

(2) 判断是否能打开该接口

如果存在该序号的接口但接口设备无法打开,则可以选择重新输入新的序号或者退出程序。

//输入合法的序号之后,尝试打开网络接口设备
for (d = alldevs, i = 0; i < num - 1; d = d->next, i++);//转到选择的设备
if ((adhandle = pcap_open_live(d->name, 65536, 1, 1000, errbuf)) == NULL)//打开失败
{
	fprintf(stderr, "\nUnable to open the adapter.\n");
	printf("Continue or Quit [c/q]:");//选择是退出程序还是重新输入序号
	char a;
	cin >> a;
	if (a == 'c' || a == 'C') 
    {
		goto LOOP;//重新输入序号
	}
	else if (a == 'q' || a == 'Q') 
    {
		pcap_freealldevs(alldevs);//释放接口列表,退出程序
		return 0;
	}
}

4、打印捕获到的数据包

(1) 创建线程开始抓包

如果存在该序号的接口,且接口可以成功打开,那么就可以释放网络接口列表并创建线程开始进行数据包的捕获。

//打开成功
printf("\nlistening on %s...\n\n", d->description);
//释放列表
pcap_freealldevs(alldevs);
//开始抓包,创建线程
HANDLE m_capturer;
struct pcap_pkthdr* packet_header = new pcap_pkthdr;
const u_char* packet_data = new u_char;
parame* m_pam = new parame;
m_pam->adhandle = adhandle;
m_pam->packet_header = packet_header;
m_pam->packet_data = packet_data;
m_pam->num = num;
m_capturer = CreateThread(NULL, NULL, &Capturer, (PVOID*)m_pam, 0, NULL);

此处能够打印出的信息包括:该包是抓到的第几个数据包、抓包的时间、抓到的包的总长度、数据包的具体信息。

获取到数据包并成功打印出以上信息后,可以调用 ethernet_protocol_packet_callback 函数对数据包的以太网协议进行分析。

/*线程参数结构体*/
struct parame
{
	pcap_t* adhandle;
	struct pcap_pkthdr* packet_header;
	const u_char* packet_data;
	int num;
};

/*抓包线程*/
DWORD WINAPI Capturer(PVOID hWnd)
{
	int res;
	int packet_number = 0;
	struct tm* ltime;
	time_t local_tv_sec;
	char timestr[16];

	//将传入线程中的参数携带的数据取出
	parame* Packet = (parame*)hWnd;
	pcap_t* adhandle = Packet->adhandle;
	struct pcap_pkthdr* packet_header = Packet->packet_header;
	const u_char* packet_data = Packet->packet_data;
	int num = Packet->num;

	while ((res = pcap_next_ex(adhandle, &packet_header, &packet_data)) >= 0)
	{
		//接收数据包超时
		if (res == 0)
		{
			continue;
		}

		printf("=========================================================================\n");
		packet_number++;

		//将时间戳转化为可识别格式
		local_tv_sec = packet_header->ts.tv_sec;
		ltime = localtime(&local_tv_sec);
		strftime(timestr, sizeof(timestr), "%H:%M:%S", ltime);
		
		printf("捕获第%d个网络数据包\n", packet_number);;
		printf("捕获时间:%s\n", timestr);
		printf("数据包长度:%d\n", packet_header->len);
		printf("---------数据包内容------------------------------------------------------\n");

		//输出数据包的具体内容
		char temp[LINE_LEN + 1];
		for (int i = 0; i < packet_header->caplen; ++i)
		{
			printf("%.2x ", packet_data[i]);
			if (isgraph(packet_data[i]) || packet_data[i] == ' ')
				temp[i % LINE_LEN] = packet_data[i];
			else
				temp[i % LINE_LEN] = '.';

			if (i % LINE_LEN == 15)
			{
				temp[16] = '\0';
				printf("        ");
				printf("%s", temp);
				printf("\n");
				memset(temp, 0, LINE_LEN);
			}
		}
		printf("\n");

		//分析数据包
		ethernet_protocol_packet_callback(packet_header, packet_data);
		printf("=========================================================================\n");

		//停止数据包捕捉
		if (_kbhit())
		{
			int ch = _getch();//使用_getch()函数获取按下的键值
			if (ch == 27)//当按下ESC时退出
			{
				cout << "\nThe Capturer has been closed..." << endl;
				return 0;
			}
		}

		//每1秒抓一次
		Sleep(1000);
	}
}

(2) 帧首部处理函数

此处对数据包的以太网协议进行分析,能够打印出的信息包括:以太网类型、上层协议、MAC帧源地址、MAC帧目的地址。

成功打印出以上信息后,如果以太网的上层协议为IP协议,可以调用 ip_protocol_packet_callback 函数对数据包的IP协议进行分析。

/*帧首部结构体*/
struct FrameHeader_t {
	byte DesMAC[6]; //目的Mac地址
	byte SrcMAC[6]; //源Mac地址
	WORD FrameType; //协议(帧)类型
};

/*帧首部处理函数*/
void ethernet_protocol_packet_callback(const struct pcap_pkthdr* packet_header, const u_char* packet_data)
{
	struct FrameHeader_t* frameHeader;
	frameHeader = (struct FrameHeader_t*)packet_data;//获得数据包内容
	printf("---------以太网协议------------------------------------------------------\n");
	printf("以太网类型:0x%04x\n", ntohs(frameHeader->FrameType));
	switch (ntohs(frameHeader->FrameType))
	{
	case 0x0800: printf("上层协议:IP协议\n"); break;
	case 0x0806: printf("上层协议:ARP协议\n"); break;
	case 0x8035: printf("上层协议:RARP协议\n"); break;
	default:printf("上层协议:Unknown\n"); break;
	}

	u_char* macS = frameHeader->SrcMAC;
	u_char* macD = frameHeader->DesMAC;
	printf("MAC帧源地址:%02x:%02x:%02x:%02x:%02x:%02x\n", *macS, *(macS + 1), *(macS + 2), *(macS + 3), *(macS + 4), *(macS + 5));
	printf("MAC帧目的地址:%02x:%02x:%02x:%02x:%02x:%02x\n", *macD, *(macD + 1), *(macD + 2), *(macD + 3), *(macD + 4), *(macD + 5));

	if (ntohs(frameHeader->FrameType) == 0x0800)//继续分析IP协议
	{
		ip_protocol_packet_callback(packet_header, packet_data);
	}
}

(3) IP协议首部处理函数

此处对数据包的IP协议进行分析,能够打印出的信息包括:IP协议的版本号、首部长度、服务类型、总长度、标识、偏移、生存时间、协议类型、上层协议、检验和、源IP地址、目的IP地址。

/*IP首部*/
struct IPHeader_t {
#if defined(WORDS_BIENDIAN)
	byte Version : 4, HeaderLength : 4;
#else
	byte HeaderLength : 4, Version : 4;
#endif
	byte TOS;//服务类型
	WORD TotalLen; //总长度
	WORD ID;//标识
	WORD Flag_Segment;//标志字段
	byte TTL;//生存时间值
	byte Protocol;//协议类型,是TCP/IP体系中的网络层协议
	WORD Checksum;//检验和
	struct in_addr SrcIP;//源IP
	struct in_addr DstIP;//目的IP
};

/*IP协议首部处理函数*/
void ip_protocol_packet_callback(const struct pcap_pkthdr* packet_header, const u_char* packet_data)
{
	struct IPHeader_t* IPHeader;
	IPHeader = (struct IPHeader_t*)(packet_data + 14);//MAC首部是14位的,加上14位得到IP协议首部
	printf("---------IP协议----------------------------------------------------------\n");
	printf("版本号:%d\n", IPHeader->Version);
	printf("首部长度:%d\n", IPHeader->HeaderLength);
	printf("服务类型:%d\n", IPHeader->TOS);
	printf("总长度:%d\n", ntohs(IPHeader->TotalLen));//ntohs()函数用于大小端转换
	printf("标识:%d\n", ntohs(IPHeader->ID));
	printf("偏移:%d\n", (ntohs(IPHeader->Flag_Segment) & 0x1fff) * 8);
	printf("生存时间:%d\n", IPHeader->TTL);
	printf("协议类型:%d\n", IPHeader->Protocol);
	switch (IPHeader->Protocol)
	{
	case 1: printf("上层协议:ICMP协议\n"); break;
	case 2: printf("上层协议:IGMP协议\n"); break;
	case 6: printf("上层协议:TCP协议\n"); break;
	case 17: printf("上层协议:UDP协议\n"); break;
	default:break;
	}
	printf("检验和:%d\n", ntohs(IPHeader->Checksum));
	printf("源IP地址:%s\n", inet_ntoa(IPHeader->SrcIP));
	printf("目的地址:%s\n", inet_ntoa(IPHeader->DstIP));
}

5、程序源码

程序运行的主要流程为:

main()ifprint()Capturer()ethernet_protocol_packet_callbackip_protocol_packet_callback

#include<iostream>
#include<winsock2.h>
#include <conio.h>
#include "pcap.h"
#pragma comment(lib,"wpcap.lib")
#pragma comment(lib,"packet.lib")
#pragma comment(lib,"ws2_32.lib")
#pragma warning(disable:4996)
#define LINE_LEN 16
#define IPTOSBUFFERS 12
using namespace std;
int i = 0;

/*帧首部*/
struct FrameHeader_t {
	byte DesMAC[6]; //目的Mac地址
	byte SrcMAC[6]; //源Mac地址
	WORD FrameType; //协议(帧)类型
};

/*IP首部*/
struct IPHeader_t {
#if defined(WORDS_BIENDIAN)
	byte Version : 4, HeaderLength : 4;
#else
	byte HeaderLength : 4, Version : 4;
#endif
	byte TOS;//服务类型
	WORD TotalLen; //总长度
	WORD ID;//标识
	WORD Flag_Segment;//标志字段
	byte TTL;//生存时间值
	byte Protocol;//协议类型,是TCP/IP体系中的网络层协议
	WORD Checksum;//检验和
	struct in_addr SrcIP;//源IP
	struct in_addr DstIP;//目的IP
};

/*IP协议首部处理函数*/
void ip_protocol_packet_callback(const struct pcap_pkthdr* packet_header, const u_char* packet_data)
{
	struct IPHeader_t* IPHeader;
	IPHeader = (struct IPHeader_t*)(packet_data + 14);//MAC首部是14位的,加上14位得到IP协议首部
	printf("---------IP协议----------------------------------------------------------\n");
	printf("版本号:%d\n", IPHeader->Version);
	printf("首部长度:%d\n", IPHeader->HeaderLength);
	printf("服务类型:%d\n", IPHeader->TOS);
	printf("总长度:%d\n", ntohs(IPHeader->TotalLen));
	printf("标识:%d\n", ntohs(IPHeader->ID));
	printf("偏移:%d\n", (ntohs(IPHeader->Flag_Segment) & 0x1fff) * 8);
	printf("生存时间:%d\n", IPHeader->TTL);
	printf("协议类型:%d\n", IPHeader->Protocol);
	switch (IPHeader->Protocol)
	{
	case 1: printf("上层协议:ICMP协议\n"); break;
	case 2: printf("上层协议:IGMP协议\n"); break;
	case 6: printf("上层协议:TCP协议\n"); break;
	case 17: printf("上层协议:UDP协议\n"); break;
	default:printf("上层协议:Unknown\n"); break;
	}
	printf("检验和:%d\n", ntohs(IPHeader->Checksum));
	printf("源IP地址:%s\n", inet_ntoa(IPHeader->SrcIP));
	printf("目的地址:%s\n", inet_ntoa(IPHeader->DstIP));
}

/*帧首部处理函数*/
void ethernet_protocol_packet_callback(const struct pcap_pkthdr* packet_header, const u_char* packet_data)
{
	struct FrameHeader_t* frameHeader;
	frameHeader = (struct FrameHeader_t*)packet_data;//获得数据包内容
	printf("---------以太网协议------------------------------------------------------\n");
	printf("以太网类型:0x%04x\n", ntohs(frameHeader->FrameType));
	switch (ntohs(frameHeader->FrameType))
	{
	case 0x0800: printf("上层协议:IP协议\n"); break;
	case 0x0806: printf("上层协议:ARP协议\n"); break;
	case 0x8035: printf("上层协议:RARP协议\n"); break;
	default:printf("上层协议:Unknown\n"); break;
	}

	u_char* macS = frameHeader->SrcMAC;
	u_char* macD = frameHeader->DesMAC;
	printf("MAC帧源地址:%02x:%02x:%02x:%02x:%02x:%02x\n", *macS, *(macS + 1), *(macS + 2), *(macS + 3), *(macS + 4), *(macS + 5));
	printf("MAC帧目的地址:%02x:%02x:%02x:%02x:%02x:%02x\n", *macD, *(macD + 1), *(macD + 2), *(macD + 3), *(macD + 4), *(macD + 5));

	if (ntohs(frameHeader->FrameType) == 0x0800)//继续分析IP协议
	{
		ip_protocol_packet_callback(packet_header, packet_data);
	}
}

/*线程参数结构体*/
struct parame
{
	pcap_t* adhandle;
	struct pcap_pkthdr* packet_header;
	const u_char* packet_data;
	int num;
};

/*抓包线程*/
DWORD WINAPI Capturer(PVOID hWnd)
{
	int res;
	int packet_number = 0;
	struct tm* ltime;
	time_t local_tv_sec;
	char timestr[16];

	//将传入线程中的参数携带的数据取出
	parame* Packet = (parame*)hWnd;
	pcap_t* adhandle = Packet->adhandle;
	struct pcap_pkthdr* packet_header = Packet->packet_header;
	const u_char* packet_data = Packet->packet_data;
	int num = Packet->num;

	while ((res = pcap_next_ex(adhandle, &packet_header, &packet_data)) >= 0)
	{
		//接收数据包超时
		if (res == 0)
		{
			continue;
		}

		printf("=========================================================================\n");
		packet_number++;

		//将时间戳转化为可识别格式
		local_tv_sec = packet_header->ts.tv_sec;
		ltime = localtime(&local_tv_sec);
		strftime(timestr, sizeof(timestr), "%H:%M:%S", ltime);
		
		printf("捕获第%d个网络数据包\n", packet_number);;
		printf("捕获时间:%s\n", timestr);
		printf("数据包长度:%d\n", packet_header->len);
		printf("---------数据包内容------------------------------------------------------\n");

		//输出数据包的具体内容
		char temp[LINE_LEN + 1];
		for (int i = 0; i < packet_header->caplen; ++i)
		{
			printf("%.2x ", packet_data[i]);
			if (isgraph(packet_data[i]) || packet_data[i] == ' ')
				temp[i % LINE_LEN] = packet_data[i];
			else
				temp[i % LINE_LEN] = '.';

			if (i % LINE_LEN == 15)
			{
				temp[16] = '\0';
				printf("        ");
				printf("%s", temp);
				printf("\n");
				memset(temp, 0, LINE_LEN);
			}
		}
		printf("\n");

		//分析数据包
		ethernet_protocol_packet_callback(packet_header, packet_data);
		printf("=========================================================================\n");

		//停止数据包捕捉
		if (_kbhit())
		{
			int ch = _getch();//使用_getch()函数获取按下的键值
			if (ch == 27)//当按下ESC时退出
			{
				cout << "\nThe Capturer has been closed..." << endl;
				return 0;
			}
		}

		//每1秒抓一次
		Sleep(1000);
	}
}

/*IP格式转化函数*/
char* iptos(u_long in)
{
	static char output[IPTOSBUFFERS][3 * 4 + 3 + 1];
	static short which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
	u_char* p = (u_char*)&in;
	sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
	return output[which];
}

/*打印本机接口列表*/
void ifprint(pcap_if_t* d)
{
	pcap_addr_t* a;//表示接口地址指针
	printf("%d.%s\n", ++i, d->name);//打印网络接口设备的名字
	if (d->description)
	{
		printf("\tDescription:(%s)\n", d->description);//打印描述信息
	}
	else {
		printf("\t(No description available)\n");
	}
	printf("\tLoopback:%s\n", (d->flags & PCAP_IF_LOOPBACK) ? "yes" : "no");//是否是虚拟接口

	//获取该网络接口设备的IP地址信息
	for (a = d->addresses; a != NULL; a = a->next)
	{
		printf("\tAddress Family:#%d\n", a->addr->sa_family);
		switch (a->addr->sa_family)
		{
		case AF_INET://判断该地址是否IP地址
			printf("\tAddress Family Name:AF_INET\n");
			if (a->addr)//ip地址
			{
				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("\tAddressFamilyName:Unknown\n");
			break;
		}
	}
}

int main()
{
	pcap_if_t* alldevs;//指向设备链表首部的指针
	pcap_if_t* d;
	int num;
	pcap_t* adhandle;//定义打开设备的返回值
	char errbuf[PCAP_ERRBUF_SIZE];//错误信息缓冲区

	//取得列表
	if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,//获取本机的接口设备
		NULL,//无需认证
		&alldevs,//指向设备列表首部
		errbuf//出错信息保存缓冲区
	) == -1) 
	{
		printf("Error in pcap_findalldevs_ex!%s\n");//错误处理
		return -1;
	}

	//输出列表
	for (d = alldevs; d != NULL; d = d->next)
	{
		ifprint(d);
	}
	if (i == 0)
	{
		printf("\nNo interfaces found!\n");
		return -1;
	}

LOOP:
	printf("\nEnter the interface number (1-%d):", i);
	scanf("%d", &num);
	if (num <1 || num >i)
	{
		printf("\nInterface number out of range.\n");
		printf("Continue or Quit [c/q]:");
		char a;
		cin >> a;
		if (a == 'c' || a == 'C') 
		{
			goto LOOP;
		}
		else if (a == 'q' || a == 'Q') 
		{
			pcap_freealldevs(alldevs);
			return 0;
		}
	}

	//转到选择的设备
	for (d = alldevs, i = 0; i < num - 1; d = d->next, i++);
	//打开失败
	if ((adhandle = pcap_open_live(d->name, 65536, 1, 1000, errbuf)) == NULL)
	{
		fprintf(stderr, "\nUnable to open the adapter.\n");
		printf("Continue or Quit [c/q]:");
		char a;
		cin >> a;
		if (a == 'c' || a == 'C') 
		{
			goto LOOP;
		}
		else if (a == 'q' || a == 'Q') 
		{
			pcap_freealldevs(alldevs);
			return 0;
		}
	}
	//打开成功
	printf("\nlistening on %s...\n\n", d->description);
	//释放列表
	pcap_freealldevs(alldevs);

	//开始抓包,创建线程
	HANDLE m_capturer;
	struct pcap_pkthdr* packet_header = new pcap_pkthdr;
	const u_char* packet_data = new u_char;
	parame* m_pam = new parame;
	m_pam->adhandle = adhandle;
	m_pam->packet_header = packet_header;
	m_pam->packet_data = packet_data;
	m_pam->num = num;
	m_capturer = CreateThread(NULL, NULL, &Capturer, (PVOID*)m_pam, 0, NULL);
	CloseHandle(m_capturer);
	while (1);
	return 0;
}

四、程序运行截图

1、打印设备列表

2、选择设备并捕获网络数据包

(1) 输入错误的接口序号

(2) 输入正确的接口序号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值