简介:这是一份面向网络编程初学者与信息安全方向学生的 WinPcap 网络嗅探器实战资料,基于 Visual C++ 与 MFC 开发,帮助读者理解网卡监听、数据包捕获与协议解析的完整流程。资源包共 52 个文件,约 24.88MB,包含 cpp、h 源码文件、vcproj 工程配置、rc 资源脚本、exe 可执行程序以及 pdf 说明文档,覆盖从工程搭建到编译运行的各个环节。实验内容涵盖列出并选择网卡、设置混杂模式监听、利用 WinPcap 函数库配置过滤规则、按协议格式解析包头与数据,并与 Wireshark 对比完善代码。已有 1047 人学习下载,适合作为课程设计或网络协议分析的参考案例。读者可借助源码与说明文档快速复现嗅探工具,掌握 WinPcap 调用方式、MFC 界面组织与抓包分析思路,并对照现有实现查漏补缺。
1. 从一份 VC++ 嗅探器源码说起:WinPcap 抓包到底怎么落地
很多人第一次接触网络嗅探,都是从 Wireshark 点几下开始,界面漂亮、协议树清晰,但真到自己动手写一个抓包工具,问题就来了:网卡怎么枚举、混杂模式怎么开、过滤器语法怎么写、抓到的裸字节怎么按协议拆开。这份「基于 WinPcap 网络嗅探器 VC++ 源码 + 说明文档」正好卡在这个位置上——它不是教你调 Wireshark,而是给你一套能编译、能跑、能改的 MFC 工程,把 WinPcap 的调用链路完整摊开。
资源里包含PackageSniffer.sln解决方案、YGSniffer工程目录、MyWinPcap.h/.cpp封装层、StructFile.h协议结构定义,以及一份网络嗅探器实验.pdf说明文档。开发环境锁定 Visual Studio 2008 + MFC + WinPcap 支持库,目标平台是 Windows。适合两类人:一是课程设计或实验需要交一个能演示的嗅探器,二是想搞明白 WinPcap 那套pcap_loop、pcap_compile到底怎么和 MFC 对话框揉在一起。下面按「能编译 → 能抓包 → 能解析 → 不翻车」的顺序拆。
2. 工程结构与 WinPcap 调用链:先看清代码怎么组织
2.1 目录里到底有什么,哪些是能改的
拿到压缩包先别急着双击PackageSniffer.exe,那个是编译产物,真正有价值的是源码目录。把文件按角色分一下,心里有数再动手:
| 文件/目录 | 角色 | 是否要改 |
|---|---|---|
PackageSniffer.sln | VS2008 解决方案入口 | 打开用,不改 |
YGSniffer/YGSniffer.vcproj | 工程文件,含编译配置 | 换环境时可能要改 |
MyWinPcap.h/.cpp | WinPcap 函数封装层 | 核心,重点读 |
StructFile.h | 以太网/IP/TCP/UDP 头结构体 | 解析逻辑靠它 |
YGSnifferDlg.cpp/.h | MFC 主对话框,抓包线程入口 | 交互逻辑在这 |
网络嗅探器实验.pdf | 实验说明与步骤 | 对照读 |
Debug/ | 已编译的 exe、pdb、ilk | 参考,可删 |
MyWinPcap这一层是整个工程的骨架,它把pcap_findalldevs、pcap_open_live、pcap_compile、pcap_loop这些 C 接口包成了 C++ 类,MFC 对话框只跟这个类打交道。读代码的顺序建议是:先看MyWinPcap.h的类声明,知道暴露了哪些方法,再回到YGSnifferDlg.cpp看这些方法在哪个按钮响应里被调用。
2.2 网卡枚举与混杂模式:抓包前的两件事
WinPcap 抓包的第一步永远是拿到网卡列表,然后选一块打开。核心调用是pcap_findalldevs,它返回一个pcap_if_t链表,每项里有网卡名、描述和地址列表。工程里通常会在对话框初始化时把这块列表填进一个 ComboBox,让用户选。
// 枚举网卡并填充下拉框(示意,对应 MyWinPcap 封装) pcap_if_t *alldevs = nullptr; char errbuf[PCAP_ERRBUF_SIZE] = {0}; if (pcap_findalldevs(&alldevs, errbuf) == -1) { // errbuf 里是具体错误,常见是 NPF 服务没起 AfxMessageBox(CString("枚举网卡失败: ") + errbuf); return; } // 遍历链表,把 description 或 name 塞进 ComboBox for (pcap_if_t *d = alldevs; d != nullptr; d = d->next) { CString name = d->description ? d->description : d->name; m_comboDev.AddString(name); m_devList.push_back(d); // 保存指针,后面 open 要用 }这里有两个参数要盯住:errbuf是错误缓冲区,任何 WinPcap 调用失败都先看它;alldevs用完必须pcap_freealldevs释放,否则反复枚举会泄漏。选好网卡后调pcap_open_live,第三个参数是snaplen(抓取长度),工程里一般给 65535 保证整包;第四个参数promisc设为 1 就是混杂模式,能收到不是发给本机的帧;第五个是超时毫秒数,影响pcap_loop的响应节奏。
// 打开选中网卡,开启混杂模式 pcap_t *adhandle = pcap_open_live( d->name, // 网卡设备名 65535, // snaplen,整包抓取 1, // promisc=1 混杂模式 1000, // 读超时 1000ms errbuf); if (adhandle == nullptr) { AfxMessageBox(CString("打开网卡失败: ") + errbuf); return; }混杂模式不是万能的,交换式网络里你只能看到本机相关的流量和广播,这点在实验报告里要写清楚,别指望抓到整个局域网。snaplen给太小会截断大包,解析时长度对不上,这是后面避坑章要展开的点。
2.3 过滤器规则:用 BPF 语法把无关流量挡在外面
抓全量包很快就堆满内存,实际用的时候一定要设过滤规则。WinPcap 用的是 BPF(Berkeley Packet Filter)语法,通过pcap_compile把字符串编译成过滤程序,再pcap_setfilter挂到句柄上。工程里通常给一个编辑框让用户输规则,比如tcp port 80、ip host 192.168.1.10。
struct bpf_program fcode; // 把过滤字符串编译成 BPF 程序 // 参数3: 1=优化编译, 参数4: 子网掩码(0 表示不限定) if (pcap_compile(adhandle, &fcode, "tcp port 80", 1, 0) < 0) { AfxMessageBox(_T("过滤规则编译失败,检查语法")); return; } // 把编译好的过滤器挂到抓包句柄 if (pcap_setfilter(adhandle, &fcode) < 0) { AfxMessageBox(_T("设置过滤器失败")); return; } pcap_freecode(&fcode); // 编译结果用完释放pcap_compile的第四个参数是网络掩码,只有规则里用到broadcast之类关键字时才需要,普通端口/主机过滤给 0 就行。规则写错时pcap_compile返回负值,不会崩,但过滤器不生效,你会以为规则没用——其实是编译没过。pcap_freecode别漏,虽然单次影响小,但反复改规则不释放会累积。
3. 抓包线程与协议解析:从裸字节到可读字段
3.1 抓包循环放哪:别在 UI 线程里死等
pcap_loop是个阻塞调用,它会一直抓直到凑够指定包数或出错。如果直接放在按钮响应里,界面立刻卡死,这是新手最常见的翻车点。正确做法是开一个工作线程,把pcap_loop丢进去,抓到包后通过自定义消息或队列把数据传回 UI 线程刷新列表。
// 工作线程函数:循环抓包 UINT SniffThread(LPVOID param) { CSnifferDlg *dlg = (CSnifferDlg *)param; // cnt=0 表示无限抓,直到 pcap_breakloop 被调用 pcap_loop(dlg->m_adhandle, 0, PacketHandler, (u_char *)dlg); return 0; } // 回调:每抓到一个包触发一次 void PacketHandler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) { CSnifferDlg *dlg = (CSnifferDlg *)param; // header->len 是包长,header->ts 是时间戳 // pkt_data 是裸字节,交给解析函数 dlg->ParsePacket(header, pkt_data); }pcap_loop第二个参数给 0 表示不限数量,靠pcap_breakloop停止;给正数则抓够就返回。回调里的header带时间戳和长度,pkt_data才是真正的帧数据。注意回调运行在工作线程,直接操作 MFC 控件会出问题,工程里一般用PostMessage把数据指针或副本发到主线程再刷新。
3.2 按协议逐层剥:以太网头、IP 头、TCP/UDP 头
StructFile.h里定义的就是各层协议头结构体,解析的本质是按偏移量一层层套。以太网头固定 14 字节,类型字段 0x0800 是 IP、0x0806 是 ARP;IP 头里协议字段 6 是 TCP、17 是 UDP;再往下才是端口和载荷。
// 逐层解析示意 void CSnifferDlg::ParsePacket(const struct pcap_pkthdr *header, const u_char *pkt_data) { // 1. 以太网头 14 字节 EthernetHeader *eth = (EthernetHeader *)pkt_data; u_short ethType = ntohs(eth->type); // 网络字节序转主机序 if (ethType == 0x0800) { // IPv4 // 2. IP 头,偏移 14 IPHeader *ip = (IPHeader *)(pkt_data + 14); u_char proto = ip->protocol; int ipHeaderLen = (ip->ver_ihl & 0x0F) * 4; // 首部长度以 4 字节为单位 if (proto == 6) { // TCP TCPHeader *tcp = (TCPHeader *)(pkt_data + 14 + ipHeaderLen); u_short sport = ntohs(tcp->sport); u_short dport = ntohs(tcp->dport); // 填充到列表:源IP、目的IP、端口、长度 } else if (proto == 17) { // UDP // 类似处理 } } }这里最容易错的是字节序和首部长度。网络传来的多字节字段是大端,x86 上必须ntohs/ntohl转换,否则端口号会显示成乱七八糟的值。IP 首部长度字段ver_ihl低 4 位才是长度,单位是 4 字节,所以乘 4;如果 IP 有选项字段,这个长度会大于 20,写死 20 就会解析错位。TCP 头同理,data offset字段决定头长,写死 20 遇到带选项的包就崩。
3.3 和 Wireshark 对照:怎么验证自己解析对了
实验要求里明确写了要和 Wireshark 对比,这不是走过场,是验证解析正确性最直接的办法。同一块网卡、同一时刻,Wireshark 抓一份,自己的工具抓一份,挑几个包逐字段对:源 MAC、目的 MAC、源 IP、目的 IP、端口、TCP 标志位、载荷长度。对不上的地方就是解析代码的 bug。
常见对不上的原因有三类:一是字节序没转,端口和长度字段全错;二是首部长度写死,遇到带选项的包偏移错位;三是snaplen太小,大包被截断,header->caplen小于header->len,解析到后面越界。对照时优先看caplen和len是否相等,不等就说明包被截了。把这几类对齐,解析逻辑基本就稳了。
4. 编译环境与依赖排查:WinPcap 装不上怎么办
4.1 VS2008 工程在新环境下的兼容处理
工程是 VS2008 建的,.vcproj格式,用 VS2010 及以上打开会触发升级向导。升级本身问题不大,但 MFC 版本和字符集设置可能变。打开后先确认两处:项目属性里「字符集」是 Unicode 还是多字节,工程里CString和_T()的用法要匹配;「MFC 的使用」是静态库还是共享 DLL,选共享 DLL 的话目标机器要有对应运行库。
WinPcap 开发包(WpdPack)的路径要在项目属性里配好:包含目录加WpdPack\Include,库目录加WpdPack\Lib,链接器输入里加wpcap.lib和ws2_32.lib。少一个就是一堆 LNK2019 未解析外部符号。如果换到 64 位编译,注意 WpdPack 的 Lib 目录分 x86 和 x64,别混用。
4.2 NPF 服务与驱动:抓不到包的根因常在这
WinPcap 装完会注册一个 NPF(Netgroup Packet Filter)驱动和服务,抓包全靠它。如果pcap_findalldevs返回空列表,或者pcap_open_live报错,八成是 NPF 没起来。检查方法是打开服务管理器看NPF服务状态,或者在命令行用net start npf手动启动。
# 查看 NPF 服务状态 sc query npf # 手动启动(需要管理员权限) net start npf装 WinPcap 时如果报「安装失败」或「npf 错误」,常见原因是系统里已经装了 Npcap(Wireshark 新版自带的),两者会冲突。解决办法是先卸掉其中一个,重启后再装。另外 WinPcap 官方版本对较新的 Windows 支持有限,如果实在装不上,可以考虑用 Npcap 的兼容模式,它保留了wpcap.dll接口,源码基本不用改,但要注意 Npcap 的安装选项里勾选「WinPcap API 兼容」。
提示:装驱动类组件前先关掉杀毒软件的实时防护,部分安全软件会拦截驱动安装导致 NPF 注册失败。
4.3 编译产物里那些文件是干嘛的
Debug目录里的PackageSniffer.exe是可直接运行的成品,pdb是调试符号,ilk是增量链接中间文件,suo存的是用户界面配置。这些都不影响源码编译,但pdb建议留着,调试时能对上源码行号。packet.idx和packet.dmp看名字像是抓包数据或索引文件,可能是程序运行时生成的缓存,删掉不影响编译,但如果你要复现实验里的抓包结果,先备份一份。
5. 避坑与常见问题:那些让我重装三次的细节
5.1 现象:程序能编译,运行后网卡列表是空的
原因:NPF 服务没启动,或者当前用户权限不够。WinPcap 枚举网卡需要访问驱动,普通用户可能被拒。 解决:用管理员身份运行程序,同时确认sc query npf显示 RUNNING。如果服务不存在,说明 WinPcap 没装好,重装并重启。
5.2 现象:抓到的包端口号是乱码,比如 80 显示成 20480
原因:网络字节序没转。ntohs漏了,或者转错了字段。 解决:所有从包里读出的多字节字段(端口、长度、校验和)统一用ntohs/ntohl转一遍,转换前确认字段本身是大端存储。
5.3 现象:解析到 TCP 载荷时越界崩溃
原因:IP 首部长度或 TCP 首部长度写死成 20,遇到带选项的包偏移算错,指针跑到包尾之外。 解决:从ver_ihl和 TCP 的data offset字段动态算头长,解析前先判断剩余长度够不够,不够就跳过这个包。
5.4 现象:过滤器写了但没效果,还是抓到一堆无关包
原因:pcap_compile返回负值但没检查,过滤器没挂上;或者规则语法本身错。 解决:每次pcap_compile后判断返回值,失败就弹错误提示。规则先用最简单的tcp测,通了再加端口和主机条件。
5.5 现象:界面卡死,点停止没反应
原因:pcap_loop跑在 UI 线程,阻塞了消息循环。 解决:把抓包放工作线程,停止时调pcap_breakloop让pcap_loop返回,再等线程退出。别用TerminateThread强杀,会泄漏句柄。
6. 进阶:把抓包结果落盘并用 Wireshark 二次分析
自己写的工具解析能力有限,但 WinPcap 支持把抓到的包直接写成 pcap 格式文件,这个文件 Wireshark 能直接打开,等于借 Wireshark 的协议解析能力做二次分析。核心调用是pcap_dump_open和pcap_dump,在回调里顺手写盘即可。
pcap_dumper_t *dumper = nullptr; // 打开网卡后,创建 dump 文件 dumper = pcap_dump_open(adhandle, "capture.pcap"); if (dumper == nullptr) { AfxMessageBox(_T("创建 pcap 文件失败")); } // 在抓包回调里写盘 void PacketHandler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) { CSnifferDlg *dlg = (CSnifferDlg *)param; if (dlg->m_dumper != nullptr) { // 把原始包按 pcap 格式写入文件 pcap_dump((u_char *)dlg->m_dumper, header, pkt_data); } dlg->ParsePacket(header, pkt_data); } // 停止抓包时刷新并关闭 pcap_dump_flush(dumper); pcap_dump_close(dumper);pcap_dump的第二个参数就是回调收到的header,第三个是pkt_data,原样写入即可,不用自己拼格式。pcap_dump_flush在停止时调一次,确保缓冲区落盘,否则最后几个包可能丢。生成的capture.pcap拖进 Wireshark,协议树、流跟踪、统计功能全能用,比自己写的解析界面强得多。
验证自己工具是否正确,我一般会做一次「双抓对比」:同一时刻自己的工具和 Wireshark 各抓一份,用 Wireshark 打开自己工具生成的 pcap,看包数量、时间戳、长度是否一致。如果数量对不上,检查是不是过滤器规则不同;如果长度对不上,检查snaplen。这个习惯是从一次实验报告被退回后养成的——当时解析代码看着没问题,结果 pcap 文件里包被截断,Wireshark 一打开全是 malformed,才发现snaplen设成了 96。从那以后我每次改抓包参数,都强制用 Wireshark 回放验证一遍再往下做。希望帮到你。
本文还有配套的精品资源,点击获取