NDIS网络过滤驱动

为什么选择NDIS驱动过滤?

如果是绑定在协议驱动,那比较麻烦,可能说要各个协议驱动都加上一个过滤驱动设备,才能监视到。协议驱动能截获接收到的包,但无法截获所有发送的包

仅拦截特定协议: 协议驱动只能拦截特定协议的数据包,例如 TCP 或 UDP 数据包。 它无法拦截其它协议的数据包,例如 ICMP 数据包。 这使得它无法实现对所有网络流量的全面监控和过滤。

但是这些协议驱动最终都要把数据发送到NDIS中间层驱动,所以我们在这里拦截能够拦截到所有的

NDIS组成

小端口(Miniport)是 NDIS 驱动架构中的一个关键组件。它是一个底层驱动,负责与具体的硬件设备(如网卡)交互,同时为上层协议驱动提供服务。

协议部分用于与上层协议驱动交互,接收上层数据并传递给小端口。

小端口部分则与底层驱动交互,负责将数据发送到网络或从网络接收数据。

为了让协议部分和小端口部分协同工作,必须通过 关联 的方式将它们绑定在一起。

通过调用 NdisIMAssociateMiniport 函数将协议部分(由 ProtHandle 标识)与小端口部分(由 DriverHandle 标识)关联。

我们要写的就是中间层驱动,夹在协议驱动和小端口驱动之间,这样可以在协议驱动这里拦截收包,在小端口这里拦截发包

NDIS DriverEntry:

**1.初始化包装句柄: **

函数:NdisMInitialzeWrapper

初始化 NDIS 驱动程序的上下文环境,分配内存和资源,为后续驱动注册和操作做准备。

2.注册小端口特征并获得DriverHandle:

函数:NdisIMRegisterLayeredMiniport

注册小端口特性,定义底层微型端口(Miniport)的功能和行为。

3.注册协议特征并获得ProtHandle:

函数:NdisRegisterProtocl

  • 注册协议部分特性,定义协议的行为,如接收数据包、发送数据包和协议初始化等。
  • 返回一个 ProtHandle,用于标识协议部分。

4.将DriverHandle与ProtHandle关联:

函数:NdisIMAssciateMiniport

作用

将小端口和协议部分进行关联,创建一个完整的中间驱动程序。

中间驱动通过该步骤实现对上下层驱动之间的数据流的完全控制。

5.收包处理(协议部分的PtReceive())

函数:PtReceive

作用:

协议部分的接收数据回调函数,当数据包到达时触发。

对接收的数据包进行处理、分析或修改。

6.发包处理(小端口部分的MpSend())

函数:MpSend

作用:

小端口部分的发送数据回调函数,当需要发送数据时触发。

中间驱动可以对发送数据包进行修改或拦截。

7.协议分析(接收,拒绝,修改)

接收数据分析

  • PtReceive 函数中,解析数据包协议头部。
  • 判断是否需要接受该数据包,或拦截数据包进行进一步处理。

拒绝数据

在协议部分,通过返回特定状态拒绝数据包。或直接丢弃数据包。

修改数据

对接收到的数据包内容(如 IP 地址、端口号等)进行修改后,传递给上层或下层。

tips:

IP地址是从IP头拿,端口是从TCP,UDP头拿

从项目看NDIS过滤驱动

GoodstudyChina/FireWall–NDIS

1736515562715

passthru.c : 这是 主模块,负责驱动的初始化、卸载和调度核心功能。它是整个驱动的入口点,协调协议和小端口部分的交互。

miniport.c: 这是 小端口(Miniport)模块,负责模拟一个虚拟网卡,并处理底层数据传输。 拦截数据包发送

protocol.c : 这是 协议(Protocol)模块,负责与上层协议驱动(如 TCP/IP 协议栈)交互。 拦截数据包接收

然后和我们之前学的创建设备对象,创建符号连接不同

这里开始使用的是NdisMRegisterDevice创建设备对象

NDIS_MINIPORT_CHARACTERISTICS MChars;

1736519632106

这个就是 miniport用来拦截发包的函数,为什么一个为空呢??

上面的英文翻译过来是:

应指定 Send 或 SendPackets 处理程序。如果指定了 SendPackets 处理程序,则忽略 SendHandler

在NDIS层,已经没有进程,IRP的概念了,那么这个包属于哪个进程??这个咋整

在这里,PtReceiveCompletePtTransferDataCompletePtReceive这三回调函数是用来拦截收包的

1736520271953

因为没有实践,先研究到这了….如果用到NDIS驱动开发,还会继续往下更新

WFP网络过滤驱动

WFP框架的组成

WFP:Windows Filter Platform

WFP框架本身已经实现了过滤的功能,缺的只是规则

WFP结构大致长这样

1736562698489

WFP系统是有数据过滤引擎的防火墙,没有规则。

编写用户层的程序给WFP引擎设置规则

编写核心态的callout驱动处理WFP抓到的网络数据包做深度处理

WFP中几个重要的概念:

Layers(层):

正如上面的图片所示

WFP 中的层表示网络协议栈中不同的数据处理阶段,它们包括:

Application Layer Enforcement (ALE)

Stream 层

Transport 层

Datagram 层

网络接口层

NDIS层

层选择的依据

你选择的层主要取决于想要实现的功能:

  1. 控制特定应用的网络连接:
    • 使用 ALE_AUTH_CONNECTALE_AUTH_RECV_ACCEPT。(ALE层)
  2. 检查和修改传输层数据(TCP/UDP 数据包):( Transport 层)
    • 使用 FWPM_LAYER_TRANSPORT
  3. 深度解析数据流(如 HTTP 或 TLS 内容):( Stream 层 )
    • 使用 FWPM_LAYER_STREAM
  4. 控制 UDP 数据报: ( Datagram 层 )
    • 使用 FWPM_LAYER_DATAGRAM_DATA
  5. 在网络接口上处理入站和出站的原始 IP 数据包。 ( 网络接口层 )
  • FWPM_LAYER_INBOUND_IPPACKET_V4/V6FWPM_LAYER_OUTBOUND_IPPACKET_V4/V6:

例如 FWPM_LAYER_ALE_AUTH_CONNECT_V4应用层授权连接层(ALE Authorization Connect Layer) , 专门用于 IPv4 的网络连接请求。选择这一层,可以在网络连接建立之前拦截并检查连接请求,决定是否允许或阻止连接。

FWPM_LAYER_ALE_AUTH_CONNECT_V4 层的 ClassifyFn 回调函数中,可以访问以下关键信息:

  1. 源和目标 IP 地址:

    • 源 IP 地址(incomingPacket->incomingValues[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_ADDRESS])。
    • 目标 IP 地址(incomingPacket->incomingValues[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_ADDRESS])。
  2. 源和目标端口:

    • 源端口LocalIp = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_ADDRESS].value.uint32;

    • 目标端口inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_ADDRESS].value.uint32;

  3. 协议类型:

    • 协议类型(如 TCP、UDP 等),通过 incomingPacket->incomingValues[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_PROTOCOL] 获取。
  4. 进程信息:

    • 发起连接的进程 ID(PID)。inMetaValues->processId
    • 进程路径和名称:inMetaValues->processPath
  5. 用户信息:

    • 发起连接的用户 SID(安全标识符)。
    • 用户名称(可以通过 SID 进一步查询)。
  6. 连接方向:

    • 连接是入站(Inbound)还是出站(Outbound)。
  7. 其他网络层信息:

    • 网络接口信息(如接口索引)。
    • 其他与连接相关的元数据。

一个 中可以有多个 子层

可以这样添加子层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <fwpmu.h>

// 定义子层 GUID
const GUID SubLayer_A = { /* GUID for SubLayer A */ };
const GUID SubLayer_B = { /* GUID for SubLayer B */ };

// 添加子层
void AddSubLayers(HANDLE engineHandle) {
FWPM_SUBLAYER0 subLayerA = {0};
subLayerA.subLayerKey = SubLayer_A;
subLayerA.displayData.name = L"SubLayer A - Firewall Rules";
subLayerA.displayData.description = L"Handles firewall-related rules";
subLayerA.flags = 0;
subLayerA.weight = 100; // 优先级高

FWPM_SUBLAYER0 subLayerB = {0};
subLayerB.subLayerKey = SubLayer_B;
subLayerB.displayData.name = L"SubLayer B - Logging Rules";
subLayerB.displayData.description = L"Handles logging-related rules";
subLayerB.flags = 0;
subLayerB.weight = 200; // 优先级低

FwpmSubLayerAdd0(engineHandle, &subLayerA, NULL);
FwpmSubLayerAdd0(engineHandle, &subLayerB, NULL);
}

SubLayer_ASubLayer_B 分别表示防火墙和日志记录规则。

weight 决定了子层的优先级。

当每个子层符合注册的filter(后面提到)的条件后,就会调用ClassifyFn函数

层的条件:

我们可以查看这个文档

每个筛选层 (Fwpmu.h) 可用的筛选条件 - Win32 apps | Microsoft Learn

每一层都可以有自己的筛选条件

例如FWPM_LAYER_OUTBOUND_TRANSPORT_V4这一层,在fieldKey设置条件类型,通过查询文档,我们选择一个远程port的条件

1736597791366

1
2
3
4
conditions[conditionIndex].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
conditions[conditionIndex].matchType = FWP_MATCH_EQUAL;
conditions[conditionIndex].conditionValue.type = FWP_UINT16;
conditions[conditionIndex].conditionValue.uint16 = 80;

1736597913163

再比如应用层ALE,可以看到这个ALE_APP_ID就是PID, ALE_USER_ID就是指用户帐户ID

层的条件的使用会在Filters展现

Filters:(过滤条件)

用于处理包的一些规则集合

一个FWPM_FILTER0 filter可以设置多个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
FWPM_FILTER0 filter = {0};
FWPM_FILTER_CONDITION0 filterConditions[3] = {0};
UINT conditionIndex;

// 设置 Filter 的层
filter.layerKey = *layerKey;

// 设置 Filter 的名称和描述
filter.displayData.name = (wchar_t*)filterName;
filter.displayData.description = (wchar_t*)filterDesc;

// 设置 Filter 的动作为调用 Callout 并终止处理
filter.action.type = FWP_ACTION_CALLOUT_TERMINATING;
filter.action.calloutKey = *calloutKey;

// 设置 Filter 的条件
filter.filterCondition = filterConditions;

// 设置 Filter 的子层
filter.subLayerKey = DD_PROXY_SUBLAYER;

// 设置 Filter 的权重为自动分配
filter.weight.type = FWP_EMPTY; // auto-weight

// 设置 Filter 的上下文
filter.rawContext = context;

// 设置第一个条件:远程端口等于指定值
conditionIndex = 0;
filterConditions[conditionIndex].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
filterConditions[conditionIndex].matchType = FWP_MATCH_EQUAL;
filterConditions[conditionIndex].conditionValue.type = FWP_UINT16;
filterConditions[conditionIndex].conditionValue.uint16 = remotePort;
conditionIndex++;

// 设置 Filter 的条件数量
filter.numFilterConditions = conditionIndex;

// 添加 Filter 到过滤引擎
status = FwpmFilterAdd0(gEngineHandle, &filter, NULL, NULL);

这里说一下FWPM_FILTER0结构里面的action.type

从微软官方文档可以查询到:这个是确定当前绑定的callout需要返回什么值

含义
FWP_ACTION_BLOCK 阻止流量。0x00000001 |FWP_ACTION_FLAG_TERMINATING
FWP_ACTION_PERMIT 允许流量。0x00000002 |FWP_ACTION_FLAG_TERMINATING
FWP_ACTION_CALLOUT_TERMINATING 调用始终返回块或许可的标注。0x00000003 |FWP_ACTION_FLAG_CALLOUT |FWP_ACTION_FLAG_TERMINATING
FWP_ACTION_CALLOUT_INSPECTION 调用从不返回块或允许的标注。0x00000004 |FWP_ACTION_FLAG_CALLOUT |FWP_ACTION_FLAG_NON_TERMINATING
FWP_ACTION_CALLOUT_UNKNOWN 调用可返回块或允许的标注。0x00000005 |FWP_ACTION_FLAG_CALLOUT
动作类型 是否调用 Callout 是否终止 描述
FWP_ACTION_BLOCK 直接阻止流量
FWP_ACTION_PERMIT 直接允许流量。
FWP_ACTION_CALLOUT_TERMINATING 调用 Callout 函数,Callout 返回决定(允许/阻止),并停止进一步匹配过滤器。(只会匹配一个 Filter)
FWP_ACTION_CALLOUT_INSPECTION 调用 Callout 函数,Callout 只能观察流量,不改变流量行为,匹配后继续检查其他过滤器。(主要用于观察用途)
FWP_ACTION_CALLOUT_UNKNOWN 不确定 调用 Callout 函数,Callout 返回决定(允许/阻止),不明确后续动作,使用时需明确后果。

FWP_ACTION_CALLOUT_TERMINATINGFWP_ACTION_CALLOUT_UNKNOWN的使用示例:

FWP_ACTION_CALLOUT_TERMINATING:终止行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Callout 函数实现
NTSTATUS MyClassifyFn(
const FWPS_INCOMING_VALUES0* inFixedValues,
const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,
void* layerData,
const void* classifyContext,
const FWPS_FILTER0* filter,
UINT64 flowContext,
FWPS_CLASSIFY_OUT0* classifyOut
)
{
// 检查源 IP 地址是否匹配
FWP_VALUE0 srcIP = inFixedValues->incomingValue[FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_LOCAL_ADDRESS].value;
FWP_VALUE0 destPort = inFixedValues->incomingValue[FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_REMOTE_PORT].value;

if (srcIP.uint32 == targetIP && destPort.uint16 == 80) {
classifyOut->actionType = FWP_ACTION_BLOCK; // 阻止流量
} else {
classifyOut->actionType = FWP_ACTION_PERMIT; // 允许流量
}
classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE; // 表示 Callout 决定了最终行为
return STATUS_SUCCESS;
}

FWP_ACTION_CALLOUT_UNKNOWN :动态决策,继续检查其他过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Callout 函数实现
NTSTATUS MyClassifyFn(
const FWPS_INCOMING_VALUES0* inFixedValues,
const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,
void* layerData,
const void* classifyContext,
const FWPS_FILTER0* filter,
UINT64 flowContext,
FWPS_CLASSIFY_OUT0* classifyOut
)
{
// 检查流量的应用程序
if (IsTargetApplication(inFixedValues)) {
LogPacketInfo(layerData); // 记录数据包信息
}

// 不决定流量行为,继续交给其他过滤器
classifyOut->actionType = FWP_ACTION_CONTINUE;
return STATUS_SUCCESS;
}

然后我们要把Filter添加到过滤引擎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//FWPM_action0结构指定在所有筛选条件都为真时所采取的操作
mFilter.action.calloutKey = *calloutKey;
//交给callout处理 由callout来决定返回阻止(block)或者允许(permit)
mFilter.action.type = FWP_ACTION_CALLOUT_TERMINATING;

//描述
mFilter.displayData.name = L"WFP TEST";
mFilter.displayData.description = L"yxp's WFP TEST";


mFilter.layerKey = *layerKey; //这个值和FwpmCalloutAdd里面使用的值一样 过滤层标识符 此过滤层允许授权对传出tcp连接的连接请求,
//以及基于发送的第一个数据包授权传出非tcp通信量

//过滤条件数
mFilter.numFilterConditions = 0;
//过滤条件
mFilter.filterCondition = mFilter_condition;

mFilter.subLayerKey = FWPM_SUBLAYER_UNIVERSAL; //此子层承载所有未分配给任何其他子层的筛选器

//BFE将根据过滤条件自动分配权重
mFilter.weight.type = FWP_EMPTY;

//添加一个新的过滤器对象到系统中
status = FwpmFilterAdd(gEngineHandle, &mFilter, NULL, filterId);
if (!NT_SUCCESS(status))
goto exit;

这里是一个添加filter到对应子层的过滤驱动

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
#include <ntddk.h>
#pragma warning(push)
#pragma warning(disable:4201) // unnamed struct/union
#pragma warning(disable:4995)
#include <fwpsk.h>
#pragma warning(pop)
#include <ndis.h>
#include <fwpmk.h>
#include <limits.h>
#include <ws2ipdef.h>
#include <in6addr.h>
#include <ip2string.h>
#include <strsafe.h>
#define INITGUID
#include <guiddef.h>
#define bool BOOLEAN
#define true TRUE
#define false FALSE
#define DEVICE_NAME L"\\Device\\WFP_TEST"
#define DEVICE_DOSNAME L"\\DosDevices\\WFP_TEST"
#define kmalloc(_s) ExAllocatePoolWithTag(NonPagedPool, _s, 'SYSQ')
#define kfree(_p) ExFreePool(_p)
#define DebugPrint(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,__VA_ARGS__)
DEFINE_GUID // {6812FC83-7D3E-499a-A012-55E0D85F348B}
(
GUID_ALE_AUTH_CONNECT_CALLOUT_V4,
0x6812fc83,
0x7d3e,
0x499a,
0xa0, 0x12, 0x55, 0xe0, 0xd8, 0x5f, 0x34, 0x8b
);
// 定义子层 GUID
const GUID SubLayer_A = { 0x3248ABFD, 0x0FE9, 0x1751, 0x8F, 0xDC, 0xD2, 0x1F, 0x30, 0xAA, 0xDB, 0x25 };
const GUID SubLayer_B = { 0xFE68BE17, 0xBAB7, 0x2846, 0xA9, 0x90, 0xBA, 0x3B, 0x19, 0x80, 0xFC, 0x44 };


//定义filter的GUID
const GUID Filter_Guid_A = { 0x1429292A, 0x3074, 0x475B, 0xBB, 0x36, 0x6A, 0x12, 0x37, 0x3B, 0x5E, 0x88 };
const GUID Filter_Guid_B = { 0x14C0D92F, 0x8745, 0x5D85, 0x9D, 0xF2, 0x9D, 0x88, 0x3A, 0x10, 0xD2, 0x6D };

PDEVICE_OBJECT gDevObj;

HANDLE gEngineHandle = 0;
HANDLE gInjectHandle = 0;
//CalloutId
UINT32 gAleConnectCalloutId = 0;
//FilterId
UINT64 gAleConnectFilterId = 0;

/*
以下两个回调函数没啥用
*/
NTSTATUS NTAPI WallNotifyFn
(
IN FWPS_CALLOUT_NOTIFY_TYPE notifyType,
IN const GUID* filterKey,
IN const FWPS_FILTER* filter
)
{
KdPrint(("NotifyFn\n"));
return STATUS_SUCCESS;
}

VOID NTAPI WallFlowDeleteFn
(
IN UINT16 layerId,
IN UINT32 calloutId,
IN UINT64 flowContext
)
{
KdPrint(("FlowDeleteFn\n"));
return;
}

//协议代码转为名称
char* ProtocolIdToName(UINT16 id)
{
char* ProtocolName = kmalloc(16);
RtlZeroMemory(ProtocolName, 16);
switch (id)
{
case 1:
strcpy_s(ProtocolName, 4 + 1, "ICMP");
break;
case 2:
strcpy_s(ProtocolName, 4 + 1, "IGMP");
break;
case 6:
strcpy_s(ProtocolName, 3 + 1, "TCP");
break;
case 17:
strcpy_s(ProtocolName, 3 + 1, "UDP");
break;
case 27:
strcpy_s(ProtocolName, 3 + 1, "RDP");
break;
default:
strcpy_s(ProtocolName, 7 + 1, "UNKNOWN");
break;
}
return ProtocolName;
}

//最重要的过滤函数
void NTAPI WallALEConnectClassify
(
IN const FWPS_INCOMING_VALUES0* inFixedValues,//此结构包含筛选层上每个数据字段的值
//incomingValue[index] => index的值是枚举类型FWPS_FIELDS_ALE_AUTH_CONNECT_V4,
//枚举名字说明了该值的类型例如:FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_ADDRESS 就是本地IP

IN const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,//此结构包含筛选层上每个元数据字段的值。
IN OUT void* layerData,//指向结构的指针,该结构描述正在过滤的层上的原始数据。此参数可能为NULL,
//这取决于所筛选的层和调用classfyFn0标注函数的条件。
//对于流层,此参数指向FWPS_stream_Callout_IO_PACKET 0结构。
//对于所有其他层,如果NetBufferList结构不是NULL,则该参数指向该结构。
IN const void* classifyContext,//由过滤器引擎指向与标注驱动程序关联的上下文数据的指针 没有找出该指针类型
IN const FWPS_FILTER* filter,//指向FWPS_FILTER1结构的指针。此结构描述指定筛选器操作的标注的筛选器。
IN UINT64 flowContext,//包含与数据流关联的上下文的UINT 64类型变量。如果没有与数据流相关联的上下文,则此参数为零。
//如果在不支持数据流的过滤层将标注添加到筛选引擎中,则clamfyFn1回调函数应忽略此参数
OUT FWPS_CLASSIFY_OUT* classifyOut //指向FWPS_GARGY_OUT 0结构的指针,该结构接收clamfyFn1回调函数返回给调用方的任何数据
)
{

// 检查过滤器的子层
if (filter != NULL) {
// 你可以通过其他方式来获取子层信息,假设我们知道这个过滤器在注册时对应某个子层
// 如果在注册过滤器时知道它所属的子层,可以通过过滤器相关信息判断它所属的子层
if (filter->subLayerWeight == 100) {
DebugPrint("Triggered by SubLayer_A\n");
}
else if (filter->subLayerWeight == 200) {
DebugPrint("Triggered by SubLayer_B\n");
}
else {
DebugPrint("Triggered by other sublayer\n");
}
}
}





// 添加子层
void AddSubLayers(HANDLE engineHandle) {
FWPM_SUBLAYER0 subLayerA = { 0 };
subLayerA.subLayerKey = SubLayer_A;
subLayerA.displayData.name = L"SubLayer A - Firewall Rules";
subLayerA.displayData.description = L"Handles firewall-related rules";
subLayerA.flags = 0;
subLayerA.weight = 100; // 优先级高

FWPM_SUBLAYER0 subLayerB = { 0 };
subLayerB.subLayerKey = SubLayer_B;
subLayerB.displayData.name = L"SubLayer B - Logging Rules";
subLayerB.displayData.description = L"Handles logging-related rules";
subLayerB.flags = 0;
subLayerB.weight = 200; // 优先级低

FwpmSubLayerAdd0(engineHandle, &subLayerA, NULL);
FwpmSubLayerAdd0(engineHandle, &subLayerB, NULL);
}



NTSTATUS RegisterCalloutForLayer
(
IN const GUID* layerKey,
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 UINT32* calloutId, //注册callout时返回此ID
OUT UINT64* filterId
)
{
NTSTATUS status = STATUS_SUCCESS;
FWPS_CALLOUT sCallout = { 0 };
FWPM_FILTER mFilter = { 0 };
FWPM_FILTER_CONDITION mFilter_condition[1] = { 0 };
FWPM_CALLOUT mCallout = { 0 };
FWPM_DISPLAY_DATA mDispData = { 0 };
BOOLEAN bCalloutRegistered = FALSE;

//这里未指定sCallout.flags
sCallout.calloutKey = *calloutKey; //一个唯一的GUID值,用来标记此callout
sCallout.classifyFn = classifyFn; //每当回调处理网络数据时,过滤器引擎将调用此函数,处理网络数据在这个回调函数里面处理.
sCallout.flowDeleteFn = flowDeleteNotifyFn; //每当终止由回调处理的数据流时,筛选器引擎将调用此函数
sCallout.notifyFn = notifyFn; //卸载callout时,会调用这个回调

//要使用哪个设备对象注册
status = FwpsCalloutRegister(gDevObj, //自己创建的设备对象
&sCallout, //该结构体有用的是指定了classifyFn回调,处理连接时的数据就会用到这个回调函数.
calloutId //返回的calloutID ,取消注册callout会用到这个值
);
if (!NT_SUCCESS(status))
goto exit;

bCalloutRegistered = TRUE;


mDispData.name = L"WFP TEST"; //可选的名字
mDispData.description = L"yxp's WFP TEST"; //可选的描述
//你感兴趣的内容
mCallout.applicableLayer = *layerKey; //过滤层标识符 此过滤层允许授权对传出tcp连接的连接请求,
//以及基于发送的第一个数据包授权传出非tcp通信量

//你感兴趣的内容的GUID
mCallout.calloutKey = *calloutKey; //该GUID值必须与FwpsCalloutRegister注册时的GUID值相同.
mCallout.displayData = mDispData;

//向过滤引擎添加callout
status = FwpmCalloutAdd(gEngineHandle,
&mCallout, //flags没有设置
NULL, //安全描述符 可以为NULL
NULL); //返回一个ID 与FwpsCalloutRegister函数返回的ID是相同的.
if (!NT_SUCCESS(status))
goto exit;


//// 设置源IP地址条件
//mFilter_condition[0].fieldKey = FWPM_CONDITION_IP_LOCAL_ADDRESS; // 源IP地址条件
//mFilter_condition[0].matchType = FWP_MATCH_EQUAL; // 精确匹配
//mFilter_condition[0].conditionValue.type = FWP_V4_ADDR; // IPv4 地址
//mFilter_condition[0].conditionValue.v4Addr = 0xC0A80101; // 192.168.1.1
//mFilter_condition[0].next = &mFilter_condition[1]; // 链接到下一个条件

//// 设置源端口条件
//mFilter_condition[1].fieldKey = FWPM_CONDITION_IP_LOCAL_PORT; // 源端口条件
//mFilter_condition[1].matchType = FWP_MATCH_EQUAL; // 精确匹配
//mFilter_condition[1].conditionValue.type = FWP_UINT16; // 端口号
//mFilter_condition[1].conditionValue.uint16 = 80; // 端口 80
//mFilter_condition[1].next = NULL; // 最后一个条件的 next 设置为 NULL

//添加子层
AddSubLayers(gEngineHandle);

mFilter.filterKey = Filter_Guid_A;

//FWPM_action0结构指定在所有筛选条件都为真时所采取的操作
mFilter.action.calloutKey = *calloutKey;
//交给callout处理 由callout来决定返回阻止(block)或者允许(permit)
mFilter.action.type = FWP_ACTION_CALLOUT_TERMINATING;

//描述
mFilter.displayData.name = L"WFP TEST for SubLayer A";
mFilter.displayData.description = L"Filter for SubLayer A";


mFilter.layerKey = *layerKey; //这个值和FwpmCalloutAdd里面使用的值一样 过滤层标识符 此过滤层允许授权对传出tcp连接的连接请求,
//以及基于发送的第一个数据包授权传出非tcp通信量

//过滤条件数
mFilter.numFilterConditions = 0;
//过滤条件
mFilter.filterCondition = mFilter_condition;

mFilter.subLayerKey = SubLayer_A; //此子层承载所有未分配给任何其他子层的筛选器

//分配权重
mFilter.weight.uint8 =0x100;

//添加一个新的过滤器对象到系统中
status = FwpmFilterAdd(gEngineHandle, &mFilter, NULL, filterId);
if (!NT_SUCCESS(status))
goto exit;



// 配置过滤器并添加到子层 B
mFilter.filterKey = Filter_Guid_B;

mFilter.displayData.name = L"WFP TEST for SubLayer B";
mFilter.displayData.description = L"Filter for SubLayer B";
mFilter.subLayerKey = SubLayer_B; // 将此过滤器附加到子层 B
//分配权重
mFilter.weight.uint8 = 0x200;
// 添加过滤器到引擎
status = FwpmFilterAdd(gEngineHandle, &mFilter, NULL, filterId);
if (!NT_SUCCESS(status))
goto exit;


exit:
if (!NT_SUCCESS(status))
{
if (bCalloutRegistered)
{
FwpsCalloutUnregisterById(*calloutId);
}
}
return status;
}





NTSTATUS WallRegisterCallouts()
{
NTSTATUS status = STATUS_SUCCESS;
BOOLEAN bInTransaction = FALSE;
BOOLEAN bEngineOpened = FALSE;
FWPM_SESSION session = { 0 };

//置此标志时,会话结束时将自动删除在会话期间添加的任何对象
session.flags = FWPM_SESSION_FLAG_DYNAMIC;

//开启WFP引擎
status = FwpmEngineOpen(NULL,
RPC_C_AUTHN_WINNT,
NULL,
&session,
&gEngineHandle);
if (!NT_SUCCESS(status))
goto exit;
bEngineOpened = TRUE;
// 在当前会话中开始显式事务
status = FwpmTransactionBegin(gEngineHandle, 0);
if (!NT_SUCCESS(status))
goto exit;
bInTransaction = TRUE;
//注册回调函数
status = RegisterCalloutForLayer(
&FWPM_LAYER_ALE_AUTH_CONNECT_V4, //此过滤层允许授权对传出tcp连接的连接请求,
//以及基于发送的第一个数据包授权传出非tcp通信量
&GUID_ALE_AUTH_CONNECT_CALLOUT_V4,
WallALEConnectClassify,
WallNotifyFn,
WallFlowDeleteFn,
&gAleConnectCalloutId,
&gAleConnectFilterId);
if (!NT_SUCCESS(status))
{
DebugPrint("RegisterCalloutForLayer失败!\n");
goto exit;
}
//确认所有内容并提交,让回调函数正式发挥作用
status = FwpmTransactionCommit(gEngineHandle);
if (!NT_SUCCESS(status))
goto exit;
bInTransaction = FALSE;
exit:
if (!NT_SUCCESS(status))
{
if (bInTransaction)
{
FwpmTransactionAbort(gEngineHandle);
}
if (bEngineOpened)
{
FwpmEngineClose(gEngineHandle);
gEngineHandle = 0;
}
}
return status;
}

NTSTATUS WallUnRegisterCallouts()
{
if (gEngineHandle != 0)
{
//删除FilterId
FwpmFilterDeleteById(gEngineHandle, gAleConnectFilterId);
//删除CalloutId
FwpmCalloutDeleteById(gEngineHandle, gAleConnectCalloutId);
//清空FilterId
gAleConnectFilterId = 0;
//反注册CalloutId
FwpsCalloutUnregisterById(gAleConnectCalloutId);
//清空CalloutId
gAleConnectCalloutId = 0;
//关闭引擎
FwpmEngineClose(gEngineHandle);
gEngineHandle = 0;
}
return STATUS_SUCCESS;
}

VOID DriverUnload(PDRIVER_OBJECT driverObject)
{
NTSTATUS status;
UNICODE_STRING deviceDosName = { 0 };
status = WallUnRegisterCallouts();
if (!NT_SUCCESS(status))
{
DebugPrint("WallUnRegisterCallouts失败\n");
return;
}
RtlInitUnicodeString(&deviceDosName, DEVICE_DOSNAME);
IoDeleteSymbolicLink(&deviceDosName);
if (gDevObj)
{
IoDeleteDevice(gDevObj);
gDevObj = NULL;
}
DebugPrint("驱动卸载成功\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING registryPath)
{
UNICODE_STRING deviceName = { 0 };
UNICODE_STRING deviceDosName = { 0 };
NTSTATUS status = STATUS_SUCCESS;
driverObject->DriverUnload = DriverUnload;
RtlInitUnicodeString(&deviceName, DEVICE_NAME);
KdBreakPoint();
status = IoCreateDevice(driverObject,
0,
&deviceName,
FILE_DEVICE_NETWORK,
0,
FALSE,
&gDevObj);
if (!NT_SUCCESS(status))
{
DebugPrint("创建设备对象失败\n");
return STATUS_UNSUCCESSFUL;
}
RtlInitUnicodeString(&deviceDosName, DEVICE_DOSNAME);
status = IoCreateSymbolicLink(&deviceDosName, &deviceName);
if (!NT_SUCCESS(status))
{
DebugPrint("创建符号链接失败\n");
return STATUS_UNSUCCESSFUL;
}
status = WallRegisterCallouts();
if (!NT_SUCCESS(status))
{
DebugPrint("WallRegisterCallouts 失败!\n");
return STATUS_SUCCESS;
}
DebugPrint("过滤驱动加载成功\n");
return status;
}

Shims:

类似于自来水过滤器里面的小垫片,可以解析网络堆栈中的流,包,事件,调用过滤引擎并根据规则评估数据,或者进一步调用callout,最后决定对包的处理

Callouts函数:

由驱动暴露出来的一组接口函数, 它本质上是一个结构,包含了过滤回调函数(例如 ClassifyFnNotifyFnFlowDeleteFn)。 负责进一步分析或者修改包。比如classify数据处理函数,可以返回一组permit(允许),block(阻止),continue(继续),defer(延迟),needmoredata(需要更多数据),Drop connection(丢弃连接) 等

先注册Callout (FWPS)

1
2
3
4
5
6
7
8
9
10
11
12
FWPS_CALLOUT    sCallout = { 0 };
//这里未指定sCallout.flags
sCallout.calloutKey = *calloutKey; //一个唯一的GUID值,用来标记此callout
sCallout.classifyFn = classifyFn; //每当回调处理网络数据时,过滤器引擎将调用此函数,处理网络数据在这个回调函数里面处理.
sCallout.flowDeleteFn = flowDeleteNotifyFn; //每当终止由回调处理的数据流时,筛选器引擎将调用此函数
sCallout.notifyFn = notifyFn; //Callout 注册时的初始通知,筛选器动态绑定到 Callout,筛选器被动态删除,卸载callout时,会调用这个回调

//要使用哪个设备对象注册
status = FwpsCalloutRegister(gDevObj, //自己创建的设备对象
&sCallout, //该结构体有用的是指定了classifyFn回调,处理连接时的数据就会用到这个回调函数.
calloutId //返回的calloutID ,取消注册callout会用到这个值
);

然后管理 WFP 过滤引擎(FWPM)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FWPM_CALLOUT    mCallout = { 0 };
FWPM_DISPLAY_DATA mDispData = { 0 };

mDispData.name = L"WFP TEST"; //可选的名字
mDispData.description = L"yxp's WFP TEST"; //可选的描述
//你感兴趣的内容
mCallout.applicableLayer = *layerKey; //过滤层标识符 此过滤层允许授权对传出tcp连接的连接请求,
//以及基于发送的第一个数据包授权传出非tcp通信量

//你感兴趣的内容的GUID
mCallout.calloutKey = *calloutKey; //该GUID值必须与FwpsCalloutRegister注册时的GUID值相同.
mCallout.displayData = mDispData;

//向过滤引擎添加callout
status = FwpmCalloutAdd(gEngineHandle,
&mCallout, //flags没有设置
NULL, //安全描述符 可以为NULL
NULL); //返回一个ID 与FwpsCalloutRegister函数返回的ID是相同的.

这样,就可以把一个Callout添加到过滤引擎了

Classify:

利用规则过滤包的过程。将包的各种属性与规则中的conditions进行比较

定义Callouts需要生成一个GUID,具体可以用GUID Generator进行生成, 这是因为 GUID(Globally Unique Identifier) 提供了一种全局唯一的标识方式,用于标识和管理 Callouts。与普通函数不同,WFP 的架构需要 GUID 来支持复杂的注册、识别和调度机制。

注册callout -R0注册

首先创建一个FWPS_CALLOUT结构体

1
2
3
4
5
6
7
typedef struct FWPS_CALLOUT {
GUID calloutKey; //标注驱动程序定义的 GUID ,用于唯一标识标注。
UINT32 flags;//指定特定于标注的参数的标志
FWPS_CALLOUT_CLASSIFY_FN classifyFn; //指向标注驱动程序的 分类Fn2 标注函数的指针。 每当有网络数据要由标注处理时,筛选器引擎都会调用此函数
FWPS_CALLOUT_NOTIFY_FN notifyFn;//指向标注驱动程序的 notifyFn 函数的指针。 筛选器引擎调用此函数以通知标注驱动程序与标注关联的事件。
FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN flowDeleteFn;//指向标注驱动程序的 flowDeleteFn 标注函数的指针。 每当由标注处理的数据流终止时,筛选器引擎就会调用此函数。
} FWPS_CALLOUT;

CLASSIFY_FN的结构

1
2
3
4
5
6
7
8
9
10
11
FWPS_CALLOUT_CLASSIFY_FN2 FwpsCalloutClassifyFn2;

void FwpsCalloutClassifyFn2(
[in] const FWPS_INCOMING_VALUES0 *inFixedValues,//WFP传进来的本层特有的数据,如端口,IP地址(看具体哪一层)
[in] const FWPS_INCOMING_METADATA_VALUES0 *inMetaValues,//本层相关的扩展数据,如PID和路径
[in, out, optional] void *layerData,//指向原始数据的指针,rawData=(PNET_BUFFER_LIST)layerData
[in, optional] const void *classifyContext,//指向筛选器引擎与标注驱动程序关联的上下文数据的指针。
[in] const FWPS_FILTER2 *filter,//指向 FWPS_FILTER2 结构的指针。 此结构描述指定筛选器操作的标注的筛选器。
[in] UINT64 flowContext,//包含与数据流关联的上下文的 UINT64 类型变量。 如果没有上下文与数据流关联,则此参数为零。
[in, out] FWPS_CLASSIFY_OUT0 *classifyOut//指向 FWPS_CLASSIFY_OUT0 结构的指针,允许或者阻止等
)

其中 inFixedValues,inMetaValues 具体能拿到什么还是要看具体注册在哪一层,如果是注册在NDIS层,那肯定拿不到IP地址,端口地址等,但是能拿Mac地址等

因此我们就可以写一个简单的向用户询问是否同意连接的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
index = FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_ADDRESS;
LocalIPADDRv4 = inFixedValues->incomingValue[index].value.uint32;

index = FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_PORT;
LocalPort = inFixedValues->incomingValue[index].value.uint16;

index = FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_ADDRESS;
remoteIPADDRv4 = inFixedValues->incomingValue[index].value.uint32;

index = FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_PORT;
remotePort = inFixedValues->incomingValue[index].value.uint16;

DbgPrint("BaseTDI:LocalIP%bx LocalPort %d\n remoteIP %bx remotePort %d", LocalIPADDRv4, LocalPort, remoteIPADDRv4, remotePort);

DbgPrint("BaseTDI:PID%d,PID'sPATH%s", inMetaValues->processId, inMetaValues->processPath->data);

if (monitoringEnabled) {
// 访问规则代码,在这里通知用户态程序
AskUser(LocalIP, LocalPort, remoteIP, remotePort, PID);

if (允许) {
classifyOut->actionType = FWP_ACTION_PERMIT; // 允许发送或接收
} else {
classifyOut->actionType = FWP_ACTION_BLOCK; // 不允许发送或接收
}
}

注册样例长这样

1
2
3
4
5
6
7
FWPS_CALLOUT    sCallout = { 0 };

//这里未指定sCallout.flags
sCallout.calloutKey = *calloutKey; //一个唯一的GUID值,用来标记此callout
sCallout.classifyFn = (FWPS_CALLOUT_CLASSIFY_FN3)classifyFn; //每当回调处理网络数据时,过滤器引擎将调用此函数,处理网络数据在这个回调函数里面处理.
sCallout.notifyFn = (FWPS_CALLOUT_NOTIFY_FN3)notifyFn;//卸载callout时,会调用这个回调
sCallout.flowDeleteFn = (FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN)flowDeleteNotifyFn; //每当终止由回调处理的数据流时,筛选器引擎将调用此函数

WFP工作的整体流程

以下是 WFP 驱动过滤的总结流程:

  1. 数据包进入网络协议栈:当一个网络数据包到达时,它会进入网络协议栈进行处理。
  2. 协议栈调用 Shim(中间层):网络协议栈会调用 Shim 层,Shim 是过滤过程中的关键参与者。
  3. Shim 调用分类过程:Shim 在指定的网络层上启动分类过程,对数据包进行过滤规则匹配。
  4. 分类过程中匹配过滤器并执行动作:在分类过程中,系统会将数据包与过滤规则进行匹配,并根据匹配结果采取相应的动作(类似于 TDI 的 quick filter() 功能)。
  5. 匹配到 Callout 过滤器时调用对应 Callout 函数:如果分类过程中匹配到了 Callout 过滤器,则会调用相应的 Callout 函数进行自定义处理。
  6. Shim 根据最终过滤决策执行动作:Shim 根据分类的最终决策(例如丢弃数据包、允许通过等)对数据包进行处理。

FWPM和FWPS

FWPM(Filtering Platform Management)是 Windows Filtering Platform (WFP) 中用于 配置和管理过滤引擎 的 API 集合

FWPM 提供了一组 API,用于在 用户态 配置和管理 WFP 过滤引擎。它的功能包括但不限于:

  • 管理 Callout
    • 添加、删除和枚举 Callout。
    • 例如:FwpmCalloutAddFwpmCalloutDeleteByKey0FwpmCalloutEnum0
  • 管理 Filter
    • 添加、删除和枚举 Filter。
    • 例如:FwpmFilterAddFwpmFilterDeleteByKey0FwpmFilterEnum0
  • 管理 Sublayer
    • 添加、删除和枚举 Sublayer。
    • 例如:FwpmSubLayerAddFwpmSubLayerDeleteByKey0FwpmSubLayerEnum0
  • 管理 Provider
    • 添加、删除和枚举 Provider。
    • 例如:FwpmProviderAddFwpmProviderDeleteByKey0FwpmProviderEnum0
  • 管理会话(Session)
    • 打开、关闭和配置过滤引擎会话。
    • 例如:FwpmEngineOpenFwpmEngineCloseFwpmTransactionBeginFwpmTransactionAbort

FWPS

(Filtering Platform Session)是 Windows Filtering Platform (WFP) 中用于 内核态 的 API 集合。它与 FWPM(用户态 API)相对应,主要用于在内核态实现网络数据包的过滤和处理。

FWPS 的作用

FWPS 提供了一组 API,用于在内核态实现以下功能:

  • 注册和管理 Callout
    • 注册 Callout,并实现分类函数(ClassifyFn)、通知函数(NotifyFn)等。
    • 例如:FwpsCalloutRegisterFwpsCalloutUnregisterById
  • 处理网络数据包
    • 在 Callout 的分类函数中,检查数据包的元数据和内容,并决定是否允许、阻止或修改数据包。
  • 注入数据包
    • 将修改后的数据包重新注入网络堆栈。
    • 例如:FwpsInjectMacSendAsyncFwpsInjectMacReceiveAsync
  • 管理网络流
    • 跟踪和管理网络流(Flow)的生命周期。
    • 例如:FwpsFlowAssociateContextFwpsFlowRemoveContext

设置filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
FWPM_FILTER0 filter = {0};
FWPM_FILTER_CONDITION0 filterConditions[3] = {0};
UINT conditionIndex;

// 设置 Filter 的层
filter.layerKey = *layerKey;

// 设置 Filter 的名称和描述
filter.displayData.name = (wchar_t*)filterName;
filter.displayData.description = (wchar_t*)filterDesc;

// 设置 Filter 的动作为调用 Callout 并终止处理
filter.action.type = FWP_ACTION_CALLOUT_TERMINATING;
filter.action.calloutKey = *calloutKey;

// 设置 Filter 的条件
filter.filterCondition = filterConditions;

// 设置 Filter 的子层
filter.subLayerKey = DD_PROXY_SUBLAYER;

// 设置 Filter 的权重为自动分配
filter.weight.type = FWP_EMPTY; // auto-weight

// 设置 Filter 的上下文
filter.rawContext = context;

// 设置第一个条件:远程端口等于指定值
conditionIndex = 0;
filterConditions[conditionIndex].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
filterConditions[conditionIndex].matchType = FWP_MATCH_EQUAL;
filterConditions[conditionIndex].conditionValue.type = FWP_UINT16;
filterConditions[conditionIndex].conditionValue.uint16 = remotePort;
conditionIndex++;

// 设置 Filter 的条件数量
filter.numFilterConditions = conditionIndex;

// 添加 Filter 到过滤引擎
status = FwpmFilterAdd0(gEngineHandle, &filter, NULL, NULL);

4. 层和子层的区别总结

特性 层(Layer) 子层(Sublayer)
定义 网络协议栈中的一个特定阶段 层内的逻辑分组
作用 决定 Filter 的作用范围 组织和管理 Filter 的执行顺序
数量 固定的,由 WFP 预定义 可自定义,一个层可以包含多个子层
顺序 按照协议栈的顺序处理 按照权重顺序处理
示例 FWPM_LAYER_ALE_AUTH_CONNECT_V4 用户自定义的子层(如 DD_PROXY_SUBLAYER
权重 通过权重决定子层的执行顺序

可以多次调用Callout注册

同一个 Callout 可以被多个 Filter 关联。 在这种情况下,MyClassifyFn 会被多次调用,每次调用时 filter 参数会指向不同的 Filter。

callout与filter的绑定流程

Callout 注册:

  • RegisterCallout 注册 Callout,并指定 GUID 和回调函数。
  • 回调函数(如 MyClassifyFnMyNotifyFn)将在数据包分类和通知时被调用。

Filter 创建:

  • AddFilter 定义了一个 Filter,将其 action.calloutKey 设置为 Callout 的 GUID,从而将两者关联。

事务管理:

  • 通过 FwpmTransactionBeginFwpmTransactionCommit 确保 Callout 和 Filter 的注册操作在一次原子事务中完成。

调用链:

  • 当网络层触发 Filter 时,WFP 会调用与该 Filter 关联的 Callout 的 MyClassifyFn 函数来处理数据包。

WFP过滤驱动实战

首先上来就一堆报错,参考以下文章进行解决

wfp 驱动编译报错 解决方案._wfp 编译失败-CSDN博客

1.在项目属性中设置预定义处理器宏

进入 **C/C++ -> 预处理器 -> 预处理器定义**。 添加: NDIS60或者NDIS620

2.检查头文件包含顺序

确保头文件的包含顺序如下:

1
2
3
#include <fwpsk.h>
#include <ndis.h>
#include <fwpmk.h>

WFP 和 NDIS 头文件的依赖性很高,包含错误顺序可能导致未定义的标识符错误。

另外这俩顺序也要注意

1
2
#include <ntifs.h>
#include <ntddk.h>

在配置WFP开发环境时需要在链接器选项卡中的附加依赖项中增加fwpkclnt.lib,uuid.lib这两个库文件

1736650516335

从以下这篇文章,我们分析一下WFP是咋注册的

一个完整的WFP驱动(详细注释)_wfp驱动 iseqal-CSDN博客

首先我们要注册一下Callouts,并且只能在内核层进行注册Callouts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
NTSTATUS WallRegisterCallouts()
{
NTSTATUS status = STATUS_SUCCESS;
BOOLEAN bInTransaction = FALSE;
BOOLEAN bEngineOpened = FALSE;
FWPM_SESSION session = { 0 };

//置此标志时,会话结束时将自动删除在会话期间添加的任何对象
session.flags = FWPM_SESSION_FLAG_DYNAMIC;

//开启WFP引擎,获得一个引擎句柄
status = FwpmEngineOpen(NULL,
RPC_C_AUTHN_WINNT,
NULL,
&session,
&gEngineHandle);
if (!NT_SUCCESS(status))
goto exit;
bEngineOpened = TRUE;
// 在当前会话中开始显式事务
status = FwpmTransactionBegin(gEngineHandle, 0);
if (!NT_SUCCESS(status))
goto exit;
bInTransaction = TRUE;
//注册回调函数
status = RegisterCalloutForLayer(
&FWPM_LAYER_ALE_AUTH_CONNECT_V4, //此过滤层允许授权对传出tcp连接的连接请求,
//以及基于发送的第一个数据包授权传出非tcp通信量
&GUID_ALE_AUTH_CONNECT_CALLOUT_V4,
WallALEConnectClassify,
(FWPS_CALLOUT_NOTIFY_FN)WallNotifyFn,
WallFlowDeleteFn,
&gAleConnectCalloutId,
&gAleConnectFilterId);
if (!NT_SUCCESS(status))
{
DbgPrint("RegisterCalloutForLayer失败!\n");
goto exit;
}
//确认所有内容并提交,让回调函数正式发挥作用
status = FwpmTransactionCommit(gEngineHandle);
if (!NT_SUCCESS(status))
goto exit;
bInTransaction = FALSE;
exit:
if (!NT_SUCCESS(status))
{
if (bInTransaction)
{
FwpmTransactionAbort(gEngineHandle);
}
if (bEngineOpened)
{
FwpmEngineClose(gEngineHandle);
gEngineHandle = 0;
}
}
return status;
}

注册Callout并启用的主要逻辑在这:

首先用FwpsCalloutRegister注册Callout

然后用FWPM Add好已经注册的Callout,然后添加Filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
NTSTATUS RegisterCalloutForLayer
(
IN const GUID* layerKey,
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 UINT32* calloutId, //注册callout时返回此ID
OUT UINT64* filterId
)
{
NTSTATUS status = STATUS_SUCCESS;
FWPS_CALLOUT sCallout = { 0 };
FWPM_FILTER mFilter = { 0 };
FWPM_FILTER_CONDITION mFilter_condition[1] = { 0 };
FWPM_CALLOUT mCallout = { 0 };
FWPM_DISPLAY_DATA mDispData = { 0 };
BOOLEAN bCalloutRegistered = FALSE;

//这里未指定sCallout.flags
sCallout.calloutKey = *calloutKey; //一个唯一的GUID值,用来标记此callout
sCallout.classifyFn = classifyFn; //每当回调处理网络数据时,过滤器引擎将调用此函数,处理网络数据在这个回调函数里面处理.
sCallout.flowDeleteFn = flowDeleteNotifyFn; //每当终止由回调处理的数据流时,筛选器引擎将调用此函数
sCallout.notifyFn = notifyFn; //Callout 注册时的初始通知,筛选器动态绑定到 Callout,筛选器被动态删除,卸载callout时,会调用这个回调

//要使用哪个设备对象注册
status = FwpsCalloutRegister(gDevObj, //自己创建的设备对象
&sCallout, //该结构体有用的是指定了classifyFn回调,处理连接时的数据就会用到这个回调函数.
calloutId //返回的calloutID ,取消注册callout会用到这个值
);
if (!NT_SUCCESS(status))
goto exit;

bCalloutRegistered = TRUE;


mDispData.name = L"WFP TEST"; //可选的名字
mDispData.description = L"yxp's WFP TEST"; //可选的描述
//你感兴趣的内容
mCallout.applicableLayer = *layerKey; //过滤层标识符 此过滤层允许授权对传出tcp连接的连接请求,
//以及基于发送的第一个数据包授权传出非tcp通信量

//你感兴趣的内容的GUID
mCallout.calloutKey = *calloutKey; //该GUID值必须与FwpsCalloutRegister注册时的GUID值相同.
mCallout.displayData = mDispData;

//向过滤引擎添加callout
status = FwpmCalloutAdd(gEngineHandle,
&mCallout, //flags没有设置
NULL, //安全描述符 可以为NULL
NULL); //返回一个ID 与FwpsCalloutRegister函数返回的ID是相同的.
if (!NT_SUCCESS(status))
goto exit;


//FWPM_action0结构指定在所有筛选条件都为真时所采取的操作
mFilter.action.calloutKey = *calloutKey;
//交给callout处理 由callout来决定返回阻止(block)或者允许(permit)
mFilter.action.type = FWP_ACTION_CALLOUT_TERMINATING;

//描述
mFilter.displayData.name = L"WFP TEST";
mFilter.displayData.description = L"yxp's WFP TEST";


mFilter.layerKey = *layerKey; //这个值和FwpmCalloutAdd里面使用的值一样 过滤层标识符 此过滤层允许授权对传出tcp连接的连接请求,
//以及基于发送的第一个数据包授权传出非tcp通信量

//过滤条件数
mFilter.numFilterConditions = 0;
//过滤条件
mFilter.filterCondition = mFilter_condition;

mFilter.subLayerKey = FWPM_SUBLAYER_UNIVERSAL; //此子层承载所有未分配给任何其他子层的筛选器

//BFE将根据过滤条件自动分配权重
mFilter.weight.type = FWP_EMPTY;

//添加一个新的过滤器对象到系统中
status = FwpmFilterAdd(gEngineHandle, &mFilter, NULL, filterId);
if (!NT_SUCCESS(status))
goto exit;
exit:
if (!NT_SUCCESS(status))
{
if (bCalloutRegistered)
{
FwpsCalloutUnregisterById(*calloutId);
}
}
return status;
}