/* -*- 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 . */ #define PSAPI_VERSION 1 #include #include #include #include #include "funchook_internal.h" typedef struct page_info { struct page_info *next; struct page_info *prev; int num_used; char used[1]; } page_list_t; const size_t page_size = PAGE_SIZE; /* 4K */ const size_t allocation_unit = ALLOCATION_UNIT; /* 64K */ static size_t max_num_pages = ALLOCATION_UNIT / PAGE_SIZE - 1; /* 15 */ static page_list_t page_list = { &page_list, &page_list, }; static const char *to_errmsg(DWORD err, char *buf, size_t bufsiz) { size_t len = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), buf, (DWORD)bufsiz, NULL); if (len == 0) { return "Unknown Error"; } if (len >= bufsiz) { len = bufsiz - 1; } while (len > 0 && (buf[len - 1] == '\r' || buf[len - 1] == '\n')) { len--; } buf[len] = '\0'; return buf; } funchook_t *funchook_alloc(void) { size_t size = ROUND_UP(funchook_size, page_size); return VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE); } int funchook_free(funchook_t *funchook) { VirtualFree(funchook, 0, MEM_RELEASE); return 0; } /* Reserve 64K bytes (allocation_unit) and use the first * 4K bytes (1 page) as the control page. */ static int alloc_page_info(funchook_t *funchook, page_list_t **pl_out, void *hint) { void *addr; page_list_t *pl; #ifdef CPU_64BIT void *old_hint = hint; while (1) { MEMORY_BASIC_INFORMATION mbi; if (VirtualQuery(hint, &mbi, sizeof(mbi)) == 0) { DWORD err = GetLastError(); char errbuf[128]; funchook_set_error_message(funchook, "Failed to execute VirtualQuery (addr=%p, error=%lu(%s))", hint, err, to_errmsg(err, errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } funchook_log(funchook, " process map: %016I64x-%016I64x %s\n", (size_t)mbi.BaseAddress, (size_t)mbi.BaseAddress + mbi.RegionSize, (mbi.State == MEM_FREE) ? "free" : "used"); if (mbi.State == MEM_FREE) { size_t addr = ROUND_UP((size_t)mbi.BaseAddress, allocation_unit); intptr_t diff = addr - (size_t)mbi.BaseAddress; if (diff >= 0) { if (mbi.RegionSize - diff >= allocation_unit) { hint = (void*)addr; funchook_log(funchook, " change hint address from %p to %p\n", old_hint, hint); break; } } } hint = (void*)((size_t)mbi.BaseAddress + mbi.RegionSize); } #else hint = NULL; #endif pl = VirtualAlloc(hint, allocation_unit, MEM_RESERVE, PAGE_NOACCESS); if (pl == NULL) { DWORD err = GetLastError(); char errbuf[128]; funchook_set_error_message(funchook, "Failed to reserve memory %p (hint=%p, size=%"PRIuPTR", errro=%lu(%s))", pl, hint, allocation_unit, err, to_errmsg(err, errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_ALLOCATION; } funchook_log(funchook, " reserve memory %p (hint=%p, size=%"PRIuPTR")\n", pl, hint, allocation_unit); addr = VirtualAlloc(pl, page_size, MEM_COMMIT, PAGE_READWRITE); if (addr == NULL) { DWORD err = GetLastError(); char errbuf[128]; funchook_set_error_message(funchook, "Failed to commit memory %p for read-write (hint=%p, size=%"PRIuPTR", error=%lu(%s))", addr, pl, page_size, err, to_errmsg(err, errbuf, sizeof(errbuf))); VirtualFree(pl, 0, MEM_RELEASE); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } funchook_log(funchook, " commit memory %p for read-write (hint=%p, size=%"PRIuPTR")\n", addr, pl, page_size); pl->next = page_list.next; pl->prev = &page_list; page_list.next->prev = pl; page_list.next = pl; *pl_out = pl; return 0; } /* * Get one page from page_list, commit it and return it. */ int funchook_page_alloc(funchook_t *funchook, funchook_page_t **page_out, uint8_t *func, ip_displacement_t *disp) { page_list_t *pl; funchook_page_t *page = NULL; size_t i; for (pl = page_list.next; pl != &page_list; pl = pl->next) { for (i = 0; i < max_num_pages; i++) { if (!pl->used[i]) { funchook_page_t *p = (funchook_page_t *)((size_t)pl + (i + 1) * page_size); if (funchook_page_avail(funchook, p, 0, func, disp)) { page = p; goto exit_loop; } } } } exit_loop: if (page == NULL) { /* no page_list is available. */ int rv = alloc_page_info(funchook, &pl, func); if (rv != 0) { return rv; } i = 0; page = (funchook_page_t *)((size_t)pl + page_size); } if (VirtualAlloc(page, page_size, MEM_COMMIT, PAGE_READWRITE) == NULL) { DWORD err = GetLastError(); char errbuf[128]; funchook_set_error_message(funchook, "Failed to commit page %p (base=%p(used=%d), idx=%"PRIuPTR", size=%"PRIuPTR", error=%lu(%s))", page, pl, pl->num_used, i, page_size, err, to_errmsg(err, errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } pl->used[i] = 1; pl->num_used++; funchook_log(funchook, " commit page %p (base=%p(used=%d), idx=%"PRIuPTR", size=%"PRIuPTR")\n", page, pl, pl->num_used, i, page_size); *page_out = page; return 0; } /* * Back to one page to page_list. */ int funchook_page_free(funchook_t *funchook, funchook_page_t *page) { page_list_t *pl = (page_list_t *)((size_t)page & ~(allocation_unit - 1)); size_t idx = ((size_t)page - (size_t)pl) / page_size - 1; BOOL ok; ok = VirtualFree(page, page_size, MEM_DECOMMIT); if (!ok) { DWORD err = GetLastError(); char errbuf[128]; funchook_set_error_message(funchook, "Failed to decommit page %p (base=%p(used=%d), idx=%"PRIuPTR", size=%"PRIuPTR", error=%lu(%s))", page, pl, pl->num_used, idx, page_size, err, to_errmsg(err, errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } funchook_log(funchook, " decommit page %p (base=%p(used=%d), idx=%"PRIuPTR", size=%"PRIuPTR")\n", page, pl, pl->num_used, idx, page_size); pl->num_used--; pl->used[idx] = 0; if (pl->num_used != 0) { return 0; } /* all pages in this allocation unit are decommitted. delete this page_list */ pl->next->prev = pl->prev; pl->prev->next = pl->next; ok = VirtualFree(pl, 0, MEM_RELEASE); if (!ok) { DWORD err = GetLastError(); char errbuf[128]; funchook_set_error_message(funchook, "Failed to release memory %p (size=%"PRIuPTR", error=%lu(%s))", pl, allocation_unit, err, to_errmsg(err, errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } funchook_log(funchook, " release memory %p (size=%"PRIuPTR")\n", pl, allocation_unit); return 0; } int funchook_page_protect(funchook_t *funchook, funchook_page_t *page) { char errbuf[128]; DWORD oldprot; BOOL ok = VirtualProtect(page, page_size, PAGE_EXECUTE_READ, &oldprot); if (ok) { funchook_log(funchook, " protect page %p (size=%"PRIuPTR", prot=read,exec)\n", page, page_size); return 0; } funchook_set_error_message(funchook, "Failed to protect page %p (size=%"PRIuPTR", prot=read,exec, error=%lu(%s))", page, page_size, GetLastError(), to_errmsg(GetLastError(), errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } int funchook_page_unprotect(funchook_t *funchook, funchook_page_t *page) { char errbuf[128]; DWORD oldprot; BOOL ok = VirtualProtect(page, page_size, PAGE_READWRITE, &oldprot); if (ok) { funchook_log(funchook, " unprotect page %p (size=%"PRIuPTR", prot=read,write)\n", page, page_size); return 0; } funchook_set_error_message(funchook, "Failed to unprotect page %p (size=%"PRIuPTR", prot=read,write, error=%lu(%s))", page, page_size, GetLastError(), to_errmsg(GetLastError(), errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } int funchook_unprotect_begin(funchook_t *funchook, mem_state_t *mstate, void *start, size_t len) { char errbuf[128]; size_t saddr = ROUND_DOWN((size_t)start, page_size); BOOL ok; mstate->addr = (void*)saddr; mstate->size = len + (size_t)start - saddr; mstate->size = ROUND_UP(mstate->size, page_size); ok = VirtualProtect(mstate->addr, mstate->size, PAGE_EXECUTE_READWRITE, &mstate->protect); if (ok) { funchook_log(funchook, " unprotect memory %p (size=%"PRIuPTR") <- %p (size=%"PRIuPTR")\n", mstate->addr, mstate->size, start, len); return 0; } funchook_set_error_message(funchook, "Failed to unprotect memory %p (size=%"PRIuPTR") <- %p (size=%"PRIuPTR", error=%lu(%s))", mstate->addr, mstate->size, start, len, GetLastError(), to_errmsg(GetLastError(), errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } int funchook_unprotect_end(funchook_t *funchook, const mem_state_t *mstate) { char errbuf[128]; DWORD oldprot; BOOL ok = VirtualProtect(mstate->addr, mstate->size, mstate->protect, &oldprot); if (ok) { funchook_log(funchook, " protect memory %p (size=%"PRIuPTR")\n", mstate->addr, mstate->size); return 0; } funchook_set_error_message(funchook, "Failed to protect memory %p (size=%"PRIuPTR", error=%lu(%s))", mstate->addr, mstate->size, GetLastError(), to_errmsg(GetLastError(), errbuf, sizeof(errbuf))); return FUNCHOOK_ERROR_MEMORY_FUNCTION; } static IMAGE_IMPORT_DESCRIPTOR *get_image_import_descriptor(HMODULE hMod, DWORD *cnt) { IMAGE_DOS_HEADER *doshdr; IMAGE_NT_HEADERS *nthdr; IMAGE_DATA_DIRECTORY *dir; if (memcmp(hMod, "MZ", 2) != 0) { return NULL; } doshdr = (IMAGE_DOS_HEADER*)hMod; nthdr = (PIMAGE_NT_HEADERS)((size_t)hMod + doshdr->e_lfanew); dir = &nthdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if (dir->VirtualAddress == 0) { return NULL; } *cnt = dir->Size / sizeof(IMAGE_IMPORT_DESCRIPTOR); return (IMAGE_IMPORT_DESCRIPTOR*)((size_t)hMod + dir->VirtualAddress); } void *funchook_resolve_func(funchook_t *funchook, void *func) { char path[MAX_PATH]; HMODULE hMod; BOOL ok; IMAGE_IMPORT_DESCRIPTOR *desc_head, *desc; insn_t *fn = (insn_t*)func; size_t pos = 0; DWORD cnt; if (*funchook_debug_file != '\0') { DWORD len = GetMappedFileNameA(GetCurrentProcess(), func, path, sizeof(path)); if (len > 0) { funchook_log(funchook, " func %p is in %.*s\n", func, (int)len, path); } } #if defined CPU_X86 || defined CPU_X86_64 if (fn[0] == 0xe9) { fn = (fn + 5) + *(int*)(fn + 1); funchook_log(funchook, " relative jump to %p\n", fn); } if (fn[0] == 0xff && fn[1] == 0x25) { #ifdef CPU_X86_64 pos = (size_t)(fn + 6) + *(int*)(fn + 2); #else pos = *(size_t*)(fn + 2); #endif funchook_log(funchook, " indirect jump to addresss at %p\n", (void*)pos); } #endif #if defined CPU_ARM64 #define ADRP_XIP0 0x90000010 #define ADRP_XIP0_MASK 0x9F00001F #define ADRP_XIP0_IMMLO 0x60000000 #define ADRP_XIP0_IMMHI 0x00FFFFE0 #define LDR_XIP0 0xF9400210 #define LDR_XIP0_MASK 0xFFC003FF #define LDR_XIP0_IMM12 0x003FFC00 #define BR_XIP0 0xD61F0200 if ((fn[0] & ADRP_XIP0_MASK) == ADRP_XIP0 && (fn[1] & LDR_XIP0_MASK) == LDR_XIP0 && fn[2] == BR_XIP0) { // fn[0]: addrp xip0, immhi&immlo // fn[1]: ldr xip0, [xip0,imm12] // fn[2]: br xip0 size_t addr = (size_t)fn & ~((1 << 12) - 1); size_t immhi = ((size_t)(fn[0] & ADRP_XIP0_IMMHI) >> 5) << (12 + 2); size_t immlo = ((size_t)(fn[0] & ADRP_XIP0_IMMLO) >> 29) << 12; size_t imm12 = ((size_t)(fn[1] & LDR_XIP0_IMM12) >> 10) << 3; pos = addr + immhi + immlo + imm12; // fprintf(stderr, "%016I64x: %08x %08x %08x : %I64x %I64x %I64x %I64x\n", (size_t)fn, fn[0], fn[1], fn[2], addr, immhi, immlo, imm12); funchook_log(funchook, " indirect jump to addresss at %p\n", (void*)pos); } #endif if (pos == 0) { return func; } ok = GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, func, &hMod); if (!ok) { return func; } desc_head = get_image_import_descriptor(hMod, &cnt); if (desc_head == NULL) { return func; } for (desc = desc_head; desc->Name != 0; desc++) { IMAGE_THUNK_DATA *addr_thunk = (IMAGE_THUNK_DATA*)((char*)hMod + desc->FirstThunk); while (addr_thunk->u1.Function != 0) { if (pos == (size_t)&addr_thunk->u1.Function) { func = (void*)addr_thunk->u1.Function; if (*funchook_debug_file != '\0') { DWORD len = GetMappedFileNameA(GetCurrentProcess(), func, path, sizeof(path)); if (len > 0) { funchook_log(funchook, " -> func %p in %.*s\n", func, (int)len, path); } else { funchook_log(funchook, " -> func %p\n", func); } } return func; } addr_thunk++; } } return func; }