TDI FILTER 网络过滤驱动完全解析

 

    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
 {
  PDEVICE_OBJECT pTdiDeviceObject; //过滤设备至少需要记录下真正Tdi网络设备的指针,来调用真正的TDI设备功能。
 } 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过滤驱动组织框架,可以为它添加一些更加完善的功能。

你可能感兴趣的:(软件分析)