WFP过滤框架

WFP框架图

在这里插入图片描述

垫片

垫片是一种特殊的内核模块,被安插在系统的网络协议栈(如TCP/IP)的不同层(栈)中,
主要作用是获取网络协议栈的数据。如垫片被安插在传输层,可以获取TCP/UDP等协议数据:
垫片被安插在网络层,可以获取IP协议等数据。安插在不同协议层的垫片,获取到的数据不同,
垫片获取到数据后,会通过内核态过滤引擎提供的分类API,把数据传递到相应的WFP分层中。

垫片除了能获取网络数据传递给内核态过滤引擎外,还有更重要的一个作用是把内核态过
滤引擎的过滤结果反馈给网络协议栈。比如,内核态过滤引擎需要拦截某一类网络数据, 内核
态过滤引擎通过分类API把过滤结果通知垫片,由于垫片被安插在网络协议栈中,所以垫片可
以在网络协议栈中直接拦截网络数据包。

由此可见,垫片的作用非常关键,它属于WFP框架的一部分,负责WFP的数据来源以及
执行数据拦截/放行的最终动作,属于网络协议栈和WFP框架之间的通信桥梁。但是,垫片对
开发者来说却是透明的,开发者在WFP框架上开发时无须过多关注垫片,这也是微软WFP框
架设计的一个精妙之处,这种设计可以让开发者把主要精力集中在对网络数据包的处理而不是
对网络数据包的获取上。

呼出接口

Callout的中文意思是标注、标记,但是笔者认为这个翻译不能准确表达其意义,笔者认为
使用“呼出接口”来代表Callout一词更为贴切,因此在下文中,统一使用“呼出接口”来表示
Callout.

呼出接口是WFP框架中重要的数据结构,也是对WFP能力的一一种扩展。简单来说,读者
可以理解成呼出接口由一系列的回调函数组成;当网络数据命中某过滤器的规则并且过滤器指
定一个呼出接口时,该呼出接口内的回调函数就会被调用。对于呼出接口的回调函数,后面会
对其进行详细的介绍。呼出接口除了包含回调函数外,还包含一个GUID值,用来唯一地识别
一个呼出接口。一般来说,不同的呼出接口的回调函数实现不同的功能,系统内置了 一部分呼
出接口可以供开发者使用,开发者也可以向系统注册自己的呼出接口来完成特定的逻辑。
下面简单来看一下呼出接口的定义。结构体FWPS_CALLOUT描述了呼出接口的信息,但
是读者可以发现FWPS_CALLOUT其实是一个宏,在不同的编译环境中对应不同的结构体:
#if (NTDDI_ VERSION >= NTDDI_WIN7)
#define FWPS_ CALLOUT FWPS_ CALLOUT1
#else
#define FWPS_ CALLOUT FWPS_ CALLOUT0
当WDK编译环境被设置为大于或等于Windows 7系统时,FWPS_ CALLOUT被定义成
FWPS_CALLOUT1;否则被定义成FWPS_ CALLOUT0.为简单起见,下面所有结构体和函数
的介绍,都以Windows 7为例,请读者务必注意。下面介绍Windows 7编译环境下
FWPS_CALLOUT1的定义:

typedef struct FWPS_CALLOUT1_
// Uniquely identifies the callout. This must be the same GUID supplied to
// FwpmCalloutAddo
	GUID calloutKey;
	// Flags
	UINT32 flags ;
	// Pointer to the classification function
	FWPS_CALLOUT_CLASSIFY_FN1 classifyFn;
	// Pointer to the notification function
	FWPS_CALLOUT_NOTIFY_FN1 notifyFn;
	// Pointer to the flow delete function
	FWPS_CALLOUT_FLOW_DELETE NOTIFY_FNO flowDeleteFn;
} FWPS_CALLOUT1;

结构体中包含了一个GUID值calloutKey,正如上面所介绍的,calloutKey 值唯一地标识这
个呼出接口: flags 成员表明了呼出接口的一些特性,可以简单设置为0: classifyFn、 notifyFn
和flowDeleteFn为三个回调函数,当有网络数据命中规则或者WFP事件发生时,相应的回调函
数就会被调用。对于三个回调函数的用法,在本章后面进行详细介绍。

分层

在介绍内核态过滤引擎时提到,内核态过滤引擎内部被划分成不同的分层(Layer), 每一个分层代表了系统网络协议栈的-一个特定的层(栈),接收特定的数据。例如,
InboundOutbound Transport Layer这个分层对应了网络协议栈中的传输层,被安插在传输层中的
垫片获取到传输层的网络数据之后,通过分类API (Clasify API)把数据传递到内核过滤引擎
中的Inbound/Outbound Transport Layer中。WFP的分层设计相当于把网络数据包进行了分类,
开发者只需根据自身需要与相应的分层交互即可。

分层是一个容器,里面包含了零个或多个过滤器(本章后面介绍)。此外,分层内部还可能
包含一个或多个子层(本章后面介绍)。
对于WFP划分的分层,每个分层均有一个唯一的值来标识,在内核态中,使用64位的LUID
来标识一个子层:在用户态中,使用128位GUID来标识一个分层。这两种分层标识存在部分
对应关系,一般来说,把内核态使用到的分层标识称为“运行时过滤分层标识"( Run-time Filtering
Layer Identifiers),把用户态使用到的分层标识称为“管理过滤分层标识”(Management Filtering
Layer Identifiers)。
下面列出部分常用的“管理过滤分层标识”,有兴趣的读者可以阅读WDK帮助文档,查阅更多的“管理过滤分层标识”。

FWPM_LAYER_INBOUND_IPPACKET_V4:接收到IPv4网络数据包层;
FWwPM_LAYER_INBOUND_IPPACKET_V6:接收到IPv6网络数据包层;
FWPM_LAYER_OUTBOUND_IPPACKET_V4:发送IPv4网络数据包层;
FWPM_LAYER_OUTBOUND_IPPACKET_V6:发送IPv6网络数据包层。

子层

上一节已经介绍了WFP的分层,分层是WFP框架已经划分好的,而子层(Sub Layer)是分层内更小的一个划分,一 个分层可能被划分成多个子层,并且这种划分是由开发者控制的。

开发者在划分子层时需要给新的子层分配一个权重(Weight), 权重的值越大,表明优先级越高,当有相应的网络数据到达分层时,WFP会按照分层内的子层优先级顺序传递网络数据,子层的权重越大,越早获取到数据。系统也内置了一些常用的子层,读者可以参考WDK帮助文档。

子层的结构体定义如下:

typedef struct FWPM_SUBLAYERO_
	GUID subLayerKey;
	FWPM_DISPLAY_DATAO displayData;
	UINT16 flags ;
	GUID* providerKey;
	FWP_BYTE_BLOB providerData;
	UINT16 weight;
} FWPM_SUBLAYERO;

subLayerKey:子层的标识。WFP使用128位的GUID来标识一个子层。
displayData:显示数据。displayData 也是一个结构体,定义如下:

typedef struct FWPM DISPLAY_ DATAO_ {
    
    
	wchar_ t* name;
	wchar_ t* description;
} FWPM_DISPLAY_DATA0;

这个结构体很简单,只有两个指针变量,其中name指向的是保存对象名字的缓冲区;
description指向的是保存对象描述的缓冲区。

flags:子层的一些特性,可以设置成FWPM_SUBLAYER_FLAG_PERSISTENT, 明该子层是“永久性”的子层,与基础过滤引擎生命周期相同。

providerKey和providerData可以暂时忽略。
weight: 16 位大小,表示子层的权重,值越大,权重就越大,即优先级越高。

过滤器

过滤器(Filter) 存在于WFP的分层中,WFP内置了一部分过滤器供开发者使用,开发者也可以添加自己的过滤器。在众多对象模型中,过滤器最为复杂,因为过滤器和WFP的其他对象均有关联。

过滤器实际上是一套规则(rule) 和动作(action) 的集合,规则指明了对哪些网络数据包感兴趣,即指明需要过滤哪些网络数据包:过滤器内还指定了动作,当过滤器的规则被命中时,过滤器里面指定的动作就会被WFP执行。

上面提到的过滤器里面的规则,实际上就是过滤器里面的过滤条件(condition),一个过滤器里面包含一个或者多个过滤条件,当这些过滤条件全部成立时,才认为这个过滤器的规则被命中。一且过滤器规则被命中,WFP就会执行过滤器中指定的动作。

开发者需要使用过滤器时,必须明确知道过滤器被添加到内核态过滤引擎的哪一个分层中,过滤器被添加到不同的分层中可以过滤不同层次的网络数据。在同一个分层内,可以存在多个过滤器,不同过滤器被赋予不同的权重。

在同一个分层内,不同过滤器的权重不能相同,为了避免权重重复,开发者还可以为过滤器指定一个子层,只要保证在子层内过滤器权重不重复就可以了:过滤器关联子层后,WFP在分层内按照子层的权重值,从大到小匹配子层内的过滤器;同样地,在子层内,WFP也是按照过滤器的权重值,从大到小匹配过滤器的规则的。

从上面的介绍可知,过滤器可以关联分层和子层,除此之外,过滤器还可以关联呼出接口。

在需要对网络数据包进行复杂的分析和处理的情况下,过滤器一般需要关联一个呼出接口,当过滤器的规则被命中时,WFP就会执行与该过滤器关联的呼出接口内的回调函数。在呼出接口内的回调函数中,可以对数据包内容进行深入的分析,根据分析情况返回过滤的结果( 允许/拦截)到WFP.

下面介绍过滤器的数据结构。过滤器的结构体为FWPM_ FILTER, WDK定义
FWPM_FILTER为:
#define FWPM_ FILTER FWPM_FILTER0

FWPM_ FILTER0的定义如下:

typedef struct FWPM_ FILTERO_ {
    
    
	//过滤器的唯一标识, 128 位的GUID,如果开发者指定这个GUID值为0,
	//当过滤器被添加到过滤引擎时,过滤引擎会自动为该过滤器分配一个GUID标识。
	GUID filterkey; 
	
	//和过滤器关联起来的人类可读的注释 FWPM_DISPLAY_DATA0(结构体中包含名字和描述的两个指针)
	FWPM_DISPLAY_DATA0 displayData;
	
	//特定值的组合,查看文档
	UINT32 flags;
	
	//可选的提供者的GUID
	GUID* providerKey;
	
	//FWP_BITY_BLOB结构,包含提供程序使用的可选特定于提供程序的数据,用于在对象中存储其他上下文信息。
	FWP_BYTE_ BLOB providerData;
	
	//过滤器所在分层的GUID 下面链接可以查看文档中定义好的分层GUID
	GUID layerKey;
	
	//过滤器所在子层的GUID 下面链接可以查看文档中定义好的子层GUID 也可以自定义子层的GUID值
	//如果将此设置为IID_NULL,则将筛选器添加到默认子层。
	// 定义格式:  
	DEFINE_GUID(
    SF_SUBLAYER,
    0x7ec7f7f5,
    0x1249,
    0x4620,
    0xa6, 0x32, 0x6f, 0x9c, 0x4d, 0x08, 0xd2, 0x5a
    );
	GUID subLayerKey;
	
	//指定过滤器权重的FWP_VALUE0结构  weight.Type=FWP_EMPTY;BFE将根据过滤条件自动分配权重。
	FWP_VALUEO weight;
	
	//过滤条件数
	UINT32 numFilterConditions;

	//包含所有筛选条件的FWPM_Filter_CONDITION 0结构的数组。对于要执行的操作,一切都必须是正确的。
	//换句话说,使用AND运算符计算条件。如果未指定任何条件,则始终执行此操作
	FWPM_FILTER_CONDITIONO* filterCondition;

	//FWPM_ACTION0结构,它指定如果所有筛选条件都为真要执行的操作
	FWPM_ACTIONO action;
	
	union {
    
    
	UINT64 rawContext;
	//当筛选器具有提供程序上下文信息时可用,即,标志包含FWPM_FILTER_FLAGE_HASS_PROVIDER_CONTEXT。
	//有关预定义的策略提供程序上下文的列表,请参见主题WFP内置提供程序上下文标识符。
	//ProviderContextKey指定的提供程序上下文的LUID用于填充相应的FWPS_FILTER0结构的上下文成员,Windows驱动程序工具包中记录了该结构。
	GUID providerContextKey;
	};
	
	GUID* reserved;
	//标识过滤器的LUID。这也是相应的FWPS_FILTER0结构的LUID,Windows驱动程序工具包中记录了这个结构。
	UINT64 filterId; 
	//包含分配给FWPS_FILTER0的权重的FWP_VALUE0结构,Windows驱动程序工具包中记录了该结构。
	FWP_VALUEO effectiveweight;
} FWPM_FILTER0;

分层GUID
子层GUID

下面对过滤器结构体内的重要成员进行介绍。
filterKey:过滤器的唯一标识, 128 位的GUID,如果开发者指定这个GUID值为0,当过
滤器被添加到过滤引擎时,过滤引擎会自动为该过滤器分配一个GUID标识。

displayData:这个成员在上面介绍子层的时候已经讲过,这里不再重复。

layerKey:分层的GUID,即前面介绍的“管理过滤分层标识”,表明当前过滤器被添加到WFP的哪一个分层中。

subLayerKey:子层的GUID,表明过滤器需要添加到哪一个子层中,如果设置成IID_NULL,则过滤器被添加到默认的子层内。

weight:权重,表明过滤器的优先级。请读者注意,前面我们在介绍子层时,同样也有一个权重,子层的权重是使用一个UINT16来表示,而过滤器的权重的表达方式稍微复杂,使用FWP_VALUE0结构体来表示。FWP_VALUE0的定义如下:

typedef struct FWP_VALUEO_ {
    
    
	FWP_ DATA TYPE type;
	union {
    
    
	// case(FWP_ EMPTY)
	UINT8 uint8;
	UINT16 uint16;
	UINT32 uint32;
	UINT64* uint64;
	INT8 int8;
	INT16 int16;
	INT32 int32;
	INT64* int64;
	float float32;
	double* double64;
	FWP_BYTE_ARRAY16* byteArray16;
	FWP_BYTE_BLOB* byteBlob;
	SID* sid;
	FWP_BYTE_BLOB* sd;
	FWP_TOKEN_INFORMATION* tokenInformation;
	FWP_BYTE_BLOB* tokenAccess Information;
	LPWSTR unicodeString;
	};
} FWP_VALUEO;

结构体主要由一个type和一个匿名联合体组成,type 是一个枚举类型,定义如下:

typedef enum FWP_DATA_TYPE_ {
    
    
  FWP_EMPTY,
  FWP_UINT8,
  FWP_UINT16,
  FWP_UINT32,
  FWP_UINT64,
  FWP_INT8,
  FWP_INT16,
  FWP_INT32,
  FWP_INT64,
  FWP_FLOAT,
  FWP_DOUBLE,
  FWP_BYTE_ARRAY16_TYPE,
  FWP_BYTE_BLOB_TYPE,
  FWP_SID,
  FWP_SECURITY_DESCRIPTOR_TYPE,
  FWP_TOKEN_INFORMATION_TYPE,
  FWP_TOKEN_ACCESS_INFORMATION_TYPE,
  FWP_UNICODE_STRING_TYPE,
  FWP_BYTE_ARRAY6_TYPE,
  FWP_BITMAP_INDEX_TYPE,
  FWP_BITMAP_ARRAY64_TYPE,
  FWP_SINGLE_DATA_TYPE_MAX,
  FWP_V4_ADDR_MASK,
  FWP_V6_ADDR_MASK,
  FWP_RANGE_TYPE,
  FWP_DATA_TYPE_MAX
} FWP_DATA_TYPE;

FWP_VALUE0结构体中的type的取值,决定了联合体内哪一个成员被使用,比如type取值为FWP_UINT8, FWP_VALUE0结构体中uint8 值被使用: type 取值为FWP UINT16,FWP_ VALUEO结构体中uint16值被使用,依此类推。

回过头来看权重weight,当weight.type = FWP_UINT64 时,weight.uint64 表示的是过滤器的实际权重值;当weight.type = FWP_ UINT8时,weight.uint8 在0和15之间取值,作为权重值的高63-60位值,WFP自动为权重值的低60位生成-一个权重值;当weight.type=FWP_EMPTY时,WFP自动为过滤器生成一个权重值。

numFilterConditions:过滤条件的个数,即filterCondition数组中元素的个数。

filterCondition:过滤条件。filterCondition 指向过滤条件的数组,数组内元素的个数由
numFilterConditions指定;过滤条件的类型为FWPM_FILTER_CONDITION,定义如下:

typedef struct FWPM_FILTER_CONDITIONO_
{
    
    
	GUID fieldKey;
	FWP_MATCH_TYPE matchType;
	FWP_CONDITION_VALUEO conditionValue;
} FWPM_FILTER_CONDITIONO;

fieldKey:网络数据包字段的标识,为128位的GUID类型,WFP把网络数据包划分为不同的字段,比如数据包远程IP地址字段用FWPM_CONDITION_IP_REMOTE_ADDRESS来标识。

matchType:匹配的类型,为FWP_MATCH_TYPE枚举值。FWP_MATCH_TYPE的定义
如下:

typedef enum FWP_MATCH_TYPE_ {
    
     
  FWP_MATCH_EQUAL                   = 0,
  FWP_MATCH_GREATER,
  FWP_MATCH_LESS,
  FWP_MATCH_GREATER_OR_EQUAL,
  FWP_MATCH_LESS_OR_EQUAL,
  FWP_MATCH_RANGE,
  FWP_MATCH_FLAGS_ALL_SET,
  FWP_MATCH_FLAGS_ANY_SET,
  FWP_MATCH_FLAGS_NONE_SET,
  FWP_MATCH_EQUAL_CASE_INSENSITIVE,
  FWP_MATCH_NOT_EQUAL,
  FWP_MATCH_TYPE_MAX
} FWP_MATCH_TYPE;

FWP_ MATCH_TYPE中的每一个枚举值代表了一种匹配方式,如FWP_ MATCH_EQUAL表示当fieldKey 指定的字段值和条件conditionValue 相等时,过滤条件成立;又如
FWP_ MATCH_ GREATER表示当fieldKey指定的字段值大于条件conditionValue时,过滤条件成立。请注意,并不是所有的fieldKey指定的字段都支持FWP_MATCH_GREATER,具体什么
字段值可以支持哪种匹配方式,读者可以参考WDK开发文档。

conditionValue:过滤条件的值,为FWP_ CONDITION_VALUEO结构体,定义如下:

typedef struct FWP_CONDITION_VALUE0_ {
    
    
  FWP_DATA_TYPE type;
  union {
    
    
    UINT8                 uint8;
    UINT16                uint16;
    UINT32                uint32;
    UINT64                *uint64;
    INT8                  int8;
    INT16                 int16;
    INT32                 int32;
    INT64                 *int64;
    float                 float32;
    double                *double64;
    FWP_BYTE_ARRAY16      *byteArray16;
    FWP_BYTE_BLOB         *byteBlob;
    SID                   *sid;
    FWP_BYTE_BLOB         *sd;
    FWP_TOKEN_INFORMATION *tokenInformation;
    FWP_BYTE_BLOB         *tokenAccessInformation;
    LPWSTR                unicodeString;
    FWP_BYTE_ARRAY6       *byteArray6;
    FWP_BITMAP_ARRAY64    *bitmapArray64;
    FWP_V4_ADDR_AND_MASK  *v4AddrMask;
    FWP_V6_ADDR_AND_MASK  *v6AddrMask;
    FWP_RANGE0            *rangeValue;
  };
} FWP_CONDITION_VALUE0;

这个结构体和FWP_VALUE0结构体非常类似,使用方法基本相同,都有一个type成员来指明
后面的联合体使用成员情况,读者可以参考FWP_VALUE0的使用介绍,这里不再对
FWP_ CONDITION_VALUE0结构体的使用进行赘述。

看回过滤器FWPM_FILTER0的定义,结构体里面的action成员至关重要,表示filterCondition
所包含的条件全部成立时所执行的动作。action 为FWPM_ACTION0结构体类型,定义如下:

typedef struct FWPM_ACTION0_ {
    
    
	FWP_ACTION_TYPE type;
	union {
    
    
		GUID filterType; 
		GUID calloutKey; 
	}; 
} FWPM_ACTION0;

type:表示动作类型,可以取值如下。
●FWP_ACTION_BLOCK:表示过滤条件成立后,拦截网络数据包;
●FWP_ACTION_PERMIT: 表示过滤条件成立后,允许(即放行)网络数据包;
●FWP_ACTION CALLOUT_TERMINATING: 表示过滤条件成立后,回调呼出接口内的回调函数,回调函数始终返回“允许”或者“拦截”;

●FWP_ ACTION_CALLOUT_INSPECTION: 表示过滤条件成立后,回调呼出接口内的回
调函数,回调函数不会返回“允许”或者“拦截”;
●FWP_ACTION_CALLOUT_UNKNOWN: 表示过滤条件成立后,回调呼出接口内的回调函数,回调函数可能返回“允许”或者“拦截”。

filterType:可以先忽略。

calloutKey:非常关键的一个值,当type取值FWP_ ACTION_ CALLOUT _TERMINATING、FWP_ACTION_CALLOUT_INSPECTION或者FWP_ACTION_CALLOUT_UNKNOWN时,
calloutKey表示呼出接口的一个GUID,当过滤器规则被命中后,WFP回调该GUID对应的呼出接口结构体内的回调函数。

呼出接口回调函数

本节介绍呼出接口回调函数,回调函数作为呼出接口最重要的部分,是呼出接口功能实现的主体。呼出接口内部包含三个回调函数,分别为: notifyFn、 classifyFn 和flowDeleteFn。下面分别介绍这三个函数。

首先介绍classifyFn 回调函数。当一个过滤器关联了呼出接口,并且规则被命中时(过滤
器内的条件全部成立),过滤引擎会回调呼出接口内的classifyFn函数,开发者可以在classifyFn
函数内获取网络数据包的相关信息,具体信息取决于过滤器所在的分层,classifyFn 函数还可以
设置对网络数据包的“允许/拦截"操作。

该回调函数原型如下:

VOID NTAPI
classifyFn1(
	IN const FWPS_INCOMING_VALUES0 * inFixedValues,
	IN const FWPS_INCOMING_METADATA_VALUES0 * inMetaValues,
	IN OUT VOID *layerData,
	IN OPTIONAL const void *classifyContext,
	IN const FWPS_FILTER1 *filter,
	IN UINT64 flowContext,
	OUT FWPS_CLASSIFY_OUT0 *classifyOut
}

前面提到,本章的数据结构和函数定义都是以WDK Windows 7编译环境为例的,所以
classifyFn函数在Windows 7编译环境下被定义为classifyFn1,请读者注意。
classifyFnl函数的参数较多,读者初学WFP时,可以先关注一些重要的参数:
inFixedValues参数是传入参数,指向FWPS_INCOMING_VALUES0结构体指针,
FWPS_INCOMING_VALUES0结构体里面包含了网络数据包的信息。

FWPS_INCOMING_VALUES0
结构体的定义如下:

typedef struct FWPS_INCOMING_VALUES0_
{
    
    
	UINT16 layerId;
	UINT32 valueCount;
	FWPS_INCOMING_VALUE0 *incomingValue;
} FWPS_INCOMING_VALUES0;

layerld表示“运行时过滤分层标识”,incomingValue 指向一个FWPS_ INCOMING_VALUE0
类型的数组,valueCount 表示数组内元素的个数。

FWPS_ INCOMING_VALUE0结构体保存的是和网络数据包相关的信息,如网络通信中的本地端口、远程端口、本地IP地址以及远程IP等。FWPS_ INCOMING_VALUE0的定义如下:

typedef struct FWPS_INCOMING_VALUEO_ {
    
    
	FWP_VALUEO value;
} FWPS_INCOMING_VALUEO;

这个结构体内部只有一个FWP_ VALUE0类型的变量,对于FWP_ VALUE0结构体的使用,读
者可以参考本章前面内容。

classifyFnl函数的第2个参数为inMetaValues,表示元数据值,inMetaValues 里面包含了和
过滤相关的信息,如进程ID、数据流句柄等。inMetaValues的类型为FWPS_ INCOMING_
METADATA_ VALUES0 结构体,定义如下:

typedef struct FWPS_INCOMING_METADATA_VALUES0_ {
    
    
  UINT32  currentMetadataValues;
  UINT32  flags;
  UINT64  reserved;
  FWPS_DISCARD_METADATA0  discardMetadata;
  UINT64  flowHandle;
  UINT32  ipHeaderSize;
  UINT32  transportHeaderSize;
  FWP_BYTE_BLOB  *processPath;
  UINT64  token;
  UINT64  processId;
  UINT32  sourceInterfaceIndex;
  UINT32  destinationInterfaceIndex;
  ULONG  compartmentId;
  FWPS_INBOUND_FRAGMENT_METADATA0  fragmentMetadata;
  ULONG  pathMtu;
  HANDLE completionHandle;
  UINT64 transportEndpointHandle;
  SCOPE_ID remoteScopeId;
  WSACMSGHDR* controlData;
  ULONG controlDataLength;
  FWP_DIRECTION packetDirection;
#if (NTDDI_VERSION >= NTDDI_WIN6SP1)
  PVOID headerIncludeHeader;
  ULONG headerIncludeHeaderLength;
#endif
} FWPS_INCOMING_METADATA_VALUES0;

这个结构体成员非常多,但是请读者务必注意,不是所有成员都是有效的,在一次
classifyFnl函数回调中,FWPS_INCOMING_METADATA_VALUES0 结构体内部有效的成员由
currentMetadataValues值决定。对于FWPS_INCOMING_METADATA_VALUES0内的每个成员,
都由一个“Metadata Field Identifiers" 标识符来代表,这些标识符是“位(bit)”数据,定义
如下:

#define FWPS_METADATA FIELD_DISCARD_REASON 0x00000001
#define FWPS_METADATA FIELD_FLOW_HANDLE 0x00000002
#define FWPS_METADATA FIELD_IP_HEADER_SIZE 0x00000004
#define FWPS_METADATA FIELD_PROCESS_PATH 0x00000008
#define FWPS_METADATA FIELD_TOKEN 0x00000010

更多定义查看微软官方文档

只有当currentMetadataValues 包含相应的标识符时,标识符对应的结构体成员才有效,比如,currentMetadataValues 包含了FWPS_ METADATA FIELD FLOW_ HANDLE标识符,说明
FWPS_INCOMING_METADATA_VALUES0结构体内的flowHandle 是有效值;

又如,currentMetadataValues包含了FWPS_ METADATA_FIELD_PROCESS_D标识符,说明FWPS
INCOMING_ METADATA_ VALUES0结构体内的processId是有效值。WDK提供了一个宏来方便开发者用来测试currentMetadataValues是否包含了某个具体的标识符:

#define FWPS_IS_METADATA_FIELD_PRESENT(metadataValues, metadataField) \
(((metadataValues)->currentMetadataValues & (metadataField)) == (metadataField))

这个宏比较简单,主要是通过“与”运算来测试一个位(bit) 的值是否存在。通过这个宏,上
面的例子可以表示成:

FWPS_IS_METADATA_FIELD_PRESENT(inMetaValues, FWPS_ METADATA_ FIELD_FLOW_HANDLE)
以及FWPS_IS_METADATA_FIELD_PRESENT(inMetaValues, FWPS_METADATA_FIELD_PROCESS_ID)

当这个宏返回非0时,表示被测试的标识符有效。请读者一定注意,在使用这个结构体内的成
员前,务必使用FWPS_IS_METADATA_FIELD_PRESENT这个宏来测试需要使用的值是否有
效。

classifyFnl函数的第3个参数layerData表示被过滤的网络原始数据。该参数可能为NULL,
是否为NULL取决于当前在哪一个分层过滤。

classifyFn1函数的第4个参数classifyContext为可选参数,表示和呼出接口驱动关联的上下文。
classifyFnl函数的第5个参数filter表示相关的过滤器指针。请读者注意,这个过滤器的类型是FWPS_ FILTER1, 而不是前面介绍的FWPM FILTER0.不过,FWPS_ FILTERI 结构体和FWPM_ FILTER0 结构体非常类似.

classifyFnl函数的第6个参数flowContext表示和流句柄关联的上下文。如果该流句柄没有
关联过上下文,这个参数为NULL。开发者可以使用FwpsFlowAssociateContext0这个API把一
个上下文和数据流句柄进行关联。

classifyFnl函数的最后一个参数classifyOut表示该函数对这个网络数据包的过滤结果,这
个过滤结果通过classifyOut参数返回给WFP。classifyOut 被定义成FWPS_CLASSIFY_OUT0
类型的结构体,具体定义如下:

typedef struct FWPS_ CLASSIFY OUT0_
	FWP_ACTION_TYPE actionType ;
	UINT64 outContext;
	UINT64 filterId;
	UINT32 rights; 
	UINT32 flags ;
	UINT32 reserved;
} FWPS_CLASSIFY_OUT0;

actionType:表示动作的类型,可以取值:
●FWP_ACTION_BLOCK:拦截网络数据包;

●FWP_ACTION_CONTINUE: 把早先过滤器设置在数据包上的动作传递给下一个过滤器:

●FWP_ ACTION_NONE:对网络数据不做任何动作或决策:

●FWP_ACTION_NONE NO_MATCH: 由于不匹配过滤器的数据类型,所以对此网络数据不做任何动作或决策;

●FWP_ACTION_PERMIT: 允许网络数据包。

outContext:系统保留值,呼出接口驱动不能修改该值。
filterld:系统保留值,呼出接口驱动不能修改该值。
reserved:系统保留值,呼出接口驱动不能修改该值。
flags:过滤动作的特性,可以取值FWPS_CLASSIFY_OUT_FLAG_ABSORB,表示当需要
拦截数据包时,系统不进行审计和记录。
rights:表示这个结构体的其他成员是否可修改,当被设置为FWPS_RIGHT_ACTION_WRITE时,表示当前结构体的其他成员可以被修改( 系统保留的成员除外)。

以上是对classifyFnl参数的介绍。classifyFnl 函数没有返回值,所以这个函数也没有实际
意义上的成功和失败。

下面介绍Callout的另一个回调函数notifyFnl:

NTSTATUS NTAPI
	notifyFn1(
	IN FWPS_CALLOUT_NOTIFY_TYPE notifyType,
	IN const GUID *filterKey,
	IN const FWPS_FILTER1 *filter
);

当过滤器被添加到过滤引擎中或者从过滤引擎中移除时,WFP会调用这个过滤器对应呼出接口
(如果这个过滤器关联了呼出接口)的notifyFnl函数。开发者通过这个函数可以得知呼出接口
关联的过滤器的操作情况。

下面介绍notifyFnl的参数。
notifyType:通告的类型,表示此次回调的具体原因。notifyType 为枚举值,可以取值如下:
●FWPS_CALLOUT_NOTIFY_ADD_FILTER: 过滤器被添加到过滤引擎中:
●FWPS_CALLOUT_NOTIFY_DELETE_FILTER: 过滤器从过滤引擎中移除;
●FWPS_CALLOUT_NOTIFY_TYPE_MAX:无意义,测试使用。

filterKey:过滤器标识,128 位的GUID类型。请注意,在使用filterKey 这个指针前务必判
空,因为只有当notifyType 等于FWPS_CALLOUT_NOTIFY_ADD_FILTER时,filterKey 才是
非空值。

filter:过滤器指针,表示将要被添加或者删除的过滤器。
在函数返回值方面,notifyFn1 不同于classifyFnl 函数,notifyFnl 函数的返回值是
NTSTATUS,返回值可以取值如下:

STATUS_SUCCESS: 表示回调函数接受这个事件。

●其他错误码: 表示回调函数不接受该事件,如果是FWPS_CALLOUT_NOTIFY_ADD_FILTER事件,返回错误码后,过滤器不会被添加到过滤引擎中:如果是FWPS_CALLOUT_NOTIFY_DELETE_FILTER事件,即使返回错误码,过滤器也会从过滤引擎中删除。

下面介绍呼出接口的最后一个回调函数flowDeleteFn. 当一个网络数据流将要被终止时,
WFP会调用flowDeleteFn函数。但是,WFP调用flowDeleteFn函数是有条件的,只有在这个将
要终止的数据流被关联了上下文的情况下,flowDeleteFn 才会被回调,开发者可以在这个函数
中清理被关联的上下文。

flowDeleteFn函数原型如下:

VOID NTAPI
flowDeleteFn(
	IN UINT16 layerId,
	IN UINT32 calloutId,
	IN UINT64 flowContext );

layerld:分层标识,属于“Run-time-Filtering-Layer-Identifiers",前文已经介绍过。
calloutld:表示相应的呼出接口。当把一个呼出接口注册到WFP后,WFP会为其分配一个
ld,用来唯一标识这个呼出接口(在本章后面读者将看到注册呼出接口时会返回一个Id)。
flowContext:关联的上下文指针,这个上下文的内容由开发者定义。

WFP操作

前面章节已经为读者介绍了WFP的框架以及基本的对象模型,接下来为读者介绍如何通过
WFP提供的API来进行网络数据包过滤。
开发者在内核态使用WFP提供的API实现网络数据包过滤,一 般可以分为以下几个步骤。
(1)定义一个或多个呼出接口,然后向过滤引擎注册呼出接口。
(2)添加步骤1的呼出接口到过滤引擎。请注意,注册呼出接口和添加呼出接口是两个不
同的操作。

(3)设计一个或者多个子层,把子层添加到分层中。
(4)设计过滤器,把呼出接口、子层、分层和过滤器关联起来,向过滤引擎添加过滤器。
下面章节会根据上面所列的步骤,为读者介绍WFP具体操作的API。

呼出接口的注册与卸载

前面已经介绍了呼出接口的用途以及组成,开发者定义一个呼出接口后,需要向过滤引擎
注册这个呼出接口。呼出接口只有被注册后,才可以被过滤引擎使用。
注册呼出接口可以使用FwpsCalloutRegister0、FwpsCalloutRegister1以及FwpsCalloutRegister2
函数,其中FwpsCalloutRegister0函数从Windows Vista系统开始支持,FwpsCalloutRegister1函
数从Windows 7系统开始支持,FwpsCalloutRegister2 函数从Windows8系统开始支持。这三个
函数的用法非常类似,只有参数存在细微的差别。下面介绍FwpsCalloutRegister1函数,其他两
个函数请读者自行查阅WDK帮助文档。FwpsCalloutRegister1 函数定义如下:

NTSTATUS NTAPI
FwpsCalloutRegister1(
	IN OUT void *device0bject,
	IN const FWPS_CALLOUT1 *callout,
	OUT OPTIONAL UINT32 *calloutId
);

deviceObject:呼出接口驱动所创建的设备对象指针,开发者在开发Callout 驱动时,需要
创建一个设备对象,用于注册呼出接口。

callout:呼出接口对象的指针。开发者在传递呼出接口指针到FwpsCalloutRegisterl前,必
须先初始化呼出接口结构体内的成员。

calloutld:呼出接口的Id。请注意,这个呼出接口的ld和FWPS_CALLOUTI结构体内的calloutKey不一样,虽然两者都是用来标识一个呼出接口,但是FWPS_CALLOUT1结构体内的
calloutKey是开发者在定义呼出接口时指定的,而FwpsCalloutRegisterl函数返回的calloutld是
指呼出接口被成功注册后,WFP 为该呼出接口分配的一个标识。一般来说,可以把calloutId这
类标识称为“运行时标识" (Run-time-identifer)。

呼出接口被成功注册后,FwpsCalloutRegister1 函数返回STATUS_SUCCESS:如果
FWPS_CALLOUT1结构体内calloutKey 所标识的呼出接口已经被注册过了,那么
FwpsCalloutRegisterl函数返回STATUS_FWP_ALREADY_EXISTS: 如果发生错误,那么
FwpsCalloutRegisterl函数返回其他错误码。

成功注册呼出接口后还需要卸载呼出接口,卸载呼出接口使用FwpsCalloutUnregisterByld
或者FwpsCalloutUnregisterByKey函数,其中FwpsCalloutUnregisterByld函数是通过指定呼出接
口的“运行时标识"来对呼出接口进行卸载的,FwpsCalloutUnregisterByKey 函数是通过指定呼
出接口的GUID值来对呼出接口进行卸载的。

呼出接口的添加与移除

成功注册呼出接口后,还需要把呼出接口添加到过滤引擎中,在将呼出接口添加到过滤引
擎中之前,开发者首先需要打开过滤引擎。
打开过滤引擎使用FwpmEngineOpen0函数,该函数定义如下:

NTSTATUS NTAPI
FwpmEngineOpen0(
	IN const wchar_t *serverName OPTIONAL,
	IN UINT32 authnService,
	IN SEC WINNT_AUTH_IDENTITYW *authIdentity OPTIONAL,
	IN const FWPM_SESSION0  *session OPTIONAL ,
	oUt HANDLE *engineHandle
);

serverName:呼出接口驱动必须把该参数设置成NULL.

authnService:认证服务,呼出接口驱动必须设置成RPC_C_AUTHN WINNT或者RPC_C_AUTHN_DEFAULT.

session: WFP 的API是基于Session的,Session.flags = FWPM_SESSION_FLAG_DYNAMIC; 如果设置了此标志,在删除服务时会自动删除管理引擎的GUID,(尽量设置这个标志,否则重启服务时会提示GUID已经存在)

engineHandle:返回句柄,表示打开的过滤引擎句柄。
有了过滤引擎句柄,开发者就可以通过这个句柄,把呼出接口添加到过滤引擎中。将呼出
接口添加到过滤引擎中可以使用FwpmCalloutAdd0函数,该定义如下:

DWORD WINAPI FwpmCalloutAdd0(
	_in HANDLE engineHandle,
	_in const FWPM_CALLOUTO* callout ,
	_in_ opt PSECURITY_DESCRIPTOR sd,
	__out_opt UINT32* id
);

engineHandle:过滤引擎句柄,这个句柄可以使用FwpmEngineOpen函数获得。
callout:呼出接口指针。请注意,这个呼出接口的类型是FWPM_ CALLOUTO,而非前面介
绍的FWPS_CALLOUT1. FWPM_CALLOUT0结构体比较简单,请读者参考15.5 节的例子,
这里不再赘述。

sd:安全描述符,可以设置为NULL.

id: 成功添加呼出接口后,系统会返回一个Id, 开发者可以使用这个Id去移除已添加的呼
出接口。

函数成功添加呼出接口到过滤引擎中后返回STATUS_SUCCESS; 如果WFP发现相同的呼
出接口已经被添加到过滤引擎中,函数返回STATUS_FWP_ALREADY_EXISTS: 如果发生错误,
函数返回其他错误码。

WDK提供两个函数移除添加到过滤引擎中的呼出接口,它们分别是FwpmCalloutDeleteByld0
和FwpmCalloutDeleteByKey0,开发者可以通过指定呼出接口的Id 或者呼出接口的GUID对呼
出接口进行移除。

子层的添加与移除

添加子层相对简单,开发者需要关心的是子层的权重以及子层的GUID。WDK提供了

FwpmSubLayerAdd0函数添加子层,该函数原型如下:

DWORD WINAPI FwpmSubLayerAdde(
	_in HANDLE engineHandle,
	_in const FWPM_SUBLAYERO* sublayer,
	_in_ opt PSECURITY_DESCRIPTOR sd
);

engineHandle:过滤引擎句柄。
subLayer:子层对象的指针,表明需要把这个子层添加到过滤引擎中。
sd:安全描述符,可以简单传递NULL。
FwpmSubLayerAdd0函数成功添加子层后返回ERROR_ SUCCESS;否则返回错误码。
开发者可以使用FwpmSubLayerDeleteByKeyO函数来移除一个子层,该函数使用非常简单,
只需一个Session的句柄以及待移除的子层GUID值作为参数,读者可以参考15.5 节例子的代码。

过滤器的添加

添加过滤器相对复杂些,主要原因是过滤器本身定义比较复杂,开发者在定义过滤器对象
时,需要为过滤器对象内的成员赋值,如权重、分层、子层、呼出接口等信息。
将过滤器添加到过滤引擎中使用FwpmFilterAdd0函数,该函数定义如下:

DWORD WINAPI FwpmFilterAdde(
_in HANDLE engineHandle,
_in const FWPM_ FILTER0* filter,
__in_ opt PSECURITY_DESCRIPTOR sd,
_out_ opt UINT64* id
);

engineHandle:过滤引擎句柄。
filter:过滤器对象指针,描述一个过滤器, 这个过滤器会被添加到过滤引擎中。
sd:安全描述符,可以传递NULL。
id:当过滤器被成功添加到过滤引擎中后,过滤引擎为这个过滤器分配一个Id来唯一标识
它。
函数成功添加过滤器后返回ERROR SUCCESS;否则返回特定的错误码。
移除过滤器可以使用FwpmFilterDeleteByld0 或者FwpmFilterDeleteByKey0 函数,其中
FwpmFilterDeleteById0函数通过指定过滤器的Id进行过滤器的移除,这个Id即是FwpmFilterAdd0
函数参数id返回的值,而FwpmFilterDeleteByKey0函数通过指定过滤器的GUID进行过滤器移
除。

WFP过滤例子

在InitWfp 函数内部首先调用OpenEngine函数打开过滤引擎,获取到过滤引擎句柄,这个过滤引擎句柄在后面添加Callout、
子层和过滤器时需要用到。

然后InitWfp函数调用WfpRegisterCallouts函数向过滤引擎注册了呼
出接口:接着调用WfpAddCallouts函数向过滤引擎添加呼出接口:

最后调用WfpAddFilters函
数向过滤引擎添加子层,调用WfpAddFilters 函数向过滤引擎添加过滤器。

猜你喜欢

转载自blog.csdn.net/qq_41490873/article/details/108655026