From b97db529be365851633a6cc1c9285d930209049d Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Sun, 22 Jan 2023 02:56:48 +0100 Subject: hook stash: a1af9ba65d57be898e079a98229845f42069c1e6 --- CMakeLists.txt | 2 +- cmake/FileEmbed.cmake | 12 ++ src/CMakeLists.txt | 8 + src/cli/commands.c | 33 ++- src/hook/CMakeLists.txt | 22 ++ src/hook/arch.c | 42 ++++ src/hook/arch.h | 9 + src/hook/hook.c | 46 +++++ src/hook/hook.h | 10 + src/hook/inject.c | 412 +++++++++++++++++++++++++++++++++++++ src/hook/inject.h | 10 + src/hook/memory.c | 115 +++++++++++ src/hook/memory.h | 13 ++ src/hook/module.c | 179 ++++++++++++++++ src/hook/module.h | 20 ++ src/hook/payloads/CMakeLists.txt | 36 ++++ src/hook/payloads/SysLoadLibrary.c | 99 +++++++++ src/proc.c | 71 +++++++ src/proc.h | 8 + src/steam.c | 154 +++++--------- src/steam.h | 2 +- src/threading/CMakeLists.txt | 2 +- 22 files changed, 1198 insertions(+), 107 deletions(-) create mode 100644 cmake/FileEmbed.cmake create mode 100644 src/hook/CMakeLists.txt create mode 100644 src/hook/arch.c create mode 100644 src/hook/arch.h create mode 100644 src/hook/hook.c create mode 100644 src/hook/hook.h create mode 100644 src/hook/inject.c create mode 100644 src/hook/inject.h create mode 100644 src/hook/memory.c create mode 100644 src/hook/memory.h create mode 100644 src/hook/module.c create mode 100644 src/hook/module.h create mode 100644 src/hook/payloads/CMakeLists.txt create mode 100644 src/hook/payloads/SysLoadLibrary.c create mode 100644 src/proc.c create mode 100644 src/proc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 51ef549..524f1cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.14) if(CMAKE_POLICY_DEFAULT_CMP0017 OR CMAKE_POLICY_DEFAULT_CMP0020) # touch these to remove warnings diff --git a/cmake/FileEmbed.cmake b/cmake/FileEmbed.cmake new file mode 100644 index 0000000..27e963e --- /dev/null +++ b/cmake/FileEmbed.cmake @@ -0,0 +1,12 @@ +get_filename_component(bin_name ${bin_in} NAME) +string(REGEX REPLACE "\\.| |-" "_" bin_name ${bin_name}) + +set(c_out ${bin_name}.c) +set(h_out ${bin_name}.h) + +file(READ ${bin_in} filedata HEX) + +string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," filedata ${filedata}) + +file(WRITE ${c_out} "#include \nconst unsigned char ${bin_name}[] = {${filedata}};\nconst size_t ${bin_name}_size = sizeof(${bin_name});\n") +file(WRITE ${h_out} "extern const char ${bin_name}[];\nextern const size_t ${bin_name}_size;\n") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f3d8a13..0dda750 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,12 +23,19 @@ list(APPEND ${CMAKE_CURRENT_SOURCE_DIR}/fs.h ${CMAKE_CURRENT_SOURCE_DIR}/net.c ${CMAKE_CURRENT_SOURCE_DIR}/net.h + ${CMAKE_CURRENT_SOURCE_DIR}/proc.c + ${CMAKE_CURRENT_SOURCE_DIR}/proc.h ${CMAKE_CURRENT_SOURCE_DIR}/steam.c ${CMAKE_CURRENT_SOURCE_DIR}/steam.h ${CMAKE_CURRENT_SOURCE_DIR}/toast.c ${CMAKE_CURRENT_SOURCE_DIR}/toast.h ) +if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_compile_definitions(HAS_HOOKS) + add_subdirectory(hook) +endif() + add_library(tvn STATIC ${CORE_SOURCES}) target_compile_options(tvn PUBLIC ${CFLAGS}) @@ -39,6 +46,7 @@ target_link_libraries(tvn LINK_PUBLIC ${LIBCURL_LIBRARIES}) target_link_libraries(tvn LINK_PUBLIC ${JSONC_LIBRARIES}) target_link_libraries(tvn LINK_PUBLIC md5) target_link_libraries(tvn LINK_PUBLIC vdf) +target_link_libraries(tvn LINK_PUBLIC hook) if (WIN32) target_link_libraries(tvn LINK_PUBLIC shlwapi) diff --git a/src/cli/commands.c b/src/cli/commands.c index 08b7ed4..7146eb4 100644 --- a/src/cli/commands.c +++ b/src/cli/commands.c @@ -4,15 +4,23 @@ #include "steam.h" #include "toast.h" +#include "proc.h" #include "commands.h" #include "updater.h" +#ifdef HAS_HOOKS +#include "hook.h" +#endif + #define ARRAY_LEN(arr) sizeof(arr) / sizeof(arr[0]) static int install(int, char**); static int update(int, char**); static int run(int, char**); +#ifdef HAS_HOOKS +static int hook(int, char**); +#endif static int version(int, char**); static int info(int, char**); @@ -22,6 +30,9 @@ const struct Command commands[] = { { .name = "install", .func = install, .description = "Install OpenFortress"}, { .name = "update", .func = update, .description = "Update an existing install"}, { .name = "run", .func = run, .description = "Run OpenFortress"}, +#ifdef HAS_HOOKS + { .name = "hook", .func = hook, .description = "Hook into OpenFortress to fix things"}, +#endif { .name = "version", .func = version, .description = "Display the OFCL version"}, { .name = "info", .func = info, .description = "Show info about the current setup"}, }; @@ -210,17 +221,17 @@ static int run(int c, char** v) if (!strcmp(v[arg_index]+2, "direct")) { - + launch_func = runOpenFortressDirect; } else if (!strcmp(v[arg_index]+2, "naive")) { - + launch_func = runOpenFortressNaive; } else if (!strcmp(v[arg_index]+2, "steam")) { - + launch_func = runOpenFortressSteam; } else @@ -264,6 +275,22 @@ static int run(int c, char** v) return exit_val; } +#ifdef HAS_HOOKS +static int hook(int c, char** v) +{ + if (getPid("hl2_linux") == -1) + { + //puts("OpenFortress is not running"); + //return 1; + } + + //puts("trying to fix Sys_LoadLibary"); + fix_SysLoadLibary(); + + return 0; +} +#endif + static int version(int c, char** v) { puts(VERSION); diff --git a/src/hook/CMakeLists.txt b/src/hook/CMakeLists.txt new file mode 100644 index 0000000..79a7186 --- /dev/null +++ b/src/hook/CMakeLists.txt @@ -0,0 +1,22 @@ + +# get all modules first + +list(APPEND + HOOK_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/arch.c + ${CMAKE_CURRENT_SOURCE_DIR}/arch.h + ${CMAKE_CURRENT_SOURCE_DIR}/hook.c + ${CMAKE_CURRENT_SOURCE_DIR}/hook.h + ${CMAKE_CURRENT_SOURCE_DIR}/inject.c + ${CMAKE_CURRENT_SOURCE_DIR}/inject.h + ${CMAKE_CURRENT_SOURCE_DIR}/memory.c + ${CMAKE_CURRENT_SOURCE_DIR}/memory.h + ${CMAKE_CURRENT_SOURCE_DIR}/module.c + ${CMAKE_CURRENT_SOURCE_DIR}/module.h +) +add_subdirectory(payloads) + +add_library(hook STATIC ${HOOK_SOURCES}) +target_compile_options(hook PUBLIC ${CFLAGS}) +target_include_directories(hook PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(hook PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/hook/arch.c b/src/hook/arch.c new file mode 100644 index 0000000..19c37e9 --- /dev/null +++ b/src/hook/arch.c @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "arch.h" + +unsigned char getProcessBits(pid_t pid) +{ + char exe_path[PATH_MAX]; + snprintf(exe_path, sizeof(exe_path), "/proc/%i/exe", pid); + + return getElfBits(exe_path); +} + +unsigned char getElfBits(const char* path) +{ + if (!path) + return 0; + + FILE* fd = fopen(path, "rb"); + if (!fd) + return 0; + + char indent[5]; + fread(indent, sizeof(*indent), sizeof(indent), fd); + fclose(fd); + + if (indent[0] != 0x7f) + return 0; + + if (indent[1] != 'E' || + indent[2] != 'L' || + indent[3] != 'F') + return 0; + + if (indent[4] == 1) + return 32; + else if (indent[4] == 2) + return 64; + + return 0; +} \ No newline at end of file diff --git a/src/hook/arch.h b/src/hook/arch.h new file mode 100644 index 0000000..8e81ad8 --- /dev/null +++ b/src/hook/arch.h @@ -0,0 +1,9 @@ +#ifndef ARCH_H +#define ARCH_H + +#include + +unsigned char getProcessBits(pid_t pid); +unsigned char getElfBits(const char* path); + +#endif \ No newline at end of file diff --git a/src/hook/hook.c b/src/hook/hook.c new file mode 100644 index 0000000..b677a79 --- /dev/null +++ b/src/hook/hook.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +#include "hook.h" +#include "inject.h" +#include "memory.h" +#include "module.h" + +#include "payloads/libSysLoadLibrary_so.h" + +// defined in tvn:proc.c +extern pid_t getPid(const char*); + +static void writeToDisk(const char* path, const char* content, const size_t size) +{ + FILE* fd = fopen(path, "wb"); + fwrite(content, sizeof(*content), size, fd); + fclose(fd); +} + +int fix_SysLoadLibary() +{ + pid_t pid = getPid("loop_forever"); + printf("%i\n", pid); + if (pid != -1) + { + char* path = "/tmp/SysLoadLibrary.so"; + + fprintf(stderr, "[~] writing library to disk\n"); + writeToDisk(path, libSysLoadLibrary_so, libSysLoadLibrary_so_size); + + //inject_syscall(pid, 1, (void*)21, NULL, NULL, NULL, NULL, NULL); + + fprintf(stderr, "[~] loading library into process\n"); + int ret = load_library(pid, path); + if (!ret) + fprintf(stderr, "[*] Success\n"); + else if (ret == 1) + fprintf(stderr, "[!] library already loaded\n"); + else + fprintf(stderr, "[!] could not load libary\n"); + } + return 0; +} \ No newline at end of file diff --git a/src/hook/hook.h b/src/hook/hook.h new file mode 100644 index 0000000..f31cdfd --- /dev/null +++ b/src/hook/hook.h @@ -0,0 +1,10 @@ +#ifndef HOOK_H +#define HOOK_H + +#ifndef HAS_HOOKS +#error hooks have not been enabled +#endif + +int fix_SysLoadLibary(); + +#endif \ No newline at end of file diff --git a/src/hook/inject.c b/src/hook/inject.c new file mode 100644 index 0000000..34c078c --- /dev/null +++ b/src/hook/inject.c @@ -0,0 +1,412 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inject.h" +#include "memory.h" +#include "module.h" +#include "arch.h" + +#define Elf64W(type) Elf64_ ## type +#define Elf32W(type) Elf32_ ## type + +#define Elf64Ehdr Elf64W(Ehdr) +#define Elf32Ehdr Elf32W(Ehdr) + +#define Elf64Shdr Elf64W(Shdr) +#define Elf32Shdr Elf32W(Shdr) + +#define Elf64Sym Elf64W(Sym) +#define Elf32Sym Elf32W(Sym) + +static uintptr_t getSymbolOffset(const char* elf_path, const char* symbol_name) +{ + unsigned char bits = getElfBits(elf_path); + + FILE* fd = fopen(elf_path, "rb"); + fseek(fd, 0, SEEK_END); + size_t elf_size = (size_t) ftell(fd); + rewind(fd); + + char* elf_body = malloc(elf_size); + fread(elf_body, 1, elf_size, fd); + fclose(fd); + + if (bits == 64) + { + Elf64Ehdr* header = (Elf64Ehdr*)elf_body; + + Elf64Shdr* section = (Elf64Shdr*)(elf_body + header->e_shoff); + Elf64Shdr* symtab_section = NULL; + for (uintptr_t i = 0; i <= header->e_shnum; i++) + { + if (i == header->e_shnum) + return 0; + + if (section[i].sh_type == SHT_SYMTAB) + { + symtab_section = section+i; + break; + } + } + + Elf64Sym* symtab = (Elf64Sym*)(elf_body + symtab_section->sh_offset); + size_t symbol_num = symtab_section->sh_size / symtab_section->sh_entsize; + char *symbol_names = (char *)(elf_body + section[symtab_section->sh_link].sh_offset); + + for (size_t j = 0; j < symbol_num; ++j) + { + char* name = symbol_names + symtab[j].st_name; + size_t symbol_name_len = strlen(symbol_name); + if (strncmp(name, symbol_name, symbol_name_len)) + continue; + if (name[symbol_name_len] != '\0' && name[symbol_name_len] != '@') + continue; + + if (symtab[j].st_value > 0) + { + uintptr_t value = symtab[j].st_value; + free(elf_body); + return value; + } + } + } + else if (bits == 32) + { + Elf32Ehdr* header = (Elf32Ehdr*)elf_body; + + Elf32Shdr* section = (Elf32Shdr*)(elf_body + header->e_shoff); + Elf32Shdr* symtab_section = NULL; + for (uintptr_t i = 0; i <= header->e_shnum; i++) + { + if (i == header->e_shnum) + return 0; + + if (section[i].sh_type == SHT_SYMTAB) + { + symtab_section = section+i; + break; + } + } + + Elf32Sym* symtab = (Elf32Sym*)(elf_body + symtab_section->sh_offset); + size_t symbol_num = symtab_section->sh_size / symtab_section->sh_entsize; + char *symbol_names = (char *)(elf_body + section[symtab_section->sh_link].sh_offset); + + for (size_t j = 0; j < symbol_num; ++j) + { + char* name = symbol_names + symtab[j].st_name; + size_t symbol_name_len = strlen(symbol_name); + if (strncmp(name, symbol_name, symbol_name_len)) + continue; + if (name[symbol_name_len] != '\0' && name[symbol_name_len] != '@') + continue; + + if (symtab[j].st_value > 0) + { + uintptr_t value = symtab[j].st_value; + free(elf_body); + return value; + } + } + } + + free(elf_body); + + return 0; +} + +void* inject_syscall( + pid_t pid, + uintptr_t syscall_n, + void* arg0, + void* arg1, + void* arg2, + void* arg3, + void* arg4, + void* arg5 +){ + void* ret = (void*)-1; + int status; + struct user_regs_struct old_regs, regs; + void* injection_addr = (void*)-1; + + //This buffer is our payload, which will run a syscall properly on x86/x64 + unsigned char injection_buf[] = + { + 0xff, 0xff, // placerholder + /* these nops are here because + * we're going to write memory using + * ptrace, and it always writes the size + * of a word, which means we have to make + * sure the buffer is long enough + */ + 0x90, //nop + 0x90, //nop + 0x90, //nop + 0x90, //nop + 0x90, //nop + 0x90 //nop + }; + + unsigned char bits = getProcessBits(pid); + if (bits == 64) + { + //syscall + injection_buf[0] = 0x0f; + injection_buf[1] = 0x05; + } + else if (bits == 32) + { + //int80 (syscall) + injection_buf[0] = 0xcd; + injection_buf[1] = 0x80; + } + else + { + return NULL; + } + + //As ptrace will always write a uintptr_t, let's make sure we're using proper buffers + uintptr_t old_data; + uintptr_t injection_buffer; + memcpy(&injection_buffer, injection_buf, sizeof(injection_buffer)); + + //Attach to process using 'PTRACE_ATTACH' + ptrace(PTRACE_ATTACH, pid, NULL, NULL); + wait(&status); + + /* Get the current registers using 'PTRACE_GETREGS' so that + * we can restore the execution later + * and also modify the bytes of EIP/RIP + */ + + ptrace(PTRACE_GETREGS, pid, NULL, &old_regs); + regs = old_regs; + + //Now, let's set up the registers that will be injected into the tracee + +#if defined(__i386__) + regs.eax = (uintptr_t)syscall_n; + regs.ebx = (uintptr_t)arg0; + regs.ecx = (uintptr_t)arg1; + regs.edx = (uintptr_t)arg2; + regs.esi = (uintptr_t)arg3; + regs.edi = (uintptr_t)arg4; + regs.ebp = (uintptr_t)arg5; + injection_addr = (void*)regs.eip; +#elif defined(__x86_64__) + if (bits == 64) + { + regs.rax = (uintptr_t)syscall_n; + regs.rdi = (uintptr_t)arg0; + regs.rsi = (uintptr_t)arg1; + regs.rdx = (uintptr_t)arg2; + regs.r10 = (uintptr_t)arg3; + regs.r8 = (uintptr_t)arg4; + regs.r9 = (uintptr_t)arg5; + injection_addr = (void*)regs.rip; + } + else if (bits == 32) + { + regs.rax = (uintptr_t)syscall_n; + regs.rbx = (uintptr_t)arg0; + regs.rcx = (uintptr_t)arg1; + regs.rdx = (uintptr_t)arg2; + regs.rsi = (uintptr_t)arg3; + regs.rdi = (uintptr_t)arg4; + regs.rbp = (uintptr_t)arg5; + injection_addr = (void*)regs.rip; + } +#endif + + //Let's store the buffer at EIP/RIP that we're going to modify into 'old_data' using 'PTRACE_PEEKDATA' + old_data = (uintptr_t)ptrace(PTRACE_PEEKDATA, pid, injection_addr, NULL); + + //Let's write our payload into the EIP/RIP of the target process using 'PTRACE_POKEDATA' + ptrace(PTRACE_POKEDATA, pid, injection_addr, injection_buffer); + + //Let's inject our modified registers into the target process using 'PTRACE_SETREGS' + ptrace(PTRACE_SETREGS, pid, NULL, ®s); + + //Let's run a single step in the target process (execute one assembly instruction) + ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL); + waitpid(pid, &status, WSTOPPED); //Wait for the instruction to run + + //Let's get the registers after the syscall to store the return value + ptrace(PTRACE_GETREGS, pid, NULL, ®s); +#if defined(__i386__) + ret = (void*)regs.eax; +#elif defined(__x86_64__) + ret = (void*)regs.rax; +#endif + + long long ret_int = (long long)ret; + + if (ret_int < 0) + fprintf(stderr, "syscall error: %s\n", strerror((int)-ret_int)); + + //Let's write the old data at EIP/RIP + ptrace(PTRACE_POKEDATA, pid, (void*)injection_addr, old_data); + + //Let's restore the old registers to continue the normal execution + ptrace(PTRACE_SETREGS, pid, NULL, &old_regs); + ptrace(PTRACE_DETACH, pid, NULL, NULL); //Detach and continue the execution + + return ret; +} + + +/** + * Return values: + * 1 already loaded + * 2 arch error + */ +int load_library(pid_t pid, char* lib_path) +{ + /* Let's get the address of the 'libc_dlopen_mode' of the target process + * and store it on 'dlopen_ex' by loading the LIBC of the target process + * on here and then getting the offset of its own '__libc_dlopen_mode'. + * Then we sum this offset to the base of the external LIBC module + */ + struct module_s lib_mod = getModule(pid, lib_path); + if (lib_mod.size) + return 1; + + struct module_s libc_ex = getModule(pid, "/libc.so"); + uintptr_t offset = getSymbolOffset(libc_ex.path, "__libc_dlopen_mode"); + + // fallback + if (!offset) + offset = getSymbolOffset(libc_ex.path, "dlopen"); + + fprintf(stderr, "%li\n", offset); + + //Get the external '__libc_dlopen_mode' by summing the offset to the libc_ex.base + void* dlopen_ex = (void*)((uintptr_t)libc_ex.base + offset); + + freeModule(&libc_ex); + + //--- Now let's go to the injection part + + int status; + struct user_regs_struct old_regs, regs; + unsigned char inj_buf_x64[] = + { + /* On 'x64', we dont have to pass anything to the stack, as we're only + * using 2 parameters, which will be stored on RDI (library path address) and + * RSI (flags, in this case RTLD_LAZY). + * This means we just have to call the __libc_dlopen_mode function, which + * will be on RAX. + */ + + 0xFF, 0xD0, //call rax + 0xCC, //int3 (SIGTRAP) + }; + + unsigned char inj_buf_x86[] = + { + /* We have to pass the parameters to the stack (in reversed order) + * The register 'ebx' will store the library path address and the + * register 'ecx' will store the flag (RTLD_LAZY) + * After pushing the parameters to the stack, we will call EAX, which + * will store the address of '__libc_dlopen_mode' + */ + 0x51, //push ecx + 0x53, //push ebx + 0xFF, 0xD0, //call eax + 0xCC, //int3 (SIGTRAP) + }; + + unsigned char* inj_buf = NULL; + size_t sizeof_inj_buf = 0; + + unsigned char bits = getProcessBits(pid); + if (bits == 64) + { + inj_buf = inj_buf_x64; + sizeof_inj_buf = sizeof(inj_buf_x64); + } + else if (bits == 32) + { + inj_buf = inj_buf_x86; + sizeof_inj_buf = sizeof(inj_buf_x86); + } + else + { + fprintf(stderr, "Could not figure out what injection buffer to use\n"); + return 2; + } + + //Let's allocate memory for the payload and the library path + size_t lib_path_len = strlen(lib_path) + 1; + size_t inj_size = sizeof_inj_buf + lib_path_len; + void* inj_addr = allocate_memory(pid, inj_size, PROT_EXEC | PROT_READ | PROT_WRITE); + void* path_addr = (void*)((uintptr_t)inj_addr + sizeof_inj_buf); + + //Write the memory to our allocated address + write_memory(pid, inj_addr, inj_buf, sizeof_inj_buf); + write_memory(pid, path_addr, (void*)lib_path, lib_path_len); + + //Attach to the target process + ptrace(PTRACE_ATTACH, pid, NULL, NULL); + wait(&status); + + //Get the current registers to restore later + ptrace(PTRACE_GETREGS, pid, NULL, &old_regs); + regs = old_regs; + + //Let's setup the registers according to our payload +# if defined(__i386__) + regs.eax = (long)dlopen_ex; + regs.ebx = (long)path_addr; + regs.ecx = (long)RTLD_LAZY; + regs.eip = (long)inj_addr; //The execution will continue from 'inj_addr' (EIP) +# elif defined(__x86_64__) + if (bits == 64) + { + regs.rax = (uintptr_t)dlopen_ex; + regs.rdi = (uintptr_t)path_addr; + regs.rsi = (uintptr_t)RTLD_LAZY; + regs.rip = (uintptr_t)inj_addr; //The execution will continue from 'inj_addr' (RIP) + } + else if (bits == 32) + { + regs.rax = (uintptr_t)dlopen_ex; + regs.rbx = (uintptr_t)path_addr; + regs.rcx = (uintptr_t)RTLD_LAZY; + regs.rip = (uintptr_t)inj_addr; //The execution will continue from 'inj_addr' (RIP) + } +# endif + + //Inject the modified registers to the target process + ptrace(PTRACE_SETREGS, pid, NULL, ®s); + + //Continue the execution + ptrace(PTRACE_CONT, pid, NULL, NULL); + + //Wait for the int3 (SIGTRAP) breakpoint + waitpid(pid, &status, WSTOPPED); + + //Set back the old registers + ptrace(PTRACE_SETREGS, pid, NULL, &old_regs); + + //Detach from the process and continue the execution + ptrace(PTRACE_DETACH, pid, NULL, NULL); + + //Deallocate the memory we allocated for the injection buffer and the library path + deallocate_memory(pid, inj_addr, inj_size); + + return 0; +} + + + diff --git a/src/hook/inject.h b/src/hook/inject.h new file mode 100644 index 0000000..e82c445 --- /dev/null +++ b/src/hook/inject.h @@ -0,0 +1,10 @@ +#ifndef INJECT_H +#define INJECT_H + +#include +#include + +void* inject_syscall(pid_t pid, uintptr_t syscall_n, void*, void*, void*, void*, void*, void*); +int load_library(pid_t pid, char* lib_path); + +#endif \ No newline at end of file diff --git a/src/hook/memory.c b/src/hook/memory.c new file mode 100644 index 0000000..eedf728 --- /dev/null +++ b/src/hook/memory.c @@ -0,0 +1,115 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include "memory.h" +#include "inject.h" +#include "arch.h" + +#define mmap_x64 9 +#define munmap_x64 11 +#define mmap2_x86 192 +#define munmap_x86 91 + +void read_memory(pid_t pid, void* src, void* dst, size_t size) +{ + /* + pid = target process id + src = address to read from on the target process + dst = address to write to on the caller process + size = size of the buffer that will be read + */ + + struct iovec iosrc; + struct iovec iodst; + iodst.iov_base = dst; + iodst.iov_len = size; + iosrc.iov_base = src; + iosrc.iov_len = size; + + if (process_vm_readv(pid, &iodst, 1, &iosrc, 1, 0) == -1) + fprintf(stderr, "process_vm_readv: %s\n", strerror(errno)); +} + +void write_memory(pid_t pid, void* dst, void* src, size_t size) +{ + /* + pid = target process id + dst = address to write to on the target process + src = address to read from on the caller process + size = size of the buffer that will be read + */ + + struct iovec iosrc; + struct iovec iodst; + iosrc.iov_base = src; + iosrc.iov_len = size; + iodst.iov_base = dst; + iodst.iov_len = size; + + if(process_vm_writev(pid, &iosrc, 1, &iodst, 1, 0) == -1) + fprintf(stderr, "process_vm_writev: %s\n", strerror(errno)); +} + +void* allocate_memory(pid_t pid, size_t size, int protection) +{ + //mmap template: + //void *mmap (void *__addr, size_t __len, int __prot, int __flags, int __fd, __off_t __offset); + + void* ret = (void*)-1; + + unsigned char bits = getProcessBits(pid); + + if (bits == 32) + { + ret = inject_syscall( + pid, + mmap2_x86, + //arguments + (void*)0, + (void*)size, + (void*)(uintptr_t)protection, + (void*)(MAP_ANON | MAP_PRIVATE), + (void*)-1, + (void*)0 + ); + } + else if (bits == 64) + { + ret = inject_syscall( + pid, + mmap_x64, + //arguments + (void*)0, + (void*)size, + (void*)(uintptr_t)protection, + (void*)(MAP_ANON | MAP_PRIVATE), + (void*)-1, + (void*)0 + ); + } + + return ret; +} + +void deallocate_memory(pid_t pid, void* src, size_t size) +{ + unsigned char bits = getProcessBits(pid); + if (bits == 64) + inject_syscall(pid, munmap_x64, src, (void*)size, NULL, NULL, NULL, NULL); + else if (bits == 32) + inject_syscall(pid, munmap_x86, src, (void*)size, NULL, NULL, NULL, NULL); +} + +void* protect_memory(pid_t pid, void* src, size_t size, int protection) +{ + //mprotect template + //int mprotect (void *__addr, size_t __len, int __prot); + return inject_syscall(pid, __NR_mprotect, src, (void*)size, (void*)(uintptr_t)protection, NULL, NULL, NULL); +} diff --git a/src/hook/memory.h b/src/hook/memory.h new file mode 100644 index 0000000..cf1f74c --- /dev/null +++ b/src/hook/memory.h @@ -0,0 +1,13 @@ +#ifndef MEMORY_H +#define MEMORY_H + +#include + +void read_memory(pid_t pid, void* src, void* dst, size_t size); +void write_memory(pid_t pid, void* dst, void* src, size_t size); + +void* allocate_memory(pid_t pid, size_t size, int protection); +void deallocate_memory(pid_t pid, void* src, size_t size); +void* protect_memory(pid_t pid, void* src, size_t size, int protection); + +#endif \ No newline at end of file diff --git a/src/hook/module.c b/src/hook/module.c new file mode 100644 index 0000000..41be1ee --- /dev/null +++ b/src/hook/module.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include + +#include "module.h" +#include "arch.h" + +#define fprintf(...) + +#define NOTFOUND (size_t)(-1) +#define PROCMAPS_LINE_MAX_LENGTH (PATH_MAX + 100) + +static void* strtoptr(pid_t pid, const char* nptr, char** endptr, int base) +{ + unsigned char bits = getProcessBits(pid); + if (bits == 64) + return (void*)strtoull(nptr, endptr, base); + else if (bits == 32) + return (void*)strtoul(nptr, endptr, base); + + return NULL; + +} +static size_t find(const char* a, const char* b, uintptr_t offset) +{ + const char* pos = a + offset; + + while (*pos && strncmp(pos, b, strlen(b))) + ++pos; + + if (!*pos) + return NOTFOUND; + + return (size_t)(pos - a); +} + +static size_t rfind(const char* a, const char* b, size_t offset) +{ + const char* pos = a + (strlen(a) - strlen(b) - offset); + + while (a <= pos && strncmp(pos, b, strlen(b))) + --pos; + + if (pos < a) + return NOTFOUND; + + return (size_t)(pos - a); +} + +static char* substr(const char* str, size_t pos, size_t len) +{ + str = str + pos; + + if (len == NOTFOUND) + len = strlen(str); + + char* buf = malloc(len+1); + strncpy(buf, str, len); + buf[len] = '\0'; + + return buf; +} + +struct module_s getModule(pid_t pid, const char* module_name) +{ + struct module_s mod = {0}; + + char maps_file_path[PATH_MAX]; + snprintf(maps_file_path, sizeof(maps_file_path), "/proc/%i/maps", pid); + fprintf(stderr, "Maps file path: %s\n", maps_file_path); + + + FILE* maps_file_fs = fopen(maps_file_path, "rb"); + if (!maps_file_fs) return mod; + + char maps_file[PROCMAPS_LINE_MAX_LENGTH]; + while( !feof(maps_file_fs) ){ + if (fgets(maps_file, sizeof(maps_file), maps_file_fs) == NULL){ + fprintf(stderr,"fgets failed, %s\n",strerror(errno)); + return mod; + } + size_t module_path_pos = 0; + size_t module_path_end = 0; + + //Get the first slash in the line of the module name + module_path_pos = find(maps_file, module_name, 0); + if (module_path_pos == NOTFOUND) + continue; + + module_path_pos = find(maps_file, "/", 0); + + //Get the end of the line of the module name + module_path_end = find(maps_file, "\n", module_path_pos); + + if(module_path_pos == NOTFOUND || module_path_end == NOTFOUND) continue; + + //Module path substring + char* module_path_str = substr(maps_file, module_path_pos, module_path_end - module_path_pos); + + fprintf(stderr, "Module path string: %s\n", module_path_str); + + //--- Module name + + char* module_name_str = substr(module_path_str, + rfind(module_path_str, "/", 0) + 1, //Substring from the last '/' to the end of the string + NOTFOUND + ); + + fprintf(stderr, "Module name: %s\n", module_name_str); + + //--- Base Address + + size_t base_address_pos = rfind(maps_file, "\n", module_path_pos) + 1; + size_t base_address_end = find(maps_file, "-", base_address_pos); + if(base_address_pos == NOTFOUND || base_address_end == NOTFOUND) continue; + char* base_address_str = substr(maps_file, base_address_pos, base_address_end - base_address_pos); + void* base_address = (void*)strtoptr(pid, base_address_str, NULL, 16);; + if (!base_address) return mod; + + fprintf(stderr, "Base Address: %p\n", base_address); + + //--- End Address + size_t end_address_pos; + size_t end_address_end; + char* end_address_str; + void* end_address; + + //Get end address pos + end_address_pos = rfind(maps_file, module_path_str, 0); + end_address_pos = rfind(maps_file, "\n", end_address_pos) + 1; + end_address_pos = find(maps_file, "-", end_address_pos) + 1; + + //Find first space from end_address_pos + end_address_end = find(maps_file, " ", end_address_pos); + + if(end_address_pos == NOTFOUND || end_address_end == NOTFOUND) continue; + + //End address substring + end_address_str = substr(maps_file, end_address_pos, end_address_end - end_address_pos); + end_address = (void*)strtoptr(pid, end_address_str, NULL, 16); + free(end_address_str); + + fprintf(stderr, "End Address: %p\n", end_address); + + //--- Module size + + uintptr_t module_size = (uintptr_t)end_address - (uintptr_t)base_address; + fprintf(stderr, "End Address: %li\n", module_size); + + mod.name = module_name_str; + mod.path = module_path_str; + mod.base = base_address; + mod.size = module_size; + mod.end = end_address; + + break; + } + + //--- Module Path + + + //--- + + //Now we put all the information we got into the mod structure + + + fclose(maps_file_fs); + + return mod; +} + +void freeModule(struct module_s* mod) +{ + free(mod->name); + free(mod->path); +} diff --git a/src/hook/module.h b/src/hook/module.h new file mode 100644 index 0000000..7fb83e1 --- /dev/null +++ b/src/hook/module.h @@ -0,0 +1,20 @@ +#ifndef MODULE_H +#define MODULE_H + +#include +#include + +struct module_s +{ + char* name; + char* path; + void* base; + void* end; + uintptr_t size; + void* handle; //this will not be used for now, only internally with dlopen +}; + +struct module_s getModule(pid_t pid, const char* module_name); +void freeModule(struct module_s* mod); + +#endif \ No newline at end of file diff --git a/src/hook/payloads/CMakeLists.txt b/src/hook/payloads/CMakeLists.txt new file mode 100644 index 0000000..fe3968e --- /dev/null +++ b/src/hook/payloads/CMakeLists.txt @@ -0,0 +1,36 @@ + +list(APPEND + modules + SysLoadLibrary +) + +foreach(module ${modules}) + set(module_name "${module}") + add_library(${module} SHARED "${module}.c") + # OF is 32 bit, so we need to be too + set_target_properties(${module} PROPERTIES COMPILE_OPTIONS "-m32" LINK_FLAGS "-m32") + + + list(APPEND + module_embed_output + ${CMAKE_CURRENT_BINARY_DIR}/lib${module_name}_so.c + ${CMAKE_CURRENT_BINARY_DIR}/lib${module_name}_so.h + ) + # embed + add_custom_command( + OUTPUT ${module_embed_output} + COMMAND ${CMAKE_COMMAND} + "-Dbin_in=$" + -P ${CMAKE_SOURCE_DIR}/cmake/FileEmbed.cmake + DEPENDS ${module} + ) + add_library(${module_name}_embed OBJECT ${module_embed_output}) + list(APPEND + HOOK_SOURCES + $ + ) + + set(module_embed_output "") +endforeach() + +set(HOOK_SOURCES ${HOOK_SOURCES} PARENT_SCOPE) diff --git a/src/hook/payloads/SysLoadLibrary.c b/src/hook/payloads/SysLoadLibrary.c new file mode 100644 index 0000000..314426d --- /dev/null +++ b/src/hook/payloads/SysLoadLibrary.c @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include +#include +#include + +#define _DEBUG 1 +#define Q_IsAbsolutePath V_IsAbsolutePath +#define Assert(f) assert(f) +#define CommandLine CommandLine_Tier0 +#define Msg printf +#define V_strcpy_safe strcpy +#define Q_snprintf snprintf +#define _getcwd getcwd + +typedef void* HMODULE; +typedef int Sys_Flags; +typedef void* CSysModule; + +static bool (*V_IsAbsolutePath)(const char *pPath) = NULL; +static HMODULE (*Sys_LoadLibrary)(const char *pLibraryName, Sys_Flags flags) = NULL; +static char* (*CommandLine__ParmValue)(const char* param) = NULL; + +CSysModule *Sys_LoadModule_replacement( const char *pModuleName, Sys_Flags flags /* = SYS_NOFLAGS (0) */ ) +{ + // If using the Steam filesystem, either the DLL must be a minimum footprint + // file in the depot (MFP) or a filesystem GetLocalCopy() call must be made + // prior to the call to this routine. + char szCwd[1024]; + HMODULE hDLL = NULL; + + if ( !Q_IsAbsolutePath( pModuleName ) ) + { + char szAbsoluteModuleName[1024]; + + // check if the sourcemod provides the library + const char *pGameDir = CommandLine__ParmValue("-game"); + if ( pGameDir ) + { + V_strcpy_safe( szCwd, pGameDir ); + if (szCwd[strlen(szCwd) - 1] == '/' || szCwd[strlen(szCwd) - 1] == '\\' ) + { + szCwd[strlen(szCwd) - 1] = 0; + } + + Q_snprintf( szAbsoluteModuleName, sizeof(szAbsoluteModuleName), "%s/bin/%s", szCwd, pModuleName ); + + hDLL = Sys_LoadLibrary( szAbsoluteModuleName, flags ); + } + + // if the lib was not in the mod check the base game + if (!hDLL) + { + // full path wasn't passed in, using the current working dir + Assert( _getcwd( szCwd, sizeof( szCwd ) ) ); + + if (szCwd[strlen(szCwd) - 1] == '/' || szCwd[strlen(szCwd) - 1] == '\\' ) + { + szCwd[strlen(szCwd) - 1] = 0; + } + + size_t cCwd = strlen( szCwd ); + if ( strstr( pModuleName, "bin/") == pModuleName || ( szCwd[ cCwd - 1 ] == 'n' && szCwd[ cCwd - 2 ] == 'i' && szCwd[ cCwd - 3 ] == 'b' ) ) + { + // don't make bin/bin path + Q_snprintf( szAbsoluteModuleName, sizeof(szAbsoluteModuleName), "%s/%s", szCwd, pModuleName ); + } + else + { + Q_snprintf( szAbsoluteModuleName, sizeof(szAbsoluteModuleName), "%s/bin/%s", szCwd, pModuleName ); + } + hDLL = Sys_LoadLibrary( szAbsoluteModuleName, flags ); + } + } + + if ( !hDLL ) + { + // full path failed, let LoadLibrary() try to search the PATH now + hDLL = Sys_LoadLibrary( pModuleName, flags ); +#if defined( _DEBUG ) + if ( !hDLL ) + { + // So you can see what the error is in the debugger... + Msg( "Failed to load %s: %s\n", pModuleName, dlerror() ); + } +#endif // DEBUG + } + + return hDLL; +} + + +void __attribute__((constructor)) lib_entry() +{ + //It prints "Injected!" once the library gets loaded. + fprintf(stderr, "Injected!\n"); +} diff --git a/src/proc.c b/src/proc.c new file mode 100644 index 0000000..2d74a1f --- /dev/null +++ b/src/proc.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#endif + +#include "proc.h" + +pid_t getPid(const char* process_name) +{ +#if defined(__linux__) || defined(__FreeBSD__) + +#ifdef __FreeBSD__ + // on FreeBSD /proc is not mounted by default + if (!isDir("/proc")) + return -1; +#endif + + pid_t pid; + char buf[PATH_MAX]; + struct dirent* ent; + DIR* proc = opendir("/proc"); + FILE* stat; + + if (proc) + { + while ((ent = readdir(proc)) != NULL) + { + long lpid = atol(ent->d_name); + if (!lpid) continue; + + snprintf(buf, sizeof(buf), "/proc/%ld/stat", lpid); + stat = fopen(buf, "r"); + + if (stat && (fscanf(stat, "%i (%[^)])", &pid, buf)) == 2) + { + if (!strcmp(buf, process_name)) + { + fclose(stat); + closedir(proc); + return pid; + } + fclose(stat); + } + } + + closedir(proc); + } + +#elif _WIN32 + HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + PROCESSENTRY32 pe32 = {0}; + pe32.dwSize = sizeof(PROCESSENTRY32); + Process32First(hSnap,&pe32); + + while(Process32Next(hSnap,&pe32)) + { + if (!strcmp(pe32.szExeFile, process_name)) + return (long)pe32.th32ProcessID; + } +#else + #error No Implementation +#endif + return -1; +} diff --git a/src/proc.h b/src/proc.h new file mode 100644 index 0000000..5990117 --- /dev/null +++ b/src/proc.h @@ -0,0 +1,8 @@ +#ifndef PROC_H +#define PROC_H + +#include + +pid_t getPid(const char*); + +#endif \ No newline at end of file diff --git a/src/steam.c b/src/steam.c index 77542e4..6bcf11a 100644 --- a/src/steam.c +++ b/src/steam.c @@ -1,16 +1,15 @@ #include #include #include -#include -#include #include +#include #include "vdf.h" +#include "proc.h" #include "steam.h" -#ifdef _WIN32 -#include -#include +#ifdef HAS_HOOKS +#include "hook.h" #endif /** @@ -181,122 +180,75 @@ char* getSourceSDK2013MpDir(void) * function to fetch the PID of a running Steam process. * If none were found returns -1 */ -long getSteamPID(void) +pid_t getSteamPID(void) { -#if defined(__linux__) || defined(__FreeBSD__) - -#ifdef __FreeBSD__ - // on FreeBSD /proc is not mounted by default - if (!isDir("/proc")) - return -1; -#endif - - long pid; - char buf[PATH_MAX]; - struct dirent* ent; - DIR* proc = opendir("/proc"); - FILE* stat; - - if (proc) - { - while ((ent = readdir(proc)) != NULL) - { - long lpid = atol(ent->d_name); - if (!lpid) continue; - - snprintf(buf, sizeof(buf), "/proc/%ld/stat", lpid); - stat = fopen(buf, "r"); - - if (stat && (fscanf(stat, "%li (%[^)])", &pid, buf)) == 2) - { - if (!strcmp(buf, STEAM_PROC)) - { - fclose(stat); - closedir(proc); - return pid; - } - fclose(stat); - } - } - - closedir(proc); - } - -#elif _WIN32 - HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - PROCESSENTRY32 pe32 = {0}; - pe32.dwSize = sizeof(PROCESSENTRY32); - Process32First(hSnap,&pe32); - - while(Process32Next(hSnap,&pe32)) - { - if (!strcmp(pe32.szExeFile, STEAM_PROC)) - return (long)pe32.th32ProcessID; - } -#else - #error No Implementation -#endif - return -1; + return getPid(STEAM_PROC); } int runOpenFortressDirect(char** args, size_t arg_count) { - int in_fork = 0; -#if defined(__linux__) || defined(__FreeBSD__) - // fork so we don't have to stay alive for the game - if (fork()) return 0; - in_fork = 1; -#endif - char* game = getSourceSDK2013MpDir(); - if (!game) - { - if (in_fork) exit(0); - else return 0; - } + pid_t pid; - char* of_dir = getOpenFortressDir(); - if (!of_dir) + if ((pid = fork())) { - free(game); - if (in_fork) exit(0); - else return 0; + // parent +#ifdef HAS_HOOKS + //fix_SysLoadLibary(pid); +#endif + return 0; } + else + { + // child + char* game = getSourceSDK2013MpDir(); + if (!game) + { + exit(1); + } - game = realloc(game, strlen(game) + strlen(OS_PATH_SEP) + strlen(HL2_EXE) + 1); - strcat(game, OS_PATH_SEP); - strcat(game, HL2_EXE); + char* of_dir = getOpenFortressDir(); + if (!of_dir) + { + free(game); + exit(1); + } + + game = realloc(game, strlen(game) + strlen(OS_PATH_SEP) + strlen(HL2_EXE) + 1); + strcat(game, OS_PATH_SEP); + strcat(game, HL2_EXE); #if defined(__linux__) || defined(__FreeBSD__) - // we need to be in the steam environment to get the right locales - setenv("SteamEnv", "1", 1); + // we need to be in the steam environment to get the right locales + setenv("SteamEnv", "1", 1); #endif - char** argv = malloc(sizeof(char*) * (arg_count + 6)); + char** argv = malloc(sizeof(char*) * (arg_count + 6)); #ifdef _WIN32 - size_t of_dir_len = strlen(of_dir); - of_dir = realloc(of_dir, of_dir_len + 3); - memmove(of_dir+1, of_dir, of_dir_len); - of_dir[0] = '"'; - of_dir[of_dir_len+1] = '"'; - of_dir[of_dir_len+2] = '\0'; + size_t of_dir_len = strlen(of_dir); + of_dir = realloc(of_dir, of_dir_len + 3); + memmove(of_dir+1, of_dir, of_dir_len); + of_dir[0] = '"'; + of_dir[of_dir_len+1] = '"'; + of_dir[of_dir_len+2] = '\0'; #endif - argv[0] = game; - argv[1] = "-game"; - argv[2] = of_dir; - argv[3] = "-secure"; - argv[4] = "-steam"; - for (size_t i = 0; i < arg_count; ++i) - argv[5+i] = args[i]; - argv[5+arg_count] = NULL; + argv[0] = game; + argv[1] = "-game"; + argv[2] = of_dir; + argv[3] = "-secure"; + argv[4] = "-steam"; + for (size_t i = 0; i < arg_count; ++i) + argv[5+i] = args[i]; + argv[5+arg_count] = NULL; - execv(game, argv); + execv(game, argv); - free(game); - free(of_dir); + free(game); + free(of_dir); - exit(0); + exit(0); + } } int runOpenFortressNaive(char** args, size_t arg_count) diff --git a/src/steam.h b/src/steam.h index 27345a9..8fa7a0f 100644 --- a/src/steam.h +++ b/src/steam.h @@ -55,7 +55,7 @@ char* getSourcemodDir(void); char* getOpenFortressDir(void); char* getAppInstallDir(const char* appid); char* getSourceSDK2013MpDir(void); -long getSteamPID(void); +pid_t getSteamPID(void); int runOpenFortressDirect(char**, size_t); int runOpenFortressNaive(char**, size_t); diff --git a/src/threading/CMakeLists.txt b/src/threading/CMakeLists.txt index b2bd203..cb0c20d 100644 --- a/src/threading/CMakeLists.txt +++ b/src/threading/CMakeLists.txt @@ -2,7 +2,7 @@ set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) list(APPEND - THREADING_SOURCES + THREADING_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cpu.c ${CMAKE_CURRENT_SOURCE_DIR}/cpu.h ${CMAKE_CURRENT_SOURCE_DIR}/pool.c -- cgit v1.2.3