Windows 8
是微软公司推出的最新的客户端OS,内部名称
Windows NT 80
。相对于
Windows NT 5.x
,其网络结构变化非常大,原有的
TDI
,
NDIS
系统挂接方法不再适用。在Windows8系统中,微软引入了两种新的网络过滤系统,
WFP
和
NDISfilter
。
WFP (Windows Filtering Platform)
其包含从用户态到核心态的一系列应用层,根据需要可以在某一层设置回调函数拦截数据。
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 ;
FWPS_CALLOUT0 结构用来组织一组callout 函数,之后用FwpsCalloutRegister0函数注册。这里详细介绍下ClassifyFunction函数,这个函数主要处理网络数据包,
设置的大体流程如下文所示,
主要程序代码讲解,
// 向 WFP 系统添加 callout
// 向 WFP 系统添加 filter
WFP (Windows Filtering Platform)
其包含从用户态到核心态的一系列应用层,根据需要可以在某一层设置回调函数拦截数据。
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系统。
设置的大体流程如下文所示,
主要程序代码讲解,
// 向 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;
}
}