安全防护软件、软件防火墙等网络防御软件能阻挡大量的网络入侵,让已知的网络攻击无处遁形,并且对未知的网络攻击缓解甚至阻断。本文中针对Windows 网络防火墙驱动开发以及WFP网络过滤框架介绍。阅读本文需要驱动开发基础。
WFP(Windows Filtering Platform)是Windows 中过滤网络数据的一个框架,代替了曾经的TDI网络过滤,是目前主流的网络过滤框架。并同时拥有用户层与内核层的过滤功能。 内核模式用fwpmk.h用户模式用fwpmu.h
WFP 框架中包含几大概念:
1. 垫片
2. 呼出端口
3. 分层
4. 子层
5. 过滤器
垫片
WFP 在网络设备栈中有多个垫片,分别截取不同网络层不同时机下的数据包。随后通过驱动自定义的Callout回调到驱动的回调中,驱动可以对截获的请求做任何事。可以理解垫片就是Hook的实现部分,用来截获函数调用的部分,只不过这里面向的是网络。这部分WFP开发者不需要为其做任何事,这些都由WFP框架完成。
呼出端口(Callout)
呼出端口是引擎截获到了相应垫片传来的网络请求和数据时,通过指定的规则分发到驱动声明的回调函数中,这些回调函数的定义在WFP中叫做呼出端口。并且每一个呼出端口创建时由开发者指定一个GUID来标识这个呼出回调。其中classifyFn代表着截获数据后的回调,notifyFn则是添加过滤器时的回调(一般使用STATUS_SUCCESS作为返回值即可),flowDeleteFn用于对绑定数据流的上下文释放。我们不绑定因此用空函数的函数指针。
[WFP对CALLOUT的定义]
(注意:FWPS_CALLOUT0_ 中 0代表版本,不同的Windows版本中可能会不一致,参数均使用宏代替,例如FWPS_CALLOUT0_ 使用它们时应该使用FWPS_CALLOUT)
分层(Layer)
分层表示着在哪一个时机执行,WFP预定义了许多层。每一个分层都有个GUID,如TCP三次握手后的分层:
分层定义均为FWPM_LAYER_*
参考MSDN:
[https://docs.microsoft.com/en-us/windows/win32/fwp/management-filtering-layer-identifiers-]
子层(SubLayer)
子层是在分层基础上添加的,一个分层中可以由多个子层组成。每个子层有它的名字以及GUID,并且还有高度,这里的高度指的是优先级,优先级越高就会在同一分层中被优先调用。WFP可以自行创建。WFP也有预定义的几个。
参考:
[https://docs.microsoft.com/en-us/windows/win32/fwp/management-filtering-sublayer-identifiers ]
[WFP对SUBLAYER的定义]
过滤器(Filter)
过滤器用于细化规则,并且将Callout、Layer、SubLayer合并到一起,
FilterKey由开发者定义用于标识Filter,filterCondition用于定义规则,action用于定义行为,行为中包括CalloutKey与行为类型,将行为类型定义为
FWP_ACTION_CALLOUT_TERMINATING则代表着行为由Callout回调的返回结构中定义,定义为FWP_ACTION_PERMIT则是直接放过。
[WFP对FILTER的定义]
Condition的定义:
其中fieldKey代表着过滤字段的GUID,WFP定义了一组字段,均为FWPM_CONDITION开头。
如:
MATCH_TYPE代表匹配类型,如相等、较小、较大。均为FWP_MATCH_开头
conditionValue则标识具体的值。
MSDN案例:
FWPM_CONDITION_IP_PROTOCOL字段表示IP的网络层协议
FWP_MATCH_EQUAL 表示相等
FWP_UINT8表示对比的数据类型是UINT8
IPPROTO_TCP 表示TCP
随后开始尝试着制作一个拦截对远程80端口访问的请求:
需要使用到的函数:
FwpmEngineOpen//打开引擎
FwpmTransactionBegin//开始事物 (开始修改)
FwpsCalloutRegister//注册呼出端口
FwpmCalloutAdd//实例化呼出端口
FwpmSubLayerAdd//添加子层
FwpmFilterAdd//添加过滤器
FwpmTransactionCommit//提交事物 (应用修改)
FwpmTransactionAbort//终止事物 (取消修改)
由于WFP需要指定设备,所以需要先创建一个设备:
首先打开WFP引擎,驱动程序应将第二个参数设置为RPC_C_AUTHN_WINNT或者RPC_C_AUTHN_DEFAULT。其他参数一般情况均为空。engineHandle返回引擎句柄。
随后通过引擎句柄开启事物。FwpmTransactionBegin的第二个参数为只读或读写
随后注册Callout以及回调函数,CalloutGuid由GUID工具生成,回调函数格式放在文章后部分,可以先创建一个空函数:
随后添加实例化Callout,其中applicableLayer就是分层类型GUID,
这里使用applicableLayer= FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4,表示过滤关于IPv4的三次握手
CalloutId是上面注册的返回值。CalloutKey是注册时用户自定义的CalloutKey。
flags 有0或三个选项,FWPM_CALLOUT_FLAG_PESISTENT表示callout在重新启动后保持不变。
displayData自定义名称。
随后创建子层,为子层设置优先级为最大,并自定义名称。
随后添加过滤器,将其组合成一个可用的过滤配置。
LayerKey仍然是实例化Callout时使用的applicableLayer参数,weigth高度的Type设置为FWP_EMPTY表示自动分配。action中表示着匹配成功后的动作。这里在action中的calloutKey设置好callout的回调GUID,action的类型设置为允许或拒绝或由callout回调决定,这里设置为由callout回调决定:FWP_ACTION_CALLOUT_TERMINATING。同样,给过滤器取一个名称。
filterConditions设置过滤规则,numFilterCondition设置过滤规则项个数。这里均为空。
filterKey可以选择自定义或者用ExUuidCreate生成一个GUID。subLayerKey用之前创建的SubLayerKey作为参数。随后调用FwpmFilterAdd创建出过滤器,返回一个FilterId。
一切准备就绪,可以提交修改了。
随后只要有用Ipv4连接的程序均会经过回调classifyFn
我们在classifyFn中只要做到判断端口为80时返回拒绝就可以到达效果。
首先看下classifyFn的结构
其中inFixedValues包含着分层中每个字段的值。
其中有个incomingValue中存放着的亦是我们关注的信息,如IP/端口。
使用以下下标访问亦可取出指定的值,注意我们的分层时用的是ALE_FLOW_ESTABLISHED_V4。如果用的是别的那么一样是FWPS_FIELD_<分层>_<字段名称>的组合。
可以看到有个FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_PORT。
通过inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_PORT].value.uint32 == 80
判断远程端口为80
随后回到classifyFn的声明中可以看到classifyOut指针,此值用于返回当前过滤器的过滤结果。
其中actionType与之前添加Filter中看到的一致。只不过在添加Filter中设置成FWP_ACTION_CALLOUT_TERMINATING用来表示在回调内决定。现在在回调中我们给定一个拒绝:
FWP_ACTION_BLOCK,对于其他的请求均用FWP_ACTION_PERMIT 表示允许。此处并打印出IP地址与端口
其中FILTER_WFP_ADDRESS_IPV4是自定义的结构
拦截效果:
正常:
当驱动卸载时,需要将过滤器、子层、Callout释放,删除操作需要打开事务。以下函数分别释放。
本文始发于微信公众号(锋刃科技):防火墙驱动开发之WFP网络过滤框架--过滤IP/端口
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论