diff --git a/PLATFORMS b/PLATFORMS index 5173e00..f1546fb 100644 --- a/PLATFORMS +++ b/PLATFORMS @@ -1,8 +1 @@ -bromolow 3.10.108 -apollolake 4.4.180 -broadwell 4.4.180 -broadwellnk 4.4.180 -denverton 4.4.180 -geminilake 4.4.180 -v1000 4.4.180 -r1000 4.4.180 +epyc7002 5.10.55 diff --git a/config/.platforms.h.swp b/config/.platforms.h.swp deleted file mode 100644 index e615aa2..0000000 Binary files a/config/.platforms.h.swp and /dev/null differ diff --git a/config/platforms.h b/config/platforms.h index 2eecfd2..458fb82 100644 --- a/config/platforms.h +++ b/config/platforms.h @@ -443,7 +443,7 @@ const struct hw_config supported_platforms[] = { }, .emulate_rtc = false, .swap_serial = false, - .reinit_ttyS0 = true, + .reinit_ttyS0 = false, .fix_disk_led_ctrl = true, .has_cpu_temp = true, .is_dt = true, diff --git a/internal/helper/ftrace_helper.h b/internal/helper/ftrace_helper.h new file mode 100644 index 0000000..9199ff0 --- /dev/null +++ b/internal/helper/ftrace_helper.h @@ -0,0 +1,200 @@ +/* + * Helper library for ftrace hooking kernel functions + * Author: Harvey Phillips (xcellerator@gmx.com) + * License: GPL + * Link: https://github.com/xcellerator/linux_kernel_hacking/blob/master/3_RootkitTechniques/3.9_hiding_logged_in_users/ftrace_helper.h + * */ + +#include +#include +#include +#include +#include + +#if defined(CONFIG_X86_64) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0)) +#define PTREGS_SYSCALL_STUBS 1 +#endif + +/* + * On Linux kernels 5.7+, kallsyms_lookup_name() is no longer exported, + * so we have to use kprobes to get the address. + * Full credit to @f0lg0 for the idea. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0) +#define KPROBE_LOOKUP 1 +#include +static struct kprobe kp = { + .symbol_name = "kallsyms_lookup_name" +}; +#endif + +#define HOOK(_name, _hook, _orig) \ +{ \ + .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) +{ +#ifdef KPROBE_LOOKUP + typedef unsigned long (*kallsyms_lookup_name_t)(const char *name); + kallsyms_lookup_name_t kallsyms_lookup_name; + register_kprobe(&kp); + kallsyms_lookup_name = (kallsyms_lookup_name_t) kp.addr; + unregister_kprobe(&kp); +#endif + 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/internal/intercept_execve.c b/internal/intercept_execve.c index 5df155e..ad44704 100644 --- a/internal/intercept_execve.c +++ b/internal/intercept_execve.c @@ -24,6 +24,7 @@ #include //struct filename #include "override/override_syscall.h" //SYSCALL_SHIM_DEFINE3, override_symbol #include "call_protected.h" //do_execve(), getname(), putname() +#include "helper/ftrace_helper.h" #ifdef RPDBG_EXECVE #include "../debug/debug_execve.h" @@ -58,61 +59,68 @@ int add_blocked_execve_filename(const char *filename) return 0; } -SYSCALL_SHIM_DEFINE3(execve, - const char __user *, filename, - const char __user *const __user *, argv, - const char __user *const __user *, envp) +static asmlinkage long (*orig_execve)(const struct pt_regs *); + +/* + * The hook for sys_execve() + */ +asmlinkage int hook_execve(const struct pt_regs *regs) { - struct filename *path = _getname(filename); + char *filename = (char *)regs->di; - //this is essentially what do_execve() (or SYSCALL_DEFINE3 on older kernels) will do if the getname ptr is invalid - if (IS_ERR(path)) - return PTR_ERR(path); + char *kbuf; + long error; + int i; - const char *pathname = path->name; -#ifdef RPDBG_EXECVE - RPDBG_print_execve_call(pathname, argv); -#endif + /* + * We need a buffer to copy filename into + */ + kbuf = kzalloc(NAME_MAX, GFP_KERNEL); + if(kbuf == NULL) + return orig_execve(regs); - for (int i = 0; i < MAX_INTERCEPTED_FILES; i++) { + /* + * Copy filename from userspace into our kernel buffer + */ + error = copy_from_user(kbuf, filename, NAME_MAX); + if(error){ + kfree(kbuf); + return orig_execve(regs); + } + + for (i = 0; i < MAX_INTERCEPTED_FILES; i++) { if (!intercepted_filenames[i]) break; - if (unlikely(strcmp(pathname, intercepted_filenames[i]) == 0)) { - pr_loc_inf("Blocked %s from running", pathname); + if (unlikely(strcmp(kbuf, intercepted_filenames[i]) == 0)) { + pr_loc_inf("Blocked %s from running", kbuf); + kfree(kbuf); //We cannot just return 0 here - execve() *does NOT* return on success, but replaces the current process ctx do_exit(0); } } -//Depending on the version of the kernel do_execve() accepts bare filename (old) or the full struct filename (newer) -//Additionally in older kernels we need to take care of the path lifetime and put it back (it's automatic in newer) -//See: https://github.com/torvalds/linux/commit/c4ad8f98bef77c7356aa6a9ad9188a6acc6b849d -#if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0) - int out = _do_execve(pathname, argv, envp); - _putname(path); - return out; -#else - return _do_execve(path, argv, envp); -#endif + /* + * Clean up and return + */ + kfree(kbuf); + return orig_execve(regs); } +/* Declare the struct that ftrace needs to hook the syscall */ +static struct ftrace_hook hooks[] = { + HOOK("__x64_sys_execve", hook_execve, &orig_execve), +}; + static override_symbol_inst *sys_execve_ovs = NULL; int register_execve_interceptor() { pr_loc_dbg("Registering execve() interceptor"); - if (sys_execve_ovs) { - pr_loc_bug("Called %s() while execve() interceptor is already registered", __FUNCTION__); - return -EEXIST; - } - -#if LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0) - override_symbol_or_exit_int(sys_execve_ovs, "SyS_execve", SyS_execve_shim); -#else - // TODO there is another __ia32_sys_execve, maybe need to override. - override_symbol_or_exit_int(sys_execve_ovs, "__x64_sys_execve", SyS_execve_shim); -#endif + int err; + err = fh_install_hooks(hooks, ARRAY_SIZE(hooks)); + if(err) + return err; pr_loc_inf("execve() interceptor registered"); return 0; @@ -121,25 +129,9 @@ int register_execve_interceptor() int unregister_execve_interceptor() { pr_loc_dbg("Unregistering execve() interceptor"); - - if (!sys_execve_ovs) { - pr_loc_bug("Called %s() while execve() interceptor is not registered (yet?)", __FUNCTION__); - return -ENXIO; - } - - int out = restore_symbol(sys_execve_ovs); - if (out != 0) - return out; - sys_execve_ovs = NULL; - - //Free all strings duplicated in add_blocked_execve_filename() - unsigned int idx = 0; - while (idx < MAX_INTERCEPTED_FILES-1 && intercepted_filenames[idx]) { - kfree(intercepted_filenames[idx]); - intercepted_filenames[idx] = NULL; - idx++; - } - + /* Unhook and restore the syscall and print to the kernel buffer */ + fh_remove_hooks(hooks, ARRAY_SIZE(hooks)); pr_loc_inf("execve() interceptor unregistered"); return 0; } + diff --git a/output/rp-epyc7002-5.10.55-dev.ko.gz b/output/rp-epyc7002-5.10.55-dev.ko.gz new file mode 100644 index 0000000..9be345a Binary files /dev/null and b/output/rp-epyc7002-5.10.55-dev.ko.gz differ diff --git a/output/rp-epyc7002-5.10.55-prod.ko.gz b/output/rp-epyc7002-5.10.55-prod.ko.gz new file mode 100644 index 0000000..2ceddd2 Binary files /dev/null and b/output/rp-epyc7002-5.10.55-prod.ko.gz differ