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

passthru.c
: 这是 主模块,负责驱动的初始化、卸载和调度核心功能。它是整个驱动的入口点,协调协议和小端口部分的交互。
miniport.c
: 这是 小端口(Miniport)模块,负责模拟一个虚拟网卡,并处理底层数据传输。 拦截数据包发送
protocol.c
: 这是 协议(Protocol)模块,负责与上层协议驱动(如 TCP/IP 协议栈)交互。 拦截数据包接收
然后和我们之前学的创建设备对象,创建符号连接不同
这里开始使用的是NdisMRegisterDevice创建设备对象
NDIS_MINIPORT_CHARACTERISTICS MChars;

这个就是 miniport用来拦截发包的函数,为什么一个为空呢??
上面的英文翻译过来是:
应指定 Send 或 SendPackets 处理程序。如果指定了 SendPackets 处理程序,则忽略 SendHandler
在NDIS层,已经没有进程,IRP的概念了,那么这个包属于哪个进程??这个咋整
在这里,PtReceiveComplete
,PtTransferDataComplete
,PtReceive
这三回调函数是用来拦截收包的

因为没有实践,先研究到这了….如果用到NDIS驱动开发,还会继续往下更新
WFP网络过滤驱动
WFP框架的组成
WFP:Windows Filter Platform
WFP框架本身已经实现了过滤的功能,缺的只是规则
WFP结构大致长这样

WFP系统是有数据过滤引擎的防火墙,没有规则。
编写用户层的程序给WFP引擎设置规则
编写核心态的callout驱动处理WFP抓到的网络数据包做深度处理
WFP中几个重要的概念:
Layers(层):
正如上面的图片所示
WFP 中的层表示网络协议栈中不同的数据处理阶段,它们包括:
Application Layer Enforcement (ALE) 层
Stream 层
Transport 层
Datagram 层
网络接口层
NDIS层
层选择的依据
你选择的层主要取决于想要实现的功能:
- 控制特定应用的网络连接:
- 使用 ALE_AUTH_CONNECT 或 ALE_AUTH_RECV_ACCEPT。(ALE层)
- 检查和修改传输层数据(TCP/UDP 数据包):( Transport 层)
- 深度解析数据流(如 HTTP 或 TLS 内容):( Stream 层 )
- 控制 UDP 数据报: ( Datagram 层 )
- 使用 FWPM_LAYER_DATAGRAM_DATA。
- 在网络接口上处理入站和出站的原始 IP 数据包。 ( 网络接口层 )
- FWPM_LAYER_INBOUND_IPPACKET_V4/V6 和 FWPM_LAYER_OUTBOUND_IPPACKET_V4/V6:
例如 FWPM_LAYER_ALE_AUTH_CONNECT_V4
是 应用层授权连接层(ALE Authorization Connect Layer) , 专门用于 IPv4 的网络连接请求。选择这一层,可以在网络连接建立之前拦截并检查连接请求,决定是否允许或阻止连接。
在 FWPM_LAYER_ALE_AUTH_CONNECT_V4
层的 ClassifyFn
回调函数中,可以访问以下关键信息:
源和目标 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]
)。
源和目标端口:
源端口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;
协议类型:
- 协议类型(如 TCP、UDP 等),通过
incomingPacket->incomingValues[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_PROTOCOL]
获取。
进程信息:
- 发起连接的进程 ID(PID)。
inMetaValues->processId
- 进程路径和名称:
inMetaValues->processPath
用户信息:
- 发起连接的用户 SID(安全标识符)。
- 用户名称(可以通过 SID 进一步查询)。
连接方向:
- 连接是入站(Inbound)还是出站(Outbound)。
其他网络层信息:
- 网络接口信息(如接口索引)。
- 其他与连接相关的元数据。
一个 层 中可以有多个 子层
可以这样添加子层
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>
const GUID SubLayer_A = { }; const GUID 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_A
和 SubLayer_B
分别表示防火墙和日志记录规则。
weight
决定了子层的优先级。
当每个子层符合注册的filter(后面提到)的条件后,就会调用ClassifyFn函数
层的条件:
我们可以查看这个文档
每个筛选层 (Fwpmu.h) 可用的筛选条件 - Win32 apps | Microsoft Learn
每一层都可以有自己的筛选条件
例如FWPM_LAYER_OUTBOUND_TRANSPORT_V4
这一层,在fieldKey设置条件类型,通过查询文档,我们选择一个远程port的条件

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;
|

再比如应用层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.layerKey = *layerKey;
filter.displayData.name = (wchar_t*)filterName; filter.displayData.description = (wchar_t*)filterDesc;
filter.action.type = FWP_ACTION_CALLOUT_TERMINATING; filter.action.calloutKey = *calloutKey;
filter.filterCondition = filterConditions;
filter.subLayerKey = DD_PROXY_SUBLAYER;
filter.weight.type = FWP_EMPTY;
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.numFilterConditions = conditionIndex;
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_TERMINATING
和FWP_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
| 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 ) { 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; 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
| 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
| mFilter.action.calloutKey = *calloutKey;
mFilter.action.type = FWP_ACTION_CALLOUT_TERMINATING;
mFilter.displayData.name = L"WFP TEST"; mFilter.displayData.description = L"yxp's WFP TEST";
mFilter.layerKey = *layerKey;
mFilter.numFilterConditions = 0;
mFilter.filterCondition = mFilter_condition;
mFilter.subLayerKey = FWPM_SUBLAYER_UNIVERSAL;
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) #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 ( GUID_ALE_AUTH_CONNECT_CALLOUT_V4, 0x6812fc83, 0x7d3e, 0x499a, 0xa0, 0x12, 0x55, 0xe0, 0xd8, 0x5f, 0x34, 0x8b );
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 };
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;
UINT32 gAleConnectCalloutId = 0;
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,
IN const FWPS_INCOMING_METADATA_VALUES0* inMetaValues, IN OUT void* layerData, IN const void* classifyContext, IN const FWPS_FILTER* filter, IN UINT64 flowContext, OUT FWPS_CLASSIFY_OUT* classifyOut ) {
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, 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.calloutKey = *calloutKey; sCallout.classifyFn = classifyFn; sCallout.flowDeleteFn = flowDeleteNotifyFn; sCallout.notifyFn = notifyFn;
status = FwpsCalloutRegister(gDevObj, &sCallout, calloutId ); if (!NT_SUCCESS(status)) goto exit;
bCalloutRegistered = TRUE;
mDispData.name = L"WFP TEST"; mDispData.description = L"yxp's WFP TEST"; mCallout.applicableLayer = *layerKey;
mCallout.calloutKey = *calloutKey; mCallout.displayData = mDispData;
status = FwpmCalloutAdd(gEngineHandle, &mCallout, NULL, NULL); if (!NT_SUCCESS(status)) goto exit;
AddSubLayers(gEngineHandle);
mFilter.filterKey = Filter_Guid_A;
mFilter.action.calloutKey = *calloutKey; 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;
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;
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; 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;
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, &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) { FwpmFilterDeleteById(gEngineHandle, gAleConnectFilterId); FwpmCalloutDeleteById(gEngineHandle, gAleConnectCalloutId); gAleConnectFilterId = 0; FwpsCalloutUnregisterById(gAleConnectCalloutId); 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函数:
由驱动暴露出来的一组接口函数, 它本质上是一个结构,包含了过滤回调函数(例如 ClassifyFn
、NotifyFn
和 FlowDeleteFn
)。 负责进一步分析或者修改包。比如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.calloutKey = *calloutKey; sCallout.classifyFn = classifyFn; sCallout.flowDeleteFn = flowDeleteNotifyFn; sCallout.notifyFn = notifyFn;
status = FwpsCalloutRegister(gDevObj, &sCallout, calloutId );
|
然后管理 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;
mCallout.calloutKey = *calloutKey; mCallout.displayData = mDispData;
status = FwpmCalloutAdd(gEngineHandle, &mCallout, NULL, NULL);
|
这样,就可以把一个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; UINT32 flags; FWPS_CALLOUT_CLASSIFY_FN classifyFn; FWPS_CALLOUT_NOTIFY_FN notifyFn; FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN 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, [in] const FWPS_INCOMING_METADATA_VALUES0 *inMetaValues, [in, out, optional] void *layerData, [in, optional] const void *classifyContext, [in] const FWPS_FILTER2 *filter, [in] UINT64 flowContext, [in, out] FWPS_CLASSIFY_OUT0 *classifyOut )
|
其中 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.calloutKey = *calloutKey; sCallout.classifyFn = (FWPS_CALLOUT_CLASSIFY_FN3)classifyFn; sCallout.notifyFn = (FWPS_CALLOUT_NOTIFY_FN3)notifyFn; sCallout.flowDeleteFn = (FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN)flowDeleteNotifyFn;
|
WFP工作的整体流程
以下是 WFP 驱动过滤的总结流程:
- 数据包进入网络协议栈:当一个网络数据包到达时,它会进入网络协议栈进行处理。
- 协议栈调用 Shim(中间层):网络协议栈会调用 Shim 层,Shim 是过滤过程中的关键参与者。
- Shim 调用分类过程:Shim 在指定的网络层上启动分类过程,对数据包进行过滤规则匹配。
- 分类过程中匹配过滤器并执行动作:在分类过程中,系统会将数据包与过滤规则进行匹配,并根据匹配结果采取相应的动作(类似于 TDI 的 quick filter() 功能)。
- 匹配到 Callout 过滤器时调用对应 Callout 函数:如果分类过程中匹配到了 Callout 过滤器,则会调用相应的 Callout 函数进行自定义处理。
- Shim 根据最终过滤决策执行动作:Shim 根据分类的最终决策(例如丢弃数据包、允许通过等)对数据包进行处理。
FWPM和FWPS
FWPM
(Filtering Platform Management)是 Windows Filtering Platform (WFP) 中用于 配置和管理过滤引擎 的 API 集合
FWPM
提供了一组 API,用于在 用户态 配置和管理 WFP 过滤引擎。它的功能包括但不限于:
- 管理 Callout:
- 添加、删除和枚举 Callout。
- 例如:
FwpmCalloutAdd
、FwpmCalloutDeleteByKey0
、FwpmCalloutEnum0
。
- 管理 Filter:
- 添加、删除和枚举 Filter。
- 例如:
FwpmFilterAdd
、FwpmFilterDeleteByKey0
、FwpmFilterEnum0
。
- 管理 Sublayer:
- 添加、删除和枚举 Sublayer。
- 例如:
FwpmSubLayerAdd
、FwpmSubLayerDeleteByKey0
、FwpmSubLayerEnum0
。
- 管理 Provider:
- 添加、删除和枚举 Provider。
- 例如:
FwpmProviderAdd
、FwpmProviderDeleteByKey0
、FwpmProviderEnum0
。
- 管理会话(Session):
- 打开、关闭和配置过滤引擎会话。
- 例如:
FwpmEngineOpen
、FwpmEngineClose
、FwpmTransactionBegin
,FwpmTransactionAbort
FWPS
(Filtering Platform Session)是 Windows Filtering Platform (WFP) 中用于 内核态 的 API 集合。它与 FWPM
(用户态 API)相对应,主要用于在内核态实现网络数据包的过滤和处理。
FWPS
的作用
FWPS
提供了一组 API,用于在内核态实现以下功能:
- 注册和管理 Callout:
- 注册 Callout,并实现分类函数(
ClassifyFn
)、通知函数(NotifyFn
)等。
- 例如:
FwpsCalloutRegister
、FwpsCalloutUnregisterById
。
- 处理网络数据包:
- 在 Callout 的分类函数中,检查数据包的元数据和内容,并决定是否允许、阻止或修改数据包。
- 注入数据包:
- 将修改后的数据包重新注入网络堆栈。
- 例如:
FwpsInjectMacSendAsync
、FwpsInjectMacReceiveAsync
。
- 管理网络流:
- 跟踪和管理网络流(Flow)的生命周期。
- 例如:
FwpsFlowAssociateContext
、FwpsFlowRemoveContext
。
设置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.layerKey = *layerKey;
filter.displayData.name = (wchar_t*)filterName; filter.displayData.description = (wchar_t*)filterDesc;
filter.action.type = FWP_ACTION_CALLOUT_TERMINATING; filter.action.calloutKey = *calloutKey;
filter.filterCondition = filterConditions;
filter.subLayerKey = DD_PROXY_SUBLAYER;
filter.weight.type = FWP_EMPTY;
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.numFilterConditions = conditionIndex;
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 和回调函数。
- 回调函数(如
MyClassifyFn
和 MyNotifyFn
)将在数据包分类和通知时被调用。
Filter 创建:
AddFilter
定义了一个 Filter,将其 action.calloutKey
设置为 Callout 的 GUID,从而将两者关联。
事务管理:
- 通过
FwpmTransactionBegin
和 FwpmTransactionCommit
确保 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
这两个库文件

从以下这篇文章,我们分析一下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;
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, &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, 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.calloutKey = *calloutKey; sCallout.classifyFn = classifyFn; sCallout.flowDeleteFn = flowDeleteNotifyFn; sCallout.notifyFn = notifyFn;
status = FwpsCalloutRegister(gDevObj, &sCallout, calloutId ); if (!NT_SUCCESS(status)) goto exit;
bCalloutRegistered = TRUE;
mDispData.name = L"WFP TEST"; mDispData.description = L"yxp's WFP TEST"; mCallout.applicableLayer = *layerKey;
mCallout.calloutKey = *calloutKey; mCallout.displayData = mDispData;
status = FwpmCalloutAdd(gEngineHandle, &mCallout, NULL, NULL); if (!NT_SUCCESS(status)) goto exit;
mFilter.action.calloutKey = *calloutKey; mFilter.action.type = FWP_ACTION_CALLOUT_TERMINATING;
mFilter.displayData.name = L"WFP TEST"; mFilter.displayData.description = L"yxp's WFP TEST";
mFilter.layerKey = *layerKey;
mFilter.numFilterConditions = 0; mFilter.filterCondition = mFilter_condition;
mFilter.subLayerKey = FWPM_SUBLAYER_UNIVERSAL;
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; }
|