diff --git a/.gitmodules b/.gitmodules index 8186bf1de4..687f0824f2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -23,9 +23,6 @@ [submodule "UnitTestFrameworkPkg/Library/GoogleTestLib/googletest"] path = UnitTestFrameworkPkg/Library/GoogleTestLib/googletest url = https://github.com/google/googletest.git -[submodule "UnitTestFrameworkPkg/Library/SubhookLib/subhook"] - path = UnitTestFrameworkPkg/Library/SubhookLib/subhook - url = https://github.com/Zeex/subhook.git [submodule "MdePkg/Library/BaseFdtLib/libfdt"] path = MdePkg/Library/BaseFdtLib/libfdt url = https://github.com/devicetree-org/pylibfdt.git diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook b/UnitTestFrameworkPkg/Library/SubhookLib/subhook deleted file mode 160000 index 83d4e1ebef..0000000000 --- a/UnitTestFrameworkPkg/Library/SubhookLib/subhook +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 83d4e1ebef3588fae48b69a7352cc21801cb70bc diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/.editorconfig b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/.editorconfig new file mode 100644 index 0000000000..8b9ca59445 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/.editorconfig @@ -0,0 +1,10 @@ +# EditorConfig is awesome: http://EditorConfig.org + +root = true + +[*] +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/.travis.yml b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/.travis.yml new file mode 100644 index 0000000000..59fa62da00 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/.travis.yml @@ -0,0 +1,30 @@ +language: c + +os: + - linux + - osx + - freebsd +env: + global: + - CTEST_OUTPUT_ON_FAILURE=ON + matrix: + - ARCH=x86 + - ARCH=x86_64 +addons: + apt: + packages: + - gcc + - g++ + - gcc-multilib + - g++-multilib + - cmake + - yasm +before_install: + - if [ $TRAVIS_OS_NAME == osx ]; then brew install yasm; fi + - if [ $TRAVIS_OS_NAME == freebsd ]; then sudo pkg install -y yasm; fi + +before_script: + - cmake . -DSUBHOOK_FORCE_32BIT=`([ $ARCH = x86 ] && echo 'ON') || echo 'OFF'` +script: + - make + - make test diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/CMakeLists.txt b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/CMakeLists.txt new file mode 100644 index 0000000000..2afd850707 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/CMakeLists.txt @@ -0,0 +1,109 @@ +cmake_minimum_required(VERSION 3.10) + +project(subhook VERSION 0.8.2 LANGUAGES C) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +include(GNUInstallDirs) + +macro(subhook_add_option_var name type default_value description) + set(${name}_DEFAULT ${default_value}) + if(DEFINED ${name}) + set(${name}_DEFAULT ${${name}}) + endif() + set(${name} ${${name}_DEFAULT} CACHE ${type} ${description}) +endmacro() + +subhook_add_option_var(SUBHOOK_STATIC BOOL OFF "Build as a static library") +subhook_add_option_var(SUBHOOK_INSTALL + BOOL ON "Enable installation and packaging of targets/files with CPack") +subhook_add_option_var(SUBHOOK_TESTS BOOL ON "Enable tests") +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 subhook_x86.c) +if(WIN32) + list(APPEND SUBHOOK_SOURCES subhook_windows.c) +elseif(UNIX) + list(APPEND SUBHOOK_SOURCES subhook_unix.c) +endif() + +if(SUBHOOK_STATIC) + add_library(subhook STATIC ${SUBHOOK_HEADERS} ${SUBHOOK_SOURCES}) + target_compile_definitions(subhook PUBLIC SUBHOOK_STATIC) +else() + add_library(subhook SHARED ${SUBHOOK_HEADERS} ${SUBHOOK_SOURCES}) +endif() + +add_library(subhook::subhook ALIAS subhook) + +target_compile_definitions(subhook PUBLIC + SUBHOOK_IMPLEMENTATION + SUBHOOK_SEPARATE_SOURCE_FILES +) +target_include_directories(subhook PUBLIC + $ + $ +) + +if(CMAKE_COMPILER_ID MATCHES GNU OR CMAKE_C_COMPILER_ID MATCHES Clang) + target_compile_options(subhook PRIVATE "-Wall -Wextra") +endif() + +if(SUBHOOK_FORCE_32BIT) + if(APPLE) + set_target_properties(subhook PROPERTIES OSX_ARCHITECTURES i386) + endif() + if(UNIX) + target_compile_options(subhook PRIVATE "-m32") + target_link_options(subhook PRIVATE "-m32") + endif() +endif() + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +if(SUBHOOK_INSTALL) + include(CMakePackageConfigHelpers) + + install(TARGETS subhook EXPORT ${PROJECT_NAME}Targets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + install(FILES ${SUBHOOK_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + set(config_file ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake) + set(version_file ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake) + set(config_install_destination lib/cmake/${PROJECT_NAME}) + + configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Config.cmake.in + ${config_file} + INSTALL_DESTINATION ${config_install_destination} + ) + + write_basic_package_version_file( + ${version_file} + COMPATIBILITY SameMajorVersion + ) + + install(FILES ${config_file} ${version_file} DESTINATION ${config_install_destination}) + install( + EXPORT ${PROJECT_NAME}Targets + NAMESPACE ${PROJECT_NAME}:: + DESTINATION ${config_install_destination} + ) +endif() + +set(CPACK_PACKAGE_NAME ${PROJECT_NAME}) +set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) + +include(CPack) +include(CTest) + +if(BUILD_TESTING AND SUBHOOK_TESTS) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/LICENSE.txt b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/LICENSE.txt new file mode 100644 index 0000000000..67e03821c6 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/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/UnitTestFrameworkPkg/Library/SubhookLib/subhook/README.md b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/README.md new file mode 100644 index 0000000000..01c905a0c3 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/README.md @@ -0,0 +1,165 @@ +[![Build Status][build_status]][build] +[![Build Status - Windows][build_status_win]][build_win] + +SubHook is a super-simple hooking library for C and C++ that works on Windows, +Linux and macOS. It supports x86 only (32-bit and 64-bit). + +Installation +------------ + +Easy method: + +1. Copy the source and header files to your project and include + [`subhook.c`](subhook.c) in your build. +2. On Windows only: Define `SUBHOOK_STATIC` before including `subhook.h`. + +With CMake: + +1. Copy the subhook repo to your project tree. +2. Call `add_subdirectory(path/to/subhook)` in your CMakeLists.txt. +3. Optional: configure how the library is built by setting these varaible prior + to `add_subdirectory(...)`: + + * `SUBHOOK_STATIC` - Build as static library (`OFF` by default) + * `SUBHOOK_INSTALL` - Enable installation and packaging of targets/files + with CPack (`OFF` by default) + * `SUBHOOK_TESTS` - Enable tests (`ON` by default) + * `SUBHOOK_FORCE_32BIT` - Configure for compiling 32-bit binaries on 64-bit + systems (default is `OFF`) + +Use of CMake is not mandatory, the library can be built without it (no extra +build configuration is required). + +Examples +-------- + +In the following examples `foo` is some function or a function pointer that +takes a single argument of type `int` and uses the same calling convention +as `my_foo` (depends on compiler). + +### Basic usage + +```c +#include +#include + +subhook_t foo_hook; + +void my_foo(int x) { + /* Remove the hook so that you can call the original function. */ + subhook_remove(foo_hook); + + printf("foo(%d) called\n", x); + foo(x); + + /* Install the hook back to intercept further calls. */ + subhook_install(foo_hook); +} + +int main() { + /* Create a hook that will redirect all foo() calls to to my_foo(). */ + foo_hook = subhook_new((void *)foo, (void *)my_foo, 0); + + /* Install it. */ + subhook_install(foo_hook); + + foo(123); + + /* Remove the hook and free memory when you're done. */ + subhook_remove(foo_hook); + subhook_free(foo_hook); +} +``` + +### Trampolines + +Using trampolines allows you to jump to the original code without removing +and re-installing hooks every time your function gets called. + +```c +typedef void (*foo_func)(int x); + +void my_foo(int x) { + printf("foo(%d) called\n", x); + + /* Call foo() via trampoline. */ + ((foo_func)subhook_get_trampoline(foo_hook))(x); +} + +int main() { + /* 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. You can delegate instruction decoding to a custom disassembler +of your choice via `subhook_set_disasm_handler()`. + +### C++ + +```c++ +#include +#include + +subhook::Hook foo_hook; +subhook::Hook foo_hook_tr; + +typedef void (*foo_func)(int x); + +void my_foo(int x) { + // ScopedHookRemove removes the specified hook and automatically re-installs + // it when the object goes out of scope (thanks to C++ destructors). + subhook::ScopedHookRemove remove(&foo_hook); + + std::cout << "foo(" << x << ") called" << std::endl; + foo(x + 1); +} + +void my_foo_tr(int x) { + std::cout << "foo(" << x << ") called" << std::endl; + + // Call the original function via trampoline. + ((foo_func)foo_hook_tr.GetTrampoline())(x + 1); +} + +int main() { + foo_hook.Install((void *)foo, (void *)my_foo); + foo_hook_tr.Install((void *)foo, (void *)my_foo_tr); +} +``` + +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 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 + 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). + +License +------- + +Licensed under the 2-clause BSD license. + +[build]: https://travis-ci.org/Zeex/subhook +[build_status]: https://travis-ci.org/Zeex/subhook.svg?branch=master +[build_win]: https://ci.appveyor.com/project/Zeex/subhook/branch/master +[build_status_win]: https://ci.appveyor.com/api/projects/status/q5sp0p8ahuqfh8e4/branch/master?svg=true diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/appveyor.yml b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/appveyor.yml new file mode 100644 index 0000000000..c159d831b3 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/appveyor.yml @@ -0,0 +1,20 @@ +version: '{build}' + +platform: + - x86 + - x64 +configuration: + - Release +environment: + CTEST_OUTPUT_ON_FAILURE: ON + +install: + - choco install -y yasm +before_build: + - if %PLATFORM% == x86 set BUILD_ARCH=Win32 + - if %PLATFORM% == x64 set BUILD_ARCH=x64 + - cmake . -A %BUILD_ARCH% +build_script: + - cmake --build . --config %CONFIGURATION% +test_script: + - ctest --build-config %CONFIGURATION% diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/cmake/Config.cmake.in b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/cmake/Config.cmake.in new file mode 100644 index 0000000000..f83ace570c --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/cmake/Config.cmake.in @@ -0,0 +1,3 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/cmake/FindYasm.cmake b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/cmake/FindYasm.cmake new file mode 100644 index 0000000000..2fcf410b04 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/cmake/FindYasm.cmake @@ -0,0 +1,9 @@ +include(FindPackageHandleStandardArgs) + +find_file(YASM_EXECUTABLE NAMES yasm yasm.exe) +mark_as_advanced(YASM_EXECUTABLE) + +find_package_handle_standard_args(Yasm + FOUND_VAR YASM_FOUND + REQUIRED_VARS YASM_EXECUTABLE +) diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook.c b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook.c new file mode 100644 index 0000000000..8ea7d51d2a --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook.c @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#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; + } + return hook->src; +} + +SUBHOOK_EXPORT void *SUBHOOK_API subhook_get_dst(subhook_t hook) { + if (hook == NULL) { + return NULL; + } + return hook->dst; +} + +SUBHOOK_EXPORT void *SUBHOOK_API subhook_get_trampoline(subhook_t hook) { + if (hook == NULL) { + return NULL; + } + return hook->trampoline; +} + +SUBHOOK_EXPORT int SUBHOOK_API subhook_is_installed(subhook_t hook) { + if (hook == NULL) { + return false; + } + 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 + #include "subhook_unix.c" +#endif + +#if defined SUBHOOK_X86 || defined SUBHOOK_X86_64 + #include "subhook_x86.c" +#endif + +#endif diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook.h b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook.h new file mode 100644 index 0000000000..5633f39b46 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook.h @@ -0,0 +1,283 @@ +/* + * 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. + */ + +#ifndef SUBHOOK_H +#define SUBHOOK_H + +#include + +#if defined _M_IX86 || defined __i386__ + #define SUBHOOK_X86 + #define SUBHOOK_BITS 32 +#elif defined _M_AMD64 || __amd64__ + #define SUBHOOK_X86_64 + #define SUBHOOK_BITS 64 +#else + #error Unsupported architecture +#endif + +#if defined _WIN32 || defined __CYGWIN__ + #define SUBHOOK_WINDOWS +#elif defined __linux__ \ + || defined __FreeBSD__ || defined __OpenBSD__ || defined __NetBSD__ + #define SUBHOOK_UNIX + #elif defined __APPLE__ + #define SUBHOOK_APPLE + #define SUBHOOK_UNIX +#else + #error Unsupported operating system +#endif + +#if !defined SUBHOOK_EXTERN + #if defined __cplusplus + #define SUBHOOK_EXTERN extern "C" + #else + #define SUBHOOK_EXTERN extern + #endif +#endif + +#if defined SUBHOOK_STATIC + #define SUBHOOK_API + #define SUBHOOK_EXPORT SUBHOOK_EXTERN +#endif + +#if !defined SUBHOOK_API + #if defined SUBHOOK_X86 + #if defined SUBHOOK_WINDOWS + #define SUBHOOK_API __cdecl + #elif defined SUBHOOK_UNIX + #define SUBHOOK_API __attribute__((cdecl)) + #endif + #else + #define SUBHOOK_API + #endif +#endif + +#if !defined SUBHOOK_EXPORT + #if defined SUBHOOK_WINDOWS + #if defined SUBHOOK_IMPLEMENTATION + #define SUBHOOK_EXPORT SUBHOOK_EXTERN __declspec(dllexport) + #else + #define SUBHOOK_EXPORT SUBHOOK_EXTERN __declspec(dllimport) + #endif + #elif defined SUBHOOK_UNIX + #if defined SUBHOOK_IMPLEMENTATION + #define SUBHOOK_EXPORT SUBHOOK_EXTERN __attribute__((visibility("default"))) + #else + #define SUBHOOK_EXPORT SUBHOOK_EXTERN + #endif + #endif +#endif + +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; + +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); +SUBHOOK_EXPORT void *SUBHOOK_API subhook_get_dst(subhook_t hook); +SUBHOOK_EXPORT void *SUBHOOK_API subhook_get_trampoline(subhook_t hook); + +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. + * + * 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); + +/* + * Returns the length of the first instruction in src. You can replace it with + * a custom function via subhook_set_disasm_handler. + */ +SUBHOOK_EXPORT int SUBHOOK_API subhook_disasm(void *src, int *reloc_op_offset); + +/* + * Sets a custom disassmbler function to use in place of the default one + * (subhook_disasm). + * + * The default function can recognize only a small subset of x86 instructions + * commonly used 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 { + +enum HookFlags { + HookNoFlags = 0, + HookFlag64BitOffset = SUBHOOK_64BIT_OFFSET +}; + +inline HookFlags operator|(HookFlags o1, HookFlags o2) { + return static_cast( + static_cast(o1) | static_cast(o2)); +} + +inline HookFlags operator&(HookFlags o1, HookFlags o2) { + return static_cast( + static_cast(o1) & static_cast(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_(NULL) {} + 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() 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; + } + + bool Install(void *src, + void *dst, + HookFlags flags = HookNoFlags) { + if (hook_ != NULL) { + subhook_remove(hook_); + subhook_free(hook_); + } + hook_ = subhook_new(src, dst, (subhook_flags_t)flags); + if (hook_ == NULL) { + return false; + } + return Install(); + } + + bool Remove() { + return subhook_remove(hook_) == 0; + } + + bool IsInstalled() const { + return !!subhook_is_installed(hook_); + } + + private: + Hook(const Hook &); + void operator=(const Hook &); + + private: + subhook_t hook_; +}; + +class ScopedHookRemove { + public: + ScopedHookRemove(Hook *hook) + : hook_(hook), + removed_(hook_->Remove()) + { + } + + ~ScopedHookRemove() { + if (removed_) { + hook_->Install(); + } + } + + private: + ScopedHookRemove(const ScopedHookRemove &); + void operator=(const ScopedHookRemove &); + + private: + Hook *hook_; + bool removed_; +}; + +class ScopedHookInstall { + public: + ScopedHookInstall(Hook *hook) + : hook_(hook), + installed_(hook_->Install()) + { + } + + ScopedHookInstall(Hook *hook, + void *src, + void *dst, + HookFlags flags = HookNoFlags) + : hook_(hook), + installed_(hook_->Install(src, dst, flags)) + { + } + + ~ScopedHookInstall() { + if (installed_) { + hook_->Remove(); + } + } + + private: + ScopedHookInstall(const ScopedHookInstall &); + void operator=(const ScopedHookInstall &); + + private: + Hook *hook_; + bool installed_; +}; + +} // namespace subhook + +#endif /* __cplusplus */ + +#endif /* SUBHOOK_H */ diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook_private.h b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook_private.h new file mode 100644 index 0000000000..4b345af69e --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook_private.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#ifndef SUBHOOK_PRIVATE_H +#define SUBHOOK_PRIVATE_H + +#include + +#ifndef true + #define true 1 +#endif +#ifndef false + #define false 0 +#endif + +struct subhook_struct { + int installed; + void *src; + void *dst; + subhook_flags_t flags; + void *code; + void *trampoline; + size_t jmp_size; + size_t trampoline_size; + size_t trampoline_len; +}; + +int subhook_unprotect(void *address, size_t size); +void *subhook_alloc_code(size_t size); +int subhook_free_code(void *address, size_t size); + +#endif /* SUBHOOK_PRIVATE_H */ diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook_unix.c b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook_unix.c new file mode 100644 index 0000000000..697974d181 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook_unix.c @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#include +#include +#include +#include "subhook.h" +#ifdef SUBHOOK_APPLE +#include +#endif + + +#define SUBHOOK_CODE_PROTECT_FLAGS (PROT_READ | PROT_WRITE | PROT_EXEC) + +int subhook_unprotect(void *address, size_t size) { + long pagesize; + + pagesize = sysconf(_SC_PAGESIZE); + void *aligned_address = (void *)((long)address & ~(pagesize - 1)); + + // Fix up the length - since we rounded the start address off, if a jump is right at the + // end of a page we could need to unprotect both. + void *end = address + size; + size_t new_size = end - aligned_address; + + int error = mprotect(aligned_address, new_size, SUBHOOK_CODE_PROTECT_FLAGS); +#ifdef SUBHOOK_APPLE + if (-1 == error) + { + /* If mprotect fails, try to use VM_PROT_COPY with vm_protect. */ + kern_return_t kret = vm_protect(mach_task_self(), (unsigned long)aligned_address, new_size, 0, SUBHOOK_CODE_PROTECT_FLAGS | VM_PROT_COPY); + if (kret != KERN_SUCCESS) + { + error = -1; + } + error = 0; + } +#endif + return error; +} + +void *subhook_alloc_code(size_t size) { + void *address; + + address = mmap(NULL, + size, + SUBHOOK_CODE_PROTECT_FLAGS, + #if defined MAP_32BIT && !defined __APPLE__ + MAP_32BIT | + #endif + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0); + return address == MAP_FAILED ? NULL : address; +} + +int subhook_free_code(void *address, size_t size) { + if (address == NULL) { + return 0; + } + return munmap(address, size); +} diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook_windows.c b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook_windows.c new file mode 100644 index 0000000000..321207e168 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook_windows.c @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#include +#include + +#define SUBHOOK_CODE_PROTECT_FLAGS PAGE_EXECUTE_READWRITE + +int subhook_unprotect(void *address, size_t size) { + DWORD old_flags; + BOOL result = VirtualProtect(address, + size, + SUBHOOK_CODE_PROTECT_FLAGS, + &old_flags); + return !result; +} + +void *subhook_alloc_code(size_t size) { + return VirtualAlloc(NULL, + size, + MEM_COMMIT | MEM_RESERVE, + SUBHOOK_CODE_PROTECT_FLAGS); +} + +int subhook_free_code(void *address, size_t size) { + (void)size; + + if (address == NULL) { + return 0; + } + return !VirtualFree(address, 0, MEM_RELEASE); +} diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook_x86.c b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook_x86.c new file mode 100644 index 0000000000..0941be4bfe --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/subhook_x86.c @@ -0,0 +1,583 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +#include "subhook.h" +#include "subhook_private.h" + +#ifdef SUBHOOK_WINDOWS + #define INT32_MAX 0x7fffffff + #define INT32_MIN (-INT32_MAX - 1) + typedef unsigned __int8 uint8_t; + typedef __int32 int32_t; + typedef unsigned __int32 uint32_t; + typedef __int64 int64_t; + #ifdef SUBHOOK_X86_64 + typedef __int64 intptr_t; + typedef unsigned __int64 uintptr_t; + #else + typedef __int32 intptr_t; + typedef unsigned __int32 uintptr_t; + #endif +#else + #include +#endif + +#define ABS(x) ((x) >= 0 ? (x) : -(x)) +#define MAX_INSN_LEN 15 /* maximum length of x86 instruction */ + +#define JMP_OPCODE 0xE9 +#define PUSH_OPCODE 0x68 +#define MOV_OPCODE 0xC7 +#define RET_OPCODE 0xC3 + +#define JMP64_MOV_MODRM 0x44 /* write to address + 1 byte displacement */ +#define JMP64_MOV_SIB 0x24 /* write to [rsp] */ +#define JMP64_MOV_OFFSET 0x04 + +#define CHECK_INT32_OVERFLOW(x) \ + ((int64_t)(x) < INT32_MIN || ((int64_t)(x)) > INT32_MAX) + +#pragma pack(push, 1) + +struct subhook_jmp32 { + uint8_t opcode; + int32_t offset; +}; + +/* Since AMD64 doesn't support 64-bit direct jumps, we'll push the address + * onto the stack, then call RET. + */ +struct subhook_jmp64 { + uint8_t push_opcode; + uint32_t push_addr; /* lower 32-bits of the address to jump to */ + uint8_t mov_opcode; + uint8_t mov_modrm; + uint8_t mov_sib; + uint8_t mov_offset; + uint32_t mov_addr; /* upper 32-bits of the address to jump to */ + uint8_t ret_opcode; +}; + +#pragma pack(pop) + +extern subhook_disasm_handler_t subhook_disasm_handler; + +SUBHOOK_EXPORT int SUBHOOK_API subhook_disasm(void *src, int *reloc_op_offset) { + enum flags { + MODRM = 1, + PLUS_R = 1 << 1, + REG_OPCODE = 1 << 2, + IMM8 = 1 << 3, + IMM16 = 1 << 4, + IMM32 = 1 << 5, + RELOC = 1 << 6 + }; + + static uint8_t prefixes[] = { + 0xF0, 0xF2, 0xF3, + 0x2E, 0x36, 0x3E, 0x26, 0x64, 0x65, + 0x66, /* operand size override */ + 0x67 /* address size override */ + }; + + struct opcode_info { + uint8_t opcode; + uint8_t reg_opcode; + unsigned int flags; + }; + + /* + * See the Intel Developer Manual volumes 2a and 2b for more information + * about instruction format and encoding: + * + * 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}, + /* CMP r/m32, imm8 */ {0x83, 7, MODRM | REG_OPCODE | IMM8}, + /* CMP r/m32, r32 */ {0x39, 0, MODRM}, + /* CMP imm16/32 */ {0x3D, 0, IMM32}, + /* DEC r/m32 */ {0xFF, 1, MODRM | REG_OPCODE}, + /* DEC r32 */ {0x48, 0, PLUS_R}, + /* ENTER imm16, imm8 */ {0xC8, 0, IMM16 | IMM8}, + /* FLD m32fp */ {0xD9, 0, MODRM | REG_OPCODE}, + /* FLD m64fp */ {0xDD, 0, MODRM | REG_OPCODE}, + /* FLD m80fp */ {0xDB, 5, MODRM | REG_OPCODE}, + /* 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}, + /* MOV r32,r/m32 */ {0x8B, 0, MODRM}, + /* MOV r/m16,Sreg */ {0x8C, 0, MODRM}, + /* MOV Sreg,r/m16 */ {0x8E, 0, MODRM}, + /* MOV AL,moffs8 */ {0xA0, 0, IMM8}, + /* MOV EAX,moffs32 */ {0xA1, 0, IMM32}, + /* MOV moffs8,AL */ {0xA2, 0, IMM8}, + /* MOV moffs32,EAX */ {0xA3, 0, IMM32}, + /* MOV r8, imm8 */ {0xB0, 0, PLUS_R | IMM8}, + /* 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}, + /* PUSH r32 */ {0x50, 0, PLUS_R}, + /* PUSH imm8 */ {0x6A, 0, IMM8}, + /* PUSH imm32 */ {0x68, 0, IMM32}, + /* RET */ {0xC3, 0, 0}, + /* RET imm16 */ {0xC2, 0, IMM16}, + /* SUB AL, imm8 */ {0x2C, 0, IMM8}, + /* SUB EAX, imm32 */ {0x2D, 0, IMM32}, + /* 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}, + /* TEST r/m8, imm8 */ {0xF6, 0, MODRM | REG_OPCODE | IMM8}, + /* 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 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; + 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]) { + len++; + if (prefixes[i] == 0x66) { + operand_size = 2; + } + } + } + +#ifdef SUBHOOK_X86_64 + if ((code[len] & 0xF0) == 0x40) { + /* This is a REX prefix (40H - 4FH). REX prefixes are valid only in + * 64-bit mode. + */ + uint8_t rex = code[len++]; + + if (rex & 8) { + /* REX.W changes size of immediate operand to 64 bits. */ + operand_size = 8; + } + } +#endif + + for (i = 0; i < sizeof(opcodes) / sizeof(*opcodes); i++) { + if (code[len] == opcodes[i].opcode) { + if (opcodes[i].flags & REG_OPCODE) { + found_opcode = ((code[len + 1] >> 3) & 7) == opcodes[i].reg_opcode; + } else { + found_opcode = true; + } + } + + if ((opcodes[i].flags & PLUS_R) + && (code[len] & 0xF8) == opcodes[i].opcode) { + found_opcode = true; + } + + if (found_opcode) { + opcode = code[len++]; + break; + } + } + + if (!found_opcode) { + return 0; + } + + if (reloc_op_offset != NULL && opcodes[i].flags & RELOC) { + /* Either a call or a jump instruction that uses an absolute or relative + * 32-bit address. + * + * Note: We don't support short (8-bit) offsets at the moment, so the + * caller can assume the operand will be always 4 bytes. + */ + *reloc_op_offset = len; + } + + if (opcodes[i].flags & MODRM) { + uint8_t modrm = code[len++]; /* +1 for Mod/RM byte */ + uint8_t mod = modrm >> 6; + uint8_t rm = modrm & 0x07; + + if (mod != 3 && rm == 4) { + uint8_t sib = code[len++]; /* +1 for SIB byte */ + uint8_t base = sib & 0x07; + + if (base == 5) { + /* The SIB is followed by a disp32 with no base if the MOD is 00B. + * Otherwise, disp8 or disp32 + [EBP]. + */ + if (mod == 1) { + len += 1; /* for disp8 */ + } else { + len += 4; /* for disp32 */ + } + } + } + +#ifdef SUBHOOK_X86_64 + if (reloc_op_offset != NULL && mod == 0 && rm == 5) { + /* RIP-relative addressing: target is at [RIP + disp32]. */ + *reloc_op_offset = (int32_t)len; + } +#endif + + if (mod == 1) { + len += 1; /* for disp8 */ + } + if (mod == 2 || (mod == 0 && rm == 5)) { + len += 4; /* for disp32 */ + } + } + + if (opcodes[i].flags & IMM8) { + len += 1; + } + if (opcodes[i].flags & IMM16) { + len += 2; + } + if (opcodes[i].flags & IMM32) { + len += operand_size; + } + + return len; +} + +static size_t subhook_get_jmp_size(subhook_flags_t flags) { +#ifdef SUBHOOK_X86_64 + if ((flags & SUBHOOK_64BIT_OFFSET) != 0) { + return sizeof(struct subhook_jmp64); + } +#else + (void)flags; +#endif + return sizeof(struct subhook_jmp32); +} + +static int subhook_make_jmp32(void *src, void *dst) { + struct subhook_jmp32 *jmp = (struct subhook_jmp32 *)src; + intptr_t src_addr = (intptr_t)src; + intptr_t dst_addr = (intptr_t)dst; +#ifdef SUBHOOK_X86_64 + int64_t distance = ABS(src_addr - dst_addr); +#endif + +#ifdef SUBHOOK_X86_64 + if (CHECK_INT32_OVERFLOW(distance)) { + return -EOVERFLOW; + } +#endif + + jmp->opcode = JMP_OPCODE; + jmp->offset = (int32_t)(dst_addr - (src_addr + sizeof(*jmp))); + + return 0; +} + +#ifdef SUBHOOK_X86_64 + +static int subhook_make_jmp64(void *src, void *dst) { + struct subhook_jmp64 *jmp = (struct subhook_jmp64 *)src; + + jmp->push_opcode = PUSH_OPCODE; + jmp->push_addr = (uint32_t)(uintptr_t)dst; /* truncate */ + jmp->mov_opcode = MOV_OPCODE; + jmp->mov_modrm = JMP64_MOV_MODRM; + jmp->mov_sib = JMP64_MOV_SIB; + jmp->mov_offset = JMP64_MOV_OFFSET; + jmp->mov_addr = (uint32_t)(((uintptr_t)dst) >> 32); + jmp->ret_opcode = RET_OPCODE; + + return 0; +} + +#endif + +static int subhook_make_jmp(void *src, + void *dst, + subhook_flags_t flags) { +#ifdef SUBHOOK_X86_64 + if ((flags & SUBHOOK_64BIT_OFFSET) != 0) { + return subhook_make_jmp64(src, dst); + } +#else + (void)flags; +#endif + return subhook_make_jmp32(src, dst); +} + +static int subhook_make_trampoline(void *trampoline, + void *src, + size_t jmp_size, + size_t *trampoline_len, + subhook_flags_t flags) { + size_t orig_size = 0; + 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); + + /* Determine how many bytes of original code needs to be copied over + * to the trampoline. + */ + while (orig_size < jmp_size) { + int reloc_op_offset = 0; + + insn_len = + disasm_handler((void *)(src_addr + orig_size), &reloc_op_offset); + + if (insn_len == 0) { + return -EINVAL; + } + + /* Copy this instruction to the trampoline. + */ + memcpy((void *)(trampoline_addr + orig_size), + (void *)(src_addr + orig_size), + insn_len); + + /* If the operand is a relative address, such as found in calls or jumps, + * it needs to be relocated because the original code and the trampoline + * reside at different locations in memory. + */ + if (reloc_op_offset > 0) { + /* Calculate how far our trampoline is from the source and change the + * address accordingly. + */ + intptr_t offset = trampoline_addr - src_addr; +#ifdef SUBHOOK_X86_64 + if (CHECK_INT32_OVERFLOW(offset)) { + /* + * Oops! It looks like the two locations are too far away from each + * other! This is not going to work... + */ + *trampoline_len = 0; + return -EOVERFLOW; + } +#endif + int32_t *op = (int32_t *)(trampoline_addr + orig_size + reloc_op_offset); + *op -= (int32_t)offset; + } + + orig_size += insn_len; + } + + *trampoline_len = orig_size + jmp_size; + + /* Insert the final jump. It goes back to the original code at + * src + orig_size. + */ + return subhook_make_jmp((void *)(trampoline_addr + orig_size), + (void *)(src_addr + orig_size), + flags); +} + +SUBHOOK_EXPORT subhook_t SUBHOOK_API subhook_new(void *src, + void *dst, + subhook_flags_t flags) { + subhook_t hook; + int error; + + hook = calloc(1, sizeof(*hook)); + if (hook == NULL) { + return NULL; + } + + hook->src = src; + hook->dst = dst; + hook->flags = flags; + hook->jmp_size = subhook_get_jmp_size(hook->flags); + hook->trampoline_size = hook->jmp_size * 2 + MAX_INSN_LEN; + + hook->code = malloc(hook->jmp_size); + if (hook->code == NULL) { + goto error_exit; + } + + memcpy(hook->code, hook->src, hook->jmp_size); + + error = subhook_unprotect(hook->src, hook->jmp_size); + if (error != 0) { + goto error_exit; + } + + hook->trampoline = subhook_alloc_code(hook->trampoline_size); + if (hook->trampoline != NULL) { + error = subhook_make_trampoline(hook->trampoline, + hook->src, + hook->jmp_size, + &hook->trampoline_len, + hook->flags); + if (error != 0) { + subhook_free_code(hook->trampoline, hook->trampoline_size); + hook->trampoline = NULL; + hook->trampoline_size = 0; + hook->trampoline_len = 0; + } + } + + return hook; + +error_exit: + subhook_free_code(hook->trampoline, hook->trampoline_size); + free(hook->code); + free(hook); + + return NULL; +} + +SUBHOOK_EXPORT void SUBHOOK_API subhook_free(subhook_t hook) { + if (hook == NULL) { + return; + } + + subhook_free_code(hook->trampoline, hook->trampoline_size); + free(hook->code); + free(hook); +} + +SUBHOOK_EXPORT int SUBHOOK_API subhook_install(subhook_t hook) { + int error; + + if (hook == NULL) { + return -EINVAL; + } + if (hook->installed) { + return -EINVAL; + } + + error = subhook_make_jmp(hook->src, hook->dst, hook->flags); + if (error >= 0) { + hook->installed = true; + return 0; + } + + return error; +} + +SUBHOOK_EXPORT int SUBHOOK_API subhook_remove(subhook_t hook) { + if (hook == NULL) { + return -EINVAL; + } + if (!hook->installed) { + return -EINVAL; + } + + memcpy(hook->src, hook->code, hook->jmp_size); + hook->installed = 0; + + return 0; +} + +SUBHOOK_EXPORT void *SUBHOOK_API subhook_read_dst(void *src) { + struct subhook_jmp32 *maybe_jmp32 = (struct subhook_jmp32 *)src; +#ifdef SUBHOOK_X86_64 + struct subhook_jmp64 *maybe_jmp64 = (struct subhook_jmp64 *)src; +#endif + + if (maybe_jmp32->opcode == JMP_OPCODE) { + return (void *)( + maybe_jmp32->offset + (uintptr_t)src + sizeof(*maybe_jmp32)); + } + +#ifdef SUBHOOK_X86_64 + if (maybe_jmp64->push_opcode == PUSH_OPCODE + && maybe_jmp64->mov_opcode == MOV_OPCODE + && maybe_jmp64->mov_modrm == JMP64_MOV_MODRM + && maybe_jmp64->mov_sib == JMP64_MOV_SIB + && maybe_jmp64->mov_offset == JMP64_MOV_OFFSET + && maybe_jmp64->ret_opcode == RET_OPCODE) { + return (void *)( + maybe_jmp64->push_addr & ((uintptr_t)maybe_jmp64->mov_addr << 32)); + } +#endif + + return NULL; +} diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/CMakeLists.txt b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/CMakeLists.txt new file mode 100644 index 0000000000..41cbb24901 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/CMakeLists.txt @@ -0,0 +1,103 @@ +find_package(Yasm REQUIRED) + +if (CMAKE_SIZEOF_VOID_P EQUAL 4 OR SUBHOOK_FORCE_32BIT) + set(BITS 32) +else() + set(BITS 64) +endif() + +set(asm_file foo_${BITS}.asm) +if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND NOT SUBHOOK_FORCE_32BIT) + if(WIN32) + set(asm_file foo_64_win.asm) + elseif(UNIX) + set(asm_file foo_64_unix.asm) + set(asm_obj_file ${asm_file}.o) + endif() +endif() +if(NOT asm_obj_file) + set(asm_obj_file ${asm_file}.obj) +endif() + +if(WIN32) + set(asm_format "win${BITS}") +elseif(APPLE) + set(asm_format "macho${BITS}") +else() + set(asm_format "elf${BITS}") +endif() + +set(options "-f" "${asm_format}") +if(APPLE OR (WIN32 AND (CMAKE_SIZEOF_VOID_P EQUAL 4 OR SUBHOOK_FORCE_32BIT))) + list(APPEND options "--prefix=_") +endif() +if(CMAKE_SYSTEM_NAME MATCHES "Linux") + list(APPEND options "-DUSE_PLT") +endif() + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${asm_obj_file}" + COMMAND "${YASM_EXECUTABLE}" ${options} "-o" + "${CMAKE_CURRENT_BINARY_DIR}/${asm_obj_file}" + "${CMAKE_CURRENT_SOURCE_DIR}/${asm_file}" + MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${asm_file}" +) + +add_executable(subhook_test_exe + test.c + "${CMAKE_CURRENT_BINARY_DIR}/${asm_obj_file}" +) +set_target_properties(subhook_test_exe PROPERTIES OUTPUT_NAME test) + +enable_language(CXX) +add_executable(subhook_cxx_test_exe + test.cpp + "${CMAKE_CURRENT_BINARY_DIR}/${asm_obj_file}" +) +set_target_properties(subhook_cxx_test_exe PROPERTIES OUTPUT_NAME test++) + +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() + + target_link_libraries(${target} subhook) + + 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 ${target} APPEND_STRING PROPERTY + LINK_FLAGS " -Wl,-no_pie") + endif() + + add_test(NAME ${target}_test COMMAND $) + + set(expected_output +"Testing initial install +foo_hooked\\(\\) called +foo\\(\\) called +Testing re-install +foo_hooked\\(\\) called +foo\\(\\) called +Testing trampoline +foo_hooked_tr\\(\\) called +foo\\(\\) called +") + set_tests_properties(${target}_test PROPERTIES + PASS_REGULAR_EXPRESSION "${expected_output}") + + if(WIN32 AND NOT SUBHOOK_STATIC) + set_tests_properties(${target}_test PROPERTIES + ENVIRONMENT PATH=$) + endif() +endforeach() diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo.cpp b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo.cpp new file mode 100644 index 0000000000..c3e7def921 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo.cpp @@ -0,0 +1,5 @@ +#include + +extern "C" void foo() { + puts("foo() called"); +} diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_32.asm b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_32.asm new file mode 100644 index 0000000000..f1de5b31d3 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_32.asm @@ -0,0 +1,31 @@ +extern puts +global foo + +section .rodata + +message: + db 'foo() called', 0 + +section .text + +;; Long nop macros for nasm/yasm borrowed from nasm-utils: +;; https://github.com/travisdowns/nasm-utils +%define nop1 nop ; just a nop, included for completeness +%define nop2 db 0x66, 0x90 ; 66 NOP +%define nop3 db 0x0F, 0x1F, 0x00 ; NOP DWORD ptr [EAX] +%define nop4 db 0x0F, 0x1F, 0x40, 0x00 ; NOP DWORD ptr [EAX + 00H] +%define nop5 db 0x0F, 0x1F, 0x44, 0x00, 0x00 ; NOP DWORD ptr [EAX + EAX*1 + 00H] +%define nop6 db 0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00 ; 66 NOP DWORD ptr [EAX + EAX*1 + 00H] +%define nop7 db 0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00 ; NOP DWORD ptr [EAX + 00000000H] +%define nop8 db 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 ; NOP DWORD ptr [EAX + EAX*1 + 00000000H] +%define nop9 db 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 ; 66 NOP DWORD ptr [EAX + EAX*1 + 00000000H] + +foo: + push ebp + mov ebp, esp + sub esp, 4 ; align the stack to a 16-byte boundary + push message + call puts + mov esp, ebp + pop ebp + ret diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_64_unix.asm b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_64_unix.asm new file mode 100644 index 0000000000..e91767a469 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_64_unix.asm @@ -0,0 +1,24 @@ +bits 64 + +extern puts +global foo + +section .data + +message: + db 'foo() called', 0 + +section .text + +foo: + nop + push rbp + mov rbp, rsp + lea rdi, [rel message] + %ifdef USE_PLT + call puts wrt ..plt + %else + call puts + %endif + pop rbp + ret diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_64_win.asm b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_64_win.asm new file mode 100644 index 0000000000..e628aa2d43 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_64_win.asm @@ -0,0 +1,22 @@ +bits 64 + +extern puts +global foo + +section .data + +message: + db 'foo() called', 0 + +section .text + +foo: + nop + push rbp + mov rbp, rsp + sub rsp, 32 + mov rcx, qword message + call puts + add rsp, 32 + pop rbp + ret diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_main.c b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_main.c new file mode 100644 index 0000000000..a26e671f1a --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_main.c @@ -0,0 +1,6 @@ +extern void foo(void); + +int main() { + foo(); + return 0; +} diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_main.cpp b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_main.cpp new file mode 100644 index 0000000000..b49a3ccae1 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/foo_main.cpp @@ -0,0 +1,6 @@ +extern "C" void foo(void); + +int main() { + foo(); + return 0; +} diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/test.c b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/test.c new file mode 100644 index 0000000000..d8b39fda69 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/test.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include + +typedef void (*foo_func_t)(void); + +#ifdef SUBHOOK_X86 + #if defined SUBHOOK_WINDOWS + #define FOO_CALL __cdecl + #elif defined SUBHOOK_UNIX + #define FOO_CALL __attribute__((cdecl)) + #endif +#endif +#ifndef FOO_CALL + #define FOO_CALL +#endif + +extern void FOO_CALL foo(void); +foo_func_t foo_tr = NULL; + +void foo_hooked(void) { + puts("foo_hooked() called"); +} + +void foo_hooked_tr(void) { + puts("foo_hooked_tr() called"); + foo_tr(); +} + +int main() { + puts("Testing initial install"); + + subhook_t foo_hook = subhook_new((void *)foo, + (void *)foo_hooked, + SUBHOOK_64BIT_OFFSET); + if (foo_hook == NULL || subhook_install(foo_hook) < 0) { + puts("Install failed"); + return EXIT_FAILURE; + } + foo(); + if (subhook_remove(foo_hook) < 0) { + puts("Remove failed"); + return EXIT_FAILURE; + } + foo(); + + puts("Testing re-install"); + + if (subhook_install(foo_hook) < 0) { + puts("Install failed"); + return EXIT_FAILURE; + } + foo(); + if (subhook_remove(foo_hook) < 0) { + puts("Remove failed"); + return EXIT_FAILURE; + } + foo(); + + subhook_free(foo_hook); + + puts("Testing trampoline"); + + subhook_t foo_hook_tr = subhook_new((void *)foo, + (void *)foo_hooked_tr, + SUBHOOK_64BIT_OFFSET); + if (subhook_install(foo_hook_tr) < 0) { + puts("Install failed"); + return EXIT_FAILURE; + } + foo_tr = (foo_func_t)subhook_get_trampoline(foo_hook_tr); + if (foo_tr == NULL) { + puts("Failed to build trampoline"); + return EXIT_FAILURE; + } + foo(); + + subhook_free(foo_hook_tr); + + return EXIT_SUCCESS; +} diff --git a/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/test.cpp b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/test.cpp new file mode 100644 index 0000000000..7ba167ad99 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/SubhookLib/subhook/tests/test.cpp @@ -0,0 +1,77 @@ +#include +#include +#include + +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 +#endif +#ifndef FOO_CALL + #define FOO_CALL +#endif + +extern "C" void FOO_CALL foo(); +foo_func_t foo_tr = 0; + +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 == 0) { + std::cout << "Failed to build trampoline" << std::endl; + return EXIT_FAILURE; + } + foo(); + + return EXIT_SUCCESS; +}