NDIS-hooking drivers and legacy Windows systems

By | March 24, 2002

The original version of this article was published on this site over 14 years ago and was named “Firewall for Windows” (firewalls were popular at that time, however the same kind of drivers can be used for the wide variety of networking applications). Lots of time has passed so far, and described old Windows operating systems are mostly obsolete. However, since it demonstrates several useful techniques to modify Windows kernel behavior, I decided to slightly update and republish it. Also, it is a good overview on how NDIS-hooking drivers included into Windows Packet Filter were designed. Yes, Windows kernel Patch Guard introduced by Windows XP x64 made NDIS-hooking less practical, but it still may have some implication. And while we can use NDIS Intermediate and Lightweight NDIS Filter drivers for modern Windows systems, but there is no way to install these new drivers is available on Windows 10 IoT (at least by this moment). This fact seriously limits the capabilities of Raspberry Pi with Windows 10 IoT for designing low-level networking applications. And may be NDIS-hooking approach still has a chance for future…
Please note that this document describes the situation before Windows Vista release and covers neither NDIS 6.x Lightweight Filter drivers (replaced NDIS 5 Intermediate drivers) nor Windows Packet Filtering (WPF) callout drivers (replaced TDI filter drivers).

Network traffic filtering technologies for Windows

Even though Windows 9x/ME and Windows NT/XP provide similar socket interface and NDIS architecture, even allows binary compatible miniport drivers for network interfaces their network subsystem internals differ a lot. Regretfully, the size of this article does not allow us to review each of them in details, some interesting NT relative information can be found in chapter13 “Inside Microsoft Windows 2000 Third Edition” (David A. Solomon, Mark E. Russinovich MS Press 2000). As for Windows 9x/ME, I can hardly recommend something. Actually, NT/2000/XP network subsystem is more complicated than 9x/ME, but we can subdivide both on some common parts:

  • NDIS. In 1989 Microsoft and 3Com jointly developed Network Driver Interface Specification (NDIS), which allows network protocol drivers use of network interface services hiding details of their realization. A network interface driver developed in conformance with this specification is commonly called NDIS-miniport. One of the Microsoft goals was to make network hardware vendor’s life easier, once developed NDIS-miniport is portable between Windows versions. Details subject description can be found in the DDK documentation (Network Drivers section), in two words NDIS describes rules (types and interfaces) to follow in the network driver development and provides a function library to call instead kernel-exported services.
  • Network Protocol Drivers. The detailed description of this driver class is out of this article subject, however, for better understanding of network filtering technologies I would advise looking into DDK documentation (Network Drivers section). In two words, the network protocol driver (like TCPIP an example) on its lower edge uses NDIS library function for network access and may provide a Transport Data Interface (TDI) on the upper edge. Exported TDI interface can be used by various TDI-clients, like an example part of socket implementation afd.sys (NT/2000/XP). The usage of TDI has much in common with socket interface, an example the next sequence of requests, establishes connection to remote system (NT specific):
    • The client allocates and initializes address open TDI IRP. On this request, TDI returns a file object known as address object and representing network address. This step is equivalent to bind call from Windows Sockets interface.
    • The client allocates and initializes connection open TDI IRP, for what TDI returns a file object known as connection object and representing the connection. This step is equivalent to a socket function call from Windows Sockets interface.
    • The client associates the connection object with an address object, through associate address TDI IRP.
    • A TDI client who wishes to accept connection issue listen TDI IRP, specifying the number of connections for this connection object, and then sending accept TDI IRP for each remote client connection. This step is equal to listen and accept functions from Windows Sockets. A TDI client who wishes to establish connection to a remote system allocates connect TDI IRP, specifying a connection object. This IRP completes when the connection will be established (or an error occurs). This step is equivalent to connect function from Windows Sockets.
  • User-Mode DLLs that forms Windows Sockets interface. These are well-known ws2_32.dll, msafd.dll, wshtcpip.dll etc.

Let us review possible ways for network traffic filtering. Maybe this will be new for some readers, but about whole traffic can be filtered from User-mode, with no need in writing any drivers. No doubt, these methods have many limitations, an example does not provide any real network protection (Trojan application can skip Windows Sockets interface and use TDI directly), however can be used for such purposes like QOS (Quality Of Service).

User-mode traffic filtering

  • Winsock Layered Service Provider (LSP). This approach is well documented in MSDN, and has a good example (SPI.CPP). As method advantage, I would mention the possibility to determine the process that called Windows Sockets. This can be used for such tasks like QOS (Quality Of Service), encryption of data streams etc. However, don’t forget that TCPIP can be called directly via TDI; therefore, this method is of no use for Trojan/virus protection and etc. Additionally, this approach can’t be used on the router because packets are routed on the TCPIP level (or even on MAC level, for details search in DDK for FFP).
  • Windows 2000 Packet Filtering Interface. Windows 2000 provides an API using which a user-mode application can install a set of “filter descriptors”, which will be used by TCPIP for packet filtering (PASS/DROP). However, rules for filtering are rather limited (pass/drop based on IP address and port information), and this approach can be used only starting from Windows 2000 and higher, what is rather serious limitation.
  • Substitution of Winsock DLL. This approach mentioned only for security reasons. I would not recommend using it nowadays because of well-known reasons.
  • Global hook of all “dangerous” functions (starting from Windows sockets, DeviceIoControl etc.). If you ask me if this can be done, I answer yes, but this approach is rather difficult to realize and developer must be careful because it has possible impact on overall system stability and security.

Kernel-mode traffic filtering

  • Kernel-mode socket filter. This technology is applicable for Windows NT/2000. It is based on interception of all calls from msafd.dll (the lowest level user-mode Windows Sockets DLL) to the kernel-mode module afd.sys (the TDI-client, kernel-mode a part of Windows Sockets). This method is interesting, but its possibilities are not much wider, than LSP’s. In addition, I would like to add that AFD interface is extended from version to version of Windows NT family, what limits it portability.
  • TDI-filter driver. This technology applied for both Windows 9x/ME and Windows NT/2000 though concrete implementations strongly differ. The example of the code for a Windows 9x can be found in the examples to Vireo/Numega VtoolsD; therefore, I will not observe it here. As for Windows NT/2000, in case of TCP/IP filtering it is necessary to intercept (using IoAttachDevice or patching dispatch table in driver object) all calls, directed to devices created by tcpip.sys driver (\Device\RawIp, \Device\Udp, \Device\Tcp, \Device\Ip, \Device\MULTICAST). I would recommend you to make simple experiment, download and install Device Filter utility from this site. Then attach to devices created by TCPIP and try any network activity (even “ping 127.0.0.1” is OK). The utility will show you all requests to the transport. This technology is well known, and also it’s used in a number of commercial products (for example, Outpost Firewall). However, as well as the methods above, this approach can be used only for creation of personal firewall class products, and it can’t protect your TCPIP stack from hacker attacks.
  • NDIS Intermediate Driver. Microsoft has provided this class of drivers just for needs similar to ours, however, implementation of this driver for Windows 98/ME/NT leaves to wish the best, and in a Windows 95 is absent at all. In particular, they are very inconvenient in installation and for end users. Actually, support of IM drivers was improved in Windows 2000/XP, but there is another problem, you will have to digitally sign your driver at Microsoft, elsewhere user will have a horrible nag-message. Interested persons can look at the NDIS IM FAQ, and documentation in the DDK. I shall not examine this variant in detail here, instead of it we shall consider a worthy alternative.
  • Windows 2000 Filter-Hook Driver. The method is described in DDK documentation and applicable only in Windows 2000 and higher; therefore, I shall not stop on this variant.
  • NDIS Hooking Filter Driver. This approach we will review in more detail here. Briefly, the technique based on interception of some subset of NDIS functions, which in the further allow tracing registration of all protocols installed in the operating system and opening of network interfaces by them. Among the advantages of the given approach, it is necessary to mention ease of installation and transparent support of Dial-Up interfaces, what requires additional work in the case of IM drivers.

NDIS Hooking Filter Driver in a Windows 9x/ME

Before continue reading, I recommend you to open MSDN and read documentation for the following NDIS functions:

  • NdisRegisterProtocol
  • NdisDeregisterProtocol
  • NdisOpenAdapter
  • NdisCloseAdapter
  • NdisSend

As an acquaintance to writing drivers of virtual devices (VxD) for the platform of a Windows 9x/ME is desirable. In the further examples of the code, we will use functions and data structures defined in Vireo/Numega VToolsD. Certainly, all code can be modified for compilation with DDK, however VtoolsD allows writing much more compact code and hides VxD implementation details.

Let’s start

As it was already mentioned above, in the Windows 9x/ME, NDIS library is realized as the virtual device driver ndis.vxd. As far as we need to intercept part of NDIS function, we should write our own virtual device driver (VxD). I am not going to teach you here to write virtual device drivers, therefore for understanding underwritten I recommend you to familiarize with a subject. In the further, it is supposed, that the reader has some knowledge of Windows 9x/ME internals and can create VxD, even thanks to such tools as Numega VtoolsD.

If you have followed to my advice and have viewed descriptions of the five mentioned above NDIS functions, it must be clear for you, that interception of five listed functions should be made before initialization of protocols (calling NdisRegisterProtocol by them). For an examined example, as the order of initialization was selected, VNETBIOS_Init_Order. I leave other parameters of the VxD to your discretion as they entirely depend on that functionality what you want to receive.

Hooking of VxD services

The technique of hooking VxD services is well documented; therefore I shall not waste time on retelling already written. I’ll bring a short code snippet instead. I do not bring the code for each concrete function, that is done below for NdisRegisterProtocol, it is necessary to do and for four other functions.

   // Function Prototype For Hooking NdisRegisterProtocol
   typedef VOID NDIS_API OS_NDIS_REGISTER_PROTOCOL (
    PNDIS_STATUS Status,
    PNDIS_HANDLE NdisProtocolHandle,
   PNDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics,
    UINT CharacteristicsLength
    );
   
   typedef OS_NDIS_REGISTER_PROTOCOL *POS_NDIS_REGISTER_PROTOCOL;...
   // Thunks for NDIS services hooking
   HDSC_Thunk thunkNdisRegisterProtocolHook;
   //...
   // Old handlers
   POS_NDIS_REGISTER_PROTOCOL OldNdisRegisterProtocol = NULL;
   //...
   // New NdisRegisterProtocol handler
   VOID NDIS_API NH_NdisRegisterProtocol (
 PNDIS_STATUS Status, 
 PNDIS_HANDLE NdisProtocolHandle,
 PNDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics,
 UINT CharacteristicsLength
 )
   {
    //...
   }
   //...
   // Hooking VxD services of NDIS.VXD
   NDIS_STATUS HookNdisVxdServices ()
   {
     OldNdisRegisterProtocol = 
     (POS_NDIS_REGISTER_PROTOCOL) Hook_Device_Service_C (
   GetVxDServiceOrdinal (NdisRegisterProtocol),
   NH_NdisRegisterProtocol, 
   *thunkNdisRegisterProtocolHook
   );

     if (! OldNdisRegisterProtocol)
      return NDIS_STATUS_FAILURE;
     //...
   
     return NDIS_STATUS_SUCCESS;
 }

HookNdisVxdServices function should be called in the initialization handler of for the virtual device.

What is next?

Before answering this question, let’s take a look, what we already have. Actually, we already can view all outgoing network traffic. We are not limited only to view, but also to modify, or, for example, to transfer packets to the application working in Ring3. Anyway, we have all necessary to move further. There is a question on how to get access to the incoming traffic. Actually, it’s not as difficult as it seems, for this purpose, we have intercepted NdisRegisterProtocol. As one of the arguments of this function, the protocol transfers the pointer to structure NDIS_PROTOCOL_CHARACTERISTICS. This structure in details is described in documentation DDK. In new handler NdisRegisterProtocol we can modify this structure, in particular, we are interested in ReceiveHandler field, this function called replying call by the driver of the network interface of NdisIndicateReceive function. How to process this call and to receive a whole packet (the network adapter can transfer only a part of the package) documented in the DDK, and I shall omit these details. Thus, actually, we can process not only outgoing, but also incoming traffic. For what, we intercepted three more functions? I believe, any well-designed firewall should watch the protocols, which have registered in the system, and the network adapters open them, namely the staying functions allow us to watch it.

Windows ME

Let’s put, you followed the above-stated guidelines and as a result of persistent work have created the network-filtering driver. Unfortunately, this driver will work normally only under Windows 95/98. If you install your VxD on Windows ME (I’m not going to review variants of VxD installation here, the most universal for the given class of drivers is installed through the registry) your driver will not intercept any registered protocol. The problem occurs because, the TCP / IP protocol has moved from the virtual device driver (VxD) into tcpip.sys that does not know anything about VxD services. To support new implementation of the TCP/IP stack ndis.vxd exports library functions not only as VxD-services (as it did before for NDIS-miniports support, which are binary compatible with Windows NT (if built with this option) though for the protocols it was not used). It is done using well-known PELDR_AddExportTable function (service of vxdldr.vxd). The problem can be resolved by simple hooking of this service, in the new handler we should check, that these are NDIS exported and change five pointers in the table (for previously mentioned functions). I shall not stop on concrete implementation, as it does not represent anything complex. Another moment with NdisRegisterProtocol function, more exactly with structure NDIS_PROTOCOL_CHARACTERISTICS is much more interesting. This structure differently defined in a Windows 9x and Windows NT DDK. You can find exact definitions in the proprietary DDK’s header file ndis.h, The most important moment here is that tcpip.sys calls NdisRegisterProtocol, transferring it NDIS_PROTOCOL_CHARACTERISTICS as it is defined in NT DDK, therefore ndis.vxd does not export the same function as VxD-service and through the PELDR-interface. The function exported through the PELDR-interface should convert NDIS_PROTOCOL_CHARACTERISTICS from the format accepted in the NT to the format understandable to VxD-service, then call the last. And it is necessary to mark, that in Windows ME VxD-service NdisRegisterProtocol has another structure as a parameter, then the one defined in a Windows 9x DDK structure, it’s extended and not documented variant (which allows tcpip.sys to use NDIS version up to 5.0 though in practice it uses version 4.0).

The definition of this structure should look approximately so:

typedef struct _ME_NDIS_PROTOCOL_CHARACTERISTICS
{
    UCHAR MajorNdisVersion;
    UCHAR MinorNdisVersion;
    ULONG Reserved;
    OPEN_ADAPTER_COMPLETE_HANDLER OpenAdapterCompleteHandler;
    CLOSE_ADAPTER_COMPLETE_HANDLER CloseAdapterCompleteHandler;
    SEND_COMPLETE_HANDLER SendCompleteHandler;
    TRANSFER_DATA_COMPLETE_HANDLER TransferDataCompleteHandler;
    RESET_COMPLETE_HANDLER ResetCompleteHandler;
    REQUEST_COMPLETE_HANDLER RequestCompleteHandler;
    RECEIVE_HANDLER ReceiveHandler;
    RECEIVE_COMPLETE_HANDLER ReceiveCompleteHandler;
    STATUS_HANDLER StatusHandler;
    STATUS_COMPLETE_HANDLER StatusCompleteHandler;
    BIND_ADAPTER_HANDLER BindAdapterHandler;
    UNBIND_ADAPTER_HANDLER UnbindAdapterHandler;
    UNLOAD_PROTOCOL_HANDLER UnloadProtocolHandler;
    NDIS_STRING Name;
    RECEIVE_PACKET_HANDLER ReceivePacketHandler;
    PNP_EVENT_HANDLER PnPEventHandler;
    PVOID ReservedHandlers [4];
    PVOID CoSendCompleteHandler;
    PVOID CoStatusHandler;
    PVOID CoReceivePacketHandler;
    PVOID CoAfRegisterNotifyHandler;
} ME_NDIS_PROTOCOL_CHARACTERISTICS;

And pseudocode NdisRegisterProtocol exported through PELDR-tools looks approximately as:

 // New entry for export from NDIS.VXD exported through PELDR_AddExportTable
VOID NDIS_API
   WDM_NdisRegisterProtocol (
   OUT PNDIS_STATUS Status,
   OUT PNDIS_HANDLE NdisProtocolHandle,
   IN PNDIS50_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics,
   IN UINT CharacteristicsLength
   )
{
   unsigned short* pUnicode = NULL;
   unsigned char* pAnsi = NULL;
   unsigned int Counter = 0;
   unsigned char* pProto = NULL;
   
   ME_NDIS_PROTOCOL_CHARACTERISTICS MilleniumCharacteristics;
   NdisZeroMemory (
 *MilleniumCharacteristics,  
 sizeof (MilleniumCharacteristics)
 );
   
   MilleniumCharacteristics.MajorNdisVersion = 
    ProtocolCharacteristics->MajorNdisVersion;
   MilleniumCharacteristics.MinorNdisVersion  =
    ProtocolCharacteristics->MinorNdisVersion;
   MilleniumCharacteristics.Reserved =
    ProtocolCharacteristics->Reserved;
   
   MilleniumCharacteristics.OpenAdapterCompleteHandler = 
    ProtocolCharacteristics->OpenAdapterCompleteHandler;
   MilleniumCharacteristics.CloseAdapterCompleteHandler = 
    ProtocolCharacteristics->CloseAdapterCompleteHandler;
   MilleniumCharacteristics.SendCompleteHandler = 
    ProtocolCharacteristics->SendCompleteHandler;
   MilleniumCharacteristics.TransferDataCompleteHandler = 
    ProtocolCharacteristics->TransferDataCompleteHandler;
   MilleniumCharacteristics.ResetCompleteHandler = 
    ProtocolCharacteristics->ResetCompleteHandler;
   MilleniumCharacteristics.RequestCompleteHandler = 
    ProtocolCharacteristics->RequestCompleteHandler;
   MilleniumCharacteristics.ReceiveHandler = 
    ProtocolCharacteristics->ReceiveHandler;
   MilleniumCharacteristics.ReceiveCompleteHandler = 
    ProtocolCharacteristics->ReceiveCompleteHandler;
   MilleniumCharacteristics.StatusHandler = 
    ProtocolCharacteristics->StatusHandler;
   MilleniumCharacteristics.StatusCompleteHandler = 
    ProtocolCharacteristics->StatusCompleteHandler;
   
   if (ProtocolCharacteristics->Ndis40Chars.Ndis30Chars.MajorNdisVersion > 3)
   {
    MilleniumCharacteristics.BindAdapterHandler = 
  ProtocolCharacteristics->BindAdapterHandler;
 MilleniumCharacteristics.UnbindAdapterHandler = 
  ProtocolCharacteristics->UnbindAdapterHandler;
    MilleniumCharacteristics.UnloadProtocolHandler = 
  ProtocolCharacteristics->UnloadHandler;
    MilleniumCharacteristics.PnPEventHandler = 
  ProtocolCharacteristics->PnPEventHandler;
    MilleniumCharacteristics.ReceivePacketHandler = 
  ProtocolCharacteristics->ReceivePacketHandler;
   }
   
   if (ProtocolCharacteristics->MajorNdisVersion > 4)
   {
   MilleniumCharacteristics.CoReceivePacketHandler = 
   ProtocolCharacteristics->CoReceivePacketHandler;
    MilleniumCharacteristics.CoSendCompleteHandler = 
  ProtocolCharacteristics->CoSendCompleteHandler;
    MilleniumCharacteristics.CoStatusHandler = 
  ProtocolCharacteristics->CoStatusHandler;
    MilleniumCharacteristics.CoAfRegisterNotifyHandler = 
  ProtocolCharacteristics->CoAfRegisterNotifyHandler;
    MilleniumCharacteristics.ReservedHandlers [0] = 
  ProtocolCharacteristics->ReservedHandlers [0];
    MilleniumCharacteristics.ReservedHandlers [1] = 
  ProtocolCharacteristics->ReservedHandlers [1];
    MilleniumCharacteristics.ReservedHandlers [2] = 
  ProtocolCharacteristics->ReservedHandlers [2];
    MilleniumCharacteristics.ReservedHandlers [3] = 
  ProtocolCharacteristics->ReservedHandlers [3];
   }
   
   MilleniumCharacteristics.Name.Length = 
      ProtocolCharacteristics->Name.Length/2;
   MilleniumCharacteristics.Name.MaximumLength =  
      ProtocolCharacteristics->Name.MaximumLength/2;
   MilleniumCharacteristics.Name.Buffer = 
      (PUCHAR) HeapAllocate (
 ProtocolCharacteristics->Name.MaximumLength,
 0
 );
        
   pAnsi = MilleniumCharacteristics.Name.Buffer;
   pUnicode = (unsigned short *)
      ProtocolCharacteristics->Name.Buffer;
   
    // Copy UNICODE string to ANSI string
   for (
 Counter = 0;
 Counter < MilleniumCharacteristics.Name.Length;
 ++Counter
 )
   {
    *(pAnsi + Counter) = (UCHAR) *(pUnicode + Counter);
   }

   // finalize ANSI string with zero
   *(pAnsi + MilleniumCharacteristics.Name.Length) = 0; 
    
   NH_NdisRegisterProtocol (
     Status, 
     NdisProtocolHandle, 
     (PNDIS_PROTOCOL_CHARACTERISTICS)*MilleniumCharacteristics, 
     sizeof (MilleniumCharacteristics)
     );
 
   if (*Status == NDIS_STATUS_SUCCESS)
   {
      // pProto points to internal NDIS structure    
      // which holds information about protocol
      pProto = (PUCHAR) (*NdisProtocolHandle);

     // Some internal operation 
     *(pProto + 0x34) = *(pProto + 0x34) | 1;
   }
   
   HeapFree ((PVOID) MilleniumCharacteristics. Name. Buffer, 0);
}

Thus, the problem with Windows Millennium Edition has been successfully resolved.

Next steps

It seems to me, I’ve given here enough information to start with implementation of the driver of the described type, certainly, I have omitted a huge set of technical details, but all of them are perfectly stated in DDK’s documentation. So have a desire, everything else will be applied. Meanwhile, we shall proceed to the next subject, let’s review how the similar approach can be realized in Windows NT/2000.

NDIS Hooking Filter Driver in Windows NT/2000

For the better understanding of the technology stated in the given part, acquaintance with the kernel architecture of Windows NT/2000/XP is recommended. Acquaintance to writing kernel-mode drivers and the PE-format is desirable.

How to start?

Certainly, you should start from the creation of the elementary NT kernel-mode driver. If for the Windows 9x/ME I have mentioned VToolsD for the given driver, for example, it is possible to use DriverWorks though certainly nothing prevents to write it, using DDK only and the command line compiler. I’ve already mentioned, that for drivers of the given type the order of loading is important, the main requirement for our driver to boot after NDIS, but before TCPIP (and accordingly other protocols which we want to filter). As far as Windows 2000 and NT have a little different order of loading for the network subsystem, I shall not result concrete guidelines. LoadOrder utility from www.osr.com displays the order of loading of drivers in a convenient manner and may be helpful to you here.

How to hook?

Implementations of NDIS are various in a Windows 9x/ME and NT/2000, now it will be enough to hook only 4 functions:

  • NdisRegisterProtocol
  • NdisDeregisterProtocol
  • NdisOpenAdapter
  • NdisCloseAdapter

Hooking NdisSend has no sense anymore, if you take a look in ndis.h you will see, that in our case this function is defined as a macro. However, these four functions need to be intercepted somehow. Right at the beginning of this part I already marked, that it is desirable to familiarize with the PE-format, this knowledge is necessary for the interception. The essence of the technology is reduced to that it is necessary to find ndis.sys header image in the memory and to patch the export table of it, changing addresses for four functions above. All necessary structures for operation with PE-image are in the winnt.h file from the DDK. In Windows NT 4.0 this technology will work fine, however, having started this driver under Windows 2000 or XP (and if large 4MB pages are not used, if used WP protection disabled), you will see only “the dark blue screen of death “. The problem is that Windows 2000 protects a kernel image from possible modification. To resolve this problem, there are two possible ways:

  • Disable protection of a kernel image through the registry. For this purpose, it is necessary to create in a HKLM\SYSTEM\CurrentControlSet\Control\S essionManager\Memory Management key REG_DWORD parameter with name EnforceWriteProtection and value 0.
  • Reset Write Protection bits in register CR0, before updating the export table. It can be done, for example, as follows:
mov ebx, cr0; to receive current state Cr0
push ebx; to save it(him) 
and ebx, ~0x10000; to reset WP bats 
mov cr0, ebx; to switch off write protection 
; Here we modify the table of export.... 
pop ebx; 
mov cr0, ebx; to restore the previous processor state 

I do not put here the code for modification of the export table, mainly because it is not a unique way to intercept the NDIS function. Daniel Lanciany in his variant of NDIS-hooking driver has gone in another way, he has updated the beginning of functions, having inserted in them transitions to his code. In his handler, he restores a body of the intercepted function, makes necessary operations on processing call, calls the restored function, and after return from it again modifies the beginning. As far as calls to these four functions set are not so often, this way is not much worse than editing of the export table, except one important drawback. As well as the similar method of hooking DLL functions in User-mode, it may appear unsafe on SMP platforms.

What else is necessary to do?

I shall not stop in detail on new handler NdisRegisterProtocol, as it should make practically the same as in a Windows 9x/ME, i.e. to expose the handler of entering traffic (actually it is necessary to change not only ReceiveHandler, but also in particular TransferDataCompleteHandler). The only thing I should mention here is that if 9x/ME TCP/IP driver (“MSTCP”) registers one protocol and this protocol directly opens both Ethernet adapters and PPPMAC (emulation Ethernet above dial-up) then in NT/2000/XP TCP/IP actually consists of two drivers TCPIP.SYS (“TCPIP”) and WANARP.SYS (“TCPIP_WANARP” in 2000/XP, “RASARP” in NT 4.0) and this second protocol opens emulation \DEVICE\NDISWANIP (emulating Ethernet upper interface of the intermediate NDISWAN driver).

The further operation in many respects is similar to writing of the driver of the network protocol; therefore, I would advise familiarizing with appropriate section NT DDK (Network Drivers). The only complex enough moment is about new handler for NdisOpenAdapter. One of the returned parameters of this function (NdisBindingHandle) actually is a pointer to NDIS_OPEN_BLOCKstructure. This structure is defined in ndis.h file, but, unfortunately, is not documented. This is important for us because it gives access to outgoing from the protocol traffic (fields SendHandler, SendPacketsHandler, and TransferDataHandler). Obviously, now it is necessary to transfer to the protocol our variant NDIS_OPEN_BLOCK, having saved the original. The following code fragment is taken from the new NdisOpenAdapter handler:

// after calling original NdisOpenAdapter
NdisAcquireSpinLock ( &g_OpenAdapterLock );
if (( *Status == NDIS_STATUS_SUCCESS )||
     ( *Status == NDIS_STATUS_PENDING)
   )
{
   // Save old binding handle and adapter selected medium
   pAdapter -> m_NdisBindingHandle = *NdisBindingHandle;
   pAdapter -> m_Medium = MediumArray[*SelectedMediumIndex];
   
   // we does not support adapters except ethernet and \DEVICE\NDISWANIP
   if ( !( ( pAdapter -> m_Medium == NdisMediumDix ) ||
   ( pAdapter -> m_Medium == NdisMedium802_3 )||
   ( pAdapter -> m_Medium == NdisMediumWan ) ) )
   {
     // Release already allocated resources
     AF_FreeAdapterEntry ( pAdapter );
     NdisReleaseSpinLock ( &g_OpenAdapterLock );
     return;
   }
  // Copy Real Open Block to our location
    NdisMoveMemory ( 
     &pAdapter -> m_OpenBlock,
     *NdisBindingHandle,
     sizeof(NDIS_OPEN_BLOCK)
     );
   
    // Substitute NDIS_OPEN_BOCK
    *NdisBindingHandle = &pAdapter->m_OpenBlock;
  // Major work for initializing adapter finished
    // Check if complete handler did the remain in case of pending
    if ((*Status == NDIS_STATUS_PENDING)&&
        (pAdapter->m_dwAdapterState == ADAPTER_STATE_COMPLETE_FAILED)
     )
    {
     // Error while opening adapter occured, 
  // OpenAdapterComplete left us to free resource
     // Release already allocated resources
     AF_FreeAdapterEntry ( pAdapter );
     NdisReleaseSpinLock ( &g_OpenAdapterLock );
     return;
    }
  // Pointer to our OPEN_BLOCK (for convnience)
    pOpenBlock = (PNDIS_OPEN_BLOCK)*NdisBindingHandle;
  pAdapter->m_MacBindingHandle = pOpenBlock->MacBindingHandle;
  // Substitute Some Real handlers by our version
    pAdapter->m_SendHandler = pOpenBlock->SendHandler;
    pOpenBlock->SendHandler = OBF_SendHandler;
  pAdapter->m_SendPacketsHandler = pOpenBlock->SendPacketsHandler;
    pOpenBlock->SendPacketsHandler = OBF_SendPacketsHandler;
  pAdapter->m_RequestHandler = pOpenBlock->RequestHandler;
    pOpenBlock->RequestHandler = OBF_RequestHandler;
  pAdapter->m_TransferDataHandler = pOpenBlock->TransferDataHandler;
 

What do we have by now?

Thus, the driver with minimum functionality turns out small enough and not, but may appear a bit complex in implementation. Installation of it does not make the problem as well as in a case with the driver for a Windows 9x/ME and require adding of several keys in the registry.

Besides the described static way of hooking NDIS library functions which demands loading the driver at a stage of the start of the operating system and as such this driver cannot be unloaded from memory, there is one more dynamic approach. This method works both under a Windows 9x/ME and under NT/2k/XP and though in the first case it is necessary to write VxD, and in the second kernel-mode the driver, a principle is identical. Among products existing in the market, it is applied (besides control TDI and all started processes in the system) in rather known firewall ZoneAlarm (with widely recognized TrueVector technology). The essence of the approach that we register the dummy protocol (call NdisRegisterProtocol) i.e., which handlers do nothing, most complex of them can be ProtocolReceive, which returns NDIS_STATUS_NOT_ACCEPTED. This protocol is one simple purpose to receive NdisProtocolHandle. Actually, it is the pointer to internal NDIS structure NDIS_PROTOCOL_BLOCK, which, unfortunately, differs from one NDIS version to another and not always defined in ndis.h. And so in addition, this structure comprises NDIS_PROTOCOL_CHARACTERISTICS with addresses of all ProtocolXXX functions, NDIS_OPEN_BLOCK list (this structure in turn contains handlers Send/SendPackets/Request) all network interfaces bound to the given protocol and the pointer to next structure NDIS_PROTOCOL_BLOCK. The further is practically obvious, moving the list of registered protocols; we substitute the handlers where it is necessary. However, despite apparent simplicity, this method is not simple and also demands the big care as we interfere with the functionality of already working system.

Conclusion

So, we superficially considered the majority of approaches to the firewall creation for the Windows platform, and the variant with interception NDIS was reviewed, as it seems to me, rather in details. As far as this is the only method that guarantees a complete control over network traffic, watching all registering protocols, it seems to be the best way for network security relative projects. I did not try to teach you to write drivers in this small article, nonspecialists can address to DDK and learn about the features of the Windows network subsystem. I can’t say that this is easy to read material, but authors of books for some reason usually practically do not give NDIS attention, so this about the only source of information.

Leave a Reply

Your email address will not be published. Required fields are marked *