開發Windows 2000/XP下的防火牆

來源:互聯網
上載者:User

開發Windows 2000/XP下的防火牆
作者:Jesús O
翻譯:PowerCPP

下載本文配套原始碼

介紹
如果你決定開發LINUX下的防火牆,你會找到很多免費的資訊與原始碼。但如果開發WINDOWS平台下的防火牆會有點困難,找到相關資訊與代碼都簡直是不可能的任務。

因此我決定寫這篇文章介紹在WINDOWS 2000/XP下開發防火牆的簡單方法。

背景
在WINDOWS 2000 DDK中,微軟包含了稱為Filter-Hook Driver的新型網路驅動。你可以使用它來過濾所有進出介面的資料。

因為關於此的文檔很少並沒有代碼,我把使用它的成功方法寫入文章,希望協助你理解這種簡單的方法。

Filter-Hook 驅動
像我剛才所說的,在Microsoft Windows 2000 DDK中介紹了Filter-Hook Driver, 事實上,它不是一種新的網路驅動,它只是擴充了IP過濾驅動(IP Filter Driver)的功能。
實際上,Filter-Hook Driver並不是網路驅動,它是一種核心模式驅動(Kernel Mode Driver). 大致上是這樣的:在Filter-Hook Driver中我們提供回呼函數(callback),然後使用IP Filter Driver註冊回呼函數。這樣當資料包發送和接收時,IP Filter Driver會調用回呼函數。那麼我們到底該如何?這些步驟呢?總結如下:

1) 建立Filter-Hook Driver.我們必須建立核心模式驅動,你可以選擇名稱,DOS名稱和其它驅動特性,這些不是必須的,但我建議使用描述名稱。

2) 如果我們要安裝過濾函數,首先我們必須得到指向IP Filter Driver的指標,這是第二步。

3) 我們已經取得了指標,現在我們可以通過發送特殊的IRP來安裝過濾函數,該"訊息"傳遞的資料包含了過濾函數的指標。

4) 過濾資料包!!!

5) 當我們想結束過濾,我們必須撤銷過濾函數。這通過傳遞null指標作為過濾函數指標來實現。

哦,只有五個步驟,這看起來非常容易,但...如何產生核心模式驅動?如何得到IP Filter Driver指標,如何..... 是的,請稍等,我現在將解釋這些步驟並提供原始碼:P

建立核心模式驅動(Kernel Mode Driver)

Filter-Hook Driver屬於核心模式驅動,因此我們要建立核心模式驅動。這篇文章不是“如何僅用5分鐘開發核心模式驅動” 這樣的指南,因此我假設讀者已經有了關於此的相關知識。

Filter-Hook Driver結構是典型的核心模式驅動的結構:

1) 一個建立裝置的驅動程式入口,為通訊建立符號串連和處理IRPs(指派,載入,卸載,建立...)的標準常式。

2)在標準常式裡管理IRPs.在開始編碼前,我建議先思考一下哪些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");    //我們必須建立裝置    RtlInitUnicodeString(&deviceNameUnicodeString, NT_DEVICE_NAME);    ntStatus = IoCreateDevice(DriverObject,                0,                &deviceNameUnicodeString,                 FILE_DEVICE_DRVFLTIP,                0,                FALSE,                &deviceObject);    if ( NT_SUCCESS(ntStatus) )    {         // 建立符號串連使win32應用程式可以處理驅動與裝置        RtlInitUnicodeString(&deviceLinkUnicodeString, DOS_DEVICE_NAME);        ntStatus = IoCreateSymbolicLink(&deviceLinkUnicodeString,                                         &deviceNameUnicodeString);        //....        // 建立用於控制、建立、關閉的dispatch指標        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代碼        case START_IP_HOOK:        {            SetFilterFunction(cbFilterFunction);            break;        }        // 關閉過濾的ioctl        case STOP_IP_HOOK:        {            SetFilterFunction(NULL);            break;        }                // 添加過濾規則的ioctl        case ADD_FILTER:        {            if(inputBufferLength == sizeof(IPFilter))            {                IPFilter *nf;                nf = (IPFilter *)ioBuffer;                                AddFilterToList(nf);            }            break;        }        // 釋放過濾規則列表的ioctl        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);    // 我們不會有未決的操作,所以總是返回狀態代碼    return ntStatus;}VOID DrvUnload(IN PDRIVER_OBJECT DriverObject){    UNICODE_STRING deviceLinkUnicodeString;    dprintf("DrvFltIp.SYS: Unloading/n");    SetFilterFunction(NULL);    // 釋放所有資源    ClearFilterList();       // 刪除符號串連    RtlInitUnicodeString(&deviceLinkUnicodeString, DOS_DEVICE_NAME);    IoDeleteSymbolicLink(&deviceLinkUnicodeString);        // 刪除裝置對象    IoDeleteDevice(DriverObject->DeviceObject);}

我們已經編寫了驅動的主代碼,下面我們繼續實現Filter-Hook Driver


註冊過濾函數

在上面的代碼中,你已經看到了SetFilterFunction(..)函數。我在IP Filter Driver中執行這個函數來註冊過濾函數,步驟如下:

1) 首先,我們必須得到IP Filter Driver的指標,這要求驅動已經安裝並執行。為了保證IP Filter Driver已經安裝並執行,在我的使用者程式中,在載入本驅動前載入並啟動IP Filter Driver。

2) 第二步,我們必須建立用IOCTL_PF_SET_EXTENSION_POINTER作為控制碼的IRP。我們必須傳遞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");    //首先我們要得到IpFilterDriver Device的指標    RtlInitUnicodeString(&filterName, DD_IPFLTRDRVR_DEVICE_NAME);    status = IoGetDeviceObjectPointer(&filterName,STANDARD_RIGHTS_ALL,                                     &ipFileObject, &ipDeviceObject);    if(NT_SUCCESS(status))    {        //用過濾函數作為參數初始化PF_SET_EXTENSION_HOOK_INFO結構        filterData.ExtensionPointer = filterFunction;        //我們需要初始化事件,用於在完成工作後通知我們        KeInitializeEvent(&event, NotificationEvent, FALSE);        //建立用於設立過濾函數的IRP        irp = IoBuildDeviceIoControlRequest(IOCTL_PF_SET_EXTENSION_POINTER,                                                             ipDeviceObject,        if(irp != NULL)        {            // 發送 IRP            status = IoCallDriver(ipDeviceObject, irp);            // 然後我們等待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        {            //如果不能分配空間,返回相應的錯誤碼            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 Driver 已經完成了IRP處理。

過濾函數

我們已經知道了如何開發驅動並安裝過濾函數,但還不知道該過濾函數中的任何東西。當主機接收或發送一個資料包,該過濾函數總會被調用,系統會根據函數的傳回值決定如何處理這個資料包。

函數的原型是這樣的:

typedef  PF_FORWARD_ACTION (*PacketFilterExtensionPtr)(  // Ip資料包的包頭  IN unsigned char *PacketHeader,  // 不包括頭的資料包  IN unsigned char *Packet,   // 包長度。不包括IP頭的長度  IN unsigned int PacketLength,   // 接收資料的介面適配器編號      IN unsigned int RecvInterfaceIndex,   // 發送資料的介面適配器編號  IN unsigned int SendInterfaceIndex,      //接收資料包的適配器IP地址  IN IPAddr RecvLinkNextHop,  //發送資料包的適配器IP地址  IN IPAddr SendLinkNextHop   ); 

PF_FORWARD_ACTION 是枚舉類型,可能的值有:

PF_FORWARD 指示IP filter Driver立即向IP棧傳遞資料。對於本機資料包,IP向上送入棧。如果目標是另外的機器並且允許路由,將通過IP路由發送。

PF_DROP 指示IP filter Driver丟棄IP包。

PF_PASS 指示IP filter Driver去過濾該資料包,IP filter Driver如果處理該資料包取決於包過濾API的設定。過濾鉤子返回pass的回應,表明它沒有處理該資料包而讓IP filter Driver去過濾該資料包。

雖然DDK文檔只包含了這三個值,如果你查看pfhook.h(Filter-Hook Driver需要的標頭檔)你可以發現還有一個PF_ICMP_ON_DROP,我猜這個值是用於對ICMP包進行丟棄。

在過濾函數的定義中,傳遞進來指向資料包頭的指標。因此,你可以修改資料頭然後發送。這對於進行NAT轉換是非常有用的,你可以修改資料包的目標地址,選擇IP路由。

在我的實現裡,過濾函數根據使用者程式的要求將每個包與規則列表進行比較,這個列表是串連列表,是在運行期間用START_IP_HOOK 的IOCTL建立的。在代碼裡可以看到這些。

原始碼

在第一個版本中我只包含了簡單的樣本,由於許多朋友要求我幫他們開發實際應用,我完善了這個例子。新的樣本是資料包的過濾程式,在這個新程式裡你可以像商業防火牆一樣添加你的過濾規則。

第一個版本中,應用程式由兩部分組成:
(一)使用者應用程式:這是管理過濾規則的MFC應用程式。程式發送規則給驅動並決定何時啟動過濾,有三個步驟:
1) 定義所需的規則。可以通過添加、刪除定製規則。
2) 安裝規則。定義好規則後,點install按鈕將他們發送給驅動
3) 啟動過濾。你可以單擊start按鈕啟動過濾

(二)Filter-Hook Driver: 根據從使用者應用程式中接收到的過濾規則對資料包進行過濾。 Filter-Hook Driver必須與使用者應用程式在相同的目錄中。

為什麼使用該方法開發防火牆

在Windows中這不是開發防火牆的唯一方法,其它的有諸如 NDIS防火牆,TDI防火牆,Winsock分層防火牆,包過濾API,...因此我將說明Filter-Hook Driver的優缺點使你在以後開發防火牆時決定是否採用這種驅動。

1) 這種方法所擁有的彈性可以使你過濾所有IP層(或以上)的通訊。但你不能過濾更低層的頭部資料,例如:你不能過濾以太幀資料。你需要用NDIS過濾器來做,雖然開發困難但彈性更大。

2) 這是一種簡單的方法。安裝防火牆和執行過濾功能非常簡單。但包過濾API(Packet Filtering API)更加容易使用,儘管它缺少彈性,例如你不能處理包的內容,你不能用包過濾API修改內容。

結論:Filter-Hook Driver不是最好但也不壞。但為什麼商業產品中並不使用它呢?
答案是簡單的:儘管這種驅動並不糟糕但卻有巨大的缺點:像我剛才所說的,每次只能有一個過濾函數可以安裝。我們開發了強大的防火牆,它可以被成千上萬個使用者下載,如果其它應用程式已經使用了過濾器(安裝了過濾函數)那麼我們的程式將不會正常工作。

這種方法的另外一個缺點是不被微軟官方明文支援。雖然DDK文檔上說你可以在過濾函數中處理包的內容, 但事實並非如此。你可以在接收資料包的時候處理包的內容,但在發送包的時候你僅能讀IP、TCP、UDP或ICMP資料包頭。我不知道這是為什麼....

微軟介紹在Windows XP中另外一種驅動沒有這樣的限制,那就是firewall hook driver。但微軟不推薦使用它,因為"it ran too high in the network stack"。或許這種驅動在以後的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.