在Linux生态中,SNAT(Source Network Address Translation,源网络地址转换)的实现堪称优雅:通过iptables
或nftables
工具链,开发者只需数行命令即可完成地址伪装与流量重定向。然而,当场景切换到Windows平台时,这一基础网络功能的实现却面临双重困境——系统架构的天然差异与版本功能的严格阉割。
Windows Server系列(如Windows Server 2016/2019/2022)通过内置的NAT网关组件和PowerShell命令集,为SNAT功能提供了官方支持。但面向消费级市场的Windows 10/11(家庭版、专业版等)则被刻意移除了核心网络服务模块,其网络栈仅保留最基础的NAT客户端功能。这种人为的"功能分层"策略,使得开发者不得不在缺乏原生工具链的困境中寻求迂回方案。
本文将以技术可行性为脉络,剖析Windows平台实现SNAT的底层逻辑,并介绍几种可行的解决方案,为读者提供参考和借鉴。我们也将分析每种方案的优缺点,帮助读者根据自身需求选择最合适的方案,最终在Windows平台上成功实现SNAT功能。
Snat本质是做地址转换, 主要用于将数据包的源IP地址/端口号修改为另一个IP地址/端口号,这样做有如下好处:
Linux灰常简单的就可以实现Snat:
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -p tcp --sport 8080 -j SNAT --to-source 203.0.113.1-203.0.113.2
SNAT的工作过程如下:
WinDivert(Windows Packet Divert)是一个用于Windows 7、Windows 8和Windows 10的用户模式数据包拦截库。它允许用户在不需要管理员权限的情况下捕获、修改和丢弃网络数据包。WinDivert广泛应用于实现用户模式的数据包过滤器、嗅探器、防火墙、NAT、VPN、入侵检测系统(IDS)、隧道应用等。
WinDivert具备以下主要功能:
WinDivert支持以下特性:
有关更多信息,请参见文档 doc/windivert.html
。
WinDivert的基本架构如下所示:
3. 数据包处理:用户程序可以选择丢弃、修改或重新注入数据包。
package main
import (
"fmt"
"net"
"time"
"github.com/williamfhe/godivert"
"github.com/williamfhe/godivert/header"
)
var (
NETWORK = 0
NETWORK_FORWARD = 1
targetIP = net.ParseIP("192.168.197.135")
routerIP = net.ParseIP("192.168.197.129")
unbutuIP = net.ParseIP("192.168.197.130")
)
// inbound -> snat
func snatPacket(wd *godivert.WinDivertHandle, packetChan <-chan *godivert.Packet) {
for packet := range packetChan {
if packet.SrcIP().Equal(unbutuIP) && packet.DstIP().Equal(targetIP) && packet.NextHeaderType() == header.ICMPv4 {
fmt.Println("snat old:" + packet.String())
packet.SetSrcIP(routerIP)
fmt.Println("snat new:" + packet.String())
}
packet.CalcNewChecksum(wd)
packet.Send(wd)
}
}
// outbound -> restore ip
func restorePacket(wd *godivert.WinDivertHandle, packetChan <-chan *godivert.Packet) {
for packet := range packetChan {
if packet.SrcIP().Equal(targetIP) && packet.DstIP().Equal(routerIP) && packet.NextHeaderType() == header.ICMPv4 {
fmt.Println("restore old:" + packet.String())
packet.SetDstIP(unbutuIP)
fmt.Println("restore new:" + packet.String())
}
packet.CalcNewChecksum(wd)
packet.Send(wd)
}
}
func main() {
postRoutingWinDivert, err := godivert.NewWinDivertHandle("true", uint8(NETWORK_FORWARD))
if err != nil {
panic(err)
}
defer postRoutingWinDivert.Close()
forwardPacketChan, err := postRoutingWinDivert.Packets()
if err != nil {
panic(err)
}
preRoutingwinDivert, err := godivert.NewWinDivertHandle("true", uint8(NETWORK))
if err != nil {
panic(err)
}
defer preRoutingwinDivert.Close()
inputPacketChan, err := preRoutingwinDivert.Packets()
if err != nil {
panic(err)
}
go snatPacket(postRoutingWinDivert, forwardPacketChan)
go restorePacket(preRoutingwinDivert, inputPacketChan)
time.Sleep(230 * time.Second)
}
unbutu机器上的抓包:
windos机器上的抓包:
目标靶机上的抓包:
灰常完美~~
Npcap 是 Nmap 项目的一个强大工具,专为 Microsoft Windows 开发,旨在提供高效的网络数据包捕获和发送功能。它实现了开放的 Pcap API,结合了自定义的 Windows 内核驱动和 libpcap 库,使 Windows 软件能够捕获原始网络流量,包括无线网络、有线以太网、localhost 流量以及许多 VPN。Npcap 还支持发送原始数据包,从而增强了网络分析和调试的能力。
自 2013 年以来,Npcap 从 WinPcap 库的改进版本发展而来,经过了大量重写,现已成为一个高效、安全且便携的解决方案。它支持所有当前的 Windows 版本,包括 Windows 7 及更高版本,利用 NDIS 6 轻量级过滤器 API 提供更好的性能和兼容性。Npcap 还通过 Windows 驱动签名要求,确保在 Windows 10 等更严格的环境中正常工作。
Npcap 具备多个显著特点。例如,它支持环回数据包捕获和注入,允许用户捕获同一机器上不同服务之间的通信。此外,Npcap 兼容 WinPcap,使得使用 WinPcap 编写的软件可以轻松迁移到 Npcap,而无需进行大规模重构。通过支持 x86、x86-64 和 ARM 架构,Npcap 还能够在新一代设备上运行,如 Microsoft Surface Pro X 平板和三星 Galaxy Book Go 笔记本。
为了增强安全性,Npcap 允许仅限管理员用户进行数据包捕获,这与 UNIX 系统中的权限管理相似。同时,它还启用了 Windows 的地址空间布局随机化(ASLR)和数据执行保护(DEP)等安全特性,确保软件的完整性和安全性。有关 Npcap 的更多详细信息,可以参考 Npcap 用户/开发者指南以及 Npcap 与 WinPcap 的功能比较。
所以,我想到一个旁路方案,不直接在报文的基础上修改,原报文直接丢弃,通过重新注入的方式发送伪造的报文,实现snat。
package main
import (
"fmt"
"log"
"net"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
)
var (
device string = `\Device\NPF_{187ABB88-AD26-4B42-92E1-0A12B8FF1E18}`
err error
handle *pcap.Handle
targetIP = net.ParseIP("192.168.197.135")
routerIP = net.ParseIP("192.168.197.129")
unbutuIP = net.ParseIP("192.168.197.130")
)
func main() {
// 不知道名称得自己先查一下
// devices, err := pcap.FindAllDevs()
// if err != nil {
// log.Fatal(err)
// }
// for _, device := range devices {
// fmt.Printf("Device Name: %s\n", device.Name)
// fmt.Printf("Description: %s\n", device.Description)
// fmt.Println()
// }
// open device capture
handle, err = pcap.OpenLive(device, 65536, true, pcap.BlockForever)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
// only test icmp
filter := fmt.Sprintf(`((src host %s and dst host %s) or (src host %s and dst host %s)) and icmp`, unbutuIP, targetIP, targetIP, routerIP)
err = handle.SetBPFFilter(filter)
if err != nil {
log.Fatal(err)
}
// handle packet
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
var ethLayer layers.Ethernet
var ipLayer layers.IPv4
var icmpLayer layers.ICMPv4
var payload gopacket.Payload
parser := gopacket.NewDecodingLayerParser(
layers.LayerTypeEthernet,
ðLayer,
&ipLayer,
&icmpLayer,
&payload,
)
foundLayerTypes := []gopacket.LayerType{}
err := parser.DecodeLayers(packet.Data(), &foundLayerTypes)
if err != nil {
continue
}
// reject snat packet
if ipLayer.SrcIP.Equal(unbutuIP) && ipLayer.DstIP.Equal(targetIP) {
modifyedIpLayer := ipLayer
modifyedIpLayer.SrcIP = routerIP
options := gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true}
buffer := gopacket.NewSerializeBuffer()
gopacket.SerializeLayers(buffer, options,
ðLayer,
&modifyedIpLayer,
&icmpLayer,
payload,
)
err := handle.WritePacketData(buffer.Bytes())
if err != nil {
fmt.Println("[snat]Failed to send modified packet: ", err)
}
fmt.Printf("snat old: %+v\n", packet)
fmt.Printf("snat new: %+v\n", buffer.Layers())
} else if ipLayer.SrcIP.Equal(targetIP) && ipLayer.DstIP.Equal(routerIP) {
modifyedIpLayer := ipLayer
modifyedIpLayer.DstIP = unbutuIP
options := gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true}
buffer := gopacket.NewSerializeBuffer()
gopacket.SerializeLayers(buffer, options,
ðLayer,
&modifyedIpLayer,
&icmpLayer,
payload,
)
err := handle.WritePacketData(buffer.Bytes())
if err != nil {
fmt.Println("[restore]Failed to send modified packet: ", err)
}
fmt.Printf("restore old: %+v\n", packet)
fmt.Printf("restore new: %+v\n", buffer.Layers())
}
}
}
unbutu机器上的抓包:
目标机器上的抓包:
windows机器上的抓包:
同样达到的效果~~
WinDivert 方案(串行部署)
Npcap 方案(旁路部署)
综合来看,选择哪种方案取决于具体的应用场景和需求。若安全性和性能不太敏感的环境要,Npcap 旁路部署是更好的选择;而在对性能要求较高且系统架构有冗余容灾的情况下,WinDivert 串行方案可能更为合适。
https://learn.microsoft.com/en-us/powershell/module/netnat/new-netnat?view=windowsserver2022-ps
https://blog.csdn.net/bibohaohao/article/details/135602354
https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/setup-nat-network
https://www.kawanoii.cn/Scrip-App/Win11_router_nat.html
https://blog.csdn.net/xqjcool/article/details/115873905
https://pkg.go.dev/github.com/google/gopacket
https://github.com/sbilly/go-windivert2
https://github.com/ffalcinelli/pydivert
https://www.cnblogs.com/wangjq19920210/p/11721281.html
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。