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應用程式傳遞非託管的資料類型?
為了能夠輸出安全日誌,必須讓主程式獲得驅動程式中的封包資訊。使用訊號量機制可以很方便的實現驅動程式和Unmanaged 程式碼間的資訊傳遞,那麼對Managed 程式碼呢?這需要向.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主程式對驅動程式中非管理的資料類型的擷取和處理。