在Windows 2000/XP下開發防火牆

來源:互聯網
上載者:User
  • 原文作者:Jesús O.
  • 原文地址:http://www.codeproject.com/internet/drvfltip.asp
  • 翻譯:zerray
  • 下載防火牆程式 - 47.7 Kb
  • 下載原始碼 - 35.2 Kb

介紹

如果你決定在 Linux 下開發一個防火牆,你可以找到很多資料和原始碼,全部免費。但對 Windows 平台下的防火牆感興趣的人們就有些困難了,不僅是找資料難,找免費的原始碼更是個不可能的任務!

所以,我決定寫這片文章來簡述一下在 Windows 2000/XP 下開發防火牆的簡單方法,藉以協助那些對此感興趣的人們。

背景

在 Windows 2000 DDK 中,微軟包含了一種新的網路驅動叫做過濾鉤子驅動( Filter-Hook Driver )。使用它,你可以建立一個函數來過濾所有到達或離開介面的資料包。

因為關於這個主題的文檔很少,並且沒有例子,所以我寫了這篇文章來介紹成功使用它所需的步驟。我希望這篇文章能幫你理解這個簡單的方法。

過濾鉤子驅動

正如我前面所說,過濾鉤子驅動是微軟在 Windows 2000 DDK 中引入的。實際上,它不是一個新的網路驅動類,它只是擴充 IP 過濾驅動(包含在 Windows 2000 及以後的版本中)功能的一種方法。

事實上,過濾鉤子驅動不是網路驅動,而是核心模式驅動。基本的,我們在過濾鉤子驅動中實現一個回呼函數,然後把這個回呼函數與 IP 過濾驅動註冊到一起。這樣做了之後,IP 過濾驅動會在一個資料包被發送或接受時調用我們的回呼函數。那麼……這麼做主要的步驟有哪些呢?

我總結了以下五個步驟:

  1. 建立一個過濾器鉤子驅動。這一步,你必須建立核心模式驅動,選擇一個名字,DOS 名和其它驅動字元,沒什麼特別的要求但我建議你用描述性的名字。
  2. 如果我們要安裝過濾函數,首先必須取得一個指向 IP 過濾驅動的指標。所以,這是第二個步驟。
  3. 我們已經有了指標,現在可以安裝過濾函數了。我們可以通過發送指定的 IRP 來做這件事。在傳遞的“訊息”資料中包含指向過濾函數的指標。
  4. 過濾資料包!!!
  5. 當我們決定停止過濾時,必須登出過濾函數。我們可以通過註冊過濾函數到null 指標來實現登出。

哦,只有五步,而且看起來很簡單,但是……要怎樣建立核心模式驅動呢?怎麼取得指向 IP 過濾驅動的指標?怎麼……是的,請稍等,我現在就來解釋這些步驟:P,展示源碼例子。

建立核心模式驅動

過濾鉤子驅動是核心模式驅動,所以如果我們想做,就得做個核心模式驅動。這篇文章並不是“5分鐘學會怎樣開發核心模式驅動”,所以我假設讀者們已經有了這方面的知識。

過濾鉤子驅動的結構是典型的核心模式驅動結構:

  1. 建立裝置的驅動入口(註:建立裝置是驅動入口的定語),設定標準常式來處理 IRP(指派,載入,卸載,建立……)和建立與其它應用程式通訊的符號串連。
  2. 管理 IRP 的標準常式。在你開始編寫代碼之前,我建議,考慮你要從裝置驅動引出到應用程式哪些 IOCTL。在我的例子中,我實現了四個 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 過濾驅動已經安裝並運行了。我的使用者應用程式,在載入這個驅動之前就載入並啟動了 IP 過濾驅動,來保證這一點。
  2. 第二,我們必須建立一個 IRP 指定 IOCTL_PF_SET_EXTENSION_POINTER 作為 IO 控制碼。我們必須以參數的形式傳遞一個 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;
}

你會發現當我們完成了建立過濾函數的處理,我們必須廢除( de-reference )在我們取得指向裝置驅動的指標時獲得的檔案對象。我用了一個將在 IP 過濾驅動完成對 IRP 的處理時提示的事件。

過濾函數

我們已經看到了如何開發驅動和如何安裝過濾函數,但我們還不知道過濾函數是什麼樣的。

我已經說過了這個函數總是在主機接受或發送一個資料包時被調用。根據這個函數的傳回值,系統來決定對這個包作些什麼。

這個函數的原型必須是這樣的:

typedef  PF_FORWARD_ACTION 
(*PacketFilterExtensionPtr)(
// Ip Packet Header
IN unsigned char *PacketHeader,
// Packet. Don't include Header
IN unsigned char *Packet,
// Packet length. Don't 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 棧返回 forward 響應。對於當地套件,IP 將他們直接入棧。如果包的目的地是另一台電腦並且允許路由,IP 將它們相應的路由。

  • PF_DROP

    指定 IP 過濾驅動立即向 IP 棧返回 drop 響應。IP 應該丟棄資料包。

  • PF_PASS

    指定 IP 過濾驅動過濾包並向 IP 棧返回結果響應。IP 過濾驅動如何處理過濾資料包取決於資料包過濾 API 的設定。

    如果過濾鉤子決定不處理這個包而交給 IP 過濾驅動來過濾這個包,返回這個 pass 響應。

雖然 DDK 文檔只包含了這三個值,但你查看 pfhook.h
(過濾鉤子驅動需要包含)就會看到另一個值。這個值是 PF_ICMP_ON_DROP 。我估計這個值對應的是丟棄包並以一個 ICMP 包反饋錯誤資訊。

正如你在過濾函數的定義中看到的,資料包及它的前序是以指標傳遞的。所以,你可以修改前序或負載然後傳遞該包。這是很有用的,例如做網路位址轉譯( NAT )。如果你改變了目標地址,IP 會路由該包。

在我的實現中,過濾函數將每個包和一個由使用者應用程式引入的規則列表比較。這個列表是用鏈表實現的,由每個 START_IP_HOOK IOCTL 在運行時建立。你可以在My Code中看到這些。

代碼

在這篇文章的第一個版本中包含了一個簡單的例子,但由於有人想讓我幫他開發實際的程式,我把它更新成了一個更複雜的例子。新的例子時一個小的資料包過濾程式。用這個新程式你可以實現你自己的過濾規則,就像你在一些商業防火牆軟體中做的那樣。

在第一個版本中,程式包含兩個組件:

  • 使用者應用程式:一個 MFC 應用程式用來管理過濾規則。這個應用程式發送規則到驅動程式並決定驅動什麼時候開始過濾。過濾資料分三步:

    • 定義你需要的規則。用添加和刪除命令你可以添加或刪除過濾規則。
    • 安裝規則。當你定義好了規則,點擊安裝按鈕,發送它們到驅動程式。
    • 開始過濾。你只需要點擊開始按鈕來開始過濾。
  • 過濾鉤子驅動:根據由使用者應用程式接收到的過濾規則來過濾 IP 資料包的驅動。

過濾鉤子驅動必須和使用者應用程式的可執行檔處在同一個目錄中。

為什麼用這種方法開發防火牆?

這不是在 Windows 開發防火牆的唯一方法,還有其它的像 NDIS 防火牆,TDI 防火牆,Winsock 層的防火牆,資料包過濾 API,……所以我總結了一些過濾鉤子驅動的優點和缺點,以供你在將來需要用到時參考。

  • 用這種方法有很大的靈活性。你可以過濾所有的 IP 資料包(以及上層的資料包)。但你不能過濾更底層的前序,例如,你不能過濾乙太網路幀。你需要用 NDIS 過濾來實現,更複雜也更靈活。
  • 這是一個簡單的方法。安裝防火牆和實現過濾函數用這種方法都是很簡單的。但資料包過濾 API 更為簡單,雖然沒有它靈活。你不能訪問資料包的內容,也不能用資料包過濾 API 修改它。

結果:過濾鉤子驅動並不是最好的,但它也沒有什麼壞特性。可是為什麼這個方法沒有用在商業產品中呢?

答案很簡單。雖然這個驅動沒有不好的特性,但它有一個很大的缺點。正如我之前提到的,每次只能安裝一個過濾函數。我們可以開發一個強大的防火牆,它可能被上千的使用者下載並安裝,但如果其它的應用程式使用了過濾(並在之前安裝了過濾函數),我們的程式就什麼都做不了了。

這個方法還有另一個缺點沒有被微軟的文檔提到。雖然 DDK 文檔說你可以在過濾函數中訪問資料包內容,但那不是真的。你可以訪問收到的包的內容,但對於發送的包,你只能讀 IP 和 TCP,UDP 或 ICMP 前序。我不明白為什麼……

微軟在 Windows XP 中引入了另一種沒有這個限制的驅動:防火牆鉤子驅動。它的安裝很類似,但微軟並不建議使用它,以為“它消耗太多的網路棧”。也許這個驅動會在以後的 Windows 版本中消失。

結論

好了,到結尾了。我只到這並不是開發防火牆的最好方法(我在前面提到了它的缺點)。但我認為這對正在尋找資料和對此感興趣的人們是個好的開始。

我希望你能得到些協助,並開始想要開發個強大的防火牆了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.