oioio-space/pkt

GitHub: oioio-space/pkt

一个纯 Go 实现的跨平台数据包捕获与操控库,在 Windows 上通过内嵌的 WinDivert 驱动实现数据包拦截、修改和丢弃,在 Linux 上通过 AF_PACKET 实现无 CGo 依赖的被动嗅探,全部接口兼容 gopacket。

Stars: 0 | Forks: 0

# pkt 用于跨平台数据包捕获的 Go 多模块工作区。提供 Windows(通过 WinDivert)上的被动嗅探、主动数据包拦截、注入和修改,以及 Linux(通过 AF_PACKET)上的被动嗅探。所有数据源均实现了 `gopacket.PacketDataSource`。 [![Go Reference — capture](https://pkg.go.dev/badge/github.com/oioio-space/pkt/capture.svg)](https://pkg.go.dev/github.com/oioio-space/pkt/capture) [![Go Reference — windivert](https://pkg.go.dev/badge/github.com/oioio-space/pkt/windivert.svg)](https://pkg.go.dev/github.com/oioio-space/pkt/windivert) [![Go Reference — afpacket](https://pkg.go.dev/badge/github.com/oioio-space/pkt/afpacket.svg)](https://pkg.go.dev/github.com/oioio-space/pkt/afpacket) [![Go Reference — bpf](https://pkg.go.dev/badge/github.com/oioio-space/pkt/bpf.svg)](https://pkg.go.dev/github.com/oioio-space/pkt/bpf) 状态:正常工作,已在 Windows 11 和 Linux (amd64) 上测试。 ## 安装说明 每个模块独立进行版本控制。只需安装您所需的模块: ``` # Cross-platform capture API (推荐起点) go get github.com/oioio-space/pkt/capture@latest # WinDivert — Windows 数据包拦截 (通过 Windows 上的 capture 包含) go get github.com/oioio-space/pkt/windivert@latest # AF_PACKET — Linux raw socket 捕获 (通过 Linux 上的 capture 包含) go get github.com/oioio-space/pkt/afpacket@latest # BPF — 为 AF_PACKET sockets 编译 pcap-filter 表达式 go get github.com/oioio-space/pkt/bpf@latest ``` ## 功能 - 跨平台捕获 API (`pkt/capture`),只需一次 `Open(iface, filter)` 调用 - Windows:WinDivert 2.2.2 — 在网络层拦截、丢弃、修改和重新注入数据包 - Linux:带有 `SO_TIMESTAMP` 的 AF_PACKET — 结合内核 BPF 过滤器的被动嗅探 - 兼容 `gopacket`:所有数据源均实现了 `gopacket.PacketDataSource` - 内嵌 WinDivert64.sys 2.2.2 — 默认无需单独安装驱动程序 - Linux 上的 BPF 内核过滤器,Windows 上的 WinDivert 过滤表达式 - pcap 导出兼容 Wireshark ## 为什么不使用 libpcap? libpcap(及其 Go 封装库 `gopacket/pcap`)是最常见的数据包捕获库。它可以在 Windows(通过 Npcap)和 Linux 上运行,且支持良好。**如果您只需要被动嗅探,libpcap 是一个完全没问题的选择。** 此项目的存在是为了应对 libpcap 显得力不从心的使用场景: | 能力 | libpcap / Npcap | AF_PACKET (Linux) | WinDivert (Windows) | |---|---|---|---| | 读取数据包 | 是 | 是 | 是 | | **在传输中丢弃数据包** | **否** | **否** | **是** | | **在传输中修改数据包** | **否** | **否** | **是** | | **真正的重新注入(同一数据包)** | **否** | **否** | **是** | | 发送独立的原始数据包 | 是 (`pcap_inject`) | 是 (`sendto`) | 是 | | 捕获层 | **L2 — 以太网帧** | **L2 — 以太网帧** | **L3 — IP 数据包** | | 查看非 IP 流量 (ARP, LLDP…) | 是 | 是 | 否 | | 查看 MAC 地址 | 是 | 是 | 否 | | 内核级过滤 | 是 (BPF) | 是 (BPF) | 是 (WinDivert 过滤器) | | CGo 依赖 | **是** | 否 (此库) | **否** — 纯 Go | | Windows 驱动程序 | 需安装 Npcap/WinPcap | — | **内嵌**,在运行时解压 | | 交叉编译 (`CGO_ENABLED=0`) | 否 | **是** | **是** | ### 副本与拦截 libpcap 和 AF_PACKET 交付每个数据包的**副本** — 原始数据包不受影响地流经网络栈。`pcap_inject()` 和 AF_PACKET 的 `sendto()` 可以发送*独立的*原始数据包,但原始数据包绝不会被拦截或修改。 WinDivert 在 Windows Filtering Platform (WFP) **拦截**数据包:该数据包会被**拦截**,直到您的代码调用 `h.Send()` 将其重新注入(可选择修改),或者通过不调用 `Send` 来丢弃它。这是一种根本不同的模型,可实现: - **防火墙** — 丢弃匹配过滤器的数据包 - **流量整形器 / 代理** — 拦截、重写并重新注入 - **协议模糊测试** — 在传输过程中损坏字段以进行测试 - **透明隧道** — 捕获、加密、转发、重新注入 ### 层级差异:L2 与 L3 libpcap 和 AF_PACKET 在 **Layer 2** 运行 — 它们交付原始以太网帧,包括 MAC 地址、VLAN 标签和非 IP 协议(ARP、LLDP 等)。 处于 `LayerNetwork` 的 WinDivert 在 **Layer 3** 运行 — 它交付没有以太网报头的 IP 数据包。MAC 地址和 ARP 不可见。如果您在 Windows 上需要 L2 可见性,libpcap/Npcap 才是合适的工具。 ### 在 Windows 网络栈中的位置 在 Windows 上,Npcap 和 WinDivert 都挂接到 **Windows Filtering Platform (WFP)**,但在不同的层级: ``` Application │ [ WinSock / TCP/IP stack ] │ ┌───┴────────────────────────────────┐ │ Windows Filtering Platform (WFP) │ │ │ │ ← WinDivert callout driver │ intercepts here — packet is held │ (FWPM_LAYER_OUTBOUND_IPPACKET…) │ └───┬────────────────────────────────┘ │ [ NDIS — Network Driver Interface ] │ ┌───┴──────────────────────────┐ │ Npcap NDIS filter driver │ copies here — original flows through └───┬──────────────────────────┘ │ Network interface card (NIC) ``` Npcap 位于 WFP **下方**的 NDIS 层 — 它在 WFP 决策做出后接收原始以太网帧的副本。它无法影响路由或防火墙决策。 WinDivert 注册一个 **WFP 呼出** — 它在防火墙决策流水线*内部*运行。内核会等待 `h.Send()` 操作才会转发数据包,这就是为什么可以进行丢弃和修改的原因。 ### 无 CGo,无外部依赖 libpcap 需要 CGo 和一个系统库(Linux 上的 `libpcap.so`,Windows 上的 `Npcap.dll`)。这使交叉编译、Docker 镜像和纯粹的 Windows 部署变得复杂。 `pkt` 全程保持 `CGO_ENABLED=0`。WinDivert64.sys 驱动程序被内嵌在二进制文件中并在运行时解压 — 无需单独的安装步骤。 ### 何时使用什么 | 情况 | 建议 | |---|---| | 在 Linux 上嗅探流量,需要 Ethernet/MAC/ARP | `gopacket/pcap` 或 `pkt/afpacket` | | 在 Linux 上嗅探 IP 流量,无 CGo | `pkt/afpacket` | | 在 Windows 上嗅探流量 | `pkt/capture` 或 `gopacket/pcap` | | 在 Windows 上需要 MAC 地址 / ARP | `gopacket/pcap` (Npcap, L2) | | **在 Windows 上丢弃 / 阻断数据包** | **`pkt/windivert`** — 唯一真正的选项 | | **在 Windows 上修改传输中的数据包** | **`pkt/windivert`** — 唯一真正的选项 | | 零 CGo,从 Linux 交叉编译到 Windows | **`pkt`** | | 已经到处都在使用 libpcap | 继续使用 `gopacket/pcap` | ## 查找网络接口 **Windows (PowerShell):** ``` Get-NetAdapter | Select Name, InterfaceIndex, Status # or ipconfig ``` **Windows (cmd):** ``` netsh interface show interface ``` **Linux:** ``` ip link show # or ls /sys/class/net/ ``` ## 用法示例 ### 1. 嗅探器 — 跨平台 (`pkt/capture`) ``` import "github.com/oioio-space/pkt/capture" // Linux — interface required src, err := capture.Open("eth0", "tcp port 443") // Windows — interface ignored, WinDivert filter syntax // src, err := capture.Open("", "tcp.DstPort == 443") if err != nil { log.Fatal(err) } defer src.Close() ps := gopacket.NewPacketSource(src, src.LinkType()) for pkt := range ps.Packets() { fmt.Println(pkt) } ``` ### 2. 嗅探器 — 直接使用 WinDivert (`pkt/windivert`) ``` import "github.com/oioio-space/pkt/windivert" h, err := windivert.OpenSniff("tcp.DstPort == 443", windivert.LayerNetwork) // equivalent: windivert.Open("tcp.DstPort == 443", windivert.LayerNetwork, windivert.WithFlags(windivert.FlagSniff)) if err != nil { log.Fatal(err) } defer h.Close() ps := gopacket.NewPacketSource(h, h.LinkType()) for pkt := range ps.Packets() { fmt.Println(pkt) } ``` ### 3. 丢弃数据包 (Windows) ``` h, err := windivert.Open("tcp.DstPort == 443", windivert.LayerNetwork) if err != nil { log.Fatal(err) } defer h.Close() ps := gopacket.NewPacketSource(h, h.LinkType()) for range ps.Packets() { // Don't call h.Send() — kernel discards the packet } ``` ### 4. 重新注入 / 转发数据包 (Windows) ``` h, err := windivert.Open("tcp", windivert.LayerNetwork) if err != nil { log.Fatal(err) } defer h.Close() ps := gopacket.NewPacketSource(h, h.LinkType()) for pkt := range ps.Packets() { addr := windivert.AddressFromPacket(pkt) if addr == nil { continue } // optionally modify pkt.Data() here _ = h.Send(pkt.Data(), addr) } ``` ### 5. 写入 pcap 文件 ``` f, _ := os.Create("out.pcap") bw := bufio.NewWriterSize(f, 1<<20) defer bw.Flush() defer f.Close() w := pcapgo.NewWriter(bw) _ = w.WriteFileHeader(65535, layers.LinkTypeIPv4) src, _ := capture.Open("", "tcp") // Windows — iface ignored defer src.Close() ps := gopacket.NewPacketSource(src, src.LinkType()) for pkt := range ps.Packets() { ci := pkt.Metadata().CaptureInfo _ = w.WritePacket(ci, pkt.Data()) } ``` ### 6. 永久安装 WinDivert 驱动程序 (Windows,需管理员权限) ``` import ( "github.com/oioio-space/pkt/windivert" "github.com/oioio-space/pkt/windivert/driver" ) // Install driver permanently (SERVICE_AUTO_START, stable path in System32\drivers\) err := windivert.InstallDriver(driver.WithPersistent()) ``` 永久安装后,随后对 `windivert.Open` 的调用将跳过 SCM 安装步骤,并直接打开设备。 ### 7. 获取内嵌驱动程序版本 ``` fmt.Println(windivert.DriverVersion) // "2.2.2" ``` ## WinDivert 过滤器语法 WinDivert 过滤器是在数据包字段上求值的类 C 布尔表达式: ``` tcp # all TCP packets tcp.DstPort == 443 # TCP to port 443 ip.SrcAddr == 192.168.1.1 # from specific IP tcp and not tcp.DstPort == 80 # TCP except port 80 ip.TTL < 5 # low TTL outbound and tcp # outbound TCP only not loopback # exclude loopback true # all packets ``` 常见字段: | 字段 | 描述 | |-------|-------------| | `tcp`, `udp`, `icmp`, `ip`, `ipv6` | 协议谓词 | | `tcp.SrcPort`, `tcp.DstPort` | TCP 端口号 | | `udp.SrcPort`, `udp.DstPort` | UDP 端口号 | | `ip.SrcAddr`, `ip.DstAddr` | IPv4 地址 | | `ipv6.SrcAddr`, `ipv6.DstAddr` | IPv6 地址 | | `ip.TTL`, `ip.Protocol` | IP 报头字段 | | `tcp.Syn`, `tcp.Ack`, `tcp.Fin`, `tcp.Rst` | TCP 标志 | | `outbound`, `inbound` | 方向 | | `loopback` | 回环接口 | | `ifIdx` | 接口索引 | 完整参考:https://reqrypt.org/windivert-doc.html#filter_language ## Linux BPF 过滤器语法 (pcap-filter) ``` tcp port 443 # TCP port 443 (src or dst) tcp dst port 80 # TCP destination port 80 host 192.168.1.1 # traffic to/from host src host 10.0.0.1 # traffic from host net 192.168.1.0/24 # subnet tcp and not port 22 # TCP except SSH udp port 53 # DNS ``` 完整参考:`man pcap-filter` ## 构建 **Linux:** ``` make linux # build Linux capture binary → dist/capture-linux-amd64 make check # build + vet make test # run unit tests (no root required) make setcap # grant CAP_NET_RAW to the Linux binary (requires sudo) ``` **Windows (PowerShell):** ``` .\build.ps1 # build all (Linux + Windows binaries) .\build.ps1 -Target windows # Windows binaries only .\build.ps1 -Target linux # Linux binary only (cross-compile) .\build.ps1 -Target check # build + vet .\build.ps1 -Target test # run unit tests .\build.ps1 -Target clean # remove dist/ ``` 构建输出会放入 `dist/` 目录。所有二进制文件均为静态链接 (`CGO_ENABLED=0`)。 ## 模块结构 ``` pkt/ ├── afpacket/ # Linux raw socket capture (AF_PACKET + SO_TIMESTAMP + BPF) ├── bpf/ # BPF filter compiler (used by afpacket on Linux) ├── capture/ # Cross-platform unified capture API (wraps windivert/afpacket) ├── windivert/ # Windows kernel driver bindings (WinDivert 2.2.2) │ ├── assets/ # Embedded WinDivert64.sys │ ├── driver/ # SCM driver installer (temporary + persistent modes) │ └── filter/ # WinDivert filter expression compiler (bytecode) └── examples/ └── cmd/ ├── capture/ # Cross-platform sniffer + pcap writer ├── drop/ # Windows: drop packets matching a filter ├── filter/ # Windows: firewall (blacklist / whitelist) └── modify-payload/ # Windows: rewrite TCP payload in-flight ``` 有关标志和用法,请参阅每个示例的 README: - [`examples/cmd/capture`](examples/cmd/capture/README.md) — 跨平台嗅探器,pcap 导出 - [`examples/cmd/drop`](examples/cmd/drop/README.md) — Windows 数据包丢弃工具 - [`examples/cmd/filter`](examples/cmd/filter/README.md) — Windows 防火墙(黑名单/白名单) - [`examples/cmd/modify-payload`](examples/cmd/modify-payload/README.md) — Windows TCP 负载修改器
标签:AF_PACKET, BPF过滤, DNS枚举, EVTX分析, gopacket, Go语言, pcap, WinDivert, Windows 11, XML 请求, 中间人攻击工具, 内核驱动, 原始套接字, 多模块工作区, 安全开发, 数据包修改, 数据包拦截, 数据包注入, 日志审计, 流量劫持, 目录遍历, 程序破解, 系统底层, 网络协议, 网络安全工具, 网络层, 网络抓包, 网络编程, 防御绕过