前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Windos10及以上系统实现SNAT功能

Windos10及以上系统实现SNAT功能

原创
作者头像
于顾而言SASE
发布2025-02-05 15:51:43
发布2025-02-05 15:51:43
10000
代码可运行
举报
文章被收录于专栏:网络安全随笔网络安全随笔
运行总次数:0
代码可运行

1. 前言

在Linux生态中,SNAT(Source Network Address Translation,源网络地址转换)的实现堪称优雅:通过iptablesnftables工具链,开发者只需数行命令即可完成地址伪装与流量重定向。然而,当场景切换到Windows平台时,这一基础网络功能的实现却面临双重困境——系统架构的天然差异版本功能的严格阉割

Windows Server系列(如Windows Server 2016/2019/2022)通过内置的NAT网关组件PowerShell命令集,为SNAT功能提供了官方支持。但面向消费级市场的Windows 10/11(家庭版、专业版等)则被刻意移除了核心网络服务模块,其网络栈仅保留最基础的NAT客户端功能。这种人为的"功能分层"策略,使得开发者不得不在缺乏原生工具链的困境中寻求迂回方案。

本文将以技术可行性为脉络,剖析Windows平台实现SNAT的底层逻辑,并介绍几种可行的解决方案,为读者提供参考和借鉴。我们也将分析每种方案的优缺点,帮助读者根据自身需求选择最合适的方案,最终在Windows平台上成功实现SNAT功能

2. Snat的本质

Snat本质是做地址转换, 主要用于将数据包的源IP地址/端口号修改为另一个IP地址/端口号,这样做有如下好处:

  • IP地址隐藏:SNAT可以隐藏内部网络的真实IP地址,增强网络安全性,防止外部攻击者直接访问内部设备。
  • 地址复用:通过将多个内部设备的源IP地址转换为一个公网IP地址,SNAT有效地节省了IP地址资源,特别是在IPv4地址紧缺的情况下。
  • 会话跟踪:SNAT能够跟踪每个会话的状态,确保返回的数据包能够正确地路由回发送请求的设备。

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的工作过程如下:

  • 数据包发送:当内部网络中的设备(如计算机或服务器)向外部网络发送数据包时,数据包的源IP地址是设备的私有IP地址。
  • 地址转换/端口映射:路由器或防火墙上的SNAT功能会拦截这个数据包,并将其源IP地址替换为外网IP或地址池中的IP。同时,SNAT还会修改源端口号,确保即使多个内部设备通过同一个公网IP地址发送请求,返回的数据包也能正确地路由到原始发送设备。
  • 返回数据包处理:当外部网络响应时,数据包会命中snat session,接着SNAT会根据之前的转换记录,将其源IP地址和端口号恢复为内部设备的私有IP地址和端口号,从而将数据包正确地转发回内部网络。

3. Windivert

3.1. what

WinDivert(Windows Packet Divert)是一个用于Windows 7、Windows 8和Windows 10的用户模式数据包拦截库。它允许用户在不需要管理员权限的情况下捕获、修改和丢弃网络数据包。WinDivert广泛应用于实现用户模式的数据包过滤器、嗅探器、防火墙、NAT、VPN、入侵检测系统(IDS)、隧道应用等。

3.1.1. 主要功能

WinDivert具备以下主要功能:

  • 捕获网络数据包:可以实时捕获通过Windows网络堆栈的数据包。
  • 过滤或丢弃数据包:根据用户定义的规则,决定是否允许数据包通过。
  • 嗅探网络数据包:监控网络流量,分析数据包内s容。
  • 重新注入数据包:可以对捕获的数据包进行修改后重新发送。
  • 修改数据包:直接修改数据包的内容,以满足特定需求。

3.1.2. 特性支持

WinDivert支持以下特性:

  • 数据包拦截、嗅探和丢弃模式:灵活的工作方式,满足不同应用需求。
  • 环回流量支持:可以处理来自本地的流量。
  • 全IPv6支持:兼容现代网络协议。
  • 简单而强大的API:易于使用的接口设计。
  • 高级过滤语言:提供灵活的过滤规则。
  • 过滤优先级:允许用户设置过滤规则的优先级。
  • 自由使用:根据GNU较宽通用公共许可证(LGPLv3)自由使用。

有关更多信息,请参见文档 doc/windivert.html

3.1.3. 架构概述

WinDivert的基本架构如下所示:

  1. 数据包拦截:新的数据包进入网络堆栈时,由WinDivert.sys驱动程序拦截。
  2. 过滤处理
  • 如果数据包与用户定义的过滤器匹配,则数据包被导入到用户程序中。
  • 如果不匹配,数据包将正常继续处理。

3. 数据包处理:用户程序可以选择丢弃、修改或重新注入数据包。

3.2. Snat Demo

3.2.1. 数据流图

3.2.2. Code

代码语言:javascript
代码运行次数:0
复制
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机器上的抓包:

目标靶机上的抓包:

灰常完美~~

4. Npcap

4.1. What

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。

4.2. Snat Demo

4.2.1. 数据流图

4.2.2. Code

代码语言:javascript
代码运行次数:0
复制
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,
			&ethLayer,
			&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,
				&ethLayer,
				&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,
				&ethLayer,
				&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机器上的抓包:

同样达到的效果~~

5. 方案比对

WinDivert 方案(串行部署)

  • 优点
    • 实现简单,易于集成到现有系统中。
    • 允许对数据包进行细粒度的控制和修改。
  • 缺点
    • 由于是串行处理,如果match的颗粒度不好,会导致非snat的数据传输中断

Npcap 方案(旁路部署)

  • 优点
    • 提供更高的安全性,旁路部署使得网络流量不会直接干扰主传输路径。
    • 能够实时捕获和分析数据包,而不影响正常的网络通信。
  • 缺点
    • 旁路部署在性能上略显不足,尤其是在高流量环境下,可能导致延迟或性能下降。

综合来看,选择哪种方案取决于具体的应用场景和需求。若安全性和性能不太敏感的环境要,Npcap 旁路部署是更好的选择;而在对性能要求较高且系统架构有冗余容灾的情况下,WinDivert 串行方案可能更为合适。

Reference

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

https://www.reqrypt.org/windivert.html

https://npcap.com/

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 前言
  • 2. Snat的本质
  • 3. Windivert
  • 3.1. what
    • 3.1.1. 主要功能
    • 3.1.2. 特性支持
    • 3.1.3. 架构概述
  • 3.2. Snat Demo
    • 3.2.1. 数据流图
    • 3.2.2. Code
  • 4. Npcap
  • 4.1. What
  • 4.2. Snat Demo
    • 4.2.1. 数据流图
    • 4.2.2. Code
  • 5. 方案比对
  • Reference
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档