/* -*- indent-tabs-mode: nil -*- * * This file is part of Funchook. * https://github.com/kubo/funchook * * Funchook is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 2 of the License, or (at your * option) any later version. * * As a special exception, the copyright holders of this library give you * permission to link this library with independent modules to produce an * executable, regardless of the license terms of these independent * modules, and to copy and distribute the resulting executable under * terms of your choice, provided that you also meet, for each linked * independent module, the terms and conditions of the license of that * module. An independent module is a module which is not derived from or * based on this library. If you modify this library, you may extend this * exception to your version of the library, but you are not obliged to * do so. If you do not wish to do so, delete this exception statement * from your version. * * Funchook is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License * along with Funchook. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #ifdef __linux #include #include #endif #ifdef __APPLE__ #include #include #endif #include "funchook_io.h" #include "funchook_internal.h" #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) #define MAP_ANONYMOUS MAP_ANON #endif const size_t page_size = PAGE_SIZE; funchook_t *funchook_alloc(void) { size_t size = ROUND_UP(funchook_size, page_size); void *mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mem == (void*)-1) { return NULL; } return (funchook_t*)mem; } int funchook_free(funchook_t *funchook) { size_t size = ROUND_UP(funchook_size, page_size); munmap(funchook, size); return 0; } #ifdef CPU_X86_64 typedef struct memory_map memory_map_t; static int memory_map_open(funchook_t *funchook, memory_map_t *mmap); static int memory_map_next(memory_map_t *mmap, size_t *start, size_t *end); static void memory_map_close(memory_map_t *mmap); #if defined(__linux) static char scan_address(const char **str, size_t *addr_p) { size_t addr = 0; const char *s = *str; while (1) { char c = *(s++); if ('0' <= c && c <= '9') { addr = (addr * 16) + (c - '0'); } else if ('a' <= c && c <= 'f') { addr = (addr * 16) + (c - 'a' + 10); } else { *str = s; *addr_p = addr; return c; } } } struct memory_map { funchook_io_t io; }; static int memory_map_open(funchook_t *funchook, memory_map_t *mm) { char buf[64]; if (funchook_io_open(&mm->io, "/proc/self/maps", FUNCHOOK_IO_READ) != 0) { funchook_set_error_message(funchook, "Failed to open /proc/self/maps (%s)", funchook_strerror(errno, buf, sizeof(buf))); return FUNCHOOK_ERROR_INTERNAL_ERROR; } return 0; } static int memory_map_next(memory_map_t *mm, size_t *start, size_t *end) { char buf[PATH_MAX]; const char *str = buf; if (funchook_io_gets(buf, sizeof(buf), &mm->io) == NULL) { return -1; } if (scan_address(&str, start) != '-') { return -1; } if (scan_address(&str, end) != ' ') { return -1; } return 0; } static void memory_map_close(memory_map_t *mm) { funchook_io_close(&mm->io); } #elif defined(__APPLE__) struct memory_map { mach_port_t task; vm_address_t addr; }; static int memory_map_open(funchook_t *funchook, memory_map_t *mm) { mm->task = mach_task_self(); mm->addr = 0; return 0; } static int memory_map_next(memory_map_t *mm, size_t *start, size_t *end) { vm_size_t size; vm_region_basic_info_data_64_t info; mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; memory_object_name_t object = 0; if (vm_region_64(mm->task, &mm->addr, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&info, &info_count, &object) != KERN_SUCCESS) { return -1; } *start = mm->addr; *end = mm->addr + size; mm->addr += size; return 0; } static void memory_map_close(memory_map_t *mm) { return; } #else #error unsupported OS #endif static int get_free_address(funchook_t *funchook, void *func_addr, void *addrs[2]) { memory_map_t mm; size_t prev_end = 0; size_t start, end; int rv; if ((rv = memory_map_open(funchook, &mm)) != 0) { return rv; } addrs[0] = addrs[1] = NULL; while (memory_map_next(&mm, &start, &end) == 0) { funchook_log(funchook, " process map: %0"SIZE_T_WIDTH SIZE_T_FMT"x-%0"SIZE_T_WIDTH SIZE_T_FMT"x\n", start, end); if (prev_end + page_size <= start) { if (start < (size_t)func_addr) { size_t addr = start - page_size; if ((size_t)func_addr - addr < INT32_MAX) { /* unused memory region before func_addr. */ addrs[0] = (void*)addr; } } if ((size_t)func_addr < prev_end) { if (prev_end - (size_t)func_addr < INT32_MAX) { /* unused memory region after func_addr. */ addrs[1] = (void*)prev_end; } funchook_log(funchook, " -- Use address %p or %p for function %p\n", addrs[0], addrs[1], func_addr); memory_map_close(&mm); return 0; } } prev_end = end; } memory_map_close(&mm); funchook_set_error_message(funchook, "Could not find a free region near %p", func_addr); return FUNCHOOK_ERROR_MEMORY_ALLOCATION; } #endif /* CPU_X86_64 */ int funchook_page_alloc(funchook_t *funchook, funchook_page_t **page_out, uint8_t *func, rip_displacement_t *disp) { #ifdef CPU_X86_64 int loop_cnt; /* Loop three times just to avoid rare cases such as * unused memory region is used between 'get_free_address()' * and 'mmap()'. */ for (loop_cnt = 0; loop_cnt < 3; loop_cnt++) { void *addrs[2]; int rv = get_free_address(funchook, func, addrs); int i; if (rv != 0) { return rv; } for (i = 1; i >= 0; i--) { /* Try to use addr[1] (unused memory region after `func`) * and then addr[0] (before `func`) */ if (addrs[i] == NULL) { continue; } *page_out = mmap(addrs[i], page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (*page_out == addrs[i]) { funchook_log(funchook, " allocate page %p (size=%"SIZE_T_FMT"u)\n", *page_out, page_size); return 0; } if (*page_out == MAP_FAILED) { char errbuf[128]; funchook_set_error_message(funchook, "mmap failed(addr=%p): %s", addrs[i], funchook_strerror(errno, errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_ALLOCATION; } funchook_log(funchook, " try to allocate %p but %p (size=%"SIZE_T_FMT"u)\n", addrs[i], *page_out, page_size); munmap(*page_out, page_size); } } funchook_set_error_message(funchook, "Failed to allocate memory in unused regions"); return FUNCHOOK_ERROR_MEMORY_ALLOCATION; #else char errbuf[128]; *page_out = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (*page_out != MAP_FAILED) { funchook_log(funchook, " allocate page %p (size=%"SIZE_T_FMT"u)\n", *page_out, page_size); return 0; } funchook_set_error_message(funchook, "mmap failed: %s", funchook_strerror(errno, errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_ALLOCATION; #endif } int funchook_page_free(funchook_t *funchook, funchook_page_t *page) { char errbuf[128]; int rv = munmap(page, page_size); if (rv == 0) { funchook_log(funchook, " deallocate page %p (size=%"SIZE_T_FMT"u)\n", page, page_size); return 0; } funchook_set_error_message(funchook, "Failed to deallocate page %p (size=%"SIZE_T_FMT"u, error=%s)", page, page_size, funchook_strerror(errno, errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } int funchook_page_protect(funchook_t *funchook, funchook_page_t *page) { char errbuf[128]; int rv = mprotect(page, page_size, PROT_READ | PROT_EXEC); if (rv == 0) { funchook_log(funchook, " protect page %p (size=%"SIZE_T_FMT"u)\n", page, page_size); return 0; } funchook_set_error_message(funchook, "Failed to protect page %p (size=%"SIZE_T_FMT"u, error=%s)", page, page_size, funchook_strerror(errno, errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } int funchook_page_unprotect(funchook_t *funchook, funchook_page_t *page) { char errbuf[128]; int rv = mprotect(page, page_size, PROT_READ | PROT_WRITE); if (rv == 0) { funchook_log(funchook, " unprotect page %p (size=%"SIZE_T_FMT"u)\n", page, page_size); return 0; } funchook_set_error_message(funchook, "Failed to unprotect page %p (size=%"SIZE_T_FMT"u, error=%s)", page, page_size, funchook_strerror(errno, errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } int funchook_unprotect_begin(funchook_t *funchook, mem_state_t *mstate, void *start, size_t len) { static int prot = PROT_READ | PROT_WRITE | PROT_EXEC; char errbuf[128]; size_t saddr = ROUND_DOWN((size_t)start, page_size); int rv; mstate->addr = (void*)saddr; mstate->size = len + (size_t)start - saddr; mstate->size = ROUND_UP(mstate->size, page_size); rv = mprotect(mstate->addr, mstate->size, prot); if (rv == 0) { funchook_log(funchook, " unprotect memory %p (size=%"SIZE_T_FMT"u, prot=read,write%s) <- %p (size=%"SIZE_T_FMT"u)\n", mstate->addr, mstate->size, (prot & PROT_EXEC) ? ",exec" : "", start, len); return 0; } if (rv == -1 && errno == EACCES && (prot & PROT_EXEC)) { rv = mprotect(mstate->addr, mstate->size, PROT_READ | PROT_WRITE); if (rv == 0) { prot = PROT_READ | PROT_WRITE; funchook_log(funchook, " unprotect memory %p (size=%"SIZE_T_FMT"u, prot=read,write) <- %p (size=%"SIZE_T_FMT"u)\n", mstate->addr, mstate->size, start, len); return 0; } } funchook_set_error_message(funchook, "Failed to unprotect memory %p (size=%"SIZE_T_FMT"u, prot=read,write%s) <- %p (size=%"SIZE_T_FMT"u, error=%s)", mstate->addr, mstate->size, (prot & PROT_EXEC) ? ",exec" : "", start, len, funchook_strerror(errno, errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } int funchook_unprotect_end(funchook_t *funchook, const mem_state_t *mstate) { char errbuf[128]; int rv = mprotect(mstate->addr, mstate->size, PROT_READ | PROT_EXEC); if (rv == 0) { funchook_log(funchook, " protect memory %p (size=%"SIZE_T_FMT"u, prot=read,exec)\n", mstate->addr, mstate->size); return 0; } funchook_set_error_message(funchook, "Failed to protect memory %p (size=%"SIZE_T_FMT"u, prot=read,exec, error=%s)", mstate->addr, mstate->size, funchook_strerror(errno, errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } void *funchook_resolve_func(funchook_t *funchook, void *func) { #ifdef __GLIBC__ struct link_map *lmap, *lm; const ElfW(Ehdr) *ehdr; const ElfW(Dyn) *dyn; const ElfW(Sym) *symtab = NULL; const ElfW(Sym) *symtab_end = NULL; const char *strtab = NULL; size_t strtab_size = 0; int i; lmap = NULL; for (lm = _r_debug.r_map; lm != NULL; lm = lm->l_next) { if ((void*)lm->l_addr <= func) { if (lmap == NULL) { lmap = lm; } else if (lmap->l_addr > lm->l_addr) { lmap = lm; } } } if (lmap == NULL) { return func; } if (lmap->l_addr != 0) { ehdr = (ElfW(Ehdr) *)lmap->l_addr; if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) { funchook_log(funchook, " not a valid ELF module %s.\n", lmap->l_name); return func; } if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) { funchook_log(funchook, " ELF type is neither ET_EXEC nor ET_DYN.\n"); return func; } } funchook_log(funchook, " link_map addr=%p, name=%s\n", (void*)lmap->l_addr, lmap->l_name); dyn = lmap->l_ld; for (i = 0; dyn[i].d_tag != DT_NULL; i++) { switch (dyn[i].d_tag) { case DT_SYMTAB: symtab = (const ElfW(Sym) *)dyn[i].d_un.d_ptr; break; case DT_STRTAB: strtab = (const char *)dyn[i].d_un.d_ptr; break; case DT_STRSZ: strtab_size = dyn[i].d_un.d_val; break; } } symtab_end = (const ElfW(Sym) *)strtab; while (symtab < symtab_end) { if (symtab->st_name >= strtab_size) { break; } if (ELF64_ST_TYPE(symtab->st_info) == STT_FUNC && symtab->st_size == 0 && (void*)symtab->st_value == func) { void *fn = dlsym(RTLD_DEFAULT, strtab + symtab->st_name); if (fn == func) { fn = dlsym(RTLD_NEXT, strtab + symtab->st_name); } if (fn != NULL) { funchook_log(funchook, " change %s address from %p to %p\n", strtab + symtab->st_name, func, fn); func = fn; } break; } symtab++; } #endif return func; } #ifndef HAVE_DECL__SYS_NERR #define HAVE_DECL__SYS_NERR 0 #endif #ifndef HAVE_DECL__SYS_ERRLIST #define HAVE_DECL__SYS_ERRLIST 0 #endif #ifndef HAVE_DECL_SYS_NERR #define HAVE_DECL_SYS_NERR 0 #endif #ifndef HAVE_DECL_SYS_ERRLIST #define HAVE_DECL_SYS_ERRLIST 0 #endif const char *funchook_strerror(int errnum, char *buf, size_t buflen) { #if HAVE_DECL__SYS_NERR && HAVE_DECL__SYS_ERRLIST if (0 <= errnum && errnum < _sys_nerr) { return _sys_errlist[errnum]; } #elif HAVE_DECL_SYS_NERR && HAVE_DECL_SYS_ERRLIST if (0 <= errnum && errnum < sys_nerr) { return sys_errlist[errnum]; } #else switch (errnum) { #undef E #define E(err, msg) case err: return msg; #include "__strerror.h" /* header file copied from musl libc */ } #endif funchook_snprintf(buf, buflen, "Unknown error (%d)", errnum); return buf; }