N-Byte網絡守望者是一款單機版網絡安全工具,簡言之,就是一個用.NET開發的個人版防火牆。在N-Byte網絡守望者1.0版的開發中,使用了NDIS Hook Driver技術來實現網絡封包過濾功能,這使N-Byte網絡守望者能夠在網絡層過濾網絡封包,從而實現強大的功能。
由於軟件的主程序是用C#寫的,C#中沒有提供具有類似DeviceIoControl函數功能的驅動設備控制函數,而NDIS Hook Driver技術下的驅動程序是用DDK下的C語言寫的,為了能夠實現主程序對驅動程序的控制和相互通信,采用了以下設計方案:
在以上方案中,需要一個負責主程序與NDIS Hook Driver驅動程序通信與控制的模塊DriverDll.dll,並用C#編寫的一個封裝驅動程序中封包信息的模塊,可以發送這個驅動程序信息到主程序,主程序可識別並操作模塊中的數據類型。
在.NET應用程序使用驅動程序的問題上,面臨著兩個問題:
1.怎樣實現.NET應用程序控制驅動程序的功能?
2.怎樣從驅動程序向.NET應用程序傳遞非托管的數據類型?
以下是我們就這些問題的詳細解決方法:
怎樣實現.NET應用程序控制驅動程序的功能?
使用托管C++編寫的DriverDll.dll來實現對驅動程序的直接控制,而主程序通過調用其中的方法來實現對驅動程序的間接控制。比如在NByte.h文件中定義了START_IP_HOOK常數用來作為傳給驅動程序用來開啟驅動程序封包過濾功能的參數,下面在托管C++模塊中定義了IoCtrl托管類並定義了下面的向緩沖區寫入參數的方法:
//向緩沖區寫入數據。 DWORD WriteIo(DWORD code,PVOID buffer,DWORD count) { if(hDriverHandle == NULL) return ERROR_DRIVER_HANDLE; DWORD bytesReturned; BOOL returnCode = DeviceIoControl(hDriverHandle, code, buffer, count, NULL, 0, &bytesReturned, NULL); if(!returnCode) return ERROR_IO_CTRL; return SUCCESS; } 當然直接使用這個方法不太方便,所以定義一個公有函數,用來提供給主程序調用: //開始進行封包過濾 bool StartIpHook() { return (WriteIo(START_IP_HOOK, NULL, 0)==SUCCESS); }
這樣,只要在主程序中聲明IoCtrl的對象ic,就可以通過ic.StartIpHook()就可以實現對驅動程序過濾功能的開啟,用同樣的方法也可以實現對驅動程序進行其它操作,比如添加、修改封包過濾規則等。
怎樣從驅動程序向.NET應用程序傳遞非托管的數據類型?
為了能夠輸出安全日志,必須讓主程序獲得驅動程序中的封包信息。使用信號量機制可以很方便的實現驅動程序和非托管代碼間的信息傳遞,那麼對托管代碼呢?這需要向.NET應用程序傳遞非托管的數據類型ACCESS_INFO。在NByte.h中,是這樣定義這個ACCESS_INFO結構的:
typedef struct _ACCESS_INFO { USHORT protocol; ULONG sourceIp; ULONG destinationIp; USHORT sourcePort; USHORT destinationPort; }ACCESS_INFO;
顯然,直接傳遞非托管數據類型是不可以的,需要轉換一下。首先,在IoCtrl類中定義了幾個要傳遞的封包信息參數:
public __gc class IoCtrl { public: USHORT protocol; //網際協議類型 ULONG sourceIp; //源IP地址 ULONG destinationIp; //目的IP地址 USHORT sourcePort; //源端口 USHORT destinationPort; //目的端口 ……………… }
然後,在GetAccessInfo()函數中來給這些參數賦值:
void GetAccessInfo() { ACCESS_INFO ai; bool result=(ReadIo(GET_INFO,&ai,sizeof(ai))==SUCCESS); this->protocol=ai.protocol; this->sourceIp=ai.sourceIp; this->destinationIp=ai.destinationIp; this->sourcePort=ai.sourcePort; this->destinationPort=ai.destinationPort; }
既然在IoCtrl類中獲得了這些信息,但是需要把它們封裝成主程序容易處理的數據類型,這樣,用C#實現了InfoEvent類用來封裝這些信息:
//本類封裝了數據包的詳細信息,可以通過事件實現對它的模塊間傳遞。
public class InfoEvent:EventArgs { string sInfo; //用來存放輸出信息的私有成員 public int pLength; //CommonFunction.sPort數組的長度 public ushort protocol; //網絡通信協議類型 public uint sourceIp; //數據包的源IP public uint destinationIp; //數據包的目的IP public ushort sourcePort; //數據包的源端口 public ushort destinationPort; //數據包的目的端口 ……………………………… }
下面在用托管C++實現的InfoProvider驅動程序信息提供者類中把個InfoEvent類的對象傳遞給主程序,需要使用一個委托生成一個事件:
//聲明委托事件,用來向主程序傳遞數據。 __delegate void DriverInfo(Object* sender, InfoEvent* e); //聲明響應事件函數。 __event DriverInfo* OnDriverInfo;
然後在InfoProvider驅動程序信息提供者類中定義一個方法,在主程序中以線程的方式運行這個方法,在這個方法中使用了事件函數OnDriverInfo:
//用來獲得驅動程序信息的進程,在主程序中將開啟該進程。 void GetInfoThreadProc() { this->hEvent=OpenEvent(SYNCHRONIZE,FALSE,"NBEvent"); if(!ic->GetDriverHandle()) { return; } while(true) { f(!hEvent) ExitThread(0); WaitForSingleObject(this->hEvent,INFINITE); nPackets++; ic->GetAccessInfo(); ic->ResetEvent(); //定義一個主程序可以識別的對象,通過OnDriverInfo傳給主程序。 InfoEvent*ie=new InfoEvent(ic->protocol,ic->sourceIp,ic->destinationIp,ic->sourcePort,ic->destinationPort); OnDriverInfo(this,ie); } ic->CloseDriverHandle(); return; }
在主程序中,會開啟這個進程並定義了OnDriverInfo的處理函數DealWithInfo:
pInfo=new InfoProvider(); //開啟與驅動交換信息的進程 FilterThread=new Thread(new ThreadStart(pInfo.GetInfoThreadProc)); FilterThread.IsBackground=true; FilterThread.Start(); pInfo.OnDriverInfo+=new InfoProvider.DriverInfo(DealWithInfo);
這樣主程序就可以在DealWithInfo函數中加入對InfoEvent對象的處理了。可見,通過中間模塊IoCtrl的轉換,便實現了.NET主程序對驅動程序中非托管數據類型的獲取和處理。