commit 21bf4d8715939feafa21b717ac33bc2a66c2c5e2 Author: Kasper Date: Sun Mar 16 18:46:12 2025 +0200 Init. Copied over heload, made a minimal tester. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94a7adf --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.idea +*.log +tmp/ + +**/build +**/compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e7d1a1b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.21) +project(Hiload) + +# I just like to have this with my tooling +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_C_STANDARD 11) + +# hiload library +# ############## + +add_library(hiload SHARED + src/hiload.c + src/symbols.c +) + +set_property(TARGET hiload PROPERTY POSITION_INDEPENDENT_CODE ON) +target_link_libraries(hiload dl) + +# Specify the public headers location +target_include_directories(hiload PUBLIC + $ # During build + $ # When installed +) + +install(TARGETS hiload + EXPORT hiloadTargets + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +# Install header files +install(DIRECTORY include/ DESTINATION include) + +# Export the library for find_package() +install(EXPORT hiloadTargets + FILE hiloadConfig.cmake + DESTINATION lib/cmake/hiload +) + +export(TARGETS hiload FILE hiloadConfig.cmake) + +# auditor libraries +# ############### + +add_library(auditor-x86_64 SHARED + src/auditor/auditor-x86_64.c +) + +install(TARGETS auditor-x86_64 + EXPORT auditor-x86_64Targets + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) diff --git a/include/hiload/hiload.h b/include/hiload/hiload.h new file mode 100644 index 0000000..e436ffe --- /dev/null +++ b/include/hiload/hiload.h @@ -0,0 +1,25 @@ +#ifndef HILOAD_H_ +#define HILOAD_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// Return codes for reload_module +typedef enum { + RELOAD_SUCCESS = 0, + RELOAD_NOT_FOUND, + RELOAD_CLOSE_ERROR, + RELOAD_OPEN_ERROR +} ReloadResult; + +int he_init(); +void he_deinit(); +void he_print_module_infos(); +ReloadResult he_reload_module(const char *module_name); + +#ifdef __cplusplus +} +#endif + +#endif // HILOAD_H_ diff --git a/include/hiload/symbols.h b/include/hiload/symbols.h new file mode 100644 index 0000000..c7a533e --- /dev/null +++ b/include/hiload/symbols.h @@ -0,0 +1,22 @@ +#ifndef SYMBOLS_H_ +#define SYMBOLS_H_ + +#include +#include +#include + +typedef struct { + char **names; + void **addresses; + size_t count; + size_t capacity; +} SymbolInfos; + +typedef enum { CREATE_SUCCESS = 0, CREATE_FAILED } CreateResult; + +struct dl_phdr_info; + +CreateResult he_create_symbol_info(SymbolInfos *, struct dl_phdr_info *); +void he_free_symbol_info(SymbolInfos *); + +#endif // SYMBOLS_H_ diff --git a/src/auditor/auditor-x86_64.c b/src/auditor/auditor-x86_64.c new file mode 100644 index 0000000..d191884 --- /dev/null +++ b/src/auditor/auditor-x86_64.c @@ -0,0 +1,81 @@ +#define _GNU_SOURCE + +#include +#include + +unsigned int la_version(unsigned int version) { + printf("la_version(): version = %u; LAV_CURRENT = %u\n", version, + LAV_CURRENT); + + return LAV_CURRENT; +} + +char *la_objsearch(const char *name, uintptr_t *cookie, unsigned int flag) { + printf("la_objsearch(): name = %s; cookie = %p", name, cookie); + printf("; flag = %s\n", (flag == LA_SER_ORIG) ? "LA_SER_ORIG" + : (flag == LA_SER_LIBPATH) ? "LA_SER_LIBPATH" + : (flag == LA_SER_RUNPATH) ? "LA_SER_RUNPATH" + : (flag == LA_SER_DEFAULT) ? "LA_SER_DEFAULT" + : (flag == LA_SER_CONFIG) ? "LA_SER_CONFIG" + : (flag == LA_SER_SECURE) ? "LA_SER_SECURE" + : "???"); + + return (char *)name; +} + +void la_activity(uintptr_t *cookie, unsigned int flag) { + printf("la_activity(): cookie = %p; flag = %s\n", cookie, + (flag == LA_ACT_CONSISTENT) ? "LA_ACT_CONSISTENT" + : (flag == LA_ACT_ADD) ? "LA_ACT_ADD" + : (flag == LA_ACT_DELETE) ? "LA_ACT_DELETE" + : "???"); +} + +unsigned int la_objopen(struct link_map *map, Lmid_t lmid, uintptr_t *cookie) { + printf("la_objopen(): loading \"%s\"; lmid = %s; cookie=%p\n", map->l_name, + (lmid == LM_ID_BASE) ? "LM_ID_BASE" + : (lmid == LM_ID_NEWLM) ? "LM_ID_NEWLM" + : "???", + cookie); + + return LA_FLG_BINDTO | LA_FLG_BINDFROM; +} + +unsigned int la_objclose(uintptr_t *cookie) { + printf("la_objclose(): %p\n", cookie); + + return 0; +} + +void la_preinit(uintptr_t *cookie) { printf("la_preinit(): %p\n", cookie); } + +uintptr_t la_symbind32(Elf32_Sym *sym, unsigned int ndx, uintptr_t *refcook, + uintptr_t *defcook, unsigned int *flags, + const char *symname) { + printf("la_symbind64(): symname = %s; sym->st_value = %p\n", symname, + sym->st_value); + printf(" ndx = %u; flags = %#x", ndx, *flags); + printf("; refcook = %p; defcook = %p\n", refcook, defcook); + + return sym->st_value; +} + +uintptr_t la_symbind64(Elf64_Sym *sym, unsigned int ndx, uintptr_t *refcook, + uintptr_t *defcook, unsigned int *flags, + const char *symname) { + printf("la_symbind64(): symname = %s; sym->st_value = %p\n", symname, + (void*)sym->st_value); + printf(" ndx = %u; flags = %#x", ndx, *flags); + printf("; refcook = %p; defcook = %p\n", refcook, defcook); + + return sym->st_value; +} + +Elf64_Addr la_i86_gnu_pltenter(Elf64_Sym *sym, unsigned int ndx, + uintptr_t *refcook, uintptr_t *defcook, + La_x86_64_regs *regs, unsigned int *flags, + const char *symname, long *framesizep) { + printf("la_x86_64_gnu_pltenter(): %s (%p)\n", symname, (void*)sym->st_value); + + return sym->st_value; +} diff --git a/src/auditor/auditor.h b/src/auditor/auditor.h new file mode 100644 index 0000000..c3349a3 --- /dev/null +++ b/src/auditor/auditor.h @@ -0,0 +1,26 @@ +#ifndef AUDITOR_H_ +#define AUDITOR_H_ + +#define _GNU_SOURCE +#include +#include + +unsigned int la_version(unsigned int version); +char *la_objsearch(const char *name, uintptr_t *cookie, unsigned int flag); +void la_activity(uintptr_t *cookie, unsigned int flag); +unsigned int la_objopen(struct link_map *map, Lmid_t lmid, uintptr_t *cookie); +unsigned int la_objclose(uintptr_t *cookie); +void la_preinit(uintptr_t *cookie); +uintptr_t la_symbind32(Elf32_Sym *sym, unsigned int ndx, uintptr_t *refcook, + uintptr_t *defcook, unsigned int *flags, + const char *symname); +uintptr_t la_symbind64(Elf64_Sym *sym, unsigned int ndx, uintptr_t *refcook, + uintptr_t *defcook, unsigned int *flags, + const char *symname); +/* Elf32_Addr la_i86_gnu_pltenter(Elf32_Sym *sym, unsigned int ndx, */ +/* uintptr_t *refcook, uintptr_t *defcook, */ +/* La_i86_regs *regs, unsigned int *flags, */ +/* const char *symname, long *framesizep); */ + + +#endif // AUDITOR_H_ diff --git a/src/hiload.c b/src/hiload.c new file mode 100644 index 0000000..c34cd19 --- /dev/null +++ b/src/hiload.c @@ -0,0 +1,266 @@ +// Required for dlinfo and other GNU specific linker code +#define _GNU_SOURCE + +#include "hiload/hiload.h" +#include "hiload/symbols.h" + +#include +#include +#include +#include +#include +#include + +typedef struct { + char **names; // Array of library names + void **handles; // Array of library handles + SymbolInfos *symbols; // Symbol info for modules + size_t count; // Number of libraries + size_t capacity; // Allocated capacity +} ModuleInfos; + +static ModuleInfos *module_infos = 0; + +// Callback function for dl_iterate_phdr +static int gather_module_infos_callback(struct dl_phdr_info *info, size_t size, + void *data) { + ModuleInfos *infos = (ModuleInfos *)data; + + // Resize arrays if needed + if (infos->count >= infos->capacity) { + infos->capacity *= 2; + char **new_names = realloc(infos->names, infos->capacity * sizeof(char *)); + void **new_handles = + realloc(infos->handles, infos->capacity * sizeof(void *)); + SymbolInfos *new_symbols = + realloc(infos->symbols, infos->capacity * sizeof(SymbolInfos)); + + if (!new_names || !new_handles || !new_symbols) { + return 1; // Stop iteration on error + } + + infos->names = new_names; + infos->handles = new_handles; + infos->symbols = new_symbols; + } + + // Store the module name + infos->names[infos->count] = strdup(info->dlpi_name); + if (!infos->names[infos->count]) { + return 1; // Stop iteration on error + } + + // Try to get the handle + infos->handles[infos->count] = + dlopen(info->dlpi_name, RTLD_LAZY | RTLD_NOLOAD); + + if (he_create_symbol_info(&infos->symbols[infos->count], info) != CREATE_SUCCESS) { + fprintf(stderr, "Failed to create symbol info for %s\n", infos->names[infos->count]); + } + infos->count++; + return 0; // Continue iteration +} + +static void free_module_infos(ModuleInfos *modules) { + if (!modules) + return; + + for (size_t i = 0; i < modules->count; i++) { + if (modules->names[i]) + free(modules->names[i]); + he_free_symbol_info(&modules->symbols[i]); + } + + free(modules->names); + free(modules->handles); + free(modules->symbols); + free(modules); +} + +static ModuleInfos *gather_shared_libraries(void) { + ModuleInfos *result = NULL; + + // Allocate the result structure + result = calloc(1, sizeof(ModuleInfos)); + if (!result) { + return NULL; + } + + // Initial capacity + result->capacity = 16; + result->names = calloc(result->capacity, sizeof(char *)); + result->handles = calloc(result->capacity, sizeof(void *)); + result->symbols = calloc(result->capacity, sizeof(SymbolInfos)); + + if (!result->names || !result->handles || !result->symbols) { + if (result->names) + free(result->names); + if (result->handles) + free(result->handles); + if (result->symbols) + free(result->symbols); + free(result); + return NULL; + } + + // Iterate over all shared objects + if (dl_iterate_phdr(gather_module_infos_callback, result) != 0) { + // Error occurred in callback + free_module_infos(result); + return NULL; + } + + return result; +} + +int he_init() { + assert(!module_infos); + + ModuleInfos *infos = gather_shared_libraries(); + if (!infos) { + fprintf(stderr, "Failed to gather module infos.\n"); + return 1; + } + if (module_infos) { + free_module_infos(module_infos); + } + module_infos = infos; + return 0; +} + +void he_deinit() { free_module_infos(module_infos); } + +/** + * Reloads a shared library module + * + * @param modules The ModuleInfos structure containing loaded modules + * @param filename The name of the module to reload + * @param updated_handle Pointer to store the new handle (can be NULL) + * @return ReloadResult indicating success or failure + */ +static ReloadResult reload_module(ModuleInfos *modules, const char *filename, + void **updated_handle) { + if (!modules || !filename) { + return RELOAD_NOT_FOUND; + } + + // Find the module by filename + int found = 0; + size_t index = 0; + + for (size_t i = 0; i < modules->count; i++) { + // Check if this is the module we're looking for + if (modules->names[i] && strcmp(modules->names[i], filename) == 0) { + found = 1; + index = i; + break; + } + + // Also check if the filename is at the end of the path + if (modules->names[i]) { + const char *basename = strrchr(modules->names[i], '/'); + if (basename && strcmp(basename + 1, filename) == 0) { + found = 1; + index = i; + break; + } + } + } + + if (!found) { + return RELOAD_NOT_FOUND; + } + + // Save the full path + char *fullpath = strdup(modules->names[index]); + if (!fullpath) { + return RELOAD_OPEN_ERROR; + } + + // Close the old handle + if (modules->handles[index]) { + if (dlclose(modules->handles[index]) != 0) { + free(fullpath); + return RELOAD_CLOSE_ERROR; + } + } + + // Open the module again with RTLD_NOW + void *new_handle = dlopen(fullpath, RTLD_NOW); + if (!new_handle) { + fprintf(stderr, "Error reloading module: %s\n", dlerror()); + free(fullpath); + return RELOAD_OPEN_ERROR; + } + + // Update the handle in our structure + modules->handles[index] = new_handle; + + // If the caller wants the new handle, provide it + if (updated_handle) { + *updated_handle = new_handle; + } + + free(fullpath); + return RELOAD_SUCCESS; +} +/** + * Helper function to print the result of a module reload + */ +static void print_reload_result(ReloadResult result, const char *filename) { + switch (result) { + case RELOAD_SUCCESS: + printf("Successfully reloaded module: %s\n", filename); + break; + case RELOAD_NOT_FOUND: + printf("Module not found: %s\n", filename); + break; + case RELOAD_CLOSE_ERROR: + printf("Error closing module: %s\n", filename); + break; + case RELOAD_OPEN_ERROR: + printf("Error reopening module: %s\n", filename); + break; + default: + printf("Unknown error reloading module: %s\n", filename); + } +} + +ReloadResult he_reload_module(const char *module_name) { + assert(module_infos); + + void *new_handle = NULL; + ReloadResult result = reload_module(module_infos, module_name, &new_handle); + print_reload_result(result, module_name); + + return result; +} + +void he_print_module_infos() { + assert(module_infos); + + const ModuleInfos *modules = module_infos; + if (!modules) { + printf("No module information available.\n"); + return; + } + + printf("Found %zu loaded modules:\n\n", modules->count); + + for (size_t i = 0; i < modules->count; i++) { + // Get handle information where possible + Dl_info info = {0}; + int has_info = 0; + + printf("%s: %p\n", modules->names[i], modules->handles[i]); + if (modules->symbols) { + for (int j = 0; j < modules->symbols->count; j++) { + const void *addr = modules->symbols->addresses[j]; + const char *name = modules->symbols->names[j]; + printf(" %p: %s\n", addr, name); + } + } + + printf("\n"); + } +} diff --git a/src/symbols.c b/src/symbols.c new file mode 100644 index 0000000..7005168 --- /dev/null +++ b/src/symbols.c @@ -0,0 +1,115 @@ +#define _GNU_SOURCE +#include "hiload/symbols.h" + +#include +#include +#include +#include +#include +#include +#include + +/** + * Gathers and populates symbols, given a dynamic module info + * + * Will clear and free the given SymbolInfo struct. Allocates enough memory to + * hold found symbols. + */ +CreateResult he_create_symbol_info(SymbolInfos *symbols, + struct dl_phdr_info *info) { + + if (!symbols) + return CREATE_FAILED; + + he_free_symbol_info(symbols); + + for (int i = 0; i < info->dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &info->dlpi_phdr[i]; + + // Look for the dynamic segment + if (phdr->p_type != PT_DYNAMIC) + continue; + + printf("Dynamic Header:\n"); + printf("p_type: %u\n", phdr->p_type); + printf("p_flags: %u\n", phdr->p_flags); + printf("p_offset: %#06x\n", phdr->p_offset); + printf("p_vaddr: %#06x\n", phdr->p_vaddr); + printf("p_paddr: %#06x\n", phdr->p_paddr); + printf("p_filesz: %zu\n", phdr->p_filesz); + printf("p_memsz: %zu\n", phdr->p_memsz); + printf("p_align: %zu\n", phdr->p_align); + + const ElfW(Dyn) *dyn = (const ElfW(Dyn) *)(info->dlpi_addr + phdr->p_vaddr); + const char *strtab = NULL; + const ElfW(Sym) *symtab = NULL; + size_t symtab_size = 0; + size_t strtab_size = 0; + + // Parse the dynamic table + for (; dyn->d_tag != DT_NULL; dyn++) { + if (dyn->d_tag == DT_STRTAB) { + strtab = (const char *)(dyn->d_un.d_ptr); + } else if (dyn->d_tag == DT_STRSZ) { + strtab_size = dyn->d_un.d_val; + } else if (dyn->d_tag == DT_SYMTAB) { + symtab = (const ElfW(Sym) *)(dyn->d_un.d_ptr); + } else if (dyn->d_tag == DT_SYMENT) { + symtab_size = dyn->d_un.d_val; + } + } + + // Ensure we found the symbol and string tables + if (!strtab || !symtab || strtab_size == 0 || symtab_size == 0) { + fprintf(stderr, "Failed to find symbol or string table in %s\n", + info->dlpi_name); + return CREATE_FAILED; + } + + symbols->capacity = symtab_size / sizeof(ElfW(Sym)); + + symbols->names = calloc(symbols->capacity, sizeof(char *)); + if (!symbols->names) { + fprintf(stderr, "Failed to allocate memory for symbol names.\n"); + return CREATE_FAILED; + } + + symbols->addresses = calloc(symbols->capacity, sizeof(void *)); + if (!symbols->addresses) { + fprintf(stderr, "Failed to allocate memory for symbol addresses.\n"); + return CREATE_FAILED; + } + + // Iterate over the symbol table + for (const ElfW(Sym) *sym = symtab; + (const char *)sym < (const char *)symtab + symtab_size; sym++) { + if (ELF64_ST_TYPE(sym->st_info) == STT_FUNC || + ELF64_ST_TYPE(sym->st_info) == STT_OBJECT) { + const char *name = strdup(&strtab[sym->st_name]); + void *address = (void *)(info->dlpi_addr + sym->st_value); + + // Store the symbol information in the struct of arrays + if (symbols->count < symbols->capacity) { + symbols->names[symbols->count] = (char *)name; + symbols->addresses[symbols->count] = address; + symbols->count++; + } else { + fprintf(stderr, "Symbol table capacity exceeded!\n"); + return CREATE_FAILED; + } + } + } + } + + return CREATE_SUCCESS; +} + +void he_free_symbol_info(SymbolInfos *symbols) { + for (size_t i = 0; i < symbols->count; i++) { + free(symbols->names[i]); + } + free(symbols->names); + free(symbols->addresses); + symbols->count = 0; + symbols->capacity = 0; +} diff --git a/test/manual/CMakeLists.txt b/test/manual/CMakeLists.txt new file mode 100644 index 0000000..d79ffee --- /dev/null +++ b/test/manual/CMakeLists.txt @@ -0,0 +1,13 @@ +project(Minimal) + +set( CMAKE_EXPORT_COMPILE_COMMANDS ON ) +add_executable(minimal + minimal.cpp +) + +add_library(mini SHARED + minimal_lib.cpp + minimal_lib.h +) + +target_link_libraries(minimal mini) diff --git a/test/manual/minimal.cpp b/test/manual/minimal.cpp new file mode 100644 index 0000000..83c5a23 --- /dev/null +++ b/test/manual/minimal.cpp @@ -0,0 +1,18 @@ +#include "minimal_lib.h" + +#include +#include +#include + +int main(int argc, char *argv[]) { + + int modified = -1; + while (modified != 0) { + modified = minimal_lib::getModified(5); + printf("getModified(5): %d\n", modified); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + return 0; +} diff --git a/test/manual/minimal_lib.cpp b/test/manual/minimal_lib.cpp new file mode 100644 index 0000000..d771678 --- /dev/null +++ b/test/manual/minimal_lib.cpp @@ -0,0 +1,7 @@ +#include "minimal_lib.h" + +namespace minimal_lib { + +int getModified(int x) { return x + 1; } + +} // namespace minimal_lib diff --git a/test/manual/minimal_lib.h b/test/manual/minimal_lib.h new file mode 100644 index 0000000..c1adf94 --- /dev/null +++ b/test/manual/minimal_lib.h @@ -0,0 +1,11 @@ +#ifndef MINIMAL_LIB_H_ +#define MINIMAL_LIB_H_ + +namespace minimal_lib { + +static int value = 5; + +int getModified(int x); + +} +#endif // MINIMAL_LIB_H_