IP spoofing 在Windows上的实践
什么是IP spoofing?
通常情况下,在网络上发包,程序员只需要考虑应用层数据和选择传输层协议,网络层及更底层的数据由操作系统填充。但是在某些情况下(善意的或恶意的),程序员想要手动改变在发送层的srcIP,这种手动改变IP头的srcIP的行为称为IP欺骗(IP spoofing)。

遇到的问题
现在需要测试某软件网络包解析性能,需要模拟发出具有不同源IP和负载数据的UDP包,不同负载数据很容易做到,但是改变源IP则不简单。为了高性能以及方便的使用,需要这个程序使用C++编写并可以原生跑在Windows系统上。
对于这种定制化需求,我们很容易想到使用socket编程,也就是使用raw socket。
在通常情况下,创建socket和发送数据使用Winsock2.h里的这些函数:
SOCKET sock = socket(AF_INET, type, protocol);
sendto(sock, buff, sizeof(buff),flag, addr, sizeof(addr));
其中type和protocol为对应关系如下:
| type | protocol |
|---|---|
| SOCK_STREAM | IPPROTO_TCP |
| SOCK_DGRAM | IPPROTO_UDP |
| SOCK_RAW | IPPROTO_TCP/IPPROTO_UDP/IPPROTO_RAW |
于是想到不指定socket类型和协议,即使用raw socket,这时type为SOCK_RAW对应的protocol为IPPROTO_RAW(当protocol为IPPROTO_RAW时,意味着系统将不自动添加IP头到数据段里),然后自己构造一个IP/UDP包放到buff里,再调用send函数发出。
实现的关键函数如下:
//创建raw socket
SOCKET sock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0, WSA_FLAG_OVERLAPPED);
//设置IP_HDRINCL,拒绝系统自动添加IP头
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)& flag, sizeof(flag));
//按照IP UDP填充buff,此处省略
//发送
sendto(sock, sendBuf, sizeof(ipHeader) + sizeof(tcpHeader));
虽然以上代码可以在非server版的Windows上运行,且不会报任何错误,但是在任务管理器的网络一栏发送速率表现为0,并且Wireshark抓取不到任何包,只有高的CPU占用率。如果发送成功,应该会显示有较大的流量通过。
原因微软为了安全原因进行了限制,具体见下图官网

表达的意思就是只有Windows Server可以使用raw发送自定义IP的包,普通版的Windows不能发自定义IP的包。可以看到Windows只有古早版本(Windows XP SP2之前)和server可以自由的使用raw socket。
那么怎样才能实现我们的需求呢,大概有以下几种方式:
- 修改系统内某个文件或注册表,破解掉限制
- 使用C
winpcap库,使用winpcap需要从数据链路层开始构造数据包,而raw socket只需从网络层开始 - 使用Windows server版
- 使用其他语言和库,其他语言或许也有类似
winpcap的库 - 在Linux上运行,Linux使用UNIX socket,具有完全的socket使用权限
这里先说结论:
| 序号 | 方法 | 可行性 |
|---|---|---|
| 方法1 | 修改Windows系统内某个文件或注册表,破解掉限制 | 未测试 |
| 方法2 | 使用Cwinpcap库 |
可行 |
| 方法3 | 使用Windows server版 | 可行 |
| 方法4 | 使用其他语言和库使用其他语言和库 | 已测试Pyhton+scapy库和Golang+net库均不可行 |
| 方法5 | 在Linux上运行 | 可行 |
实现方法
在Windows Server 2016上运行
这里使用虚拟机,把本机Windows 10之前可以跑但是抓不到包的程序直接拷贝到装了Windows Server 2016虚拟机上,运行后可以抓到包。

小插曲
相同程序运行在没有装Visual Studio 2019版本及以上时Windows Server系统会报错:找不到vcruntime140.dll,这时需要把编译程序的机器上的“C:\Windows\System32”和“C:\Windows\SysWOW64“里的vcruntime140.dll复制到运行机器对应的目录里,然后在命令行里运行 regsvr32 vcruntime140.dll 即可解决。
在Linux上运行
Linux系统上具有完全的Socket控制权,可以通过raw socket直接发送数据包。
在Windows10上运行
在Windows上通过pcap.h实现。
什么是WinPcap?

具体操作流程就是下载winpcap库的dll和头文件,然后在项目里包含这两个文件,winpcap官网有完整的文档可以参考,此处不再赘述。通过构造以太网帧头、IP头、UDP头最后通过win pcap发送包,最后在本机Windows10系统上跑,结果如下:

小知识
大名鼎鼎的Wireshark也使用的是Winpcap库,在安装Wireshark时通常会一并安装Winpcap。
后记
为什么通过winpcap可以在Win10上自由的使用Socket?和系统的Winsock.h有何区别?
根据官网文档WinPcap: WinPcap internals
可以知道:实际上winpcap库实现了一个网卡驱动程序,相当于绕过了系统本身的驱动,直接去操控网卡的收发。

可以通过winpcap发送组播包/其他包吗?
在文档中有这样一段话:
WinPcap receives and sends the packets independently from the host protocols, like TCP-IP. This means that it isn’t able to block, filter or manipulate the traffic generated by other programs on the same machine: it simply “sniffs” the packets that transit on the wire. Therefore, it does not provide the appropriate support for applications like traffic shapers, QoS schedulers and personal firewalls.
说明winpcap并不会适配任何协议的内容,换言之,它只是个无情的传输机器,不考虑发送的是什么数据流。对于TCP,不考虑拥塞、分包、ACK…,同样的也不管发送的目的IP。但是互联网的规则还是要遵守的,比如MAC地址,一般情况下是目的MAC地址写本地网关就好,不要变太多,不然会对交换机的性能有影响。
特别的,对于组播地址,目的MAC是根据一定的规则确定的,否则组播网络无法接收数据。
在Win10上通过winpcap发包的性能如何?
在本机Windows10、i5-8500、16GB RAM、1Gbps网卡上使用winpcap的pcap_sendpacket函数连续发送大小为700Bytes的UDP包,网卡发送速率为140-150Mbps。测试程序CPU占用10-20。同时运行7个测试程序可以跑满网卡传输速率(1Gbps)。
性能瓶颈在于CPU和网卡性能以及程序的发包数量。CPU和网卡性能越高,单位时间内发包数量越少,最大发送速率越大。总的来说就是减小写入网卡时的中断时间。优化方法是可以换更高性能的电脑,或者可以使用winpcap的发送队列函数pcap_sendqueue_transmit,一次性写入很多包然后合并发送。因为pcap_sendpacket使用简单且性能已经满足需求,发送队列功能未测试。