├── .clang-format ├── .editorconfig ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── src ├── CMakeLists.txt └── renhook │ ├── exception.cpp │ ├── exception.hpp │ ├── executable.cpp │ ├── executable.hpp │ ├── hook_writer.cpp │ ├── hook_writer.hpp │ ├── hooks │ └── prologue_hook.hpp │ ├── memory │ ├── memory_allocator.cpp │ ├── memory_allocator.hpp │ ├── protection_enum.hpp │ ├── utils.hpp │ ├── virtual_protect.cpp │ └── virtual_protect.hpp │ ├── pattern.cpp │ ├── pattern.hpp │ ├── renhook.hpp │ ├── suspend_threads.cpp │ ├── suspend_threads.hpp │ ├── utils.cpp │ ├── utils.hpp │ ├── version.hpp │ ├── version.hpp.in │ ├── zydis.cpp │ └── zydis.hpp └── tests ├── CMakeLists.txt ├── executable.cpp ├── hook_writer.cpp ├── hooks └── prologue_hook.cpp ├── main.cpp ├── memory ├── memory_allocator.cpp ├── utils.cpp └── virtual_protect.cpp ├── pattern.cpp ├── suspend_threads.cpp ├── utils.cpp └── zydis.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | IndentWidth: 4 3 | UseTab: false 4 | ColumnLimit: 0 5 | MaxEmptyLinesToKeep: 1 6 | --- 7 | Language: Cpp 8 | Standard: Cpp11 9 | 10 | AccessModifierOffset: -4 11 | AlignAfterOpenBracket: Align 12 | AlignConsecutiveAssignments: false 13 | AlignConsecutiveDeclarations: false 14 | AlignEscapedNewlines: Left 15 | AlignOperands: true 16 | AlignTrailingComments: false 17 | AllowAllParametersOfDeclarationOnNextLine: false 18 | AllowShortBlocksOnASingleLine: false 19 | AllowShortCaseLabelsOnASingleLine: false 20 | AllowShortFunctionsOnASingleLine: None 21 | AllowShortIfStatementsOnASingleLine: false 22 | AllowShortLoopsOnASingleLine: false 23 | AlwaysBreakAfterReturnType: None 24 | AlwaysBreakBeforeMultilineStrings: false 25 | AlwaysBreakTemplateDeclarations: Yes 26 | BinPackArguments: true 27 | BinPackParameters: true 28 | BreakBeforeBinaryOperators: None 29 | BreakBeforeTernaryOperators: false 30 | BreakConstructorInitializers: BeforeComma 31 | BreakInheritanceList: BeforeComma 32 | BreakStringLiterals: true 33 | CompactNamespaces: false 34 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 35 | ConstructorInitializerIndentWidth: 4 36 | ContinuationIndentWidth: 2 37 | Cpp11BracedListStyle: false 38 | DerivePointerAlignment: false 39 | FixNamespaceComments: false 40 | IncludeBlocks: Preserve 41 | IndentCaseLabels: true 42 | IndentPPDirectives: None 43 | IndentWrappedFunctionNames: false 44 | KeepEmptyLinesAtTheStartOfBlocks: false 45 | NamespaceIndentation: All 46 | PointerAlignment: Left 47 | ReflowComments: true 48 | SortIncludes: true 49 | SortUsingDeclarations: true 50 | SpaceAfterCStyleCast: false 51 | SpaceAfterTemplateKeyword: false 52 | SpaceBeforeAssignmentOperators: true 53 | SpaceBeforeCpp11BracedList: true 54 | SpaceBeforeCtorInitializerColon: true 55 | SpaceBeforeInheritanceColon: true 56 | SpaceBeforeParens: ControlStatements 57 | SpaceBeforeRangeBasedForLoopColon: true 58 | SpaceInEmptyParentheses: false 59 | SpacesBeforeTrailingComments: 1 60 | SpacesInAngles: false 61 | SpacesInCStyleCastParentheses: false 62 | SpacesInContainerLiterals: true 63 | SpacesInParentheses: false 64 | SpacesInSquareBrackets: false 65 | 66 | BreakBeforeBraces: Custom 67 | BraceWrapping: 68 | AfterClass: true 69 | AfterControlStatement: true 70 | AfterEnum: true 71 | AfterFunction: true 72 | AfterNamespace: true 73 | AfterStruct: true 74 | AfterUnion: true 75 | AfterExternBlock: true 76 | BeforeCatch: true 77 | BeforeElse: true 78 | IndentBraces: false 79 | SplitEmptyFunction: true 80 | SplitEmptyRecord: true 81 | SplitEmptyNamespace: true 82 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{cpp,hpp}] 11 | indent_size = 4 12 | 13 | [*.yml] 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [ push, pull_request ] 3 | 4 | jobs: 5 | build: 6 | name: Build (${{ matrix.platform }}, ${{ matrix.config }}, C++${{ matrix.standard }}) 7 | runs-on: windows-latest 8 | 9 | strategy: 10 | matrix: 11 | platform: [ Win32, x64 ] 12 | config: [ Debug, Release ] 13 | standard: [ 11, 17, 20 ] 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | with: 19 | submodules: recursive 20 | fetch-depth: 0 21 | 22 | - name: Create build directory 23 | run: mkdir build 24 | 25 | - name: Configure 26 | working-directory: build 27 | run: | 28 | cmake ` 29 | -A ${{ matrix.platform }} ` 30 | -DCMAKE_CXX_STANDARD=${{ matrix.standard }} ` 31 | ${{ github.workspace }} 32 | 33 | - name: Build 34 | working-directory: build 35 | run: | 36 | cmake ` 37 | --build . ` 38 | --config ${{ matrix.config }} 39 | 40 | - name: Test 41 | working-directory: build/${{ matrix.config }}/bin 42 | run: ./Tests.exe 43 | 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directory. 2 | [Bb]uild/ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/catch2"] 2 | path = deps/catch2 3 | url = https://github.com/catchorg/Catch2.git 4 | [submodule "deps/zydis"] 5 | path = deps/zydis 6 | url = https://github.com/zyantific/zydis.git 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(RenHook LANGUAGES CXX VERSION 1.0.0) 4 | 5 | # Properties. 6 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 7 | 8 | # Variables. 9 | set(CMAKE_CXX_STANDARD 11) 10 | set(CMAKE_CXX_STANDARD_REQUIRED YES) 11 | 12 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/libs") 13 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") 14 | 15 | foreach(configuration ${CMAKE_CONFIGURATION_TYPES}) 16 | string(TOLOWER ${configuration} configuration_lower) 17 | string(TOUPPER ${configuration} configuration_upper) 18 | 19 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${configuration_upper} "${CMAKE_BINARY_DIR}/${configuration_lower}/libs") 20 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${configuration_upper} "${CMAKE_BINARY_DIR}/${configuration_lower}/bin") 21 | endforeach() 22 | 23 | # Options. 24 | option(RENHOOK_ENABLE_TESTS "Build unit tests" ON) 25 | 26 | # Dependencies. 27 | 28 | # Catch2 29 | if(RENHOOK_ENABLE_TESTS) 30 | set(CATCH_BUILD_STATIC_LIBRARY OFF CACHE INTERNAL "") 31 | set(BUILD_TESTING OFF CACHE INTERNAL "") 32 | set(CATCH_USE_VALGRIND OFF CACHE INTERNAL "") 33 | set(CATCH_BUILD_TESTING OFF CACHE INTERNAL "") 34 | set(CATCH_BUILD_EXAMPLES OFF CACHE INTERNAL "") 35 | set(CATCH_BUILD_EXTRA_TESTS OFF CACHE INTERNAL "") 36 | set(CATCH_ENABLE_COVERAGE OFF CACHE INTERNAL "") 37 | set(CATCH_ENABLE_WERROR OFF CACHE INTERNAL "") 38 | set(CATCH_INSTALL_DOCS OFF CACHE INTERNAL "") 39 | set(CATCH_INSTALL_HELPERS OFF CACHE INTERNAL "") 40 | 41 | add_subdirectory(deps/catch2) 42 | endif() 43 | 44 | # Zydis 45 | set(ZYAN_DEV_MODE OFF CACHE INTERNAL "") 46 | set(ZYAN_NO_LIBC OFF CACHE INTERNAL "") 47 | set(ZYAN_WHOLE_PROGRAM_OPTIMIZATION OFF CACHE INTERNAL "") 48 | 49 | set(ZYCORE_BUILD_EXAMPLES OFF CACHE INTERNAL "") 50 | set(ZYCORE_BUILD_SHARED_LIB OFF CACHE INTERNAL "") 51 | set(ZYCORE_BUILD_TESTS OFF CACHE INTERNAL "") 52 | 53 | set(ZYDIS_BUILD_EXAMPLES OFF CACHE INTERNAL "") 54 | set(ZYDIS_BUILD_SHARED_LIB OFF CACHE INTERNAL "") 55 | set(ZYDIS_BUILD_TOOLS OFF CACHE INTERNAL "") 56 | set(ZYDIS_FEATURE_AVX512 ON CACHE INTERNAL "") 57 | set(ZYDIS_FEATURE_DECODER ON CACHE INTERNAL "") 58 | set(ZYDIS_FEATURE_FORMATTER ON CACHE INTERNAL "") 59 | set(ZYDIS_FEATURE_KNC ON CACHE INTERNAL "") 60 | set(ZYDIS_FUZZ_AFL_FAST OFF CACHE INTERNAL "") 61 | set(ZYDIS_LIBFUZZER OFF CACHE INTERNAL "") 62 | set(ZYDIS_MINIMAL_MODE OFF CACHE INTERNAL "") 63 | 64 | add_subdirectory(deps/zydis) 65 | 66 | set_target_properties(Zydis PROPERTIES FOLDER "Dependencies") 67 | set_target_properties(Zycore PROPERTIES FOLDER "Dependencies") 68 | 69 | # Projects. 70 | add_subdirectory(src) 71 | 72 | if(RENHOOK_ENABLE_TESTS) 73 | add_subdirectory(tests) 74 | endif() 75 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 - present Octavian Dima 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RenHook 2 | 3 | [![Build Status](https://github.com/WopsS/RenHook/actions/workflows/build.yml/badge.svg)](https://github.com/WopsS/RenHook/actions/workflows/build.yml) 4 | 5 | An open-source **x86 / x86-64** hooking library for **Windows**. 6 | 7 | ## Features 8 | 9 | * Supports x86 and x86-64 (uses [Zydis](https://github.com/zyantific/zydis) as diassembler) 10 | * Completely written in C++11 11 | * Safe and easy to use 12 | * Hooking methods 13 | * **Inline hook** - Patches the prologue of a function to redirect its code flow, also allocates a trampoline to that can be used to execute the original function. 14 | 15 | ## Quick examples 16 | 17 | ### Hooking by address 18 | 19 | ```cpp 20 | #include 21 | #include 22 | 23 | void func_detour(); 24 | 25 | using func_t = void(*)(); 26 | renhook::inline_hook func_hook(0x14000000, &func_detour); 27 | 28 | void func_detour() 29 | { 30 | OutputDebugStringA("Hello from the hook!\n"); 31 | func_hook(); 32 | } 33 | 34 | int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) 35 | { 36 | func_hook.attach(); 37 | func_hook(); 38 | func_hook.detach(); 39 | 40 | func_hook(); 41 | return 0; 42 | } 43 | ``` 44 | 45 | ### Hooking by pattern 46 | 47 | ```cpp 48 | #include 49 | #include 50 | 51 | void func_detour(); 52 | 53 | using func_t = void(*)(); 54 | renhook::inline_hook func_hook({ 0x89, 0x79, 0xF8, 0xE8, 0xCC, 0xCC, 0xCC, 0xCC, 0x8B, 0x0D, 0xCC, 0xCC, 0xCC, 0xCC }, &func_detour, 0xCC, 3); 55 | 56 | void func_detour() 57 | { 58 | OutputDebugStringA("Hello from the hook!\n"); 59 | func_hook(); 60 | } 61 | 62 | int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) 63 | { 64 | func_hook.attach(); 65 | func_hook(); 66 | func_hook.detach(); 67 | 68 | func_hook(); 69 | return 0; 70 | } 71 | ``` 72 | 73 | ### Hooking a function from a module 74 | 75 | ```cpp 76 | #include 77 | #include 78 | 79 | int WINAPI msgbox_detour(HWND wnd, LPCWSTR text, LPCWSTR caption, UINT type); 80 | 81 | using MessageBoxW_t = int(WINAPI*)(HWND, LPCWSTR, LPCWSTR, UINT); 82 | renhook::inline_hook msgbox_hook("user32", "MessageBoxW", &msgbox_detour); 83 | 84 | int WINAPI msgbox_detour(HWND wnd, LPCWSTR text, LPCWSTR caption, UINT type) 85 | { 86 | return msgbox_hook(wnd, L"Hello from the hook!", L"RenHook", MB_OK | MB_ICONINFORMATION); 87 | } 88 | 89 | int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) 90 | { 91 | msgbox_hook.attach(); 92 | MessageBoxW(nullptr, L"Hello", L"Message", MB_OK); 93 | msgbox_hook.detach(); 94 | 95 | MessageBoxW(nullptr, L"Hello", L"Message", MB_OK); 96 | return 0; 97 | } 98 | ``` 99 | 100 | ## Build instructions 101 | 102 | ### Requirements 103 | 104 | * **[CMake 3.8+](https://cmake.org/)**. 105 | 106 | ### Windows 107 | 108 | 1. Download and install **[Visual Studio 2019 Community Edition](https://www.visualstudio.com/)** or a higher version. 109 | 2. Download and install the **[Requirements](#requirements)**. 110 | 3. Clone this repository. 111 | 4. Clone the dependencies (`git submodule update --init --recursive`). 112 | 5. Create a directory named `build` and run **[CMake](https://cmake.org/)** in it. 113 | 6. Open the solution (**RenHook.sln**) located in **build** directory. 114 | 7. Build the projects. 115 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(RenHook STATIC "") 2 | 3 | math(EXPR PROJECT_VERSION_INT "${PROJECT_VERSION_MAJOR} * 10000 + ${PROJECT_VERSION_MINOR} * 100 + ${PROJECT_VERSION_PATCH}") 4 | configure_file("renhook/version.hpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/renhook/version.hpp" @ONLY) 5 | 6 | file(GLOB_RECURSE HEADERS renhook/*.hpp) 7 | file(GLOB_RECURSE SOURCES renhook/*.cpp) 8 | 9 | source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/renhook" FILES ${HEADERS} ${SOURCES}) 10 | 11 | target_include_directories(RenHook PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 12 | target_sources(RenHook PRIVATE ${HEADERS} ${SOURCES}) 13 | target_link_libraries(RenHook PUBLIC Zydis) 14 | -------------------------------------------------------------------------------- /src/renhook/exception.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | renhook::exception::exception(const char* message) 4 | : runtime_error(message) 5 | { 6 | } 7 | 8 | renhook::exception::exception(const std::string& message) 9 | : runtime_error(message) 10 | { 11 | } 12 | 13 | renhook::exception::exception(const std::string& message, uint32_t last_error) 14 | : runtime_error(message + ", last_error: " + std::to_string(last_error)) 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /src/renhook/exception.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_EXCEPTION_H 2 | #define RENHOOK_EXCEPTION_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace renhook 9 | { 10 | /** 11 | * @brief The general exception of the library. 12 | */ 13 | class exception : public std::runtime_error 14 | { 15 | public: 16 | 17 | /** 18 | * @brief Construct a new exception. 19 | * 20 | * @param[in] message The message. 21 | */ 22 | exception(const char* message); 23 | 24 | /** 25 | * @brief Construct a new exception. 26 | * 27 | * @param[in] message The message. 28 | */ 29 | exception(const std::string& message); 30 | 31 | /** 32 | * @brief Construct a new exception. 33 | * 34 | * @param[in] message The message. 35 | * @param[in] last_error The error code returned by GetLastError. 36 | */ 37 | exception(const std::string& message, uint32_t last_error); 38 | 39 | ~exception() = default; 40 | }; 41 | } 42 | #endif 43 | -------------------------------------------------------------------------------- /src/renhook/executable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace 6 | { 7 | PIMAGE_NT_HEADERS get_nt_header() 8 | { 9 | auto base_address = renhook::executable::get_base_address(); 10 | auto dos_header = reinterpret_cast(base_address); 11 | return reinterpret_cast(base_address + static_cast(dos_header->e_lfanew)); 12 | } 13 | } 14 | 15 | uintptr_t renhook::executable::get_base_address() 16 | { 17 | static auto base_address = reinterpret_cast(GetModuleHandle(nullptr)); 18 | return base_address; 19 | } 20 | 21 | uintptr_t renhook::executable::get_code_base_address() 22 | { 23 | static auto code_base_address = get_base_address() + get_nt_header()->OptionalHeader.BaseOfCode; 24 | return code_base_address; 25 | } 26 | 27 | uintptr_t renhook::executable::get_code_end_address() 28 | { 29 | static auto code_end_address = get_code_base_address() + get_nt_header()->OptionalHeader.SizeOfCode; 30 | return code_end_address; 31 | } 32 | 33 | size_t renhook::executable::get_code_size() 34 | { 35 | static auto code_size = get_nt_header()->OptionalHeader.SizeOfCode; 36 | return code_size; 37 | } 38 | -------------------------------------------------------------------------------- /src/renhook/executable.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_EXECUTABLE_H 2 | #define RENHOOK_EXECUTABLE_H 3 | 4 | #include 5 | 6 | namespace renhook 7 | { 8 | /** 9 | * @brief Helper namespace for the executable. 10 | */ 11 | namespace executable 12 | { 13 | /** 14 | * @brief Returns the base address of the executable. 15 | * 16 | * @return The address of the executable. 17 | */ 18 | uintptr_t get_base_address(); 19 | 20 | /** 21 | * @brief Returns the start of the code section. 22 | * 23 | * @return The start of the code section. 24 | */ 25 | uintptr_t get_code_base_address(); 26 | 27 | /** 28 | * @brief Returns the base address of the executable. 29 | * 30 | * @return The address of the executable. 31 | */ 32 | uintptr_t get_code_end_address(); 33 | 34 | /** 35 | * @brief Returns the size of code (text) section. 36 | * 37 | * @return The size of code (text) section. 38 | */ 39 | uintptr_t get_code_size(); 40 | } 41 | } 42 | #endif 43 | -------------------------------------------------------------------------------- /src/renhook/hook_writer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | renhook::hook_writer::hook_writer(uint8_t* address) 7 | : m_address(address) 8 | { 9 | } 10 | 11 | renhook::hook_writer::hook_writer(uintptr_t address) 12 | : hook_writer(reinterpret_cast(address)) 13 | { 14 | } 15 | 16 | void renhook::hook_writer::copy_from(uint8_t* address, size_t length) 17 | { 18 | std::memcpy(m_address, address, length); 19 | m_address += length; 20 | } 21 | 22 | void renhook::hook_writer::copy_from(uintptr_t address, size_t length) 23 | { 24 | copy_from(reinterpret_cast(address), length); 25 | } 26 | 27 | #ifdef _WIN64 28 | void renhook::hook_writer::write_indirect_jump(uintptr_t target_address) 29 | { 30 | uint8_t bytes[] = 31 | { 32 | 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp [rip+0] 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Address where to jump. 34 | }; 35 | 36 | *(reinterpret_cast(&bytes[6])) = target_address; 37 | copy_from(bytes, sizeof(bytes)); 38 | } 39 | #endif 40 | 41 | void renhook::hook_writer::write_relative_jump(uintptr_t target_address) 42 | { 43 | uint8_t bytes[] = 44 | { 45 | 0xE9, 0x00, 0x00, 0x00, 0x00 // jmp [rip+0x??] 46 | }; 47 | 48 | *(reinterpret_cast(&bytes[1])) = static_cast(utils::calculate_displacement(reinterpret_cast(m_address), target_address, 5)); 49 | copy_from(bytes, sizeof(bytes)); 50 | } 51 | 52 | void renhook::hook_writer::write_nops(size_t size) 53 | { 54 | std::memset(m_address, 0x90, size); 55 | m_address += size; 56 | } 57 | -------------------------------------------------------------------------------- /src/renhook/hook_writer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_HOOK_WRITER_H 2 | #define RENHOOK_HOOK_WRITER_H 3 | 4 | #include 5 | 6 | namespace renhook 7 | { 8 | /** 9 | * @brief Helper class for writing a codecave. 10 | */ 11 | class hook_writer 12 | { 13 | public: 14 | 15 | /** 16 | * @brief Construct a hook writer. 17 | * 18 | * @param address[in] The address where to write. 19 | */ 20 | hook_writer(uint8_t* address); 21 | 22 | /** 23 | * @copydoc renhook::hook_writer::hook_writer(uint8_t*) 24 | */ 25 | hook_writer(uintptr_t address); 26 | 27 | ~hook_writer() = default; 28 | 29 | /** 30 | * @brief Copy bytes to the codecave. 31 | * 32 | * @param address[in] The address from where to copy. 33 | * @param length[in] The length. 34 | */ 35 | void copy_from(uint8_t* address, size_t length); 36 | 37 | /** 38 | * @copydoc renhook::hook_writer::copy_from(uint8_t*, size_t) 39 | */ 40 | void copy_from(uintptr_t address, size_t length); 41 | 42 | #ifdef _WIN64 43 | /** 44 | * @brief Write an indirect (14 bytes) jump to #target_address. 45 | * 46 | * @param target_address[in] The address where the jump is. 47 | */ 48 | void write_indirect_jump(uintptr_t target_address); 49 | #endif 50 | 51 | /** 52 | * @brief Write a relative (5 bytes) jump to #target_address. 53 | * 54 | * @param target_address[in] The address where the jump is. 55 | */ 56 | void write_relative_jump(uintptr_t target_address); 57 | 58 | /** 59 | * @brief Write NOP. 60 | * 61 | * @param size[in] The number of NOPs. 62 | */ 63 | void write_nops(size_t size); 64 | 65 | private: 66 | 67 | uint8_t* m_address; 68 | }; 69 | } 70 | #endif 71 | -------------------------------------------------------------------------------- /src/renhook/hooks/prologue_hook.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_HOOKS_PROLOGUE_HOOK_H 2 | #define RENHOOK_HOOKS_PROLOGUE_HOOK_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | namespace renhook 22 | { 23 | /** 24 | * @brief A function prologue hook. 25 | * 26 | * @tparam T The hooked function type. 27 | */ 28 | template 29 | class prologue_hook 30 | { 31 | public: 32 | 33 | /** 34 | * @brief The size of a relative jump. 35 | */ 36 | static constexpr size_t relative_jump_size = 5; 37 | 38 | /** 39 | * @brief The size of an indirect jump. 40 | */ 41 | static constexpr size_t indirect_jump_size = 14; 42 | 43 | /** 44 | * @brief Construct an empty hook. 45 | */ 46 | prologue_hook() 47 | : m_target_address(0) 48 | , m_detour_address(0) 49 | , m_wildcard(0) 50 | , m_offset(0) 51 | , m_attached(false) 52 | , m_decoded_length(0) 53 | , m_block(nullptr) 54 | { 55 | } 56 | 57 | /** 58 | * @brief Construct a new hook. 59 | * 60 | * @param target_address[in] The address of the function that will be hooked. 61 | * @param detour_address[in] The address of the callback. 62 | */ 63 | prologue_hook(uintptr_t target_address, uintptr_t detour_address) 64 | : prologue_hook() 65 | { 66 | m_target_address = target_address; 67 | m_detour_address = detour_address; 68 | } 69 | 70 | /** 71 | * @brief Construct a new hook. 72 | * 73 | * @param target_address[in] The address of the function that will be hooked. 74 | * @param detour_address[in] The address of the callback. 75 | */ 76 | prologue_hook(uintptr_t target_address, T detour_address) 77 | : prologue_hook(target_address, reinterpret_cast(detour_address)) 78 | { 79 | } 80 | 81 | /** 82 | * @brief Construct a new hook using a pattern. 83 | * 84 | * @param pattern[in] The pattern of the function that will be hooked. 85 | * @param detour_address[in] The address of the callback. 86 | * @param wildcard[in] The wildcard for #pattern. 87 | * @param offset[in] The offset of the #pattern. 88 | */ 89 | prologue_hook(pattern pattern, uintptr_t detour_address, uint8_t wildcard = 0xCC, size_t offset = 0) 90 | : prologue_hook() 91 | { 92 | m_detour_address = detour_address; 93 | m_pattern = std::move(pattern); 94 | m_wildcard = wildcard; 95 | m_offset = offset; 96 | } 97 | 98 | /** 99 | * @brief Construct a new hook using a pattern. 100 | * 101 | * @param pattern[in] The pattern of the function that will be hooked. 102 | * @param detour_address[in] The address of the callback. 103 | * @param wildcard[in] The wildcard for #pattern. 104 | * @param offset[in] The offset of the #pattern. 105 | */ 106 | prologue_hook(pattern pattern, T detour_address, uint8_t wildcard = 0xCC, size_t offset = 0) 107 | : prologue_hook(pattern, reinterpret_cast(detour_address), wildcard, offset) 108 | { 109 | } 110 | 111 | /** 112 | * @brief Construct a new hook. 113 | * 114 | * @param module[in] The module which contain the #function. 115 | * @param function[in] The function that will be hooked. 116 | * @param detour_address[in] The address of the callback. 117 | * 118 | * @note If the module is not loaded, the library will load it when the hook is attached. 119 | */ 120 | prologue_hook(const std::string& module, const std::string& function, uintptr_t detour_address) 121 | : prologue_hook() 122 | { 123 | m_module = module; 124 | m_function = function; 125 | m_detour_address = detour_address; 126 | } 127 | 128 | /** 129 | * @brief Construct a new hook. 130 | * 131 | * @param module[in] The module which contain the #function. 132 | * @param function[in] The function that will be hooked. 133 | * @param detour_address[in] The address of the callback. 134 | * 135 | * @note If the module is not loaded, the library will load it when the hook is attached. 136 | */ 137 | prologue_hook(const std::string& module, const std::string& function, T detour_address) 138 | : prologue_hook(module, function, reinterpret_cast(detour_address)) 139 | { 140 | } 141 | 142 | prologue_hook(prologue_hook&& rhs) noexcept 143 | : m_target_address(rhs.m_target_address) 144 | , m_detour_address(rhs.m_detour_address) 145 | , m_pattern(std::move(rhs.m_pattern)) 146 | , m_wildcard(rhs.m_wildcard) 147 | , m_offset(rhs.m_offset) 148 | , m_attached(rhs.m_attached) 149 | , m_decoded_length(rhs.m_decoded_length) 150 | , m_block(rhs.m_block) 151 | { 152 | rhs.m_attached = false; 153 | rhs.m_block = nullptr; 154 | } 155 | 156 | ~prologue_hook() 157 | { 158 | if (m_attached) 159 | { 160 | // Hacky fix: Check if the memory is stil valid. Usually it isn't when the program is finished and all global variables are uninitialized. 161 | // Another solution would be to store the global allocator and all hooks in a structure then remove the hooks when the structure's destructor is called, 162 | // but that would require the library to consume more memory. 163 | 164 | MEMORY_BASIC_INFORMATION memory_info = { 0 }; 165 | if (VirtualQuery(m_block, &memory_info, sizeof(memory_info))) 166 | { 167 | if (memory_info.State == MEM_COMMIT) 168 | { 169 | detach(); 170 | } 171 | } 172 | } 173 | } 174 | 175 | prologue_hook& operator=(prologue_hook&& rhs) noexcept 176 | { 177 | m_target_address = rhs.m_target_address; 178 | m_detour_address = rhs.m_detour_address; 179 | m_pattern = std::move(rhs.m_pattern); 180 | m_wildcard = rhs.m_wildcard; 181 | m_offset = rhs.m_offset; 182 | m_attached = rhs.m_attached; 183 | m_decoded_length = rhs.m_decoded_length; 184 | m_block = rhs.m_block; 185 | 186 | rhs.m_attached = false; 187 | rhs.m_block = nullptr; 188 | 189 | return *this; 190 | } 191 | 192 | prologue_hook(prologue_hook&) = delete; 193 | prologue_hook& operator=(const prologue_hook&) = delete; 194 | 195 | /** 196 | * @brief Call the original function. 197 | * 198 | * @return The value returned by the original function. 199 | */ 200 | operator T() const 201 | { 202 | return reinterpret_cast(m_block); 203 | } 204 | 205 | /** 206 | * @brief Enable the hook. 207 | */ 208 | void attach() 209 | { 210 | using namespace renhook::memory; 211 | 212 | if (m_attached) 213 | { 214 | return; 215 | } 216 | 217 | if (!m_module.empty()) 218 | { 219 | auto module_handle = GetModuleHandleA(m_module.c_str()); 220 | if (!module_handle) 221 | { 222 | LoadLibraryA(m_module.c_str()); 223 | 224 | module_handle = GetModuleHandleA(m_module.c_str()); 225 | if (!module_handle) 226 | { 227 | throw renhook::exception("module not found"); 228 | } 229 | } 230 | 231 | m_target_address = reinterpret_cast(GetProcAddress(module_handle, m_function.c_str())); 232 | if (m_target_address == 0) 233 | { 234 | throw renhook::exception("cannot find function in module"); 235 | } 236 | } 237 | 238 | if (m_target_address == 0) 239 | { 240 | if (m_pattern.empty()) 241 | { 242 | throw renhook::exception("cannot attach an empty hook"); 243 | } 244 | 245 | auto addresses = m_pattern.find(m_wildcard); 246 | m_target_address = addresses.at(m_offset); 247 | } 248 | 249 | m_target_address = skip_jumps(m_target_address); 250 | m_detour_address = skip_jumps(m_detour_address); 251 | 252 | renhook::zydis zydis; 253 | auto decoded_info = zydis.decode(m_target_address, executable::get_code_size(), relative_jump_size, m_decoded_length); 254 | 255 | // Find the jump bounds (± 2GB). 256 | auto lower_bound = (std::min)(m_target_address, decoded_info.lowest_relative_address); 257 | auto upper_bound = (std::max)(m_target_address, decoded_info.highest_relative_address); 258 | 259 | constexpr auto two_gb_in_bytes = (std::numeric_limits::max)(); 260 | constexpr auto max_pointer_address = (std::numeric_limits::max)(); 261 | 262 | // Used to prevent upper bound overflow. 263 | constexpr auto max_upper_bound_memory = max_pointer_address - two_gb_in_bytes; 264 | 265 | if (lower_bound > two_gb_in_bytes) 266 | { 267 | lower_bound -= two_gb_in_bytes; 268 | } 269 | 270 | if (upper_bound < max_upper_bound_memory) 271 | { 272 | upper_bound += two_gb_in_bytes; 273 | } 274 | 275 | suspend_threads threads(m_target_address, m_decoded_length); 276 | 277 | extern memory_allocator global_allocator; 278 | m_block = static_cast(global_allocator.alloc(lower_bound, upper_bound)); 279 | 280 | // Write the bytes to our memory. 281 | virtual_protect block_protection(m_block, memory_allocator::block_size, protection::read | protection::write | protection::execute); 282 | hook_writer block_writer(m_block); 283 | 284 | block_writer.copy_from(m_target_address, m_decoded_length); 285 | 286 | #ifdef _WIN64 287 | block_writer.write_indirect_jump(m_target_address + m_decoded_length); 288 | #else 289 | block_writer.write_relative_jump(m_target_address + m_decoded_length); 290 | #endif 291 | 292 | #ifdef _WIN64 293 | // On x86-64 place an indirect jump to the detour. It might be far away. 294 | block_writer.write_indirect_jump(m_detour_address); 295 | #endif 296 | 297 | relocate_instructions(decoded_info.instructions, &block_writer); 298 | 299 | // Write the jump in the original function. 300 | virtual_protect func_protection(m_target_address, m_decoded_length, protection::read | protection::write | protection::execute); 301 | hook_writer func_writer(m_target_address); 302 | 303 | #ifdef _WIN64 304 | // On x86-64 jump to our memory then jump to the detour. 305 | func_writer.write_relative_jump(reinterpret_cast(m_block) + m_decoded_length + indirect_jump_size); 306 | #else 307 | // On x86 jump directly to the detour. 308 | func_writer.write_relative_jump(m_detour_address); 309 | #endif 310 | 311 | func_writer.write_nops(m_decoded_length - relative_jump_size); 312 | 313 | flush_cache(); 314 | m_attached = true; 315 | } 316 | 317 | /** 318 | * @brief Disable the hook. 319 | */ 320 | void detach() 321 | { 322 | using namespace renhook::memory; 323 | 324 | if (!m_attached) 325 | { 326 | return; 327 | } 328 | 329 | suspend_threads threads(m_target_address, m_decoded_length); 330 | virtual_protect func_protection(m_target_address, m_decoded_length, protection::read | protection::write | protection::execute); 331 | 332 | hook_writer func_writer(m_target_address); 333 | func_writer.copy_from(m_block, m_decoded_length); 334 | 335 | renhook::zydis zydis; 336 | auto decoded_info = zydis.decode(reinterpret_cast(m_block), executable::get_code_size(), m_decoded_length, m_decoded_length); 337 | 338 | relocate_instructions(decoded_info.instructions, nullptr); 339 | 340 | extern memory_allocator global_allocator; 341 | global_allocator.free(m_block); 342 | 343 | m_block = nullptr; 344 | 345 | flush_cache(); 346 | m_attached = false; 347 | } 348 | 349 | /** 350 | * @brief Get the attach status. 351 | * 352 | * @return True if the hook is attached, false otherwise. 353 | */ 354 | bool is_attached() const 355 | { 356 | return m_attached; 357 | } 358 | 359 | /** 360 | * @brief Get the block address. 361 | * 362 | * @return The block address. 363 | */ 364 | uint8_t* get_block_address() const 365 | { 366 | return m_block; 367 | } 368 | 369 | private: 370 | 371 | /** 372 | * @brief Check if the first instruction is a jump, if it is follow it until the real address of the function is found. 373 | * 374 | * @param address[in] The address to check. 375 | * 376 | * @return The real function's address. 377 | */ 378 | uintptr_t skip_jumps(uintptr_t address) const 379 | { 380 | if (address == 0) 381 | { 382 | return 0; 383 | } 384 | 385 | constexpr auto min_decode_length = 2; 386 | constexpr auto max_decode_length = 32; 387 | 388 | size_t decoded_length; 389 | 390 | renhook::zydis zydis; 391 | auto decoded_info = zydis.decode(address, max_decode_length, min_decode_length, decoded_length); 392 | if (decoded_info.instructions.size() == 0) 393 | { 394 | return address; 395 | } 396 | 397 | // JCC and friends not supported. 398 | auto& instr = decoded_info.instructions[0]; 399 | if (instr.mnemonic == ZYDIS_MNEMONIC_JMP) 400 | { 401 | // Instructions using RIP relative addresses. 402 | if ((instr.attributes & ZYDIS_ATTRIB_HAS_MODRM) && instr.raw.modrm.mod == 0 && instr.raw.modrm.rm == 5) 403 | { 404 | #ifdef _WIN64 405 | auto absAddr = reinterpret_cast(instr.disp.absolute_address); 406 | return skip_jumps(*absAddr); 407 | #else 408 | return skip_jumps(static_cast(instr.raw.disp.value)); 409 | #endif 410 | } 411 | 412 | if (instr.is_relative) 413 | { 414 | return skip_jumps(instr.disp.absolute_address); 415 | } 416 | else 417 | { 418 | throw renhook::exception("jump instruction not handled"); 419 | } 420 | } 421 | 422 | return address; 423 | } 424 | 425 | /** 426 | * @brief Relocate all EIP / RIP instructions. 427 | * 428 | * @param instructions[in] An array of decoded instructions. 429 | * @param block_writer[in] The writer of the block (only necessary if the hook is attached). 430 | */ 431 | void relocate_instructions(std::vector& instructions, hook_writer* block_writer) 432 | { 433 | auto instr_address = m_attached ? m_target_address : reinterpret_cast(m_block); 434 | size_t index = 0; 435 | 436 | for (auto& instr : instructions) 437 | { 438 | if (instr.is_relative) 439 | { 440 | // Calculate where the displacement is in instruction. 441 | auto disp_address = instr_address + instr.disp.offset; 442 | 443 | if (instr.add_to_jump_table && instr.disp.size < sizeof(int32_t) * 8) 444 | { 445 | #ifdef _WIN64 446 | constexpr size_t jmp_size = indirect_jump_size; 447 | constexpr size_t jmp_instr_size = 6; 448 | #else 449 | constexpr size_t jmp_size = relative_jump_size; 450 | constexpr size_t jmp_instr_size = 1; 451 | #endif 452 | 453 | auto table_address = m_block + m_decoded_length + jmp_size; 454 | 455 | #ifdef _WIN64 456 | // On x86-64 we have another jump that redirect the target to detour. 457 | table_address += indirect_jump_size; 458 | #endif 459 | 460 | // The address of the jump instruction in jump table for the current instruction. 461 | auto jmp_instr_address = table_address + (jmp_size * index); 462 | 463 | // Create a jump table if it is not attached, else get the real address from jump table. 464 | if (!m_attached) 465 | { 466 | #ifdef _WIN64 467 | block_writer->write_indirect_jump(instr.disp.absolute_address); 468 | #else 469 | block_writer->write_relative_jump(instr.disp.absolute_address); 470 | #endif 471 | 472 | instr.disp.absolute_address = reinterpret_cast(jmp_instr_address); 473 | } 474 | else 475 | { 476 | instr.disp.absolute_address = *reinterpret_cast(jmp_instr_address + jmp_instr_size); 477 | 478 | #ifndef _WIN64 479 | // On x86 we have a displacement instead of absolute address. 480 | instr.disp.absolute_address += reinterpret_cast(jmp_instr_address) + jmp_size; 481 | #endif 482 | } 483 | } 484 | 485 | switch (instr.disp.size) 486 | { 487 | case 8: 488 | { 489 | *reinterpret_cast(disp_address) = static_cast(utils::calculate_displacement(instr_address, instr.disp.absolute_address, instr.length)); 490 | break; 491 | } 492 | case 16: 493 | { 494 | *reinterpret_cast(disp_address) = static_cast(utils::calculate_displacement(instr_address, instr.disp.absolute_address, instr.length)); 495 | break; 496 | } 497 | case 32: 498 | { 499 | *reinterpret_cast(disp_address) = static_cast(utils::calculate_displacement(instr_address, instr.disp.absolute_address, instr.length)); 500 | break; 501 | } 502 | case 64: 503 | { 504 | *reinterpret_cast(disp_address) = static_cast(utils::calculate_displacement(instr_address, instr.disp.absolute_address, instr.length)); 505 | break; 506 | } 507 | } 508 | 509 | index++; 510 | } 511 | 512 | instr_address += instr.length; 513 | } 514 | } 515 | 516 | /** 517 | * @brief Flush instruction cache for the original function and for the codecave. 518 | */ 519 | void flush_cache() const 520 | { 521 | auto current_process = GetCurrentProcess(); 522 | 523 | FlushInstructionCache(current_process, m_block, memory::memory_allocator::block_size); 524 | FlushInstructionCache(current_process, reinterpret_cast(m_target_address), m_decoded_length); 525 | } 526 | 527 | uintptr_t m_target_address; 528 | uintptr_t m_detour_address; 529 | 530 | pattern m_pattern; 531 | uint8_t m_wildcard; 532 | size_t m_offset; 533 | 534 | std::string m_module; 535 | std::string m_function; 536 | 537 | bool m_attached; 538 | 539 | size_t m_decoded_length; 540 | uint8_t* m_block; 541 | }; 542 | 543 | template 544 | using 545 | #if (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) 546 | [[deprecated("Use 'prologue_hook' instead.")]] 547 | #endif 548 | inline_hook = prologue_hook; 549 | } 550 | #endif 551 | -------------------------------------------------------------------------------- /src/renhook/memory/memory_allocator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace renhook 10 | { 11 | namespace memory 12 | { 13 | renhook::memory::memory_allocator global_allocator; 14 | } 15 | } 16 | 17 | renhook::memory::memory_allocator::memory_allocator() noexcept 18 | : m_regions(nullptr) 19 | { 20 | SYSTEM_INFO system_info = { 0 }; 21 | GetSystemInfo(&system_info); 22 | 23 | m_region_size = system_info.dwAllocationGranularity; 24 | m_minimum_address = reinterpret_cast(system_info.lpMinimumApplicationAddress); 25 | m_maximum_address = reinterpret_cast(system_info.lpMaximumApplicationAddress); 26 | } 27 | 28 | renhook::memory::memory_allocator::~memory_allocator() noexcept 29 | { 30 | while (m_regions) 31 | { 32 | auto current_region = m_regions; 33 | m_regions = current_region->next; 34 | 35 | VirtualFree(current_region, 0, MEM_RELEASE); 36 | } 37 | } 38 | 39 | void* renhook::memory::memory_allocator::alloc(uintptr_t lower_bound, uintptr_t upper_bound) 40 | { 41 | if (lower_bound < m_minimum_address) 42 | { 43 | lower_bound = m_minimum_address; 44 | } 45 | 46 | if (upper_bound > m_maximum_address) 47 | { 48 | upper_bound = m_maximum_address; 49 | } 50 | 51 | std::lock_guard _(m_mutex); 52 | 53 | auto region = m_regions; 54 | if (!region) 55 | { 56 | region = alloc_region(lower_bound, upper_bound); 57 | } 58 | 59 | auto region_address = reinterpret_cast(region); 60 | 61 | // If the region doesn't have a free block, check all regions. 62 | if (region->free_blocks == 0 || !is_region_in_range(region_address, lower_bound, upper_bound)) 63 | { 64 | region = region->next; 65 | while (region) 66 | { 67 | // Stop if we find a region with a free block. 68 | if (region->free_blocks > 0) 69 | { 70 | region_address = reinterpret_cast(region); 71 | if (is_region_in_range(region_address, lower_bound, upper_bound)) 72 | { 73 | break; 74 | } 75 | } 76 | 77 | region = region->next; 78 | } 79 | 80 | // No region found? Then allocate a new one. 81 | if (!region) 82 | { 83 | region = alloc_region(lower_bound, upper_bound); 84 | } 85 | } 86 | 87 | virtual_protect protection(region, m_region_size, protection::read | protection::write); 88 | 89 | auto block = region->next_block; 90 | region->next_block = block->next; 91 | 92 | region->free_blocks--; 93 | return block; 94 | } 95 | 96 | void renhook::memory::memory_allocator::free(void* address) 97 | { 98 | std::lock_guard _(m_mutex); 99 | 100 | auto region = reinterpret_cast(utils::align_down(reinterpret_cast(address), m_region_size)); 101 | auto block = static_cast(address); 102 | 103 | virtual_protect protection(region, m_region_size, protection::read | protection::write); 104 | 105 | block->next = region->next_block; 106 | region->next_block = block; 107 | 108 | region->free_blocks++; 109 | 110 | // Free the region if we reached the maximum number of blocks. 111 | auto maximum_blocks = m_region_size / block_size - 1; 112 | if (region->free_blocks == maximum_blocks) 113 | { 114 | auto prev = region->prev; 115 | auto next = region->next; 116 | 117 | if (prev) 118 | { 119 | virtual_protect protection(prev, m_region_size, protection::write); 120 | prev->next = next; 121 | } 122 | 123 | if (next) 124 | { 125 | virtual_protect protection(next, m_region_size, protection::write); 126 | next->prev = prev; 127 | } 128 | 129 | if (m_regions == region) 130 | { 131 | m_regions = nullptr; 132 | } 133 | 134 | VirtualFree(region, 0, MEM_RELEASE); 135 | } 136 | } 137 | 138 | renhook::memory::memory_allocator::regionptr_t renhook::memory::memory_allocator::alloc_region(uintptr_t lower_bound, uintptr_t upper_bound) 139 | { 140 | auto middle = (lower_bound + upper_bound) / 2; 141 | auto alloc_address_lower = utils::align_down(middle, m_region_size); 142 | auto alloc_address_higher = utils::align_up(middle, m_region_size); 143 | 144 | regionptr_t region = nullptr; 145 | 146 | bool is_lower_in_range = false; 147 | bool is_upper_in_range = false; 148 | 149 | do 150 | { 151 | is_lower_in_range = is_region_in_range(alloc_address_lower, lower_bound, upper_bound); 152 | if (is_lower_in_range) 153 | { 154 | region = try_alloc_region_at_address(alloc_address_lower); 155 | if (region) 156 | { 157 | break; 158 | } 159 | 160 | alloc_address_lower -= m_region_size; 161 | } 162 | 163 | is_upper_in_range = is_region_in_range(alloc_address_higher, lower_bound, upper_bound); 164 | if (is_upper_in_range) 165 | { 166 | region = try_alloc_region_at_address(alloc_address_higher); 167 | if (region) 168 | { 169 | break; 170 | } 171 | 172 | alloc_address_higher += m_region_size; 173 | } 174 | } while (is_lower_in_range || is_upper_in_range); 175 | 176 | if (!region) 177 | { 178 | throw renhook::exception("no free region found", GetLastError()); 179 | } 180 | 181 | #ifdef _DEBUG 182 | // In debug fill it with 0xCC, it makes it easier to debug. 183 | std::memset(region, 0xCC, m_region_size); 184 | #endif 185 | 186 | region->next_block = nullptr; 187 | region->free_blocks = m_region_size / block_size - 1; 188 | 189 | for (size_t i = region->free_blocks; i > 0; i--) 190 | { 191 | auto block_address = reinterpret_cast(region) + block_size * i; 192 | auto block = reinterpret_cast(block_address); 193 | 194 | block->next = region->next_block; 195 | region->next_block = block; 196 | } 197 | 198 | if (m_regions) 199 | { 200 | virtual_protect protection(m_regions, m_region_size, protection::write); 201 | m_regions->prev = region; 202 | } 203 | 204 | region->prev = nullptr; 205 | region->next = m_regions; 206 | m_regions = region; 207 | 208 | virtual_protect protection(region, m_region_size, protection::read | protection::execute, true); 209 | return region; 210 | } 211 | 212 | renhook::memory::memory_allocator::regionptr_t renhook::memory::memory_allocator::try_alloc_region_at_address(uintptr_t address) 213 | { 214 | MEMORY_BASIC_INFORMATION memory_info = { 0 }; 215 | if (!VirtualQuery(reinterpret_cast(address), &memory_info, sizeof(memory_info))) 216 | { 217 | throw renhook::exception("cannot retrieves region information", GetLastError()); 218 | } 219 | 220 | if (memory_info.State == MEM_FREE && memory_info.RegionSize >= m_region_size) 221 | { 222 | auto result = static_cast(VirtualAlloc(memory_info.BaseAddress, m_region_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); 223 | if (result) 224 | { 225 | return result; 226 | } 227 | } 228 | 229 | return nullptr; 230 | } 231 | 232 | bool renhook::memory::memory_allocator::is_region_in_range(uintptr_t address, uintptr_t lower_bound, uintptr_t upper_bound) 233 | { 234 | return address >= lower_bound && address < upper_bound; 235 | } 236 | -------------------------------------------------------------------------------- /src/renhook/memory/memory_allocator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_MEMORY_ALLOCATOR_H 2 | #define RENHOOK_MEMORY_ALLOCATOR_H 3 | 4 | #include 5 | #include 6 | 7 | namespace renhook 8 | { 9 | namespace memory 10 | { 11 | /** 12 | * @brief A memory allocator class. 13 | */ 14 | class memory_allocator 15 | { 16 | public: 17 | 18 | /** 19 | * @brief The size of a block. 20 | */ 21 | static constexpr size_t block_size = 256; 22 | 23 | memory_allocator() noexcept; 24 | ~memory_allocator() noexcept; 25 | 26 | memory_allocator(memory_allocator&) = delete; 27 | memory_allocator(memory_allocator&&) = delete; 28 | 29 | memory_allocator& operator=(const memory_allocator&) = delete; 30 | memory_allocator& operator=(memory_allocator&&) = delete; 31 | 32 | /** 33 | * @brief Return a memory block between #lower_bound and #upper_bound. 34 | * 35 | * @param lower_bound[in] The lower bound of the memory. 36 | * @param upper_bound[in] The lower bound of the memory. 37 | * 38 | * @return The memory block of \ref block_size bytes. 39 | */ 40 | void* alloc(uintptr_t lower_bound, uintptr_t upper_bound); 41 | 42 | /** 43 | * @brief Free the allocated memory block. 44 | * 45 | * @param[in] address The block's address. 46 | * 47 | * @note This function does not check if the address was allocated by this class. 48 | */ 49 | void free(void* address); 50 | 51 | private: 52 | 53 | struct block 54 | { 55 | block* next; 56 | }; 57 | using blockptr_t = block*; 58 | 59 | struct region 60 | { 61 | blockptr_t next_block; 62 | size_t free_blocks; 63 | 64 | region* prev; 65 | region* next; 66 | }; 67 | using regionptr_t = region*; 68 | 69 | /** 70 | * @brief Allocate a region between #lower_bound and #upper_bound. 71 | * 72 | * @param lower_bound[in] The lower bound of the memory. 73 | * @param upper_bound[in] The lower bound of the memory. 74 | * 75 | * @return The allocated region. 76 | */ 77 | regionptr_t alloc_region(uintptr_t lower_bound, uintptr_t upper_bound); 78 | 79 | /** 80 | * @brief Try to allocate a region at a specific address. 81 | * 82 | * @param address[in] The address where to allocation. 83 | 84 | * @return The allocated region. 85 | * 86 | * @note A region is allocated by calling VirtualAlloc. 87 | */ 88 | regionptr_t try_alloc_region_at_address(uintptr_t address); 89 | 90 | /** 91 | * @brief Check if a region is in rage of #lower_bound and #upper_bound. 92 | * 93 | * @param address The region address. 94 | * @param lower_bound[in] The lower bound of the memory. 95 | * @param upper_bound[in] The lower bound of the memory. 96 | * 97 | * @return true if it is. 98 | * @return false if it is not. 99 | */ 100 | bool is_region_in_range(uintptr_t address, uintptr_t lower_bound, uintptr_t upper_bound); 101 | 102 | /** 103 | * @brief The size of a memory. 104 | * 105 | */ 106 | size_t m_region_size; 107 | 108 | /** 109 | * @brief The lowest address accessible to applications. 110 | * 111 | */ 112 | uintptr_t m_minimum_address; 113 | 114 | /** 115 | * @brief The highest address accessible to applications. 116 | * 117 | */ 118 | uintptr_t m_maximum_address; 119 | 120 | regionptr_t m_regions; 121 | std::mutex m_mutex; 122 | }; 123 | 124 | extern memory_allocator global_allocator; 125 | } 126 | } 127 | #endif 128 | -------------------------------------------------------------------------------- /src/renhook/memory/protection_enum.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_PROTECTION_ENUM_H 2 | #define RENHOOK_PROTECTION_ENUM_H 3 | 4 | #include 5 | #include 6 | 7 | namespace renhook 8 | { 9 | namespace memory 10 | { 11 | enum class protection : uint8_t 12 | { 13 | read = 1 << 0, 14 | write = 1 << 1, 15 | execute = 1 << 2 16 | }; 17 | 18 | inline std::underlying_type::type operator&(protection lhs, protection rhs) 19 | { 20 | using underlying_type = std::underlying_type::type; 21 | return static_cast(lhs) & static_cast(rhs); 22 | } 23 | 24 | inline protection operator|(protection lhs, protection rhs) 25 | { 26 | using underlying_type = std::underlying_type::type; 27 | return static_cast(static_cast(lhs) | static_cast(rhs)); 28 | } 29 | } 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /src/renhook/memory/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_MEMORY_UTILS_H 2 | #define RENHOOK_MEMORY_UTILS_H 3 | 4 | #include 5 | 6 | namespace renhook 7 | { 8 | namespace memory 9 | { 10 | namespace utils 11 | { 12 | /** 13 | * @brief Return the aligned down number by the specified #alignment. 14 | * 15 | * @tparam T The type of the numbers. 16 | * @param[in] number The number to be aligned. 17 | * @param[in] alignment The alignment. 18 | * 19 | * @return The number aligned by the specified #alignment. 20 | */ 21 | template::value, T>::type> 22 | inline T align_down(T number, T alignment) 23 | { 24 | return number & ~(alignment - 1); 25 | } 26 | 27 | /** 28 | * @brief Return the aligned up number by the specified #alignment. 29 | * 30 | * @tparam T The type of the numbers. 31 | * @param[in] number The number to be aligned. 32 | * @param[in] alignment The alignment. 33 | * 34 | * @return The number aligned by the specified #alignment. 35 | */ 36 | template::value, T>::type> 37 | inline T align_up(T number, T alignment) 38 | { 39 | return align_down(number + (alignment - 1), alignment); 40 | } 41 | } 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /src/renhook/memory/virtual_protect.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | renhook::memory::virtual_protect::virtual_protect(void* address, size_t size, protection new_protection, bool permanent) 7 | : m_address(address) 8 | , m_size(size) 9 | , m_is_permanent(permanent) 10 | { 11 | uint32_t protection_option = 0; 12 | 13 | if ((new_protection & protection::read) && (new_protection & protection::write) && (new_protection & protection::execute)) 14 | { 15 | protection_option = PAGE_EXECUTE_READWRITE; 16 | } 17 | else if ((new_protection & protection::read) && (new_protection & protection::write) || new_protection == protection::write) 18 | { 19 | protection_option = PAGE_READWRITE; 20 | } 21 | else if ((new_protection & protection::read) && (new_protection & protection::execute)) 22 | { 23 | protection_option = PAGE_EXECUTE_READ; 24 | } 25 | else if (new_protection == protection::read) 26 | { 27 | protection_option = PAGE_READONLY; 28 | } 29 | else if (new_protection == protection::execute) 30 | { 31 | protection_option = PAGE_EXECUTE; 32 | } 33 | 34 | if (!VirtualProtect(address, size, protection_option, reinterpret_cast(&m_old_protection))) 35 | { 36 | throw renhook::exception("couldn't change protection", GetLastError()); 37 | } 38 | } 39 | 40 | renhook::memory::virtual_protect::virtual_protect(uintptr_t address, size_t size, protection new_protection, bool permanent) 41 | : virtual_protect(reinterpret_cast(address), size, new_protection, permanent) 42 | { 43 | } 44 | 45 | renhook::memory::virtual_protect::~virtual_protect() noexcept 46 | { 47 | if (!m_is_permanent) 48 | { 49 | decltype(m_old_protection) old_protection; 50 | VirtualProtect(m_address, m_size, m_old_protection, reinterpret_cast(&old_protection)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/renhook/memory/virtual_protect.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_MEMORY_VIRTUAL_PROTECT_H 2 | #define RENHOOK_MEMORY_VIRTUAL_PROTECT_H 3 | 4 | #include 5 | #include 6 | 7 | namespace renhook 8 | { 9 | namespace memory 10 | { 11 | /** 12 | * @brief Change protection of the address in a RAII fashion. 13 | */ 14 | class virtual_protect 15 | { 16 | public: 17 | 18 | /** 19 | * @brief Change protection of the address. 20 | * 21 | * @param address[in] A pointer to the start of the region. 22 | * @param size[in] The size of the region. 23 | * @param new_protection[in] The new protection. 24 | * @param permanent[in] If true the protection is applied permanently. 25 | * If false the protection will be reverted to the original one when object is destroyed. 26 | */ 27 | virtual_protect(void* address, size_t size, protection new_protection, bool permanent = false); 28 | virtual_protect(uintptr_t address, size_t size, protection new_protection, bool permanent = false); 29 | 30 | ~virtual_protect() noexcept; 31 | 32 | virtual_protect(virtual_protect&) = delete; 33 | virtual_protect(virtual_protect&&) = delete; 34 | 35 | virtual_protect& operator=(const virtual_protect&) = delete; 36 | virtual_protect& operator=(virtual_protect&&) = delete; 37 | 38 | private: 39 | 40 | void* m_address; 41 | size_t m_size; 42 | uint32_t m_old_protection; 43 | 44 | bool m_is_permanent; 45 | }; 46 | } 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /src/renhook/pattern.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | renhook::pattern::pattern(std::vector pattern) 8 | : m_pattern(std::move(pattern)) 9 | { 10 | } 11 | 12 | renhook::pattern::pattern(std::initializer_list pattern) 13 | : m_pattern(pattern) 14 | { 15 | } 16 | 17 | bool renhook::pattern::empty() const 18 | { 19 | return m_pattern.empty(); 20 | } 21 | 22 | size_t renhook::pattern::size() const 23 | { 24 | return m_pattern.size(); 25 | } 26 | 27 | std::vector renhook::pattern::find(uint8_t wildcard, uint8_t* start, uint8_t* end) const 28 | { 29 | if (empty()) 30 | { 31 | throw renhook::exception("pattern is empty"); 32 | } 33 | 34 | if (start == 0) 35 | { 36 | start = reinterpret_cast(executable::get_code_base_address()); 37 | } 38 | 39 | if (end == 0) 40 | { 41 | end = reinterpret_cast(executable::get_code_end_address()); 42 | } 43 | 44 | std::vector offsets; 45 | 46 | while (true) 47 | { 48 | auto offset = std::search(start, end, m_pattern.begin(), m_pattern.end(), [&wildcard](uint8_t memory_value, uint8_t pattern_value) 49 | { 50 | return memory_value == pattern_value || pattern_value == wildcard; 51 | }); 52 | 53 | // Did we found something? 54 | if (offset >= end) 55 | { 56 | break; 57 | } 58 | 59 | offsets.emplace_back(reinterpret_cast(offset)); 60 | start = offset + m_pattern.size(); 61 | } 62 | 63 | return offsets; 64 | } 65 | -------------------------------------------------------------------------------- /src/renhook/pattern.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_PATTERN_H 2 | #define RENHOOK_PATTERN_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace renhook 9 | { 10 | /** 11 | * @brief The pattern class. 12 | */ 13 | class pattern 14 | { 15 | public: 16 | 17 | /** 18 | * @brief Construct a new empty pattern. 19 | */ 20 | pattern() = default; 21 | 22 | /** 23 | * @brief Construct a new pattern. 24 | * 25 | * @param[in] pattern The pattern. 26 | */ 27 | pattern(std::vector pattern); 28 | 29 | /** 30 | * @brief Construct a new pattern. 31 | * 32 | * @param[in] pattern The pattern. 33 | */ 34 | pattern(std::initializer_list pattern); 35 | 36 | ~pattern() = default; 37 | 38 | /** 39 | * @brief Checks if the pattern is empty. 40 | * 41 | * @return true if the pattern is empty. 42 | * @return false otherwise. 43 | */ 44 | bool empty() const; 45 | 46 | /** 47 | * @brief Return the size of the pattern. 48 | * 49 | * @return The size of the patter. 50 | */ 51 | size_t size() const; 52 | 53 | /** 54 | * @brief Return an array of addresses where the pattern was found. 55 | * 56 | * @param[in] wildcard The wildcard. 57 | * @param[in] start The start address. If value is \b 0 then \ref renhook::executable::get_code_base_address() is used. 58 | * @param[in] end The end address. If value is \b 0 then \ref renhook::executable::get_code_end_address() is used. 59 | * 60 | * @return An array of addresses where the pattern was found. 61 | * 62 | * @par Examples 63 | * 64 | * The following 65 | * 66 | * @code{.cpp} 67 | * renhook::pattern pattern({ 0x48, 0x89, 0x5C, 0x24, 0x08 }); 68 | * auto addresses = pattern.find(0xCC); 69 | * 70 | * std::cout << addresses.size() << " matches found\n"; 71 | * 72 | * for (auto& address : addresses) 73 | * { 74 | * std::cout << "Pattern found at " << std::hex << address << '\n'; 75 | * } 76 | * @endcode 77 | * 78 | * is equivalent with 79 | * 80 | * @code{.cpp} 81 | * renhook::pattern pattern({ 0x48, 0x89, 0x5C, 0x24, 0x08 }); 82 | * auto addresses = pattern.find(0xCC, renhook::executable::get_code_base_address(), renhook::executable::get_code_end_address()); 83 | * 84 | * std::cout << addresses.size() << " matches found\n"; 85 | * 86 | * for (auto& address : addresses) 87 | * { 88 | * std::cout << "Pattern found at " << std::hex << address << '\n'; 89 | * } 90 | * @endcode 91 | */ 92 | std::vector find(uint8_t wildcard, uint8_t* start = 0, uint8_t* end = 0) const; 93 | 94 | private: 95 | 96 | /** 97 | * @brief Pattern to search. 98 | */ 99 | std::vector m_pattern; 100 | }; 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /src/renhook/renhook.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /src/renhook/suspend_threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | renhook::suspend_threads::suspend_threads(uintptr_t ip_address, size_t ip_length) 12 | { 13 | suspend(ip_address, ip_length); 14 | } 15 | 16 | renhook::suspend_threads::~suspend_threads() 17 | { 18 | resume(); 19 | } 20 | 21 | void renhook::suspend_threads::suspend(uintptr_t ip_address, size_t ip_length) 22 | { 23 | auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); 24 | if (snapshot == INVALID_HANDLE_VALUE) 25 | { 26 | throw renhook::exception("cannot take process snapshot", GetLastError()); 27 | } 28 | 29 | THREADENTRY32 entry; 30 | entry.dwSize = sizeof(THREADENTRY32); 31 | 32 | if (!Thread32First(snapshot, &entry)) 33 | { 34 | throw renhook::exception("cannot retrieves information about the first thread", GetLastError()); 35 | } 36 | 37 | auto process_id = GetCurrentProcessId(); 38 | auto thread_id = GetCurrentThreadId(); 39 | 40 | do 41 | { 42 | if (entry.th32OwnerProcessID == process_id && entry.th32ThreadID != thread_id) 43 | { 44 | auto handle = suspend_thread(entry.th32ThreadID, ip_address, ip_length); 45 | m_handles.emplace_back(handle); 46 | } 47 | } while (Thread32Next(snapshot, &entry)); 48 | } 49 | 50 | void renhook::suspend_threads::resume() 51 | { 52 | for (auto handle : m_handles) 53 | { 54 | resume_thread(handle); 55 | } 56 | 57 | m_handles.clear(); 58 | } 59 | 60 | void* renhook::suspend_threads::suspend_thread(uint32_t id, uintptr_t ip_address, size_t ip_length) 61 | { 62 | auto handle = OpenThread(THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME, false, id); 63 | if (!handle) 64 | { 65 | throw renhook::exception("cannot open thread object", GetLastError()); 66 | } 67 | 68 | auto count = SuspendThread(handle); 69 | if (count == -1) 70 | { 71 | CloseHandle(handle); 72 | throw renhook::exception("cannot suspend thread", GetLastError()); 73 | } 74 | 75 | size_t tries = 1; 76 | do 77 | { 78 | CONTEXT context = { 0 }; 79 | context.ContextFlags = CONTEXT_CONTROL; 80 | 81 | if (!GetThreadContext(handle, &context)) 82 | { 83 | auto a = GetLastError(); 84 | throw renhook::exception("cannot get thread context", GetLastError()); 85 | } 86 | 87 | #ifdef _WIN64 88 | auto ip = context.Rip; 89 | #else 90 | auto ip = context.Eip; 91 | #endif 92 | 93 | if (ip_address < ip || ip_address > (ip + ip_length)) 94 | { 95 | break; 96 | } 97 | 98 | if (tries <= 3) 99 | { 100 | ResumeThread(handle); 101 | 102 | using namespace std::chrono_literals; 103 | std::this_thread::sleep_for(tries * 50ms); 104 | 105 | SuspendThread(handle); 106 | } 107 | 108 | tries++; 109 | } while (tries <= 3); 110 | 111 | return handle; 112 | } 113 | 114 | void renhook::suspend_threads::resume_thread(void* handle) 115 | { 116 | ResumeThread(handle); 117 | CloseHandle(handle); 118 | } 119 | -------------------------------------------------------------------------------- /src/renhook/suspend_threads.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_SUSPEND_THREADS_H 2 | #define RENHOOK_SUSPEND_THREADS_H 3 | 4 | #include 5 | #include 6 | 7 | namespace renhook 8 | { 9 | /** 10 | * @brief Temporary suspend all threads, except the running one, in a RAII fashion. 11 | */ 12 | class suspend_threads 13 | { 14 | public: 15 | 16 | /** 17 | * @brief Suspends all threads and make sure their instruction pointer is not in range. 18 | * 19 | * @param ip_address[in] The address of the instruction pointer. 20 | * @param ip_length[in] The length of the instruction pointer. 21 | */ 22 | suspend_threads(uintptr_t ip_address, size_t ip_length); 23 | 24 | suspend_threads(suspend_threads&) = delete; 25 | suspend_threads(suspend_threads&&) = delete; 26 | 27 | ~suspend_threads(); 28 | 29 | suspend_threads& operator=(const suspend_threads&) = delete; 30 | suspend_threads& operator=(suspend_threads&&) = delete; 31 | 32 | private: 33 | 34 | void suspend(uintptr_t ip_address, size_t ip_length); 35 | void resume(); 36 | 37 | void* suspend_thread(uint32_t id, uintptr_t ip_address, size_t ip_length); 38 | void resume_thread(void* handle); 39 | 40 | std::vector m_handles; 41 | }; 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /src/renhook/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | intptr_t renhook::utils::calculate_displacement(uintptr_t from, uintptr_t to, size_t instruction_size) 4 | { 5 | if (to < from) 6 | { 7 | return 0 - (from - to) - instruction_size; 8 | } 9 | 10 | return to - (from + instruction_size); 11 | } 12 | -------------------------------------------------------------------------------- /src/renhook/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_UTILS_H 2 | #define RENHOOK_UTILS_H 3 | 4 | #include 5 | 6 | namespace renhook 7 | { 8 | namespace utils 9 | { 10 | /** 11 | * @brief Calculate the displacement between two addresses. 12 | * 13 | * @param from[in] The source. 14 | * @param to[in] The destination. 15 | * @param instruction_size[in] The size of the instruction at the source (#from). 16 | * 17 | * @return The displacement. 18 | */ 19 | intptr_t calculate_displacement(uintptr_t from, uintptr_t to, size_t instruction_size); 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /src/renhook/version.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_VERSION_H 2 | #define RENHOOK_VERSION_H 3 | 4 | #include 5 | 6 | namespace renhook 7 | { 8 | /** 9 | * @brief The namespace containing library version helpers. 10 | */ 11 | namespace version 12 | { 13 | /** 14 | * @brief Return the version as string. 15 | * 16 | * @return The version as string. 17 | */ 18 | constexpr const char* as_string() 19 | { 20 | return "1.0.0"; 21 | } 22 | 23 | /** 24 | * @brief Return the version as integer (uint32_t). 25 | * 26 | * @return The version as integer (uint32_t). 27 | */ 28 | constexpr uint32_t as_int() 29 | { 30 | return 10000; 31 | } 32 | 33 | /** 34 | * @brief Return the major version of the library. 35 | * 36 | * @return The major version of the library. 37 | */ 38 | constexpr uint8_t get_major() 39 | { 40 | return 1; 41 | } 42 | 43 | /** 44 | * @brief Return the minor version of the library. 45 | * 46 | * @return The minor version of the library. 47 | */ 48 | constexpr uint8_t get_minor() 49 | { 50 | return 0; 51 | } 52 | 53 | /** 54 | * @brief Return the patch version of the library. 55 | * 56 | * @return The patch version of the library. 57 | */ 58 | constexpr uint8_t get_patch() 59 | { 60 | return 0; 61 | } 62 | } 63 | } 64 | #endif 65 | -------------------------------------------------------------------------------- /src/renhook/version.hpp.in: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_VERSION_H 2 | #define RENHOOK_VERSION_H 3 | 4 | #include 5 | 6 | namespace renhook 7 | { 8 | /** 9 | * @brief The namespace containing library version helpers. 10 | */ 11 | namespace version 12 | { 13 | /** 14 | * @brief Return the version as string. 15 | * 16 | * @return The version as string. 17 | */ 18 | constexpr const char* as_string() 19 | { 20 | return "@PROJECT_VERSION@"; 21 | } 22 | 23 | /** 24 | * @brief Return the version as integer (uint32_t). 25 | * 26 | * @return The version as integer (uint32_t). 27 | */ 28 | constexpr uint32_t as_int() 29 | { 30 | return @PROJECT_VERSION_INT@; 31 | } 32 | 33 | /** 34 | * @brief Return the major version of the library. 35 | * 36 | * @return The major version of the library. 37 | */ 38 | constexpr uint8_t get_major() 39 | { 40 | return @PROJECT_VERSION_MAJOR@; 41 | } 42 | 43 | /** 44 | * @brief Return the minor version of the library. 45 | * 46 | * @return The minor version of the library. 47 | */ 48 | constexpr uint8_t get_minor() 49 | { 50 | return @PROJECT_VERSION_MINOR@; 51 | } 52 | 53 | /** 54 | * @brief Return the patch version of the library. 55 | * 56 | * @return The patch version of the library. 57 | */ 58 | constexpr uint8_t get_patch() 59 | { 60 | return @PROJECT_VERSION_PATCH@; 61 | } 62 | } 63 | } 64 | #endif 65 | -------------------------------------------------------------------------------- /src/renhook/zydis.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | ZydisDecoder renhook::zydis::m_decoder; 5 | bool renhook::zydis::m_initialized = false; 6 | 7 | renhook::zydis::zydis() 8 | { 9 | if (!m_initialized) 10 | { 11 | m_initialized = true; 12 | 13 | #ifdef _WIN64 14 | auto status = ZydisDecoderInit(&m_decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64); 15 | #else 16 | auto status = ZydisDecoderInit(&m_decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_ADDRESS_WIDTH_32); 17 | #endif 18 | 19 | if (!ZYAN_SUCCESS(status)) 20 | { 21 | throw renhook::exception("decoder initialization failed"); 22 | } 23 | } 24 | } 25 | 26 | const renhook::zydis::decoded_info renhook::zydis::decode(uintptr_t address, size_t length, size_t minimum_decoded_length, size_t& decoded_length) const 27 | { 28 | decoded_info info; 29 | info.lowest_relative_address = -1; 30 | info.highest_relative_address = 0; 31 | 32 | decoded_length = 0; 33 | while (decoded_length < minimum_decoded_length) 34 | { 35 | decoded_info::instruction instruction; 36 | auto instruction_address = address + decoded_length; 37 | 38 | auto status = ZydisDecoderDecodeBuffer(&m_decoder, reinterpret_cast(instruction_address), length, &instruction); 39 | if (!ZYAN_SUCCESS(status)) 40 | { 41 | break; 42 | } 43 | 44 | instruction.is_relative = instruction.attributes & ZYDIS_ATTRIB_IS_RELATIVE; 45 | 46 | // Calculate the absolute address if it is relative. 47 | if (instruction.is_relative) 48 | { 49 | get_absolute_address(instruction_address, instruction, instruction.disp); 50 | 51 | if (instruction.disp.absolute_address < info.lowest_relative_address) 52 | { 53 | info.lowest_relative_address = instruction.disp.absolute_address; 54 | } 55 | 56 | if (instruction.disp.absolute_address > info.highest_relative_address) 57 | { 58 | info.highest_relative_address = instruction.disp.absolute_address; 59 | } 60 | 61 | instruction.add_to_jump_table = 62 | (instruction.opcode & 0xF0) == 0x70 63 | || (instruction.opcode_map == ZYDIS_OPCODE_MAP_0F && (instruction.opcode & 0xF0) == 0x80) 64 | || instruction.mnemonic == ZYDIS_MNEMONIC_CALL; 65 | } 66 | else 67 | { 68 | instruction.disp.absolute_address = 0; 69 | instruction.disp.offset = 0; 70 | instruction.disp.size = 0; 71 | } 72 | 73 | info.instructions.emplace_back(std::move(instruction)); 74 | decoded_length += instruction.length; 75 | } 76 | 77 | return info; 78 | } 79 | 80 | void renhook::zydis::get_absolute_address(uintptr_t instr_address, const ZydisDecodedInstruction& decoded_instr, decoded_info::instruction::displacement& displacement) const 81 | { 82 | if (decoded_instr.raw.imm[0].is_relative) 83 | { 84 | displacement.absolute_address = instr_address + decoded_instr.length + static_cast(decoded_instr.raw.imm[0].value.s); 85 | displacement.offset = decoded_instr.raw.imm[0].offset; 86 | displacement.size = decoded_instr.raw.imm[0].size; 87 | } 88 | else if ((decoded_instr.attributes & ZYDIS_ATTRIB_HAS_MODRM) && decoded_instr.raw.modrm.mod == 0 && decoded_instr.raw.modrm.rm == 5) 89 | { 90 | displacement.absolute_address = instr_address + decoded_instr.length + static_cast(decoded_instr.raw.disp.value); 91 | displacement.offset = decoded_instr.raw.disp.offset; 92 | displacement.size = decoded_instr.raw.disp.size; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/renhook/zydis.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENHOOK_ZYDIS_H 2 | #define RENHOOK_ZYDIS_H 3 | 4 | #include 5 | #include 6 | 7 | namespace renhook 8 | { 9 | /** 10 | * @brief A Zydis C++ wrapper. 11 | */ 12 | class zydis 13 | { 14 | public: 15 | 16 | struct decoded_info 17 | { 18 | struct instruction : ZydisDecodedInstruction 19 | { 20 | struct displacement 21 | { 22 | uintptr_t absolute_address; 23 | uint8_t offset; 24 | 25 | 26 | /** 27 | * @brief The size of displacement in bits. 28 | */ 29 | uint8_t size; 30 | }; 31 | 32 | bool is_relative; 33 | bool add_to_jump_table; 34 | 35 | displacement disp; 36 | }; 37 | 38 | std::vector instructions; 39 | 40 | uintptr_t lowest_relative_address; 41 | uintptr_t highest_relative_address; 42 | }; 43 | 44 | zydis(); 45 | ~zydis() = default; 46 | 47 | /** 48 | * @brief Decode instructions until #minimum_decoded_length is met. 49 | * 50 | * @param address[in] The start address where to begin the decoding. 51 | * @param length[in] The length of the code section. 52 | * @param minimum_decoded_length[in] The minimum length of decoded instructions. 53 | * @param decoded_length[out] The actual decoded length. 54 | * 55 | * @return A structure containing the decoded information. 56 | */ 57 | const decoded_info decode(uintptr_t address, size_t length, size_t minimum_decoded_length, size_t& decoded_length) const; 58 | 59 | private: 60 | 61 | void get_absolute_address(uintptr_t instr_address, const ZydisDecodedInstruction& decoded_instr, decoded_info::instruction::displacement& displacement) const; 62 | 63 | static ZydisDecoder m_decoder; 64 | static bool m_initialized; 65 | }; 66 | } 67 | #endif 68 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(Tests "") 2 | 3 | file(GLOB_RECURSE HEADERS *.hpp) 4 | file(GLOB_RECURSE SOURCES *.cpp) 5 | 6 | source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${HEADERS} ${SOURCES}) 7 | 8 | target_include_directories(Tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 9 | target_sources(Tests PRIVATE ${HEADERS} ${SOURCES}) 10 | target_link_libraries(Tests PRIVATE Catch2::Catch2 RenHook) 11 | -------------------------------------------------------------------------------- /tests/executable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | TEST_CASE("executable") 7 | { 8 | SECTION("base address") 9 | { 10 | REQUIRE(renhook::executable::get_base_address() == reinterpret_cast(GetModuleHandle(nullptr))); 11 | } 12 | SECTION("code section") 13 | { 14 | auto base_address = renhook::executable::get_base_address(); 15 | auto dos_header = reinterpret_cast(base_address); 16 | auto nt_header = reinterpret_cast(base_address + static_cast(dos_header->e_lfanew)); 17 | auto code_base_address = base_address + nt_header->OptionalHeader.BaseOfCode; 18 | 19 | SECTION("base address") 20 | { 21 | REQUIRE(code_base_address == renhook::executable::get_code_base_address()); 22 | } 23 | SECTION("end address") 24 | { 25 | auto code_end_address = code_base_address + nt_header->OptionalHeader.SizeOfCode; 26 | REQUIRE(code_end_address == renhook::executable::get_code_end_address()); 27 | } 28 | SECTION("size") 29 | { 30 | auto code_size = nt_header->OptionalHeader.SizeOfCode; 31 | REQUIRE(code_size == renhook::executable::get_code_size()); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/hook_writer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | bool compare_memory(const uint8_t* memory, std::vector bytes) 7 | { 8 | if (bytes.size() == 0) 9 | { 10 | return false; 11 | } 12 | 13 | for (size_t i = 0; i < bytes.size(); i++) 14 | { 15 | auto byte = bytes[i]; 16 | if (byte == 0xCC) 17 | { 18 | continue; 19 | } 20 | 21 | if (byte != memory[i]) 22 | { 23 | return false; 24 | } 25 | } 26 | 27 | return true; 28 | } 29 | 30 | TEST_CASE("hook_writer") 31 | { 32 | uint8_t bytes[64]; 33 | uint8_t copy_bytes[] = { 0x57, 0x56, 0x55, 0x90, 0x90 }; 34 | 35 | #ifdef _WIN64 36 | size_t relative_jump_index = 19; 37 | #else 38 | size_t relative_jump_index = 5; 39 | #endif 40 | 41 | renhook::hook_writer writer(bytes); 42 | writer.copy_from(copy_bytes, sizeof(copy_bytes)); 43 | 44 | #ifdef _WIN64 45 | writer.write_indirect_jump(0); 46 | #endif 47 | 48 | writer.write_relative_jump(reinterpret_cast(&bytes[relative_jump_index] + 5 - 0x30)); 49 | writer.write_nops(5); 50 | 51 | std::vector expected_bytes = 52 | { 53 | 0x57, 0x56, 0x55, 0x90, 0x90, 54 | 55 | #ifdef _WIN64 56 | 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 57 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | #endif 59 | 60 | 0xE9, 0xD0, 0xFF, 0xFF, 0xFF, 61 | 0x90, 0x90, 0x90, 0x90, 0x90 62 | }; 63 | 64 | REQUIRE(compare_memory(bytes, expected_bytes)); 65 | } 66 | -------------------------------------------------------------------------------- /tests/hooks/prologue_hook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | template 7 | using hook_t = renhook::prologue_hook; 8 | 9 | extern renhook::memory::memory_allocator global_allocator; 10 | extern bool compare_memory(const uint8_t* memory, std::vector bytes); 11 | 12 | __declspec(noinline) uint32_t fibonacci(uint32_t a) 13 | { 14 | if (a == 0 || a == 1) 15 | { 16 | return a; 17 | } 18 | 19 | return fibonacci(a - 1) + fibonacci(a - 2); 20 | } 21 | 22 | __declspec(noinline) uint32_t fibonacci_hooked(uint32_t a) 23 | { 24 | return a; 25 | } 26 | 27 | TEST_CASE("hooks::inline_hook", "[hooks][inline_hook]") 28 | { 29 | using void_func_t = void(*)(); 30 | 31 | SECTION("skip jumps") 32 | { 33 | uint8_t data[] = 34 | { 35 | 0xEB, 0x00, 36 | 0xE9, 0x00, 0x00, 0x00, 0x00, 37 | 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 38 | 39 | #ifdef _WIN64 40 | 0x48, 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 41 | 0x48, 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 42 | #endif 43 | 44 | 0xE9, 0x06, 0x00, 0x00, 0x00, 45 | 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 46 | 0xEB, 0x00, 47 | 48 | 0x57, // push edi / rdi 49 | 0x8D, 0x85, 0xE8, 0x03, 0x00, 0x00, // lea r8, [rbp+3C0h+arg_1] 50 | 0x8D, 0x54, 0x24, 0x50, // lea rdx, [rsp+4C0h+var_2] 51 | 0x8B, 0xC8, // mov rcx, rax 52 | 0xE8, 0x00, 0x00, 0x00, 0x00, // call 0x00000000 53 | 54 | 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 55 | 56 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 57 | 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 58 | 59 | 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 60 | 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 61 | 62 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 63 | 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 64 | 65 | 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 66 | 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC 67 | }; 68 | 69 | #ifdef _WIN64 70 | auto data_len = sizeof(data); 71 | struct displacements 72 | { 73 | uint8_t* source; 74 | uint8_t* target; 75 | uint8_t* indirect_storage; 76 | uint8_t prefix_length; 77 | 78 | displacements(uint8_t* source, uint8_t* target, uint8_t* indirect_storage, uint8_t prefix_length) 79 | : source(source) 80 | , target(target) 81 | , indirect_storage(indirect_storage) 82 | , prefix_length(prefix_length) 83 | { 84 | } 85 | }; 86 | 87 | std::vector disps = 88 | { 89 | displacements(&data[9], &data[13], &data[data_len - 64], 2), 90 | displacements(&data[16], &data[20], &data[data_len - 48], 3), 91 | displacements(&data[23], &data[27], &data[data_len - 32], 3), 92 | displacements(&data[34], &data[33], &data[data_len - 16], 2) 93 | }; 94 | 95 | for (auto& disp : disps) 96 | { 97 | *reinterpret_cast(disp.indirect_storage) = reinterpret_cast(disp.target); 98 | 99 | auto rel_disp = disp.indirect_storage - disp.source - sizeof(int32_t); 100 | *reinterpret_cast(disp.source) = static_cast(rel_disp); 101 | } 102 | #else 103 | *(reinterpret_cast(&data[9])) = reinterpret_cast(&data[13]); 104 | *(reinterpret_cast(&data[20])) = reinterpret_cast(&data[24]); 105 | #endif 106 | 107 | hook_t hook(reinterpret_cast(&data), static_cast(0)); 108 | hook.attach(); 109 | 110 | auto block = hook.get_block_address(); 111 | std::vector expected_block_bytes = 112 | { 113 | 0x57, 114 | 0x8D, 0x85, 0xE8, 0x03, 0x00, 0x00 115 | }; 116 | 117 | REQUIRE(compare_memory(block, expected_block_bytes)); 118 | } 119 | SECTION("empty hook") 120 | { 121 | hook_t empty_hook; 122 | REQUIRE_THROWS(empty_hook.attach()); 123 | } 124 | SECTION("fake hook") 125 | { 126 | SECTION("without relative instruction pointers") 127 | { 128 | uint8_t data[] = 129 | { 130 | #ifdef _WIN64 131 | 0x48, 0x89, 0x5C, 0x24, 0x08, // mov [rsp+arg_0], rbx 132 | 0x57, // push rdi 133 | 0x48, 0x83, 0xEC, 0x20, // sub rsp, 20h 134 | 0x48, 0x8B, 0xD9, // mov rbx, rcx 135 | 0x33, 0xFF, // xor edi, edi 136 | 0x48, 0x83, 0xC1, 0x08, // add rcx, 8 137 | 0x48, 0x89, 0x79, 0xF8, // mov [rcx-8], rdi 138 | 0xE8, 0x34, 0x32, 0xAF, 0xFE, // call 0xFFFFFFFFFEAF3239 139 | 0x48, 0x8B, 0x0D, 0x2D, 0x76, 0x2F, 0x01 // mov rcx, [0x00000000012F7634] 140 | #else 141 | 0x89, 0x54, 0x24, 0x08, // mov [esp+arg_0], ebx 142 | 0x57, // push edi 143 | 0x83, 0xEC, 0x20, // sub esp, 20h 144 | 0x8B, 0xD9, // mov ebx, ecx 145 | 0x33, 0xFF, // xor edi, edi 146 | 0x83, 0xC1, 0x08, // add ecx, 8 147 | 0x89, 0x79, 0xF8, // mov [ecx-8], edi 148 | 0xE8, 0x34, 0x32, 0xAF, 0xFE, // call 0xEAF3239 149 | 0x8B, 0x0D, 0x2D, 0x76, 0x2F, 0x01 // mov ecx, [0x12F7634] 150 | #endif 151 | }; 152 | 153 | hook_t hook(reinterpret_cast(&data), static_cast(0)); 154 | hook.attach(); 155 | 156 | auto block = hook.get_block_address(); 157 | std::vector expected_data_bytes = 158 | { 159 | #ifdef _WIN64 160 | 0xE9, 0x00, 0x00, 0x00, 0x00, 161 | 0x57, 162 | 0x48, 0x83, 0xEC, 0x20, 163 | 0x48, 0x8B, 0xD9, 164 | 0x33, 0xFF, 165 | 0x48, 0x83, 0xC1, 0x08, 166 | 0x48, 0x89, 0x79, 0xF8 167 | #else 168 | 0xE9, 0x00, 0x00, 0x00, 0x00, 169 | 0x83, 0xEC, 0x20 170 | #endif 171 | }; 172 | 173 | std::vector expected_block_bytes = 174 | { 175 | #ifdef _WIN64 176 | 0x48, 0x89, 0x5C, 0x24, 0x08, 177 | 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 178 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 179 | #else 180 | 0x89, 0x54, 0x24, 0x08, 181 | 0x57, 182 | 0xE9, 0x00, 0x00, 0x00, 0x00 183 | #endif 184 | }; 185 | 186 | #ifdef _WIN64 187 | *(reinterpret_cast(&expected_data_bytes[1])) = static_cast(renhook::utils::calculate_displacement(reinterpret_cast(&data), reinterpret_cast(block + 5 + hook_t::indirect_jump_size), 5)); 188 | *(reinterpret_cast(&expected_block_bytes[11])) = reinterpret_cast(&data[5]); 189 | #else 190 | *(reinterpret_cast(&expected_data_bytes[1])) = static_cast(renhook::utils::calculate_displacement(reinterpret_cast(&data), 0, 5)); 191 | *(reinterpret_cast(&expected_block_bytes[6])) = renhook::utils::calculate_displacement(reinterpret_cast(block + 5), reinterpret_cast(&data[5]), 5); 192 | #endif 193 | 194 | REQUIRE(compare_memory(data, expected_data_bytes)); 195 | REQUIRE(compare_memory(block, expected_block_bytes)); 196 | 197 | hook.detach(); 198 | 199 | expected_data_bytes = 200 | { 201 | #ifdef _WIN64 202 | 0x48, 0x89, 0x5C, 0x24, 0x08, 203 | 0x57, 204 | 0x48, 0x83, 0xEC, 0x20, 205 | 0x48, 0x8B, 0xD9, 206 | 0x33, 0xFF, 207 | 0x48, 0x83, 0xC1, 0x08, 208 | 0x48, 0x89, 0x79, 0xF8 209 | #else 210 | 0x89, 0x54, 0x24, 0x08, 211 | 0x57, 212 | 0x83, 0xEC, 0x20 213 | #endif 214 | }; 215 | 216 | REQUIRE(compare_memory(data, expected_data_bytes)); 217 | } 218 | SECTION("with relative instruction pointers") 219 | { 220 | uint8_t data[] = 221 | { 222 | 0x57, // push edi / rdi 223 | 0x74, 0x60, // jz 0x60 224 | 0x0F, 0x84, 0x80, 0xFF, 0xFF, 0xFF, // jz 0xFFFFFF80 225 | 0x8D, 0x85, 0xE8, 0x03, 0x00, 0x00, // lea r8, [rbp+3C0h+arg_1] 226 | 0x8D, 0x54, 0x24, 0x50, // lea rdx, [rsp+4C0h+var_2] 227 | 0x8B, 0xC8, // mov rcx, rax 228 | 0xE8, 0x97, 0x05, 0xD0, 0x00, // call 0x00D0059C 229 | 0x8B, 0xC8 // mov rcx, rax 230 | }; 231 | 232 | auto first_jump_real_addr = reinterpret_cast(&data[1]) + data[2] + 2; 233 | auto second_jump_real_addr = reinterpret_cast(&data[3]) + *reinterpret_cast(&data[5]) + 6; 234 | 235 | hook_t hook(reinterpret_cast(&data), static_cast(0)); 236 | hook.attach(); 237 | 238 | auto block = hook.get_block_address(); 239 | std::vector expected_data_bytes = 240 | { 241 | 0xE9, 0x00, 0x00, 0x00, 0x00, 242 | 0x90, 0x90, 0x90, 0x90, 243 | 0x8D, 0x85, 0xE8, 0x03, 0x00, 0x00 244 | }; 245 | 246 | std::vector expected_block_bytes = 247 | { 248 | 0x57, 249 | 0x74, 0x00, 250 | 0x0F, 0x84, 0x00, 0x00, 0x00, 0x00, 251 | 252 | #ifdef _WIN64 253 | // Return to function jump. 254 | 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 255 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 256 | 257 | // Indirect jump to detour. 258 | 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 259 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 260 | 261 | // First item in jump table (for the 2 bytes jump). 262 | 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 263 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 264 | #else 265 | // Return to function jump. 266 | 0xE9, 0x00, 0x00, 0x00, 0x00, 267 | 268 | // First item in jump table (for the 2 bytes jump). 269 | 0xE9, 0x00, 0x00, 0x00, 0x00, 270 | #endif 271 | }; 272 | 273 | #ifdef _WIN64 274 | *(reinterpret_cast(&expected_data_bytes[1])) = static_cast(renhook::utils::calculate_displacement(reinterpret_cast(&data), reinterpret_cast(block + 9 + hook_t::indirect_jump_size), 5)); 275 | 276 | *(reinterpret_cast(&expected_block_bytes[2])) = static_cast(renhook::utils::calculate_displacement(reinterpret_cast(&data[1]), reinterpret_cast(&data[0] + 37), 2)); 277 | *(reinterpret_cast(&expected_block_bytes[15])) = reinterpret_cast(&data[9]); 278 | *(reinterpret_cast(&expected_block_bytes[43])) = first_jump_real_addr; 279 | *(reinterpret_cast(&expected_block_bytes[5])) = static_cast(renhook::utils::calculate_displacement(reinterpret_cast(block + 3), second_jump_real_addr, 6)); 280 | #else 281 | *(reinterpret_cast(&expected_data_bytes[1])) = static_cast(renhook::utils::calculate_displacement(reinterpret_cast(&data), 0, 5)); 282 | 283 | *(reinterpret_cast(&expected_block_bytes[2])) = static_cast(renhook::utils::calculate_displacement(reinterpret_cast(&data[1]), reinterpret_cast(&data[0] + 14), 2)); 284 | *(reinterpret_cast(&expected_block_bytes[5])) = renhook::utils::calculate_displacement(reinterpret_cast(&data[3]), reinterpret_cast(&data[0] + 19), 6); 285 | *(reinterpret_cast(&expected_block_bytes[10])) = renhook::utils::calculate_displacement(reinterpret_cast(block + 9), reinterpret_cast(&data[9]), 5); 286 | *(reinterpret_cast(&expected_block_bytes[15])) = renhook::utils::calculate_displacement(reinterpret_cast(block + 14), first_jump_real_addr, 5); 287 | *(reinterpret_cast(&expected_block_bytes[5])) = renhook::utils::calculate_displacement(reinterpret_cast(block + 3), second_jump_real_addr, 6); 288 | #endif 289 | 290 | REQUIRE(compare_memory(data, expected_data_bytes)); 291 | REQUIRE(compare_memory(block, expected_block_bytes)); 292 | 293 | hook.detach(); 294 | 295 | expected_data_bytes = 296 | { 297 | 0x57, 298 | 0x74, 0x60, 299 | 0x0F, 0x84, 0x80, 0xFF, 0xFF, 0xFF, 300 | 0x8D, 0x85, 0xE8, 0x03, 0x00, 0x00, 301 | 0x8D, 0x54, 0x24, 0x50 302 | }; 303 | 304 | REQUIRE(compare_memory(data, expected_data_bytes)); 305 | } 306 | } 307 | SECTION("real hook") 308 | { 309 | REQUIRE(fibonacci(11) == 89); 310 | 311 | using fibonacci_t = uint32_t (*)(uint32_t); 312 | hook_t hook(reinterpret_cast(&fibonacci), &fibonacci_hooked); 313 | 314 | hook.attach(); 315 | REQUIRE(fibonacci(127) == 127); 316 | 317 | hook.detach(); 318 | REQUIRE(fibonacci(17) == 1597); 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /tests/memory/memory_allocator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | TEST_CASE("memory::memory_allocator", "[memory][memory_allocator]") 9 | { 10 | SYSTEM_INFO system_info = { 0 }; 11 | GetSystemInfo(&system_info); 12 | 13 | size_t region_size = system_info.dwAllocationGranularity; 14 | auto minimum_address = reinterpret_cast(system_info.lpMinimumApplicationAddress); 15 | auto maximum_address = reinterpret_cast(system_info.lpMaximumApplicationAddress); 16 | 17 | renhook::memory::memory_allocator allocator; 18 | 19 | char* block_a = static_cast(allocator.alloc(0, -1)); 20 | 21 | MEMORY_BASIC_INFORMATION memoryInfo = { 0 }; 22 | VirtualQuery(block_a, &memoryInfo, sizeof(memoryInfo)); 23 | 24 | REQUIRE(memoryInfo.Protect == PAGE_EXECUTE_READ); 25 | 26 | renhook::memory::virtual_protect protection(block_a, renhook::memory::memory_allocator::block_size, renhook::memory::protection::write); 27 | 28 | REQUIRE(block_a != nullptr); 29 | block_a[0] = '1'; 30 | block_a[255] = '1'; 31 | 32 | auto block_b = allocator.alloc(minimum_address + 0x10000, maximum_address / 2); 33 | REQUIRE(block_b != nullptr); 34 | 35 | auto block_c = allocator.alloc(minimum_address + 0x10000, maximum_address / 2 + 0x500); 36 | REQUIRE(block_c != nullptr); 37 | 38 | size_t diff = std::abs(reinterpret_cast(block_c) - reinterpret_cast(block_b)); 39 | REQUIRE(diff < region_size); 40 | 41 | REQUIRE_THROWS(allocator.alloc(reinterpret_cast(block_c), reinterpret_cast(block_c) + 0x10)); 42 | 43 | auto block_d = allocator.alloc(maximum_address / 2 + 0x100, maximum_address); 44 | REQUIRE(block_d != nullptr); 45 | 46 | diff = std::abs(reinterpret_cast(block_d) - reinterpret_cast(block_b)); 47 | REQUIRE(diff >= region_size); 48 | 49 | std::vector blocks; 50 | for (size_t i = 0; i < 10000; i++) 51 | { 52 | auto block = allocator.alloc(0, -1); 53 | blocks.emplace_back(block); 54 | } 55 | 56 | auto block_e = allocator.alloc(0, -1); 57 | auto block_f = allocator.alloc(0, -1); 58 | auto block_g = allocator.alloc(0, -1); 59 | 60 | allocator.free(block_a); 61 | 62 | auto block_h = allocator.alloc(0, -1); 63 | 64 | allocator.free(block_b); 65 | allocator.free(block_c); 66 | allocator.free(block_g); 67 | 68 | auto block_i = allocator.alloc(0, -1); 69 | allocator.free(block_h); 70 | 71 | allocator.free(block_d); 72 | allocator.free(block_e); 73 | allocator.free(block_f); 74 | allocator.free(block_i); 75 | 76 | for (auto block : blocks) 77 | { 78 | allocator.free(block); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/memory/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | TEST_CASE("memory::utils", "[memory][utils]") 5 | { 6 | constexpr size_t granularity = 128; 7 | 8 | SECTION("align up") 9 | { 10 | REQUIRE(renhook::memory::utils::align_up(1, granularity) == 128); 11 | REQUIRE(renhook::memory::utils::align_up(127, granularity) == 128); 12 | REQUIRE(renhook::memory::utils::align_up(128, granularity) == 128); 13 | 14 | REQUIRE(renhook::memory::utils::align_up(129, granularity) == 256); 15 | REQUIRE(renhook::memory::utils::align_up(200, granularity) == 256); 16 | REQUIRE(renhook::memory::utils::align_up(240, granularity) == 256); 17 | 18 | REQUIRE(renhook::memory::utils::align_up(257, granularity) == 384); 19 | } 20 | SECTION("align down") 21 | { 22 | REQUIRE(renhook::memory::utils::align_down(1, granularity) == 0); 23 | REQUIRE(renhook::memory::utils::align_down(30, granularity) == 0); 24 | REQUIRE(renhook::memory::utils::align_down(127, granularity) == 0); 25 | 26 | REQUIRE(renhook::memory::utils::align_down(128, granularity) == 128); 27 | REQUIRE(renhook::memory::utils::align_down(129, granularity) == 128); 28 | REQUIRE(renhook::memory::utils::align_down(240, granularity) == 128); 29 | 30 | REQUIRE(renhook::memory::utils::align_down(256, granularity) == 256); 31 | } 32 | } -------------------------------------------------------------------------------- /tests/memory/virtual_protect.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | constexpr size_t allocation_size = 0x10000; 9 | 10 | uint32_t get_memory_protection(void* address) 11 | { 12 | MEMORY_BASIC_INFORMATION memory_info = { 0 }; 13 | if (!VirtualQuery(address, &memory_info, sizeof(memory_info))) 14 | { 15 | throw renhook::exception("retrieving information about memory failed", GetLastError()); 16 | } 17 | 18 | return memory_info.Protect; 19 | } 20 | 21 | TEST_CASE("memory::virtual_protect", "[memory][protection]") 22 | { 23 | using protection = renhook::memory::protection; 24 | 25 | SECTION("valid address") 26 | { 27 | auto address = VirtualAlloc(nullptr, allocation_size, MEM_COMMIT, PAGE_NOACCESS); 28 | REQUIRE(get_memory_protection(address) == PAGE_NOACCESS); 29 | 30 | SECTION("protection::read") 31 | { 32 | renhook::memory::virtual_protect _(address, allocation_size, protection::read); 33 | REQUIRE(get_memory_protection(address) == PAGE_READONLY); 34 | } 35 | SECTION("protection::write") 36 | { 37 | renhook::memory::virtual_protect _(address, allocation_size, protection::write); 38 | REQUIRE(get_memory_protection(address) == PAGE_READWRITE); 39 | } 40 | SECTION("protection::execute") 41 | { 42 | renhook::memory::virtual_protect _(address, allocation_size, protection::execute); 43 | REQUIRE(get_memory_protection(address) == PAGE_EXECUTE); 44 | } 45 | SECTION("protection::read | protection::write") 46 | { 47 | renhook::memory::virtual_protect _(address, allocation_size, protection::read | protection::write); 48 | REQUIRE(get_memory_protection(address) == PAGE_READWRITE); 49 | } 50 | SECTION("protection::read | protection::execute") 51 | { 52 | renhook::memory::virtual_protect _(address, allocation_size, protection::read | protection::execute); 53 | REQUIRE(get_memory_protection(address) == PAGE_EXECUTE_READ); 54 | } 55 | SECTION("protection::read | protection::write | protection::execute") 56 | { 57 | renhook::memory::virtual_protect _(address, allocation_size, protection::read | protection::write | protection::execute); 58 | REQUIRE(get_memory_protection(address) == PAGE_EXECUTE_READWRITE); 59 | } 60 | 61 | REQUIRE(get_memory_protection(address) == PAGE_NOACCESS); 62 | 63 | SECTION("permanent change") 64 | { 65 | { 66 | renhook::memory::virtual_protect _(address, allocation_size, protection::read, true); 67 | } 68 | 69 | REQUIRE(get_memory_protection(address) == PAGE_READONLY); 70 | } 71 | 72 | VirtualFree(address, 0, MEM_RELEASE); 73 | address = nullptr; 74 | } 75 | SECTION("invalid address") 76 | { 77 | REQUIRE_THROWS(renhook::memory::virtual_protect(nullptr, allocation_size, protection::read)); 78 | REQUIRE_THROWS(renhook::memory::virtual_protect(reinterpret_cast(1), allocation_size, protection::read)); 79 | REQUIRE_THROWS(renhook::memory::virtual_protect(reinterpret_cast(-1), allocation_size, protection::read)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/pattern.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE("pattern") 6 | { 7 | std::vector fake_memory = 8 | { 9 | 0x48, 0x89, 0x5C, 0x24, 0x08, // mov [rsp+arg_0], rbx 10 | 0x57, // push rdi 11 | 0x48, 0x83, 0xEC, 0x20, // sub rsp, 20h 12 | 0x48, 0x8B, 0xD9, // mov rbx, rcx 13 | 0x33, 0xFF, // xor edi, edi 14 | 0x48, 0x83, 0xC1, 0x08, // add rcx, 8 15 | 0x48, 0x89, 0x79, 0xF8, // mov [rcx-8], rdi 16 | 0xE8, 0x34, 0x32, 0xAF, 0xFE, // call 0xFFFFFFFFFEAF3239 17 | 0x48, 0x8B, 0x0D, 0x2D, 0x76, 0x2F, 0x01, // mov rcx, [0x00000000012F7634] 18 | 0xE8, 0x2B, 0x32, 0xAF, 0xFE, // call 0xFFFFFFFFF4A13636 19 | 0x48, 0x8D, 0x4B, 0x20, // mov rcx, [0xFFFFFFFFA1277631] 20 | 0xE8, 0x22, 0x32, 0xAF, 0xFE, // call 0x0000000045030105 21 | 0x48, 0x89, 0x7B, 0x30, // mov [rbx+30h], rdi 22 | 0x89, 0x7B, 0x38, // mov [rbx+38h], edi 23 | 0x48, 0x8B, 0xC3, // mov rax, rbx 24 | 0x48, 0x8B, 0x5C, 0x24, 0x30, // mov rbx, [rsp+28h+arg_0] 25 | 0x48, 0x83, 0xC4, 0x20, // add rsp, 20h 26 | 0x5F, // pop rdi 27 | 0xC3 // retn 28 | }; 29 | 30 | auto start = fake_memory.data(); 31 | auto end = start + fake_memory.size(); 32 | 33 | SECTION("{}") 34 | { 35 | renhook::pattern pattern; 36 | 37 | REQUIRE(pattern.empty()); 38 | REQUIRE(pattern.size() == 0); 39 | REQUIRE_THROWS(pattern.find(0xCC, start, end)); 40 | } 41 | SECTION("48 89 5C 24 08") 42 | { 43 | renhook::pattern pattern({ 0x48, 0x89, 0x5C, 0x24, 0x08 }); 44 | 45 | REQUIRE(!pattern.empty()); 46 | REQUIRE(pattern.size() == 5); 47 | 48 | auto offsets = pattern.find(0xCC, start, end); 49 | 50 | REQUIRE(offsets.size() == 1); 51 | REQUIRE(offsets[0] == reinterpret_cast(&fake_memory[0])); 52 | } 53 | SECTION("E8 ? ? ? ?") 54 | { 55 | renhook::pattern pattern({ 0xE8, 0xCC, 0xCC, 0xCC, 0xCC }); 56 | 57 | REQUIRE(!pattern.empty()); 58 | REQUIRE(pattern.size() == 5); 59 | 60 | auto offsets = pattern.find(0xCC, start, end); 61 | 62 | REQUIRE(offsets.size() == 3); 63 | REQUIRE(offsets[0] == reinterpret_cast(&fake_memory[23])); 64 | REQUIRE(offsets[1] == reinterpret_cast(&fake_memory[35])); 65 | REQUIRE(offsets[2] == reinterpret_cast(&fake_memory[44])); 66 | } 67 | SECTION("48 89 79 F8 E8 ? ? ? ? 48 8B 0D ? ? ? ?") 68 | { 69 | renhook::pattern pattern({ 0x48, 0x89, 0x79, 0xF8, 0xE8, 0xCC, 0xCC, 0xCC, 0xCC, 0x48, 0x8B, 0x0D, 0xCC, 0xCC, 0xCC, 0xCC }); 70 | 71 | REQUIRE(!pattern.empty()); 72 | REQUIRE(pattern.size() == 16); 73 | 74 | auto offsets = pattern.find(0xCC, start, end); 75 | 76 | REQUIRE(offsets.size() == 1); 77 | REQUIRE(offsets[0] == reinterpret_cast(&fake_memory[19])); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/suspend_threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace 10 | { 11 | std::atomic_bool running = true; 12 | void suspend_threads_test() 13 | { 14 | while (running) 15 | { 16 | } 17 | } 18 | } 19 | 20 | TEST_CASE("suspend_threads") 21 | { 22 | std::thread thread(suspend_threads_test); 23 | auto handle = thread.native_handle(); 24 | 25 | { 26 | renhook::suspend_threads _(0, 0); 27 | 28 | auto count = SuspendThread(handle); 29 | ResumeThread(handle); 30 | 31 | REQUIRE(count > 0); 32 | 33 | running = false; 34 | } 35 | 36 | auto count = SuspendThread(handle); 37 | ResumeThread(handle); 38 | 39 | REQUIRE(count == 0); 40 | 41 | thread.join(); 42 | } 43 | -------------------------------------------------------------------------------- /tests/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE("utils") 6 | { 7 | REQUIRE(renhook::utils::calculate_displacement(100, 50, 5) == -55); 8 | REQUIRE(renhook::utils::calculate_displacement(30, 60, 5) == 25); 9 | } 10 | -------------------------------------------------------------------------------- /tests/zydis.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE("zydis") 6 | { 7 | uint8_t data[] = 8 | { 9 | #ifdef _WIN64 10 | 0x48, 0x89, 0x5C, 0x24, 0x08, // mov [rsp+arg_0], rbx 11 | 0x57, // push rdi 12 | 0x48, 0x83, 0xEC, 0x20, // sub rsp, 20h 13 | 0x48, 0x8B, 0xD9, // mov rbx, rcx 14 | 0x33, 0xFF, // xor edi, edi 15 | 0x48, 0x83, 0xC1, 0x08, // add rcx, 8 16 | 0x48, 0x89, 0x79, 0xF8, // mov [rcx-8], rdi 17 | 0xE8, 0x34, 0x32, 0xAF, 0xFE, // call 0xFFFFFFFFFEAF3239 18 | 0x48, 0x8B, 0x0D, 0x2D, 0x76, 0x2F, 0x01 // mov rcx, [0x00000000012F7634] 19 | #else 20 | 0x89, 0x54, 0x24, 0x08, // mov [esp+arg_0], ebx 21 | 0x57, // push edi 22 | 0x83, 0xEC, 0x20, // sub esp, 20h 23 | 0x8B, 0xD9, // mov ebx, ecx 24 | 0x33, 0xFF, // xor edi, edi 25 | 0x83, 0xC1, 0x08, // add ecx, 8 26 | 0x89, 0x79, 0xF8, // mov [ecx-8], edi 27 | 0xE8, 0x34, 0x32, 0xAF, 0xFE, // call 0xEAF3239 28 | 0x8B, 0x0D, 0x2D, 0x76, 0x2F, 0x01 // mov ecx, [0x12F7634] 29 | #endif 30 | }; 31 | 32 | size_t decoded_length; 33 | 34 | renhook::zydis zydis; 35 | auto decoded_info = zydis.decode(reinterpret_cast(&data), sizeof(data), 5, decoded_length); 36 | 37 | #ifdef _WIN64 38 | REQUIRE(decoded_length == 5); 39 | REQUIRE(decoded_info.instructions.size() == 1); 40 | 41 | decoded_info = zydis.decode(reinterpret_cast(&data), sizeof(data), 16, decoded_length); 42 | 43 | REQUIRE(decoded_length == 19); 44 | REQUIRE(decoded_info.instructions.size() == 6); 45 | 46 | decoded_info = zydis.decode(reinterpret_cast(&data), sizeof(data), 32, decoded_length); 47 | 48 | REQUIRE(decoded_length == 35); 49 | REQUIRE(decoded_info.instructions.size() == 9); 50 | #else 51 | REQUIRE(decoded_length == 5); 52 | REQUIRE(decoded_info.instructions.size() == 2); 53 | 54 | decoded_info = zydis.decode(reinterpret_cast(&data), sizeof(data), 14, decoded_length); 55 | 56 | REQUIRE(decoded_length == 15); 57 | REQUIRE(decoded_info.instructions.size() == 6); 58 | 59 | decoded_info = zydis.decode(reinterpret_cast(&data), sizeof(data), 29, decoded_length); 60 | 61 | REQUIRE(decoded_length == 29); 62 | REQUIRE(decoded_info.instructions.size() == 9); 63 | #endif 64 | } 65 | --------------------------------------------------------------------------------