diff options
-rw-r--r-- | CMakeLists.txt | 11 | ||||
-rw-r--r-- | LICENSE.txt | 23 | ||||
-rw-r--r-- | README.md | 18 | ||||
-rw-r--r-- | subhook.c | 14 | ||||
-rw-r--r-- | subhook.h | 46 | ||||
-rw-r--r-- | subhook_private.h | 3 | ||||
-rw-r--r-- | subhook_unix.c | 3 | ||||
-rw-r--r-- | subhook_windows.c | 3 | ||||
-rw-r--r-- | subhook_x86.c | 78 |
9 files changed, 157 insertions, 42 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index c4e1065..f647e26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(SUBHOOK_VERSION_MAJOR 0) -set(SUBHOOK_VERSION_MINOR 5) +set(SUBHOOK_VERSION_MINOR 6) set(SUBHOOK_VERSION_PATCH 0) set(SUBHOOK_VERSION ${SUBHOOK_VERSION_MAJOR}) @@ -31,9 +31,14 @@ subhook_add_option_var(SUBHOOK_FORCE_32BIT BOOL OFF "Configure for compiling 32-bit binaries (on 64-bit systems)") set(SUBHOOK_HEADERS subhook.h) -set(SUBHOOK_SOURCES subhook.c subhook_private.h) +set(SUBHOOK_SOURCES subhook.c subhook_private.h subhook_x86.c) +if(WIN32) + list(APPEND SUBHOOK_SOURCES subhook_windows.c) +elseif(UNIX) + list(APPEND SUBHOOK_SOURCES subhook_unix.c) +endif() -add_definitions(-DSUBHOOK_IMPLEMENTATION) +add_definitions(-DSUBHOOK_IMPLEMENTATION -DSUBHOOK_SEPARATE_SOURCE_FILES) if(SUBHOOK_STATIC) add_library(subhook STATIC ${SUBHOOK_HEADERS} ${SUBHOOK_SOURCES}) diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..67e0382 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,23 @@ +Copyright (c) 2012-2018 Zeex +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. 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. @@ -71,14 +71,15 @@ void my_foo(int x) { } int main() { - /* Same code as in previous example. */ + /* Same code as in the previous example. */ } ``` Please note that subhook has a very simple length disassmebler engine (LDE) that works only with most common prologue instructions like push, mov, call, etc. When it encounters an unknown instruction subhook_get_trampoline() will -return NULL. +return NULL. You can delegate instruction decoding to a custom disassembler +of your choice via `subhook_set_disasm_handler()`. ### C++ @@ -93,7 +94,7 @@ typedef void (*foo_func)(int x); void my_foo(int x) { // ScopedHookRemove removes the specified hook and automatically re-installs - // it when the objectt goes out of scope (thanks to C++ destructors). + // it when the object goes out of scope (thanks to C++ destructors). subhook::ScopedHookRemove remove(&foo_hook); std::cout << "foo(" << x << ") called" << std::endl; @@ -116,14 +117,19 @@ int main() { Known issues ------------ +* `subhook_get_trampoline()` may return NULL because only a small subset of + x86 instructions is supported by the disassembler in this library (just + common prologue instructions). As a workaround you can plug in a more + advanced instruction length decoder using `subhook_set_disasm_handler()`. + * If a target function (the function you are hooking) is less than N bytes in length, for example if it's a short 2-byte jump to a nearby location (sometimes compilers generate code like this), then you will not be able to hook it. - N is 5 by default (1-byte jmp opcode + 32-bit offset), but it you enable - the use of 64-bit offsets in 64-bit mode N becomes 14 (see the definition - of `subhook_jmp64`). + N is 5 by default: 1 byte for jmp opcode + 4 bytes for offset. But if you + enable the use of 64-bit offsets in 64-bit mode N becomes 14 (see the + definition of `subhook_jmp64`). * Some systems protect executable code form being modified at runtime, which will not allow you to install hooks, or don't allow to mark heap-allocated @@ -1,4 +1,5 @@ -/* Copyright (c) 2012-2018 Zeex +/* + * Copyright (c) 2012-2018 Zeex * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,6 +27,8 @@ #include "subhook.h" #include "subhook_private.h" +subhook_disasm_handler_t subhook_disasm_handler = NULL; + SUBHOOK_EXPORT void *SUBHOOK_API subhook_get_src(subhook_t hook) { if (hook == NULL) { return NULL; @@ -54,6 +57,13 @@ SUBHOOK_EXPORT int SUBHOOK_API subhook_is_installed(subhook_t hook) { return hook->installed; } +SUBHOOK_EXPORT void SUBHOOK_API subhook_set_disasm_handler( + subhook_disasm_handler_t handler) { + subhook_disasm_handler = handler; +} + +#ifndef SUBHOOK_SEPARATE_SOURCE_FILES + #if defined SUBHOOK_WINDOWS #include "subhook_windows.c" #elif defined SUBHOOK_UNIX @@ -63,3 +73,5 @@ SUBHOOK_EXPORT int SUBHOOK_API subhook_is_installed(subhook_t hook) { #if defined SUBHOOK_X86 || defined SUBHOOK_X86_64 #include "subhook_x86.c" #endif + +#endif @@ -1,4 +1,5 @@ -/* Copyright (c) 2012-2018 Zeex +/* + * Copyright (c) 2012-2018 Zeex * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,7 +49,7 @@ #error Unsupported operating system #endif -#if !defined SUHOOK_EXTERN +#if !defined SUBHOOK_EXTERN #if defined __cplusplus #define SUBHOOK_EXTERN extern "C" #else @@ -97,9 +98,14 @@ typedef enum subhook_flags { struct subhook_struct; typedef struct subhook_struct *subhook_t; -SUBHOOK_EXPORT subhook_t SUBHOOK_API subhook_new(void *src, - void *dst, - subhook_flags_t flags); +typedef int (SUBHOOK_API *subhook_disasm_handler_t)( + void *src, + int *reloc_op_offset); + +SUBHOOK_EXPORT subhook_t SUBHOOK_API subhook_new( + void *src, + void *dst, + subhook_flags_t flags); SUBHOOK_EXPORT void SUBHOOK_API subhook_free(subhook_t hook); SUBHOOK_EXPORT void *SUBHOOK_API subhook_get_src(subhook_t hook); @@ -110,13 +116,25 @@ SUBHOOK_EXPORT int SUBHOOK_API subhook_install(subhook_t hook); SUBHOOK_EXPORT int SUBHOOK_API subhook_is_installed(subhook_t hook); SUBHOOK_EXPORT int SUBHOOK_API subhook_remove(subhook_t hook); -/* Reads hook destination address from code. +/* + * Reads hook destination address from code. * - * This is useful when you don't know the address or want to check - * whether src is already hooked. + * This function may be useful when you don't know the address or want to + * check whether src is already hooked. */ SUBHOOK_EXPORT void *SUBHOOK_API subhook_read_dst(void *src); +/* + * Sets a custom disassmbler function to use in place of the default one + * (subhook_disasm). + * + * The default function recognized a small st of x86 instructiosn commonly + * in prologues. If it fails in your situation you might want to use a more + * advanced disassembler library. + */ +SUBHOOK_EXPORT void SUBHOOK_API subhook_set_disasm_handler( + subhook_disasm_handler_t handler); + #ifdef __cplusplus namespace subhook { @@ -136,6 +154,14 @@ inline HookFlags operator&(HookFlags o1, HookFlags o2) { static_cast<unsigned int>(o1) & static_cast<unsigned int>(o2)); } +inline void *ReadHookDst(void *src) { + return subhook_read_dst(src); +} + +inline void SetDisasmHandler(subhook_disasm_handler_t handler) { + subhook_set_disasm_handler(handler); +} + class Hook { public: Hook() : hook_(0) {} @@ -174,10 +200,6 @@ class Hook { return !!subhook_is_installed(hook_); } - static void *ReadDst(void *src) { - return subhook_read_dst(src); - } - private: Hook(const Hook &); void operator=(const Hook &); diff --git a/subhook_private.h b/subhook_private.h index ec0dcc0..9d54781 100644 --- a/subhook_private.h +++ b/subhook_private.h @@ -1,4 +1,5 @@ -/* Copyright (c) 2012-2018 Zeex +/* + * Copyright (c) 2012-2018 Zeex * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/subhook_unix.c b/subhook_unix.c index 1c5260d..31f927e 100644 --- a/subhook_unix.c +++ b/subhook_unix.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2012-2018 Zeex +/* + * Copyright (c) 2012-2018 Zeex * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/subhook_windows.c b/subhook_windows.c index 13a514b..b1f0be6 100644 --- a/subhook_windows.c +++ b/subhook_windows.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2012-2018 Zeex +/* + * Copyright (c) 2012-2018 Zeex * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/subhook_x86.c b/subhook_x86.c index adb5dda..7c36b82 100644 --- a/subhook_x86.c +++ b/subhook_x86.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2012-2018 Zeex +/* + * Copyright (c) 2012-2018 Zeex * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -85,7 +86,9 @@ struct subhook_jmp64 { #pragma pack(pop) -static size_t subhook_disasm(void *src, int32_t *reloc_op_offset) { +extern subhook_disasm_handler_t subhook_disasm_handler; + +static int subhook_disasm(void *src, int *reloc_op_offset) { enum flags { MODRM = 1, PLUS_R = 1 << 1, @@ -116,13 +119,34 @@ static size_t subhook_disasm(void *src, int32_t *reloc_op_offset) { * https://www-ssl.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html */ static struct opcode_info opcodes[] = { + /* ADD AL, imm8 */ {0x04, 0, IMM8}, + /* ADD EAX, imm32 */ {0x05, 0, IMM32}, + /* ADD r/m8, imm8 */ {0x80, 0, MODRM | REG_OPCODE | IMM8}, + /* ADD r/m32, imm32 */ {0x81, 0, MODRM | REG_OPCODE | IMM32}, + /* ADD r/m32, imm8 */ {0x83, 0, MODRM | REG_OPCODE | IMM8}, + /* ADD r/m8, r8 */ {0x00, 0, MODRM}, + /* ADD r/m32, r32 */ {0x01, 0, MODRM}, + /* ADD r8, r/m8 */ {0x02, 0, MODRM}, + /* ADD r32, r/m32 */ {0x03, 0, MODRM}, + /* AND AL, imm8 */ {0x24, 0, IMM8}, + /* AND EAX, imm32 */ {0x25, 0, IMM32}, + /* AND r/m8, imm8 */ {0x80, 4, MODRM | REG_OPCODE | IMM8}, + /* AND r/m32, imm32 */ {0x81, 4, MODRM | REG_OPCODE | IMM32}, + /* AND r/m32, imm8 */ {0x83, 4, MODRM | REG_OPCODE | IMM8}, + /* AND r/m8, r8 */ {0x20, 0, MODRM}, + /* AND r/m32, r32 */ {0x21, 0, MODRM}, + /* AND r8, r/m8 */ {0x22, 0, MODRM}, + /* AND r32, r/m32 */ {0x23, 0, MODRM}, /* CALL rel32 */ {0xE8, 0, IMM32 | RELOC}, /* CALL r/m32 */ {0xFF, 2, MODRM | REG_OPCODE}, - /* DEC r/m16/32 */ {0xFF, 1, MODRM | REG_OPCODE }, /* CMP r/m16/32, imm8*/ {0x83, 7, MODRM | REG_OPCODE | IMM8 }, + /* DEC r/m16/32 */ {0xFF, 1, MODRM | REG_OPCODE }, + /* ENTER imm16, imm8 */ {0xC8, 0, IMM16 | IMM8}, + /* INT 3 */ {0xCC, 0, 0}, /* JMP rel32 */ {0xE9, 0, IMM32 | RELOC}, /* JMP r/m32 */ {0xFF, 4, MODRM | REG_OPCODE}, /* LEA r32,m */ {0x8D, 0, MODRM}, + /* LEAVE */ {0xC9, 0, 0}, /* MOV r/m8,r8 */ {0x88, 0, MODRM}, /* MOV r/m32,r32 */ {0x89, 0, MODRM}, /* MOV r8,r/m8 */ {0x8A, 0, MODRM}, @@ -137,6 +161,16 @@ static size_t subhook_disasm(void *src, int32_t *reloc_op_offset) { /* MOV r32, imm32 */ {0xB8, 0, PLUS_R | IMM32}, /* MOV r/m8, imm8 */ {0xC6, 0, MODRM | REG_OPCODE | IMM8}, /* MOV r/m32, imm32 */ {0xC7, 0, MODRM | REG_OPCODE | IMM32}, + /* NOP */ {0x90, 0, 0}, + /* OR AL, imm8 */ {0x0C, 0, IMM8}, + /* OR EAX, imm32 */ {0x0D, 0, IMM32}, + /* OR r/m8, imm8 */ {0x80, 1, MODRM | REG_OPCODE | IMM8}, + /* OR r/m32, imm32 */ {0x81, 1, MODRM | REG_OPCODE | IMM32}, + /* OR r/m32, imm8 */ {0x83, 1, MODRM | REG_OPCODE | IMM8}, + /* OR r/m8, r8 */ {0x08, 0, MODRM}, + /* OR r/m32, r32 */ {0x09, 0, MODRM}, + /* OR r8, r/m8 */ {0x0A, 0, MODRM}, + /* OR r32, r/m32 */ {0x0B, 0, MODRM}, /* POP r/m32 */ {0x8F, 0, MODRM | REG_OPCODE}, /* POP r32 */ {0x58, 0, PLUS_R}, /* PUSH r/m32 */ {0xFF, 6, MODRM | REG_OPCODE}, @@ -150,7 +184,9 @@ static size_t subhook_disasm(void *src, int32_t *reloc_op_offset) { /* SUB r/m8, imm8 */ {0x80, 5, MODRM | REG_OPCODE | IMM8}, /* SUB r/m32, imm32 */ {0x81, 5, MODRM | REG_OPCODE | IMM32}, /* SUB r/m32, imm8 */ {0x83, 5, MODRM | REG_OPCODE | IMM8}, + /* SUB r/m8, r8 */ {0x28, 0, MODRM}, /* SUB r/m32, r32 */ {0x29, 0, MODRM}, + /* SUB r8, r/m8 */ {0x2A, 0, MODRM}, /* SUB r32, r/m32 */ {0x2B, 0, MODRM}, /* TEST AL, imm8 */ {0xA8, 0, IMM8}, /* TEST EAX, imm32 */ {0xA9, 0, IMM32}, @@ -158,15 +194,23 @@ static size_t subhook_disasm(void *src, int32_t *reloc_op_offset) { /* TEST r/m32, imm32 */ {0xF7, 0, MODRM | REG_OPCODE | IMM32}, /* TEST r/m8, r8 */ {0x84, 0, MODRM}, /* TEST r/m32, r32 */ {0x85, 0, MODRM}, - /* XOR r32,r/m32 */ {0x33, 0, MODRM }, - /* NOP */ {0x90, 0, 0} + /* XOR AL, imm8 */ {0x34, 0, IMM8}, + /* XOR EAX, imm32 */ {0x35, 0, IMM32}, + /* XOR r/m8, imm8 */ {0x80, 6, MODRM | REG_OPCODE | IMM8}, + /* XOR r/m32, imm32 */ {0x81, 6, MODRM | REG_OPCODE | IMM32}, + /* XOR r/m32, imm8 */ {0x83, 6, MODRM | REG_OPCODE | IMM8}, + /* XOR r/m8, r8 */ {0x30, 0, MODRM}, + /* XOR r/m32, r32 */ {0x31, 0, MODRM}, + /* XOR r8, r/m8 */ {0x32, 0, MODRM}, + /* XOR r32, r/m32 */ {0x33, 0, MODRM} }; uint8_t *code = src; size_t i; - size_t len = 0; - size_t operand_size = 4; + int len = 0; + int operand_size = 4; uint8_t opcode = 0; + int found_opcode = false; for (i = 0; i < sizeof(prefixes) / sizeof(*prefixes); i++) { if (code[len] == prefixes[i]) { @@ -192,33 +236,31 @@ static size_t subhook_disasm(void *src, int32_t *reloc_op_offset) { #endif for (i = 0; i < sizeof(opcodes) / sizeof(*opcodes); i++) { - int found = false; - if (code[len] == opcodes[i].opcode) { if (opcodes[i].flags & REG_OPCODE) { - found = ((code[len + 1] >> 3) & 7) == opcodes[i].reg_opcode; + found_opcode = ((code[len + 1] >> 3) & 7) == opcodes[i].reg_opcode; } else { - found = true; + found_opcode = true; } } if ((opcodes[i].flags & PLUS_R) && (code[len] & 0xF8) == opcodes[i].opcode) { - found = true; + found_opcode = true; } - if (found) { + if (found_opcode) { opcode = code[len++]; break; } } - if (opcode == 0) { + if (!found_opcode) { return 0; } if (reloc_op_offset != NULL && opcodes[i].flags & RELOC) { - *reloc_op_offset = (int32_t)len; /* relative call or jump */ + *reloc_op_offset = len; /* relative call or jump */ } if (opcodes[i].flags & MODRM) { @@ -341,6 +383,8 @@ static int subhook_make_trampoline(void *trampoline, size_t insn_len; intptr_t trampoline_addr = (intptr_t)trampoline; intptr_t src_addr = (intptr_t)src; + subhook_disasm_handler_t disasm_handler = + subhook_disasm_handler != NULL ? subhook_disasm_handler : subhook_disasm; assert(trampoline_len != NULL); @@ -348,10 +392,10 @@ static int subhook_make_trampoline(void *trampoline, * to the trampoline. */ while (orig_size < jmp_size) { - int32_t reloc_op_offset = 0; + int reloc_op_offset = 0; insn_len = - subhook_disasm((void *)(src_addr + orig_size), &reloc_op_offset); + disasm_handler((void *)(src_addr + orig_size), &reloc_op_offset); if (insn_len == 0) { return -EINVAL; |