本篇代码实战是WFP,TDI是铺垫,Windows网络杂谈开始。
XP老版本上,有几个驱动afd.sys,NetBt.sys,tdi.sys,tlnp.sys,tcpip.sys,ndis.sys等,Windows系统实现了OSI模型的4层,分别物理层,数据层,网际层和传输层。
Ndis好像分为三小层,没有写过代码这里不扩展,Win7后为了兼容老的TDI.sys,添加了新驱动TDX.sys,也保证了附加网络设备栈方式一致兼容,如/device/upd,/device/tcp等。Win7之后WFP是微软出来的新一代微型框架,为了更好的系统兼容性(稳定)和考虑上层过滤驱动开发更方便快捷。当然也怕hack不停Hook导致系统不稳定性和破坏,Minifilter诞生估计就有这小部分这个因素。
一个抽象的图:
TDI,Transport Driver Interface,传输驱动接口,连接着socket和协议驱动,是协议驱动实现,有些资料介绍是低层次内核态网络栈接口,用于访问传输层功能,比NDIS更接近于应用层。古老系统win2000、xp、03上的东西,虽然Vista之后不推荐使用该接口,但是现在来看都兼容,都会有TDI接口,所以是全平台不过以后可能会被微软剔除。
获取系统ip、端口数据,winapi就可以去做,如GetExtendedTcpTable 可以获取比较完善的数据信息。但并不能去截获包数据,写任意进程抓包器或端口抓包,三环HOOK必不可少,HOOK socket等系列函数,捕获发送的包过滤敏感信息。而在内核驱动层,可以创建过滤设备,但是要修改系统原生态的处理函数也可能用到HOOK操作,创建过滤设备能满足我们大多需求。
参考:http://www.codemachine.com/article_tdi.html 经典好文,当然是纯理论课本式,谷歌翻译就能学习,英语好底子好的越过本文的基础铺垫与TDI协议,直接去看原文回来在撸代码。
下文tdi默认是xp环境,TCPIP.sys对上接口暴露的就是TDI,内核访问TCPIP栈会用到TDI函数编程,TDI成了内核唯一的socket接口。
如上图所示,可以看到TDI Client,TDI Filter,TDI Transport,那么TDI Filter是可选的,其实这与以前键盘串口过滤本质是相同的,但是TDI有客户、过滤、传输三种。
说到windows,那就不能不提对象,进程也好、文件也罢,TDI也一样,对象如下:
很重要的一部分,看上述的图,Client到Transport,Filter可有可无,那么既然说了是网络,定然有交互协议约定。TDI传输依赖IRP与Callback,TDI Client使用IRP,而TDI Transport用EventCallback。
划重点,上图可以知道TDI使用IRP来TDIclient --> TDITransport请求,TDIClient驱动使用内部IO控制IRP,功能码IRP_MJ_INTERNAL_DEVICE_CONTROL发送TDI_xxx请求。IO stack Locations格式化了结构请求特定参数TDI_REQUEST_KERNEL_XXX。下述功能我个人化了一下,请大家看原文:
回调事件是为了优化内存使用,传输时候用底层网络驱动直接传输数据缓冲,整理如下:
拦截callback与irp不一样,irp拦截获取的是结构数据信息,irp包含所需的内容,而过滤事件先要拦截TDI_SET_EVENT_HANDLER请求,里面保存了TDIClient驱动注册的回调函数,替换实现拦截效果。
既然是连接两个端点,一个本地,一个远程。socket需要一一对应,当客户端请求,ip:port一致的话就会被TDI传输驱动在对象连接池中分配一个连接对象,有点NetworkPool的感觉多线程。
客户端交互TDI:
服务端交互TDI:
概念性请参考msdn:
https://docs.microsoft.com/zh-cn/windows-hardware/drivers/network/roadmap-for-developing-wfp-callout-drivers
本篇是以前学习编写的demo示例,Wfp再应用层和内核层都提供了API,本套代码逻辑User负责添加Callout,Sublayer及filter,内核层负责注册Callout。
1) wfp引擎初始化如下,HlprFwpmEngineOpen负责打开引擎:
FwpmCalloutAdd0添加CallOut,函数原型如下:
2) 函数有一个重要参数就是callout,FWPM_CALLOUT0结构原型如下,calloutKey是128bit的GUID唯一标识,是标记添加的CallOut,displayData则是CallOut的名字和描述,applicableLayer是Callout添加到哪一个层:
3) 监控Tcp流量,第一步使用CallOutAdd添加两个Callout,GUID分别如下:
4) 添加完成Callout之后,使用FwpmSubLayerAdd添加子层和过滤条件,这里添加一个MONITOR_SAMPLE_SUBLAYER的子层:
5) FWPM_SUBLAYER用于添加一个子层,FWPM_FILTER添加一个过滤器,而FWPM_FILTER_CONDITION用于添加过滤条件,它可以指定多个过滤条件,定义成为数组:
6) 过滤条件:TcpSublayerfilterConditions有几个重要参数,conditionValue.uint8表示IPPROTO_TCP符合TCP才会触发。
7) 过滤器:Tcpfilter.SubLayerKey参数关联第三步添加的子层,Tcpfilter.filterCondition 关联第四步添加的过滤器,这个概念就像子层里面有多个过滤器,过滤器有多个过滤条件:
用户态负责添加Callout,添加子层和绑定过滤器及添加过滤条件,这套代码同样可以再内核层实现,这里是写成了应用层。
协议栈流量经过我们注册的子层,通过匹配过滤器及条件筛选,符合条件将流量交给添加的CallOut处理,应用层只添加了Callout,还少至关重要的一步,流量符合条件流入Callout之后如何处理?放行-拦截-转发代理?接下来内核态注册Callout。
1) FwpsCalloutRegister函数负责注册Callout生效,函数原型如下,参数1对象,参数2:FWPS_CALLOUT结构,参数3 ID标识:
3) FWPS_CALLOUT包含了处理回调,子层命中数据流进行处理,calloutKey指定那一层的数据,比如TCP_FLOW_ESTABLISHED_CALLOUT_V4(应用层的第三步),ClassifyFnction回调函数负责处理流量数据:
4) 分别来看FWPM_LAYER_STREAM_CALLOUT | FWPM_LAYER_ALE_FLOW_ENTABLISHED的ClassifyFunction
4.1) FWPM_LAYER_ALE_FLOW_ENTABLISHED:
该层中两个重要参数可以获取"五要素"数据,进行上线文关联。
这里介绍参数中那些可能关心的数据:
申请结构体,通过回调参数获取元素保存再结构体中,并且通过FwpsFlowAssociateContext关联至其它层。
4.1) FWPM_LAYER_STREAM_CALLOUT :
Established层中已经将ip-port等数据关联至该层,所以从第三个参数可以获取到关联的数据.
将packet转换成FWPS_STREAM_CALLOUT_IO_PACKET结构体,然后获取数据长度和数据包,进行数据解析和打印,这里可以做过滤拦截,比如根据IP-PORT可以选择动作,放行或拦截。
放行设置FWP_ACTION_CONTINUE:
拦截设置FWP_ACTION_BLOCK:
wfp业务场景通常和代理(重定向)有关,一种是发起连接时候直接重定向,注册connect redirect层
另一种需要改包,将原数据包复制且block,然后处理复制的包(修改),重新注入层发送,微软也提供了一个udp代理,有兴趣的可以学习,后面有时间分享一些商用驱动框架,如NetFilter SDK2.0。
以前的旧笔记,和其它两篇驱动是姐妹篇:
Device Name |
Socket Type |
\Device\Ip |
ICMP |
\Device\RawIp |
Raw |
\Device\Tcp |
Stream |
\Device\Udp |
Datagram |
\Device\IPMULTICAST |
IGMP |
Request |
Build Function |
AFD/TCPIP Usage |
TDI_ASSOCIATE_ADDRESS |
TdiBuildAssociateAddress() |
关联一个地址和一个连接对象 |
TDI_DISASSOCIATE_ADDRESS |
TdiBuildDisassociateAddress() |
解绑连接地址于传输地址 |
TDI_CONNECT |
TdiBuildConnect() |
初始化TCP三次握手 |
TDI_LISTEN |
TdiBuildListen() |
监听 |
TDI_ACCEPT |
TdiBuildAccept() |
连接 |
TDI_DISCONNECT |
TdiBuildDisconnect() |
挥手 |
TDI_SEND |
TdiBuildSend() |
发送 |
TDI_RECEIVE |
TdiBuildReceive() |
接收 |
TDI_SEND_DATAGRAM |
TdiBuildSendDatagram() |
UDP发送 |
TDI_RECEIVE_DATAGRAM |
TdiBuildReceiveDatagram() |
UDP返回 |
TDI_SET_EVENT_HANDLER |
TdiBuildSetEventHandler() |
指定地址于TCPIP.sys注销、注册回调函数 |
TDI_QUERY_INFORMATION |
TdiBuildQueryInformation() |
读取MIB信息 |
TDI_SET_INFORMATION |
TdiBuildSetInformation() |
Not Supported by TCPIP.sys.不支持 |
TDI_ACTION |
TdiBuildAction() |
Not Supported by TCPIP.sys.不支持 |
Event |
AFD/TCP Usage |
TDI_EVENT_CONNECT |
AFD.sys指示到来的TCP SYN报文段 |
TDI_EVENT_DISCONNECT |
AFD.sys指示到来的TCP FIN报文段 |
TDI_EVENT_ERROR |
没被TCPIP.sys使用 |
TDI_EVENT_RECEIVE |
AFD.sys指示到来的TCP 数据报文段 |
TDI_EVENT_RECEIVE_DATAGRAM |
AFD.sys指示到来的UDP数据报文段 |
TDI_EVENT_RECEIVE_EXPEDITED |
AFD.sys指示带有TCP URGENT标记的数据报文段 |
TDI_EVENT_SEND_POSSIBLE |
没被TCPIP.sys使用 |
TDI_EVENT_CHAINED_RECEIVE |
AFD.sys用NDIS_BUFFER描述的数据,不使用TCPIP的缓冲区且对该数据进行复制 |
TDI_EVENT_CHAINED_RECEIVE _DATAGRAM |
没被TCPIP.sys使用 |
TDI_EVENT_CHAINED_RECEIVE_EXPEDITED |
没被TCPIP.sys使用 |
TDI_EVENT_ERROR_EX |
TCPIP.sys向AFD.sys通知,远端不可达错误 |
/
/
OpenEngine
result
=
HlprFwpmEngineOpen(&engineHandle, &session);
if
(NO_ERROR !
=
result)
{
printf(
"Error: HlprFwpmEngineOpen = %d\r\n"
, result);
return
-
1
;
}
/
/
TransactionBegin
result
=
HlprFwpmTransactionBegin(&engineHandle);
if
(NO_ERROR !
=
result)
{
printf(
"Error: HlprFwpmTransactionBegin = %d\r\n"
, result);
return
-
1
;
}
RtlZeroMemory(&callout, sizeof(FWPM_CALLOUT));
RtlZeroMemory(&displayData, sizeof(FWPM_DISPLAY_DATA));
displayData.description
=
MONITOR_FLOW_ESTABLISHED_CALLOUT_DESCRIPTION;
displayData.name
=
MONITOR_FLOW_ESTABLISHED_CALLOUT_NAME;
/
/
OpenEngine
result
=
HlprFwpmEngineOpen(&engineHandle, &session);
if
(NO_ERROR !
=
result)
{
printf(
"Error: HlprFwpmEngineOpen = %d\r\n"
, result);
return
-
1
;
}
/
/
TransactionBegin
result
=
HlprFwpmTransactionBegin(&engineHandle);
if
(NO_ERROR !
=
result)
{
printf(
"Error: HlprFwpmTransactionBegin = %d\r\n"
, result);
return
-
1
;
}
RtlZeroMemory(&callout, sizeof(FWPM_CALLOUT));
RtlZeroMemory(&displayData, sizeof(FWPM_DISPLAY_DATA));
displayData.description
=
MONITOR_FLOW_ESTABLISHED_CALLOUT_DESCRIPTION;
displayData.name
=
MONITOR_FLOW_ESTABLISHED_CALLOUT_NAME;
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
NTAPI
FwpmCalloutAdd0(
_In_ HANDLE engineHandle,
_In_ const FWPM_CALLOUT0
*
callout,
_In_opt_ PSECURITY_DESCRIPTOR sd,
_Out_opt_ UINT32
*
id
);
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
NTAPI
FwpmCalloutAdd0(
_In_ HANDLE engineHandle,
_In_ const FWPM_CALLOUT0
*
callout,
_In_opt_ PSECURITY_DESCRIPTOR sd,
_Out_opt_ UINT32
*
id
);
typedef struct FWPM_CALLOUT0_
{
GUID calloutKey;
FWPM_DISPLAY_DATA0 displayData;
UINT32 flags;
/
*
[unique]
*
/
GUID
*
providerKey;
FWP_BYTE_BLOB providerData;
GUID applicableLayer;
UINT32 calloutId;
} FWPM_CALLOUT0;
typedef struct FWPM_CALLOUT0_
{
GUID calloutKey;
FWPM_DISPLAY_DATA0 displayData;
UINT32 flags;
/
*
[unique]
*
/
GUID
*
providerKey;
FWP_BYTE_BLOB providerData;
GUID applicableLayer;
UINT32 calloutId;
} FWPM_CALLOUT0;
FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4
/
V6 关联TCP上下文数据包
FWPM_LAYER_STREAM_V4
/
V6 监控发送流量,如send
/
sendto
FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4
/
V6 关联TCP上下文数据包
FWPM_LAYER_STREAM_V4
/
V6 监控发送流量,如send
/
sendto
FWPM_SUBLAYER TcpSubLayer, UdpSubLayer;
/
/
FWPM_SUBLAYER monitorUdpICMPSubLayer;
FWPM_FILTER Tcpfilter, Udpfilter;
FWPM_FILTER_CONDITION TcpSublayerfilterConditions[
3
]
=
{
0
, }, UdpSublayerfilterConditions[
3
]
=
{
0
, };
/
/
We only need two
for
this call.
FWPM_SUBLAYER TcpSubLayer, UdpSubLayer;
/
/
FWPM_SUBLAYER monitorUdpICMPSubLayer;
FWPM_FILTER Tcpfilter, Udpfilter;
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-7-15 08:57
被一半人生编辑
,原因: