|=-------------------------=[ Bypassing Chkrootkit ]=-------------------------=| |=----------------------------------------------------------------------------=| |=---------------------=[ anonyme ]=---------------------=| |=----------------------------------------------------------------------------=| |=----------------=[ Translated by N-0-X ]=----------------=| --[ 1 Introduction In this short article, we will mainly see how to bypass chkrootkit and especially its tool called chkproc Today, chkrootkit is one of the most used program to check for rootkits on a machine. This program only works in userland without checking /dev/kmem nor /dev/mem devices. Therefore, we can wonder if this tool is really reliable. Only few rootkit detection tools exist today and this is why chkrootkit is so widely spread. --[ 2 About chkproc To keep it simple, chkproc retrieves the ps command's output, the values of /proc and brute forces /proc to check if all the pids are returned by ps and /proc. Let's see how ckproc's bruteforce works, since it is the most significant part of the program. [...] strcpy(buf, "/proc/"); retps = retdir = 0; for (i = FIRST_PROCESS; i <= MAX_PROCESSES; i++) { snprintf(&buf[6], 6, "%d", i); [1] if (!chdir(buf)) { if (!dirproc[i] && !psproc[i]) [...] retdir++; [...] How can we not pass in the test [1] and therefore stay hidden? Tons of papers show that it is possible to hide processes either by hijacking sys_getdents or some VFS functions, etc. In this case, we can return an error if we hijack the open or chdir system calls in a rootkit. We could also use the new library with the variable LD_PRELOAD. --[ 3 Hijacking The easiest way is to use the proc_pid_readdir function which prints the processes in /proc. /* FILE : /usr/src/linux/fs/proc/base.c */ int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir) { [...] for (;;) { nr_tgids = get_tgid_list(nr, next_tgid, tgid_array); [...] } get_tgid_list will retrieve the pid list by checking the chained list. /* FILE : /usr/src/linux/fs/proc/base.c */ static int get_tgid_list(int index, unsigned long version, unsigned int *tgids) { [...] for ( ; p != &init_task; p = next_task(p)) { int tgid = p->pid; [1] if (!pid_alive(p)) continue; if (--index >= 0) continue; tgids[nr_tgids] = tgid; nr_tgids++; if (nr_tgids >= PROC_MAXPIDS) break; } read_unlock(&tasklist_lock); return nr_tgids; } An interesting test in [1] will go to the next process in the list is the function returns 0. /* FILE : /usr/src/linux/include/linux/sched.h */ /** * pid_alive - check that a task structure is not stale * @p: Task structure to be checked. * * Test if a process is not yet dead (at most zombie state) * If pid_alive fails, then pointers within the task structure * can be stale and must not be dereferenced. */ static inline int pid_alive(struct task_struct *p) { return p->pids[PIDTYPE_PID].nr != 0; } struct task_struct { [...] /* PID/PID hash table linkage. */ struct pid pids[PIDTYPE_MAX]; [...] }; struct pid { /* Try to keep pid_chain in the same cacheline as nr for find_pid */ int nr; struct hlist_node pid_chain; /* list of pids with the same nr, only one of them is in the hash */ struct list_head pid_list; }; nr usually contains the process pid, so we will put it to zero. --[ 4 Application owned rootkit # ps aux | grep backdoor test 8603 0.0 0.1 1320 268 pts/5 S+ 14:57 0:00 ./backdoor root 8605 0.0 0.2 1516 472 pts/0 S+ 14:57 0:00 grep backdoor owned rootkit # chkproc owned rootkit # ./main -p -h 8603 PID 8603 is now hide !! owned rootkit # ps aux | grep backdoor root 8610 0.0 0.2 1516 472 pts/0 S+ 14:58 0:00 grep backdoor owned rootkit # chkproc owned rootkit # zeppoo -c -p Kernel : 2.6 Running on i386 !! Memory : /dev/kmem ------------------------------------------------------------------------------- [+] Begin : Task LIST OF HIDDEN TASKS PID UID GID NAME ADDR 8603 1000 100 backdoor @ 0xc2403570 [+] End : Task ------------------------------------------------------------------------------- --[ 5 Conclusion Detecting rookits in user land and naively trusting your system is a bad idea: the result isn't reliable. The solution is to be directly in kernel land or to be in the userland and the kernel land thanks to /dev/mem and /dev/kmem. This solution can also be bypassed but not as easily! :x Have fun !! References : [1] http://www.chkrootkit.org [2] http://www.zeppoo.net