feat: port intercept execve (#1)

Signed-off-by: Jim Ma <majinjing3@gmail.com>

Signed-off-by: Jim Ma <majinjing3@gmail.com>
This commit is contained in:
Jim Ma 2022-12-17 17:05:58 +08:00 committed by GitHub
parent d97931a735
commit 41fc4fd882
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 249 additions and 64 deletions

View File

@ -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

Binary file not shown.

View File

@ -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,

View File

@ -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 <linux/ftrace.h>
#include <linux/linkage.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#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 <linux/kprobes.h>
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]);
}

View File

@ -24,6 +24,7 @@
#include <linux/fs.h> //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;
}

Binary file not shown.

Binary file not shown.