(转)TDI FILTER 网络过滤驱动完全解析
http://blog.csdn.net/charlesprince/article/details/5924376
TDI FILTER 過濾驅動的功能一般用來進行整個系統中的所有網絡流量的分析,記錄和管理,可以實現非常強大的管理功能,這里就將討論它的設計架構,和具體實現的方法。
進行系統級網絡數據包的過濾,很明顯,第一步需要在系統內核中截取到網絡數據包,那么在WINDOWS平臺下,應該如何實現這樣的功能?
在WINDOWS內核中,數據的通信載體是IRP包,如果希望截取到IRP數據包,當然必須生成核模塊以驅動的方式加載至內核之中。如果只是需要用來進行IRP數據包的截取,進而進行數據的分析,及下一步工作的控制。比較合適的方式就是使用TDI FILTER驅動的方式。
它在內核中的結構如圖所示:
TDI FILTER ( 你的DRIVER )
TDI DRIVER ( AFD.SYS )
附加至TDI設備的方法:
在DriverEntry時,生成兩個設備,將其附加至(Attach)至Tdi驅動的Udp和Tcp設備,實現IRP包過濾功能,具體代碼如下:
?
TDI驅動的組織結構分為兩個部分:
1.龐大的INTERNAL IO CONTROL子功能,包括以下功能:
TDI_ASSOCIATE_ADDRESS 可以通過它截取出自己和對端的套接字信息,一般就是IP地址+端口號,可以在此IRP功能響應中進行套接字信息的記錄。
TDI_DISASSOCIATE_ADDRESS 它的IRP包是在closesocket函數時發生的,所以如果我們在 TDI_ASSOCIATE_ADDRESS中記錄了信息,需要在此IRP的功能響應中取消之前的記錄。
TDI_CONNECT:主動連接
TDI_LISTEN
TDI_ACCEPT
TDI_DISCONNECT
TDI_SEND 它的IRP包是在調用send函數時發生的,必然,對它的響應將會實現對基于TCP協議的網絡上傳流量的截取。
TDI_RECEIVE 它的IRP包是在調用recv函數時發生的,必然,對它的響應將會實現對基于TCP協議的網絡下載流量的截取。
TDI_SEND_DATAGRAM 它的IRP包是在調用sendto函數時發生的,必然,對它的響應將會實現對基于UDP協議的網絡上傳流量的截取。
TDI_RECEIVE_DATAGRAM 它的IRP包是在調用recvfrom函數時發生的,必然,對它的響應將會實現對基于UDP協議的網絡下載流量的截取。
TDI_SET_EVENT_HANDLER 它的IRP包是在TDI驅動中注冊一些回調用函數,當接收到數據包時,將會首先執行它們,它的具體功能將會在下一步講述。
TDI_QUERY_INFORMATION
TDI_SET_INFORMATION
TDI_ACTION
TDI_DIRECT_SEND
TDI_DIRECT_SEND_DATAGRAM
在TDI_SET_EVENT_HANDLER子功能中,可以注冊以下回調涵數:
TDI_EVENT_CONNECT:被動連接
TDI_EVENT_DISCONNECT
TDI_EVENT_ERROR
TDI_EVENT_RECEIVE 對應于recv函數有返回數據時,將會調用此回調函數。
TDI_EVENT_RECEIVE_DATAGRAM 對應于recvfrom函數接收到數據時,將會調用此回調函數。
TDI_EVENT_RECEIVE_EXPEDITED 對應于函數接收到帶外數據時,將會調用此回調函數。( 帶外數也就是OOB數據, 在全部IRP數據包中會優先進行發送或接收,TCP協議功能 )
TDI_EVENT_SEND_POSSIBLE
以下將講述數據具體傳輸回調功能的過濾方法
4.實現事件回調函數掛鉤的方法:
響應IRP_MJ_INTERNAL_DEVICE_CONTROL中的TDI_SET_EVENT_HANDLER子功能,記錄下原始的注冊事件回調函數和參數,但真正注冊的是自己的回調函數,來截取所有的事件回調函數調用,實現過濾功能。
具體代碼如下:
?
以上對事件回調函數加入了鉤子,下一步,必須考慮對其釋放的問題,否則,當原始回調函數對應的套接字釋放后,你的系統將會崩潰,以下為具體代碼:
在套接字關閉后,要在IRP_MJ_CLEANUP功能函數中將相關的事件回調鉤子釋放掉:
NTSTATUS TdiFilterCleanUp(PDEVICE_OBJECT DeviceObject, PIRP pIrp ) {NTSTATUS ntStatus;KIRQL OldIrql;PTDI_EVENT_HANDLER_LIST pTdiEventHandlerList;PTDI_EVENT_HANDLER_WRAP pTdiEventHandlerWrap;PFILE_OBJECT pFileObject;TDI_FILTER_DEVICE_EXTENSION *pDeviceExtension;PIO_STACK_LOCATION pIrpSp;pDeviceExtension = ( TDI_FILTER_DEVICE_EXTENSION* )DeviceObject->DeviceExtension;pIrpSp = IoGetCurrentIrpStackLocation( pIrp );pFileObject = pIrpSp->FileObject;...//如果是主控制設備,要將調用IoCompleteIrp完成Irp, 如果是過濾設備,調用PDO設備驅動 IoSkipCurrentIrpStackLocation( pIrp );ntStatus = IoCallDriver( pDeviceExtension->pTdiDeviceObject, pIrp );if( !NT_SUCCESS( ntStatus ) ){DebugPrintEx( CLEANUP_INFO,"netmon TdiFilterCleanUp IoCallDriver return ERROR/n" );return ntStatus;}//下一步,釋放套接字對應的事件回調鉤子KeAcquireSpinLock( &g_SpLockTdiEventHandlerInfo, &OldIrql );FIND_LIST_AGAIN:pListEntry = g_TdiEventHandlerInfoList.Flink;for( ; ; ){if( pListEntry == &g_TdiEventHandlerInfoList ){break;}pTdiEventHandlerList = ( PTDI_EVENT_HANDLER_LIST )pListEntry;pTdiEventHandlerWrap = pTdiEventHandlerList->pTdiEventHandlerWrap;if( pTdiEventHandlerWrap->pAssocAddr == pFileObject ){RemoveEntryList( pListEntry );ExFreePoolWithTag( pTdiEventHandlerWrap, 0 );ExFreePoolWithTag( pTdiEventHandlerList, 0 );goto FIND_LIST_AGAIN;}pListEntry = pListEntry->Flink;}KeReleaseSpinLock( &g_SpLockTdiEventHandlerInfo, OldIrql );return ntStatus; }?
那么,可以在事件回調過濾鉤子函數對數據進行處理了
NTSTATUS TdiFilterRecvEventHandler( IN PVOID TdiEventContext,IN CONNECTION_CONTEXT ConnectionContext,IN ULONG ReceiveFlags,IN ULONG BytesIndicated,IN ULONG BytesAvailable,OUT ULONG *BytesTaken,IN PVOID Tsdu,OUT PIRP *IoRequestPacket) {NTSTATUS ntStatus;PIO_STACK_LOCATION pIrpSp;PTDI_EVENT_HANDLER_WRAP pEventHandlerWrap;PTDI_COMPLETION_WRAP pCompletionWrap;LARGE_INTEGER RecvedDataSize;pEventHandlerWrap = ( PTDI_EVENT_HANDLER_WRAP )TdiEventContext;if( FALSE == g_bFiltering ) //是否進行過濾 {goto CALL_ORIGINAL_EVENT_HANDLER;}if( FALSE != bStopRecv ){ ntStatus = STATUS_DATA_NOT_ACCEPTED;goto RELEASE_PROCESS_IO_INFO_RETURN;}ntStatus = ( ( ClientEventReceive )pEventHandlerWrap->pOrgEventHandler )( pEventHandlerWrap->pOrgEventContext, ConnectionContext, ReceiveFlags, BytesIndicated, BytesAvailable, BytesTaken, Tsdu, IoRequestPacket );if( NULL != BytesTaken && 0 != *BytesTaken ){//這里對數據進行處理, 比如可以進行通信數據量的統計 }if( STATUS_MORE_PROCESSING_REQUIRED != ntStatus ){goto RELEASE_PROCESS_IO_INFO_RETURN;}if( NULL == *IoRequestPacket ){goto RELEASE_PROCESS_IO_INFO_RETURN;}//IoRequestPacket表示當前接收IRP中的數據如果并不完整, 并且認為接下來的數據是有價值,需要接收的話,那么需要自己新建一個IRP包,將其指針傳入此參數中,并返回STATUS_MORE_PROCESSING_REQUIRED,通知IO管理不終止此IRP,TDI驅動將繼續接收接下來的數據。//所以如果此IRP包存在,可以截取它的信息,具體方法下一步講述。return ntStatus;CALL_ORIGINAL_EVENT_HANDLER:return ( ( ClientEventReceive )pEventHandlerWrap->pOrgEventHandler )( //直接調用原始的IRP鉤子函數,不進行處理pEventHandlerWrap->pOrgEventContext, ConnectionContext, ReceiveFlags, BytesIndicated, BytesAvailable, BytesTaken, Tsdu, IoRequestPacket); }上面講述了使用事件回調函數鉤子的方式進行通信數據的截取方法,下面講述直接IRP包數據傳輸方式,也就是以下4個子功能的截取方法:
TDI_SEND
TDI_RECEIVE
TDI_SEND_DATAGRAM
TDI_RECEIVE_DATAGRAM
在IRP_MJ_INTERNAL_DEVICE_CONTROL函數中響應以上子功能時,確認參數DeviceObject為TDI過濾設備,對所有截取到的IRP加入自己的完成函數,在此IRP被完成時( 調用IoCompleteRequest )此完成函數被調用,取得IRP處理的返回結果,進行處理。具體數據的處理對應于TDI_SEND子功能可以在IoCallDriver之前得到,因為它是應用程序傳給你的,而TDI_RECEIVE子功能,應該在TDI事件回調函數或Completion回調函數中取得。
相關代碼如下:
加入自定義的CompletionRoutine的方法:
if( TDI_SEND == MinorFunction ||TDI_SEND_DATAGRAM == MinorFunction || TDI_RECEIVE == MinorFunction || TDI_RECEIVE_DATAGRAM == MinorFunction ) {if( TDI_RECEIVE == MinorFunction && TDI_RECEIVE_PEEK == ( ULONG )pIrpSp->Parameters.Others.Argument2 ){//TDI_RECEIVE_PEEK不會真正接收數據,可以不需要對其進行過濾。goto SKIP_CURRENT_STACK_LOCATION;}pCompletionWrap = ( PTDI_COMPLETION_WRAP )ExAllocateFromNPagedLookasideList( &g_CompletionWrapList ); //可以使用鏈表或HASH等數據結構來管理所有的CompletionRoutine包裝信息,這里使用了NPAGED_LOOKASIDE_LIST,它的優勢在于系統中所有的NPAGED_LOOKASIDE_LIST資源的最大占用量將會被內存管理器動態管理if( NULL == pCompletionWrap ){goto SKIP_CURRENT_STACK_LOCATION;}//這里可以設置CompletionRoutine的具體工作參數,比如具體操作的類型,原始的Completion函數等,在用戶層傳送至的IRP中是不會設置CompletionRoutine函數的,但其它驅動傳送至的IRP中可能會進行設置,如在Receive事件回調函數中的IoRequestPacket參數IoCopyCurrentIrpStackLocationToNext( pIrp ); //設置下一個設備棧工作參數 IoSetCompletionRoutine( pIrp, TdiFilterCompletion, pCompletionWrap, TRUE, TRUE, TRUE);//這里就為這個IRP加入自己的CompletionRoutine函數goto CALL_PDO_DRIVER;SKIP_CURRENT_STACK_LOCATION:IoSkipCurrentIrpStackLocation( pIrp );CALL_PDO_DRIVER:return IoCallDriver( pDeviceExtension->pTdiDeviceObject, pIrp ); }具體的Completion函數的工作:
NTSTATUS TdiFilterCompletion( PDEVICE_OBJECT pDeviceObject, PIRP pIrp, LPVOID pContext ) {NTSTATUS ntStatus;PTDI_COMPLETION_WRAP pCompletionWrap;LARGE_INTEGER TransferredDataSize;PIRP pMasterIrp;PIO_STACK_LOCATION pIrpSp;ntStatus = pIrp->IoStatus.Status;pCompletionWrap = ( PTDI_COMPLETION_WRAP )pContext;if( NT_SUCCESS( ntStatus ) ){//可以在這里對成功傳輸的數據進行處理 }//這里可以調用原始的Completion函數 RETURN_SUCCESS:return ntStatus; }?
需要注意的是,如果為IRP包加入了CompletionRoutine之后,那么在驅動卸載( Unload )之前,必須保證所有IRP已經執行過此Completion函數, 如果在驅動被從內存中卸載后才執行, 將會使系統崩潰。
處理方法為:
1.不實現DriverUnload函數,使驅動只有在系統關閉,底層設備被卸載時,才能完成真正的卸載。這是的一般 FILTER驅動的工作方式,
2.使用線程同步的方法保證Completion函數的執行,Windows XP或之后的系統也提供了一個API, SetCompletionRoutineEx來保證驅動在Completion函數完成前不被卸載。
至此,講述TDI過濾驅動組織框架,可以為它添加一些更加完善的功能。
轉載于:https://www.cnblogs.com/himessage/archive/2013/01/15/2860834.html
總結
以上是生活随笔為你收集整理的(转)TDI FILTER 网络过滤驱动完全解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CorelDRAWX4的VBA插件开发(
- 下一篇: 三.选择结构(一)