WFP备忘 Home

WFP备忘

WFP的定位

WFP用来扩展NDIS Filter引擎的功能,完成更加复杂的数据包操作。

WFP提供的主要扩展机制是Callout Driver。

通过Callout Driver 向Filter 引擎注册一个或者多个Callout。

Filter引擎通过Filter关联的Callout,来决定何时调用Callout的函数。

每个Filter规则都工作在一个特定的 Filtering Layer

TCP/IP 网络栈中预先定义了若干 Filtering Layer,开发者无法添加和删除 Filtering Layer

WFP架构

TCP/IP 网络栈处于硬件设备网卡和用户态软件应用之间。当数据包进入网卡并向上流往用户应用程序时,会经过一系列的网络栈进行封包/解包等处理。WFP使用网络栈中预先定义的一些 Filtering Layer,实现对网络包的复杂过滤操作。

Filter Engine 是WFP的核心,负责管理Filter和Callout。

WFP架构图

Filtering Layer

Filtering Layer 表示 TCP/IP 网络栈中的一个可以过滤的点。

每一个 Filtering Layer 可以访问数据包的特定字段。

用户态的 Filtering Layer 识别符,每个用128 bit的GUID代表,名字格式 FWPM_XXX。列表如下:

Management Filtering Layer Identifiers

内核态的 Filtering Layer识别符,每个用64 bit的LUID代表,名字格式 FWPS_XXX。列表如下:

Run-time Filtering Layer Identifiers

开发者可以为 Filtering Layer 创建多个子层(sub-layers),以方便过滤决策。

Callout

Callout的注册

每个 Callout 有一个GUID,及三个Callout函数。

1
2
3
4
5
6
7
typedef struct FWPS_CALLOUT0_ {
  GUID                                calloutKey;
  UINT32                              flags;
  FWPS_CALLOUT_CLASSIFY_FN0           classifyFn;
  FWPS_CALLOUT_NOTIFY_FN0             notifyFn;
  FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN0 flowDeleteFn;
} FWPS_CALLOUT0;

Win7使用 FWPS_CALLOUT1, Win8使用FWPS_CALLOUT2,及相应编号的注册函数。参考 WFP Version-Independent Names and Targeting Specific Versions of Windows

通过调用 FwpsCalloutRegister0 把 Callout 注册到 Filter 引擎。注册成功后,返回 CalloutId,后续的操作,如注销Callout等,需要该Id。

1
2
3
4
5
6
status =
 FwpsCalloutRegister0(
 deviceObject,
      &Callout,
      &CalloutId
      );

每个 Callout Driver 可以注册多个Callout,每个Callout都需要调用一次 FwpsCalloutRegister0

Callout 函数

notifyFn

原型:

1
2
3
4
5
NTSTATUS NTAPI notifyFn0(
  _In_       FWPS_CALLOUT_NOTIFY_TYPE notifyType,
  _In_ const GUID                     *filterKey,
  _In_ const FWPS_FILTER0             *filter
)

发生跟Callout相关联的事件时,Filter引擎会回调该函数。目前的事件有:

添加了一条使用了该Callout的Filter。

一条使用该Callout的Filter被移除。

classifyFn

原型:

1
2
3
4
5
6
7
8
void NTAPI classifyFn0(
  _In_    const FWPS_INCOMING_VALUES0          *inFixedValues,
  _In_    const FWPS_INCOMING_METADATA_VALUES0 *inMetaValues,
  _Inout_       void                           *layerData,
  _In_    const FWPS_FILTER0                   *filter,
  _In_          UINT64                         flowContext,
  _Inout_       FWPS_CLASSIFY_OUT0             *classifyOut
)

当数据包满足Filter的条件,Filter引擎会回调该函数,把数据包交给Callout处理。

flowDeleteFn

原型:

1
2
3
4
5
void NTAPI flowDeleteFn(
  _In_ UINT16 layerId,
  _In_ UINT32 calloutId,
  _In_ UINT64 flowContext
)

当关联了一个Context的数据流停止时,Filter引擎回调该函数,通知Callout。

Filter

创建Filter

Filter 代表一条过滤规则,描述了过滤条件、挂接的Flitering Layer、及满足条件后采取的行动。

原型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct FWPM_FILTER0_ {
  GUID                   filterKey;
  FWPM_DISPLAY_DATA0     displayData;
  UINT32                 flags;
  GUID                   *providerKey;
  FWP_BYTE_BLOB          providerData;
  GUID                   layerKey;
  GUID                   subLayerKey;
  FWP_VALUE0             weight;
  UINT32                 numFilterConditions;
  FWPM_FILTER_CONDITION0 *filterCondition;
  FWPM_ACTION0           action;
  union {
    UINT64 rawContext;
    GUID   providerContextKey;
  };
  GUID                   *reserved;
  UINT64                 filterId;
  FWP_VALUE0             effectiveWeight;
} FWPM_FILTER0;

filterCondition 是一个过滤条件数组,条目数由 numFilterConditions 指定。

如下代码设定初始化了一个过滤条件。

1
2
3
4
5
6
FWPM_FILTER_CONDITION0 conds[1];
// ICMP Type == Destination Unreachable.
conds[0].fieldKey = FWPM_CONDITION_ICMP_TYPE;
conds[0].matchType = FWP_MATCH_EQUAL;
conds[0].conditionValue.type = FWP_UINT16;
conds[0].conditionValue.uint16 = 3;

action 表示对满足过滤条件的数据采取的行动。可以通过calloutkey 把 Callout 指定为 action

1
2
3
4
5
6
7
typedef struct FWPM_ACTION0_ {
  FWP_ACTION_TYPE type;
  union {
    GUID filterType;
    GUID calloutKey;
  };
} FWPM_ACTION0;

注册Filter

1
2
3
4
5
6
7
8
9
10
11
12
13
result = FwpmEngineOpen0(
    NULL,
    RPC_C_AUTHN_WINNT,
    NULL,
    NULL,
    &engineHandle );

result = FwpmTransactionBegin0(engine, 0);
EXIT_ON_ERROR(FwpmTransactionBegin0);
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
result = FwpmTransactionCommit0(engine);
EXIT_ON_ERROR(FwpmTransactionCommit0);

Filter 决策

当流量抵达 Filtering Layer 后,会按照sub-layers的权重,依次流经每个sub-layer。 每个sub-layer对流量返回 PermitBlockContinue 或者没有匹配到任何条件。 即使高权重的sub-layer已经返回了 Block ,依然会调用后面的 sub-layer,以保证到达 Filtering Layer 的流量通过每个 sub-layer。 这样做的好处是,不会出现观察者类型的Filter(比如log)漏掉记录。

然后过滤引擎综合所有sub-layer的结果,依据预定义的策略,得出最终结果。 预定义策略遵循[1]:

  1. 按照优先级顺序,依次评估sub-layer的结果
  2. Block 覆盖 Permit
  3. 如果Filter没有设置 FWPS_RIGHT_ACTION_WRITE 标志, BLOCK 即为最终结果。
  4. 设置了 FWPS_RIGHT_ACTION_WRITE 标志的 Filter 可以被其他 sub-layer 覆盖结果。

过滤决策例图

过滤决策流程图 外面的大框表示Filtering Layer, 里面的小框表示sub-layer 每个sub-layer包含一些过滤规则

参考文档

[1] Filter Arbitration https://msdn.microsoft.com/en-us/library/aa364008(v=vs.85).aspx

Fork me on GitHub