如何在Go单二进制中拦截修改随机端口连接的2106端口数据包?
可行方案:单Go二进制实现2106端口数据包拦截与修改
完全可以在单个Go二进制文件里实现需求,不需要依赖iptables。核心是用gopacket库同时完成数据包捕获、修改与发送,不过需要处理TCP协议的状态细节,且程序需要root权限运行(原始网络操作需要特权)。
核心实现思路
- 捕获目标数据包:保留你现有代码中基于pcap的2106端口过滤逻辑
- 解析与修改数据包:解析出IP、TCP层,修改payload内容
- 重构合法数据包:重新计算IP/TCP头部的校验和,调整TCP序列号/确认号以匹配连接状态
- 发送修改后的数据包:通过原始套接字将新包发送到网络
- (可选)拦截原数据包:如果不希望原包到达目标,可以结合pcap的
SetDirection或操作系统的netfilter,纯Go实现也可通过抢占发送覆盖(复杂场景建议用ebpf或netfilter保证可靠性)
修改后的示例代码
基于你现有的捕获代码,添加解析、修改、发送逻辑:
package main import ( "fmt" "log" "sync" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcap" ) // 维护TCP连接状态,记录序列号偏移(简化版,实际需双向跟踪) var connState = struct { sync.Mutex entries map[string]int64 // key: src:dst:sport:dport, value: seq偏移量 }{ entries: make(map[string]int64), } func captureAndModifyPackets(device string, wg *sync.WaitGroup) { defer wg.Done() // 打开捕获句柄,同时支持发送 handle, err := pcap.OpenLive(device, 65536, true, pcap.BlockForever) if err != nil { log.Printf("打开接口%s失败: %v", device, err) return } defer handle.Close() // 设置过滤规则:2106端口的TCP包 filter := "tcp and (dst port 2106 or src port 2106)" if err := handle.SetBPFFilter(filter); err != nil { log.Printf("设置过滤器失败: %v", err) return } packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) for packet := range packetSource.Packets() { // 解析IP和TCP层 ipLayer := packet.Layer(layers.LayerTypeIPv4) tcpLayer := packet.Layer(layers.LayerTypeTCP) if ipLayer == nil || tcpLayer == nil { continue } ip, _ := ipLayer.(*layers.IPv4) tcp, _ := tcpLayer.(*layers.TCP) // 构造连接唯一标识 connKey := fmt.Sprintf("%s:%d->%s:%d", ip.SrcIP, tcp.SrcPort, ip.DstIP, tcp.DstPort) // 示例:修改payload内容(将原内容替换为"modified_content") newPayload := []byte("modified_content") // 计算payload长度变化,生成序列号偏移 offset := int64(len(newPayload) - len(tcp.Payload)) // 更新连接状态的序列号偏移 connState.Lock() currentOffset, exists := connState.entries[connKey] if !exists { currentOffset = 0 } connState.entries[connKey] = currentOffset + offset connState.Unlock() // 构造新的TCP层 newTCP := *tcp newTCP.Payload = newPayload // 关联IP层以重新计算TCP校验和 newTCP.SetNetworkLayerForChecksum(ip) // 调整序列号和确认号(基于偏移量) newTCP.Seq += uint32(currentOffset) if newTCP.Ack { reverseConnKey := fmt.Sprintf("%s:%d->%s:%d", ip.DstIP, tcp.DstPort, ip.SrcIP, tcp.SrcPort) connState.Lock() reverseOffset := connState.entries[reverseConnKey] connState.Unlock() newTCP.Ack += uint32(reverseOffset) } // 序列化新数据包 buffer := gopacket.NewSerializeBuffer() opts := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, } if err := gopacket.SerializeLayers(buffer, opts, packet.Layer(layers.LayerTypeEthernet), ip, &newTCP, gopacket.Payload(newPayload), ); err != nil { log.Printf("序列化数据包失败: %v", err) continue } // 发送修改后的数据包 if err := handle.WritePacketData(buffer.Bytes()); err != nil { log.Printf("发送数据包失败: %v", err) } fmt.Printf("已修改并发送数据包: 原Payload长度%d -> 新长度%d\n", len(tcp.Payload), len(newPayload)) } } func main() { devices, err := pcap.FindAllDevs() if err != nil { log.Fatal(err) } var wg sync.WaitGroup for _, device := range devices { wg.Add(1) go captureAndModifyPackets(device.Name, &wg) } wg.Wait() }
关键注意点
- Root权限:必须用
sudo运行程序,原始数据包捕获和发送需要CAP_NET_RAW系统权限 - TCP状态跟踪:示例中的连接状态是简化版,生产环境需要完善状态管理(比如处理FIN/RST包、超时清理无效连接)
- 校验和与序列号:修改payload后必须重新计算校验和,同时调整TCP序列号/确认号,否则目标端会判定数据包非法并丢弃
- 性能与可靠性:相比iptables+用户态代理的方案,原始数据包操作对TCP协议细节要求更高,适合特定场景;通用代理需求建议用iptables+Go代理的组合,稳定性更强
内容的提问来源于stack exchange,提问作者Ming
相关产品推荐
相关产品推荐

