使用WinDivert抓包,开发windows弱网工具(C++版)

介绍

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文件夹
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210630221526393.png

  • 在项目中包含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
打印如下:
在这里插入图片描述

总结

本文主要用于学习使用,具体的业务逻辑可以根据自己的需求去做,接入流程挺容易出问题的,欢迎留言,看到定会回复。

### 解决 WinDivert64 工具Windows 中因被占用而无法删除的问题 当遇到像 WinDivert64 这样的驱动程序或系统组件因为正在使用而无法删除的情况时,通常是因为某些进程仍然依赖于它。为了安全有效地移除这些组件,建议按照以下方法操作: #### 方法一:重启计算机进入安全模式后卸载 对于大多数情况下,如果某个服务或驱动正被其他应用所调用,则可以在启动过程中断开这种关联来解决问题。具体来说,在Windows的安全模式下启动操作系统能够阻止大部分非必要的第三方软件和服务加载。 - **进入安全模式**:可以通过按住Shift键的同时点击电源按钮中的“重新启动”,随后选择疑难解答 -> 高级选项 -> 启动设置 -> 重启 来访问启动菜单。 - **禁用WinDivert64**:一旦处于安全模式中,可以尝试再次停止与之相关的所有服务,并最终完成其卸载过程[^2]。 #### 方法二:终止相关进程并强制删除文件 有时即使已经停止了可见的服务实例,仍可能存在后台线程继续持有对目标文件句柄的锁定状态。此时就需要借助一些专门工具来进行更深入的操作。 - 使用Process Explorer 或 Handle Utility 查找哪些进程打开了 `WinDivert64.sys` 文件; ```powershell handle.exe -a | findstr /i "windivert" ``` - 终止那些不必要的进程;注意要谨慎行事以免影响系统的稳定性。 - 如果上述步骤完成后依旧无法正常删除文件的话,还可以考虑利用命令提示符下的 `del` 命令配合 `/f` 参数强行覆盖删除指定路径下的文件(需确保已获得管理员权限),不过这一步骤存在风险,请务必小心执行。 #### 方法三:使用专用清理工具 考虑到手动排查可能存在的复杂性和不确定性,也可以寻找专业的系统维护类应用程序帮助自动识别残留项并彻底清除它们。例如提到过的Clumsy就具备类似功能[^3],尽管这不是专门为了解决这个问题设计的应用,但在处理顽固文件方面或许能提供一定辅助作用。 无论采取哪种方式,都强烈建议先备份重要数据以防万一,并在整个过程中保持足够的耐心以避免误操作带来的潜在损害。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值