程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 開發Windows 2000/XP下的防火牆

開發Windows 2000/XP下的防火牆

編輯:關於VC++

介紹

如果你決定開發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版本中會消失。

結束:

好的,我知道這不是開發防火牆的最好的方法(我剛才已經提及了它巨大的缺點),但我想這對於在研究這個或對這個有所興趣的人們是一個好的開始。希望你能夠讀明白,並開發出一款強大的防火牆。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved