TxaKy Network v0.6


 

Developing Firewalls with Filter-Hook driver

Introduction

If you decide to develop a Firewall for Linux, you will find a lot of information and source code, all free. However the people interested in Firewall for Windows Platforms have a little more difficulty, not only for finding information, finding free source code is a task impossible!!.

So, I have decided to write this article that describes a simple method for developing Firewalls for Windows 2000/XP to help people interested in this subject.

 

Background

In the Windows 2000 DDK, Microsoft includes a new type of network driver denominated Filter-Hook Driver. With it, you can establish a function to filter all traffic that arrive/leave to interfaces.

Because the documentation about this topic is a little and don't include samples, I write in this article the steps needed to use it successfully. I hope this article will help you to understand this easy method.

 

The Filter-Hook Driver

As I said before, the Filter-Hook Driver was introduced by Microsoft in Windows 2000 DDK. In fact, it is not a new network driver class, it is only a way to extends IP Filter Driver (included with Windows 2000 and ¿later?) functionality.

In fact, Filter-Hook Driver isn't a Network driver, it is a Kernel Mode Driver. Basically, in this Filter-Hook driver we implement a callback function, and then, we register this callback with the IP Filter Driver. When we do this, the IP Filter Driver calls our callback function when a packet is been sent or received. Then... What are the main steps to do this?

We can summarize then in the following steps:

Create a Filter-Hook Driver. For this, you must create a Kernel Mode Driver, you choose the name, DOS name and other driver characteristics, nothing obligatory but I recommended to use descriptive names.
If we want to install the filter function, first we must get a pointer to IP Filter Driver. So, It will be the second step.
We already have the pointer, now we can install the filter function. We can do it sending a specific IRP. The data passed in this "message" included a pointer to the filter function.
Filtering Packets!!!!
When we decide to finish filtering, we must deregister the filter function. We can do it, "registering" as filter function the null pointer.
Oh, Oh, five steps only, and it seem very easy, but... how I can make a Kernel mode driver? How I can get a pointer to the IP Filter Driver? How I ..... yessssssss, one moment please, I will explain all these steps now :P, showing the source code sample.

 

Create the Kernel Mode Driver

Filter-Hook driver is a Kernel Mode Driver, so if we want to do one, we have to make a Kernel Mode Driver. This article isn't a "How to develop Kernel Mode drivers in 5 minutes" guide, so I assume that the reader has some knowledge on the subject.

The structure of the Filter-Hook driver is the typical Kernel Mode Driver structure:

A driver entry where we create the device, set the standard routines in order to process IRPs (Dispatch, load, unload, create....) and create the symbolic link for communication with user applications.
The standard routines to manage IRPs. Before you begin to code, I recommend, think what IOCTL you "export" to applications from device driver. In my sample, I implement four IOCTL Codes: START_IP_HOOK (registers the filter function), STOP_IP_HOOK (deregisters the filter function), ADD_FILTER(installs a new rule) and CLEAR_FILTER (free all rules).
For our driver, we must implement one more function: the filter function.
I recommended you to use a program that generate the structure of a Kernel Mode Driver, so you only have to put code into the generate functions. For example, I have used QuickSYS in this project.

You can see my own implementation of the structure of the Driver , in the following code:


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

    objectIoDeleteDevice(DriverObject->DeviceObject);

}					

We already have made the driver main code, so we follow with code of the Filter-Hook Driver.

 

Registering a Filter function

In the above code, you have seen a function called SetFilterFunction(..). I implemented this function to register a function in the IP Filter Driver. I will describe the steps followed:

  1. First, we must get a pointer to the IP Filter Driver.
    That requires that driver is installed and executing. My user application, load and start IP Filter Driver before loading this driver, in order to assure this.

  2. Second, we must build an IRP specifying IOCTL_PF_SET_EXTENSION_POINTER as IO Control Code.
    We must pass as parameter a PF_SET_EXTENSION_HOOK_INFO structure that has information about the pointer to filter function. If you want to uninstall the function, you have to follow the same steps but passing NULL as the pointer to filter function.

  3. Send the build IRP to the device driver.
    Here there are one of the bigger problems of this driver. Only one filter function can be installed, so if other application installed one, you can't install your function.

I will show in the following lines, the code of this function:

 


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 can't allocate memory

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

}

 

The Filter function

We have seen how we can develop the driver and how to install the filter function, but we don't know anything about this function yet.

I already said that this function is called always when the host receive or send a packet. Depending in the return value of this function, the system decide what to do with the packet.

The prototype of this function must be:


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 is a enumerated type that can value( in Microsoft Words):

  • PF_FORWARD
    Specifies for the IP filter driver to immediately return the forward response to the IP stack. For local packets, IP forwards them up the stack. If the destination for packets is another computer and routing is enabled, IP routes them accordingly.

  • PF_DROP
    Specifies for the IP filter driver to immediately return the drop response to the IP stack. IP should drop the packet.

  • PF_PASS
    Specifies for the IP filter driver to filter packets and return the resulting response to the IP stack. How the IP filter driver proceeds to filter packets is determined by how it was set with the Packet Filtering API.

The filter hook returns this pass response if it determined that it should not process the packet but should allow the IP filter driver to filter the packet.

Although DDK documentation only include these 3 values, if you look into pfhook.h (include needed for Filter-Hook Driver), you can see one more. This value is PF_ICMP_ON_DROP. I suppose this value correspond with dropoing the packet and inform source for error with an ICMP packet.

As you can see in the definition of the filter function, the packet and its header are passed as pointers. So, you can modify header or payload and then forward the packets. This is very useful for example to do Network Address Translation (NAT). If we change destination address IP routes the packets.

In my implementation, the filter function compares each packet with a list of rules, introduced by the user application. This list is implemented as a linked list, that is built in runtime with each START_IP_HOOK IOCTL. You can see this in my source code.

The Code
As an example, I developed a little firewall to show people how to use this method. The example has two parts:

  • User Application: MFC application that load the driver and send IOCTL to start, stop, and establish filter rules. For testing the driver, the rules forbid ICMP traffic and local http traffic (port 80). Before doing it, the application should be loaded (it's usually already load), and change its state to executing.


  • Filter-Hook Driver: Driver that filter IP Traffic based in the filter rules received from user application.
    The Filter-Hook Driver must be in the same directory as the user application executable.

 

Why use(or not) this method to develop a Firewall?

It isn't the unique method to develop firewalls for Windows, there are other as NDIS Firewall, TDI Firewall, Winsock Layered Firewall, Packet Filtering API,.... so I will mention some advantages and disadvantages of Filter-Hook Driver in order for you to decide if your future firewall must use this driver.

You have much flexibility filtering with this method. You can filter all IP traffic (and above) and you can modify packets easily. However you can't filter lower layer header, for example, you cant filter Ethernet frame. You need an NDIS filter to do it, more complicated to develop but more flexible.
It is an easy method. Installing a firewall and the implementation of filter function is an easy procedure with this method. However Packet Filtering API is more easy yet, although is less flexible. You can't access packet content, and you can't modify this with Packet Filtering API.
Result: Filter-Hook Driver isn't the best in nothing, but it hasn't bad characteristics. However why this method isn't used in commercial products?

The answer is simple. Although this driver hasn't bad characteristics it has a great disadvantage, too. As I mentioned this before. Only one filter function can be installed each time. We can develop a great firewall, it can be downloaded and installed by thousands of users but if other applications use this filter (and installed the filter function before) our program won't do anything.

Microsoft introduced another type of driver without this limitation in Windows 2000: firewall-hook driver. Its installation is very similar, but Microsoft doesn't recommend its use because "it ran too high in the network stack". Maybe this driver will disappear in later Windows versions.

 

Conclusion

Ok, this is the finish. I know this is not the best method for developing firewalls (I mentioned the great disadvantage before), but I think this is a good method for beginners who are searching information

I hope you understood something, and you want to develop a great firewall now.

Article writen by Jesús O . This Article was published in www.codeproject.com - 19/12/03

Download Article's Source Code