From 0142fb19905808101878148ca188161f016eba5e Mon Sep 17 00:00:00 2001 From: spacehen Date: Fri, 30 Dec 2022 12:05:55 +0200 Subject: CDL: Compact Detour Library Initial commit for the CDL x86_64 detour library. --- README.md | 35 +++ cdl.c | 900 +++++++++++++++++++++++++++++++++++++++++++++++++++++ cdl.h | 208 +++++++++++++ tests/Makefile | 16 + tests/basic_jmp.c | 52 ++++ tests/basic_swbp.c | 52 ++++ 6 files changed, 1263 insertions(+) create mode 100644 README.md create mode 100644 cdl.c create mode 100644 cdl.h create mode 100644 tests/Makefile create mode 100644 tests/basic_jmp.c create mode 100644 tests/basic_swbp.c diff --git a/README.md b/README.md new file mode 100644 index 0000000..225452d --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# cdl86 + +cdl86 - Compact Detour Library 86 + +# Abstract +cdl86 is a simple cross platform detours library written in C for Linux and Windows. + +It allows for the interception of x86 and x86_64 C/C++ functions in memory. + +[https://journal.lunar.sh/2022/linux-detours.html](https://journal.lunar.sh/2022/linux-detours.html) + +The library currently supports two types of function hooks: +* JMP patch - patches origin function with a JMP to detour. +* INT3 patch - places software breakpoint (SWBP) at origin address. Handles control flow to detour. + +This project makes use of an internal x86 instruction length disassembly engine. + +# API +```C +struct cdl_jmp_patch cdl_jmp_attach(void **target, void *detour); +struct cdl_swbp_patch cdl_swbp_attach(void **target, void *detour); +void cdl_jmp_detach(struct cdl_jmp_patch *jmp_patch); +void cdl_swbp_detach(struct cdl_swbp_patch *swbp_patch); +void cdl_jmp_dbg(struct cdl_jmp_patch *jmp_patch); +void cdl_swbp_dbg(struct cdl_swbp_patch *swbp_patch); +``` +The API is documented in more detail in the corresponding header and source +files. + +# Info +**cdl.c** - C source file for CDL.
+**cdl.h** - CDL header file to include. + +Folders: +* **/tests** - CDL test suite. Run `make all`. diff --git a/cdl.c b/cdl.c new file mode 100644 index 0000000..314223f --- /dev/null +++ b/cdl.c @@ -0,0 +1,900 @@ +/** + * @file cdl.c + * @brief cdl86 (Compact Detour Library) - cdl.c + * + * Experimental Linux x86_64 detour library. + * + * Copyright (c) 2022 (Dylan Muller) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cdl.h" + +/** + * Software breakpoint initialization. + */ +static int cdl_swbp_alloc( + __in void +); + +/** + * JMP patch info struct. + * + * @param code address to set page protections. + */ +static int cdl_set_page_protect( + __in uint8_t* code +); + +/** + * Generate 64-bit jmp. + * + * @param code address to place jmp. + * @param address address to jmp to. + */ +static uint8_t* cdl_gen_64_jmp( + __in uint8_t* code, + __in uint8_t* address +); + +/** + * Generate 32-bit jmp. + * + * @param code address to place jmp. + * @param address address to jmp to. + */ +static uint8_t* cdl_gen_32_jmp( + __in uint8_t* code, + __in uint8_t* address +); + +/** + * Generate NOP instruction. + * + * @param code address to place NOP. + */ +static uint8_t* cdl_gen_nop( + __in uint8_t* code +); + +/** + * Generate INT3 instruction. + * + * @param code address to place breakpoint. + */ +static uint8_t* cdl_gen_swbp( + __in uint8_t* code +); + +/** + * Create trampoline function. + * + * @param target address to jump back to. + * @param bytes_orig original bytes of target. + * @param size size of bytes_orig. + */ +static uint8_t* cdl_gen_trampoline( + __in uint8_t* target, + __in uint8_t* bytes_orig, + __in int size +); + +/** + * Reserve bytes at address of target. Calculates + * minimum number of bytes required to fully replace + * instructions at target address by size 'reserve + * + * @param target address to jump back to. + * @param reserve size of hook type. + * @param alloc_size final allocation size. + */ +static uint8_t* cdl_reserve_bytes( + __in uint8_t* target, + __in int reserve, + __out int* alloc_size +); + +/** + * Fill unpatched bytes with NOPs to avoid segfault. + * + * @param target address to fill. + * @param size size of region to fill. + * @param patch_size size of patch type. + */ +static void cdl_nop_fill( + __in uint8_t* target, + __in int size, + __in int patch_size +); + +#ifdef _WIN32 + +/** + * Vector exception handler for windows. + * + * @param except exception pointer. + */ +static long NTAPIcdl_swbp_handler_win( + __in PEXCEPTION_POINTERS except +); + +#else + +/** + * Vector exception handler for linux. + * + * @param sig signal id. + * @param info signal information. + * @param context execution context. + */ +static void cdl_swbp_handler_linux( + __in int sig, + __in siginfo_t* info, + __in struct ucontext_t* context +); + +#endif + +/** + * Instruction length disassembler. + * + * @param address address to disassemble. + * @param x86_64_mode use 64-bit mode. + */ +size_t len_disasm( + __in const void* const address, + __in const bool x86_64_mode +); + +/* Global variables for state machine. */ +int cdl_swbp_size = 0x0; +bool cdl_swbp_init = false; +void* vector_handler = 0x0; +struct cdl_swbp_patch* cdl_swbp_hk = 0x0; + +const uint8_t prefixes[] = +{ + 0xF0, 0xF2, 0xF3, 0x2E, 0x36, 0x3E, 0x26, 0x64, 0x65, 0x66, 0x67 +}; +const uint8_t op1modrm[] = +{ + 0x62, 0x63, 0x69, 0x6B, 0xC0, 0xC1, 0xC4, 0xC5, 0xC6, 0xC7, 0xD0, 0xD1, + 0xD2, 0xD3, 0xF6, 0xF7, 0xFE, 0xFF +}; +const uint8_t op1imm8[] = +{ + 0x6A, 0x6B, 0x80, 0x82, 0x83, 0xA8, 0xC0, 0xC1, 0xC6, 0xCD, 0xD4, 0xD5, + 0xEB +}; +const uint8_t op1imm32[] = +{ + 0x68, 0x69, 0x81, 0xA9, 0xC7, 0xE8, 0xE9 +}; +const uint8_t op2modrm[] = +{ + 0x0D, 0xA3, 0xA4, 0xA5, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF +}; + +/* Four high-order bits of an opcode to index a row of the opcode table. */ +#define R (*b >> 4) +/* Four low-order bits to index a column of the table. */ +#define C (*b & 0xF) + +static int cdl_swbp_alloc( + __in void +) +{ + extern struct cdl_swbp_patch* cdl_swbp_hk; + bool found = false; + int i = 0x0; + int size = sizeof(cdl_swbp_hk[0]); + extern struct cdl_swbp_patch* cdl_swbp_hk; + + /* If cdl_swbp_hk is null, allocate memory. */ + if (!cdl_swbp_hk) + { + cdl_swbp_hk = (struct cdl_swbp_patch*)malloc(size); + cdl_swbp_size++; + return 0; + } + else + { + /* Search through struct for inactive member. */ + for (i = 0; i < cdl_swbp_size; i++) + { + if (cdl_swbp_hk[i].active == false) + { + found = true; + break; + } + } + + /* If we couldn't find inactive member, resize memory. */ + if (!found) + { + cdl_swbp_size++; + cdl_swbp_hk = (struct cdl_swbp_patch*)realloc(cdl_swbp_hk, + size * cdl_swbp_size); + return cdl_swbp_size - 1; + } + else + { + return i; + } + } +} + +static struct cdl_ins_probe cdl_asm_probe( + __in uint8_t* code +) +{ + int size = 0x0; + struct cdl_ins_probe probe; + + size = len_disasm(code, true); + probe.size = size; + probe.bytes = (uint8_t*)malloc(sizeof(uint8_t) * size); + memcpy(probe.bytes, code, size); + + return probe; +} + +static int cdl_set_page_protect( + __in uint8_t* code +) +{ + int ret = 0x0; + + #ifdef _WIN32 + + SYSTEM_INFO sys_info = {0}; + unsigned long old_protect = 0x0; + GetSystemInfo(&sys_info); + ret = VirtualProtect((LPVOID)code, sys_info.dwPageSize, + PAGE_EXECUTE_READWRITE, &old_protect); + + #else + + /* Calculate page size */ + uintptr_t page_size = sysconf(_SC_PAGE_SIZE); + ret = mprotect(code - ((uintptr_t)(code) % page_size), page_size, + PROT_EXEC | PROT_READ | PROT_WRITE); + + #endif + + return ret; +} + +static uint8_t* cdl_gen_64_jmp( + __in uint8_t* code, + __in uint8_t* address +) +{ + /* Generate 'mov rax', address */ + *(code + 0x0) = 0x48; + *(code + 0x1) = 0xB8; + *(uint64_t*)(code + 0x2) = (uint64_t)address; + /* Generate 'jmpq *%rax' instruction. */ + *(code + 0xA) = 0xFF; + *(code + 0xB) = 0xE0; + + return code; +} + +static uint8_t* cdl_gen_32_jmp( + __in uint8_t* code, + __in uint8_t* address +) +{ + uint8_t* operand = 0x0; + /* Generate 'jmp address' */ + *(code + 0x0) = 0xE9; + operand = (uint8_t*)(address - (code + BYTES_JMP_PATCH)); + *(uint32_t*)(code + 0x1) = (uintptr_t)operand; + + return code; +} + +static uint8_t* cdl_gen_nop( + __in uint8_t* code +) +{ + *(code + 0x0) = 0x90; + return code; +} + +static uint8_t* cdl_gen_swbp( + __in uint8_t* code +) +{ + *(code + 0x0) = 0xCC; + return code; +} + +static uint8_t* cdl_gen_trampoline( + __in uint8_t* target, + __in uint8_t* bytes_orig, + __in int size +) +{ + uint8_t* trampoline = 0x0; + + /* Allocate trampoline memory pool. */ + + #ifdef _WIN32 + + trampoline = (uint8_t*)VirtualAlloc(NULL, size + BYTES_JMP_PATCH, + MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE); + + #else + + trampoline = (uint8_t*)mmap(NULL, size + BYTES_JMP_PATCH, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + #endif + + memcpy(trampoline, bytes_orig, size); + /* Generate jump to address just after call + * to detour in trampoline. */ + + #ifdef ENV_64 + + cdl_gen_64_jmp(trampoline + size, target + size); + + #else + + cdl_gen_32_jmp(trampoline + size, target + size); + + #endif + + return trampoline; +} + +static uint8_t* cdl_reserve_bytes( + __in uint8_t* target, + __in int reserve, + __out int* alloc_size +) +{ + int bytes = 0x0; + uint8_t* bytes_orig = NULL; + struct cdl_ins_probe probe; + + /* Ensure we can't reserve more than + * BYTES_RESERVE_MAX. + */ + if (reserve > BYTES_RESERVE_MAX) + { + return (uint8_t*)NULL; + } + /* Allocate buffer to hold original instruction + * bytes. + */ + bytes_orig = (uint8_t*)malloc(BYTES_RESERVE_MAX); + /* Prove instructions until bytes > reserve. */ + while (bytes < reserve) + { + probe = cdl_asm_probe(target + bytes); + memcpy(bytes_orig + bytes, probe.bytes, probe.size); + bytes += probe.size; + free(probe.bytes); + }; + + *alloc_size = bytes; + /* Return original instruction bytes. + * buffer + */ + + return bytes_orig; +} + +/* Fill unpatched bytes with NOPs to + * avoid segfault. + */ +static void cdl_nop_fill( + __in uint8_t* target, + __in int size, + __in int patch_size +) +{ + int nops = 0x0; + + nops = size - patch_size; + while (nops-- > 0) + { + cdl_gen_nop(target + patch_size + nops); + } + + return; +} + +#ifdef _WIN32 + +/* Vector breakpoint handler for windows. Handles incomming + * PEXCEPTION once INT3 breakpoint is hit. + * + * The handler functions by comparing the value + * of ContextRecord->Rip as provided by the PEXCEPTION_RECORD + * struct of the signal to the active breakpoint addresses + * (bp_addr). + * + * If a match is found then the RIP/EIP register of the current + * context if updated to the address of the detour function. + */ +static long NTAPI cdl_swbp_handler_win( + __in PEXCEPTION_POINTERS except +) +{ + extern struct cdl_swbp_patch* cdl_swbp_hk; + PEXCEPTION_RECORD except_record = except->ExceptionRecord; + PCONTEXT context_record = except->ContextRecord; + uint8_t* bp_addr = (uint8_t*)(except_record->ExceptionAddress); + bool active = false; + int i = 0x0; + + switch (except->ExceptionRecord->ExceptionCode) + { + case EXCEPTION_BREAKPOINT: + for (i = 0; i < cdl_swbp_size; i++) + { + active = cdl_swbp_hk[i].active; + /* Compare breakpoint addresses. */ + if (bp_addr == cdl_swbp_hk[i].bp_addr && active) + { + #ifdef _WIN64 + + context_record->Rip = (uintptr_t)cdl_swbp_hk[i].detour; + + #else + + context_record->Eip = (uintptr_t)cdl_swbp_hk[i].detour; + + #endif + + return EXCEPTION_CONTINUE_EXECUTION; + } + } + } + + return EXCEPTION_CONTINUE_SEARCH; +} + +#else + +/* Software breakpoint handler for linux. Handles incomming + * SIGTRAP signal once INT3 breakpoint is hit. + * + * The handler functions by comparing the value + * of the RIP-0x1 register as provided by the ucontext_t + * struct of the signal to the active breakpoint addresses + * (bp_addr). + * + * If a match is found then the RIP/EIP register of the current + * context if updated to the address of the detour function. + */ +static void cdl_swbp_handler_linux( + __in int sig, + __in siginfo_t* info, + __in struct ucontext_t* context +) +{ + extern struct cdl_swbp_patch* cdl_swbp_hk; + int i = 0x0; + bool active = false; + uint8_t* bp_addr = NULL; + /* RIP register point to instruction after the + * int3 breakpoint so we subtract 0x1. + */ + bp_addr = (uint8_t*)(context->uc_mcontext.gregs[REG_IP] - 0x1); + + /* Iterate over all breakpoint structs. */ + for (i = 0; i < cdl_swbp_size; i++) + { + active = cdl_swbp_hk[i].active; + /* Compare breakpoint addresses. */ + if (bp_addr == cdl_swbp_hk[i].bp_addr && active) + { + /* Update RIP and reset context. */ + context->uc_mcontext.gregs[REG_IP] = (greg_t)cdl_swbp_hk[i].detour; + setcontext(context); + } + } +} +#endif + +/* Patches function pointed to by 'target' with + * a JMP to the detour function. *target is then + * updated to point to the newly allocated trampoline. + */ +struct cdl_jmp_patch cdl_jmp_attach( + __in_out void** target, + __in void* detour +) +{ + int bytes = 0x0; + int i = 0x0; + int nops = 0x0; + uint8_t* trampoline = NULL; + uint8_t* target_origin = NULL; + uint8_t* plt_got = NULL; + uint8_t* bytes_orig = NULL; + struct cdl_jmp_patch jmp_patch = {}; + + target_origin = (uint8_t*)*target; + /* Check if target pointer is PLT entry. */ + jmp_patch.target = (uint8_t**)target; + + /* Reserve BYTES_JMP_PATCH bytes for incoming + * patch. + */ + bytes_orig = cdl_reserve_bytes(target_origin, BYTES_JMP_PATCH, &bytes); + jmp_patch.code = bytes_orig; + jmp_patch.nt_alloc = bytes; + + /* Generate trampoline stub. */ + trampoline = cdl_gen_trampoline(target_origin, bytes_orig, bytes); + jmp_patch.trampoline = trampoline; + + /* Set memory permissions. */ + cdl_set_page_protect(target_origin); + + /* Generate JMP to detour function. */ + + #ifdef ENV_64 + + cdl_gen_64_jmp(target_origin, (uint8_t*)detour); + + #else + + cdl_gen_32_jmp(target_origin, (uint8_t*)detour); + + #endif + + /* Fill remaining bytes with NOPs. */ + cdl_nop_fill(target_origin, bytes, BYTES_JMP_PATCH); + + jmp_patch.origin = target_origin; + /* Set* target to newly allocated trampoline. */ + *target = trampoline; + + /* Mark patch as active. */ + jmp_patch.active = true; + + return jmp_patch; +} + +/* Detach JMP patch and free memory. */ +void cdl_jmp_detach( + __in_out struct cdl_jmp_patch* jmp_patch +) +{ + uint8_t* origin = jmp_patch->origin; + uint8_t* code = jmp_patch->code; + int nt_alloc = jmp_patch->nt_alloc; + + /* If JMP patch is active, free memory. */ + if (jmp_patch->active) + { + memcpy(origin, code, nt_alloc); + /* Deallocate trampoline. */ + + #ifdef _WIN32 + + VirtualFree(jmp_patch->trampoline, nt_alloc + BYTES_JMP_PATCH, + MEM_RELEASE); + + #else + + munmap(jmp_patch->trampoline, nt_alloc + BYTES_JMP_PATCH); + + #endif + + *jmp_patch->target = jmp_patch->origin; + free(jmp_patch->code); + + /* Set jmp_patch memory to 0. */ + memset(jmp_patch, 0, sizeof(*jmp_patch)); + } + + return; +} + +/* Patches function pointed to by 'target' with + * a INT3 BP to the detour function. *target is then + * updated to point to the newly allocated stub. + */ +struct cdl_swbp_patch cdl_swbp_attach( + __in_out void** target, + __in void* detour +) +{ + extern struct cdl_swbp_patch* cdl_swbp_hk; + int bytes = 0x0; + int id = 0x0; + int size = 0x0; + uint8_t* stub = NULL; + uint8_t* bytes_orig = NULL; + uint8_t* target_origin = NULL; + uint8_t* plt_got = NULL; + struct cdl_swbp_patch swbp_patch = {}; + + /* Initialise cdl signal handler. */ + if (!cdl_swbp_init) + { + /* Request signal context info which + * is required for RIP register comparison. + */ + + #ifdef _WIN32 + + vector_handler = AddVectoredExceptionHandler(1, &cdl_swbp_handler_win); + + #else + + struct sigaction sa = {0}; + sa.sa_flags = SA_SIGINFO | SA_ONESHOT; + sa.sa_sigaction = (void*)cdl_swbp_handler_linux; + sigaction(SIGTRAP, &sa, NULL); + cdl_swbp_init = true; + + #endif + } + + target_origin = (uint8_t*)*target; + swbp_patch.target = (uint8_t**)target; + swbp_patch.detour = (uint8_t*)detour; + + /* Reserve bytes for INT3 patch. */ + bytes_orig = cdl_reserve_bytes(target_origin, BYTES_SWBP_PATCH, &bytes); + swbp_patch.code = bytes_orig; + swbp_patch.ns_alloc = bytes; + + /* Generate stub function. */ + stub = cdl_gen_trampoline(target_origin, bytes_orig, bytes); + swbp_patch.stub = stub; + + /* Set memory permissions and generate INT3. */ + cdl_set_page_protect(target_origin); + cdl_gen_swbp(target_origin); + + /* Fill remaining bytes with NOPs. */ + cdl_nop_fill(target_origin, bytes, BYTES_SWBP_PATCH); + + /* Allocate new SW BP id. */ + id = cdl_swbp_alloc(); + swbp_patch.gid = id; + size = sizeof(swbp_patch); + + swbp_patch.bp_addr = target_origin; + *target = stub; + + swbp_patch.active = true; + /* Copy struct data to global SWBP variable + * (cdl_swbp_hk). + */ + memcpy(cdl_swbp_hk + (size * id), &swbp_patch, size); + + return swbp_patch; +} + +/* Detach INT3 patch and free memory. */ +void cdl_swbp_detach( + __in_out struct cdl_swbp_patch* swbp_patch +) +{ + extern struct cdl_swbp_patch* cdl_swbp_hk; + uint8_t* bp_addr = swbp_patch->bp_addr; + uint8_t* stub = swbp_patch->stub; + uint8_t* code = swbp_patch->code; + int ns_alloc = swbp_patch->ns_alloc; + + /* If JMP patch is active, free memory. */ + if (swbp_patch->active) + { + memcpy(bp_addr, code, ns_alloc); + /* Unmap stub function. */ + + #ifdef _WIN32 + + VirtualFree(swbp_patch->stub, ns_alloc + BYTES_JMP_PATCH, + MEM_RELEASE); + + #else + + munmap(swbp_patch->stub, ns_alloc + BYTES_JMP_PATCH); + + #endif + + *swbp_patch->target = swbp_patch->bp_addr; + free(code); + + /* Set global SWBP active status for gid to + * flase. + */ + cdl_swbp_hk[swbp_patch->gid].active = false; + memset(swbp_patch, 0, sizeof(*swbp_patch)); + cdl_swbp_size--; + } + + return; +} + +/* Print debug info for JMP patch. */ +void cdl_jmp_dbg( + __in struct cdl_jmp_patch* jmp_patch +) +{ + printf("origin : 0x%" PTR_SIZE "\n", (uintptr_t)jmp_patch->origin); + printf("trampoline : 0x%" PTR_SIZE "\n", (uintptr_t)jmp_patch->trampoline); + printf("nt_alloc : %i\n", jmp_patch->nt_alloc); + printf("active : 0x%" PTR_SIZE "\n", (uintptr_t)jmp_patch->active); +} + +/* Print debug info for INT3 patch. */ +void cdl_swbp_dbg( + __in struct cdl_swbp_patch* swbp_patch +) +{ + printf("bp_addr : 0x%" PTR_SIZE "\n", (uintptr_t)swbp_patch->bp_addr); + printf("stub : 0x%" PTR_SIZE "\n", (uintptr_t)swbp_patch->stub); + printf("ns_alloc : %i\n", swbp_patch->ns_alloc); + printf("gid : %i\n", swbp_patch->gid); + printf("active : 0x%" PTR_SIZE "\n", (uintptr_t)swbp_patch->active); +} + +static bool findByte( + __in const uint8_t* arr, + __in const size_t N, + __in const uint8_t x +) +{ + for (size_t i = 0; i < N; i++) + { + if (arr[i] == x) + { + return true; + } + }; + + return false; +} + +static void parseModRM( + __in uint8_t** b, + __in const bool addressPrefix +) +{ + uint8_t modrm = *++*b; + + if (!addressPrefix || (addressPrefix && **b >= 0x40)) + { + /* Check for SIB byte. */ + bool hasSIB = false; + if (**b < 0xC0 && (**b & 0b111) == 0b100 && !addressPrefix) + hasSIB = true, (*b)++; + + /* disp8 (ModR/M). */ + if (modrm >= 0x40 && modrm <= 0x7F) + (*b)++; + else if ((modrm <= 0x3F && (modrm & 0b111) == 0b101) + /* disp16,32 (ModR/M). */ + || (modrm >= 0x80 && modrm <= 0xBF)) + *b += (addressPrefix) ? 2 : 4; + + /* disp8,32 (SIB). */ + else if (hasSIB && (**b & 0b111) == 0b101) + *b += (modrm & 0b01000000) ? 1 : 4; + } + else if (addressPrefix && modrm == 0x26) + *b += 2; +}; + +size_t len_disasm( + __in const void* const address, + __in const bool x86_64_mode +) +{ + size_t offset = 0x0; + bool operandPrefix = false; + bool addressPrefix = false; + bool rexW = false; + uint8_t* b = (uint8_t*)(address); + + /* Parse legacy prefixes & REX prefixes. */ + for (int i = 0; i < 14 && findByte(prefixes, sizeof(prefixes), *b) + || ((x86_64_mode) ? (R == 4) : false); i++, b++) + { + if (*b == 0x66) + operandPrefix = true; + else if (*b == 0x67) + addressPrefix = true; + else if (R == 4 && C >= 8) + rexW = true; + } + + /* Parse opcode(s). */ + if (*b == 0x0F) // 2,3 bytes. + { + b++; + /* 3 bytes. */ + if (*b == 0x38 || *b == 0x3A) + { + if (*b++ == 0x3A) + offset++; + + parseModRM(&b, addressPrefix); + } + /* 2 bytes. */ + else + { + /* disp32. */ + if (R == 8) + offset += 4; + else if ((R == 7 && C < 4) || *b == 0xA4 || + *b == 0xC2 || (*b > 0xC3 && *b <= 0xC6) + || *b == 0xBA || *b == 0xAC) /* imm8. */ + offset++; + + /* Check for ModR/M, SIB and displacement. */ + if (findByte(op2modrm, sizeof(op2modrm), *b) + || (R != 3 && R > 0 && R < 7) + || *b >= 0xD0 || (R == 7 && C != 7) + || R == 9 || R == 0xB + || (R == 0xC && C < 8) + || (R == 0 && C < 4)) + parseModRM(&b, addressPrefix); + } + } + else /* 1 byte. */ + { + /* Check for immediate field. */ + if ((R == 0xE && C < 8) || (R == 0xB && C < 8) + || R == 7 || (R < 4 && (C == 4 || C == 0xC)) + || (*b == 0xF6 && !(*(b + 1) & 48)) + || findByte(op1imm8, sizeof(op1imm8), *b)) /* imm8. */ + offset++; + else if (*b == 0xC2 || *b == 0xCA) /* imm16. */ + offset += 2; + else if (*b == 0xC8) /* imm16 + imm8 */ + offset += 3; + else if ((R < 4 && (C == 5 || C == 0xD)) + || (R == 0xB && C >= 8) + || (*b == 0xF7 && !(*(b + 1) & 48)) + || findByte(op1imm32, sizeof(op1imm32), *b)) /* imm32,16. */ + offset += (rexW) ? 8 : (operandPrefix ? 2 : 4); + else if (R == 0xA && C < 4) + offset += (rexW) ? 8 : (addressPrefix ? 2 : 4); + else if (*b == 0xEA || *b == 0x9A) /* imm32,48. */ + offset += operandPrefix ? 4 : 6; + + /* Check for ModR/M, SIB and displacement. */ + if (findByte(op1modrm, sizeof(op1modrm), *b) || + (R < 4 && (C < 4 || (C >= 8 && C < 0xC))) + || R == 8 || (R == 0xD && C >= 8)) + parseModRM(&b, addressPrefix); + } + + return (size_t)((ptrdiff_t)(++b + offset) - (ptrdiff_t)(address)); +} diff --git a/cdl.h b/cdl.h new file mode 100644 index 0000000..fa6661a --- /dev/null +++ b/cdl.h @@ -0,0 +1,208 @@ +/** + * @file cdl.h + * @brief cdl86 (Compact Detour Library) - cdl.h + * + * Experimental Linux x86_64 detour library. + * + * Copyright (c) 2022 (Dylan Muller) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CDL_H +#define CDL_H + +#define _GNU_SOURCE + +/* Global includes */ +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +#define __in +#define __out +#define __in_out + +/* Determine CPU type */ +/* Check MSVC */ +#if _WIN32 || _WIN64 +#if _WIN64 +#define ENV_64 +#else +#define ENV_32 +#endif +#else +/* Check other compilers */ +#if __x86_64__ +#define ENV_64 +#else +#define ENV_32 +#endif +#endif + +/* Set ARCH flags */ +#ifdef ENV_64 +#define REG_IP REG_RIP +#define BYTES_JMP_PATCH 12 +#define PTR_SIZE PRIx64 +#else +#define REG_IP REG_EIP +#define BYTES_JMP_PATCH 5 +#define PTR_SIZE PRIx32 +#endif + +/* Define SW BP patch length, see (cdl_gen_swbp) */ +#define BYTES_SWBP_PATCH 1 + +/* General : reserve bytes */ +#define BYTES_RESERVE_MAX 20 + +/** + * Intruction probe struct + * + * @param size size of instruction (bytes) + * @param bytes byte array of instruction (uint8_t*) + */ +struct cdl_ins_probe +{ + int size; + uint8_t* bytes; +}; + +/** + * JMP patch info struct + * + * @param active is patch active (bool) + * @param nt_alloc number of bytes allocated to trampoline (int) + * @param code instructions replaced by JMP patch (uint8_t*) + * @param target pointer to function pointer (uint8_t**) + * @param origin pointer to origin(real) target address (uint8_t*) + * @param trampoline pointer to trampoline (uint8_t*) + */ +struct cdl_jmp_patch +{ + bool active; + int nt_alloc; + uint8_t* code; + uint8_t** target; + uint8_t* origin; + uint8_t* trampoline; +}; + +/** + * SWBP patch info struct. + * + * @param gid global id for SW BP (int) + * @param active is patch active (bool) + * @param ns_alloc number of bytes allocated to stub (int) + * @param code instructions replaced by SWBP patch (uint8_t*) + * @param target pointer to function pointer (uint8_t**) + * @param stub pointer to stub (uint8_t*) + * @param detour pointer to detour function (uint8_t*) + * @param bp_add address of breakpoint (uint8_t*) + */ +struct cdl_swbp_patch +{ + int gid; + bool active; + int ns_alloc; + uint8_t* code; + uint8_t** target; + uint8_t* stub; + uint8_t* detour; + uint8_t* bp_addr; +}; + +/** + * Attach JMP patch to target funciton. + * + * @param target pointer to function pointer to function to hook. + * @param detour function pointer to detour function + */ +struct cdl_jmp_patch cdl_jmp_attach( + __in_out void** target, + __in void* detour +); + +/** + * Attach INT3 patch to target funciton. + * + * @param target pointer to function pointer to function to hook. + * @param detour function pointer to detour function + */ +struct cdl_swbp_patch cdl_swbp_attach( + __in_out void** target, + __in void* detour +); + +/** + * Detach JMP patch. + * + * @param jmp_patch pointer to cdl_jmp_patch struct. + */ +void cdl_jmp_detach( + __in_out struct cdl_jmp_patch* jmp_patch +); + +/** + * Detach INT3 patch. + * + * @param swbp_patch pointer to cdl_swbp_patch struct. + */ +void cdl_swbp_detach( + __in_out struct cdl_swbp_patch* swbp_patch +); + +/** + * Print JMP patch debug info. + * + * @param jmp_patch pointer to cdl_jmp_patch struct. + */ +void cdl_jmp_dbg( + __in struct cdl_jmp_patch* jmp_patch +); + +/** + * Print SW BP debug info. + * + * @param jmp_patch pointer to cdl_swbp_patch struct. + */ +void cdl_swbp_dbg( + __in struct cdl_swbp_patch* swbp_patch +); + +#endif diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..d28b133 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,16 @@ +IDIR=../ +CC=gcc +CFLAGS=-I$(IDIR) +CFILES_CDL=../cdl.c + +all: basic_jmp basic_swbp + +basic_jmp: basic_jmp.c + $(CC) $(CFLAGS) basic_jmp.c $(CFILES_CDL) -o basic_jmp +basic_swbp: basic_swbp.c + $(CC) $(CFLAGS) basic_swbp.c $(CFILES_CDL) -o basic_swbp + +.PHONY: clean +clean: + rm -f basic_jmp + rm -f basic_swbp diff --git a/tests/basic_jmp.c b/tests/basic_jmp.c new file mode 100644 index 0000000..e8aa85a --- /dev/null +++ b/tests/basic_jmp.c @@ -0,0 +1,52 @@ +#include "cdl.h" + +typedef int add_t( + __in int x, + __in int y +); + +add_t* addo = NULL; + +int add( + __in int x, + __in int y +) +{ + printf("Inside original function\n"); + return x + y; +} + +int add_detour( + __in int x, + __in int y +) +{ + printf("Inside detour function\n"); + return addo(5,5); +} + +int main( + __in void +) +{ + struct cdl_jmp_patch jmp_patch = {}; + addo = (add_t*)add; + + printf("Before attach: \n"); + printf("add(1,1) = %i\n\n", add(1,1)); + + jmp_patch = cdl_jmp_attach((void**)&addo, add_detour); + if(jmp_patch.active) + { + printf("After attach: \n"); + printf("add(1,1) = %i\n\n", add(1,1)); + printf("== DEBUG INFO ==\n"); + cdl_jmp_dbg(&jmp_patch); + } + + cdl_jmp_detach(&jmp_patch); + printf("\nAfter detach: \n"); + printf("add(1,1) = %i\n\n", add(1,1)); + + return 0; +} diff --git a/tests/basic_swbp.c b/tests/basic_swbp.c new file mode 100644 index 0000000..684f71d --- /dev/null +++ b/tests/basic_swbp.c @@ -0,0 +1,52 @@ +#include "cdl.h" + +typedef int add_t( + __in int x, + __in int y +); + +add_t* addo = NULL; + +int add( + __in int x, + __in int y +) +{ + printf("Inside original function\n"); + return x + y; +} + +int add_detour( + __in int x, + __in int y +) +{ + printf("Inside detour function\n"); + return addo(5,5); +} + +int main( + __in void +) +{ + struct cdl_swbp_patch swbp_patch = {}; + addo = (add_t*)add; + + printf("Before attach: \n"); + printf("add(1,1) = %i\n\n", add(1,1)); + + swbp_patch = cdl_swbp_attach((void**)&addo, add_detour); + if(swbp_patch.active) + { + printf("After attach: \n"); + printf("add(1,1) = %i\n\n", add(1,1)); + printf("== DEBUG INFO ==\n"); + cdl_swbp_dbg(&swbp_patch); + } + + cdl_swbp_detach(&swbp_patch); + printf("\nAfter detach: \n"); + printf("add(1,1) = %i\n\n", add(1,1)); + + return 0; +} \ No newline at end of file -- cgit v1.2.3-70-g09d2