Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1367765
  • 博文数量: 946
  • 博客积分: 52360
  • 博客等级: 大将
  • 技术积分: 13080
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-07 17:31
文章分类

全部博文(946)

文章存档

2011年(1)

2008年(945)

我的朋友

分类: C/C++

2008-08-07 17:38:44

下载本文示例代码
下载本文配套源代码



介绍
如果你决定开发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版本中会消失。

结束:
好的,我知道这不是开发防火墙的最好的方法(我刚才已经提及了它巨大的缺点),但我想这对于在研究这个或对这个有所兴趣的人们是一个好的开始。希望你能够读明白,并开发出一款强大的防火墙。

下载本文示例代码
阅读(162) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~