diff --git a/README.md b/README.md index be8233f..c4e3e19 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ # Linux Kernel Hook -This is just a small project to hook syscalls in an x86_64 Linux kernel. I've tested it on `4.9.0-3-amd64`. It's mostly just a project for me to explore writing kernel modules. -Most of the other kernel hooks I've seen on the public internet use an outdated mechanism to grab the syscall table - the oldest tutorials relied on `sys_call_table` being exported as a public symbol, and slightly newer ones had a brute-forcing approach where they would try to find the syscall table in between two different symbols. This one doesn't do anything that fancy - the `load.sh` script just greps `/proc/kallsyms` for the syscall table addresses. +Syscall hooks for x86_64 and arm32. -## Usage -It comes built in with a `mkdir` hook that just proxies the syscall over to the original syscall. `module.c:69` is responsible for adding the hook, and the code at `hooks.c:8` is the actual hooked function which does the proxying -```bash -make && sh ./load.sh +# Usage ``` +cd +make +cd .. +./load.sh +``` + +The example hook for mkdir prints the argument of the created folder in the kernel logs. + +To unload the module: `rmmod lkh` \ No newline at end of file diff --git a/Makefile b/arm/Makefile similarity index 100% rename from Makefile rename to arm/Makefile diff --git a/arm/hooks.c b/arm/hooks.c new file mode 100644 index 0000000..e8053c0 --- /dev/null +++ b/arm/hooks.c @@ -0,0 +1,16 @@ +#include +#include "hooks.h" +#include "sys_hook.h" + +extern struct sys_hook *lkh_sys_hook; + +asmlinkage int +mkdir_hook(const char *path, int mode) +{ + sys_mkdir_t sys_mkdir; + printk(KERN_INFO "mk_dir hook triggered %s\n", path); + + sys_mkdir = (sys_mkdir_t)sys_hook_get_orig(lkh_sys_hook, __NR_mkdir); + + return sys_mkdir(path, mode); +} diff --git a/hooks.h b/arm/hooks.h similarity index 100% rename from hooks.h rename to arm/hooks.h diff --git a/arm/module.c b/arm/module.c new file mode 100644 index 0000000..2942baf --- /dev/null +++ b/arm/module.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include "sys_hook.h" +#include "hooks.h" + +struct sys_hook *lkh_sys_hook; + +static uintptr_t +hex_addr_to_pointer(const char *str) +{ + uintptr_t sum; + + /* Go through str char by char and accumulate into sum */ + for (sum = 0; *str != '\0'; str++) { + sum *= 16; + if (*str >= '0' && *str <= '9') + sum += (*str - '0'); + else if (*str >= 'a' && *str <= 'f') + sum += (*str - 'a') + 10; + else if (*str >= 'A' && *str <= 'F') + sum += (*str - 'A') + 10; + else + return 0; + } + + return sum; +} + +/* Module parameter macros */ +static char *kbaseArm = NULL; +module_param(kbaseArm, charp, 0); +MODULE_PARM_DESC(kbaseArm, "Base address of the arm syscall table, in hex"); + +static int __init +module_entry(void) +{ + uintptr_t k_arm; + + printk(KERN_INFO "lkh initializing...\n"); + + /* Validate that we got parameters */ + if (kbaseArm == NULL || kbaseArm[0] == '\0') { + printk(KERN_INFO "failed to get arm syscall table\n"); + return 1; + } + + /* Validate that we got valid syscall base addresses */ + if ((k_arm = hex_addr_to_pointer(kbaseArm)) == 0) { + printk(KERN_INFO "invalid arm syscall address %p\n", (void *)k_arm); + return 1; + } + + if ((lkh_sys_hook = sys_hook_init(k_arm)) == NULL) { + printk(KERN_INFO "failed to initialize sys_hook\n"); + return 1; + } + + sys_hook_add(lkh_sys_hook, __NR_mkdir, (void *)mkdir_hook); + + printk(KERN_INFO "lkh loaded\n"); + return 0; +} + +static void __exit +module_cleanup(void) +{ + sys_hook_free(lkh_sys_hook); + printk(KERN_INFO "lkh has finished\n"); +} + +/* Declare the entry and exit points of our module */ +module_init(module_entry); +module_exit(module_cleanup); + +/* Shut up kernel warnings about tainted kernels due to non-free software */ +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("github:jha"); +MODULE_DESCRIPTION("Linux Kernel Hook"); diff --git a/arm/sys_hook.c b/arm/sys_hook.c new file mode 100644 index 0000000..f50404e --- /dev/null +++ b/arm/sys_hook.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include +#include "sys_hook.h" + +struct sys_hook * +sys_hook_init(uintptr_t k_arm) +{ + struct sys_hook *sh; + + sh = kmalloc(sizeof (struct sys_hook), GFP_KERNEL); + if (IS_ERR(sh)) { + printk(KERN_INFO "not enough memory for hooks\n"); + return NULL; + } + + sh->arm_sct = (unsigned int *)k_arm; + + sh->head = sh->tail = NULL; + + return sh; +} + +bool_t +sys_hook_add(struct sys_hook *hook, unsigned int syscall_id, void *func) +{ + struct sys_hook_ent *ent; + + ent = kmalloc(sizeof (struct sys_hook_ent), GFP_KERNEL); + if (IS_ERR(ent)) { + printk(KERN_INFO "not enough memory for sys hook\n"); + return FALSE; + } + + /* Create our new hook entry */ + ent->next = NULL; + ent->syscall_id = syscall_id; + ent->original = hook->arm_sct[syscall_id]; + ent->hooked = (uintptr_t)func; + ent->type = SHT_ARM; + + /* Overwrite the entry in the syscall table */ + hook->arm_sct[syscall_id] = (unsigned int)ent->hooked; + + /* Update the hook list */ + if (hook->head == NULL) + hook->head = hook->tail = ent; + else { + hook->tail->next = ent; + hook->tail = ent; + } + + return TRUE; +} + +bool_t +sys_hook_del(struct sys_hook *hook, unsigned int syscall_id) +{ + return TRUE; +} + +uintptr_t +sys_hook_get_orig(struct sys_hook *hook, unsigned int syscall_id) +{ + struct sys_hook_ent *curr; + + for (curr = hook->head; curr != NULL; curr = curr->next) { + if (curr->type == SHT_ARM && curr->syscall_id == syscall_id) + return curr->original; + } + + + return 0; +} + +void +sys_hook_free(struct sys_hook *hook) +{ + struct sys_hook_ent *curr, *tmp; + + if (hook == NULL) + return; + + for (curr = hook->head; curr != NULL;) { + switch (curr->type) { + case SHT_ARM: + hook->arm_sct[curr->syscall_id] = (unsigned int)curr->original; + break; + default: + printk(KERN_EMERG "possible memory corruption in syscall hooks - invalid hook state\n"); + break; + } + + tmp = curr->next; + kfree(curr); + curr = tmp; + } + + kfree(hook); +} diff --git a/arm/sys_hook.h b/arm/sys_hook.h new file mode 100644 index 0000000..8090a62 --- /dev/null +++ b/arm/sys_hook.h @@ -0,0 +1,34 @@ +#pragma once + +#include "types.h" + +struct sys_hook { + unsigned int *arm_sct; + struct sys_hook_ent *head, *tail; +}; + +enum sys_hook_type { + SHT_ARM, +}; + +struct sys_hook_ent { + struct sys_hook_ent *next; + unsigned int syscall_id; + uintptr_t original, hooked; + enum sys_hook_type type; +}; + +struct sys_hook * +sys_hook_init(uintptr_t k_arm); + +bool_t +sys_hook_add(struct sys_hook *, unsigned int syscall_id, void *func); + +bool_t +sys_hook_del(struct sys_hook *, unsigned int syscall_id); + +uintptr_t +sys_hook_get_orig(struct sys_hook *, unsigned int syscall_id); + +void +sys_hook_free(struct sys_hook *hook); diff --git a/types.h b/arm/types.h similarity index 100% rename from types.h rename to arm/types.h diff --git a/load.sh b/load.sh old mode 100644 new mode 100755 index 787c948..6f03e26 --- a/load.sh +++ b/load.sh @@ -11,5 +11,10 @@ fi KBASE32=$(cat /proc/kallsyms | grep " ia32_sys_call_table" | awk '{ print $1 }') KBASE64=$(cat /proc/kallsyms | grep " sys_call_table" | awk '{ print $1 }') +KBASEARM=$(cat /proc/kallsyms | grep " sys_call_table" | awk '{ print $1 }') -insmod ./lkh.ko kbase32="$KBASE32" kbase64="$KBASE64" +if [ "$(uname -m)" = "x86_64" ]; then + insmod ./x86_64/lkh.ko kbase32="$KBASE32" kbase64="$KBASE64" +else + insmod ./arm/lkh.ko kbaseArm="$KBASEARM" +fi \ No newline at end of file diff --git a/x86_64/Makefile b/x86_64/Makefile new file mode 100644 index 0000000..2a1f7ab --- /dev/null +++ b/x86_64/Makefile @@ -0,0 +1,10 @@ +obj-m := lkh.o +lkh-objs += module.o +lkh-objs += sys_hook.o +lkh-objs += hooks.o + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean diff --git a/hooks.c b/x86_64/hooks.c similarity index 100% rename from hooks.c rename to x86_64/hooks.c diff --git a/x86_64/hooks.h b/x86_64/hooks.h new file mode 100644 index 0000000..4425f0c --- /dev/null +++ b/x86_64/hooks.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include "types.h" + +typedef asmlinkage int (*sys_mkdir_t)(const char *, int); + +asmlinkage int +mkdir_hook(const char *, int); diff --git a/module.c b/x86_64/module.c similarity index 100% rename from module.c rename to x86_64/module.c diff --git a/sys_hook.c b/x86_64/sys_hook.c similarity index 100% rename from sys_hook.c rename to x86_64/sys_hook.c diff --git a/sys_hook.h b/x86_64/sys_hook.h similarity index 100% rename from sys_hook.h rename to x86_64/sys_hook.h diff --git a/x86_64/types.h b/x86_64/types.h new file mode 100644 index 0000000..15c616b --- /dev/null +++ b/x86_64/types.h @@ -0,0 +1,5 @@ +#pragma once + +typedef unsigned int bool_t; +#define FALSE 0 +#define TRUE 1