# 基于 WFP 实现的网络监控
WFP 全称 Windows Filtering Platform,即 Windows 过滤平台。随着网络的高速发展,网络安全问题越来越受到重视,同时随着 WindowsOS 的快速更新换代,以往的网络过滤框架已经不能满足需要,于是导致了 WFP 的出现。WFP 是 VISTA 中引入的 API 集,也是从 VISTA 系统后新增的一套系统 API 和服务,在新版的操作系统中,开发人员可以通过这套 API 集将 Windows 防火墙嵌入到开发软件中,可以恰到好去的处理 Windows 防火墙的一些设置。
WFP 为网络数据包过滤提供了架构支持,是微软在 VISTA 之后,替代之前的基于包过滤的防火墙设计,如 Transport DriverInterface(TDI)过滤、Network Driver InterfaceSpecification(NDIS)过滤、Winsocklayered Service Providers(LSP)。
在 VISITA 及以后的系统中,系统防火墙的过滤钩子驱动不再适用,只能使用 WFP。WFP 允许程序员编写代码和操作系统的网络协议栈进行交互,同时在网络数据到达最后的归宿前,将数据进行过滤,拦截,修改等。流程如下图所示。

Filter Engine 是 WFP 的核心组件,用来过滤 TCP/IP 协议的网络数据。在 TCP/IP 协议栈中存在 Filtering Layer,把网络数据传递到 Filter Engine 中处理。如果 Filtering Layer 中 Filter 的所有过滤条件都满足,Filter Engine 就会执行 Filter 指定的过滤操作。其中,Filter 可以指定 Callout 去完成特定的过滤操作。Callout 是 WFP 的功能拓展,驱动程序需要将 Callout 注册到 Filter Engine 中,这样 Filter Engine 才能调用 Callout 函数去处理网络数据。
接下来,本文将介绍基于 WFP 实现监控系统上网络连接情况,并阻止指定进程建立通信连接。
### 实现过程
在调用 WFP 函数开发程序之前,先来介绍下程序所需要包含的头文件以及导入的库函数。
要使用 WFP 框架,就需要向驱动程序中加入头文件以及导入库文件,头文件有:
```c++
# include <fwpsk.h>
# include <fwpmk.h>
```
在链接器中添加库文件 fwpkclnt.lib 和 uuid.lib 库文件:
```c++
属性-->链接器-->输入-->附加依赖库,添加fwpkclnt.lib和uuid.lib库文件
```
由于程序使用的是 NDIS6,所以,需要在预处理器中添加预处理指令:
```c++
属性-->C/C++ -->预处理器,添加“NDIS_SUPPORT_NDIS6”
```
经过上述的设置,接下来,就可以进行 WFP 开发了。
在驱动程序创建好驱动设备之后,就可以调用 FwpsCalloutRegister 函数向 Filter Engine 注册一个 Callout,即使 Filter Engine 还没有启动。FwpsCalloutRegister 函数的最后一个参数是一个 GUID 的数据类型,该数值表示 Callout 的 Key,代表了一个 Callout,具有唯一性。
其中,WFP 一次性要注册的 Callout 函数不是 1 个,而是 3 个:
- **notifyFn**:负责处理 notifications
- **classifyFn**:负责处理 classifications
- **flowDeleteFn**:负责处理 flow deletions,是可选的
为了便于理解,可以认为 Callout 函数相当于回调函数,classifyFn 相当于 pre 事前回调,notifyFn 和 flowDeleteFn 相当于事后回调函数。
WFP API 是面向会话(Session)的,大多数函数调用是在会话的上下文中进行。驱动程序可以通过调用 FwpmEngineOpen 函数创建新会话,调用 FwpmEngineClose 函数来结束会话。
WFP API 同时具有事务性,大多数函数调用是在事务的上下文中进行。驱动程序可以调用 FwpmTransactionBegin 函数开始事务,调用 FwpmTransactionCommit 函数提交事务,调用 FwpmTransactionAbort 来终止事务。
驱动程序中,每个会话只能进行一个事务。如果在第一个事务提交或者中止之前就开始第二个事务,程序则会返回错误。
那么,一个 WFP 框架驱动程序大体是这样子的:
1. 首先,调用 FwpsCalloutRegister 函数根据驱动设备对象向 Filter Engine 注册一个 Callout,指明 Callout Key 以及 3 个 Callout 函数 notifyFn、classifyFn 和 flowDeleteFn
2. 然后,调用 FwpmEngineOpen 函数创建一个 WFP 会话句柄,并调用 FwpmTransactionBegin 函数开始事务
3. 接着,创建过滤点。先调用 FwpmCalloutAdd 函数将前面注册好的 Callout 添加到会话,注意 Callout Key 要保持一致;再调用 FwpmFilterAdd 函数添加 Filter,注意设置过滤层和 Callout Key。本文要实现的是过滤进程联网的功能,而且联网一般都是用 IPV4 协议,所以过滤条件标志设置为 FWPM_LAYER_ALE_AUTH_CONNECT_V4。同时,必须为这个过滤条件标志指定一个 GUID,该 GUID 值任意,只要在系统范围内不重复
4. 最后,调用 FwpmTransactionCommit 函数提交事务,让上述操作开始生效
经过上述 4 个步骤,就可以完成 Callout 的注册以及设置过滤条件。当满足所有过滤条件的数据包出现的时候,系统便会调用 Callout 函数 notifyFn 进行处理。
当程序成功注册回调函数之后,就可以在 notifyFn 函数中实现对网络连接情况进行监控,还能对连接进行控制。其中,回调函数第 1 个参数 FWPS_INCOMING_VALUES0 中存储着网络连接的 IP、端口、协议等信息;第 2 个参数 FWPS_INCOMING_METADATA_VALUES0 存储着进程 ID、路径等信息;第 3 个参数 FWPS_CLASSIFY_OUT0 还可以控制是允许连接还是拒绝连接。
当程序不用 WFP 的时候,就要调用 FwpmFilterDeleteById、FwpmCalloutDeleteById 以及 FwpsCalloutUnregisterById 函数把添加的过滤器对象和回调函数删除掉,并调用 FwpmEngineClose 关闭 WFP 会话。
注册 Callout 的实现代码如下所示。
```c++
// 注册Callout
NTSTATUS RegisterCallout(
PDEVICE_OBJECT pDevObj,
IN const GUID *calloutKey,
IN FWPS_CALLOUT_CLASSIFY_FN classifyFn,
IN FWPS_CALLOUT_NOTIFY_FN notifyFn,
IN FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN flowDeleteNotifyFn,
OUT ULONG32 *calloutId)
{
NTSTATUS status = STATUS_SUCCESS;
FWPS_CALLOUT sCallout = { 0 };
// 设置Callout
sCallout.calloutKey = *calloutKey;
sCallout.classifyFn = classifyFn;
sCallout.flowDeleteFn = flowDeleteNotifyFn;
sCallout.notifyFn = notifyFn;
// 注册Callout
status = FwpsCalloutRegister(pDevObj, &sCallout, calloutId);
if (!NT_SUCCESS(status))
{
ShowError("FwpsCalloutRegister", status);
return status;
}
return status;
}
```
创建过滤点的实现代码如下所示。
```c++
// 设置过滤点
NTSTATUS SetFilter(
IN const GUID *layerKey,
IN const GUID *calloutKey,
OUT ULONG64 *filterId,
OUT HANDLE *engine)
{
HANDLE hEngine = NULL;
NTSTATUS status = STATUS_SUCCESS;
FWPM_SESSION session = { 0 };
FWPM_FILTER mFilter = { 0 };
FWPM_CALLOUT mCallout = { 0 };
FWPM_DISPLAY_DATA mDispData = { 0 };
// 创建Session
session.flags = FWPM_SESSION_FLAG_DYNAMIC;
status = FwpmEngineOpen(NULL,
RPC_C_AUTHN_WINNT,
NULL,
&session,
&hEngine);
if (!NT_SUCCESS(status))
{
ShowError("FwpmEngineOpen", status);
return status;
}
// 开始事务
status = FwpmTransactionBegin(hEngine, 0);
if (!NT_SUCCESS(status))
{
ShowError("FwpmTransactionBegin", status);
return status;
}
// 设置Callout参数
mDispData.name = L"MY WFP TEST";
mDispData.description = L"WORLD OF DEMON";
mCallout.applicableLayer = *layerKey;
mCallout.calloutKey = *calloutKey;
mCallout.displayData = mDispData;
// 添加Callout到Session中
status = FwpmCalloutAdd(hEngine, &mCallout, NULL, NULL);
if (!NT_SUCCESS(status))
{
ShowError("FwpmCalloutAdd", status);
return status;
}
// 设置过滤器参数
mFilter.action.calloutKey = *calloutKey;
mFilter.action.type = FWP_ACTION_CALLOUT_TERMINATING;
mFilter.displayData.name = L"MY WFP TEST";
mFilter.displayData.description = L"WORLD OF DEMON";
mFi