WFP (Windows Filtering Platform)
![](https://img-my.csdn.net/uploads/201206/08/1339146312_6074.png)
其包含从用户态到核心态的一系列应用层,根据需要可以在某一层设置回调函数拦截数据。
1、 callout
callout 是 WFP 系统提供的扩展其功能的一种机制, callout 由一组 callout 函数组成,每组有三种函数,
ClassifyFunction ,处理收到的网络数据,例如端口号IP 地址等。NotifyFunction,处理加载、删除callout事件。
FlowDeleteFunction ,删除层与层之间关联的上下文。
callout 由callout 驱动具体实现,每个驱动可以注册多个callout。
2 、WFP 的层和层数据
WFP 有很多层, 每一层分成若干子层,具体有哪些请参阅微软文档,我以 FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4 层为例进行讲解。这层位于 ALE 层,是其子层之一。面向连接的应用程序准备连接,面向无连接的程序准备通信,都发生在这一层。假如在这里拒绝了上述操作,应用程序就不能访问网络,这类似以前的 TDI 程序的 Create 事件,就是应用程序访问网络的请求刚到协议栈还没有处理。 WFP 每一层都有其特定的数据,根据这些数据又有特定的过滤条件,例如这层包括 FWPS_METADATA_FIELD_PROCESS_ID 类型数据,这个类型由 UINT64 类型数据定义,表示和本次网络访问请求相关的进程 ID ,可是 FWPS_LAYER_INBOUND_IPPACKET_V4 层就不包括这一类型的数据,其实 FWPS_LAYER_INBOUND_IPPACKET_V4 已经到了 IP 层,这里进程 ID 已经没用了。因此在 FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4 蹭可以以进程 ID 作为过滤条件,而到了 FWPS_LAYER_INBOUND_IPPACKET_V4 层就不能用进程 ID 作为过滤条件了。每一层都有哪些数据类型,根据这些数据有哪些过滤条件可用,请参阅微软的 WFP 文档层标识符等章节。
综上, WFP 系统很像一个已经有了数据过滤引擎的防火墙,但是没有规则。我们编写用户层的程序给 WFP 引擎设置规则,编写核心态的 callout 驱动处理 WFP 抓到的网络数据包。根据微软的文档所示, WFP 能够到达 IP 层,假如我们想进行 MAC 层的处理,就必须利用 NDISfilter 驱动。
3、 应用WFP 实现应用程序访问网络时提示
这是个人防火墙的基本功能之一,当有应用程序访问网络时询问用户是否允许。首先我们编写一个callout 驱动,用来处理WFP抓到的网络数据。由于WFP抓到的数据只送到callout驱动不会送到用户层程序,所以这里必须用驱动根据数据判定放行还是阻止。Callout驱动向系统注册callout函数,
FWPS_CALLOUT0 sCallout ;
sCallout. calloutKey = * calloutKey;
sCallout. flags = flags;
sCallout. classifyFn = ClassifyFunction; //在实例程序代码中是 MonitorCoFlowEstablishedCalloutV4
sCallout. notifyFn = NotifyFunction;
sCallout. flowDeleteFn = FlowDeleteFunction;
status = FwpsCalloutRegister0 ( deviceObject , & sCallout , calloutId ); FWPS_CALLOUT0 结构用来组织一组callout 函数,之后用FwpsCalloutRegister0函数注册。这里详细介绍下ClassifyFunction函数,这个函数主要处理网络数据包,
NTSTATUS MonitorCoFlowEstablishedCalloutV4(
IN const FWPS_INCOMING_VALUES0* inFixedValues,//WFP传进来的本层特有的数据
IN const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,//本层相关的扩展数据
IN VOID* packet,
IN const FWPS_FILTER0* filter,
IN UINT64 flowContext,
OUT FWPS_CLASSIFY_OUT0* classifyOut//用这个结构里的字段告知WFP对数据包做出处理
)
{
NTSTATUS status = STATUS_SUCCESS;
UINT64 flowHandle;
UINT64 flowContextLocal;
UINT32 index;
UINT32 LocalIPADDRv4, remoteIPADDRv4;
USHORT LocalPort, remotePort;
UNREFERENCED_PARAMETER( packet);
UNREFERENCED_PARAMETER( filter);
UNREFERENCED_PARAMETER( flowContext);
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 %lx LocalPort %d \n remoteIP %lx remotePort %d", LocalIPADDRv4, LocalPort,
remoteIPADDRv4, remotePort);
DbgPrint( "BaseTDI: PID %d ,PID's PATH %s", inMetaValues-> processId, inMetaValues-> processPath-> data);
DbgPrint( "\n");
if ( monitoringEnabled)
{
// 访问规则程序代码,在这里通知用户态程序
AskUser(LocalIP, LocalPort, remoteIP, remotePort, PID);
If 允许
classifyOut-> actionType = FWP_ACTION_PERMIT; //允许发送或接收
else
classifyOut-> actionType = FWP_ACTION_BLOCK; //不允许发送或接收
}
return status;
}
在完成callout驱动后,下面介绍用户态程序如何设置WFP系统。 设置的大体流程如下文所示,
![](https://img-my.csdn.net/uploads/201206/08/1339146399_3296.png)
主要程序代码讲解,
// 向 WFP 系统添加 callout
DWORD WFPAppAddCallouts()
{
FWPM_CALLOUT0 callout;
DWORD result;
FWPM_DISPLAY_DATA0 displayData;
HANDLE engineHandle = NULL;
FWPM_SESSION0 session;
// 初始化一次会话
RtlZeroMemory(& session, sizeof( FWPM_SESSION0));
session. displayData. name = L "TEMP WFP Session";
session. displayData. description = L "For Adding callouts";
// 创建WFP引擎句柄
result = FwpmEngineOpen0(
NULL,
RPC_C_AUTHN_WINNT,
NULL,
& session,
& engineHandle
);
if ( NO_ERROR != result)
{ goto cleanup;}
// 开始与引擎交互
result = FwpmTransactionBegin0( engineHandle, 0);
if ( NO_ERROR != result)
{ goto abort; }
ADD CALLOUT
RtlZeroMemory(& callout, sizeof( FWPM_CALLOUT0));
displayData. description = MONITOR_FLOW_ESTABLISHED_CALLOUT_DESCRIPTION;
displayData. name = MONITOR_FLOW_ESTABLISHED_CALLOUT_NAME;
callout. calloutKey = TEMP_MONITOR_FLOW_ESTABLISHED_CALLOUT_V4;
callout. displayData = displayData;
callout. applicableLayer = FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4;
callout. flags = FWPM_CALLOUT_FLAG_PERSISTENT; //flags 置这个标志表示callout始终被WFP加载
result = FwpmCalloutAdd0( engineHandle, & callout, NULL, NULL);
if ( NO_ERROR != result)
{ goto abort; }
// 结束本次会话
result = FwpmTransactionCommit0( engineHandle);
if ( NO_ERROR == result)
{;}
goto cleanup;
abort:
// 说明本次会话失败
result = FwpmTransactionAbort0( engineHandle);
if ( NO_ERROR == result)
{;}
cleanup:
// 关闭引擎
if ( engineHandle)
{
FwpmEngineClose0( engineHandle);
}
return result;
} // 向 WFP 系统添加 filter
DWORD
WFPAppAddFilters( IN HANDLE engineHandle /*,IN FWP_BYTE_BLOB* applicationPath*/)
{
DWORD result = NO_ERROR;
FWPM_SUBLAYER0 monitorSubLayer;
FWPM_FILTER0 filter;
FWPM_FILTER_CONDITION0 filterConditions[ 1]; // 需要几条规则就定义几条
// 初始化过滤条件
RtlZeroMemory( filterConditions, sizeof( filterConditions));
filterConditions[ 0]. fieldKey = FWPM_CONDITION_IP_PROTOCOL;//所有IP协议数据
filterConditions[ 0]. matchType = FWP_MATCH_GREATER_OR_EQUAL; // 匹配度,大于,小于,大于等于 …
filterConditions[ 0]. conditionValue. type = FWP_UINT8;
filterConditions[ 0]. conditionValue. uint8 = IPPROTO_IP;
// 初始化子层
RtlZeroMemory(& monitorSubLayer, sizeof( FWPM_SUBLAYER0));
monitorSubLayer. subLayerKey = TEMP_MONITOR_SUBLAYER;
monitorSubLayer. displayData. name = L "TEMP Monitor Sub layer";
monitorSubLayer. displayData. description = L "TEMP Monitor Sub layer";
monitorSubLayer. flags = 0; //FWMP_SUBLAYER_FLAG_PERSISTENT;
// We don't really mind what the order of invocation is.
monitorSubLayer. weight = 0;
// 与WFP引擎开始一次会话
result = FwpmTransactionBegin0( engineHandle, 0);
if ( NO_ERROR != result)
{ goto abort;}
// 增加一个子层
result = FwpmSubLayerAdd0( engineHandle, & monitorSubLayer, NULL);
if ( NO_ERROR != result)
{ goto abort;}
FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4
RtlZeroMemory(& filter, sizeof( FWPM_FILTER0));
filter. layerKey = FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4;
filter. displayData. name = L "Flow established filter.";
filter. displayData. description = L "Sets up flow for traffic that we are interested in.";
filter. action. type = FWP_ACTION_CALLOUT_INSPECTION; // 表示把符合条件数据包交给callout处理
filter. action. calloutKey = TEMP_MONITOR_FLOW_ESTABLISHED_CALLOUT_V4;
filter. filterCondition = filterConditions;
filter. subLayerKey = monitorSubLayer. subLayerKey;
filter. weight. type = FWP_EMPTY; // 系统自动设置weight。weight值越大加载越靠前
filter. numFilterConditions = 1;//过滤条件数
result = FwpmFilterAdd0( engineHandle,
& filter,
NULL,
&( filterID[ 0]));
if ( NO_ERROR != result)
{ goto abort;}
// 结束本次会话
result = FwpmTransactionCommit0( engineHandle);
if ( NO_ERROR == result)
{;}
goto cleanup;
abort:
// 说明本次会话失败
result = FwpmTransactionAbort0( engineHandle);
if ( NO_ERROR == result)
{;}
cleanup:
return result;
}
二、NDISfilter
NDISfilter是利用系统提供的NDIS过滤引擎,获得MAC级别的网络数据包(这里可以看出WFP,NDISfilter,还有本文未提到的FileSystemMiniFilter,他们都是利用了微软提供的过滤引擎,向其注册回调函数,得到数据后处理)。关键程序代码说明,其中的详细数据结构请参阅微软文档NDISfilter一节,
NDIS_FILTER_DRIVER_CHARACTERISTICS FChars;
NdisZeroMemory(& FChars, sizeof( NDIS_FILTER_DRIVER_CHARACTERISTICS));
FChars. Header. Type = NDIS_OBJECT_TYPE_FILTER_DRIVER_CHARACTERISTICS;
FChars. Header. Size = sizeof( NDIS_FILTER_DRIVER_CHARACTERISTICS);
FChars. Header. Revision = NDIS_FILTER_CHARACTERISTICS_REVISION_1;
FChars. MajorNdisVersion = FILTER_MAJOR_NDIS_VERSION;
FChars. MinorNdisVersion = FILTER_MINOR_NDIS_VERSION;
FChars. MajorDriverVersion = 1;
FChars. MinorDriverVersion = 0;
FChars. Flags = 0;
FChars. FriendlyName = FriendlyName;
FChars. UniqueName = UniqueName;
FChars. ServiceName = ServiceName;
FChars. SetOptionsHandler = FilterRegisterOptions;
FChars. AttachHandler = FilterAttach;// 假如是我们想挂接的网络介质,就在这里通知系统挂接
FChars. DetachHandler = FilterDetach;
FChars. RestartHandler = FilterRestart;
FChars. PauseHandler = FilterPause;
FChars. SetFilterModuleOptionsHandler = FilterSetModuleOptions;
FChars. OidRequestHandler = FilterOidRequest;
FChars. OidRequestCompleteHandler = FilterOidRequestComplete;
FChars. CancelOidRequestHandler = FilterCancelOidRequest;
FChars. SendNetBufferListsHandler = FilterSendNetBufferLists; // 发送回调函数
FChars. ReturnNetBufferListsHandler = FilterReturnNetBufferLists;
FChars. SendNetBufferListsCompleteHandler = FilterSendNetBufferListsComplete;
FChars. ReceiveNetBufferListsHandler = FilterReceiveNetBufferLists; // 接收回调函数
FChars. DevicePnPEventNotifyHandler = FilterDevicePnPEventNotify;
FChars. NetPnPEventHandler = FilterNetPnPEvent;
FChars. StatusHandler = FilterStatus;
FChars. CancelSendNetBufferListsHandler = FilterCancelSendNetBufferLists;
NDIS_FILTER_DRIVER_CHARACTERISTICS 这个结构用来组织NDISfilter功能函数供NDIS系统回调,例如 FilterSendNetBufferLists ,发送数据回调函数,NDIS发送MAC帧时回调这个函数,相应数据可以在这个函数里得到处理,之后还给NDIS系统继续处理。
VOID
FilterSendNetBufferLists(
IN NDIS_HANDLE FilterModuleContext,
IN PNET_BUFFER_LIST NetBufferLists,
IN NDIS_PORT_NUMBER PortNumber,
IN ULONG SendFlags
)
{
PMS_FILTER pFilter = ( PMS_FILTER) FilterModuleContext;
NDIS_STATUS Status = NDIS_STATUS_SUCCESS;
PNET_BUFFER_LIST CurrNbl;
BOOLEAN DispatchLevel;
// 这里开始解释PNET_BUFFER_LIST指向的网络数据,并显示如何获得MAC地址
PNET_BUFFER_LIST pNetBufList, pNextNetBufList;
PMDL pMdl;
PNDISPROT_ETH_HEADER pEthHeader = NULL;
ULONG TotalLength, Offset, BufferLength;
pNetBufList = NetBufferLists;
while ( pNetBufList != NULL)
{
pNextNetBufList = NET_BUFFER_LIST_NEXT_NBL ( pNetBufList);
// 得到当前和包相关的MDL,MDL里即MAC帧,详细的NET_BUFFER_LIST结构请参阅微软相关文档
pMdl = NET_BUFFER_CURRENT_MDL( NET_BUFFER_LIST_FIRST_NB( pNetBufList));
TotalLength = NET_BUFFER_DATA_LENGTH( NET_BUFFER_LIST_FIRST_NB( pNetBufList));
Offset = NET_BUFFER_CURRENT_MDL_OFFSET( NET_BUFFER_LIST_FIRST_NB( pNetBufList));
BufferLength = 0;
do
{
ASSERT( pMdl != NULL);
if ( pMdl)
{
NdisQueryMdl(
pMdl,
& pEthHeader,
& BufferLength,
NormalPagePriority);
}
if ( pEthHeader == NULL)
{
BufferLength = 0;
break;
}
if ( BufferLength == 0)
{
break;
}
ASSERT( BufferLength > Offset);
BufferLength -= Offset;
pEthHeader = ( PNDISPROT_ETH_HEADER)(( PUCHAR) pEthHeader + Offset);
DbgPrint( "DstMAC %x-%x-%x-%x-%x-%x", pEthHeader-> DstAddr[ 0],
pEthHeader-> DstAddr[ 1], pEthHeader-> DstAddr[ 2],
pEthHeader-> DstAddr[ 3], pEthHeader-> DstAddr[ 4],
pEthHeader-> DstAddr[ 5]);
DbgPrint( "srcMAC %x-%x-%x-%x-%x-%x", pEthHeader-> SrcAddr[ 0],
pEthHeader-> SrcAddr[ 1], pEthHeader-> SrcAddr[ 2],
pEthHeader-> SrcAddr[ 3], pEthHeader-> SrcAddr[ 4],
pEthHeader-> SrcAddr[ 5]);
DbgPrint( "\n");
if ( BufferLength < sizeof( NDISPROT_ETH_HEADER))
{
break;
}
} while ( FALSE);
pNetBufList = pNextNetBufList;
}
}