Skip to content

Kernel

Kernel

Systemcalls

Creating a custom syscall

https://medium.com/@aryan20/create-custom-system-call-on-linux-6-8-126edef6caaf

Drivers

Implementing Drivers in other Languages

Kernel Modules

https://github.com/sysprog21/lkmpg
https://sysprog21.github.io/lkmpg/
https://xcellerator.github.io/posts/linux_rootkits_11/

Basic Kernel Module:

#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");

#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

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 int (*orig_open_t) (const char*, int, int);
orig_open_t orig_open;

/* 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_open(const char* file, int flags, int mode)
{
    printk("A file was opened\n");
    return orig_open(file, flags, mode);
}

/* 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);
}

static inline unsigned long sys_call_table_lookup(){

#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

    long unsigned int syscall_table = kallsyms_lookup_name("sys_call_table");
    if(syscall_table != 0){
        return syscall_table;
    }

    // Find by checking through memory for table matching known function pointer 
    unsigned long int i;

    for (i = (unsigned long int)sys_close; i < ULONG_MAX; i += sizeof(void *)) {
        syscall_table = i;

        if (syscall_table[__NR_close] == (unsigned long)sys_close)
            return syscall_table;
    }
    return NULL;
}


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

    /* Grab the function pointer to the real sys_open syscall */
    orig_open = (orig_open_t)__sys_call_table[__NR_open];

    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: open @ 0x%lx\n", orig_open);
    
    unprotect_memory();

    printk(KERN_INFO "rootkit: hooking open syscall\n");
    /* Patch the function pointer to sys_open with our hook instead */
    __sys_call_table[__NR_open] = (unsigned long)hook_open;

    protect_memory();

    return 0;
}

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

module_init(rootkit_init);
module_exit(rootkit_exit);

Generate Kernel Module:

>>> cat 
obj-m += hello-1.o
VERS=$(shell uname -r)
VERS=5.15.24-1-lts
PWD := $(CURDIR)

all:
        make -C /lib/modules/$(VERS)/build M=$(PWD) modules

clean:
        make -C /lib/modules/$(VERS)/build M=$(PWD) clean
>>>  sudo -E make
make -C /lib/modules/5.15.24-1-lts/build M=/tmp/kern_mod  modules
make[1]: Entering directory '/usr/lib/modules/5.15.24-1-lts/build'
  CC [M]  /tmp/kern_mod/hello-1.o
  MODPOST /tmp/kern_mod/Module.symvers
  CC [M]  /tmp/kern_mod/hello-1.mod.o
  LD [M]  /tmp/kern_mod/hello-1.ko
  BTF [M] /tmp/kern_mod/hello-1.ko
make[1]: Leaving directory '/usr/lib/modules/5.15.24-1-lts/build'

Get Kernel Mod Info:

>>> modinfo hello-1.ko
filename:       /tmp/kern_mod/hello-1.ko
license:        GPL
srcversion:     6EEFF9BA2D82E6F0305588C
depends:
retpoline:      Y
name:           hello_1
vermagic:       5.15.24-1-lts SMP mod_unload

Run Kernel Module:

#Run Module
sudo insmod hello-1.ko

#Check that is is running
sudo lsmod | grep hello

#Remove
sudo rmmod hello_1

IORing

https://chomp.ie/Blog+Posts/Put+an+io_uring+on+it+-+Exploiting+the+Linux+Kernel
https://ruia-ruia.github.io/2022/08/05/CVE-2022-29582-io-uring/

Kernel Debugging

https://github.com/0xricksanchez/like-dbg