開發基於Windows2000/XP的防火牆介紹
如果你決定為Linux系統開發一個防火牆,你將會找到好多相關的資訊及各式免費代碼。但當人們想要
在Windows平台上開發防火牆時可就有點困難了,就那麼可憐的一點點資料資訊,至於免費代碼,幾乎是不
可能的。
所以我決定寫這篇關於在Windows 2000/XP平台上開發一個簡易功能的防火牆文章去協助那些個對這方
面有興趣的人。
背景
微軟公司在它發布的Windows 2000 DDK中,已經包含了一個新的網路驅動類:Filter-Hook Driver。
用它你可以建立過濾所有通訊的介面。
Filter-Hook Driver
正如我剛才所說的,Filter-Hook Driver是在微軟的Windows 2000 DDK中被引入進來。但實際上它並
不是一個新的網路驅動類,它只是一個IP過濾功能的驅動。
事實上Filter-Hook Driver不是一個網路驅動類,它是屬於核心驅模型。在這個Filter-Hook Driver
中我們只實現了一個回呼函數,我們只是在Filter-Hook Driver中註冊這個回呼函數。當我們實現並註冊
好回呼函數後,IP過濾驅動就會在資料包到達和發送的時候調用它。
我們把實現步驟歸納為如下幾步:
1、建立一個Filter-Hook Driver。在這裡你必須建立一個核心模式驅動,你將任選名字,DOS名字以及其
它一些個屬性,不一定是必須的,但我建議你這樣做。
2、如果我們想安裝過濾功能,首先我們必須得到一個IP過濾驅動的指標。這便是第二步。
3、我們已經得到了指標,現在我們能正確的安裝我們的過濾功能函數。我們將發送一個特定的IPR,裡面
將包括我們的過濾函數的指標。
4、過濾包!!!
5、當我們完成過濾後,我們必須登出過濾功能函數。我們可以註冊一個null 指標來替換我們的過濾函數指標
。
哦,就上面五個步驟,看起來似乎很簡單,但是我如何產生一個核心模式驅動呢?如何得到IP過濾驅動的
指標呢?如何…………,稍等一下,我將會解釋上面所有的步驟並列出源碼例子。
建立核心模式驅動
Filter-Hook Driver是一個核心模式驅動,因此我們將要建立一個核心模式的驅動程式。但這篇文章
不是“教你在5分鐘內如何開發核心驅動程式”的指南,所以我將假設讀者已據有以上知識。
Filter-Hook驅動據有一個典型的核心驅動結構。
1、我們將為驅動程式建立一個典型的驅動入口(DriverEntry),為IRP設定標準分發常式(Dispatch, load,
unload, Create...),為方便與應用程式通訊建立一個符號串連。
2、標準分發常式將管理IRP。在你開使編寫代碼前,我建議你先建立IOCTL以便應用程式來操縱驅動。在我
的例子裡,我實現了4個IOCTL編碼:START_IP_HOOK(註冊一個過濾功能函數),STOP_IP_HOOK(登出過濾功
能函數),ADD_FILTER(安裝一個新的過濾規則),CLEAR_FILTER(清空所有過濾規則)
3、為了我們的驅動,我們必須實現一個或更多的過濾功能函數。
我建議你用一個程式來產生典型的核心驅動程式架構,因此你只須在裡面填上你的功能代碼既可,比
如我就用了 QuickSYS 來產生我的例子工程。
你將看到我自己產現的驅動結構,如下代碼所示:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
//....
dprintf(DrvFltIp.SYS: entering DriverEntry/n);
//we have to create the device
RtlInitUnicodeString(&deviceNameUnicodeString, NT_DEVICE_NAME);
ntStatus = IoCreateDevice(DriverObject,
0,
&deviceNameUnicodeString,
FILE_DEVICE_DRVFLTIP,
0,
FALSE,
&deviceObject);
if ( NT_SUCCESS(ntStatus) )
{
// Create a symbolic link that Win32 apps can specify to gain access
// to this driver/device
RtlInitUnicodeString(&deviceLinkUnicodeString, DOS_DEVICE_NAME);
ntStatus = IoCreateSymbolicLink(&deviceLinkUnicodeString,
&deviceNameUnicodeString);
//....
// Create dispatch points for device control, create, close.
DriverObject->MajorFunction[IRP_MJ_CREATE] =
DriverObject->MajorFunction[IRP_MJ_CLOSE] =
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DrvDispatch;
DriverObject->DriverUnload = DrvUnload;
}
if ( !NT_SUCCESS(ntStatus) )
{
dprintf(Error in initialization. Unloading...);
DrvUnload(DriverObject);
}
return ntStatus;
}
NTSTATUS DrvDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
// ....
switch (irpStack->MajorFunction)
{
case IRP_MJ_CREATE:
dprintf(DrvFltIp.SYS: IRP_MJ_CREATE/n);
break;
case IRP_MJ_CLOSE:
dprintf(DrvFltIp.SYS: IRP_MJ_CLOSE/n);
break;
case IRP_MJ_DEVICE_CONTROL:
dprintf(DrvFltIp.SYS: IRP_MJ_DEVICE_CONTROL/n);
ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
switch (ioControlCode)
{
// ioctl code to start filtering
case START_IP_HOOK:
{
SetFilterFunction(cbFilterFunction);
break;
}
// ioctl to stop filtering
case STOP_IP_HOOK:
{
SetFilterFunction(NULL);
break;
}
// ioctl to add a filter rule
case ADD_FILTER:
{
if(inputBufferLength == sizeof(IPFilter))
{
IPFilter *nf;
nf = (IPFilter *)ioBuffer;
AddFilterToList(nf);
}
break;
}
// ioctl to free filter rule list
case CLEAR_FILTER:
{
ClearFilterList();
break;
}
default:
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
dprintf(DrvFltIp.SYS: unknown IRP_MJ_DEVICE_CONTROL/n);
break;
}
break;
}
ntStatus = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
// We never have pending operation so always return the status code.
return ntStatus;
}
VOID DrvUnload(IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING deviceLinkUnicodeString;
dprintf(DrvFltIp.SYS: Unloading/n);
SetFilterFunction(NULL);
// Free any resources
ClearFilterList();
// Delete the symbolic link
RtlInitUnicodeString(&deviceLinkUnicodeString, DOS_DEVICE_NAME);
IoDeleteSymbolicLink(&deviceLinkUnicodeString);
// Delete the device object
IoDeleteDevice(DriverObject->DeviceObject);
}
我們已經完成驅動程式主體代碼,接下來將是過濾鉤子代碼。
註冊過濾功能函數
在上面的代碼中,我們已經看到了調用SetFilterFunction(...)函數,現在我們將實現這個註冊IP過
慮功能函數,他將分以下幾步實現。
1、首先,我們必須得到一個IP過濾驅動的指標,那需要驅動已正確安裝且已經運行起來。現在假設在載入
這個驅動之前我的使用者應用程式將載入並起動IP過濾驅動。
2、我們必須建立一個特定的IRP包含IOCTL_PF_SET_EXTENSION_POINTER的控制碼。我們還得必須傳送參數
如PF_SET_EXTENSION_HOOK_INFO結構來包含過濾函數指標。如果你要卸載函數,你得採取同樣的步驟傳送
一個NULL指標來取代過濾函數。
3、傳送剛建立的IRP到裝置驅動程式。
這裡關於驅動的大問題,就是一次只能安裝一個過濾功能函數。因此如果其它應用程式已安裝了一個,那
麼你的將不能被安裝上。
接下來的代碼我將出示該函數。
NTSTATUS SetFilterFunction
(PacketFilterExtensionPtr filterFunction)
{
NTSTATUS status = STATUS_SUCCESS, waitStatus=STATUS_SUCCESS;
UNICODE_STRING filterName;
PDEVICE_OBJECT ipDeviceObject=NULL;
PFILE_OBJECT ipFileObject=NULL;
PF_SET_EXTENSION_HOOK_INFO filterData;
KEVENT event;
IO_STATUS_BLOCK ioStatus;
PIRP irp;
dprintf(Getting pointer to IpFilterDriver/n);
//first of all, we have to get a pointer to IpFilterDriver Device
RtlInitUnicodeString(&filterName, DD_IPFLTRDRVR_DEVICE_NAME);
status = IoGetDeviceObjectPointer(&filterName,STANDARD_RIGHTS_ALL,
&ipFileObject, &ipDeviceObject);
if(NT_SUCCESS(status))
{
//initialize the struct with functions parameters
filterData.ExtensionPointer = filterFunction;
//we need initialize the event used later by
//the IpFilterDriver to signal us
//when it finished its work
KeInitializeEvent(&event, NotificationEvent, FALSE);
//we build the irp needed to establish fitler function
irp = IoBuildDeviceIoControlRequest(IOCTL_PF_SET_EXTENSION_POINTER,
ipDeviceObject,
if(irp != NULL)
{
// we send the IRP
status = IoCallDriver(ipDeviceObject, irp);
//and finally, we wait for
//acknowledge of IpFilter Driver
if (status == STATUS_PENDING)
{
waitStatus = KeWaitForSingleObject(&event,
Executive, KernelMode, FALSE, NULL);
if (waitStatus != STATUS_SUCCESS )
dprintf(Error waiting for IpFilterDriver response.);
}
status = ioStatus.Status;
if(!NT_SUCCESS(status))
dprintf(Error, IO error with ipFilterDriver/n);
}
else
{
//if we cant allocate the space,
//we return the corresponding code error
status = STATUS_INSUFFICIENT_RESOURCES;
dprintf(Error building IpFilterDriver IRP/n);
}
if(ipFileObject != NULL)
ObDereferenceObject(ipFileObject);
ipFileObject = NULL;
ipDeviceObject = NULL;
}
else
dprintf(Error while getting the pointer/n);
return status;
}
當我們完成處理過濾功能函數後你能看到,我們必須引用檔案對象來擷取裝置驅動指標。當IpFilter
驅動完成IRP處理時我使用了一個事件來通知。
過濾函數
我們明白了如何開發驅動以及如何安裝過濾函數,但我們還不知道過濾函數呢。
早先我已經說過這個函數總是當傳出或發送資料包時被調用。系統跟據它的傳回值來決定如何處理數
據包。
下面是函數的申明
typedef PF_FORWARD_ACTION
(*PacketFilterExtensionPtr)(
// Ip Packet Header
IN unsigned char *PacketHeader,
// Packet. Dont include Header
IN unsigned char *Packet,
// Packet length. Dont Include length of ip header
IN unsigned int PacketLength,
// Index number for the interface adapter
//over which the packet arrived
IN unsigned int RecvInterfaceIndex,
// Index number for the interface adapter
//over which the packet will be transmitted
IN unsigned int SendInterfaceIndex,
//IP address for the interface
//adapter that received the packet
IN IPAddr RecvLinkNextHop,
//IP address for the interface adapter
//that will transmit the packet
IN IPAddr SendLinkNextHop
);
PF_FORWARD_ACTION是一個枚舉類型
PF_FORWARD:指定IP過濾驅動立即將資料包返回到IP棧。對於送到本地的包,IP將被送到棧頂。如果
是要發送到其他機算機,IP包也將會被立即發送走。
PF_DROP:指定IP過濾驅動立即將該資料包從IP棧裡丟棄掉。該IP資料包將會被丟失。
PF_PASS:指定IP過濾驅動過濾該包並返迴響應結果到IP棧。IP過濾驅動如何處理該資料包取決於他對
包過濾API的設定。當過濾鉤子發覺他不能處理該資料包就得交給IP過濾驅動去處理此包時就返回該參數。
雖然DDK的文只介紹了上面三個傳回值,但當你查看pfhook.h檔案時會發現還有另一個供返回的數值。
那就是PF_ICMP_NO_DROP。我猜想這個值與丟棄包相似並提供ICMP包錯誤資訊。
正如你在過濾功能函數的定義中所看到的,包和他的包頭都是以指標方式傳遞的。因此你可以修改包
頭或內容然後再將其傳送走。這在寫比如NAT程式時是非常有用的。如果我們更改其目標地址,那麼資料包
將向你所更改的方向傳送。
在我的實現在,過濾功能函數會與規則鏈表中的每個過濾規則函數所定義的規則進行比對。這個規則
鏈表會在運行時收到START_IP_HOOK控制碼時建立。這個你會在源碼中看到它。
代碼
在我的第一個版本中,我只實現了一個相對簡單的版本,因此好多人都在詢問我是否能協助他們開發
一個更為實用的應用。所以我現在升級了一個相對複雜的版本。在這個新的例子中有一個小的包過濾應用
。在這個新的應用程式中,你可以加入你的一些通用防火牆的規則。
在第一個版本中,這個應用有兩個部分組成。
使用者應用程式:它是一個MFC應用程式來管理各種過濾規則。這個應用程式發送各種過濾規則到應用中
並決定何時開始起動過濾。過濾動作分三步
按照你的需要定一個過濾規則。發送添加或刪除命令來增加或刪除這個過濾規則。
安裝規則。當你定義好一個過濾規則後,按下安裝按扭來發送他到驅動中。
開始過濾。你只需要按下開始按扭就可以開始過濾了。
過濾驅動程式:驅動程式按照使用者應用程式發來的過濾規則來過濾發送和收到的各資料包。
過濾鉤子驅動程式必須和使用者應用程式在同一目錄中。
為什麼要用這種方法來開發防火牆?
當然,它並不是Windows下開發防火牆的唯一方法,還有其它NDIS防火牆、TDI防火牆、WinSock層防火
牆、包過濾API等等從多方法。因此我將說明用過濾鉤子驅動開發防火牆的一些優缺點。
基於過濾鉤子方法來開發防火牆程式有較好的彈性。你可以在進入到IP棧前過濾所有的IP包。當然了
,你不能再過濾更底層的數的資料,例如網卡硬體幀。那麼你需要寫NDIS驅動去過濾它,那你將獲得更好
的靈活性但也更難開發。
基於過濾鉤子方法來開發防火牆程式不失為一個簡單有效方法。安裝及實現防火牆只需幾個簡單的
過程。當然,包過濾API將更簡單,但那樣你將失去更多的靈活性,你再不能隨意更改包內容了。
結論:過濾鉤子驅動算不上開發防火牆的最好方法,但也不是最差的。那麼為什麼不能把它列為開發
商業軟體的一部分呢?
回答是簡單的,但這個驅動算不上最好的,但也不是最差的。我再次提及它只因為過濾鉤子驅動只能
一次安裝一個。我們可以開發一個更好的,它可以任意裝載來為成百上千的應用程式過濾包並不會與其它
過濾程式相衝突。
在微軟體的文檔中,該方法還有另一個缺點。那就是雖然DDK文檔說你可以對所有發送和收到的資料包
為所欲為但那並不是真的。你能任意更改所有收到的資料包,但只能對IP、TCP、UDP以及ICMP包頭時行更
改。我也不知道那是為什嗎?
微軟公司還為WindowsXP系統引入了另一個無限制的防火牆鉤子驅動。它的裝載非常的簡單,但微軟並
不建議你去用它來開發。因為它運行在網路棧的上層,太高了。可能這個驅動將會在下一版本的Windows中
消失。
結論
OK,所有的都講完了。我知道這不是開發防火牆的最佳方法(我已經提及它的優缺點)。但我想這是為
所有對防火牆開發感性趣的朋友一個很好的起點,或是因為看了這篇文章對防火牆開發更感性趣了。
我希望你現已經有些明白了Filter-Hook Driver,並準備開發一個功能更為強大的防火牆了。