Some fun with Linux Netfilter Hooks
|=------------=[ Some Tricks with Linux netfilter hooks ]=---------------=|
|=-----------------------------------------------------------------------=|
This document is based on my understanding of bioforge's "Hacking the Linux Kernel Network Stack" article in Phrack issue 61. Please do correct me if I am wrong somewhere.The sourcecodes provided here is compiled and tested in 2.4.28 kernel.
Netfilter is a subsystem in the Linux 2.4 kernel. Netfilter provides an generic and abstract interface to the standard routing code.This is currently used in Linux kernel for packet filtering,mangling,NAT(network address translation) and queuing packets to the userspace.Netfilter makes 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.
Although Linux 2.4 supports hooks for IPv4,IPv6 and DECnet, only IPv4 will be discussed in this document.
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 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 on 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. NF_REPEAT requests that Netfilter calls the hook function again.
Registration of a hook function is a very simple process that revolvesaround 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
/* 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);
}
Now its time to start looking at what data gets passed into hook
functions and how that data an 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" .
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.
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.It is defined in "linux/netdevice.h". 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.
We will build a module which filters 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 (define in linux/tcp.h) and pointing after the IP header in our packet data.Perhaps an example would help. Listing 2 presents code to check if the destination TCP port of a packet matches some port we want to drop all packets for.
Listing 2. Checking the TCP destination port of a received packet
/* Sample code to install a Netfilter hook function that will
* drop all incoming packets to a particular TCP destination port.*/
#define __KERNEL__
#define MODULE
#include
#include
#include
#include
#include
#include
#include
#include
/* This is the structure we shall use to register our function */
static struct nf_hook_ops nfho;
/* Port no we want to drop packets going to, in NB order */
unsigned char *deny_port = "\x00\x19"; /* port 25 */
/* 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;
struct tcphdr *thead;
/* We don't want any NULL pointers in the chain
* to the IP header. */
if (!sb ) return NF_ACCEPT;
if (!(sb->nh.iph)) return NF_ACCEPT;
/* Be sure this is a TCP packet first */
if (sb->nh.iph->protocol != IPPROTO_TCP) {
return NF_ACCEPT;
}
thead = (struct tcphdr *)(sb->data +
(sb->nh.iph->ihl * 4));
/* Now check the destination port */
if ((thead->dest) == *(unsigned short *)deny_port) {
return NF_DROP;
}
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 for IPv4 */
nfho.pf = PF_INET;
nfho.priority = NF_IP_PRI_FIRST; /* Make our func first */
nf_register_hook(&nfho);
return 0;
}
/* Cleanup routine */
void cleanup_module()
{
nf_unregister_hook(&nfho);
}
So, as you can see, using Netfilter hooks we can create our own custom filters.There are a lot of interesting things using these hooks. i.e-
1.Kernel level Firewall ( For an implementation plz check bioforge's article).
2.Kernel level sniffer.
3.Module to hide packets from libpcap so that user level sniffers can't see it.
(For more details plz check bioforge's article).
4.Kernel level Backdoor daemon which will allow users to upload files and execute commands remotely. (Sounds like a cool trojan. isn' it ? )
....
....
The list is only limited by your imagination :). With the powers made availble to a kernel level programmer, you can do whatever you want ( Just beaware of those pretty nasty kernel faults :-P).
Happy experimenting with linux network stack.