开发Windows2000/XP环境下的防火墙

原文:Developing Firewalls for Windows 2000/XP

作者:Jesús O

翻译:五一

说明:

Jesús O在CodeProject上发了篇文章,讲如何在Windows 2000/xp环境下开发防火墙的技术。这几天学习了一下,并且翻译出来。有些东西Jesús O没讲到,比如如何编译他提供的驱动源代码等等。我这里一并进行补充说明。

译文:

==================================================================================

>>Introduction

如果在Linux下开发一个Firewall,将能够找到很多有用信息和源代码,并且是免费的。可是,如果在Windows下开发,你会发现,可用的信息少得可怜,免费得源代码也很难找到。
因此我决定写这篇文章介绍一下在Windows平台下开发Firewall的简单方法,以帮助对Firewall开发感兴趣的人们。

>>Background

在Windows 2000 DDK中,Microsoft包括了新的网络驱动类型Filter-Hook Driver。有了它,你可以为应用程序加入一个功能用来过滤所有进出端口的传输数据包。

由于关于这方面的文档很少且不包括例子,因此我在这篇文档中将按照步骤说明如何成功的使用它。我希望这篇文章能够使你理解这种简单的方法。

>>Filter-Hook Driver

Microsoft在Windows 2000 DDK中已经介绍了Filter-Hook Driver,它实际上并不是一个新的network driver class,而仅仅是IP Filter Driver(包括Windows2000 和以后版本)在功能上的扩展。

事实上,Filter-Hook Driver不是一个network driver,它是一个Kernel Mode Driver。基本的,在这个Filter-Hook Driver上我们执行一个callback function。然后,我们向IP Filter Driver注册这个callback。当完成这些工作后,当一个数据包被发送或者接收时,IP Filter Driver将Call我们的Callback function。好,主要都包括哪些步骤呢?

我们可以总结成下列步骤:
1、Create一个Filter-Hook Driver。因此,你必须创建一个Kernel Mode Driver。选择一个名称,DOS Name和其它字符,当然名字起什么并不重要,这仅仅是我的推荐。
2、如果我们想要安装Filter Function , 首先我们需要得到一个IP Filter Driver的Pointer。因此,这将是第二个步骤。
3、我们已经有了一个Pointer,现在我们可以安装Filter Driver了。要做这件事情需要发送一个得数的IRP(I/O 要求数据包)。发送的数据包括了我们的Pointer。
4、过滤包!!!
5、当需要结束过滤时,我们必须deregister我们的filter function。需要做的工作是用一个NULL Pointer做第3步的工作。

呵呵,只有5步工作要做。看起来非常Easy。但实际上还有很多问题,比如:
怎样到Kernel mode drive呢?
怎样得到一个IP Filter Driver的Pointer?
怎样... ...?
OK,我将继续解释这些问题。

>>Create The Kernel Mode Driver

Filter-Hook Driver是一个Kernel Mode Driver,因此如果我们想要使用它,我们不得不自己构造一个Kernel Mode Driver。我这篇文章并不是“How to develop kernel mode drivers in 5 minutes”,因此,我假定读者已经对这方面知识有所了解。

Filter-Hook Driver的结构是标准的Kernel Mode Driver结构:
1、一个driver entry(指向我们创建的Device);为了处理IRPs而设置参数(Dispatch、load、unload、create... );表征用户应用通信的链接。
2、将构造的数据规范交给IRPs管理。在开始代码前,我希望你能考虑用哪个IOCTL在你的应用输出接口处。在我这个Sample中,我使用了四个IOCTL:
START_IP_HOOK,registers the filter function
STOP_IP_HOOK,deregisters the filter function,
ADD_FILTER,installs a new rule
CLEAR_FILTER ,frees all rules

3、对于我们的Driver, 我们必须实现一个或者多个功能: the filter function.

我推荐你使用一个程序来生成一个Kernel Mode Driver的结构。你只需要将生成的代码添加到你的Function中,比如,我用QuickSys来帮助完成这项工作。

下面是我自己的Driver结构实例:

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);
}

我们已经构造了Driver的主要代码,接下来编写Filter-Hook Driver代码。


>>Registering a Filter function

在上面代码中,我们已经看到有一个函数的名字叫SetFilterFunction(..)。执行这个函数将用来向IP Filter Driver注册一个Function。下面是具体的步骤:

1、 必须获得IP Filter Driver的pointer。这要求Driver必须已经被安装和正在执行。我们的用户应用程序,在loading这个Driver之前loads和starts IP Filter Driver,为了assure this.
2、我们必须以IOCTL_PF_SET_EXTENSION_POINTER为IO Control code创建一个IRP。必须传递PF_SET_EXTENSION_HOOK_INFO structure作为参数 ,结构中包含了关于指向Filter Function的Pointer。如果想要uninstall the function, 则使用相同的步骤,但传递 NULL作为pointer to filter function.
3、发送创建的IRP给device driver.

这里有一个比较大的问题,就是一个OS里只能有一个filter function被安装,如果其它Application安装了一个,则你不能再安装。

下面是具体实现代码:

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;
}

============

你可以发现,当你完成创建filter function工作以后,我们必须de-reference the file object obtained 当我们得到一个指向device driver的Pointer时。当IpFilter Driver完成IRP处理时,我使用一个Event来通报。

>>The Filter function

我们已经知道了怎样开发driver和安装filter function,但对于这个function的具体功能我们还一无所知。我已经说过,这个function总是在主机接收或者发送数据包时被调用。这个function有独立的返回值,使系统知道怎样对待当前处理的packet。

function的原型必须是:

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 是一个枚举类型, (in Microsoft Words):

PF_FORWARD
告知IP filter driver立即返回投递响应给IP stack. 对于本地packets, IP 将数据包投递入栈. 如果packets的目标地址是另外的计算机且路由可用,则向外投递。

PF_DROP
丢弃package.

PF_PASS
告知IP filter driver去过滤packets,返回响应结果给IP stack.IP filter driver怎样处理filter packets将由Packet Filtering API完成定义.
filter hook返回pass response如果检测到的话。它将不处理packet除非允许IP filter driver去filter the packet.

虽然DDK文档中只包括了上述三个值。但如果你看pfhook.h文件的话,你会看到更多值。比如:PF_ICMP_ON_DROP。我猜想它可以用来处理ICMP数据包,比如DROP行为等。

正如你看到的filter function定义的那样,数据包和它的header被pointer传送。因此,你可以调整header或者payload(静荷)然后再投递出数据包。这是非常有用的对于NAT(Network Address Translation)来说。

在我的实例中,filter function为每个数据包packet和规则列表做比较,规则由用户应用程序进行定义。这个list就像一个链接的list,它在runtime时和每个START_IP_HOOK IOCTL创建,你可以在我的源代码中看到。


>> The Code

In the first version of this article I included a simple example and, because some people requested me to help them to develop real applications, I updated it with a more complex one. The new example is a little packet filtering application. With this new program you can establish your filter rules as you can do in some commercial firewalls.

在第一个Version中,包括了两个组件:

用户应用:一个MFC应用,用来管理filter rules。这个应用发送rules 给应用驱动。要过滤传输需要三个步骤:
1)定义规则,使用Add和Delete命令完成。
2)安装规则。当定义完规则后,需要运行install按钮将规则发送给driver。
3)开始过滤。单击start按钮开始过滤。

Filter-Hook Driver,一个Filter IP Traffic的过滤驱动。
Filter-Hook Driver必须和应用程序在相同的目录下。

>> Why use this method to develop a firewall?

对于Windows来说,这不是唯一的开发防火墙的方法。还有其它的很多技术,比如NDIS、TDI、Winsock Layerd、Packet Filtering API等等。因此,下面说说使用Filter-Hook Driver的优点和缺点。

1)使用Filter-Hook Driver对于过滤来说很灵活。你可以过滤所有IP层传输,但无法过滤更低层的Header。比如,你不能过滤以太帧。NDIS能够完成此功能。
2)这是相对容易的方法。安装和执行都很简单。不如Packet Filtering API则更简单。虽然它可能不够灵活。因为Packet Filtering API不能够访问Packet的内容,也不能调整Packet。

就上面的结论,还有一个疑问。就是为什么Filter-Hook Driver没有被用到通用产品中呢?

===== 译文完 ============================================================

补充编译驱动的方法:

要编译Jesús O提供的驱动源代码首先要安装DDK。安装很简单,只要运行DDK提供的Setup按照提示完成就可以了。

安装DDK后,在DDK程序组下有checked和free两个编译环境,checked环境用于编译带调试信息的驱动程序,free环境则用于编译正式发布版本的驱动程序。


DDK的目录结构:
bin可执行文件
help关于DDK的各种帮助文件
inc编译驱动程序所需的各种头文件
src各类驱动程序例子的源代码
……

src 子目录下包含了几十个真实的驱动程序例子的源代码,这些例 子几乎覆盖了驱动程序编程的方方面面。
如果在 Windows 操作系统正式发行版中一个实际的驱动程序与 DDK 的例子驱动程序同名,那么例子驱动程序往往就是实际驱动 程序的源代码。
所以, DDK 提供的例子是学习编写驱动程序的最好资源。
驱动程序的编译受 DDK 提供的 BUILD 工具控制。
成功编译一个驱动程序需要在要被编译的源文件目录下面提供三 个文本文件(三个文件都没有扩展名):
MAKEFILE
SOURCES
DIRS
BUILD 从这三个文件读取输入,并且创建 BUILD.LOG BUILD.ERR 等文件作为输出,如果一切正常,执行 BUILD 的最 后结果是创建驱动程序的可执行版本,其文件类型是 .SYS
Jesús O提供了名为DrvFltIp.c和DrvFltIp.h的两个源代码文件,同时应该将他提供的makefile和sources两个文件也拷贝出来放在一个指定的目录下。
编译时:
单击开始-》所有程序?-》Development Kits -》Windows DDK -》Build Enviroments-》 Win XP Checked Build Enviroment,将出现一个控制台窗口。
使用cd命令进入待编译驱动程序所在的目录,键入build命令即可编译。
运行build命令只编译需要重新编译的文件, 而build -c命令则强迫编译器重新编译所有的文件。
我实际编译的时候发现他的源文件中有5处dprintf的语句报错,为了省事起见,都注释掉了。编译通过,测试生成的驱动,功能没问题。OK!
能够编译了,我们就可以在驱动源文件的基础上修改一些功能,或者添加一些功能等等。

你可能感兴趣的:(数据结构,windows,网络应用,防火墙,XP)