前言

作为现在主流的过滤方案 WFP 已经占据一定市场地位,
毕竟到本文行文这个时间点上,还在使用xp系统的已经屈指可数了。

本文将来聊一聊WFP 有关的一些内容。

WFP

WFP是什么?

By providing a simpler development platform, WFP is designed to replace previous packet filtering technologies such as Transport Driver Interface (TDI) filters, Network Driver Interface Specification (NDIS) filters, and Winsock Layered Service Providers (LSP).Starting in Windows Server 2008 and Windows Vista, the firewall hook and the filter hook drivers are not available; applications that were using these drivers should use WFP instead.

WFP 设计目的是为了替代以前的数据包过滤技术,例如 TDI 过滤, NDIS 过滤 以及 LSP。自 Windows Server 2008 && Windows Vista 起,以前的过滤方式不再有效,这些程序均需要改用 WFP实现。

wfp-architecture.png

首先过滤器引擎包含用户模式组件及内核模式组件两大部分,它们将共同完成数据过滤工作。
然而无论使用用户模式过滤引擎还是内核模式过滤引擎,最终都是与内核模式过滤引擎交互。

再看这两个组件中又包含有多个过滤层,通常称为分层,这多个过滤层又被分别对应着具体的网络协议栈。
又根据过滤层所属的组件,分为了用户模式过滤层及内核模式过滤层。

用户模式组件是针对 RPCIPsec 的过滤,其中包含有10个用户模式过滤层。
内核模式组件属于 WFP的核心部分,主要在 TPC/IP协议栈及传输层进行过滤,包含大约50个内核模式过滤层。

接着看 Shim,通常翻译为垫片,其被以内核模块的形式插入到网络协议栈中,负责网络协议栈和过滤引擎交互,关于垫片,在实际开发并不需要关注。

过滤引擎概览

wfparch.png

在概览图和上面的框架图中,都能看到一个名为 Callout 的模块,官网上使用的机翻,翻译为 标记、标注,由于该翻译不能反映其特性,本文中,决定不对其进行翻译。

那么Callout 是什么呢?
在此先简单理解为,当过滤引擎检测到过滤条件为TRUE时,会触发相应行为,Callout则用于定义被触发的行为。

由此便可以将 WFP 驱动开发分为两个部分:

  1. Callout 添加到指定位置
  2. 定义 Callout 中的行为

Callout注册及反注册

本文并不打算贴源码,只对关键函数进行说明,可配合文末github中的源码阅读。

注册

  1. FwpmEngineOpen -- 用于打开过滤引擎,返回一个句柄
  2. FwpmTransactionBegin -- 设置会话权限,设置为0即可
  3. FwpsCalloutRegister -- 注册 Callout 到过滤引擎,从其参数上看,也可看做绑定设备对象
  4. FwpmCalloutAdd -- 正式向过滤引擎添加 Callout,与上面注册的 Callout 相关联
  5. FwpmSubLayerAdd -- 添加一个子层(可以不添加),通过控制子层的权重,使得本驱动的过滤模块更具话语权
  6. FwpmFilterAdd -- 添加一个过滤器
  7. FwpmTransactionCommit -- 提交以上内容,在此函数被正确调用后,过滤正式生效
  8. FwpmTransactionAbort -- 在上述过程失败时进行回滚

接下来将对其中重要的部分逐步说明
3步中,会用到结构FWPS_CALLOUT,其成员说明如下:
calloutKey 对应一个 GUID,作为本 Callout的唯一标识
classifyFn 会在触发过滤时被调用,可以在其中拦截/放行当前操作。
flowDeleteNotifyFn 会在数据流将终止且该数据流已关联上下文时被调用
notifyFn 在加入或移除过滤器时触发。

4步中,会用到结构 FWPM_CALLOUT,其中关键成员说明如下:
applicableLayer 指明该 Callout 适用于哪一个层。
calloutKey对应到第三步中 Callout 的唯一标识,如此便能将本结构与第3步中的结构进行关联。

5步中,添加一个子层,并非必要操作,只是在添加子层后,能够更加灵活的控制过滤模块的位置。

6步中,会用到 FWPM_FILTER 结构,其中关键成员说明如下:
layerKey 指明该过滤器被添加到哪个层,设置为与 applicableLayer 一致即可。
displayData 字符描述,不重要
action.type 指明过滤器的操作类型,通常使用 FWP_ACTION_CALLOUT_TERMINATINGFWP_ACTION_CALLOUT_INSPECTION,二者都会调用 Callout,区别在于是否允许阻止/允许操作。
action.calloutKey3步的 GUID,将过滤器与Callout关联,是否需要关联还取决于上面 type
subLayerKey 指定子层,如果第5步中,不添加新的子层,可以指定为系统默认子层FWPM_SUBLAYER_UNIVERSAL
weight.type 指定过滤器权重,将该type设置为 FWP_EMPTY,可以由系统自动分配权重。
numFilterConditions 设置过滤条件个数,可以是零到多个。
filterCondition 这是个结构体数组,可以在中间指定过滤条件,注意条件个数要与numFilterConditions一致。
filterConditions[0].fieldKey 指明过滤的条件
filterConditions[0].matchType 指明匹配的方式
filterConditions[0].conditionValue.type 指明值的类型
filterConditions[0].conditionValue.v4AddrMask 指明值,这是个联合体,应根据类型选定需要的成员

e.g.
A(条件) ==(匹配方式) B(值)

相关参考:

反注册

反注册为注册的逆过程,类似栈操作,先入后出

  1. FwpmFilterDeleteById
  2. FwpmSubLayerDeleteByKey
  3. FwpmCalloutDeleteById
  4. FwpsCalloutUnregisterById
  5. FwpmEngineClose

因为很简单,本文就不说明了。

Callout中的回调函数

在上节中以及提到,Callout一共有3个回调函数可以供用户处理。

其中
notifyFnflowDeleteNotifyFn,应用较少,
flowDeleteNotifyFn 在获取流数据时可以由它来最后回收资源。

关于绑定流句柄上下文的函数有:
FwpsFlowAssociateContext0
FwpsFlowRemoveContext0

重点说一下 classifyFn,这个函数有7个参数,原型如下

void FwpsCalloutClassifyFn1(
  const FWPS_INCOMING_VALUES0 *inFixedValues,
  const FWPS_INCOMING_METADATA_VALUES0 *inMetaValues,
  void *layerData,
  const void *classifyContext,
  const FWPS_FILTER1 *`filter`,
  UINT64 flowContext,
  FWPS_CLASSIFY_OUT0 *classifyOut
)

inFixedValues 包含网络数据信息,例如ip port等
inMetaValues 元数据,包含和过滤相关的信息,其内成员并不都可用,需要配合宏FWPS_IS_METADATA_FIELD_PRESENT测试
layerData 网络原始数据
classifyContext 驱动的上下文信息
filter 过滤器相关信息
flowContext 流句柄关联的上下文信息
classifyOut 返回值,可以在该结构的 actionType 中指示动作类型,诸如 拦截放行,下发到下层过滤器等操作。

分层与子层

简单说一下分层与子层。
在读源码的时候我也困惑了一会,为何一个子层可以用于不同分层的过滤器。

WFPLayers.png
上图就是分层与子层之间的关系了。

事实上,每一个分层都会涵盖所有子层,也就是说子层是公用的。

概览与总结

首先 WFP 分为两大组件,组件中又包含多个分层(过滤层),每个分层中又涵盖所有子层,每个子层内又包含一定数量的过滤器,这些过滤器只会在其适配的模式下进行工作,
过滤器中又包含条件和 Callout,通过注册 Callout,就能对条件成立的数据包进行更细粒度的过滤。
过滤器中也可以不包含 Callout 仅根据条件进行过滤,又或是不包含条件,仅依赖 Callout 进行过滤。
如果都不包含,那这个过滤器就没意义了。

本文对 WFP 相关的知识只是进行了初步的介绍,剩下的细节仍旧需要根据实际的需求进一步学习。

参考


插曲

本来写完了,结果防火墙出了点问题,导致提交的时候全部被过滤掉了。后面又花费数个小时重写,好在由于已经梳理了一次,重写的时候条理更加清晰了,不过相应的也去掉了很多细节。