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包過濾功能,具體代碼如下:
#define UDP_DEVICE_NAME L"//Device//Udp" #define TCP_DEVICE_NAME L"//Device//Tcp" #define TDI_FILTER_DEVICE_NAME L"//Device//TdiFilter" typedef struct __TDI_FILTER_DEVICE_EXTENSION { //過濾設備至少需要記錄下真正Tdi網絡設備的指針,來調用真正的TDI設備功能。 PDEVICE_OBJECT pTdiDeviceObject; } TDI_FILTER_DEVICE_EXTENSION, *PTDI_FILTER_DEVICE_EXTENSION; DriverEntry( PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath ) { UNICODE_STRING TdiDeviceName; ...... RtlInitUnicodeString( ( PUNICODE_STRING )&TdiDeviceName, UDP_DEVICE_NAME ); ntStatus = IoCreateDevice( DriverObject, sizeof( TDI_FILTER_DEVICE_EXTENSION ), //指定設備擴展長度 NULL, FILE_DEVICE_NETWORK, //網絡類型設備 0, 0, &DeviceObject ); //生成一個無名、網絡類型設備,附加至TDI TCP/UDP設備,實現過濾功能。 if( NT_SUCCESS( ntStatus ) ) { DeviceObject->Flags |= DO_DIRECT_IO; //生成新的頁表將同樣的用戶內存空間映射至系統虛擬內存空間來進行通信 ntStatus = IoAttachDevice( DeviceObject, TdiDeviceName, ( PDEVICE_OBJECT* )DeviceObject->DeviceExtension //附加至的設備的指針將會輸出至此參數中,這樣就將真正的TDI設備的指針記錄在過濾設備的擴展中 ); } }
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子功能,記錄下原始的注冊事件回調函數和參數,但真正注冊的是自己的回調函數,來截取所有的事件回調函數調用,實現過濾功能。
具體代碼如下:
typedef struct __TDI_EVENT_CONTEXT_WRAP { DWORD dwEventContextMark; //對自己生成的結構實例加一個四字節的標志,可以不使用。 DWORD dwEventType; //記錄事件回調函數的類型 PVOID pOrgEventHandler; //記錄原始的事件回調函數 PVOID pOrgEventContext; //記錄原始的事件回調函數參數 PFILE_OBJECT pAssocAddr; //記錄事件回調函數所綁定的本機套接字 PDEVICE_OBJEXT pDeviceObjext; //記錄注冊事件IRP所發送至的TDI設備 } TDI_EVENT_HANDLER_WRAP, *PTDI_EVENT_HANDLER_WRAP; typedef struct __TDI_EVENT_HANDLER_LINK { LIST_ENTRY List; //將事件回調鈎子記錄以鏈表形式進行管理 PTDI_EVENT_HANDLER_WRAP pTdiEventHandlerWrap; } TDI_EVENT_HANDLER_LIST, *PTDI_EVENT_HANDLER_LIST; LIST_ENTRY g_TdiEventHandlerInfoList; NTSTATUS DeviceInternalIoControl( PDEVICE_OBJECT DeviceObject, PIRP Irp ) { PKIRQL OldIrql; PLIST_ENTRY pListEntry; PIO_STACK_LOCATION IrpSp; PTDI_EVENT_HANDLER_WRAP pTdiEventHandlerWrap; PTDI_EVENT_HANDLER_LIST pTdiEventHandlerList; PTDI_EVENT_HANDLER_WRAP pTdiEventHandlerWrap_; PTDI_EVENT_HANDLER_LIST pTdiEventHandlerList_; PTDI_FILTER_DEVICE_EXTENSION pTdiDeviceExtension; pTdiDeviceExtension = ( PTDI_FILTER_DEVICE_EXTENSION )DeviceObject->DeviceExtension; switch( IrpSp->MinorFunction ) { case TDI_SET_EVENT_HANDLER: pTdiSetEvent = ( PTDI_REQUEST_KERNEL_SET_EVENT )&pIrpSp->Parameters; if( TDI_EVENT_RECEIVE == pTdiSetEvent->EventType )//|| TDI_EVENT_RECEIVE_EXPEDITED == pTdiSetEvent->EventType || TDI_EVENT_CHAINED_RECEIVE == pTdiSetEvent->EventType || TDI_EVENT_CHAINED_RECEIVE_EXPEDITED == pTdiSetEvent->EventType || TDI_EVENT_RECEIVE_DATAGRAM == pTdiSetEvent->EventType ) { pTdiEventHandlerList = NULL; pTdiEventHandlerWrap = NULL; pProcessNetWorkTrafficInfo = NULL; if( NULL == pTdiSetEvent->EventHandler ) { //注意!如果注冊的事件回調函數是NULL的話,它表示的取消之前曾經注冊過的事件回調函數, 這里當然不能掛鈎,可以加入釋放鈎子資源的操作。 goto CALL_PDO_DRIVER; } KeAcquireSpinLock( &g_SpLockTdiEventHandlerInfo, &OldIrql ); //對事件回調函數鈎子列表寫操作加鎖保護 pListEntry = g_TdiEventHandlerInfoList.Flink; for( ; ; ) { if( pListEntry == &g_TdiEventHandlerInfoList ) { pTdiEventHandlerWrap_ = NULL; break; } pTdiEventHandlerList_ = ( PTDI_EVENT_HANDLER_LIST )pListEntry; pTdiEventHandlerWrap_ = pTdiEventHandlerList_->pTdiEventHandlerWrap; if( pTdiEventHandlerWrap_->pAssocAddr == pFileObject && pTdiEventHandlerWrap_->dwEventType == dwEventType ) //如果此本機套接字對象的相應事件回調函數已經存在,則直接對其進行修改就可以了,而不是不斷的新建事件件回調鈎子 { pTdiEventHandlerWrap_->pOrgEventHandler = pEventHandler; pTdiEventHandlerWrap_->pOrgEventContext = pEventContext; break; } } if( NULL == pTdiEventHandlerWrap_ ) //沒有找到,加入新的事件回調函數鈎子 { pTdiEventHandlerWrap = ( PTDI_EVENT_HANDLER_WRAP )ExAllocatePoolWithTag( NonPagedPool, sizeof( TDI_EVENT_HANDLER_WRAP ), 0 ); if( NULL == pTdiEventHandlerWrap ) { goto RELEASE_RESOURCE; } pTdiEventHandlerList = ( PTDI_EVENT_HANDLER_LIST )ExAllocatePoolWithTag( NonPagedPool, sizeof( TDI_EVENT_HANDLER_LIST ), 0 ); if( NULL == pTdiEventHandlerList ) { goto RELEASE_RESOURCE; } pTdiEventHandlerWrap->dwEventContextMark = TDI_EVENT_CONTEXT_MARK; pTdiEventHandlerWrap->dwEventType = dwEventType; pTdiEventHandlerWrap->pOrgEventHandler = pEventHandler; pTdiEventHandlerWrap->pOrgEventContext = pEventContext; pTdiEventHandlerWrap->pAssocAddr = pFileObject; pTdiEventHandlerWrap->pDeviceObject = pTdiDeviceExtension->pTdiDeviceObject; pTdiEventHandlerList->pTdiEventHandlerWrap = pTdiEventHandlerWrap; InsertTailList( &g_TdiEventHandlerInfoList, pTdiEventHandlerList ); } else { pTdiEventHandlerWrap = pTdiEventHandlerWrap_; pTdiEventHandlerList = pTdiEventHandlerList_; } KeReleaseSpinLock( &g_SpLockTdiEventHandlerInfo, OldIrql ); //釋放事件回調鈎子列表鎖 if( TDI_EVENT_RECEIVE == pTdiSetEvent->EventType || TDI_EVENT_RECEIVE_EXPEDITED == pTdiSetEvent->EventType ) { pTdiSetEvent->EventHandler = TdiFilterRecvEventHandler; //加入自己的事件過濾回調函數 } else if( TDI_EVENT_CHAINED_RECEIVE == pTdiSetEvent->EventType || TDI_EVENT_CHAINED_RECEIVE_EXPEDITED == pTdiSetEvent->EventType ) { pTdiSetEvent->EventHandler = TdiFilterChainedRecvHandler; } else { pTdiSetEvent->EventHandler = TdiFilterRecvDatagramEventHandler; } pTdiSetEvent->EventContext = pTdiEventHandlerWrap; IoSkipCurrentIrpStackLocation( pIrp ); ntStatus = IoCallDriver( pDeviceExtension->pTdiDeviceObject, pIrp ); if( !NT_SUCCESS( ntStatus ) ) { if( NULL == pTdiEventHandlerWrap_ ) { //如果是新加入的事件回調函數鈎子,可以在出錯時將其釋放, 也可以保留至套接字關閉時,再進行釋放 KeAcquireSpinLock( &g_SpLockTdiEventHandlerInfo, &OldIrql ); //對事件回調函數鈎子列表寫操作加鎖保護 RemoveEntryList( ( PLIST_ENTRY )pTdiEventHandlerList ); ExFreePoolWithTag( pTdiEventHandlerWrap ); ExFreePoolWithTag( pTdiEventHandlerList ); KeReleaseSpinLock( &g_SpLockTdiEventHandlerInfo, OldIrql ); //釋放事件回調鈎子列表鎖 } } return ntStatus; } break; default: goto CALL_PDO_DRIVER; break; } RELEASE_RESOURCE: if( NULL != pTdiEventHandlerWrap ) { ExFreePoolWithTag( pTdiEventHandlerWrap, NonPagedPool ); } if( NULL != pTdiEventHandlerList ) { ExFreePoolWithTag( pTdiEventHandlerList, NonPagedPool ); } CALL_PDO_DRIVER: IoSkipCurrentIrpStackLocation( pIrp ); return IoCallDriver( pDeviceExtension->pTdiDeviceObject, pIrp ); }
以上對事件回調函數加入了鈎子,下一步,必須考慮對其釋放的問題,否則,當原始回調函數對應的套接字釋放后,你的系統將會崩潰,以下為具體代碼:
在套接字關閉后,要在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回調函數中取得。
相關代碼如下:
typedef struct __TDI_COMPLETION_WRAP { ...//可以加入用來記錄/處理數據的成員, 比如通信標志, 流量統計等 PIO_COMPLETION_ROUTINE pCompletionRoutine; LPVOID pContext; } TDI_COMPLETION_WRAP, *PTDI_COMPLETION_WRAP;
加入自定義的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過濾驅動組織框架,可以為它添加一些更加完善的功能。