diff options
-rw-r--r-- | README.md | 18 | ||||
-rw-r--r-- | subhook.h | 46 | ||||
-rw-r--r-- | subhook_private.h | 2 | ||||
-rw-r--r-- | subhook_x86.c | 26 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 64 | ||||
-rw-r--r-- | tests/test.c | 4 | ||||
-rw-r--r-- | tests/test.cpp | 75 |
7 files changed, 159 insertions, 76 deletions
@@ -7,8 +7,8 @@ Linux and macOS. It supports x86 only (32-bit and 64-bit). Installation ------------ -Simply copy the files to your project and include subhook.c in your build. -The other source files wil be `#included` by the main C file automatically +Simply copy the files to your project and include subhook.c in your build. +The other source files wil be `#included` by the main C file automatically depending on the OS and achitecture. Use of CMake is not mandatory, the library can be built wihtout it (no extra @@ -116,19 +116,19 @@ int main() { Known issues ------------ -* 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 +* 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 + 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`). -* 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 +* 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 memory as executable, which prevents the use of trampolines. - + For example, on Fedora you can have such problems because of SELinux (though you can disable it or exclude your files). @@ -89,17 +89,17 @@ #endif #endif -typedef enum subhook_options { - /* Use 64-bit jump method on x86-64 (requires more space). */ - SUBHOOK_OPTION_64BIT_OFFSET = 1u << 1 -} subhook_options_t; +typedef enum subhook_flags { + /* Use the 64-bit jump method on x86-64 (requires more space). */ + SUBHOOK_64BIT_OFFSET = 1 +} subhook_flags_t; struct subhook_struct; typedef struct subhook_struct *subhook_t; SUBHOOK_EXPORT subhook_t SUBHOOK_API subhook_new(void *src, void *dst, - subhook_options_t options); + 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); @@ -121,37 +121,37 @@ SUBHOOK_EXPORT void *SUBHOOK_API subhook_read_dst(void *src); namespace subhook { -enum HookOptions { - HookOptionsNone = 0, - HookOption64BitOffset = SUBHOOK_OPTION_64BIT_OFFSET +enum HookFlags { + HookNoFlags = 0, + HookFlag64BitOffset = SUBHOOK_64BIT_OFFSET }; -inline HookOptions operator|(HookOptions o1, HookOptions o2) { - return static_cast<HookOptions>( +inline HookFlags operator|(HookFlags o1, HookFlags o2) { + return static_cast<HookFlags>( static_cast<unsigned int>(o1) | static_cast<unsigned int>(o2)); } -inline HookOptions operator&(HookOptions o1, HookOptions o2) { - return static_cast<HookOptions>( +inline HookFlags operator&(HookFlags o1, HookFlags o2) { + return static_cast<HookFlags>( static_cast<unsigned int>(o1) & static_cast<unsigned int>(o2)); } class Hook { public: Hook() : hook_(0) {} - Hook(void *src, - void *dst, - HookOptions options = HookOptionsNone) - : hook_(subhook_new(src, dst, (subhook_options_t)options)) {} + Hook(void *src, void *dst, HookFlags flags = HookNoFlags) + : hook_(subhook_new(src, dst, (subhook_flags_t)flags)) + { + } ~Hook() { subhook_remove(hook_); subhook_free(hook_); } - void *GetSrc() { return subhook_get_src(hook_); } - void *GetDst() { return subhook_get_dst(hook_); } - void *GetTrampoline() { return subhook_get_trampoline(hook_); } + void *GetSrc() const { return subhook_get_src(hook_); } + void *GetDst() const { return subhook_get_dst(hook_); } + void *GetTrampoline() const { return subhook_get_trampoline(hook_); } bool Install() { return subhook_install(hook_) >= 0; @@ -159,9 +159,9 @@ class Hook { bool Install(void *src, void *dst, - HookOptions options = HookOptionsNone) { + HookFlags flags = HookNoFlags) { if (hook_ == 0) { - hook_ = subhook_new(src, dst, (subhook_options_t)options); + hook_ = subhook_new(src, dst, (subhook_flags_t)flags); } return Install(); } @@ -220,9 +220,9 @@ class ScopedHookInstall { ScopedHookInstall(Hook *hook, void *src, void *dst, - HookOptions options = HookOptionsNone) + HookFlags flags = HookNoFlags) : hook_(hook) - , installed_(hook_->Install(src, dst, options)) + , installed_(hook_->Install(src, dst, flags)) { } diff --git a/subhook_private.h b/subhook_private.h index 37ef7b5..ec0dcc0 100644 --- a/subhook_private.h +++ b/subhook_private.h @@ -39,7 +39,7 @@ struct subhook_struct { int installed; void *src; void *dst; - subhook_options_t options; + subhook_flags_t flags; void *code; void *trampoline; size_t jmp_size; diff --git a/subhook_x86.c b/subhook_x86.c index 613ec94..a9342ee 100644 --- a/subhook_x86.c +++ b/subhook_x86.c @@ -266,13 +266,13 @@ static size_t subhook_disasm(void *src, int32_t *reloc_op_offset) { return len; } -static size_t subhook_get_jmp_size(subhook_options_t options) { +static size_t subhook_get_jmp_size(subhook_flags_t flags) { #ifdef SUBHOOK_X86_64 - if ((options & SUBHOOK_OPTION_64BIT_OFFSET) != 0) { + if ((flags & SUBHOOK_64BIT_OFFSET) != 0) { return sizeof(struct subhook_jmp64); } #else - (void)options; + (void)flags; #endif return sizeof(struct subhook_jmp32); } @@ -318,13 +318,13 @@ static int subhook_make_jmp64(void *src, void *dst) { static int subhook_make_jmp(void *src, void *dst, - subhook_options_t options) { + subhook_flags_t flags) { #ifdef SUBHOOK_X86_64 - if ((options & SUBHOOK_OPTION_64BIT_OFFSET) != 0) { + if ((flags & SUBHOOK_64BIT_OFFSET) != 0) { return subhook_make_jmp64(src, dst); } #else - (void)options; + (void)flags; #endif return subhook_make_jmp32(src, dst); } @@ -333,7 +333,7 @@ static int subhook_make_trampoline(void *trampoline, void *src, size_t jmp_size, size_t *trampoline_len, - subhook_options_t options) { + subhook_flags_t flags) { size_t orig_size = 0; size_t insn_len; intptr_t trampoline_addr = (intptr_t)trampoline; @@ -383,12 +383,12 @@ static int subhook_make_trampoline(void *trampoline, */ return subhook_make_jmp((void *)(trampoline_addr + orig_size), (void *)(src_addr + orig_size), - options); + flags); } SUBHOOK_EXPORT subhook_t SUBHOOK_API subhook_new(void *src, void *dst, - subhook_options_t options) { + subhook_flags_t flags) { subhook_t hook; if ((hook = malloc(sizeof(*hook))) == NULL) { @@ -398,8 +398,8 @@ SUBHOOK_EXPORT subhook_t SUBHOOK_API subhook_new(void *src, hook->installed = 0; hook->src = src; hook->dst = dst; - hook->options = options; - hook->jmp_size = subhook_get_jmp_size(hook->options); + hook->flags = flags; + hook->jmp_size = subhook_get_jmp_size(hook->flags); hook->trampoline_size = hook->jmp_size * 2 + MAX_INSN_LEN; hook->trampoline_len = 0; @@ -430,7 +430,7 @@ SUBHOOK_EXPORT subhook_t SUBHOOK_API subhook_new(void *src, hook->src, hook->jmp_size, &hook->trampoline_len, - hook->options); + hook->flags); if (hook->trampoline_len == 0) { free(hook->trampoline); @@ -459,7 +459,7 @@ SUBHOOK_EXPORT int SUBHOOK_API subhook_install(subhook_t hook) { return -EINVAL; } - error = subhook_make_jmp(hook->src, hook->dst, hook->options); + error = subhook_make_jmp(hook->src, hook->dst, hook->flags); if (error >= 0) { hook->installed = true; return 0; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1b2514e..92c8461 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,39 +40,46 @@ add_custom_command( MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${asm_file}" ) -add_executable(subhook_test +add_executable(subhook_test_exe test.c "${CMAKE_CURRENT_BINARY_DIR}/${asm_file}.obj" ) +set_target_properties(subhook_test_exe PROPERTIES OUTPUT_NAME test) -set_target_properties(subhook_test PROPERTIES OUTPUT_NAME test) +enable_language(CXX) +add_executable(subhook_cxx_test_exe + test.cpp + "${CMAKE_CURRENT_BINARY_DIR}/${asm_file}.obj" +) +set_target_properties(subhook_cxx_test_exe PROPERTIES OUTPUT_NAME test++) -if(SUBHOOK_FORCE_32BIT) - if(APPLE) - set_target_properties(subhook_test PROPERTIES OSX_ARCHITECTURES i386) - endif() - if(UNIX) - set_property(TARGET subhook_test APPEND_STRING PROPERTY - COMPILE_FLAGS " -m32") - set_property(TARGET subhook_test APPEND_STRING PROPERTY LINK_FLAGS " -m32") +foreach(target subhook_test_exe subhook_cxx_test_exe) + if(SUBHOOK_FORCE_32BIT) + if(APPLE) + set_target_properties(${target} PROPERTIES OSX_ARCHITECTURES i386) + endif() + if(UNIX) + set_property(TARGET ${target} APPEND_STRING PROPERTY + COMPILE_FLAGS " -m32") + set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS " -m32") + endif() endif() -endif() -target_link_libraries(subhook_test subhook) + target_link_libraries(${target} subhook) -if(MSVC) - set_property(TARGET subhook_test - APPEND_STRING PROPERTY LINK_FLAGS " /INCREMENTAL:NO") -endif() + if(MSVC) + set_property(TARGET ${target} + APPEND_STRING PROPERTY LINK_FLAGS " /INCREMENTAL:NO") + endif() -if(APPLE AND CMAKE_SIZEOF_VOID_P EQUAL 8 AND NOT SUBHOOK_FORCE_32BIT) - set_property(TARGET subhook_test APPEND_STRING PROPERTY - LINK_FLAGS " -Wl,-no_pie") -endif() + if(APPLE AND CMAKE_SIZEOF_VOID_P EQUAL 8 AND NOT SUBHOOK_FORCE_32BIT) + set_property(TARGET ${target} APPEND_STRING PROPERTY + LINK_FLAGS " -Wl,-no_pie") + endif() -add_test(NAME test COMMAND $<TARGET_FILE:subhook_test>) + add_test(NAME ${target}_test COMMAND $<TARGET_FILE:${target}>) -set(expected_output "\ + set(expected_output "\ Testing initial install[\r\n]+\ foo_hooked\\(\\) called[\r\n]+\ foo\\(\\) called[\r\n]+\ @@ -83,10 +90,11 @@ Testing trampoline[\r\n]+\ foo_hooked_tr\\(\\) called[\r\n]+\ foo\\(\\) called[\r\n]+\ ") -set_tests_properties(test PROPERTIES - PASS_REGULAR_EXPRESSION "${expected_output}") + set_tests_properties(${target}_test PROPERTIES + PASS_REGULAR_EXPRESSION "${expected_output}") -if(WIN32 AND NOT SUBHOOK_STATIC) - set_tests_properties(test PROPERTIES - ENVIRONMENT PATH=$<TARGET_FILE_DIR:subhook>) -endif() + if(WIN32 AND NOT SUBHOOK_STATIC) + set_tests_properties(${target}_test PROPERTIES + ENVIRONMENT PATH=$<TARGET_FILE_DIR:subhook>) + endif() +endforeach() diff --git a/tests/test.c b/tests/test.c index 278a4fd..513c060 100644 --- a/tests/test.c +++ b/tests/test.c @@ -32,7 +32,7 @@ int main() { subhook_t foo_hook = subhook_new((void *)foo, (void *)foo_hooked, - SUBHOOK_OPTION_64BIT_OFFSET); + SUBHOOK_64BIT_OFFSET); if (foo_hook == NULL || subhook_install(foo_hook) < 0) { puts("Install failed"); return EXIT_FAILURE; @@ -61,7 +61,7 @@ int main() { subhook_t foo_hook_tr = subhook_new((void *)foo, (void *)foo_hooked_tr, - SUBHOOK_OPTION_64BIT_OFFSET); + SUBHOOK_64BIT_OFFSET); if (subhook_install(foo_hook_tr) < 0) { puts("Install failed"); return EXIT_FAILURE; diff --git a/tests/test.cpp b/tests/test.cpp new file mode 100644 index 0000000..0eee1fa --- /dev/null +++ b/tests/test.cpp @@ -0,0 +1,75 @@ +#include <iostream> +#include <subhook.h> + +typedef void (*foo_func_t)(); + +#ifdef SUBHOOK_X86 + #if defined SUBHOOK_WINDOWS + #define FOO_CALL __cdecl + #elif defined SUBHOOK_UNIX + #define FOO_CALL __attribute__((cdecl)) + #endif +#else + #define FOO_CALL +#endif + +extern "C" void FOO_CALL foo(); +foo_func_t foo_tr = nullptr; + +void foo_hooked() { + std::cout << "foo_hooked() called" << std::endl;; +} + +void foo_hooked_tr() { + std::cout << "foo_hooked_tr() called" << std::endl; + foo_tr(); +} + +int main() { + std::cout << "Testing initial install" << std::endl; + + subhook::Hook foo_hook((void *)foo, + (void *)foo_hooked, + subhook::HookFlag64BitOffset); + if (!foo_hook.Install()) { + std::cout << "Install failed" << std::endl; + return EXIT_FAILURE; + } + foo(); + if (!foo_hook.Remove()) { + std::cout << "Remove failed" << std::endl; + return EXIT_FAILURE; + } + foo(); + + std::cout << "Testing re-install" << std::endl; + + if (!foo_hook.Install()) { + std::cout << "Install failed" << std::endl; + return EXIT_FAILURE; + } + foo(); + if (!foo_hook.Remove()) { + std::cout << "Remove failed" << std::endl; + return EXIT_FAILURE; + } + foo(); + + std::cout << "Testing trampoline" << std::endl; + + subhook::Hook foo_hook_tr((void *)foo, + (void *)foo_hooked_tr, + subhook::HookFlag64BitOffset); + if (!foo_hook_tr.Install()) { + std::cout << "Install failed" << std::endl; + return EXIT_FAILURE; + } + foo_tr = (foo_func_t)foo_hook_tr.GetTrampoline(); + if (foo_tr == nullptr) { + std::cout << "Failed to build trampoline" << std::endl; + return EXIT_FAILURE; + } + foo(); + + return EXIT_SUCCESS; +} |