NDIS(Network Driver Interface Specification)是網(wǎng)絡(luò)驅(qū)動(dòng)程序接口規(guī)范的簡(jiǎn)稱。它橫跨傳輸層、網(wǎng)絡(luò)層和數(shù)據(jù)鏈路層,定義了網(wǎng)卡或網(wǎng)卡驅(qū)動(dòng)程序與上層協(xié)議驅(qū)動(dòng)程序之間的通信接口規(guī)范,屏蔽了底層物理硬件的不同,使上層的協(xié)議驅(qū)動(dòng)程序可以和底層任何型號(hào)的網(wǎng)卡通信。NDIS為網(wǎng)絡(luò)驅(qū)動(dòng)程序創(chuàng)建了一個(gè)完整的開發(fā)環(huán)境,只需調(diào)用NDIS函數(shù),而不用考慮操作系統(tǒng)的內(nèi)核以及與其他驅(qū)動(dòng)程序的接口問題,從而使得網(wǎng)絡(luò)驅(qū)動(dòng)程序可以從與操作系統(tǒng)的復(fù)雜通訊中分離,極大地方便了網(wǎng)絡(luò)驅(qū)動(dòng)程序的編寫。另外,利用NDIS的封裝特性,可以專注于一層驅(qū)動(dòng)的設(shè)計(jì),減少了設(shè)計(jì)的復(fù)雜性,同時(shí)易于擴(kuò)展驅(qū)動(dòng)程序棧。
NDIS支持的網(wǎng)絡(luò)驅(qū)動(dòng)程序類型:
網(wǎng)卡驅(qū)動(dòng)程序(NIC Drivers):網(wǎng)卡驅(qū)動(dòng)程序是網(wǎng)卡與上層驅(qū)動(dòng)程序通信的接口,它負(fù)責(zé)接收來自上層的數(shù)據(jù)包,或?qū)?shù)據(jù)包發(fā)送到上層相應(yīng)的驅(qū)動(dòng)程序,同時(shí)它還完成處理中斷等工作。
中間驅(qū)動(dòng)程序(Intermediate Protocol Drivers):中間驅(qū)動(dòng)程序位于網(wǎng)卡驅(qū)動(dòng)程序和協(xié)議驅(qū)動(dòng)程序之間,它向上提供小端口(Minport)函數(shù)集,向下提供協(xié)議(protocol)函數(shù)集,因此對(duì)于上層驅(qū)動(dòng)程序而言,它是小端口驅(qū)動(dòng)程序。對(duì)于底層的驅(qū)動(dòng)程序,它是協(xié)議驅(qū)動(dòng)程序。
協(xié)議驅(qū)動(dòng)程序(Upper Level Protocol Drivers):協(xié)議驅(qū)動(dòng)程序執(zhí)行具體的網(wǎng)絡(luò)協(xié)議,如IPX/SPX、TCP/IP等。協(xié)議驅(qū)動(dòng)程序?yàn)閼?yīng)用層客戶程序提供服務(wù),接收來自網(wǎng)卡或中間驅(qū)動(dòng)程序的信息。
防火墻的開發(fā)一般采用的是中間驅(qū)動(dòng)程序。通過NDIS中間層驅(qū)動(dòng),我們可以截獲來自網(wǎng)卡的所有原始數(shù)據(jù)包。圖1則是NDIS中間層驅(qū)動(dòng)的工作過程圖:
NDIS中間層驅(qū)動(dòng)程序是工作在MINIPROT和PROTOCOL接口之間的,驅(qū)動(dòng)程序必須向下導(dǎo)出一個(gè)PROTOCOL接口,向上導(dǎo)出一個(gè)MINIPORT接口。將自己創(chuàng)建的驅(qū)動(dòng)程序插入到網(wǎng)卡驅(qū)動(dòng)程序與傳輸驅(qū)動(dòng)程序之間。如此一來,當(dāng)下層的網(wǎng)卡驅(qū)動(dòng)程序接收到數(shù)據(jù)后會(huì)通過MINIPORT接口發(fā)送到導(dǎo)出的PROTOCOL接口上,NDIS中間層驅(qū)動(dòng)程序便接收到了來自網(wǎng)卡的數(shù)據(jù)并調(diào)用準(zhǔn)備好的回調(diào)函數(shù)處理數(shù)據(jù)包信息。接著NDIS中間層驅(qū)動(dòng)在處理數(shù)據(jù)包完畢后再繼續(xù)把數(shù)據(jù)通過導(dǎo)出的MINIPROT接口向PROTOCOL接口發(fā)送,這樣就完成了一個(gè)截獲數(shù)據(jù)包的過程[1]。
在開始學(xué)習(xí)NDIS中間層驅(qū)動(dòng)之前,我們有必要了解下NDIS是怎樣工作的。當(dāng)然這就包括了它的接收數(shù)據(jù)包的流程了。那么我們來看看NDIS接收數(shù)據(jù)包流程到底是怎樣的:
1.低層的網(wǎng)卡驅(qū)動(dòng)調(diào)用NdisMIndicateReceive或者NdisMEthIndicateReceive函數(shù)通知上一層已經(jīng)它們已經(jīng)收到數(shù)據(jù)。
2.接著系統(tǒng)調(diào)用自定義的PtReceive或者PtReceivePacket函數(shù),到底系統(tǒng)會(huì)調(diào)用哪個(gè)函數(shù)跟機(jī)器的網(wǎng)卡有關(guān)。接著在函數(shù)中調(diào)用NdisGetReceivedPacket函數(shù)接受低層傳上來的數(shù)據(jù),如果我們得到了一個(gè)完整的packet包,我們就申請(qǐng)一個(gè)緩沖區(qū)存放下層傳上來的數(shù)據(jù),接著調(diào)用NdisMIndicateReceivePacket通知上層設(shè)備。如果此時(shí)MyPacket的status是NDIS_STATUS_RESOURCES,我們就在本函數(shù)中釋放我們分配的緩沖區(qū);否則我們?cè)谏蠈影l(fā)送4的時(shí)候,在MPReturnPacket中釋放該緩沖區(qū)。
3.如果在PtReceive或者PtReceivePacket函數(shù)中無法得到一個(gè)完整的packet,那么就調(diào)用NdisMEthIndicateReceive等函數(shù)通知系統(tǒng)。
4.當(dāng)上層設(shè)備得到了一個(gè)完整的數(shù)據(jù)并且處理完畢以后,它會(huì)調(diào)用NdisReturnPacket,然后NDIS會(huì)調(diào)用我們的MPReturnPacket。如果申請(qǐng)的緩沖區(qū)沒釋放,則在MPReturnPacket函數(shù)中釋放該緩沖區(qū)。然后同樣的向下層調(diào)用NdisReturnPacket。下層會(huì)釋放他們自己申請(qǐng)的緩沖區(qū)。
5.如果3發(fā)生,那么系統(tǒng)會(huì)調(diào)用PtReceiveComplete函數(shù)。在PtReceiveComplete函數(shù)中我們應(yīng)該調(diào)用NdisMEthIndicateReceiveComplete,通知系統(tǒng)收到了完整的數(shù)據(jù)。
6.當(dāng)上層協(xié)議驅(qū)動(dòng)得知底層已經(jīng)收到了完整的數(shù)據(jù)報(bào)文以后,可能會(huì)調(diào)用NdisTransferData,要求下層把剩余的數(shù)據(jù)傳上來。然后系統(tǒng)調(diào)用MPTransferData例程。在MPTransferData中,調(diào)用NdisTransferData。必須注意的是該函數(shù)的返回值:如果返回success,說明剩余的數(shù)據(jù)立刻就傳上來了。此時(shí)會(huì)立即返回。7步驟就不會(huì)調(diào)用;如果返回pending,表明底層在此阻塞,底層會(huì)在稍后的時(shí)候調(diào)用7。
7.當(dāng)?shù)讓?/span>miniport驅(qū)動(dòng)做好了一個(gè)完整的packet,它會(huì)調(diào)用NdisTransferDataComplete。同樣的,系統(tǒng)會(huì)調(diào)用我們的PtTransferDataComplete函數(shù)。這樣,整個(gè)接收數(shù)據(jù)的流程就結(jié)束了[2]。
通過流程圖可以知道在PtReceive或者PtReceivePacket中可以得到我們所希望的數(shù)據(jù),然后在以上2個(gè)函數(shù)中加入自己的處理代碼,就可以達(dá)到截獲數(shù)據(jù)并進(jìn)行相應(yīng)處理的目的了。
我們首先必須在驅(qū)動(dòng)程序中向系統(tǒng)注冊(cè)導(dǎo)出虛擬接口。這些工作將在DriverEntry函數(shù)中完成,代碼如下:
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
NDIS_STATUS Status;
NDIS_PROTOCOL_CHARACTERISTICS PChars; //保存有關(guān)導(dǎo)出PROTOCOL接口的回調(diào)函數(shù)地址的結(jié)構(gòu)
NDIS_MINIPORT_CHARACTERISTICS MChars; //保存有關(guān)導(dǎo)出MINIPORT接口的回調(diào)函數(shù)地址的結(jié)構(gòu)
PNDIS_CONFIGURATION_PARAMETER Param;
NDIS_STRING Name;
NdisMInitializeWrapper(&NdisWrapperHandle, DriverObject, RegistryPath, NULL); //初始化NdisWrapperHandle
//設(shè)置其他的回調(diào)函數(shù)
MChars.SendPacketsHandler = MPSendPackets; //設(shè)置發(fā)送數(shù)據(jù)包的回調(diào)函數(shù)
//向NDIS注冊(cè)我們的MINIPORT接口
Status = NdisIMRegisterLayeredMiniport(NdisWrapperHandle,
&MChars,
sizeof(MChars),
&DriverHandle);
PChars.ReceivePacketHandler = PtReceivePacket; //設(shè)置接收數(shù)據(jù)包的回調(diào)函數(shù)
//向NDIS注冊(cè)MINIPORT接口
NdisRegisterProtocol(&Status,
&ProtHandle,
&PChars,
sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
//通知NDIS生成所注冊(cè)的2個(gè)接口
NdisIMAssociateMiniport(DriverHandle, ProtHandle);
}
如此一來,驅(qū)動(dòng)程序可以看成是工作在網(wǎng)卡層與協(xié)議層之間了,當(dāng)?shù)讓泳W(wǎng)卡有數(shù)據(jù)到來時(shí)會(huì)先經(jīng)過驅(qū)動(dòng)程序處理后再往上層設(shè)備發(fā)送的。那么我們就可以在自己的回調(diào)函數(shù)中處理來自網(wǎng)絡(luò)的數(shù)據(jù)了。
在向系統(tǒng)注冊(cè)的回調(diào)函數(shù)中,比較重要的就是PtReceive和PtReceivePacket函數(shù)了。為了程序的通用性,2個(gè)回調(diào)函數(shù)的大致處理流程是一樣的。我們僅拿PtReceive函數(shù)來做例子。PtReceive函數(shù)的原型如下:
NDIS_STATUS
PtReceive(
IN NDIS_HANDLE ProtocolBindingContext,
IN NDIS_HANDLE MacReceiveContext,
IN PVOID HeaderBuffer,
IN UINT HeaderBufferSize,
IN PVOID LookAheadBuffer,
IN UINT LookAheadBufferSize,
IN UINT PacketSize
)
在該函數(shù)中,第三個(gè)參數(shù)的指向幀頭的起始緩沖區(qū),第五個(gè)參數(shù)指向數(shù)據(jù)體的起始緩沖區(qū),第七個(gè)參數(shù)的值為緩沖區(qū)大小。如果PacketSize大于LookAheadBufferSize,表明數(shù)據(jù)還未全部拷貝上來。如果這2個(gè)參數(shù)相等,那么說明數(shù)據(jù)全部在LookAheadBuffer變量指向的緩沖區(qū)內(nèi)。來看看下面的代碼:
NDIS_STATUS
PtReceive(
IN NDIS_HANDLE ProtocolBindingContext,
IN NDIS_HANDLE MacReceiveContext,
IN PVOID HeaderBuffer, //以太頭數(shù)據(jù)
IN UINT HeaderBufferSize, //以太頭數(shù)據(jù)大小
IN PVOID LookAheadBuffer, //數(shù)據(jù)體部分
IN UINT LookAheadBufferSize, //LookAheadBuffer數(shù)據(jù)大小
IN UINT PacketSize //數(shù)據(jù)包大小
)
{
PADAPT pAdapt =(PADAPT)ProtocolBindingContext;
PNDIS_PACKET MyPacket, Packet;
NDIS_STATUS Status = NDIS_STATUS_SUCCESS , DataStatus ;
if(!pAdapt->MiniportHandle)
{
Status = NDIS_STATUS_FAILURE;
}
else do
{
if(pAdapt->isSecondary)
ASSERT(0);
//從下層驅(qū)動(dòng)獲取數(shù)據(jù)包
Packet = NdisGetReceivedPacket(pAdapt->BindingHandle, MacReceiveContext);
if(Packet != NULL)
{
//如果數(shù)據(jù)包不為空那么就為下層即將
//發(fā)送上來的數(shù)據(jù)包分配空間
NdisDprAllocatePacket(&Status
, &MyPacket, pAdapt->RecvPacketPoolHandle);
if(Status == NDIS_STATUS_SUCCESS)
{
//拷貝原下層數(shù)據(jù)包到我們分配的緩沖中
MyPacket->Private.Head = Packet->Private.Head;
MyPacket->Private.Tail = Packet->Private.Tail;
NDIS_SET_ORIGINAL_PACKET(
MyPacket, NDIS_GET_ORIGINAL_PACKET(Packet));
NDIS_SET_PACKET_HEADER_SIZE(MyPacket, HeaderBufferSize);
NdisGetPacketFlags(MyPacket) = NdisGetPacketFlags(Packet);
NDIS_SET_PACKET_STATUS(MyPacket, DIS_STATUS_RESOURCES);
ASSERT(NDIS_GET_PACKET_STATUS(MyPacket) == NDIS_STATUS_RESOURCES);
//拷貝數(shù)據(jù)包完成
//數(shù)據(jù)包分析處理函數(shù)
PacketAnalysis(MyPacket);
//處理代碼
//通知NDIS已復(fù)制數(shù)據(jù)包到緩沖區(qū)中
NdisMIndicateReceivePacket(pAdapt->MiniportHandle, &MyPacket, 1);
//釋放數(shù)據(jù)包
NdisDprFreePacket(MyPacket);
break;
在PtReceive函數(shù)中我們要做的就是為從下層傳上來的數(shù)據(jù)分配緩沖區(qū),然后將收到的數(shù)據(jù)拷貝到分配的緩沖區(qū)中,接著調(diào)用NdisMIndicateReceivePacket函數(shù)將數(shù)據(jù)傳給上一層。PacketAnalysis函數(shù)就是包分析函數(shù),在該函數(shù)中我們就可以對(duì)傳來的數(shù)據(jù)進(jìn)行處理,過濾和攔截了。
在以上代碼中,其實(shí)在MyPacket這個(gè)結(jié)構(gòu)中就儲(chǔ)存了所希望得到的數(shù)據(jù)包地址,但是如何得到數(shù)據(jù)呢?我們?cè)诘玫綌?shù)據(jù)的過程中需要了解NDIS_PACKET和NDIS_BUFFER這兩個(gè)結(jié)構(gòu)。下面給出這兩個(gè)結(jié)構(gòu)的定義:
// NDIS_PACKET結(jié)構(gòu)的定義
typedef struct _NDIS_PACKET
{
NDIS_PACKET_PRIVATE Private;
//這個(gè)其實(shí)是一個(gè)鏈表結(jié)構(gòu),Private.Head指向第一個(gè)鏈表,Private.Tail指向最后一個(gè)
//以下有關(guān)于這個(gè)結(jié)構(gòu)的解釋
union
{
struct // For Connection-less miniports
{
UCHAR MiniportReserved[2*sizeof(PVOID)];
UCHAR WrapperReserved[2*sizeof(PVOID)];
};
struct
{
// For de-serialized miniports. And by implication conn-oriented miniports.
// This is for the send-path only. Packets indicated will use WrapperReserved
// instead of WrapperReservedEx
UCHAR MiniportReservedEx[3*sizeof(PVOID)];
UCHAR WrapperReservedEx[sizeof(PVOID)];
struct
UCHAR MacReserved[4*sizeof(PVOID)];
ULONG_PTR Reserved[2]; // For compatibility with Win95
UCHAR ProtocolReserved[1];
} NDIS_PACKET, *PNDIS_PACKET, **PPNDIS_PACKET;
// NDIS_PACKET_PRIVATE 的定義
typedef struct _NDIS_PACKET_PRIVATE
{
UINT PhysicalCount; // number of physical pages in packet.
UINT TotalLength; // Total amount of data in the packet.
PNDIS_BUFFER Head; // 鏈表指針,指向下一個(gè)
PNDIS_BUFFER Tail; // 鏈表指針,指向前面一個(gè)
// if Head is NULL the chain is empty; Tail doesn\'t have to be NULL also
PNDIS_PACKET_POOL Pool; // so we know where to free it back to
UINT Count;
ULONG Flags;
BOOLEAN ValidCounts;
UCHAR NdisPacketFlags; // See fPACKET_xxx bits below
USHORT NdisPacketOobOffset;
} NDIS_PACKET_PRIVATE, * PNDIS_PACKET_PRIVATE;
//NDIS_BUFFER定義 其實(shí)就是一個(gè)內(nèi)存描述符
typedef struct _NDIS_BUFFER {
struct _NDIS_BUFFER *Next; //指向下一個(gè)節(jié)點(diǎn)的指針
PVOID VirtualAddress; //指向報(bào)文首地址
PNDIS_BUFFER_POOL Pool;
UINT Length; //報(bào)文數(shù)據(jù)長(zhǎng)度
UINT Signature;
} NDIS_BUFFER, * PNDIS_BUFFER;
我們要的數(shù)據(jù)就儲(chǔ)存在NDIS_BUFFER這個(gè)結(jié)構(gòu)中的VirtualAddress成員里面,這個(gè)指針指向數(shù)據(jù)包的首地址。關(guān)系圖如圖3所示:
NDIS_PACKET是一個(gè)描述NDIS_BUFFER鏈表的結(jié)構(gòu),在NDIS_PACKET中的成員Private中有指向第一個(gè)NDIS_BUFFER的指針和指向最后一個(gè)NDIS_BUFFER的指針分別是Private.Head和Private.Tail[3][4]。而NDIS_BUFFER中就記錄了我們數(shù)據(jù)包的地址和下一個(gè)NDIS_BUFFER的地址。操作有很多種方法,但是由于這些結(jié)構(gòu)體本來對(duì)我們是不透明的,所以最安全的方法是用微軟提供的一系列函數(shù)來操作NDIS_PACKET和NDIS_BUFFER。這些函數(shù)都可以在DDK中查得到。
獲取數(shù)據(jù)包內(nèi)容的代碼如下:
NDIS_STATUS status ;
PNDIS_BUFFER NdisBuffer ;
UINT TotalPacketLength = 0 , copysize = 0 , DataOffset = 0 , PhysicalBufferCount , BufferCount ;
PUCHAR mybuffer = NULL ,tembuffer = NULL ;
//假設(shè)這個(gè)是在PtReceive等函數(shù)中得到的PACKET
NdisQueryPacket(packet //先得到第一個(gè)NDISBUFFER的指針
, &PhysicalBufferCount
, &BufferCount
,&NdisBuffer //NdisBuffer就是指向鏈表頭
, &TotalPacketLength
);
其實(shí)也可以直接 NdisBuffer = packet->Private.Head ;就可以取得第一個(gè)BUFFER了
status = NdisAllocateMemory( &mybuffer, 2048, 0, HighestAcceptableMax ); //分配內(nèi)存塊
if( status != NDIS_STATUS_SUCCESS )
return NDIS_STATUS_FAILURE ;
NdisZeroMemory( mybuffer, 2048 ) ;
NdisQueryBufferSafe( //取得NDIS_BUFFER描述符中數(shù)據(jù)的首地址和大小
NdisBuffer,
&tembuffer,
©size,
NormalPagePriority
//將數(shù)據(jù)復(fù)制到內(nèi)存中
NdisMoveMemory(mybuffer, tembuffer, copysize) ;
DataOffset = copysize ;
while(1)
{
也可以這樣操作而不用NdisGetNextBuffer
if(NdisBuffer->Next == packet->Private.Tail )
break ;
NdisBuffer = NdisBuffer->Next ;
if(pmdl == NULL )
break ;
//獲得下一個(gè)NDIS_BUFFER的的指針
NdisGetNextBuffer(NdisBuffer , &NdisBuffer ) ;
如果指針是NULL那么表示到鏈表尾了
if( NdisBuffer == NULL )
break ;
NdisQueryBufferSafe(
NdisBuffer,
&tembuffer,
©size,
NormalPagePriority
) ;
NdisMoveMemory( mybuffer + DataOffset , tembuffer, copysize) ;
DataOffset += copysize ;
//我們要的數(shù)據(jù)就全部都在申請(qǐng)的內(nèi)存mybuffer中,數(shù)據(jù)大小為DataOffset
我們想要的截獲數(shù)據(jù)包的功能就達(dá)到了,如果想要過濾數(shù)據(jù)包,那么就只需要對(duì)數(shù)據(jù)包的內(nèi)容進(jìn)行判斷就可以了。但是需要注意的是mybuffer里面的數(shù)據(jù)為原始數(shù)據(jù)包的數(shù)據(jù),也就是包括了包頭等一系列信息,需要自己分析包頭信息來獲取希望的數(shù)據(jù)。
本文只通過簡(jiǎn)單的一些示例代碼闡述了如何利用驅(qū)動(dòng)來截獲數(shù)據(jù)包的方法。大部分防火墻就是通過該技術(shù)截獲網(wǎng)絡(luò)數(shù)據(jù)并判斷數(shù)據(jù)的合法性實(shí)現(xiàn)保護(hù)的。但是要寫出很具有通用性的代碼還需要更廣泛的知識(shí)作為基礎(chǔ)。在這里僅給大家拋磚引玉,至于關(guān)于NDIS中間驅(qū)動(dòng)更詳細(xì)的信息讀者們可以去參考微軟提供的WDK文檔。
參 考 文 獻(xiàn)
[1] 朱耀輝《Windows防火墻與數(shù)據(jù)封包截獲技術(shù)》 北京.電子工業(yè)出版社
[2] 《關(guān)于passthru的Send/Receive的流程圖》(http://bbs.driverdevelop.com/htm_data/10/0305/40727.html)
[3] 《NDIS_PACKET結(jié)構(gòu)討論[一]》(http://feikoo.bokee.com/viewdiary.10774705.html)
[4] 《NDIS_PACKET結(jié)構(gòu)討論[二]》(http://feikoo.bokee.com/viewdiary.10774711.html)
聯(lián)系客服