NDIS中间层驱动包截获技术摘要:简要概括了NDIS的概念,阐述了NDIS的工作流程,详细说明了如何编写NDIS中间层驱动程序以获得网络封包的详细信息。并且给出了一些代表性的示例代码,供读者参考。一.NDIS驱动模型简介NDIS(NetworkDriverInterfaceSpecification)是网络驱动程序接口规范的简称。它横跨传输层、网络层和数据链路层,定义了网卡或网卡驱动程序与上层协议驱动程序之间的通信接口规范,屏蔽了底层物理硬件的不同,使上层的协议驱动程序可以和底层任何型号的网卡通信。NDIS为网络驱动程序创建了一个完整的开发环境,只需调用NDIS函数,而不用考虑操作系统的内核以及与其他驱动程序的接口问题,从而使得网络驱动程序可以从与操作系统的复杂通讯中分离,极大地方便了网络驱动程序的编写。另外,利用NDIS的封装特性,可以专注于一层驱动的设计,减少了设计的复杂性,同时易于扩展驱动程序栈。防火墙的开发一般采用的是中间驱动程序。通过NDIS中间层驱动,我们可以截获来自网卡的所有原始数据包。图1则是NDIS中间层驱动的工作过程图图1NDIS中间层驱动程序是工作在MINIPROT和PROTOCOL接口之间的,驱动程序必须向下导出一个PROTOCOL接口,向上导出一个MINIPORT接口。将自己创建的驱动程序插入到网卡驱动程序与传输驱动程序之间。如此一来,当下层的网卡驱动程序接收到数据后会通过MINIPORT接口发送到我们导出的PROTOCOL接口上,NDIS中间层驱动程序便接收到了来自网卡的数据并调用我们准备好的回调函数处理数据包信息。接着NDIS中间层驱动在处理数据包完毕后再继续把数据通过导出的MINIPROT接口向PROTOCOL接口发送。这样就完成了一个截获数据包的过程。二.NDIS中间层驱动的工作流程在开始学习NDIS中间层驱动之前,我们有必要了解下NDIS是怎样工作的。当然这就包括了它的接收数据包的流程了。那么我们来看看NDIS接收数据包流程到底是怎样的:1.低层的网卡驱动调用NdisMIndicateReceive或者NdisMEthIndicateReceive函数通知上一层它们已经收到数据。2.接着系统调用我们自定义的PtReceive或者PtReceivePacket函数,到底系统会调用哪个函数跟机器的网卡有关。接着在函数中调用NdisGetReceivedPacket函数接受低层传上来的数据,如果我们得到了一个完整的packet包,我们就申请一个缓冲区存放下层传上来的数据,接着调用NdisMIndicateReceivePacket通知上层设备。如果此时MyPacket的status是NDIS_STATUS_RESOURCES,我们就在本函数中释放我们分配的缓冲区;否则我们在上层发送4的时候,在MPReturnPacket中释放该缓冲区。3.如果在PtReceive或者PtReceivePacket函数中无法得到一个完整的packet,那么就调用NdisMEthIndicateReceive等函数通知系统。4.当上层设备得到了一个完整的数据并且处理完毕以后,它会调用NdisReturnPacket,然后NDIS会调用我们的MPReturnPacket。如果申请的缓冲区没释放,则在MPReturnPacket函数中释放该缓冲区。然后同样的向下层调用NdisReturnPacket。下层会释放他们自己申请的缓冲区。5.如果3发生,那么系统会调用我们的PtReceiveComplete函数。在PtReceiveComplete函数中我们应该调用NdisMEthIndicateReceiveComplete,通知系统我们收到了完整的数据。6.当上层协议驱动得知底层已经收到了完整的数据报文以后,可能会调用NdisTransferData,要求下层把剩余的数据传上来。然后系统调用我们的MPTransferData例程。在MPTransferData中,调用NdisTransferData。必须注意的是该函数的返回值:如果返回success,说明剩余的数据立刻就传上来了。此时会立即返回。7步骤就不会调用;如果返回pending,表明底层在此阻塞,底层会在稍后的时候调用7。7.当底层miniport驱动做好了一个完整的packet,它会调用NdisTransferDataComplete。同样的,系统会调用我们的PtTransferDataComplete函数。这样,整个接收数据的流程就结束了。图2通过流程图我们可以知道在PtReceive或者PtReceivePacket中可以得到我们所希望的数据,然后在以上2个函数中加入我们自己的处理代码,就可以达到截获数据并进行相应处理的目的了。三.在驱动程序中导出接口我们首先必须在我们的驱动程序中向系统注册导出我们的虚拟接口。这些工作将在DriverEntry函数中完成,代码如下:复制代码1NDIS_STATUSStatus;2NDIS_PROTOCOL_CHARACTERISTICSPChars;//保存有关导出PROTOCOL接口的回调函数地址的结构3NDIS_MINIPORT_CHARACTERISTICSMChars;//保存有关导出MINIPORT接口的回调函数地址的结构4PNDIS_CONFIGURATION_PARAMETERParam;5NDIS_STRINGName;6NdisMInitializeWrapper(&NdisWrapperHandle,DriverObject,RegistryPath,NULL);//初始化NdisWrapperHandle7…………//设置其他的回调函数8MChars.SendPacketsHandler=MPSendPackets;//设置发送数据包的回调函数9//向NDIS注册我们的MINIPORT接口10Status=NdisIMRegisterLayeredMiniport(NdisWrapperHandle,11&MChars,12sizeof(MChars),13&DriverHandle);14…………//设置其他的回调函数15PChars.ReceivePacketHandler=PtReceivePacket;//设置接收数据包的回调函数16//向NDIS注册我们的MINIPORT接口17NdisRegisterProtocol(&Status,18&ProtHandle,19&PChars,20sizeof(NDIS_PROTOCOL_CHARACTERISTICS));21//通知NDIS生成我们所注册的2个接口22NdisIMAssociateMiniport(DriverHandle,ProtHandle);23…………24//theend如此一来,我们的驱动程序可以看成是工作在网卡层与协议层之间了,当底层网卡有数据到来时会先经过我们的驱动程序处理后再往上层设备发送的。那么我们就可以在自己的回调函数中处理来自网络的数据了。四.回调函数的工作在我们向系统注册的回调函数中,比较重要的就是PtReceive和PtReceivePacket函数了。为了程序的通用性,2个回调函数的大致处理流程是一样的。我们仅拿PtReceive函数来做例子。PtReceive函数的原型如下:复制代码25NDIS_STATUS26PtReceive(27INNDIS_HANDLEProtocolBindingContext,28INNDIS_HANDLEMacReceiveContext,29INPVOIDHeaderBuffer,//以太头数据30INUINTHeaderBufferSize,//以太头数据大小31INPVOIDLookAheadBuffer,//数据体部分32INUINTLookAheadBufferSize,//LookAheadBuffer数据大小33INUINTPacketSize//数据包大小34);在该函数中,第三个参数的指向帧头的起始缓冲区,第五个参数指向数据体的起始缓冲区,第七个参数的值为缓冲区大小。如果PacketSize大于LookAheadBufferSize,表明数据还未全部拷贝上来。如果这2个参数相等,那么说明数据全部在LookAheadBuffer变量指向的缓冲区内。让我们来看看下面的代码:复制代码35PADAPTpAdapt=(PADAPT)ProtocolBindingContext;36PNDIS_PACKETMyPacket,Packet;37NDIS_STATUSStatus=NDIS_STATUS_SUCCESS,DataStatus;38if(!pAdapt-MiniportHandle)39{40Status=NDIS_STATUS_FAILURE;41}42elsedo43{44if(pAdapt-isSecondary)45ASSERT(0);46//从下层驱动获取数据包47Packet=NdisGetReceivedPacket(pAdapt-BindingHandle,MacReceiveContext);48if(Packet!=NULL)49{50//如果数据包不为空那么就为下层即将51//发送上来的数据包分配空间52NdisDprAllocatePacket(&Status53,&MyPacket,pAdapt-RecvPacketPoolHandle);54if(Status==NDIS_STATUS_SUCCESS)55{56//拷贝原下层数据包到我们分配的缓冲中57MyPacket-Private.Head=Packet-Private.Head;58MyPacket-Private.Tail=Packet-Private.Tail;59NDIS_SET_ORIGINAL_PACKET(60MyPacket,NDIS_GET_ORIGINAL_PACKET(Packet));61NDIS_SET_PACKET_HEADER_SIZE(MyPacket,HeaderBufferSize);62NdisGetPacketFlags(MyPacket)=NdisGetPacketFlags(Packet);63NDIS_SET_PACKET_STATUS(MyPacket,NDIS_STATUS_RESOURCES);64ASSERT(NDIS_GET_PACKET_STATUS(MyPacket)==NDIS_STATUS_RESOURCES);65//拷贝数据包完成66//我们自己的数据包分析处理函数67PacketAnalysis(MyPacket);68//我们自己的处理代码69…………70//通知NDIS我们已复制数据包到缓冲区中71NdisMIndicateReceivePacket(pAdapt-MiniportHandle,&MyPacket,1);72//释放数据包73NdisDprFreePacket(MyPacket);74break;75}76}77…………78}while(FALSE);在PtReceive函数中我们要做的就是为从下层传上来的数据分配缓冲区,然后将收到的数据拷贝到我们分配的缓冲区中,接着调用NdisMIndicateReceivePacket函数将数据传给上一层。PacketAnalysis函数就是我们的包分析函数,在该函数中我们就可以对传来的数据进行处理,过滤和拦截了。五.数据包的分析与处理在以上代码中,其实在MyPacket这个结构中就储存了我们说希望得到的数据包地址,但是如何得到数据呢?我们在得到数据的过程中需要了解NDIS_PACKET和NDIS_BUFFER这两个结构。下面给出这2个结构的定义:复制代码79//80//NDIS_PACKET结构的定义81//82typedefstruct_NDIS_PACKET83{84NDIS_PACKET_PRIVATEPr