Windows过滤平台WFP
一、前言
在WFP之前,由于网络数据包处理比较复杂,诸如:NDIS Filter、TDI、Windows LSP,所以在Vista之后,微软开发一种框架,称之为WFP(Windows过滤平台),其将NDIS、TDI等诸多细节隐藏了起来,用一系列WFP相关的内核API进行分装,使得开发人员可以更加方便地编写网络数据包过滤驱动。WFP可用来开发防火墙、网络监视程序等
二、WFP细节
1.框架模型

在整个框架中,分为用户与内核两层,内核层中最重要的也是最核心的就是内核过滤引擎KFE,用户层最重要的就是基础过滤引擎BFE。下面主要针对内核过滤引擎进行描述。
WFP整个框架的核心就是通过传递网络数据栈中的数据流量包给过滤引擎,过滤引擎内部会做一系列操作,并且调用外部定义的一些函数,并且函数返回的结果会反馈给过滤引擎,过滤引擎进而反馈给网络数据栈中存在的一种称之为垫片的程序,根据反馈的结果垫片程序决定是否进行拦截、放行数据包。
在过滤引擎中存在着多个分层,与网络数据栈中的分层一一对应,哪一个分层的数据包到达分层时,垫片程序就会发送传递给内核引擎中的对应的分层,进而根据注册在过滤引擎中的过滤器去做拦截、放行的结果反馈,反馈给垫片,由垫片去拦截或者放行数据包。
在每一个分层中,可以人为地添加一个或多个子层。这些子层有一个名称为权重weight的属性。weight越大,则子层在分层中越高,越高就会越优先获取到数据。
在WFP中,过滤器也是非常重要。过滤器决定着过滤数据的条件以及满足条件之后的操作。其可以添加到分层中,也可以添加到分层中的子层中。只不过在同一个分层或者子层中,不同过滤器的权重不能相同。类似于分层的权重,过滤器的权重越大,则说明此过滤器越会优先匹配条件进而操作。除此之外,过滤器还可以关联callout呼出接口,当满足过滤器的条件后,某个callout呼出接口便是会被调用。
callout呼出接口有三个,如下:
//过滤器的规则被全部命中时,过滤引擎会调用此回调函数
typedef VOID NTAPI Wfp_Sample_Established_ClassifyFn_V4(
IN const FWPS_INCOMING_VALUES0 *inFixedValues,
IN const FWPS_INCOMING_METADATA_VALUES *inMetaValues,
IN OUT VOID *layerData, //表示被过滤的网络原始数据
IN OPTIONAL const void *classifyContext,
IN const FWPS_FILTER3 *filter,
IN UINT64 flowContext,
OUT FWPS_CLASSIFY_OUT *classifyOut //表示拦截数据包或者放行
)
ClassifyFn
;
typedef NTSTATUS NTAPI Wfp_Sample_Established_NotifyFn_V4(
IN FWPS_CALLOUT_NOTIFY_TYPE notifyType,
IN const GUID* filterKey,
IN const FWPS_FILTER* filter)
NotifyFn
;
typedef VOID NTAPI Wfp_Sample_Established_FlowDeleteFn_V4(
IN UINT16 layerId,
IN UINT32 calloutId,
IN UINT64 flowContext
)
Deletefn
;
其中ClassifyFn只有当满足相关联的过滤器的条件时,才会被调用。而NotifyFn是在过滤器被添加或者删除的时候被调用的。DeleteFn是当一个网络数据流即将被终止时才会调用。只有在这个将被终止的数据流被关联了上下文的情况下,才会被回调。
2.WFP建立过程
1)首先需要我们打开过滤器引擎,获取返回值引擎句柄,打开引擎使用的是FwpmEngineOpen
2)构造类型为FWPS_CALLOUT的变量Callout,其中包含了我们上面说的3中callout呼出接口。然后使用
FwpsCalloutRegister来对callout进行注册。主要Callout中的calloutKey是一串GUID值,用于唯一表示callout结构。后面会根据此GUID值找到callout呼出接口进行调用。
3)构造FWPM_CALLOUT(注意与第二步的callout类型FWPS_CALLOUT不一样)变量fwpmCallout。虽然类型不同,但是变量中的calloutKey值确实相同的。还需要将callout添加到的分层的GUID值赋给成员applicableLayer。最后使用FwpmCalloutAdd添加到过滤引擎中。要是callout已经存在,那么函数返回STATUS_FWP_ALREADY_EXISTS。

3.添加子层
设置子层的GUID以及权重,随后FwpmSubLayerAdd添加子层。

4.添加过滤器
过滤器类型为FWPM_FILTER0(0会随着版本变化,WFP相关函数及结构体后跟数字的,数字会随着版本变化)。FWPM_FILTER0类型如下:
typedef struct FWPM_FILTER0_
{
GUID filterKey;
FWPM_DISPLAY_DATA0 displayData;
UINT32 flags;
/* [unique] */ GUID *providerKey;
FWP_BYTE_BLOB providerData;
GUID layerKey;
GUID subLayerKey;
FWP_VALUE0 weight;
UINT32 numFilterConditions;
/* [unique][size_is] */ FWPM_FILTER_CONDITION0 *filterCondition;
FWPM_ACTION0 action;
/* [switch_is] */ /* [switch_type] */ union
{
/* [case()] */ UINT64 rawContext;
/* [case()] */ GUID providerContextKey;
} ;
/* [unique] */ GUID *reserved;
UINT64 filterId;
FWP_VALUE0 effectiveWeight;
} FWPM_FILTER0;
filterKey:过滤器的唯一标识,如果为0,系统会自动分配
displayData:里面是描述信息。随意填。
flags:置0
layerKey:分层的唯一标识(GUID),表明过滤器被添加到哪一分层中。
subLayerKey:子层的唯一标识(GUID),表明过滤器被添加到哪一子层中。如果置IID_NULL,则添加到默认子层中。
weight:过滤器的权重。子层的权重用一个UINT16表示。过滤器权重为FWP_VALUE0。
FWP_VALUE0结构如下:
typedef struct FWP_VALUE0_
{
FWP_DATA_TYPE type;
/* [switch_is][switch_type] */ union
{
/* [case()] */ /* Empty union arm */
/* [case()] */ UINT8 uint8;
/* [case()] */ UINT16 uint16;
/* [case()] */ UINT32 uint32;
/* [case()][unique] */ UINT64 *uint64;
/* [case()] */ INT8 int8;
/* [case()] */ INT16 int16;
/* [case()] */ INT32 int32;
/* [case()][unique] */ INT64 *int64;
/* [case()] */ float float32;
/* [case()][unique] */ double *double64;
/* [case()][unique] */ FWP_BYTE_ARRAY16 *byteArray16;
/* [case()][unique] */ FWP_BYTE_BLOB *byteBlob;
/* [case()][unique] */ SID *sid;
/* [case()][unique] */ FWP_BYTE_BLOB *sd;
/* [case()][unique] */ FWP_TOKEN_INFORMATION *tokenInformation;
/* [case()][unique] */ FWP_BYTE_BLOB *tokenAccessInformation;
/* [case()][string] */ LPWSTR unicodeString;
/* [case()][unique] */ FWP_BYTE_ARRAY6 *byteArray6;
/* [case()][unique] */ FWP_BITMAP_ARRAY64 *bitmapArray64;
} ;
} FWP_VALUE0;
type指定了联合体中那一成员有效。类似于位图。type类型定义如下:
enum FWP_DATA_TYPE_
{
FWP_EMPTY = 0,
FWP_UINT8 = ( FWP_EMPTY + 1 ) ,
FWP_UINT16 = ( FWP_UINT8 + 1 ) ,
FWP_UINT32 = ( FWP_UINT16 + 1 ) ,
FWP_UINT64 = ( FWP_UINT32 + 1 ) ,
FWP_INT8 = ( FWP_UINT64 + 1 ) ,
FWP_INT16 = ( FWP_INT8 + 1 ) ,
FWP_INT32 = ( FWP_INT16 + 1 ) ,
FWP_INT64 = ( FWP_INT32 + 1 ) ,
FWP_FLOAT = ( FWP_INT64 + 1 ) ,
FWP_DOUBLE = ( FWP_FLOAT + 1 ) ,
FWP_BYTE_ARRAY16_TYPE = ( FWP_DOUBLE + 1 ) ,
FWP_BYTE_BLOB_TYPE = ( FWP_BYTE_ARRAY16_TYPE + 1 ) ,
FWP_SID = ( FWP_BYTE_BLOB_TYPE + 1 ) ,
FWP_SECURITY_DESCRIPTOR_TYPE = ( FWP_SID + 1 ) ,
FWP_TOKEN_INFORMATION_TYPE = ( FWP_SECURITY_DESCRIPTOR_TYPE + 1 ) ,
FWP_TOKEN_ACCESS_INFORMATION_TYPE = ( FWP_TOKEN_INFORMATION_TYPE + 1 ) ,
FWP_UNICODE_STRING_TYPE = ( FWP_TOKEN_ACCESS_INFORMATION_TYPE + 1 ) ,
FWP_BYTE_ARRAY6_TYPE = ( FWP_UNICODE_STRING_TYPE + 1 ) ,
FWP_BITMAP_INDEX_TYPE = ( FWP_BYTE_ARRAY6_TYPE + 1 ) ,
FWP_BITMAP_ARRAY64_TYPE = ( FWP_BITMAP_INDEX_TYPE + 1 ) ,
FWP_SINGLE_DATA_TYPE_MAX = 0xff,
FWP_V4_ADDR_MASK = ( FWP_SINGLE_DATA_TYPE_MAX + 1 ) ,
FWP_V6_ADDR_MASK = ( FWP_V4_ADDR_MASK + 1 ) ,
FWP_RANGE_TYPE = ( FWP_V6_ADDR_MASK + 1 ) ,
FWP_DATA_TYPE_MAX = ( FWP_RANGE_TYPE + 1 )
} FWP_DATA_TYPE;
当type为FWP_EMPTY时,自动生成权重值。
numFilterConditions:表示条件数组中的元素数,即条件的数量。
filterCondition:条件数组,类型为FWPM_FILTER_CONDITION0。定义如下:
typedef struct FWPM_FILTER_CONDITION0_
{
GUID fieldKey;
FWP_MATCH_TYPE matchType;
FWP_CONDITION_VALUE0 conditionValue;
} FWPM_FILTER_CONDITION0;
fieldKey:网络数据包字段的标识。128位的GUID值。可以取下列链接中的值。我们例子中使用的是FWPM_CONDITION_IP_REMOTE_ADDRESS,表示远程地址。
matchType为条件判断类型,判断fieldKey指定的字段值是否满足条件判断。
enum FWP_MATCH_TYPE_
{
FWP_MATCH_EQUAL = 0, //相等
FWP_MATCH_GREATER = ( FWP_MATCH_EQUAL + 1 ) ,//大于
FWP_MATCH_LESS = ( FWP_MATCH_GREATER + 1 ) , //小于
FWP_MATCH_GREATER_OR_EQUAL = ( FWP_MATCH_LESS + 1 ) ,
FWP_MATCH_LESS_OR_EQUAL = ( FWP_MATCH_GREATER_OR_EQUAL + 1 ) ,
FWP_MATCH_RANGE = ( FWP_MATCH_LESS_OR_EQUAL + 1 ) ,
FWP_MATCH_FLAGS_ALL_SET = ( FWP_MATCH_RANGE + 1 ) ,
FWP_MATCH_FLAGS_ANY_SET = ( FWP_MATCH_FLAGS_ALL_SET + 1 ) ,
FWP_MATCH_FLAGS_NONE_SET = ( FWP_MATCH_FLAGS_ANY_SET + 1 ) ,
FWP_MATCH_EQUAL_CASE_INSENSITIVE = ( FWP_MATCH_FLAGS_NONE_SET + 1 ) ,
FWP_MATCH_NOT_EQUAL = ( FWP_MATCH_EQUAL_CASE_INSENSITIVE + 1 ) ,
FWP_MATCH_PREFIX = ( FWP_MATCH_NOT_EQUAL + 1 ) ,
FWP_MATCH_NOT_PREFIX = ( FWP_MATCH_PREFIX + 1 ) ,
FWP_MATCH_TYPE_MAX = ( FWP_MATCH_NOT_PREFIX + 1 )
} FWP_MATCH_TYPE;
conditionValue:需要与fieldKey比较判断的值所在结构。类似于上面位图的结构,都是通过type来指定联合体中可用字段。
action:条件满足之后的动作。FWPM_ACTION0类型:
typedef struct FWPM_ACTION0_
{
FWP_ACTION_TYPE type;
/* [switch_is] */ /* [switch_type] */ union
{
/* [case()] */ GUID filterType;
/* [case()] */ GUID calloutKey;
/* [case()] */ UINT8 bitmapIndex;
} ;
} FWPM_ACTION0;
type:表示动作类型

calloutKey:表示此动作调用的callout呼出接口函数。
最后使用FwpmFilterAdd添加过滤器。

那么根据代码来看,过滤器条件满足就是远程ip地址是否等于v4AddrMask。v4AddrMask为0,所以是任何地址都可以。
5.callout呼出函数细节
//过滤器的规则被全部命中时,过滤引擎会调用此回调函数
VOID NTAPI Wfp_Sample_Established_ClassifyFn_V4(
IN const FWPS_INCOMING_VALUES0 *inFixedValues,
IN const FWPS_INCOMING_METADATA_VALUES *inMetaValues,
IN OUT VOID *layerData, //表示被过滤的网络原始数据
IN OPTIONAL const void *classifyContext,
IN const FWPS_FILTER3 *filter,
IN UINT64 flowContext,
OUT FWPS_CLASSIFY_OUT *classifyOut //表示拦截数据包或者放行
)
{
WORD wDirection = 0;
WORD wRemotePort = 0;
WORD wSrcPort = 0;
WORD wProtocol = 0;
ULONG ulSrcIPAddress = 0;
ULONG ulRemoteIPAddress = 0;
if (!(classifyOut->rights & FWPS_RIGHT_ACTION_WRITE)) //此属性表示classifyOut其他成员可被修改
{
return;
}
//wDirection表示数据包的方向,取值为 //FWP_DIRECTION_INBOUND/FWP_DIRECTION_OUTBOUND
wDirection = inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_DIRECTION].value.int8;
//wSrcPort表示本地端口,主机序
wSrcPort = inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_PORT].value.uint16;
//wRemotePort表示远端端口,主机序
wRemotePort = inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_PORT].value.uint16;
KdPrint(("--------------------------------------Port:%d\n", wRemotePort));
//ulSrcIPAddress 表示源IP
ulSrcIPAddress = inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_ADDRESS].value.uint32;
KdPrint(("-----------%d----------------\n", ulSrcIPAddress));
//ulRemoteIPAddress 表示远端IP
ulRemoteIPAddress = inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_ADDRESS].value.uint32;
KdPrint(("-----------%d----------------\n", ulRemoteIPAddress));
//wProtocol表示网络协议,可以取值是IPPROTO_ICMP/IPPROTO_UDP/IPPROTO_TCP
wProtocol = inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_PROTOCOL].value.uint8;
//默认"允许"(PERMIT)
classifyOut->actionType = FWP_ACTION_PERMIT;
if( wRemotePort == 443 )
{
classifyOut->actionType = FWP_ACTION_BLOCK;
}
//简单的策略判断,读者可以重写这部分
// if( (wProtocol == IPPROTO_TCP) &&
// (wDirection == FWP_DIRECTION_OUTBOUND) &&
// (wRemotePort == HTTP_DEFAULT_PORT) )
// {
// //TCP协议尝试发起80端口的访问,拦截(BLOCK)
// classifyOut->actionType = FWP_ACTION_BLOCK;
// }
//清除FWPS_RIGHT_ACTION_WRITE标记
if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT)
{
classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
}
return ;
}
其中要注意的是classifyOut->rights,此成员如果设置了FWPS_RIGHT_ACTION_WRITE,那么classifyOut其余成员才可以修改。incomingValue数组索引要根据分层的类型来确定。分层是ALE,那么就需要使用ALE对应的索引。
typedef enum FWPS_FIELDS_ALE_FLOW_ESTABLISHED_V4_
{
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_APP_ID,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_USER_ID,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_ADDRESS,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_ADDRESS_TYPE,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_PORT,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_PROTOCOL,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_ADDRESS,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_PORT,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_REMOTE_USER_ID,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_REMOTE_MACHINE_ID,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_DESTINATION_ADDRESS_TYPE,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_INTERFACE,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_DIRECTION,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_INTERFACE_TYPE,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_TUNNEL_TYPE,
#if (NTDDI_VERSION >= NTDDI_WIN6SP1)
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_FLAGS,
#if (NTDDI_VERSION >= NTDDI_WIN8)
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_ORIGINAL_APP_ID,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_PACKAGE_ID,
#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_SECURITY_ATTRIBUTE_FQBN_VALUE,
#if (NTDDI_VERSION >= NTDDI_WIN10_RS2)
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_COMPARTMENT_ID,
#endif // (NTDDI_VERSION >= NTDDI_WIN10_RS2)
#endif // (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
#endif // (NTDDI_VERSION >= NTDDI_WIN8)
#endif // (NTDDI_VERSION >= NTDDI_WIN6SP1)
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_BITMAP_IP_LOCAL_ADDRESS,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_BITMAP_IP_LOCAL_PORT,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_BITMAP_IP_REMOTE_ADDRESS,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_BITMAP_IP_REMOTE_PORT,
FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_MAX
} FWPS_FIELDS_ALE_FLOW_ESTABLISHED_V4;
可以看见,inFixedValues都是一些与数据包相关的信息。而layerData表示被过滤的网络原始数据classifyOut->actionType为放行数据包或者拦截。有以下取值

三、总结
WFP的分层,每一个分层都有一个唯一的标识符标识。内核层的标识符成为运行时过滤分层标识,用户层的称为管理时过滤分层标识。相比之下,内核层的标识为LUID(64位),用户层的位GUID(128位)。并且两种层面的标识符宏定义一致。可以看此。
呼出接口、分层、子层、过滤器都具有唯一标识符,而子层与过滤器则具有权重,分层上下顺序已经设计好的,不具有变动性,所以不具备权重weight。
子层、callout、过滤器这些新创建的,删除的时候可以通过key或者id进行删除。并且还要关闭过滤引擎。

与3环的交互则可以通过设置IRP派遣函数进行。
参考:
《Windows内核编程》 –谭文
《筛选层标识符》–MSDN
Windows过滤平台WFP:等您坐沙发呢!
发表评论
