diff options
Diffstat (limited to 'src/hook/inject.c')
-rw-r--r-- | src/hook/inject.c | 412 |
1 files changed, 412 insertions, 0 deletions
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 <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sys/user.h> +#include <sys/wait.h> +#include <sys/ptrace.h> +#include <sys/mman.h> +#include <dlfcn.h> +#include <unistd.h> +#include <elf.h> + +#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; +} + + + |