简介:这份资源是华中科技大学2018年计算机网络安全课程的实验源码,面向正在学习网络安全、网络编程与协议分析的高校学生及自学者,用于完成基于Winpcap的数据包嗅探与ARP攻击实验。压缩包共27个文件,约293KB,以C++源码为主体,包含7个cpp与8个h文件,另有2个ui界面文件、1个pro工程文件和1个qrc资源文件,配合5张png与2张jpg截图,整体结构清晰,便于直接编译运行与二次修改。实验内容覆盖数据包抓取与协议解析、ARP欺骗实现中间人攻击等核心环节,读者可从中理解抓包线程、发包线程与ARP攻击线程的协作机制,掌握协议头解析与界面交互的实现思路。目前已有2814人学习下载,适合作为课程实验参考、网络安全入门练手项目或协议分析工具的开发模板。
1. 从抓包到解析:一份能跑通的网络安全实验源码长什么样
很多人第一次接触网络安全实验,卡住的地方不是协议本身,而是环境。老师布置一个“基于 WinPcap 实现数据包分析器”的任务,听起来无非就是抓包、拆包、打印,但真坐到电脑前,第一步装库就能劝退一半人。这份实验源码解决的正是这个断层:它把 WinPcap 的调用、网卡枚举、抓包回调、协议解析、统计输出串成一条完整链路,让你不用从零去啃文档,直接能看到一个能跑起来的最小嗅探器。适合两类人:一是正在做同类课程实验、需要一份可参照实现的学生;二是想理解“抓包工具底层到底在干什么”的开发者。它不教你写一个 Wireshark,但能让你把“数据包从网卡到应用层”这条路走通一遍,知道每一层协议头长什么样、偏移量怎么算、哪些字段值得提取。下面我按实际复现顺序拆开讲,包括环境怎么配、代码怎么读、参数怎么调、哪里最容易翻车。
2. 环境搭建与 WinPcap 选型:为什么不是随便装个库就行
2.1 WinPcap 与 Npcap 的关系,以及这份源码为什么依赖前者
WinPcap 是 Windows 平台上最早的抓包驱动加库的组合,它提供了一套底层接口,让应用程序能绕过协议栈直接访问网卡收发的原始帧。后来 Win10 之后官方不再更新,社区出了 Npcap 作为替代,API 基本兼容,但安装选项和驱动签名策略变了。这份实验源码用的是 WinPcap 的头文件和库,典型文件是pcap.h、wpcap.lib、Packet.lib。如果你直接拿 Npcap 的 SDK 去编译,大部分函数能过,但个别结构体字段和链接库名字有差异,容易在链接阶段报LNK2019。常见做法是:先确认源码里#include <pcap.h>和#include <Packet.h>的路径,然后在项目属性里把包含目录指向 WinPcap 的Include,库目录指向Lib,链接器附加依赖项写wpcap.lib和Packet.lib。如果手头只有 Npcap,就把附加依赖项改成wpcap.lib和Packet.lib对应的 Npcap 版本,同时把#define WPCAP和#define HAVE_REMOTE保留,这两个宏决定了能不能用远程抓包和部分扩展函数。
提示:WinPcap 安装包在 Win10/Win11 上可能提示驱动不兼容,安装时勾选“兼容模式”或者直接用 Npcap 的“WinPcap API 兼容模式”安装,后者会同时提供
wpcap.dll,源码不用大改。
2.2 从零建一个能编译的工程:步骤与参数
我一般会新建一个空的 C++ 控制台工程,字符集选“使用多字节字符集”,因为源码里大量用了char*和printf,改成 Unicode 会多出一堆转换。然后把源码里的.c或.cpp文件加进来,再配三个地方:
# 以 Visual Studio 为例,在项目属性里设置 # 1. C/C++ -> 常规 -> 附加包含目录 C:\WinPcap\Include # 2. 链接器 -> 常规 -> 附加库目录 C:\WinPcap\Lib # 3. 链接器 -> 输入 -> 附加依赖项 wpcap.lib Packet.lib ws2_32.libws2_32.lib容易被漏掉,因为源码里可能用了ntohs、inet_ntoa这类函数,它们属于 Winsock 库。不链接这个库,报错信息是无法解析的外部符号 __imp__ntohs@4,新手看到这种名字往往不知道去哪找。参数上,包含目录和库目录必须指向你实际安装的路径,不要照抄网上的C:\WpdPack,那是旧版开发包的默认路径,现在很少见。如果编译通过但运行时报“找不到 wpcap.dll”,就把C:\Windows\System32\wpcap.dll确认存在,或者把 WinPcap 安装目录下的 dll 复制到 exe 同目录。
2.3 网卡枚举与设备选择:为什么你的程序抓不到包
源码里通常有一段pcap_findalldevs的调用,用来列出所有网卡,然后让用户输入编号选择。这里最常见的翻车是:程序列出了五六个设备,你选了第一个,结果一个包都抓不到。原因是 WinPcap 列出的设备里包含“拨号适配器”“虚拟网卡”“回环适配器”,真正在用的物理网卡可能排在后面。判断方法看设备名里的描述,带“Intel”“Realtek”“Ethernet”字样的通常是物理网卡,带“WAN Miniport”“VirtualBox”“VMware”的是虚拟的。选错设备不会报错,只是pcap_next_ex一直返回 0,看起来像程序卡死。另一个参数是pcap_open_live的snaplen,源码里一般设 65536,意思是抓完整帧;如果设成 96,就只能抓到帧头,解析 IP 选项和 TCP 载荷时会缺数据。promisc参数设 1 表示混杂模式,能抓到经过网卡但不是发给本机的包,实验里通常设 1,但某些无线网卡驱动不支持混杂模式,设了也无效,这时候只能抓本机流量。
3. 抓包主循环与回调函数:把数据包从网卡捞上来
3.1 pcap_loop 与 pcap_next_ex 的取舍
源码里抓包有两种写法:一种是pcap_loop(adhandle, 0, packet_handler, NULL),另一种是while ((res = pcap_next_ex(adhandle, &header, &pkt_data)) >= 0)。前者把控制权交给库,每来一个包就调一次回调函数,代码结构清晰;后者是主动轮询,适合需要在两次抓包之间做其他事情的场景。这份实验源码用的是回调方式,因为要统计包数量、按协议分类,回调里直接累加最方便。pcap_loop的第二个参数是抓包数量,0 表示无限抓,直到按 Ctrl+C。第三个参数是回调函数指针,签名固定为void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)。header里有ts时间戳、caplen实际抓到的长度、len原始帧长度,这两个长度在正常情况相等,如果 snaplen 设小了就会不等。pkt_data指向帧的起始位置,解析就从这里开始。
3.2 以太网帧头解析:14 字节里的偏移与类型判断
拿到pkt_data后,第一步是跳过以太网帧头。帧头固定 14 字节:6 字节目的 MAC、6 字节源 MAC、2 字节类型。类型字段用ntohs转成主机字节序后判断:0x0800 是 IPv4,0x0806 是 ARP,0x86DD 是 IPv6。源码里通常定义一个结构体:
// 以太网帧头结构,注意字段顺序和字节序 struct ethernet_header { u_char dest_mac[6]; // 目的 MAC u_char src_mac[6]; // 源 MAC u_short type; // 上层协议类型,网络字节序 };解析时用ethernet_header *eth = (ethernet_header*)pkt_data;,然后ntohs(eth->type)判断。这里有个坑:结构体直接强转依赖内存对齐,如果编译器默认按 4 字节对齐,type字段的偏移可能变成 12 而不是 12?实际上前两个字段各 6 字节,加起来 12,u_short在偏移 12 处正好是 2 字节对齐,所以没问题。但如果换成#pragma pack(1)之外的对齐设置,或者结构体里混了其他类型,偏移就会错。稳妥做法是用指针加法手动算:u_short type = ntohs(*(u_short*)(pkt_data + 12));,这样不依赖结构体布局。MAC 地址打印用%02x:%02x:...格式,源码里一般写个循环或宏。
3.3 IP 头与 TCP/UDP 头解析:可变长度字段怎么处理
跳过 14 字节后就是 IP 头。IP 头固定部分 20 字节,但有个ihl字段表示首部长度,单位是 4 字节,所以实际长度是ihl * 4。源码里常见错误是直接按 20 字节跳过,遇到带选项的 IP 包就解析错位。正确做法:
// 假设 pkt_data 已经指向 IP 头起始 u_char ip_header_len = (pkt_data[0] & 0x0f) * 4; // 低 4 位是首部长度 u_char protocol = pkt_data[9]; // 协议字段 // 跳过 IP 头,指向 TCP/UDP 头 const u_char *transport = pkt_data + ip_header_len;protocol为 6 是 TCP,17 是 UDP,1 是 ICMP。TCP 头也有可变长度,data offset在高 4 位,单位也是 4 字节。UDP 头固定 8 字节。解析端口时用ntohs(*(u_short*)(transport))取源端口,+2取目的端口。这里要注意:如果抓到的包被 snaplen 截断,caplen小于实际需要解析的长度,继续读就会越界。源码里一般用header->caplen做边界检查,但很多实验版本省略了,跑在小流量下没事,一抓大包就崩。我一般会在解析每个头之前加一句if (header->caplen < 14 + ip_header_len + 20) return;之类的判断,虽然啰嗦但能避免玄学崩溃。
4. 协议统计与输出:把抓到的包变成可读信息
4.1 计数器设计与线程安全
源码里通常定义一组全局变量:int tcp_count, udp_count, icmp_count, other_count;,在回调里根据协议类型累加。单线程抓包时没问题,但如果想边抓边刷新界面,就得考虑线程安全。实验版本一般用pcap_loop阻塞抓包,按 Ctrl+C 后打印统计,所以全局变量够用。输出格式常见的是每抓一个包打印一行:时间戳、源 IP、目的 IP、协议、长度。时间戳用header->ts.tv_sec和tv_usec,转成HH:MM:SS.mmm格式。IP 地址用inet_ntoa(*(struct in_addr*)&ip_header[12])取源地址,+16取目的地址。注意inet_ntoa返回的是静态缓冲区,连续调用会覆盖,所以要么立刻打印,要么用inet_ntop自己提供缓冲区。
4.2 过滤规则设置:BPF 语法与常见表达式
WinPcap 支持 BPF 过滤,在pcap_open_live之后、pcap_loop之前调用pcap_compile和pcap_setfilter。源码里可能把过滤表达式写死成"ip"或"tcp",也可能让用户输入。常见表达式:
| 表达式 | 含义 |
|---|---|
ip | 只抓 IPv4 包 |
tcp port 80 | 只抓 TCP 80 端口 |
host 192.168.1.1 | 只抓与该 IP 相关的包 |
not arp | 排除 ARP |
udp and port 53 | 只抓 DNS 查询 |
设置过滤的代码:
struct bpf_program fcode; if (pcap_compile(adhandle, &fcode, "tcp port 80", 1, 0xffffffff) < 0) { fprintf(stderr, "编译过滤规则失败\n"); return -1; } if (pcap_setfilter(adhandle, &fcode) < 0) { fprintf(stderr, "设置过滤规则失败\n"); return -1; }pcap_compile的第四个参数optimize设 1 表示优化,第五个参数是子网掩码,不知道就填0xffffffff。如果表达式写错,pcap_compile返回 -1,错误信息用pcap_geterr(adhandle)取。新手常犯的错是把过滤规则写成"port 80",这在 BPF 里合法,但同时匹配 TCP 和 UDP 的 80 端口,如果只想抓 TCP 就得写"tcp port 80"。
4.3 输出重定向与日志落盘
实验里经常要求把抓包结果保存到文件,方便后续分析。WinPcap 提供pcap_dump_open和pcap_dump,可以把原始包存成.pcap格式,用 Wireshark 打开。用法:
pcap_dumper_t *dumpfile = pcap_dump_open(adhandle, "capture.pcap"); // 在回调里 pcap_dump((u_char*)dumpfile, header, pkt_data); // 结束前 pcap_dump_close(dumpfile);这样存下来的是原始帧,不带解析结果。如果想存文本日志,就在回调里用fprintf写文件,但要注意频繁写盘会影响抓包性能,高流量下可能丢包。我一般会加一个缓冲区,攒够一定行数再fflush,或者直接用setvbuf设置全缓冲。另外,pcap_dump_open的文件名如果已存在会覆盖,实验里最好用时间戳命名,避免多次运行互相覆盖。
5. 避坑与排查:那些让实验卡住的典型问题
5.1 编译报错“无法打开 pcap.h”
现象:包含目录明明设了,还是提示找不到头文件。原因通常是项目属性里改的是 Debug 配置,但编译的是 Release,或者反过来。解决:在属性管理器里确认当前配置和平台(Win32/x64)与包含目录设置一致。另一个原因是路径里有空格或中文,WinPcap 的旧版开发包对中文路径支持不好,把开发包放到纯英文路径下。
5.2 运行时报“Error opening adapter”
现象:pcap_open_live返回 NULL,错误信息是“Error opening adapter: 系统找不到指定的文件”。原因:选错了设备,或者没有以管理员权限运行。WinPcap 抓包需要管理员权限,普通用户运行会打开失败。解决:右键 exe 以管理员身份运行,或者在项目属性里把“链接器 -> 清单文件 -> UAC 执行级别”改成requireAdministrator,这样每次启动自动提权。
5.3 抓到的包全是乱码或解析错位
现象:打印出来的 MAC 地址、IP 地址看起来像随机数。原因:字节序没转换,或者结构体对齐导致偏移错误。解决:所有多字节字段用ntohs或ntohl转主机序;不要依赖结构体强转,用指针加固定偏移读取。如果还是不对,用 Wireshark 抓同一个包对比,看你的解析结果和 Wireshark 的差异在哪一层。
5.4 程序运行一段时间后崩溃
现象:抓了几百个包后突然闪退。原因:回调里用了固定大小的缓冲区,或者解析时越界读。比如把 IP 地址拷到char buf[16],但inet_ntoa返回的字符串可能超过 15 字符(带端口时)。解决:检查所有数组边界,解析前用caplen判断剩余长度。另一个常见原因是pcap_loop的回调里做了耗时操作(比如写文件不缓冲),导致库内部缓冲区溢出。把耗时操作移到单独线程,或者用pcap_next_ex主动控制节奏。
5.5 统计结果与 Wireshark 不一致
现象:你的程序统计 TCP 包 100 个,Wireshark 显示 120 个。原因:过滤规则不同,或者抓包时机不同。Wireshark 默认抓所有包,你的程序可能设了"ip"过滤,排除了 ARP 和 IPv6。另外,pcap_loop的cnt参数如果设了非 0 值,抓够就停,而 Wireshark 一直抓。解决:把过滤规则设成空字符串,cnt设 0,再对比。如果还有差异,检查是否开启了混杂模式,以及网卡是否支持。
6. 进阶技巧:把嗅探器改成能用的分析工具
实验源码跑通只是起点,真正让这份代码有价值的是把它改成能解决具体问题的工具。我一般会做三件事:第一,加一个简单的会话重组,把同一个 TCP 流的包按序列号排序,提取载荷里的 HTTP 请求行,这样能直接看到访问了哪些域名。实现思路是用一个哈希表,键是“源 IP + 源端口 + 目的 IP + 目的端口”,值是包列表,收到 FIN 或 RST 后按序列号排序拼接。第二,加协议识别,不只看端口,还看载荷特征,比如 DNS 查询的\x00\x01标志、HTTP 的GET /开头,这样能识别非标准端口的服务。第三,把统计输出改成实时刷新,用SetConsoleCursorPosition把光标移回开头覆盖打印,看起来像top命令。这些改动都不大,但能让你的实验报告多出不少可写的内容。
验证方法上,我习惯用ping产生 ICMP、用nslookup产生 DNS、用浏览器访问一个 HTTP 网站产生 TCP,然后看程序能不能分别识别。如果 ICMP 统计为 0,检查 IP 头的protocol字段是不是 1;如果 DNS 统计不到,检查是否过滤了 UDP 53。最后留一个我自己的习惯:每次改完解析代码,先用一个固定的.pcap文件回放,而不是实时抓包。pcap_open_offline可以打开之前存的包文件,这样每次运行结果可复现,排查问题时不用靠运气。从那以后我每次调解析逻辑都强制走一遍离线回放,确认无误再上真网卡。希望帮到你。
本文还有配套的精品资源,点击获取