1)用户态(user-mode)。
在用户态下进行网络数据包的拦截有三种方法:WinsockLayeredServiceProvider(LSP)、Windows2000包过滤接口、替换系统自带的WINSOCK动态连接库。在用户态下进行数据包拦截最致命的缺点就是只能在Winsock层次上进行,而对于网络协议栈中底层协议的数据包无法进行处理。因此,这些方法并不适合个人防火墙。
2)内核态(kernel-mode)。
a)TDI过滤驱动程序(TDIFilterDriver)。
当应用程序要发送或接收网络数据包的时候,都是通过与协议驱动所提供的接口来进行的。协议驱动提供了一套系统预定义的标准接口来和应用程序之间进行交互。因此,只需要开发一个过滤驱动来截获这些交互的接口,就可以实现网络数据包的拦截。当应用程序要发送或接收网络数据包的时候,都是通过与协议驱动所提供的接口来进行的。协议驱动提供了一套系统预定义的标准接口来和应用程序之间进行交互。在Windows2000/NT下,ip,tcp,udp是在一个驱动程序里实现的,叫做tcp.sys,这个驱动程序创建了5个设备:DeviceRawIp,DeviceUdp,DeviceTcp,DeviceIp,DeviceMULTICAST。应用程序所有的网络数据操作都是通过这些设备进行的。因此,我们只需要开发一个过滤驱动来截获这些交互的接口,就可以实现网络数据包的拦截。另外,TDI层的网络数据拦截还可以得到操作网络数据包的进程详细信息,这也是个人防火墙的一个重要功能。但是,TDI传输驱动程序有一个缺陷,TDIFilterdriver属于Upperdriver,位于TcpIP.sys之上,这就意味着由TcpIP.sys接收并处理的数据包不会传送到上层,从而无法过滤某些接收的数据包,例如ICMP包。ICMP的应答包直接由TcpIP.sys生成并回应,而上面的过滤驱动程序全然不知。另外,该方法需要在系统核心层编写驱动程序,需要编写人员对Windows操作核心层的工作机制非常熟悉,同时,驱动程序对代码质量要求非常高,稍有不慎就会使系统崩溃,
b)Win2kFilter-HookDriver。这是从Windows2000开始系统所提供的一种驱动程序,该驱动程序主要是利用Ipfiltdrv.sys所提供的功能来拦截网络数据包。Filter-HookDriver的结构非常简单,易于实现。但是正因为其结构过于简单,并且依赖于Ipfiltdrv.sys,Microsoft并不推荐使用Filter-HookDriver。
c)NDISHookDriver。该方法在Windows2000/xp下是非公开的,因此这种方法对平台的依赖性比较大,需要在程序中判断不同的操作系统版本而使用不同的方法。
d)NDIS中间层驱动程序(NDISIntermediateDriver)。NDIS(NetworkDriverInterfaceSpecification)是Microsoft和3Com公司开发的网络驱动程序接口规范的简称,它支持如下三种类型的网络驱动程序:微端口驱动程序、中间层驱动程序(IntermediateDriver)和协议驱动程序。其中中间层驱动介于协议层驱动和小端口驱动之间,其功能非常强大,可以提供多种服务,能够截获所有的网络数据包(以太帧),过滤微端口驱动程序,实现特定的协议或其他诸如数据包加密、认证等功能。综上所述,在NDIS中间层进行网络数据包截获的方法结构规范,功能强大,该技术极其适合个人防火墙所采用。
中间层驱动程序(NDIS)的内部结构
NDIS支持3种驱动:微端口驱动,中间层驱动和协议驱动。
1) 微端口驱动。就是网卡驱动,它负责管理网卡,包括通过网卡发送和接受数据,它也为上层驱动提供接口。
2) 中间层驱动。它通常位于微端口驱动和传输协议驱动之间,是基于链路层和网络层之间的驱动,由于中间层驱动在驱动层次中的中间层位置,它必须与其上层的协议和下层的微端口驱动通信,并且导出两种协议的函数。虽然中间层驱动导出MINIPORTXX函数,但它并不真正的管理物理网卡,而是导出一个或者多个虚拟适配器,上层协议可以绑定到上面。对于协议驱动来说,中间层导出的虚拟适配器看起来像一个物理网卡,当它向这个虚拟适配器发送封包或者请求时,中间层驱动将这些封包和请求传播到下层微端口驱动;当下层微端口驱动向上指示接收封包或者状态时,中间层驱动向上到绑定虚拟适配器上的协议驱动。中间层驱动的主要作用就是过滤封包,其优点是能够截获所有的网络数据包。
3) 协议驱动,即网络协议。它位于NDIS体系的最高层,经常用作实现传输协议堆栈的传输驱动中的最底层驱动。传输协议驱动申请封包,从发送应用程序将数据复制到封包中,通过调用NDIS函数将这些封包发送到下层驱动。协议驱动也是提供了一个协议接口来接收来自下层驱动的封包。传输协议驱动将收到的封包传递给相应的客户应用程序。在下层,协议驱动与中间层微端口驱动交互。协议驱动调用NDISXX函数发送封包,读取和设置下层驱动维护的信息,使用操作系统服务。协议驱动也要导出一系列的入口点,NDIS调用它来指示封包的接受,指示下层驱动的状态,或者是和其他协议驱动的通信。
中间层内部的工作流程
1) 中间层对数据包的管理
中间层驱动程序从高层驱动程序接收数据包描述符,并在网络上发送,该包描述符与一个或多个链式数据缓冲区相关联。中间层驱动程序能够对数据进行重新打包,并使用新的数据包描述符进行数据传输,也可以直接将数据包传递给低层驱动程序,如果驱动程序下边界面向无连接,可调用NdisSend或NdisSendPackets函数完成该功能,如果驱动程序下边界是面向连接的,可调用NdisCoSendPackets函数完成此项功能。中间层驱动程序也可以进行一些操作改变链式缓冲区的内容,或者调整内入数据包相对于其他发送任务的发送次序或发送定时。但是,即使中间层驱动程序只是向下层传递上层引入的数据报,例如,仅仅只是对数据包进行计数,也必须分配新的数据包描述符,并且要管理部分或者全部新的包结构。
每一个中间层驱动程序都必须分配自己的包描述符来代替高层的数据包描述符。如果中间层驱动程序要把数据包从一种格式转化为另一种格式,也必须分配缓冲区描述符来映射用于复制转配数据的缓冲区,该缓冲区由中间层驱动程序进行分配。如果有与复制的包描述符相关的OOB数据,那么可以将这些数据复制到与包描述符(中间层驱动程序分配的)相关的新OOB数据块,其过程是,首先,用NDIS_OOB_DATA_FROM_PACKET宏获取OOB数据区的指针,然后,调用disMoveMemory将其内容移入与新包描述符相关的OOB数据区。该驱动程序也能够用NDIS_GET_PACKET_XXX或NDIS_SET_PACKET_XXX宏从与老的包描述符相关的OOB数据区中,读取相关的内容,并写入与新包描述符相关的OOB数据区。
包描述符通过调用以下NDIS函数进行分配
a)调用NdisAllocatePacketPool或者NdisAllocatePacketPoolEx,为固定尺寸包描述符(由呼叫器指定数量)分配并初始化一组非可分页池。
b)调用NdisAllocatePacket函数,从NdisAllocatePacketPool(Ex)已经分配的池中分配包描述符。根据中间层驱动程序目的的不同,驱动程序能够对引入包描述符连接的缓冲区进行重新打包。例如,中间层驱动程序可以在接下来的情况下分配包缓冲池、对引入包数据重新打包.如果中间层驱动程序从高层协议驱动程序接收到的数据缓冲区,比低层介质能够发送的单个缓冲区更大,那么中间层驱动程序必须将引入的数据缓冲分割成更小的、满足低层发送要求的数据缓冲。中间层驱动程序在将发送任务转交低层驱动程序之前,可以通过压缩或加密数据方式来改变内入数据包的长度。调用以下NDIS函数分配上面所要求的缓冲区:
NdisAllocateBufferPool获取用于分配缓冲区描述符的句柄;
NdisAllocateMemory或NdisAllocateMemoryWithTag分配缓冲区;
c)调用NdisAllocateBuffer分配和设置缓冲区描述符,映射由NdisAllocateMemory(WithTag)分配的缓冲区,并链接到NdisAllocatePacket分配的包描述符上。驱动程序可以通过调用NdisChainBufferAtBack或NdisChainBufferAtFront函数,将缓冲区描述符和包描述符进行链接。调用NdisAllocateMemory(WithTag)返回的虚拟地址和缓冲区长度,将被传递给NdisAllocateBuffer函数来初始化其所映射的缓冲区描述符。符合典型要求的包描述符能够在驱动程序初始化时根据要求进行分配,也可以通过ProtocolBindAdapter函数调用来实现。如果必要或者出于性能方面的考虑,中间层驱动程序开发者可以在初始化阶段,分配一定数量的包描述符和由缓冲区描述符映射的缓冲区,这样,就为ProtocolReceive复制内入数据(将向高层驱动程序指示)预先分配了资源,也为MiniportSend或MiniportSendPackets向相邻低层驱动程序传递引入的发送数据包,准备了可用的描述符和缓冲区。如果在中间层驱动程序复制接收/发送数据到一个或多个缓冲区时,最末的一个缓冲的实际数据长度比缓冲区的长度小,那么,中间层驱动程序将调用NdisAdjustBufferLength把该缓冲区描述符调节到数据的实际长度。当该包返回到中间层驱动程序时,应再次调用该函数将其长度调节到完整缓冲区的实际尺寸。
2)下边界面向无连接的中间层驱动程序的工作流程
通过ProtocolReceivePacket函数,从低层NIC驱动程序以完整数据包形式接收内入数据,该数据包由NDIS_PACKET类型的包描述符指定,也能够通过将内入数据指示给ProtocolReceive函数,并将数据复制到中间层驱动程序提供的数据包中。下边界面向连接的中间层驱动程序总是用ProtocolCoReceivePacket函数,从低层NIC驱动程序接收数据作为一个完整的数据包。
在如下情况下,中间层驱动程序能够保持对接收数据包的所有权:当下边界面向无连接的中间层驱动程序向ProtocolReceivePacket函数指示完整数据包时,当下边界面向连接的中间层驱动程序向ProtocolCoReceivePacket函数指示数据包时,其中DIS_PACKET_OOB_DATA的Status成员设置为除NDIS_STATUS_RESOURCES以外的任何值。在这些情况下,中间层驱动程序能够保持对该包描述符和其所描述的资源的所有权,直到所接收数据处理完毕,并调用NdisReturnPackets函数将这些资源返还给低层驱动程序为止。如果ProtocolReceivePacket向高层驱动程序传递其所接收的资源,那么至少应该用中间层驱动程序已经分配的包描述符替代引入包描述符。根据中间层驱动程序目的的不同,当其从低层驱动程序接收完整数据包时,将有几种不同的包管理策略。例如,以下是几种可能的包管理策略:复制缓冲区内容到中间层驱动程序分配的缓冲区中,该缓冲区被映射并链接到一个新的包描述符,向低层驱动程序返回该输入包描述符,然后可以向高层驱动程序指示新的数据包;创建新的包描述符,将缓冲区(与被指示包描述符相关联)链接到新的包描述符,然后将新的包描述符指示给高层驱动程序。当高层驱动程序返回包描述符时,中间层驱动程序必须拆除缓冲区与包描述符间的链接,并将这些缓冲区链接到最初从低层驱动程序接收到的包描述符,最后向低层驱动程序返还最初的包描述符及其所描述的资源。即使下边界面向无连接的中间层驱动程序支持ProtocolReceivePacket函数,它也提供ProtocolReceive函数。当低层驱动程序不释放包描述符所指示资源的所有权时,NDIS将调用ProtocolReceive函数,当这类情况出现时,中间层驱动程序必须复制所接收的数据到它自己的缓冲区中。对于下边界面向连接的中间层驱动程序,当低层驱动程序不释放包描述符所指示资源的所有权时,则将数据包的NDIS_PACKET_OOB_DATA的Status成员设为NDIS_STATUS_RESOURCES,然后驱动程序的ProtocolCoReceivePacket函数必须将接收到数据复制到自己的缓冲区中
5) 中间层驱动过滤数据包的原理
NDIS中间层驱动程序在NDIS中起着转发上层驱动程序送来的数据包,并将其向下层驱动程序发送的接口功能。当中间层驱动程序从下层驱动程序接收到数据包时,它要么调用NdisMXxxIndicateReceive函数,要么调用NdisMindicateReceivePacket函数向上层指示该数据包中间层驱动程序通过调用NDIS打开和建立一个对低层NIC驱动程序或者NDIS中间层驱动程序的绑定。中间层驱动程序提供MiniportSetInformation和MiniportQueryInformation函数来处理高层驱动程序的设置和查询请求,某些情况下,可能还要将这些请求向低层NDIS驱动程序进行传递,如果其下边界是面向无连接的可通过调用NidsRequest实现这一功能,如果其下边界是面向连接的则通过调用NidsCoRequest实现该功能。中间层驱动程序通过调用NDIS提供的函数向网络低层NDIS驱动程序发送数据包。例如,下边界面向无连接的中间层驱动程序必须调用NdisSend或NdisSendPackets来发送数据包或者包数组,而在下边界面向连接的情况下就必须调用NdisCoSendPackets来发送包数组数据包。如果中间层驱动程序是基于非NDISNIC驱动程序的,那么在调用中间层驱动程序的MiniportSend或Miniport(Co)SendPackets函数之后,发送接口对NDIS将是不透明的。NDIS提供了一组隐藏低层操作系统细节的NdisXxx函数和宏。例如,中间层驱动程序可以调用NdisMInitializeTimer来创建同步时钟,可以调用NdisInitializeListHead创建链表。中间层驱动程序使用符合NDIS标准的函数,来提高其在支持Win32接口的微软操作系统上的可移植性。
在防火墙的设计中,最核心的部分应该是数据包的过滤。
其他的功能都是建立在数据包过滤的基础之上,如:入侵检测功能和邮件检测功能都是建立在数据包过滤的基础之上。数据包过滤中主要是IP包头的分析,例如:在以太网中,得到的数据报大致是如下结构,以太帧头14个字节,放在PUCHAR结构数组的第0个元素到第13个元素中,其中前六个字节是目的MAC地址,然后六个字节源MAC地址,然后两个字节是协议类型,通常的协议类型有0x080x00->IP,0x080x06->ARP,0x080x35->RARP,所以,可以通过数组的第12个元素和第13个元素来判断协议类型。过滤规则就是在这个基础之上建立。如果要过滤特定协议,只要在相应的字节读取数据,判断是否符合要过滤的规则就可以了,当然实际的过滤规则要复杂的多的多,比如对指定的端口指定的IP的过滤。