使用WinDivert抓包,开发windows弱网工具(C++版)
介绍WinDivert最简单的接入教程,以及使用的一些心得目录介绍前言一、WinDivert是什么?二、使用步骤1.下载库2.接入项目3.使用测试:总结前言-最近开发的实时战斗游戏,在国外某国测试了,有很多其他国家的也想体验,通过梯子来到了我们游戏,我们发现他们在直播玩的时候,拉扯很严重,延迟较高,我们当时在这种极度弱网下表现并不是很好,因此打算找一个弱网工具模拟这种情况。经过分析,我发现主要原因
介绍
WinDivert最简单的接入教程,以及使用的一些心得
前言
-最近开发的实时战斗游戏,在国外某国测试了,有很多其他国家的也想体验,通过梯子来到了我们游戏,我们发现他们在直播玩的时候,拉扯很严重,延迟较高,我们当时在这种极度弱网下表现并不是很好,因此打算找一个弱网工具模拟这种情况。经过分析,我发现主要原因在于UDP包的堆积,然后大量同时到达导致。
曾经用过一个叫NEWT(Network Emulator for Windows Toolkit)模拟,效果挺好,但是只支持win7,现在我们公司以及全部普及win10了,win10下只能断网,但是再难找到这么好用的工具了,因此,打算自己造轮子,做一个弱网工具。那么难点主要在于怎么捕获通过windows的包?WinDivert就是一个最好的工具
提示:以下是本篇文章正文内容,下面案例可供参考
一、WinDivert是什么?
WinDivert是一个功能强大的用户模式capture/sniffing/modification/blocking/re-injection工具,适用于Windows 7、Windows 8和Windows 10。WinDivert可用于实现用户模式包过滤器、包嗅探器、防火墙、NAT、VPN、隧道应用程序等,而无需编写内核模式代码。
二、使用步骤
由于还没上升到需要改源码的程度,因此以下为直接使用库的方法,并没有去自己编译源码,后续如果有需求可以自己编译
1.下载库
主页地址:https://reqrypt.org/windivert.html
进入网站以后点击如下,直接下载WinDivert-2.2.0-A.zip,当然你也可以下载源码查看,编译
下载解压后内容如下
2.接入项目
代码如下(示例):
-
新建一个C++控制台应用程序
-
在项目目录下新增Include文件夹Src文件夹与Libs文件夹
-
在项目中包含Include文件夹Src文件夹,方法如下:
-
在链接器中包含Libs文件夹:
- 我这里使用x64进行测试,因此把x64/WinDivert.lib 拷贝到Libs文件夹,然后在将Lib库包含在链接器中:
- 我这里选择在64位下测试,因此把调试模式设置问debug x64:
- 预先生成一次,会在项目指定的生成目录下生成x64文件,以及.exe,如下,
- 把对应的x64/WinDivert.dll,x64/WinDivert64.sys拷贝到该执行目录下:
- 把include/windivert.h 放到前面配置的include目录,然后在项目中把此文件包含在项目,配置完成。
3.使用
先包含头文件:
#include "windivert.h"
创建一个带网络包筛选器的WinDivert handle:
HANDLE divertHandle = WinDivertOpen("inbound and udp and ip.SrcAddr == 10.234.36.130", WINDIVERT_LAYER_NETWORK, 1, 0);
if (divertHandle == INVALID_HANDLE_VALUE)
{
std::cout << "divertHandle == INVALID_HANDLE_VALUE : error " << GetLastError();
return 0;
}
第一个参数筛选方式,这里有很多筛选方式,用的最多的就是:
- inbound / outbound :表示处理收包 跟发包
- udp / tcp 筛选不同的传输层协议
- ip.* ip地址筛选
- udp.* / tcp.* 针对某一传输层进行更具体的筛选
第二个参数,枚举类型,layer选择如下:
/*
* WinDivert layers.
*/
typedef enum
{
WINDIVERT_LAYER_NETWORK = 0, /* Network layer. */
WINDIVERT_LAYER_NETWORK_FORWARD = 1,/* Network layer (forwarded packets) */
WINDIVERT_LAYER_FLOW = 2, /* Flow layer. */
WINDIVERT_LAYER_SOCKET = 3, /* Socket layer. */
WINDIVERT_LAYER_REFLECT = 4, /* Reflect layer. */
} WINDIVERT_LAYER, *PWINDIVERT_LAYER;
第三个参数,优先级,用于定义多个handle,值越大,优先级越高:
第四个参数,flag,可以对抓取的包进行更精确的控制,暂不详讲:
/*
* WinDivert flags.
*/
#define WINDIVERT_FLAG_SNIFF 0x0001
#define WINDIVERT_FLAG_DROP 0x0002
#define WINDIVERT_FLAG_RECV_ONLY 0x0004
#define WINDIVERT_FLAG_READ_ONLY WINDIVERT_FLAG_RECV_ONLY
#define WINDIVERT_FLAG_SEND_ONLY 0x0008
#define WINDIVERT_FLAG_WRITE_ONLY WINDIVERT_FLAG_SEND_ONLY
#define WINDIVERT_FLAG_NO_INSTALL 0x0010
#define WINDIVERT_FLAG_FRAGMENTS 0x0020
更多细节,可以下载源码,通过看examples理解:
这里有一些坑,可以通过打印GetLastError() 的错误号处理:
可以对照错误,知道为什么,我遇到的主要是要管理员权限,错误号5,可以通过使用用管理员权限运行VS解决。还有就是第一个参数筛选方式,错误号87
接下来接收捕获的数据的class:
#define DIVERT_MAX_PACKETSIZE 0xFFFF
#define DIVERT_INIT_PACKETSIZE 128
class DivertPacket
{
public:
DivertPacket()
{
Reset();
}
~DivertPacket()
{
}
char packet[DIVERT_MAX_PACKETSIZE];
UINT packetLength;
WINDIVERT_ADDRESS addr;
void Reset()
{
packetLength = 0;
memset(packet, 0, DIVERT_MAX_PACKETSIZE);
}
};
数据处理的代码:
DivertPacket* dPacket = new DivertPacket();
while (true)
{
if (WinDivertRecv(divertHandle, dPacket->packet, DIVERT_MAX_PACKETSIZE, &dPacket->packetLength, &dPacket->addr))
{
/*
PWINDIVERT_IPHDR ppIpHdr;
PWINDIVERT_UDPHDR udpHDR;
WinDivertHelperParsePacket(dPacket->packet, dPacket->packetLength, &ppIpHdr, nullptr, nullptr, nullptr, nullptr, nullptr, &udpHDR, nullptr, nullptr, nullptr, nullptr);
if (ppIpHdr != NULL && udpHDR != NULL)
{
memset(buffer, 0, 64);
WinDivertHelperFormatIPv4Address(ppIpHdr->SrcAddr, buffer, 64);
spdlog::debug("ip src", buffer);
memset(buffer, 0, 64);
WinDivertHelperFormatIPv4Address(ppIpHdr->DstAddr, buffer, 64);
spdlog::debug("ip dst", buffer);
spdlog::debug("port srt", to_string(udpHDR->SrcPort));
spdlog::debug("port dst", to_string(udpHDR->DstPort));
}
*/
WinDivertHelperCalcChecksums(dPacket->packet, dPacket->packetLength, &dPacket->addr, 0);
if (!WinDivertSend(divertHandle, dPacket->packet, dPacket->packetLength, NULL, &dPacket->addr))
{
// Handle send error
std::cout << "WinDivertSend : error " << GetLastError();
continue;
}
spdlog::debug("packege len : {}", dPacket->packetLength);
spdlog::debug("Timestamp : {}", dPacket->addr.Timestamp);
spdlog::debug("now : {}", GetTicks());
//std::cout <<"packege len : " << dPacket->packetLength <<"\n";
memset(dPacket, 0, DIVERT_MAX_PACKETSIZE);
}
else
{
std::cout << "WinDivertRecv : error " << GetLastError();
continue;
}
Sleep(10);
}
spdlog可以看我之前一篇文章,接入的c++日志库,注释部分为具体解析包的代码
WinDivertRecv 与 WinDivertSend 分别为捕获与再次转发包的代码。
具体我们对包的处理还在做,属于机密就不公开了,思路是:链表保存捕获的数据包,增加一个多模块的处理,通过后的,转发给本地应用或者发出,否则保存直到满足每一个module的条件了。
更加详细的说明可以去官方查看:
官方:https://reqrypt.org/windivert.html
github:https://github.com/basil00/Divert/tree/master/examples
文档:file:///E:/study/Document/WinDivert-2.2.0-A/WinDivert-2.2.0-A/doc/WinDivert.html
测试:
修改筛选器,14.215.177.38 是百度的网址:
HANDLE divertHandle = WinDivertOpen("inbound and ip.SrcAddr == 14.215.177.38", WINDIVERT_LAYER_NETWORK, 1, 0);
然后我们在cmd中:ping 14.215.177.38
打印如下:
总结
本文主要用于学习使用,具体的业务逻辑可以根据自己的需求去做,接入流程挺容易出问题的,欢迎留言,看到定会回复。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)