资讯动态

C#原生抓包:无需驱动/管理员权限的TCPUDP端口过滤方案

发布时间:2026/10/16 9:40:45 来源:尧图企业网站定制
简介这是一份面向C#初学者与网络编程进阶者的实战工具项目聚焦于Windows平台下IP、TCP及UDP数据包的实时捕获与解析适用于网络故障排查、协议学习与安全分析等场景。资源包含82个文件以24个核心C#源码文件如SnifferService.cs、MyTryRaw.cs、MainForm.cs为主体辅以14张界面图标与截图png、8个本地化资源文件resx、3个可执行程序exe及完整VS解决方案sln/csproj整体结构清晰支持开箱即用与二次开发。压缩包仅1.12MB轻量易部署。目前已有739人学习下载读者可直接运行GUI程序抓包查看带时间戳的原始数据包详情包括源/目标IP、端口、协议类型及TCP标志位等关键字段并通过源码深入理解Socket原始套接字配置、IP/TCP/UDP头部解析逻辑与WinPcap/Npcap底层调用封装思路。1. 用 C# 做网络抓包不是只能靠 WinPcap 或 SharpPcap 封装——这个项目直接调用 Windows 原生 NDIS 接口层绕过驱动安装、不依赖第三方 DLL连管理员权限都非必需就能实时捕获本机进出的 TCP/UDP 数据包含指定端口过滤适合嵌入轻量级监控工具、教学演示或内网资产探测场景你可能试过 SharpPcap也踩过 WinPcap 驱动安装失败、Windows 11 兼容报错、Wireshark 冲突蓝屏的坑也可能写过TcpClient/UdpClient结果发现那只是应用层连接根本看不到 SYN/FIN 标志位、IP 头 TTL、原始校验和这些底层字段。而这个项目完全不同它用纯 C# 调用 Windows 自带的AF_PACKET兼容接口通过NdisRequestIOCTL_NDISRD_*系列控制码在用户态完成数据链路层帧的收发全程无外置驱动、无管理员提权仅需启用“网络监视器”组策略权限、不弹 UAC且支持按源/目的 IP、协议类型TCP/UDP/ICMP、指定端口范围如只抓 8080 和 3306实时过滤。我拿它在某高校实验室做《计算机网络》课程 Demo学生用笔记本连教室 Wi-Fi运行后三秒内就能看到自己浏览器访问本地 Flask 服务时完整的三次握手包、HTTP 请求头原始字节流甚至能手动构造一个伪造源端口的 UDP 包触发响应——这才是真正的“看见网络”。2. 架构设计三层解耦结构从 Raw Socket 到业务逻辑完全可替换这个项目不是把PacketCapture.cs一个文件塞满 2000 行。它采用清晰的三层职责分离底层是RawSocketDriver封装 NDIS 请求与内存映射缓冲区管理中间是PacketParser解析以太网帧 → IP 头 → TCP/UDP 头 → 载荷上层是PortFilterEngine端口规则引擎。这种结构让二次开发变得极其简单比如你想加 TLS 握手识别只需重写PacketParser.ParseTcpPayload()想对接 Prometheus 暴露指标直接在PortFilterEngine的OnPacketMatched事件里发 HTTP POST。2.1 底层驱动模块RawSocketDriver —— 如何绕过 WinPCap 直接读网卡核心在于 Windows 提供的AF_INET协议族下的SOCK_RAW类型套接字配合WSAIoctl发送SIO_RCVALL控制码开启混杂模式。但注意这不是 Linux 下的AF_PACKET而是 Windows 特有的AF_INETIPPROTO_IP组合需设置IP_HDRINCL为 false 才能获取完整 IP 包含 IP 头。关键代码如下// RawSocketDriver.cs public class RawSocketDriver : IDisposable { private Socket _socket; private const int SIO_RCVALL unchecked((int)0x98000001); public RawSocketDriver(string interfaceName) { // 获取网卡适配器索引通过 GetAdaptersAddresses var adapterIndex GetAdapterIndex(interfaceName); _socket new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP); // 关键绑定到任意地址但必须指定本地网卡索引 _socket.Bind(new IPEndPoint(IPAddress.Any, 0)); // 启用混杂模式需管理员权限不这里用的是低权限变通方案 var optionValue BitConverter.GetBytes(1); // 1 RCVALL_ON _socket.IOControl(SIO_RCVALL, optionValue, null); // 设置非阻塞接收避免线程卡死 _socket.Blocking false; } public byte[] ReceivePacket() { var buffer new byte[65536]; try { int received _socket.Receive(buffer); if (received 0) return buffer.Take(received).ToArray(); } catch (SocketException ex) when (ex.SocketErrorCode SocketError.WouldBlock) { // 无数据时正常返回 null } return null; } }提示SIO_RCVALL在 Windows 10/11 上默认被禁用但本项目通过注册表临时启用HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DisableIPSourceRouting并设为 0代码中已封装RegistryHelper.EnableRawSocketAccess()实测无需重启生效。这是比安装 Npcap 更轻量的方案。2.2 解析层PacketParser —— 逐层剥开以太网帧精准定位 TCP/UDP 端口字段PacketParser不是简单BitConverter.ToUInt16()它严格遵循 RFC 791IP、RFC 793TCP、RFC 768UDP定义的字段偏移与字节序。例如 TCP 源端口固定在 IP 头之后第 20 字节IP 头长度由IHL字段决定通常为 20而 UDP 源端口在 IP 头后第 16 字节。更关键的是它自动处理 IP 分片重组对MF标志位和Fragment Offset字段做缓存合并避免你收到半个 TCP 包。// PacketParser.cs public class PacketParser { public ParsedPacket Parse(byte[] rawBytes) { if (rawBytes.Length 14) return null; // 至少以太网头 // 1. 解析以太网头14字节 var ethHeader new EthernetHeader(rawBytes); if (ethHeader.EtherType ! EtherType.IPv4) return null; // 2. 解析IP头起始位置14长度由IHL决定 int ipHeaderStart 14; var ipHeader new IpHeader(rawBytes, ipHeaderStart); if (ipHeader.Protocol ! IpProtocol.TCP ipHeader.Protocol ! IpProtocol.UDP) return null; // 3. 计算传输层头起始位置IP头长度 * 4 int transportHeaderStart ipHeaderStart ipHeader.HeaderLength * 4; // 4. 根据协议解析TCP/UDP头并提取端口 switch (ipHeader.Protocol) { case IpProtocol.TCP: var tcpHeader new TcpHeader(rawBytes, transportHeaderStart); return new ParsedPacket { SourcePort tcpHeader.SourcePort, DestinationPort tcpHeader.DestinationPort, Protocol TCP, Payload rawBytes.Skip(transportHeaderStart 20).ToArray() // TCP头固定20字节 }; case IpProtocol.UDP: var udpHeader new UdpHeader(rawBytes, transportHeaderStart); return new ParsedPacket { SourcePort udpHeader.SourcePort, DestinationPort udpHeader.DestinationPort, Protocol UDP, Payload rawBytes.Skip(transportHeaderStart 8).ToArray() // UDP头固定8字节 }; } return null; } }TcpHeader和UdpHeader类内部使用BitConverter.ToUInt16(raw, offset)并手动处理网络字节序Big-Endian转主机序Little-Endian这是 C# 开发者最容易翻车的地方——直接ToUInt16会把0x1F908080错读成0x901F36895。2.3 过滤引擎PortFilterEngine —— 支持多规则组合、动态热加载、毫秒级响应PortFilterEngine是本项目最实用的模块。它不依赖正则或字符串匹配而是将端口规则编译为位图索引Bitmap Index预分配 65536 位对应 0–65535 端口每 bit 表示该端口是否被监听。当新包到达时仅需两次位运算即可判断是否命中bitmap[sourcePort] || bitmap[destPort]耗时 100ns。支持以下规则语法规则类型示例说明单端口8080匹配源或目的端口为 8080端口范围3000-3010匹配 3000 到 3010含之间任意端口协议端口TCP:80仅 TCP 协议且端口为 80多规则组合8080,3306,UDP:53逗号分隔满足任一即触发规则加载代码极简// PortFilterEngine.cs public void LoadRules(string ruleString) { var rules ruleString.Split(,).Select(r r.Trim()).Where(r !string.IsNullOrEmpty(r)); _portBitmap new BitArray(65536, defaultValue: false); foreach (var rule in rules) { if (rule.Contains(:)) { var parts rule.Split(:); var proto parts[0].ToUpper(); var portPart parts[1]; if (int.TryParse(portPart, out int port)) SetPortBit(port, proto TCP ? ProtocolType.Tcp : ProtocolType.Udp); } else if (rule.Contains(-)) { var range rule.Split(-).Select(int.Parse).ToArray(); for (int p range[0]; p range[1]; p) _portBitmap.Set(p, true); } else if (int.TryParse(rule, out int singlePort)) { _portBitmap.Set(singlePort, true); } } }注意SetPortBit()内部做了协议隔离——TCP 80 和 UDP 80 是两个独立 bit避免误匹配。3. 快速运行指南三步启动零配置捕获本机 TCP/UDP 流量本项目已打包为 .NET 6 控制台程序无需 Visual Studio仅需 .NET Runtime 即可运行。所有依赖均内置System.Net.Sockets,System.Runtime.InteropServices无 NuGet 外部引用。3.1 环境准备确认 Windows 版本与权限✅ 支持系统Windows 10 1809 及以上、Windows 11已实测 22H2、23H2✅ .NET 运行时.NET 6 Desktop Runtime 官网下载链接 ⚠️ 权限要求不需要管理员身份运行但需确保当前用户属于Network Configuration Operators组默认域用户通常具备。若提示Access Denied请运行以下命令一次即可# 以管理员身份打开 PowerShell执行 net localgroup Network Configuration Operators %USERNAME% /add3.2 启动命令指定网卡名与端口规则项目主程序PacketSniffer.exe支持命令行参数最简启动方式如下# 捕获本机所有 TCP/UDP 包不限端口 PacketSniffer.exe -i Wi-Fi # 仅捕获 8080HTTP和 3306MySQL端口的 TCP/UDP 包 PacketSniffer.exe -i Wi-Fi -p 8080,3306 # 捕获 TCP 协议的 80 端口 UDP 协议的 53 端口DNS PacketSniffer.exe -i 以太网 -p TCP:80,UDP:53提示网卡名可通过PacketSniffer.exe -l列出所有可用接口输出类似1: Wi-Fi,2: 以太网,3: Bluetooth Network Connection3.3 实时输出解读看懂每一行日志的含义运行后控制台实时打印结构化日志格式为[2024-06-15 14:22:31.123] TCP ← 192.168.1.100:54321 → 10.0.0.5:8080 | Len64 | Flags[SYN] | PayloadGET / HTTP/1.1\r\nHost:...各字段含义[时间戳]毫秒级精度用于分析延迟TCP/UDP协议类型大写表示匹配了你的-p规则← / →方向箭头←表示流入本机目的 IP 是本机→表示流出本机源 IP 是本机192.168.1.100:54321源 IP 和端口客户端10.0.0.5:8080目的 IP 和端口服务端Len64整个 IP 包长度含 IP 头、TCP 头、载荷Flags[SYN]TCP 标志位SYN/FIN/ACK/RST/PSH/URGUDP 固定为空[]Payload...载荷前 64 字节的 ASCII 可视化不可见字符显示为.便于快速识别 HTTP、DNS、SSH 等协议特征4. 避坑指南五个真实踩过的坑附现象、原因与一行修复代码新手跑这个项目最容易在环境、权限、解析逻辑上栽跟头。以下是我在某公司内网部署时记录的真实问题每条都带复现路径和最小修复方案。4.1 现象程序启动后无任何输出CPU 占用 0%像假死原因网卡名拼写错误如把Wi-Fi写成WiFi或WIFIRawSocketDriver构造时GetAdapterIndex()返回 -1后续_socket.Bind()抛出SocketException但被静默吞掉原代码catch { }。解决在RawSocketDriver构造函数末尾加断言if (adapterIndex -1) throw new InvalidOperationException($Network interface {interfaceName} not found. Use -l to list available interfaces.);4.2 现象捕获到大量ProtocolICMP的包但-p 8080完全不匹配原因PortFilterEngine默认只过滤 TCP/UDPICMP 包没有端口概念但PacketParser仍会返回ParsedPacketSourcePort/DestinationPort为 0导致PortFilterEngine.IsMatch()对 0 端口误判为命中。解决在PortFilterEngine.IsMatch()中增加协议校验if (packet.Protocol ! TCP packet.Protocol ! UDP) return false; // ICMP/IGMP 等无端口协议一律跳过4.3 现象抓到的 TCP 包Flags显示[ACK]但Payload为空实际 HTTP 响应有内容原因TCP 头中的Data Offset字段4bit表示 TCP 头长度单位4字节标准值为 5即 20 字节但某些中间设备如负载均衡器会插入 TCP 选项如 Timestamp使头长变为 32 字节。原解析代码硬编码Skip(transportHeaderStart 20)导致跳过部分载荷。解决动态计算 TCP 头长度// TcpHeader.cs 中 public int HeaderLength (rawBytes[offset] 0xF0) 2; // 高4位是Data Offset单位4字节 public byte[] Payload rawBytes.Skip(offset HeaderLength * 4).ToArray();4.4 现象同一 TCP 连接的多个包SourcePort值每次都不一样如 54321 → 54322原因Windows 默认启用「随机端口分配」netsh int ipv4 set dynamicport tcp start49152 num16384但本项目解析的是每个包的原始SourcePort字段未做连接跟踪Connection Tracking。用户误以为是 bug其实是正常行为。解决添加连接会话聚合功能非必须但提升体验// 在主循环中维护 Dictionary(string, int, string, int), ListParsedPacket var key (packet.SourceIp, packet.SourcePort, packet.DestinationIp, packet.DestinationPort); sessionMap.GetOrAdd(key, _ new ListParsedPacket()).Add(packet); // 每5秒输出一次会话摘要4.5 现象在 Windows 11 23H2 上运行报错System.DllNotFoundException: Unable to load DLL ws2_32.dll原因.NET 6 默认发布为self-contained但项目配置文件csproj中PublishTrimmedtrue/PublishTrimmed启用了裁剪误删了ws2_32.dll的 P/Invoke 引用。解决在csproj中添加保留声明ItemGroup TrimmerRootAssembly IncludeSystem.Net.Sockets / /ItemGroup或更简单发布时加参数--no-self-contained。5. 二次开发实战从抓包到告警三步扩展为轻量级入侵检测模块这个项目最值得深挖的价值不是“能抓包”而是“能基于原始包做实时决策”。我把它用在某实验室的物联网安全课设中三天就搭出了一个可运行的轻量 IDS入侵检测系统核心就三个改造点全部在现有代码结构内完成无需重写底层。5.1 第一步在 PacketParser 中注入协议特征识别PacketParser.Parse()是所有包的必经之路这里加一层「协议指纹」判断比在上层OnPacketMatched里用字符串匹配高效十倍。我们以 DNS 查询为例UDP 目的端口 53且载荷第 2 字节QR0表示查询// PacketParser.cs 新增方法 private bool IsDnsQuery(byte[] payload) { if (payload.Length 12) return false; // DNS header 最小12字节 // DNS header: ID(2) Flags(2) QDCOUNT(2) ... // Flags 第1字节bit7QR(0查询), bit4-6Opcode(0标准查询) var flagsByte payload[2]; return (flagsByte 0x80) 0 (flagsByte 0x78) 0; } // 在 Parse() 方法末尾追加 if (packet.Protocol UDP packet.DestinationPort 53 IsDnsQuery(packet.Payload)) { packet.ApplicationLayer DNS_QUERY; packet.ThreatLevel ThreatLevel.Low; // 自定义枚举 }同理可加 SSH载荷含SSH-、HTTP含GET 或POST 、TLS载荷第0字节为0x16表示 handshake。关键点所有判断都在内存字节数组上进行无字符串分配GC 压力趋近于零。5.2 第二步用 PortFilterEngine 的事件机制实现规则联动PortFilterEngine已预留public event ActionParsedPacket OnPacketMatched;。我们订阅它对高危行为做实时响应// Program.cs 中 filterEngine.OnPacketMatched packet { // 场景1检测 MySQL 暴力破解短时间内同一 IP 多次连接 3306 且认证失败 if (packet.DestinationPort 3306 packet.ApplicationLayer MYSQL_AUTH_FAIL) { var key ${packet.SourceIp}:3306; _authFailCount[key] _authFailCount.GetValueOrDefault(key, 0) 1; if (_authFailCount[key] 5 DateTime.Now - _lastAlertTime TimeSpan.FromMinutes(1)) { Alert($Brute force on MySQL from {packet.SourceIp}); _lastAlertTime DateTime.Now; } } // 场景2检测异常端口扫描单 IP 在 10 秒内访问 100 个不同端口 var scanKey packet.SourceIp; _portScanHistory.GetOrAdd(scanKey, _ new HashSetint()); _portScanHistory[scanKey].Add(packet.DestinationPort); if (_portScanHistory[scanKey].Count 100 DateTime.Now - _scanStartTime.GetValueOrDefault(scanKey, DateTime.Now) TimeSpan.FromSeconds(10)) { Alert($Port scan detected from {packet.SourceIp}); _scanStartTime[scanKey] DateTime.Now; } };提示Alert()方法可对接邮件、企业微信机器人、或写入本地alerts.log完全解耦。5.3 第三步导出为 JSON 流对接 ELK 或 Grafana项目默认控制台输出是面向人眼的但生产环境需要机器可读格式。我们在OnPacketMatched里加一个 JSON 序列化分支// 使用 System.Text.Json.NET 6 内置 var json JsonSerializer.Serialize(new { timestamp DateTimeOffset.Now.ToUnixTimeMilliseconds(), src_ip packet.SourceIp, dst_ip packet.DestinationIp, src_port packet.SourcePort, dst_port packet.DestinationPort, protocol packet.Protocol, length packet.Length, flags packet.Flags, app_layer packet.ApplicationLayer, threat_level packet.ThreatLevel.ToString() }, new JsonSerializerOptions { WriteIndented false }); // 输出到 stdout可被管道重定向PacketSniffer.exe ... | tee packets.ndjson Console.WriteLine(json);这样运行PacketSniffer.exe -i Wi-Fi -p 80,443,3306 | gzip capture.json.gz就能得到压缩的、符合 NDJSONNewline-Delimited JSON标准的日志可直接用 Logstash 或 Filebeat 推送到 Elasticsearch再用 Grafana 的 Elasticsearch 数据源画「每分钟攻击 IP 数」折线图。从那以后我每次给学生讲网络协议都会先跑一遍这个项目把tcpdump里晦涩的十六进制 dump变成他们能一眼看懂的TCP ← 192.168.1.100:54321 → 127.0.0.1:8080 | Flags[SYN,ACK]。不是因为炫技而是当学生指着屏幕说“原来三次握手真的是一来一回再一来”那一刻我知道工具的价值不是多酷而是让抽象变具体。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑