aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt11
-rw-r--r--LICENSE.txt23
-rw-r--r--README.md18
-rw-r--r--subhook.c14
-rw-r--r--subhook.h46
-rw-r--r--subhook_private.h3
-rw-r--r--subhook_unix.c3
-rw-r--r--subhook_windows.c3
-rw-r--r--subhook_x86.c78
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.
diff --git a/README.md b/README.md
index d7d1eb9..23e1620 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/subhook.c b/subhook.c
index 7118884..8ea7d51 100644
--- a/subhook.c
+++ b/subhook.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
@@ -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
diff --git a/subhook.h b/subhook.h
index 20402de..3fe102c 100644
--- a/subhook.h
+++ b/subhook.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
@@ -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;