├── .idea ├── .name ├── PolyHook_2_0.iml ├── .gitignore ├── codeStyles │ └── codeStyleConfig.xml ├── dictionaries │ └── project.xml ├── vcs.xml ├── modules.xml ├── misc.xml └── inspectionProfiles │ └── Project_Default.xml ├── UnitTests ├── linux │ ├── TestEatHook.cpp │ ├── TestIatHook.cpp │ ├── TestBreakpointHook.cpp │ ├── TestDetourNoTDx64.cpp │ ├── TestDetourNoTDx86.cpp │ ├── TestVFuncSwapHook.cpp │ ├── TestVTableSwapHook.cpp │ ├── TestDetourSchemex64.cpp │ ├── TestHWBreakpointHook.cpp │ ├── TestDetourTranslationx64.cpp │ ├── TestMemProtector.cpp │ ├── TestDetourx64.cpp │ └── TestDetourx86.cpp ├── TestUtils.cpp ├── windows │ ├── TestIatHook.cpp │ ├── TestBreakpointHook.cpp │ ├── TestHWBreakpointHook.cpp │ ├── TestVTableSwapHook.cpp │ ├── TestVFuncSwapHook.cpp │ ├── TestMemProtector.cpp │ ├── TestDetourTranslationx64.cpp │ ├── TestEatHook.cpp │ └── TestDetourSchemex64.cpp └── TestUtils.hpp ├── _config.yml ├── docs └── _config.yml ├── Examples ├── VCPKG_DynamicLink │ ├── .gitignore │ ├── vcpkg.json │ ├── CMakeLists.txt │ ├── main.cpp │ └── CMakeSettings.json └── VCPKG_StaticLink │ ├── .gitignore │ ├── vcpkg.json │ ├── CMakeLists.txt │ ├── main.cpp │ └── CMakeSettings.json ├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── Seal_of_Approval.png ├── polyhook2 ├── Tests │ ├── StackCanary.hpp │ └── TestEffectTracker.hpp ├── UID.hpp ├── Detour │ ├── NatDetour.hpp │ ├── x86Detour.hpp │ ├── x64Detour.hpp │ └── ILCallback.hpp ├── EventDispatcher.hpp ├── PolyHookOsIncludes.hpp ├── Exceptions │ ├── BreakPointHook.hpp │ ├── HWBreakPointHook.hpp │ └── AVehHook.hpp ├── Virtuals │ ├── VFuncSwapHook.hpp │ └── VTableSwapHook.hpp ├── ErrorLog.hpp ├── PE │ ├── IatHook.hpp │ ├── EatHook.hpp │ └── PEB.hpp ├── MemProtector.hpp ├── MemAccessor.hpp ├── RangeAllocator.hpp ├── PolyHookOs.hpp ├── FBAllocator.hpp ├── Enums.hpp ├── ZydisDisassembler.hpp ├── IHook.hpp └── Misc.hpp ├── .gitmodules ├── sources ├── UID.cpp ├── InternalUtils.hpp ├── PolyHookOs.cpp ├── StackCanary.cpp ├── TestEffectTracker.cpp ├── ErrorLog.cpp ├── BreakPointHook.cpp ├── VFuncSwapHook.cpp ├── AVehHook.cpp ├── VTableSwapHook.cpp ├── HWBreakPointHook.cpp ├── MemProtector.cpp ├── FBAllocator.cpp ├── RangeAllocator.cpp ├── IatHook.cpp ├── MemAccessor.cpp ├── EatHook.cpp └── ZydisDisassembler.cpp ├── .clang-format ├── MainTests.cpp ├── polyhook_2-config.cmake.in ├── LICENSE ├── CMakeSettings.json ├── .gitignore └── README.md /.idea/.name: -------------------------------------------------------------------------------- 1 | PolyHook_2 -------------------------------------------------------------------------------- /UnitTests/linux/TestEatHook.cpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /UnitTests/linux/TestIatHook.cpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /UnitTests/linux/TestBreakpointHook.cpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /UnitTests/linux/TestDetourNoTDx64.cpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /UnitTests/linux/TestDetourNoTDx86.cpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /UnitTests/linux/TestVFuncSwapHook.cpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /UnitTests/linux/TestVTableSwapHook.cpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /UnitTests/linux/TestDetourSchemex64.cpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /UnitTests/linux/TestHWBreakpointHook.cpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /Examples/VCPKG_DynamicLink/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | out 3 | 4 | -------------------------------------------------------------------------------- /Examples/VCPKG_StaticLink/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | out 3 | 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [stevemk14ebr] 4 | -------------------------------------------------------------------------------- /Seal_of_Approval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevemk14ebr/PolyHook_2_0/HEAD/Seal_of_Approval.png -------------------------------------------------------------------------------- /.idea/PolyHook_2_0.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /Examples/VCPKG_DynamicLink/vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testpolyhook2", 3 | "version-string": "1.0.0.1", 4 | "dependencies": [ 5 | "polyhook2" 6 | ] 7 | } -------------------------------------------------------------------------------- /Examples/VCPKG_StaticLink/vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testpolyhook2", 3 | "version-string": "1.0.0.1", 4 | "dependencies": [ 5 | "polyhook2" 6 | ] 7 | } -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/dictionaries/project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | trmp 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /polyhook2/Tests/StackCanary.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace PLH { 4 | class StackCanary { 5 | public: 6 | StackCanary(); 7 | bool isStackGood(); 8 | ~StackCanary() noexcept(false); 9 | private: 10 | unsigned char buf[50]; 11 | }; 12 | } -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "zydis"] 2 | path = zydis 3 | url = https://github.com/zyantific/zydis.git 4 | [submodule "asmjit"] 5 | path = asmjit 6 | url = https://github.com/asmjit/asmjit.git 7 | [submodule "asmtk"] 8 | path = asmtk 9 | url = https://github.com/asmjit/asmtk.git 10 | -------------------------------------------------------------------------------- /sources/UID.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/UID.hpp" 2 | 3 | PLH::UID::UID(long val) { 4 | this->val = val; 5 | } 6 | 7 | std::atomic_long& PLH::UID::singleton() { 8 | static std::atomic_long base = { -1 }; 9 | base++; 10 | return base; 11 | } 12 | 13 | PLH::UID::UID() { 14 | this->val = -1; 15 | } 16 | -------------------------------------------------------------------------------- /sources/InternalUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // This header defines utilities that are not meant to be used by the users of this library 4 | 5 | #ifdef PLH_DIAGNOSTICS 6 | #define PLH_SET_DIAGNOSTIC(DIAGNOSTIC) setDiagnostic(DIAGNOSTIC) 7 | #else 8 | #define PLH_SET_DIAGNOSTIC(DIAGNOSTIC) 9 | #endif 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sources/PolyHookOs.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/PolyHookOs.hpp" 2 | #include "polyhook2/PolyHookOsIncludes.hpp" 3 | 4 | #ifdef POLYHOOK2_OS_WINDOWS 5 | 6 | void PolyHook2DebugBreak() { 7 | DebugBreak(); 8 | } 9 | 10 | #else 11 | 12 | void PolyHook2DebugBreak() { 13 | __asm__("int3"); 14 | } 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /UnitTests/TestUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "./TestUtils.hpp" 2 | 3 | #include "polyhook2/ErrorLog.hpp" 4 | 5 | #include 6 | 7 | namespace PLH::test { 8 | 9 | void registerTestLogger() { 10 | const auto logger = std::make_shared(); 11 | logger->setLogLevel(PLH::ErrorLevel::INFO); 12 | PLH::Log::registerLogger(logger); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /polyhook2/UID.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by steve on 6/23/17. 3 | // 4 | 5 | #ifndef POLYHOOK_2_UID_HPP 6 | #define POLYHOOK_2_UID_HPP 7 | 8 | #include "polyhook2/PolyHookOs.hpp" 9 | namespace PLH { 10 | class UID { 11 | public: 12 | UID(); 13 | UID(long val); 14 | static std::atomic_long& singleton(); 15 | 16 | long val; 17 | }; 18 | } 19 | #endif //POLYHOOK_2_UID_HPP -------------------------------------------------------------------------------- /polyhook2/Detour/NatDetour.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "polyhook2/PolyHookOs.hpp" 4 | 5 | #ifdef POLYHOOK2_ARCH_X64 6 | #include "polyhook2/Detour/x64Detour.hpp" 7 | #else 8 | #include "polyhook2/Detour/x86Detour.hpp" 9 | #endif 10 | 11 | namespace PLH { 12 | #ifdef POLYHOOK2_ARCH_X64 13 | using NatDetour = x64Detour; 14 | #else 15 | using NatDetour = x86Detour; 16 | #endif 17 | } -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Settings for clang-format 20 2 | # See: https://releases.llvm.org/20.1.0/tools/clang/docs/ClangFormatStyleOptions.html 3 | 4 | BasedOnStyle: LLVM 5 | ColumnLimit: 120 6 | UseTab: Always 7 | IndentWidth: 4 8 | TabWidth: 4 9 | 10 | BinPackArguments: false 11 | BinPackParameters: OnePerLine 12 | AlignAfterOpenBracket: BlockIndent 13 | BracedInitializerIndentWidth: 4 14 | FixNamespaceComments: false 15 | KeepEmptyLines: 16 | AtEndOfFile: true -------------------------------------------------------------------------------- /sources/StackCanary.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/Tests/StackCanary.hpp" 2 | 3 | PLH::StackCanary::StackCanary() { 4 | for (int i = 0; i < 50; i++) { 5 | buf[i] = 0xCE; 6 | } 7 | } 8 | 9 | bool PLH::StackCanary::isStackGood() { 10 | for (int i = 0; i < 50; i++) { 11 | if (buf[i] != 0xCE) 12 | return false; 13 | } 14 | return true; 15 | } 16 | 17 | PLH::StackCanary::~StackCanary() noexcept(false) { 18 | if (!isStackGood()) 19 | throw "Stack corruption detected"; 20 | } -------------------------------------------------------------------------------- /Examples/VCPKG_StaticLink/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.3.0) 2 | cmake_policy(SET CMP0074 NEW) 3 | cmake_policy(SET CMP0091 NEW) 4 | 5 | project(test_polyhook2) 6 | 7 | find_package(PolyHook_2 CONFIG REQUIRED) 8 | 9 | set(CMAKE_CXX_FLAGS "/std:c++latest ${CMAKE_CXX_FLAGS}") 10 | 11 | add_executable(test_polyhook2 main.cpp) 12 | set_property(TARGET test_polyhook2 PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 13 | target_link_libraries(test_polyhook2 PRIVATE PolyHook_2::PolyHook_2) 14 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | -------------------------------------------------------------------------------- /Examples/VCPKG_DynamicLink/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.3.0) 2 | cmake_policy(SET CMP0074 NEW) 3 | if(POLICY CMP0091) 4 | cmake_policy(SET CMP0091 NEW) 5 | endif() 6 | 7 | project(test_polyhook2) 8 | 9 | find_package(PolyHook_2 CONFIG REQUIRED) 10 | 11 | set(CMAKE_CXX_FLAGS "/std:c++latest ${CMAKE_CXX_FLAGS}") 12 | 13 | add_executable(test_polyhook2 main.cpp) 14 | set_property(TARGET test_polyhook2 PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") 15 | target_link_libraries(test_polyhook2 PRIVATE PolyHook_2::PolyHook_2) 16 | -------------------------------------------------------------------------------- /polyhook2/Tests/TestEffectTracker.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYHOOK_2_0_EFFECTSTRACKER_HPP 2 | #define POLYHOOK_2_0_EFFECTSTRACKER_HPP 3 | 4 | #include "../UID.hpp" 5 | 6 | class Effect { 7 | public: 8 | Effect(); 9 | 10 | Effect& operator=(const Effect& rhs); 11 | 12 | void trigger(); 13 | 14 | bool didExecute(); 15 | private: 16 | bool m_executed; 17 | PLH::UID m_uid; 18 | }; 19 | 20 | /**Track if some side effect happened.**/ 21 | class EffectTracker { 22 | public: 23 | void PushEffect(); 24 | Effect PopEffect(); 25 | Effect& PeakEffect(); 26 | private: 27 | std::vector m_effectQ; 28 | }; 29 | #endif -------------------------------------------------------------------------------- /MainTests.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_RUNNER 2 | #include "Catch.hpp" 3 | #include 4 | 5 | #include "polyhook2/ErrorLog.hpp" 6 | int main(int argc, char* const argv[]) { 7 | #if defined(POLYHOOK2_OS_WINDOWS) && !defined(__GNUC__) 8 | _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF ); 9 | #endif 10 | std::cout << "Welcome to PolyHook -By- Stevemk14ebr" << std::endl; 11 | auto logger = std::make_shared(); 12 | logger->setLogLevel(PLH::ErrorLevel::INFO); 13 | PLH::Log::registerLogger(logger); 14 | int result = Catch::Session().run(argc, argv); 15 | 16 | // getchar(); 17 | return result; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /Examples/VCPKG_DynamicLink/main.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/IHook.hpp" 2 | #include "polyhook2/Detour/NatDetour.hpp" 3 | 4 | #include 5 | uint64_t hookPrintfTramp = NULL; 6 | NOINLINE int __cdecl h_hookPrintf(const char* format, ...) { 7 | char buffer[512]; 8 | va_list args; 9 | va_start(args, format); 10 | vsprintf_s(buffer, format, args); 11 | va_end(args); 12 | 13 | return PLH::FnCast(hookPrintfTramp, &printf)("INTERCEPTED YO:%s", buffer); 14 | } 15 | 16 | int main() 17 | { 18 | PLH::NatDetour detour = PLH::NatDetour((uint64_t)&printf, (uint64_t)h_hookPrintf, &hookPrintfTramp); 19 | detour.hook(); 20 | 21 | printf("%s %f\n", "hi", .5f); 22 | detour.unHook(); 23 | return 0; 24 | } -------------------------------------------------------------------------------- /Examples/VCPKG_StaticLink/main.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/IHook.hpp" 2 | #include "polyhook2/Detour/NatDetour.hpp" 3 | 4 | #include 5 | uint64_t hookPrintfTramp = NULL; 6 | NOINLINE int __cdecl h_hookPrintf(const char* format, ...) { 7 | char buffer[512]; 8 | va_list args; 9 | va_start(args, format); 10 | vsprintf_s(buffer, format, args); 11 | va_end(args); 12 | 13 | return PLH::FnCast(hookPrintfTramp, &printf)("INTERCEPTED YO:%s", buffer); 14 | } 15 | 16 | int main() 17 | { 18 | PLH::NatDetour detour = PLH::NatDetour((uint64_t)&printf, (uint64_t)h_hookPrintf, &hookPrintfTramp); 19 | detour.hook(); 20 | 21 | printf("%s %f\n", "hi", .5f); 22 | detour.unHook(); 23 | return 0; 24 | } -------------------------------------------------------------------------------- /polyhook2/EventDispatcher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "polyhook2/PolyHookOs.hpp" 4 | 5 | namespace PLH { 6 | template 7 | class EventDispatcher 8 | { 9 | public: 10 | typedef std::function Event; 11 | void operator+=(const Event& event); 12 | 13 | template 14 | typename Event::result_type Invoke(Args&& ...Params) 15 | { 16 | assert(m_Event); 17 | return m_Event(std::forward(Params)...); 18 | } 19 | 20 | operator bool() const 21 | { 22 | return m_Event != nullptr; 23 | } 24 | private: 25 | Event m_Event; 26 | }; 27 | 28 | template 29 | void EventDispatcher::operator+=(const Event& event) 30 | { 31 | m_Event = event; 32 | } 33 | } -------------------------------------------------------------------------------- /polyhook2/PolyHookOsIncludes.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYHOOK_2_OS_INCLUDES_HPP 2 | #define POLYHOOK_2_OS_INCLUDES_HPP 3 | 4 | // This file is used to not include os specific functions that might break other projects 5 | // You should use it in sources 6 | 7 | #if defined(POLYHOOK2_OS_WINDOWS) 8 | 9 | #ifndef WIN32_LEAN_AND_MEAN 10 | #define WIN32_LEAN_AND_MEAN 11 | #endif 12 | #ifndef NOMINMAX 13 | #define NOMINMAX 14 | #endif 15 | #include 16 | 17 | #elif defined(POLYHOOK2_OS_LINUX) 18 | 19 | #include 20 | #include 21 | 22 | #elif defined(POLYHOOK2_OS_APPLE) 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #endif 30 | 31 | #endif -------------------------------------------------------------------------------- /sources/TestEffectTracker.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/Tests/TestEffectTracker.hpp" 2 | 3 | Effect::Effect() : m_uid(PLH::UID::singleton()) { 4 | m_executed = false; 5 | } 6 | 7 | Effect& Effect::operator=(const Effect& rhs) { 8 | m_uid = rhs.m_uid; 9 | m_executed = rhs.m_executed; 10 | return *this; 11 | } 12 | 13 | void Effect::trigger() { 14 | m_executed = true; 15 | } 16 | 17 | bool Effect::didExecute() { 18 | return m_executed; 19 | } 20 | 21 | void EffectTracker::PushEffect() { 22 | m_effectQ.push_back(Effect()); 23 | } 24 | 25 | Effect EffectTracker::PopEffect() { 26 | Effect effect = m_effectQ.back(); 27 | m_effectQ.pop_back(); 28 | return effect; 29 | } 30 | 31 | Effect& EffectTracker::PeakEffect() { 32 | if (m_effectQ.size() <= 0) { 33 | PolyHook2DebugBreak(); 34 | PushEffect(); 35 | } 36 | 37 | return m_effectQ.back(); 38 | } 39 | -------------------------------------------------------------------------------- /polyhook2/Detour/x86Detour.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by steve on 7/4/17. 3 | // 4 | #pragma once 5 | 6 | #include "polyhook2/PolyHookOs.hpp" 7 | #include "polyhook2/Detour/ADetour.hpp" 8 | #include "polyhook2/Enums.hpp" 9 | #include "polyhook2/Instruction.hpp" 10 | 11 | using namespace std::placeholders; 12 | 13 | namespace PLH { 14 | 15 | class x86Detour : public Detour { 16 | public: 17 | x86Detour(uint64_t fnAddress, uint64_t fnCallback, uint64_t* userTrampVar); 18 | 19 | virtual ~x86Detour() = default; 20 | 21 | virtual bool hook() override; 22 | 23 | Mode getArchType() const override; 24 | 25 | protected: 26 | bool fixSpecialCases(insts_t& prologue); 27 | bool fixCallRoutineReturningSP(Instruction& callInst, const insts_t& routine); 28 | bool fixCallInlineReturningSP(Instruction& callInst); 29 | 30 | bool makeTrampoline(insts_t& prologue, insts_t& trampolineOut); 31 | }; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /polyhook_2-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | set(POLYHOOK_BUILD_SHARED_LIBS @POLYHOOK_BUILD_SHARED_LIBS@) 4 | set(POLYHOOK_BUILD_DLL @POLYHOOK_BUILD_SHARED_LIBS@) 5 | set(POLYHOOK_BUILD_STATIC_RUNTIME @POLYHOOK_BUILD_STATIC_RUNTIME@) 6 | 7 | set(POLYHOOK_FEATURE_EXCEPTION @POLYHOOK_FEATURE_EXCEPTION@) 8 | set(POLYHOOK_FEATURE_DETOURS @POLYHOOK_FEATURE_DETOURS@) 9 | set(POLYHOOK_FEATURE_INLINENTD @POLYHOOK_FEATURE_INLINENTD@) 10 | set(POLYHOOK_FEATURE_PE @POLYHOOK_FEATURE_PE@) 11 | set(POLYHOOK_FEATURE_VIRTUALS @POLYHOOK_FEATURE_VIRTUALS@) 12 | 13 | include(CMakeFindDependencyMacro) 14 | find_dependency(Zydis) 15 | if(POLYHOOK_FEATURE_DETOURS) 16 | find_dependency(asmjit) 17 | endif() 18 | if(POLYHOOK_FEATURE_INLINENTD) 19 | find_dependency(asmtk) 20 | endif() 21 | 22 | get_filename_component(POLYHOOK_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 23 | include("${POLYHOOK_CMAKE_DIR}/PolyHook_2-targets.cmake") 24 | -------------------------------------------------------------------------------- /polyhook2/Exceptions/BreakPointHook.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYHOOK_2_0_BPHOOK_HPP 2 | #define POLYHOOK_2_0_BPHOOK_HPP 3 | 4 | #include "polyhook2/PolyHookOs.hpp" 5 | #include "polyhook2/Exceptions/AVehHook.hpp" 6 | #include "polyhook2/Misc.hpp" 7 | 8 | namespace PLH { 9 | 10 | class BreakPointHook : public AVehHook { 11 | public: 12 | BreakPointHook(const uint64_t fnAddress, const uint64_t fnCallback); 13 | BreakPointHook(const char* fnAddress, const char* fnCallback); 14 | ~BreakPointHook() { 15 | m_impls.erase(AVehHookImpEntry(m_fnAddress, this)); 16 | if (m_hooked) { 17 | unHook(); 18 | } 19 | } 20 | 21 | virtual bool hook() override; 22 | virtual bool unHook() override; 23 | auto getProtectionObject() { 24 | return finally([&] () { 25 | hook(); 26 | }); 27 | } 28 | protected: 29 | uint64_t m_fnCallback; 30 | uint64_t m_fnAddress; 31 | uint8_t m_origByte; 32 | 33 | LONG OnException(EXCEPTION_POINTERS* ExceptionInfo) override; 34 | }; 35 | } 36 | #endif -------------------------------------------------------------------------------- /polyhook2/Exceptions/HWBreakPointHook.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYHOOK_2_0_HWBPHOOK_HPP 2 | #define POLYHOOK_2_0_HWBPHOOK_HPP 3 | 4 | #include "polyhook2/PolyHookOs.hpp" 5 | #include "polyhook2/Exceptions/AVehHook.hpp" 6 | #include "polyhook2/Misc.hpp" 7 | 8 | namespace PLH { 9 | 10 | class HWBreakPointHook : public AVehHook { 11 | public: 12 | HWBreakPointHook(const uint64_t fnAddress, const uint64_t fnCallback, HANDLE hThread); 13 | HWBreakPointHook(const char* fnAddress, const char* fnCallback, HANDLE hThread); 14 | ~HWBreakPointHook() { 15 | m_impls.erase(AVehHookImpEntry(m_fnAddress, this)); 16 | if (m_hooked) { 17 | unHook(); 18 | } 19 | } 20 | 21 | virtual bool hook() override; 22 | virtual bool unHook() override; 23 | auto getProtectionObject() { 24 | return finally([&] () { 25 | hook(); 26 | }); 27 | } 28 | protected: 29 | uint64_t m_fnCallback; 30 | uint64_t m_fnAddress; 31 | uint8_t m_regIdx; 32 | 33 | HANDLE m_hThread; 34 | 35 | LONG OnException(EXCEPTION_POINTERS* ExceptionInfo) override; 36 | }; 37 | } 38 | 39 | #endif -------------------------------------------------------------------------------- /polyhook2/Virtuals/VFuncSwapHook.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYHOOK_2_0_VFUNCSWAPHOOK_HPP 2 | #define POLYHOOK_2_0_VFUNCSWAPHOOK_HPP 3 | 4 | #include "polyhook2/PolyHookOs.hpp" 5 | #include "polyhook2/IHook.hpp" 6 | #include "polyhook2/MemProtector.hpp" 7 | #include "polyhook2/Misc.hpp" 8 | 9 | namespace PLH { 10 | typedef std::map VFuncMap; 11 | 12 | 13 | class VFuncSwapHook : public PLH::IHook { 14 | public: 15 | VFuncSwapHook(const uint64_t Class, const VFuncMap& redirectMap, VFuncMap* origVFuncs); 16 | VFuncSwapHook(const char* Class, const VFuncMap& redirectMap, VFuncMap* origVFuncs); 17 | virtual ~VFuncSwapHook() { 18 | if (m_hooked) { 19 | unHook(); 20 | } 21 | } 22 | 23 | virtual bool hook() override; 24 | virtual bool unHook() override; 25 | virtual HookType getType() const override { 26 | return HookType::VTableSwap; 27 | } 28 | protected: 29 | uint16_t countVFuncs(); 30 | uint64_t m_class; 31 | uintptr_t* m_vtable; 32 | 33 | uint16_t m_vFuncCount; 34 | 35 | // index -> ptr val 36 | VFuncMap m_redirectMap; 37 | VFuncMap* m_userOrigMap; 38 | }; 39 | } 40 | #endif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Stephen Eckels 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 | -------------------------------------------------------------------------------- /polyhook2/Virtuals/VTableSwapHook.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYHOOK_2_0_VTBLSWAPHOOK_HPP 2 | #define POLYHOOK_2_0_VTBLSWAPHOOK_HPP 3 | 4 | #include "polyhook2/PolyHookOs.hpp" 5 | #include "polyhook2/IHook.hpp" 6 | #include "polyhook2/MemProtector.hpp" 7 | #include "polyhook2/Misc.hpp" 8 | 9 | namespace PLH { 10 | 11 | typedef std::map VFuncMap; 12 | 13 | class VTableSwapHook : public PLH::IHook { 14 | public: 15 | VTableSwapHook(const uint64_t Class, VFuncMap* origVFuncs); 16 | VTableSwapHook(const uint64_t Class, const VFuncMap& redirectMap, VFuncMap* origVFuncs); 17 | VTableSwapHook(const char* Class, const VFuncMap& redirectMap, VFuncMap* origVFuncs); 18 | 19 | virtual ~VTableSwapHook() { 20 | if (m_hooked) { 21 | unHook(); 22 | } 23 | } 24 | 25 | virtual bool hook() override; 26 | virtual bool unHook() override; 27 | virtual HookType getType() const override { 28 | return HookType::VTableSwap; 29 | } 30 | protected: 31 | uint16_t countVFuncs(); 32 | 33 | std::unique_ptr m_newVtable; 34 | uintptr_t* m_origVtable; 35 | 36 | uint64_t m_class; 37 | 38 | uint16_t m_vFuncCount; 39 | 40 | // index -> ptr val 41 | VFuncMap m_redirectMap; 42 | VFuncMap* m_userOrigMap; 43 | }; 44 | 45 | } 46 | 47 | #endif -------------------------------------------------------------------------------- /polyhook2/ErrorLog.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYHOOK_2_0_ERRORLOG_HPP 2 | #define POLYHOOK_2_0_ERRORLOG_HPP 3 | 4 | #include "polyhook2/PolyHookOs.hpp" 5 | #include "polyhook2/Enums.hpp" 6 | 7 | namespace PLH { 8 | 9 | // abstract base class for logging, clients should subclass this to intercept log messages 10 | class Logger 11 | { 12 | public: 13 | // copy 14 | virtual void log(const std::string& msg, ErrorLevel level) = 0; 15 | virtual ~Logger() {}; 16 | }; 17 | 18 | // class for registering client loggers 19 | class Log 20 | { 21 | private: 22 | static std::shared_ptr m_logger; 23 | public: 24 | static void registerLogger(std::shared_ptr logger); 25 | static void log(std::string msg, ErrorLevel level); 26 | }; 27 | 28 | // simple logger implementation 29 | 30 | struct Error { 31 | std::string msg; 32 | ErrorLevel lvl; 33 | }; 34 | 35 | class ErrorLog : public Logger { 36 | public: 37 | void setLogLevel(ErrorLevel level); 38 | 39 | //copy 40 | void log(const std::string& msg, ErrorLevel level) override; 41 | 42 | // copy 43 | void push(const Error& err); 44 | 45 | Error pop(); 46 | static ErrorLog& singleton(); 47 | private: 48 | std::vector m_log; 49 | ErrorLevel m_logLevel = ErrorLevel::INFO; 50 | }; 51 | 52 | } 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /sources/ErrorLog.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/ErrorLog.hpp" 2 | 3 | std::shared_ptr PLH::Log::m_logger = nullptr; 4 | 5 | void PLH::Log::registerLogger(std::shared_ptr logger) { 6 | m_logger = logger; 7 | } 8 | 9 | void PLH::Log::log(std::string msg, ErrorLevel level) { 10 | if (m_logger) { 11 | m_logger->log(std::move(msg), level); 12 | } 13 | } 14 | 15 | void PLH::ErrorLog::setLogLevel(PLH::ErrorLevel level) { 16 | m_logLevel = level; 17 | } 18 | 19 | void PLH::ErrorLog::log(const std::string& msg, ErrorLevel level) 20 | { 21 | push({ msg, level }); 22 | } 23 | 24 | void PLH::ErrorLog::push(const PLH::Error& err) { 25 | if (err.lvl >= m_logLevel) { 26 | switch (err.lvl) { 27 | case ErrorLevel::INFO: 28 | std::cout << "[+] Info: " << err.msg << std::endl; 29 | break; 30 | case ErrorLevel::WARN: 31 | std::cout << "[!] Warn: " << err.msg << std::endl; 32 | break; 33 | case ErrorLevel::SEV: 34 | std::cout << "[!] Error: " << err.msg << std::endl; 35 | break; 36 | default: 37 | std::cout << "Unsupported error message logged " << err.msg << std::endl; 38 | } 39 | } 40 | 41 | m_log.push_back(err); 42 | } 43 | 44 | PLH::Error PLH::ErrorLog::pop() { 45 | Error err{}; 46 | if (!m_log.empty()) { 47 | err = m_log.back(); 48 | m_log.pop_back(); 49 | } 50 | return err; 51 | } 52 | 53 | PLH::ErrorLog& PLH::ErrorLog::singleton() { 54 | static ErrorLog log; 55 | return log; 56 | } 57 | -------------------------------------------------------------------------------- /polyhook2/PE/IatHook.hpp: -------------------------------------------------------------------------------- 1 | //https://github.com/odzhan/shellcode/blob/master/os/win/getapi/dynamic/getapi.c 2 | //https://modexp.wordpress.com/2017/01/15/shellcode-resolving-api-addresses/ 3 | 4 | #include "polyhook2/PolyHookOs.hpp" 5 | #include "polyhook2/ErrorLog.hpp" 6 | #include "polyhook2/IHook.hpp" 7 | #include "polyhook2/MemProtector.hpp" 8 | #include "polyhook2/Misc.hpp" 9 | #include "polyhook2/PE/PEB.hpp" 10 | 11 | #define RVA2VA(type, base, rva) (type)((ULONG_PTR) base + rva) 12 | 13 | namespace PLH { 14 | class IatHook : public IHook { 15 | public: 16 | IatHook(const std::string& dllName, const std::string& apiName, const char* fnCallback, uint64_t* userOrigVar, const std::wstring& moduleName); 17 | IatHook(const std::string& dllName, const std::string& apiName, const uint64_t fnCallback, uint64_t* userOrigVar, const std::wstring& moduleName); 18 | virtual ~IatHook() { 19 | if (m_hooked) { 20 | unHook(); 21 | } 22 | } 23 | 24 | virtual bool hook() override; 25 | virtual bool unHook() override; 26 | virtual HookType getType() const override { 27 | return HookType::IAT; 28 | } 29 | protected: 30 | IMAGE_THUNK_DATA* FindIatThunk(const std::string& dllName, const std::string& apiName, const std::wstring& moduleName = L""); 31 | IMAGE_THUNK_DATA* FindIatThunkInModule(void* moduleBase, const std::string& dllName, const std::string& apiName); 32 | 33 | std::string m_dllName; 34 | std::string m_apiName; 35 | std::wstring m_moduleName; 36 | 37 | uint64_t m_fnCallback; 38 | uint64_t m_origFunc; 39 | uint64_t* m_userOrigVar; 40 | }; 41 | } -------------------------------------------------------------------------------- /polyhook2/MemProtector.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by steve on 7/10/17. 3 | // 4 | 5 | #ifndef POLYHOOK_2_MEMORYPROTECTOR_HPP 6 | #define POLYHOOK_2_MEMORYPROTECTOR_HPP 7 | 8 | #include "polyhook2/PolyHookOs.hpp" 9 | #include "polyhook2/MemAccessor.hpp" 10 | #include "polyhook2/Enums.hpp" 11 | 12 | std::ostream& operator<<(std::ostream& os, const PLH::ProtFlag v); 13 | 14 | // prefer enum class over enum 15 | #pragma warning( disable : 26812) 16 | 17 | namespace PLH { 18 | int TranslateProtection(const PLH::ProtFlag flags); 19 | ProtFlag TranslateProtection(const int prot); 20 | 21 | class MemoryProtector { 22 | public: 23 | MemoryProtector(const uint64_t address, const uint64_t length, const PLH::ProtFlag prot, MemAccessor& accessor, bool unsetOnDestroy = true) : m_accessor(accessor) { 24 | m_address = address; 25 | m_length = length; 26 | unsetLater = unsetOnDestroy; 27 | 28 | m_origProtection = PLH::ProtFlag::UNSET; 29 | m_origProtection = m_accessor.mem_protect(address, length, prot, status); 30 | } 31 | 32 | PLH::ProtFlag originalProt() { 33 | return m_origProtection; 34 | } 35 | 36 | bool isGood() { 37 | return status; 38 | } 39 | 40 | ~MemoryProtector() { 41 | if (m_origProtection == PLH::ProtFlag::UNSET || !unsetLater) 42 | return; 43 | 44 | m_accessor.mem_protect(m_address, m_length, m_origProtection, status); 45 | } 46 | private: 47 | PLH::ProtFlag m_origProtection; 48 | MemAccessor& m_accessor; 49 | 50 | uint64_t m_address; 51 | uint64_t m_length; 52 | bool status; 53 | bool unsetLater; 54 | }; 55 | } 56 | #endif //POLYHOOK_2_MEMORYPROTECTOR_HPP 57 | -------------------------------------------------------------------------------- /UnitTests/windows/TestIatHook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "polyhook2/PE/IatHook.hpp" 3 | #include "polyhook2/Tests/TestEffectTracker.hpp" 4 | #include "polyhook2/Tests/StackCanary.hpp" 5 | #include "polyhook2/PolyHookOsIncludes.hpp" 6 | 7 | EffectTracker iatEffectTracker; 8 | 9 | typedef DWORD(__stdcall* tGetCurrentThreadId)(); 10 | uint64_t oGetCurrentThreadID; 11 | 12 | NOINLINE DWORD __stdcall hkGetCurrentThreadId() { 13 | iatEffectTracker.PeakEffect().trigger(); 14 | return ((tGetCurrentThreadId)oGetCurrentThreadID)(); 15 | } 16 | 17 | TEST_CASE("Iat Hook Tests", "[IatHook]") { 18 | SECTION("Verify api thunk is found and hooked") { 19 | PLH::StackCanary canary; 20 | volatile DWORD thrdId2 = GetCurrentThreadId(); 21 | UNREFERENCED_PARAMETER(thrdId2); 22 | PLH::IatHook hook("kernel32.dll", "GetCurrentThreadId", (char*)&hkGetCurrentThreadId, (uint64_t*)&oGetCurrentThreadID, L""); 23 | REQUIRE(hook.hook()); 24 | 25 | iatEffectTracker.PushEffect(); 26 | REQUIRE(canary.isStackGood()); 27 | volatile DWORD thrdId = GetCurrentThreadId(); 28 | thrdId++; 29 | REQUIRE(iatEffectTracker.PopEffect().didExecute()); 30 | REQUIRE(hook.unHook()); 31 | } 32 | 33 | SECTION("Verify api thunk is found and hooked when module explicitly named") { 34 | PLH::StackCanary canary; 35 | PLH::IatHook hook("kernel32.dll", "GetCurrentThreadId", (char*)&hkGetCurrentThreadId, (uint64_t*)&oGetCurrentThreadID, L"polyhook_2.exe"); 36 | REQUIRE(hook.hook()); 37 | 38 | iatEffectTracker.PushEffect(); 39 | volatile DWORD thrdId = GetCurrentThreadId(); 40 | thrdId++; 41 | REQUIRE(hook.unHook()); 42 | } 43 | } -------------------------------------------------------------------------------- /UnitTests/linux/TestDetourTranslationx64.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "polyhook2/Detour/x64Detour.hpp" 7 | #include "polyhook2/PolyHookOsIncludes.hpp" 8 | #include "polyhook2/Tests/StackCanary.hpp" 9 | #include "polyhook2/Tests/TestEffectTracker.hpp" 10 | 11 | #include "../TestUtils.hpp" 12 | 13 | namespace { 14 | EffectTracker effects; 15 | } 16 | 17 | // TODO: Translation + INPLACE scheme 18 | 19 | PLH_TEST_DETOUR_CALLBACK(dlmopen, { 20 | printf("Hooked dlmopen\n"); 21 | }); 22 | 23 | TEST_CASE("Testing Detours with Translations", "[Translation][ADetour]") { 24 | PLH::test::registerTestLogger(); 25 | 26 | // dlmopen may or may not have instructions requiring translations. 27 | // Hence, this test was disabled. We need to construct reliable synthetic tests instead. 28 | #if 0 29 | SECTION("dlmopen (INPLACE)") { 30 | PLH::StackCanary canary; 31 | 32 | const auto* handleBefore = dlmopen(LM_ID_BASE, LIBM_SO, RTLD_NOW); 33 | 34 | PLH::x64Detour detour((uint64_t)dlmopen, (uint64_t)dlmopen_hooked, &dlmopen_trmp); 35 | // Only INPLACE creates conditions for translation, since 36 | // trampoline will be close to 0x0, where as 37 | // dlmopen will be close to 0x00007F__________ 38 | detour.setDetourScheme(PLH::x64Detour::detour_scheme_t::INPLACE); 39 | REQUIRE(detour.hook()); 40 | 41 | effects.PushEffect(); 42 | const auto* handleAfter = dlmopen(LM_ID_BASE, LIBM_SO, RTLD_NOW); 43 | 44 | REQUIRE(detour.hasDiagnostic(PLH::Diagnostic::TranslatedInstructions)); 45 | REQUIRE(effects.PopEffect().didExecute()); 46 | REQUIRE(handleAfter == handleBefore); 47 | 48 | REQUIRE(detour.unHook()); 49 | } 50 | #endif 51 | } 52 | -------------------------------------------------------------------------------- /sources/BreakPointHook.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/Exceptions/BreakPointHook.hpp" 2 | 3 | PLH::BreakPointHook::BreakPointHook(const uint64_t fnAddress, const uint64_t fnCallback) : AVehHook() { 4 | m_fnCallback = fnCallback; 5 | m_fnAddress = fnAddress; 6 | 7 | auto entry = AVehHookImpEntry(fnAddress, this); 8 | assert(m_impls.find(entry) == m_impls.end()); 9 | m_impls.insert(entry); 10 | } 11 | 12 | PLH::BreakPointHook::BreakPointHook(const char* fnAddress, const char* fnCallback) : AVehHook() { 13 | m_fnCallback = (uint64_t)fnCallback; 14 | m_fnAddress = (uint64_t)fnAddress; 15 | 16 | auto entry = AVehHookImpEntry((uint64_t)fnAddress, this); 17 | assert(m_impls.find(entry) == m_impls.end()); 18 | m_impls.insert(entry); 19 | } 20 | 21 | bool PLH::BreakPointHook::hook() { 22 | MemoryProtector prot(m_fnAddress, 1, ProtFlag::R | ProtFlag::W | ProtFlag::X, *this); 23 | m_origByte = *(uint8_t*)m_fnAddress; 24 | *(uint8_t*)m_fnAddress = 0xCC; 25 | m_hooked = true; 26 | return true; 27 | } 28 | 29 | bool PLH::BreakPointHook::unHook() { 30 | assert(m_hooked); 31 | if (!m_hooked) { 32 | Log::log("BPHook unhook failed: no hook present", ErrorLevel::SEV); 33 | return false; 34 | } 35 | 36 | MemoryProtector prot(m_fnAddress, 1, ProtFlag::R | ProtFlag::W | ProtFlag::X, *this); 37 | *(uint8_t*)m_fnAddress = m_origByte; 38 | m_hooked = false; 39 | return true; 40 | } 41 | 42 | LONG PLH::BreakPointHook::OnException(EXCEPTION_POINTERS* ExceptionInfo) { 43 | if (ExceptionInfo->ExceptionRecord->ExceptionCode != EXCEPTION_BREAKPOINT) 44 | return EXCEPTION_CONTINUE_SEARCH; 45 | 46 | // restored via getProtectionObject() 47 | unHook(); 48 | ExceptionInfo->ContextRecord->XIP = (decltype(ExceptionInfo->ContextRecord->XIP))m_fnCallback; 49 | return EXCEPTION_CONTINUE_EXECUTION; 50 | } 51 | 52 | -------------------------------------------------------------------------------- /polyhook2/MemAccessor.hpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef POLYHOOK_2_MEMORYACCESSOR_HPP 3 | #define POLYHOOK_2_MEMORYACCESSOR_HPP 4 | #include "polyhook2/PolyHookOs.hpp" 5 | #include "polyhook2/Enums.hpp" 6 | 7 | #define MEMORY_ROUND(_numToRound_, _multiple_) \ 8 | (_numToRound_ & (((size_t)-1) ^ (_multiple_ - 1))) 9 | 10 | // Round _numToRound_ to the next higher _multiple_ 11 | #define MEMORY_ROUND_UP(_numToRound_, _multiple_) \ 12 | ((_numToRound_ + (_multiple_ - 1)) & (((size_t)-1) ^ (_multiple_ - 1))) 13 | 14 | namespace PLH { 15 | /** 16 | Overriding these routines can allow cross-process/cross-arch hooks 17 | **/ 18 | class MemAccessor { 19 | public: 20 | virtual ~MemAccessor() = default; 21 | 22 | /** 23 | Defines a memory read/write routine that may fail ungracefully. It's expected 24 | this library will only ever use this routine in cases that are expected to succeed. 25 | **/ 26 | virtual bool mem_copy(uint64_t dest, uint64_t src, uint64_t size) const; 27 | 28 | /** 29 | Defines a memory write routine that will not throw exceptions, and can handle potential 30 | writes to NO_ACCESS or otherwise innaccessible memory pages. Defaults to writeprocessmemory. 31 | Must fail gracefully 32 | **/ 33 | virtual bool safe_mem_write(uint64_t dest, uint64_t src, uint64_t size, size_t& written) const noexcept; 34 | 35 | /** 36 | Defines a memory read routine that will not throw exceptions, and can handle potential 37 | reads from NO_ACCESS or otherwise innaccessible memory pages. Defaults to readprocessmemory. 38 | Must fail gracefully 39 | **/ 40 | virtual bool safe_mem_read(uint64_t src, uint64_t dest, size_t size, size_t& read) const noexcept; 41 | 42 | virtual PLH::ProtFlag mem_protect(uint64_t dest, uint64_t size, PLH::ProtFlag newProtection, bool& status) const; 43 | }; 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /UnitTests/windows/TestBreakpointHook.cpp: -------------------------------------------------------------------------------- 1 | //// 2 | //// Created by steve on 7/9/18. 3 | //// 4 | //#include 5 | // 6 | //#include 7 | // 8 | //#include "polyhook2/Exceptions/BreakPointHook.hpp" 9 | //#include "polyhook2/tests/TestEffectTracker.hpp" 10 | // 11 | //EffectTracker effects2; 12 | // 13 | //NOINLINE int hookMe() { 14 | // volatile int i = 0; 15 | // i += 1; 16 | // i += 2; 17 | // return i; 18 | //} 19 | // 20 | //std::shared_ptr bpHook; // must be ptr because we need to call getProtectionObject 21 | //NOINLINE int hookMeCallback() { 22 | // auto protObj = bpHook->getProtectionObject(); 23 | // volatile int i = 0; 24 | // i += 1; 25 | // 26 | // effects2.PeakEffect().trigger(); 27 | // return hookMe(); // just call original yourself now 28 | //} 29 | // 30 | //TEST_CASE("Testing Software Breakpoint", "[AVehHook],[BreakpointHook]") { 31 | // SECTION("Verify callback is executed") { 32 | // bpHook = std::make_shared((char*)&hookMe, (char*)&hookMeCallback); 33 | // REQUIRE(bpHook->hook() == true); 34 | // 35 | // effects2.PushEffect(); 36 | // 37 | // REQUIRE(hookMe() == 3); 38 | // REQUIRE(effects2.PopEffect().didExecute()); 39 | // bpHook->unHook(); 40 | // bpHook.reset(); 41 | // } 42 | // 43 | // SECTION("Verify multiple calls in a row reprotect") { 44 | // bpHook = std::make_shared((char*)&hookMe, (char*)&hookMeCallback); 45 | // REQUIRE(bpHook->hook() == true); 46 | // 47 | // effects2.PushEffect(); 48 | // REQUIRE(hookMe() == 3); 49 | // REQUIRE(effects2.PopEffect().didExecute()); 50 | // 51 | // effects2.PushEffect(); 52 | // REQUIRE(hookMe() == 3); 53 | // REQUIRE(effects2.PopEffect().didExecute()); 54 | // 55 | // effects2.PushEffect(); 56 | // REQUIRE(hookMe() == 3); 57 | // REQUIRE(effects2.PopEffect().didExecute()); 58 | // bpHook->unHook(); 59 | // } 60 | //} -------------------------------------------------------------------------------- /polyhook2/RangeAllocator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYHOOK_2_PAGEALLOCATOR_HPP 2 | #define POLYHOOK_2_PAGEALLOCATOR_HPP 3 | 4 | #include "polyhook2/PolyHookOs.hpp" 5 | #include "polyhook2/Misc.hpp" 6 | #include "polyhook2/FBAllocator.hpp" 7 | 8 | namespace PLH { 9 | 10 | // wrapper over fb_allocator in C, with heap backing from VirtualAlloc2 to enforce range 11 | class FBAllocator 12 | { 13 | public: 14 | FBAllocator(uint64_t min, uint64_t max, uint8_t blockSize, uint8_t blockCount); 15 | ~FBAllocator(); 16 | bool initialize(); 17 | 18 | char* allocate(); 19 | 20 | char* callocate(uint8_t num); 21 | 22 | void deallocate(char* mem); 23 | 24 | bool inRange(uint64_t addr); 25 | 26 | bool intersectsRange(uint64_t min, uint64_t max); 27 | 28 | // if a range intersections, by what % of the given range is the overlap 29 | uint8_t intersectionLoadFactor(uint64_t min, uint64_t max); 30 | private: 31 | bool m_alloc2Supported; 32 | uint8_t m_usedBlocks; 33 | uint8_t m_maxBlocks; 34 | uint8_t m_blockSize; 35 | uint64_t m_min; 36 | uint64_t m_max; 37 | uint64_t m_dataPool; 38 | 39 | ALLOC_Allocator* m_allocator; 40 | ALLOC_HANDLE m_hAllocator; 41 | }; 42 | 43 | class RangeAllocator 44 | { 45 | public: 46 | RangeAllocator(uint8_t blockSize, uint8_t blockCount); 47 | ~RangeAllocator() = default; 48 | 49 | char* allocate(uint64_t min, uint64_t max); 50 | void deallocate(uint64_t addr); 51 | private: 52 | std::shared_ptr findOrInsertAllocator(uint64_t min, uint64_t max); 53 | 54 | uint8_t m_maxBlocks; 55 | uint8_t m_blockSize; 56 | std::mutex m_mutex; 57 | std::vector> m_allocators; 58 | std::unordered_map> m_allocMap; 59 | }; 60 | 61 | } 62 | 63 | #endif -------------------------------------------------------------------------------- /UnitTests/windows/TestHWBreakpointHook.cpp: -------------------------------------------------------------------------------- 1 | //// 2 | //// Created by steve on 7/9/18. 3 | //// 4 | //#include 5 | // 6 | //#include 7 | // 8 | //#include "polyhook2/Exceptions/HWBreakPointHook.hpp" 9 | //#include "polyhook2/tests/TestEffectTracker.hpp" 10 | // 11 | //EffectTracker effects3; 12 | // 13 | //NOINLINE int hookMeHWBP() { 14 | // volatile int i = 0; 15 | // i += 1; 16 | // i += 2; 17 | // return i; 18 | //} 19 | // 20 | //std::shared_ptr hwBpHook; // must be ptr because we need to call getProtectionObject 21 | //NOINLINE int hookMeCallbackHWBP() { 22 | // auto protObj = hwBpHook->getProtectionObject(); 23 | // volatile int i = 0; 24 | // i += 1; 25 | // 26 | // effects3.PeakEffect().trigger(); 27 | // return hookMeHWBP(); // just call original yourself now 28 | //} 29 | // 30 | //TEST_CASE("Testing Hardware Breakpoints", "[AVehHook],[HWBreakPointHook]") { 31 | // SECTION("Verify callback is executed") { 32 | // hwBpHook = std::make_shared((char*)&hookMeHWBP, (char*)&hookMeCallbackHWBP); 33 | // REQUIRE(hwBpHook->hook() == true); 34 | // 35 | // effects3.PushEffect(); 36 | // REQUIRE(hookMeHWBP() == 3); 37 | // REQUIRE(effects3.PopEffect().didExecute()); 38 | // hwBpHook->unHook(); 39 | // hwBpHook.reset(); 40 | // } 41 | // 42 | // SECTION("Verify multiple calls in a row reprotect") { 43 | // hwBpHook = std::make_shared((char*)&hookMeHWBP, (char*)&hookMeCallbackHWBP); 44 | // REQUIRE(hwBpHook->hook() == true); 45 | // 46 | // effects3.PushEffect(); 47 | // REQUIRE(hookMeHWBP() == 3); 48 | // REQUIRE(effects3.PopEffect().didExecute()); 49 | // 50 | // effects3.PushEffect(); 51 | // REQUIRE(hookMeHWBP() == 3); 52 | // REQUIRE(effects3.PopEffect().didExecute()); 53 | // 54 | // effects3.PushEffect(); 55 | // REQUIRE(hookMeHWBP() == 3); 56 | // REQUIRE(effects3.PopEffect().didExecute()); 57 | // hwBpHook->unHook(); 58 | // } 59 | //} -------------------------------------------------------------------------------- /polyhook2/PE/EatHook.hpp: -------------------------------------------------------------------------------- 1 | //https://github.com/odzhan/shellcode/blob/master/os/win/getapi/dynamic/getapi.c 2 | //https://modexp.wordpress.com/2017/01/15/shellcode-resolving-api-addresses/ 3 | 4 | #include "polyhook2/PolyHookOs.hpp" 5 | #include "polyhook2/ErrorLog.hpp" 6 | #include "polyhook2/IHook.hpp" 7 | #include "polyhook2/MemProtector.hpp" 8 | #include "polyhook2/Misc.hpp" 9 | #include "polyhook2/PE/PEB.hpp" 10 | #include "polyhook2/ZydisDisassembler.hpp" 11 | #include "polyhook2/RangeAllocator.hpp" 12 | 13 | #define RVA2VA(type, base, rva) (type)((ULONG_PTR) base + rva) 14 | 15 | namespace PLH { 16 | class EatHook : public IHook { 17 | public: 18 | EatHook(const std::string& apiName, const std::wstring& moduleName, const char* fnCallback, uint64_t* userOrigVar); 19 | EatHook(const std::string& apiName, const std::wstring& moduleName, const uint64_t fnCallback, uint64_t* userOrigVar); 20 | EatHook(const std::string& apiName, const HMODULE moduleHandle, const char* fnCallback, uint64_t* userOrigVar); 21 | EatHook(const std::string& apiName, const HMODULE moduleHandle, const uint64_t fnCallback, uint64_t* userOrigVar); 22 | virtual ~EatHook() 23 | { 24 | if (m_trampoline) { 25 | m_allocator.deallocate(m_trampoline); 26 | m_trampoline = 0; 27 | } 28 | } 29 | 30 | virtual bool hook() override; 31 | virtual bool unHook() override; 32 | 33 | virtual HookType getType() const override { 34 | return HookType::EAT; 35 | } 36 | protected: 37 | EatHook(std::string apiName, std::wstring moduleName, HMODULE moduleHandle, uint64_t fnCallback, uint64_t* userOrigVar); 38 | 39 | uint32_t* FindEatFunction(); 40 | uint32_t* FindEatFunctionInModule() const; 41 | uint64_t FindModule(); 42 | 43 | const uint16_t m_trampolineSize = 32; 44 | 45 | std::wstring m_moduleName; 46 | std::string m_apiName; 47 | 48 | uint64_t m_fnCallback; 49 | uint64_t m_origFunc; 50 | uint64_t* m_userOrigVar; 51 | 52 | // only used if EAT offset points >= 2GB 53 | RangeAllocator m_allocator; 54 | uint64_t m_trampoline; 55 | 56 | uint64_t m_moduleBase; 57 | }; 58 | } -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x86-Debug", 5 | "generator": "Visual Studio 17 2022", 6 | "configurationType": "Debug", 7 | "buildRoot": "${projectDir}\\build32", 8 | "cmakeCommandArgs": "", 9 | "buildCommandArgs": "-m -v:minimal", 10 | "ctestCommandArgs": "", 11 | "inheritEnvironments": [] 12 | }, 13 | { 14 | "name": "x86-Release", 15 | "generator": "Visual Studio 17 2022", 16 | "configurationType": "Release", 17 | "buildRoot": "${projectDir}\\build32", 18 | "cmakeCommandArgs": "", 19 | "buildCommandArgs": "-m -v:minimal", 20 | "ctestCommandArgs": "", 21 | "inheritEnvironments": [] 22 | }, 23 | { 24 | "name": "x86-ReleaseWithDebInfo", 25 | "generator": "Visual Studio 17 2022", 26 | "configurationType": "RelWithDebInfo", 27 | "buildRoot": "${projectDir}\\build32", 28 | "cmakeCommandArgs": "", 29 | "buildCommandArgs": "-m -v:minimal", 30 | "ctestCommandArgs": "", 31 | "inheritEnvironments": [] 32 | }, 33 | { 34 | "name": "x64-Debug", 35 | "generator": "Visual Studio 17 2022 Win64", 36 | "configurationType": "Debug", 37 | "buildRoot": "${projectDir}\\build64", 38 | "cmakeCommandArgs": "", 39 | "buildCommandArgs": "-m -v:minimal", 40 | "ctestCommandArgs": "", 41 | "inheritEnvironments": [] 42 | }, 43 | { 44 | "name": "x64-Release", 45 | "generator": "Visual Studio 17 2022 Win64", 46 | "configurationType": "Release", 47 | "buildRoot": "${projectDir}\\build64", 48 | "cmakeCommandArgs": "", 49 | "buildCommandArgs": "-m -v:minimal", 50 | "ctestCommandArgs": "", 51 | "inheritEnvironments": [] 52 | }, 53 | { 54 | "name": "x64-ReleaseWithDebInfo", 55 | "generator": "Visual Studio 17 2022 Win64", 56 | "configurationType": "RelWithDebInfo", 57 | "buildRoot": "${projectDir}\\build64", 58 | "cmakeCommandArgs": "", 59 | "buildCommandArgs": "-m -v:minimal", 60 | "ctestCommandArgs": "", 61 | "inheritEnvironments": [] 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /UnitTests/windows/TestVTableSwapHook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "polyhook2/Virtuals/VTableSwapHook.hpp" 6 | #include "polyhook2/Tests/StackCanary.hpp" 7 | #include "polyhook2/Tests/TestEffectTracker.hpp" 8 | 9 | EffectTracker vTblSwapEffects; 10 | 11 | class VirtualTest { 12 | public: 13 | virtual ~VirtualTest() {} 14 | 15 | virtual int __stdcall NoParamVirt() { 16 | return 4; 17 | } 18 | 19 | virtual int __stdcall NoParamVirt2() { 20 | return 7; 21 | } 22 | }; 23 | 24 | #pragma warning(disable: 4100) 25 | PLH::VFuncMap origVFuncs; 26 | HOOK_CALLBACK(&VirtualTest::NoParamVirt, hkVirtNoParams, { 27 | vTblSwapEffects.PeakEffect().trigger(); 28 | return ((hkVirtNoParams_t)origVFuncs.at(1))(_args...); 29 | }); 30 | 31 | HOOK_CALLBACK(&VirtualTest::NoParamVirt2, hkVirt2NoParams, { 32 | vTblSwapEffects.PeakEffect().trigger(); 33 | return ((hkVirt2NoParams_t)origVFuncs.at(2))(_args...); 34 | }); 35 | 36 | TEST_CASE("VTableSwap tests", "[VTableSwap]") { 37 | std::shared_ptr ClassToHook(new VirtualTest); 38 | 39 | SECTION("Verify vtable redirected") { 40 | PLH::StackCanary canary; 41 | PLH::VFuncMap redirect = {{(uint16_t)1, (uint64_t)hkVirtNoParams}}; 42 | PLH::VTableSwapHook hook((char*)ClassToHook.get(), redirect, &origVFuncs); 43 | REQUIRE(hook.hook()); 44 | REQUIRE(origVFuncs.size() == 1); 45 | 46 | vTblSwapEffects.PushEffect(); 47 | ClassToHook->NoParamVirt(); 48 | REQUIRE(vTblSwapEffects.PopEffect().didExecute()); 49 | REQUIRE(hook.unHook()); 50 | } 51 | 52 | SECTION("Verify multiple vtable redirected") { 53 | PLH::StackCanary canary; 54 | PLH::VFuncMap redirect = {{(uint16_t)1, (uint64_t)hkVirtNoParams},{(uint16_t)2, (uint64_t)hkVirtNoParams}}; 55 | PLH::VTableSwapHook hook((char*)ClassToHook.get(), redirect, &origVFuncs); 56 | REQUIRE(hook.hook()); 57 | REQUIRE(origVFuncs.size() == 2); 58 | 59 | vTblSwapEffects.PushEffect(); 60 | ClassToHook->NoParamVirt(); 61 | REQUIRE(vTblSwapEffects.PopEffect().didExecute()); 62 | 63 | vTblSwapEffects.PushEffect(); 64 | ClassToHook->NoParamVirt2(); 65 | REQUIRE(vTblSwapEffects.PopEffect().didExecute()); 66 | REQUIRE(hook.unHook()); 67 | } 68 | } -------------------------------------------------------------------------------- /polyhook2/PolyHookOs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(WIN64) || defined(_WIN64) || defined(__MINGW64__) 4 | #define POLYHOOK2_OS_WINDOWS 5 | #define POLYHOOK2_ARCH_X64 6 | 7 | #ifdef __GNUC__ 8 | 9 | // VirtualAlloc2 requires NTDII_WIN10_RS4 on my distrubition of mingw 10 | #define NTDDI_VERSION NTDDI_WIN10_RS4 11 | 12 | // This was taken from Microsofts Detours library 13 | #define ERROR_DYNAMIC_CODE_BLOCKED 1655L 14 | 15 | #endif 16 | 17 | #elif defined(WIN32) || defined(_WIN32) || defined(__MINGW32__) 18 | #define POLYHOOK2_OS_WINDOWS 19 | #define POLYHOOK2_ARCH_X86 20 | 21 | #ifdef __GNUC__ 22 | 23 | // VirtualAlloc2 requires NTDII_WIN10_RS4 on my distrubition of mingw 24 | #define NTDDI_VERSION NTDDI_WIN10_RS4 25 | 26 | // This was taken from Microsofts Detours library 27 | #define ERROR_DYNAMIC_CODE_BLOCKED 1655L 28 | 29 | #endif 30 | 31 | #elif defined(__linux__) || defined(linux) 32 | #if defined(__x86_64__) 33 | #define POLYHOOK2_OS_LINUX 34 | #define POLYHOOK2_ARCH_X64 35 | #else 36 | #define POLYHOOK2_OS_LINUX 37 | #define POLYHOOK2_ARCH_X86 38 | #endif 39 | #elif defined(__APPLE__) 40 | #if defined(__x86_64__) 41 | #define POLYHOOK2_OS_APPLE 42 | #define POLYHOOK2_ARCH_X64 43 | #else 44 | #define POLYHOOK2_OS_APPLE 45 | #define POLYHOOK2_ARCH_X86 46 | #endif 47 | #endif 48 | 49 | #if defined(_MSC_VER) 50 | #define PLH_INLINE __forceinline 51 | #elif defined(__GNUC__) 52 | #define PLH_INLINE inline __attribute__((always_inline)) 53 | #else 54 | #define PLH_INLINE inline 55 | #endif 56 | 57 | #include //for debug printing 58 | #include 59 | #include //setw 60 | #include 61 | 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | 69 | #include 70 | #include 71 | 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | 82 | #include 83 | #include 84 | #include 85 | 86 | #include 87 | #include 88 | #include 89 | 90 | void PolyHook2DebugBreak(); 91 | -------------------------------------------------------------------------------- /UnitTests/windows/TestVFuncSwapHook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "polyhook2/Virtuals/VFuncSwapHook.hpp" 6 | #include "polyhook2/Tests/StackCanary.hpp" 7 | #include "polyhook2/Tests/TestEffectTracker.hpp" 8 | 9 | EffectTracker vFuncSwapEffects; 10 | 11 | class VirtualTest2 { 12 | public: 13 | virtual ~VirtualTest2() {} 14 | 15 | virtual int __stdcall NoParamVirt() { 16 | return 4; 17 | } 18 | 19 | virtual int __stdcall NoParamVirt2() { 20 | return 7; 21 | } 22 | }; 23 | 24 | #pragma warning(disable: 4100) 25 | 26 | PLH::VFuncMap origVFuncs2; 27 | HOOK_CALLBACK(&VirtualTest2::NoParamVirt, hkVirtNoParams2, { 28 | PLH::StackCanary canary; 29 | vFuncSwapEffects.PeakEffect().trigger(); 30 | return ((hkVirtNoParams2_t)origVFuncs2.at(1))(_args...); 31 | }); 32 | 33 | HOOK_CALLBACK(&VirtualTest2::NoParamVirt2, hkVirt2NoParams2, { 34 | PLH::StackCanary canary; 35 | vFuncSwapEffects.PeakEffect().trigger(); 36 | return ((hkVirtNoParams2_t)origVFuncs2.at(2))(_args...); 37 | }); 38 | 39 | TEST_CASE("VFuncSwap tests", "[VFuncSwap]") { 40 | std::shared_ptr ClassToHook(new VirtualTest2); 41 | 42 | SECTION("Verify vfunc redirected") { 43 | PLH::StackCanary canary; 44 | PLH::VFuncMap redirect = {{(uint16_t)1, (uint64_t)hkVirtNoParams2}}; 45 | PLH::VFuncSwapHook hook((char*)ClassToHook.get(), redirect, &origVFuncs2); 46 | REQUIRE(hook.hook()); 47 | REQUIRE(origVFuncs2.size() == 1); 48 | 49 | vFuncSwapEffects.PushEffect(); 50 | ClassToHook->NoParamVirt(); 51 | REQUIRE(vFuncSwapEffects.PopEffect().didExecute()); 52 | REQUIRE(hook.unHook()); 53 | } 54 | 55 | SECTION("Verify multiple vfunc redirected") { 56 | PLH::StackCanary canary; 57 | PLH::VFuncMap redirect = {{(uint16_t)1, (uint64_t)hkVirtNoParams2},{(uint16_t)2, (uint64_t)hkVirt2NoParams2}}; 58 | PLH::VFuncSwapHook hook((char*)ClassToHook.get(), redirect, &origVFuncs2); 59 | REQUIRE(hook.hook()); 60 | REQUIRE(origVFuncs2.size() == 2); 61 | 62 | vFuncSwapEffects.PushEffect(); 63 | ClassToHook->NoParamVirt(); 64 | REQUIRE(vFuncSwapEffects.PopEffect().didExecute()); 65 | 66 | vFuncSwapEffects.PushEffect(); 67 | ClassToHook->NoParamVirt2(); 68 | REQUIRE(vFuncSwapEffects.PopEffect().didExecute()); 69 | REQUIRE(hook.unHook()); 70 | } 71 | } -------------------------------------------------------------------------------- /sources/VFuncSwapHook.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/Virtuals/VFuncSwapHook.hpp" 2 | #include "polyhook2/ErrorLog.hpp" 3 | 4 | PLH::VFuncSwapHook::VFuncSwapHook(const char* Class, const VFuncMap& redirectMap, VFuncMap* userOrigMap) 5 | : VFuncSwapHook((uint64_t)Class, redirectMap, userOrigMap) 6 | {} 7 | 8 | PLH::VFuncSwapHook::VFuncSwapHook(const uint64_t Class, const VFuncMap& redirectMap, VFuncMap* userOrigMap) 9 | : m_class(Class) 10 | , m_vtable(nullptr) 11 | , m_vFuncCount(0) 12 | , m_redirectMap(redirectMap) 13 | , m_userOrigMap(userOrigMap) 14 | {} 15 | 16 | bool PLH::VFuncSwapHook::hook() { 17 | assert(m_userOrigMap != nullptr); 18 | MemoryProtector prot(m_class, sizeof(void*), ProtFlag::R | ProtFlag::W, *this); 19 | m_vtable = *(uintptr_t**)m_class; 20 | m_vFuncCount = countVFuncs(); 21 | if (m_vFuncCount <= 0) 22 | return false; 23 | 24 | MemoryProtector prot2((uint64_t)&m_vtable[0], sizeof(uintptr_t) * (uint64_t)m_vFuncCount, ProtFlag::R | ProtFlag::W, *this); 25 | for (const auto& p : m_redirectMap) { 26 | assert(p.first < m_vFuncCount); 27 | if (p.first >= m_vFuncCount) 28 | return false; 29 | 30 | // redirect ptr at VTable[i] 31 | (*m_userOrigMap)[p.first] = (uint64_t)m_vtable[p.first]; 32 | m_vtable[p.first] = (uintptr_t)p.second; 33 | } 34 | 35 | m_hooked = true; 36 | return true; 37 | } 38 | 39 | bool PLH::VFuncSwapHook::unHook() { 40 | assert(m_userOrigMap != nullptr); 41 | assert(m_hooked); 42 | if (!m_hooked) { 43 | Log::log("vfuncswap unhook failed: no hook present", ErrorLevel::SEV); 44 | return false; 45 | } 46 | 47 | MemoryProtector prot2((uint64_t)&m_vtable[0], sizeof(uintptr_t) * (uint64_t)m_vFuncCount, ProtFlag::R | ProtFlag::W, *this); 48 | for (const auto& p : (*m_userOrigMap)) { 49 | assert(p.first < m_vFuncCount); 50 | if (p.first >= m_vFuncCount) 51 | return false; 52 | 53 | m_vtable[p.first] = (uintptr_t)p.second; 54 | } 55 | 56 | (*m_userOrigMap).clear(); 57 | 58 | m_hooked = false; 59 | return true; 60 | } 61 | 62 | uint16_t PLH::VFuncSwapHook::countVFuncs() { 63 | uint16_t count = 0; 64 | for (;; count++) { 65 | // if you have more than 500 vfuncs you have a problem and i don't support you :) 66 | if (!IsValidPtr((void*)m_vtable[count]) || count > 500) 67 | break; 68 | } 69 | return count; 70 | } 71 | -------------------------------------------------------------------------------- /polyhook2/FBAllocator.hpp: -------------------------------------------------------------------------------- 1 | // https://www.codeproject.com/Articles/1272619/A-Fixed-Block-Memory-Allocator-in-C 2 | // 3 | // The fb_allocator is a fixed block memory allocator that handles a 4 | // single block size. 5 | // 6 | 7 | 8 | #ifndef _FB_ALLOCATOR_H 9 | #define _FB_ALLOCATOR_H 10 | 11 | #include "polyhook2/PolyHookOs.hpp" 12 | #include "MemAccessor.hpp" 13 | 14 | typedef void* ALLOC_HANDLE; 15 | 16 | typedef struct 17 | { 18 | void* pNext; 19 | } ALLOC_Block; 20 | 21 | typedef struct 22 | { 23 | const char* name; 24 | const char* pPool; 25 | const size_t objectSize; 26 | const size_t blockSize; 27 | const uint32_t maxBlocks; 28 | ALLOC_Block* pHead; 29 | uint16_t poolIndex; 30 | uint16_t blocksInUse; 31 | uint16_t maxBlocksInUse; 32 | uint16_t allocations; 33 | uint16_t deallocations; 34 | } ALLOC_Allocator; 35 | 36 | // Align fixed blocks on X-byte boundary based on CPU architecture. 37 | // Set value to 1, 2, 4 or 8. 38 | #define ALLOC_MEM_ALIGN (1) 39 | 40 | // Get the maximum between a or b 41 | #define ALLOC_MAX(a,b) (((a)>(b))?(a):(b)) 42 | 43 | // Ensure the memory block size is: (a) is aligned on desired boundary and (b) at 44 | // least the size of a ALLOC_Allocator*. 45 | #define ALLOC_BLOCK_SIZE(_size_) \ 46 | (ALLOC_MAX((MEMORY_ROUND_UP(_size_, ALLOC_MEM_ALIGN)), sizeof(ALLOC_Allocator*))) 47 | 48 | // Defines block memory, allocator instance and a handle. On the example below, 49 | // the ALLOC_Allocator instance is myAllocatorObj and the handle is myAllocator. 50 | // _name_ - the allocator name 51 | // _size_ - fixed memory block size in bytes 52 | // _objects_ - number of fixed memory blocks 53 | // e.g. ALLOC_DEFINE(myAllocator, 32, 10) 54 | #define ALLOC_DEFINE(_name_, _size_, _objects_) \ 55 | static char _name_##Memory[ALLOC_BLOCK_SIZE(_size_) * (_objects_)] = { 0 }; \ 56 | static ALLOC_Allocator _name_##Obj = { #_name_, _name_##Memory, _size_, \ 57 | ALLOC_BLOCK_SIZE(_size_), _objects_, NULL, 0, 0, 0, 0, 0 }; \ 58 | static ALLOC_HANDLE _name_ = &_name_##Obj; 59 | 60 | void* ALLOC_Alloc(ALLOC_HANDLE hAlloc, size_t size); 61 | void* ALLOC_Calloc(ALLOC_HANDLE hAlloc, size_t num, size_t size); 62 | void ALLOC_Free(ALLOC_HANDLE hAlloc, void* pBlock); 63 | #endif // _FB_ALLOCATOR_H -------------------------------------------------------------------------------- /Examples/VCPKG_DynamicLink/CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-MDRelease", 5 | "generator": "Ninja", 6 | "configurationType": "Release", 7 | "buildRoot": "${projectDir}\\out\\build\\${name}", 8 | "installRoot": "${projectDir}\\out\\install\\${name}", 9 | "cmakeCommandArgs": "", 10 | "buildCommandArgs": "", 11 | "ctestCommandArgs": "", 12 | "inheritEnvironments": [ "msvc_x64_x64" ], 13 | "cmakeToolchain": "${env.VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", 14 | "variables": [ 15 | { 16 | "name": "VCPKG_TARGET_TRIPLET", 17 | "value": "x64-windows", 18 | "type": "STRING" 19 | } 20 | ] 21 | }, 22 | { 23 | "name": "x64-MDDebug", 24 | "generator": "Ninja", 25 | "configurationType": "Debug", 26 | "buildRoot": "${projectDir}\\out\\build\\${name}", 27 | "installRoot": "${projectDir}\\out\\install\\${name}", 28 | "cmakeCommandArgs": "", 29 | "buildCommandArgs": "", 30 | "ctestCommandArgs": "", 31 | "cmakeToolchain": "${env.VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", 32 | "inheritEnvironments": [ "msvc_x64_x64" ], 33 | "variables": [ 34 | { 35 | "name": "VCPKG_TARGET_TRIPLET", 36 | "value": "x64-windows", 37 | "type": "STRING" 38 | } 39 | ] 40 | }, 41 | { 42 | "name": "x86-MDRelease", 43 | "generator": "Ninja", 44 | "configurationType": "Release", 45 | "buildRoot": "${projectDir}\\out\\build\\${name}", 46 | "installRoot": "${projectDir}\\out\\install\\${name}", 47 | "cmakeCommandArgs": "", 48 | "buildCommandArgs": "", 49 | "ctestCommandArgs": "", 50 | "inheritEnvironments": [ "msvc_x86" ], 51 | "variables": [ 52 | { 53 | "name": "VCPKG_TARGET_TRIPLET", 54 | "value": "x86-windows", 55 | "type": "STRING" 56 | } 57 | ] 58 | }, 59 | { 60 | "name": "x86-MDDebug", 61 | "generator": "Ninja", 62 | "configurationType": "Debug", 63 | "buildRoot": "${projectDir}\\out\\build\\${name}", 64 | "installRoot": "${projectDir}\\out\\install\\${name}", 65 | "cmakeCommandArgs": "", 66 | "buildCommandArgs": "", 67 | "ctestCommandArgs": "", 68 | "inheritEnvironments": [ "msvc_x86" ], 69 | "variables": [ 70 | { 71 | "name": "VCPKG_TARGET_TRIPLET", 72 | "value": "x86-windows", 73 | "type": "STRING" 74 | } 75 | ] 76 | } 77 | ] 78 | } -------------------------------------------------------------------------------- /Examples/VCPKG_StaticLink/CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-MTRelease", 5 | "generator": "Ninja", 6 | "configurationType": "Release", 7 | "buildRoot": "${projectDir}\\out\\build\\${name}", 8 | "installRoot": "${projectDir}\\out\\install\\${name}", 9 | "cmakeCommandArgs": "", 10 | "buildCommandArgs": "", 11 | "ctestCommandArgs": "", 12 | "inheritEnvironments": [ "msvc_x64_x64" ], 13 | "cmakeToolchain": "${env.VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", 14 | "variables": [ 15 | { 16 | "name": "VCPKG_TARGET_TRIPLET", 17 | "value": "x64-windows-static", 18 | "type": "STRING" 19 | } 20 | ] 21 | }, 22 | { 23 | "name": "x64-MTDebug", 24 | "generator": "Ninja", 25 | "configurationType": "Debug", 26 | "buildRoot": "${projectDir}\\out\\build\\${name}", 27 | "installRoot": "${projectDir}\\out\\install\\${name}", 28 | "cmakeCommandArgs": "", 29 | "buildCommandArgs": "", 30 | "ctestCommandArgs": "", 31 | "cmakeToolchain": "${env.VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", 32 | "inheritEnvironments": [ "msvc_x64_x64" ], 33 | "variables": [ 34 | { 35 | "name": "VCPKG_TARGET_TRIPLET", 36 | "value": "x64-windows-static", 37 | "type": "STRING" 38 | } 39 | ] 40 | }, 41 | { 42 | "name": "x86-MTRelease", 43 | "generator": "Ninja", 44 | "configurationType": "Release", 45 | "buildRoot": "${projectDir}\\out\\build\\${name}", 46 | "installRoot": "${projectDir}\\out\\install\\${name}", 47 | "cmakeCommandArgs": "", 48 | "buildCommandArgs": "", 49 | "ctestCommandArgs": "", 50 | "inheritEnvironments": [ "msvc_x86" ], 51 | "variables": [ 52 | { 53 | "name": "VCPKG_TARGET_TRIPLET", 54 | "value": "x86-windows-static", 55 | "type": "STRING" 56 | } 57 | ] 58 | }, 59 | { 60 | "name": "x86-MTDebug", 61 | "generator": "Ninja", 62 | "configurationType": "Debug", 63 | "buildRoot": "${projectDir}\\out\\build\\${name}", 64 | "installRoot": "${projectDir}\\out\\install\\${name}", 65 | "cmakeCommandArgs": "", 66 | "buildCommandArgs": "", 67 | "ctestCommandArgs": "", 68 | "inheritEnvironments": [ "msvc_x86" ], 69 | "variables": [ 70 | { 71 | "name": "VCPKG_TARGET_TRIPLET", 72 | "value": "x86-windows-static", 73 | "type": "STRING" 74 | } 75 | ] 76 | } 77 | ] 78 | } -------------------------------------------------------------------------------- /sources/AVehHook.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/Exceptions/AVehHook.hpp" 2 | 3 | PLH::RefCounter PLH::AVehHook::m_refCount; 4 | void* PLH::AVehHook::m_hHandler; 5 | std::unordered_set PLH::AVehHook::m_impls; 6 | PLH::eException PLH::AVehHook::m_onException; 7 | PLH::eException PLH::AVehHook::m_onUnhandledException; 8 | 9 | // https://reverseengineering.stackexchange.com/questions/14992/what-are-the-vectored-continue-handlers 10 | PLH::AVehHook::AVehHook() { 11 | if (m_refCount.m_count == 0) { 12 | m_hHandler = AddVectoredExceptionHandler(1, &AVehHook::Handler); 13 | if (m_hHandler == NULL) { 14 | Log::log("Failed to add VEH", ErrorLevel::SEV); 15 | } 16 | } 17 | 18 | m_refCount.m_count++; 19 | } 20 | 21 | PLH::AVehHook::~AVehHook() { 22 | assert(m_refCount.m_count >= 1); 23 | 24 | m_refCount.m_count--; 25 | if (m_refCount.m_count == 0) { 26 | assert(m_hHandler != nullptr); 27 | ULONG status = RemoveVectoredExceptionHandler(m_hHandler); 28 | m_hHandler = nullptr; 29 | if (status == 0) { 30 | Log::log("Failed to remove VEH", ErrorLevel::SEV); 31 | } 32 | } 33 | } 34 | 35 | PLH::eException& PLH::AVehHook::EventException() { 36 | return m_onException; 37 | } 38 | 39 | PLH::eException& PLH::AVehHook::EventUnhandledException() { 40 | return m_onUnhandledException; 41 | } 42 | 43 | LONG CALLBACK PLH::AVehHook::Handler(EXCEPTION_POINTERS* ExceptionInfo) { 44 | DWORD ExceptionCode = ExceptionInfo->ExceptionRecord->ExceptionCode; 45 | uint64_t ip = ExceptionInfo->ContextRecord->XIP; 46 | 47 | // invoke callback (let users filter) 48 | DWORD code = EXCEPTION_CONTINUE_SEARCH; 49 | if (m_onException && m_onException.Invoke(ExceptionInfo, &code)) 50 | return code; 51 | 52 | switch (ExceptionCode) { 53 | case 0xE06D7363: // oooh aaahh a magic value 54 | std::cout << "C++ exception thrown" << std::endl; 55 | break; 56 | // these could all reasonably be hooked by someone 57 | case EXCEPTION_GUARD_PAGE: 58 | case EXCEPTION_ACCESS_VIOLATION: 59 | case EXCEPTION_BREAKPOINT: 60 | case EXCEPTION_SINGLE_STEP: 61 | // lookup which instance to forward exception to 62 | for (const auto& hk : m_impls) { 63 | switch (hk.type) { 64 | case AVehHookImpType::SINGLE: 65 | if (hk.startAddress == ip) { 66 | return hk.impl->OnException(ExceptionInfo); 67 | } 68 | break; 69 | case AVehHookImpType::RANGE: 70 | if (ip >= hk.startAddress && ip < hk.endAddress) { 71 | return hk.impl->OnException(ExceptionInfo); 72 | } 73 | break; 74 | } 75 | } 76 | break; 77 | default: 78 | // let users extend manually 79 | if (m_onUnhandledException && m_onUnhandledException.Invoke(ExceptionInfo, &code)) 80 | return code; 81 | } 82 | return EXCEPTION_CONTINUE_SEARCH; 83 | } -------------------------------------------------------------------------------- /polyhook2/Detour/x64Detour.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by steve on 7/4/17. 3 | // 4 | 5 | #ifndef POLYHOOK_2_X64DETOUR_HPP 6 | #define POLYHOOK_2_X64DETOUR_HPP 7 | 8 | #include "polyhook2/PolyHookOs.hpp" 9 | #include "polyhook2/Detour/ADetour.hpp" 10 | #include "polyhook2/Enums.hpp" 11 | #include "polyhook2/Instruction.hpp" 12 | #include "polyhook2/RangeAllocator.hpp" 13 | #include 14 | 15 | namespace PLH { 16 | 17 | using std::optional; 18 | 19 | class x64Detour : public Detour { 20 | 21 | public: 22 | enum detour_scheme_t : uint8_t { 23 | VALLOC2 = 1 << 0, // use virtualalloc2 to allocate in range. Only on win10 > 1803 24 | INPLACE = 1 << 1, // use push-ret for fnCallback in-place storage. 25 | CODE_CAVE = 1 << 2, //searching for code-cave to keep fnCallback. 26 | INPLACE_SHORT = 1 << 3, // spoils rax register 27 | RECOMMENDED = VALLOC2 | INPLACE | CODE_CAVE, 28 | // first try to allocate, then fallback to code cave if not supported. 29 | // will not fallback on failure of allocation 30 | ALL = RECOMMENDED | INPLACE_SHORT, 31 | }; 32 | 33 | x64Detour(uint64_t fnAddress, uint64_t fnCallback, uint64_t* userTrampVar); 34 | 35 | ~x64Detour() override; 36 | 37 | bool hook() override; 38 | 39 | bool unHook() override; 40 | 41 | Mode getArchType() const override; 42 | 43 | static uint8_t getMinJmpSize(); 44 | 45 | detour_scheme_t getDetourScheme() const; 46 | 47 | void setDetourScheme(detour_scheme_t scheme); 48 | 49 | static const char* printDetourScheme(detour_scheme_t scheme); 50 | 51 | protected: 52 | detour_scheme_t m_detourScheme = detour_scheme_t::RECOMMENDED; // this is the most stable configuration. 53 | optional m_valloc2_region; 54 | RangeAllocator m_allocator; 55 | asmjit::JitRuntime m_asmjit_rt; 56 | detour_scheme_t m_chosen_scheme = detour_scheme_t::VALLOC2; 57 | 58 | bool makeTrampoline(insts_t& prologue, insts_t& outJmpTable); 59 | 60 | // assumes we are looking within a +-2GB window 61 | template 62 | optional findNearestCodeCave(uint64_t address); 63 | 64 | optional generateTranslationRoutine(const Instruction& instruction, uint64_t resume_address); 65 | 66 | using builder_fn_t = std::function; 67 | std::optional make_inplace_trampoline(uint64_t base_address, const builder_fn_t& builder); 68 | 69 | bool allocate_jump_to_callback(const insts_t& originalInsts); 70 | 71 | bool fitHookInstsIntoPrologue( 72 | const insts_t& originalInsts, 73 | const insts_t& hookInsts, 74 | detour_scheme_t chosenScheme 75 | ); 76 | }; 77 | 78 | } 79 | #endif //POLYHOOK_2_X64DETOUR_HPP 80 | -------------------------------------------------------------------------------- /polyhook2/Enums.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by steve on 4/20/17. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "polyhook2/PolyHookOs.hpp" 8 | 9 | namespace PLH { 10 | 11 | enum class HookType { 12 | Detour, 13 | VEHHOOK, 14 | VTableSwap, 15 | IAT, 16 | EAT, 17 | UNKNOWN 18 | }; 19 | 20 | 21 | 22 | /* Used by detours class only. This doesn't live in instruction because it 23 | * only makes sense for specific jump instructions (perhaps re-factor instruction 24 | * to store inst. specific stuff when needed?). There are two classes of information for jumps 25 | * 1) how displacement is encoded, either relative to I.P. or Absolute 26 | * 2) where the jmp points, either absolutely to the destination or to a memory loc. that then points to the final dest. 27 | * 28 | * The first information is stored internal to the PLH::Instruction object. The second is this enum class that you 29 | * tack on via a pair or tuple when you need to transfer that knowledge.*/ 30 | enum class JmpType { 31 | Absolute, 32 | Indirect 33 | }; 34 | 35 | enum class Mode { 36 | x86, 37 | x64 38 | }; 39 | 40 | enum class ErrorLevel { 41 | INFO, 42 | WARN, 43 | SEV, 44 | NONE 45 | }; 46 | 47 | //unsafe enum by design to allow binary OR 48 | enum class ProtFlag : uint8_t { 49 | UNSET = 0, // Value means this give no information about protection state (un-read) 50 | X = 1 << 1, 51 | R = 1 << 2, 52 | W = 1 << 3, 53 | S = 1 << 4, 54 | P = 1 << 5, 55 | NONE = 1 << 6, //The value equaling the linux flag PROT_UNSET (read the prot, and the prot is unset) 56 | RWX = R | W | X 57 | }; 58 | 59 | inline ProtFlag operator|(const ProtFlag lhs, const ProtFlag rhs) { 60 | using underlying = std::underlying_type::type; 61 | return static_cast ( 62 | static_cast(lhs) | 63 | static_cast(rhs) 64 | ); 65 | } 66 | 67 | inline bool operator&(const ProtFlag lhs, const ProtFlag rhs) { 68 | using underlying = std::underlying_type_t; 69 | return static_cast(lhs) & 70 | static_cast(rhs); 71 | } 72 | 73 | #ifdef PLH_DIAGNOSTICS 74 | enum class Diagnostic : uint32_t { 75 | None = 0, 76 | TranslatedInstructions = 1 << 0, 77 | FixedCallToRoutineReadingSP = 1 << 1, // #215 78 | FixedInlineCallToReadSP = 1 << 2, // #217 79 | }; 80 | // Bitwise operators 81 | inline Diagnostic operator|(const uint32_t flags, const Diagnostic diagnostic) { 82 | return static_cast(flags | static_cast(diagnostic)); 83 | } 84 | inline Diagnostic operator&(const uint32_t flags, const Diagnostic diagnostic) { 85 | return static_cast(flags & static_cast(diagnostic)); 86 | } 87 | #endif 88 | 89 | } 90 | -------------------------------------------------------------------------------- /polyhook2/Exceptions/AVehHook.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYHOOK_2_0_VEHHOOK_HPP 2 | #define POLYHOOK_2_0_VEHHOOK_HPP 3 | 4 | #include "polyhook2/PolyHookOs.hpp" 5 | #include "polyhook2/PolyHookOsIncludes.hpp" 6 | #include "polyhook2/MemProtector.hpp" 7 | #include "polyhook2/ErrorLog.hpp" 8 | #include "polyhook2/IHook.hpp" 9 | #include "polyhook2/Enums.hpp" 10 | #include "polyhook2/EventDispatcher.hpp" 11 | 12 | namespace PLH { 13 | 14 | #ifdef _WIN64 15 | #define XIP Rip 16 | #else 17 | #define XIP Eip 18 | #endif // _WIN64 19 | 20 | class RefCounter { 21 | public: 22 | uint16_t m_count = 0; 23 | }; 24 | 25 | enum class AVehHookImpType { 26 | SINGLE, // will exception occur at one address (end address ignored) 27 | RANGE // will exception occur over a potential range 28 | }; 29 | 30 | class AVehHook; 31 | struct AVehHookImpEntry { 32 | uint64_t startAddress; // start address impl applies to 33 | uint64_t endAddress; // end address impl applies to 34 | AVehHook* impl; // the instance to forward to 35 | AVehHookImpType type; 36 | 37 | AVehHookImpEntry(uint64_t start, AVehHook* imp) { 38 | startAddress = start; 39 | endAddress = 0; 40 | impl = imp; 41 | type = AVehHookImpType::SINGLE; 42 | } 43 | 44 | AVehHookImpEntry(uint64_t start, uint64_t end, AVehHook* imp) { 45 | startAddress = start; 46 | endAddress = end; 47 | impl = imp; 48 | type = AVehHookImpType::RANGE; 49 | } 50 | }; 51 | 52 | inline bool operator==(const AVehHookImpEntry& lhs, const AVehHookImpEntry& rhs) 53 | { 54 | return lhs.type == rhs.type && lhs.startAddress == rhs.startAddress && lhs.endAddress == rhs.endAddress; 55 | } 56 | 57 | 58 | 59 | 60 | typedef EventDispatcher eException; 61 | class AVehHook : public IHook { 62 | public: 63 | AVehHook(); 64 | virtual ~AVehHook(); 65 | 66 | virtual HookType getType() const { 67 | return HookType::VEHHOOK; 68 | } 69 | 70 | /**If true is returned**/ 71 | static eException& EventException(); 72 | static eException& EventUnhandledException(); 73 | protected: 74 | // May not allocate or acquire synchonization objects in this 75 | virtual LONG OnException(EXCEPTION_POINTERS* ExceptionInfo) = 0; 76 | 77 | static RefCounter m_refCount; 78 | static void* m_hHandler; 79 | static std::unordered_set m_impls; 80 | static LONG CALLBACK Handler(EXCEPTION_POINTERS* ExceptionInfo); 81 | static eException m_onException; 82 | static eException m_onUnhandledException; 83 | }; 84 | } 85 | 86 | namespace std { 87 | template<> struct hash 88 | { 89 | std::size_t operator()(const PLH::AVehHookImpEntry& e) const noexcept 90 | { 91 | auto h1 = std::hash{}(e.startAddress); 92 | auto h2 = std::hash{}(e.endAddress); 93 | return h1 ^ (h2 << 1); 94 | } 95 | }; 96 | } 97 | 98 | #endif -------------------------------------------------------------------------------- /UnitTests/TestUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // clang-format off 6 | /** 7 | * This macro is used to define a hooked function that needs to proxy calls to the original one. 8 | * The main challenge with such a macro is that it needs to perfectly mirror original function signature, 9 | * especially the noexcept attribute. 10 | * This is accomplished by using inner noexcept, which 11 | * tests whether calling the original function with those arguments would be noexcept 12 | * and outer noexcept, which 13 | * uses the result of that test to set the noexcept specification on the lambda. 14 | * In simple terms, this construct says: 15 | * "This lambda is noexcept if and only if calling the original function with the same arguments would be noexcept." 16 | * This is a compile-time mechanism that perfectly mirrors the exception specification of the original function. 17 | * 18 | * Note that this is not supported by GCC, since generic lambdas cannot be assigned to function pointers in GCC. 19 | * Clang allows generic lambdas to decay to function pointers if the instantiated signature matches. 20 | * This is a known divergence from the C++ standard. 21 | */ 22 | #define PLH_TEST_CALLBACK(FUNC, HOOK, TRMP, ...) \ 23 | uint64_t TRMP = 0; \ 24 | decltype(&FUNC) HOOK = [](Args... $args) \ 25 | noexcept(noexcept(std::declval()(std::declval()...))) -> auto { \ 26 | PLH::StackCanary canary; \ 27 | PLH_STOP_OPTIMIZATIONS(); \ 28 | effects.PeakEffect().trigger(); \ 29 | __VA_ARGS__ \ 30 | return PLH::FnCast(TRMP, &FUNC)($args...); \ 31 | } 32 | // clang-format on 33 | 34 | /** 35 | * Most test hooks follow the same convention, 36 | * where hooked functions and trampoline variables derive their name from the original function. 37 | * Hence, it makes sense to create a corresponding macro utility 38 | */ 39 | #define PLH_TEST_DETOUR_CALLBACK(FUNC, ...) PLH_TEST_CALLBACK(FUNC, FUNC##_hooked, FUNC##_trmp, __VA_ARGS__) 40 | #define PLH_TEST_DETOUR(FUNC) detour((uint64_t)&FUNC, (uint64_t)FUNC##_hooked, &FUNC##_trmp); 41 | 42 | /** 43 | * These tests can spontaneously fail if the compiler decides to optimize away 44 | * the handler or inline the function. PLH_NOINLINE attempts to fix the latter, the former 45 | * is out of our control but typically returning volatile things, volatile locals, and a 46 | * printf inside the body can mitigate this significantly. Do serious checking in Debug 47 | * or ReleaseWithDebInfo mode (ReleaseWithDebInfo optimizes _slightly_ less). 48 | */ 49 | #define PLH_STOP_OPTIMIZATIONS() \ 50 | volatile int i = 0; \ 51 | PH_UNUSED(i) 52 | 53 | namespace PLH::test { 54 | 55 | void registerTestLogger(); 56 | 57 | } 58 | -------------------------------------------------------------------------------- /sources/VTableSwapHook.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/Virtuals/VTableSwapHook.hpp" 2 | #include "polyhook2/ErrorLog.hpp" 3 | 4 | PLH::VTableSwapHook::VTableSwapHook(const char* Class, const VFuncMap& redirectMap, VFuncMap* userOrigMap) 5 | : VTableSwapHook((uint64_t)Class, redirectMap, userOrigMap) 6 | {} 7 | 8 | PLH::VTableSwapHook::VTableSwapHook(const uint64_t Class, VFuncMap* userOrigMap) 9 | : VTableSwapHook(Class, PLH::VFuncMap{ }, userOrigMap) 10 | {} 11 | 12 | PLH::VTableSwapHook::VTableSwapHook(const uint64_t Class, const VFuncMap& redirectMap, VFuncMap* userOrigMap) 13 | : m_newVtable(nullptr) 14 | , m_origVtable(nullptr) 15 | , m_class(Class) 16 | , m_vFuncCount(0) 17 | , m_redirectMap(redirectMap) 18 | , m_userOrigMap(userOrigMap) 19 | {} 20 | 21 | bool PLH::VTableSwapHook::hook() { 22 | assert(m_userOrigMap != nullptr); 23 | assert(!m_hooked); 24 | if (m_hooked) { 25 | Log::log("vtable hook failed: hook already present", ErrorLevel::SEV); 26 | return false; 27 | } 28 | 29 | MemoryProtector prot(m_class, sizeof(void*), ProtFlag::R | ProtFlag::W, *this); 30 | m_origVtable = *(uintptr_t**)m_class; 31 | m_vFuncCount = countVFuncs(); 32 | assert(m_vFuncCount > 0); 33 | if (m_vFuncCount <= 0) 34 | { 35 | Log::log("vtable hook failed: class has no virtual functions", ErrorLevel::SEV); 36 | return false; 37 | } 38 | 39 | m_newVtable.reset(new uintptr_t[m_vFuncCount]); 40 | 41 | // deep copy orig vtable into new 42 | memcpy(m_newVtable.get(), m_origVtable, sizeof(uintptr_t) * m_vFuncCount); 43 | 44 | for (const auto& p : m_redirectMap) { 45 | assert(p.first < m_vFuncCount); 46 | if (p.first >= m_vFuncCount) { 47 | Log::log("vtable hook failed: index exceeds virtual function count", ErrorLevel::SEV); 48 | m_newVtable = nullptr; 49 | (*m_userOrigMap).clear(); 50 | return false; 51 | } 52 | 53 | // redirect ptr at VTable[i] 54 | (*m_userOrigMap)[p.first] = (uint64_t)m_newVtable[p.first]; 55 | m_newVtable[p.first] = (uintptr_t)p.second; 56 | } 57 | 58 | *(uint64_t**)m_class = (uint64_t*)m_newVtable.get(); 59 | m_hooked = true; 60 | Log::log("vtable hooked", ErrorLevel::INFO); 61 | return true; 62 | } 63 | 64 | bool PLH::VTableSwapHook::unHook() { 65 | assert(m_hooked); 66 | if (!m_hooked) { 67 | Log::log("vtable unhook failed: no hook present", ErrorLevel::SEV); 68 | return false; 69 | } 70 | 71 | MemoryProtector prot(m_class, sizeof(void*), ProtFlag::R | ProtFlag::W, *this); 72 | *(uint64_t**)m_class = (uint64_t*)m_origVtable; 73 | 74 | m_newVtable.reset(); 75 | 76 | m_hooked = false; 77 | m_origVtable = nullptr; 78 | 79 | (*m_userOrigMap).clear(); 80 | 81 | Log::log("vtable unhooked", ErrorLevel::INFO); 82 | return true; 83 | } 84 | 85 | uint16_t PLH::VTableSwapHook::countVFuncs() { 86 | uint16_t count = 0; 87 | for (;; count++) { 88 | // if you have more than 500 vfuncs you have a problem and i don't support you :) 89 | if (!IsValidPtr((void*)m_origVtable[count]) || count > 500) 90 | break; 91 | } 92 | return count; 93 | } 94 | -------------------------------------------------------------------------------- /polyhook2/Detour/ILCallback.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYHOOK_2_0_ILCALLBACK_HPP 2 | #define POLYHOOK_2_0_ILCALLBACK_HPP 3 | 4 | #pragma warning(push, 0) 5 | #include 6 | #pragma warning( pop ) 7 | 8 | #pragma warning( disable : 4200) 9 | #include "polyhook2/PolyHookOs.hpp" 10 | #include "polyhook2/ErrorLog.hpp" 11 | #include "polyhook2/Enums.hpp" 12 | #include "polyhook2/MemAccessor.hpp" 13 | 14 | namespace PLH { 15 | class ILCallback : public MemAccessor { 16 | public: 17 | struct Parameters { 18 | template 19 | void setArg(const uint8_t idx, const T val) const { 20 | *(T*)getArgPtr(idx) = val; 21 | } 22 | 23 | template 24 | T getArg(const uint8_t idx) const { 25 | return *(T*)getArgPtr(idx); 26 | } 27 | 28 | // asm depends on this specific type 29 | // we the ILCallback allocates stack space that is set to point here 30 | volatile uint64_t m_arguments; 31 | private: 32 | // must be char* for aliasing rules to work when reading back out 33 | char* getArgPtr(const uint8_t idx) const { 34 | return ((char*)&m_arguments) + sizeof(uint64_t) * idx; 35 | } 36 | }; 37 | 38 | struct ReturnValue { 39 | unsigned char* getRetPtr() const { 40 | return (unsigned char*)&m_retVal; 41 | } 42 | uint64_t m_retVal; 43 | }; 44 | 45 | typedef void(*tUserCallback)(const Parameters* params, const uint8_t count, const ReturnValue* ret); 46 | 47 | ILCallback(); 48 | ~ILCallback(); 49 | 50 | /* Construct a callback given the raw signature at runtime. 'Callback' param is the C stub to transfer to, 51 | where parameters can be modified through a structure which is written back to the parameter slots depending 52 | on calling convention.*/ 53 | uint64_t getJitFunc(const asmjit::FuncSignature& sig, const asmjit::Arch arch, const tUserCallback callback); 54 | 55 | /* Construct a callback given the typedef as a string. Types are any valid C/C++ data type (basic types), and pointers to 56 | anything are just a uintptr_t. Calling convention is defaulted to whatever is typical for the compiler you use, you can override with 57 | stdcall, fastcall, or cdecl (cdecl is default on x86). On x64 those map to the same thing.*/ 58 | uint64_t getJitFunc(const std::string& retType, const std::vector& paramTypes, const asmjit::Arch arch, const tUserCallback callback, std::string callConv = ""); 59 | uint64_t* getTrampolineHolder(); 60 | private: 61 | // does a given type fit in a general purpose register (i.e. is it integer type) 62 | bool isGeneralReg(const asmjit::TypeId typeId) const; 63 | // float, double, simd128 64 | bool isXmmReg(const asmjit::TypeId typeId) const; 65 | 66 | asmjit::CallConvId getCallConv(const std::string& conv); 67 | asmjit::TypeId getTypeId(const std::string& type); 68 | 69 | uint64_t m_callbackBuf; 70 | asmjit::x86::Mem argsStack; 71 | 72 | // ptr to trampoline allocated by hook, we hold this so user doesn't need to. 73 | uint64_t m_trampolinePtr; 74 | }; 75 | } 76 | #endif // POLYHOOK_2_0_ILCALLBACK_HPP 77 | -------------------------------------------------------------------------------- /UnitTests/windows/TestMemProtector.cpp: -------------------------------------------------------------------------------- 1 | #include "Catch.hpp" 2 | #include "polyhook2/MemProtector.hpp" 3 | #include "polyhook2/Tests/StackCanary.hpp" 4 | 5 | #if defined(POLYHOOK2_OS_WINDOWS) 6 | 7 | #include "polyhook2/PolyHookOsIncludes.hpp" 8 | 9 | TEST_CASE("Test protflag translation", "[MemProtector],[Enums]") { 10 | SECTION("flags to native") { 11 | PLH::StackCanary canary; 12 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::X) == PAGE_EXECUTE); 13 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::R) == PAGE_READONLY); 14 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::W) == PAGE_READWRITE); 15 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::R | PLH::ProtFlag::W) == PAGE_READWRITE); 16 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::X | PLH::ProtFlag::R) == PAGE_EXECUTE_READ); 17 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::X | PLH::ProtFlag::W) == PAGE_EXECUTE_READWRITE); 18 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::X | PLH::ProtFlag::W | PLH::ProtFlag::R) == PAGE_EXECUTE_READWRITE); 19 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::NONE) == PAGE_NOACCESS); 20 | } 21 | 22 | SECTION("native to flags") { 23 | PLH::StackCanary canary; 24 | REQUIRE(PLH::TranslateProtection(PAGE_EXECUTE) == PLH::ProtFlag::X); 25 | REQUIRE(PLH::TranslateProtection(PAGE_READONLY) == PLH::ProtFlag::R); 26 | REQUIRE(PLH::TranslateProtection(PAGE_READWRITE) == (PLH::ProtFlag::W | PLH::ProtFlag::R)); 27 | REQUIRE(PLH::TranslateProtection(PAGE_EXECUTE_READ) == (PLH::ProtFlag::X | PLH::ProtFlag::R)); 28 | REQUIRE(PLH::TranslateProtection(PAGE_EXECUTE_READWRITE) == (PLH::ProtFlag::X | PLH::ProtFlag::W | PLH::ProtFlag::R)); 29 | REQUIRE(PLH::TranslateProtection(PAGE_NOACCESS) == PLH::ProtFlag::NONE); 30 | } 31 | } 32 | 33 | TEST_CASE("Test setting page protections", "[MemProtector]") { 34 | PLH::StackCanary canary; 35 | char* page = (char*)VirtualAlloc(0, 4 * 1024, MEM_COMMIT, PAGE_NOACCESS); 36 | bool isGood = page != nullptr; // indirection because catch reads var, causing access violation 37 | REQUIRE(isGood); 38 | PLH::MemAccessor accessor; 39 | 40 | { 41 | PLH::MemoryProtector prot((uint64_t)page, 4 * 1024, PLH::ProtFlag::R, accessor); 42 | REQUIRE(prot.isGood()); 43 | REQUIRE(prot.originalProt() == PLH::ProtFlag::NONE); 44 | 45 | PLH::MemoryProtector prot1((uint64_t)page, 4 * 1024, PLH::ProtFlag::W, accessor); 46 | REQUIRE(prot1.isGood()); 47 | REQUIRE(prot1.originalProt() == PLH::ProtFlag::R); 48 | 49 | PLH::MemoryProtector prot2((uint64_t)page, 4 * 1024, PLH::ProtFlag::X, accessor); 50 | REQUIRE(prot2.isGood()); 51 | REQUIRE((prot2.originalProt() & PLH::ProtFlag::W)); 52 | } 53 | 54 | // protection should now be NOACCESS if destructors worked 55 | { 56 | PLH::MemoryProtector prot((uint64_t)page, 4 * 1024, PLH::ProtFlag::X | PLH::ProtFlag::R, accessor); 57 | REQUIRE(prot.isGood()); 58 | REQUIRE(prot.originalProt() == PLH::ProtFlag::NONE); 59 | 60 | PLH::MemoryProtector prot1((uint64_t)page, 4 * 1024, PLH::ProtFlag::X | PLH::ProtFlag::W, accessor); 61 | REQUIRE(prot.isGood()); 62 | REQUIRE((prot1.originalProt() == (PLH::ProtFlag::X | PLH::ProtFlag::R))); 63 | 64 | PLH::MemoryProtector prot2((uint64_t)page, 4 * 1024, PLH::ProtFlag::X | PLH::ProtFlag::R | PLH::ProtFlag::W, accessor); 65 | REQUIRE(prot.isGood()); 66 | REQUIRE(prot2.originalProt() == (PLH::ProtFlag::X | PLH::ProtFlag::R | PLH::ProtFlag::W)); 67 | } 68 | VirtualFree(page, 0, MEM_RELEASE); 69 | } 70 | 71 | #endif -------------------------------------------------------------------------------- /sources/HWBreakPointHook.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/Exceptions/HWBreakPointHook.hpp" 2 | 3 | PLH::HWBreakPointHook::HWBreakPointHook(const uint64_t fnAddress, const uint64_t fnCallback, HANDLE hThread) : AVehHook() { 4 | m_fnCallback = fnCallback; 5 | m_fnAddress = fnAddress; 6 | 7 | auto entry = AVehHookImpEntry(fnAddress, this); 8 | assert(m_impls.find(entry) == m_impls.end()); 9 | m_impls.insert(entry); 10 | 11 | m_hThread = hThread; 12 | } 13 | 14 | PLH::HWBreakPointHook::HWBreakPointHook(const char* fnAddress, const char* fnCallback, HANDLE hThread) : AVehHook() { 15 | m_fnCallback = (uint64_t)fnCallback; 16 | m_fnAddress = (uint64_t)fnAddress; 17 | 18 | auto entry = AVehHookImpEntry((uint64_t)fnAddress, this); 19 | assert(m_impls.find(entry) == m_impls.end()); 20 | m_impls.insert(entry); 21 | 22 | m_hThread = hThread; 23 | } 24 | 25 | bool PLH::HWBreakPointHook::hook() 26 | { 27 | CONTEXT ctx; 28 | ZeroMemory(&ctx, sizeof(ctx)); 29 | ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; 30 | if (!GetThreadContext(m_hThread, &ctx)) { 31 | Log::log("Failed to get thread context", ErrorLevel::SEV); 32 | return false; 33 | } 34 | 35 | bool freeReg = false; 36 | for (m_regIdx = 0; m_regIdx < 4; m_regIdx++) { 37 | if ((ctx.Dr7 & (1ULL << (m_regIdx * 2))) == 0) { 38 | freeReg = true; 39 | break; 40 | } 41 | } 42 | 43 | if (!freeReg) { 44 | Log::log("All HW BP's are used", ErrorLevel::SEV); 45 | return false; 46 | } 47 | 48 | assert(m_regIdx < 4); 49 | 50 | switch (m_regIdx) { 51 | case 0: 52 | ctx.Dr0 = (decltype(ctx.Dr0))m_fnAddress; 53 | break; 54 | case 1: 55 | ctx.Dr1 = (decltype(ctx.Dr1))m_fnAddress; 56 | break; 57 | case 2: 58 | ctx.Dr2 = (decltype(ctx.Dr2))m_fnAddress; 59 | break; 60 | case 3: 61 | ctx.Dr3 = (decltype(ctx.Dr3))m_fnAddress; 62 | break; 63 | } 64 | 65 | ctx.Dr7 &= ~(3ULL << (16 + 4 * m_regIdx)); //00b at 16-17, 20-21, 24-25, 28-29 is execute bp 66 | ctx.Dr7 &= ~(3ULL << (18 + 4 * m_regIdx)); // size of 1 (val 0), at 18-19, 22-23, 26-27, 30-31 67 | ctx.Dr7 |= 1ULL << (2 * m_regIdx); 68 | 69 | // undefined, suspendthread needed 70 | if (!SetThreadContext(m_hThread, &ctx)) { 71 | Log::log("Failed to set thread context", ErrorLevel::SEV); 72 | } 73 | 74 | m_hooked = true; 75 | return true; 76 | } 77 | 78 | bool PLH::HWBreakPointHook::unHook() { 79 | assert(m_hooked); 80 | if (!m_hooked) { 81 | Log::log("HWBPHook unhook failed: no hook present", ErrorLevel::SEV); 82 | return false; 83 | } 84 | 85 | CONTEXT ctx; 86 | ZeroMemory(&ctx, sizeof(ctx)); 87 | ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; 88 | if (!GetThreadContext(m_hThread, &ctx)) { 89 | Log::log("Failed to get thread context", ErrorLevel::SEV); 90 | return false; 91 | } 92 | 93 | ctx.Dr7 &= ~(1ULL << (2 * m_regIdx)); 94 | 95 | //Still need to call suspend thread 96 | if (!SetThreadContext(m_hThread, &ctx)) { 97 | Log::log("Failed to set thread context", ErrorLevel::SEV); 98 | return false; 99 | } 100 | m_hooked = false; 101 | return true; 102 | } 103 | 104 | LONG PLH::HWBreakPointHook::OnException(EXCEPTION_POINTERS* ExceptionInfo) { 105 | if (ExceptionInfo->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP) 106 | return EXCEPTION_CONTINUE_SEARCH; 107 | 108 | ExceptionInfo->ContextRecord->Dr7 &= ~(1ULL << (2 * m_regIdx)); 109 | ExceptionInfo->ContextRecord->XIP = (decltype(ExceptionInfo->ContextRecord->XIP))m_fnCallback; 110 | return EXCEPTION_CONTINUE_EXECUTION; 111 | } 112 | 113 | -------------------------------------------------------------------------------- /UnitTests/linux/TestMemProtector.cpp: -------------------------------------------------------------------------------- 1 | #include "Catch.hpp" 2 | #include "polyhook2/MemProtector.hpp" 3 | #include "polyhook2/Tests/StackCanary.hpp" 4 | 5 | #include "polyhook2/PolyHookOsIncludes.hpp" 6 | 7 | TEST_CASE("Test protflag translation", "[MemProtector],[Enums]") { 8 | SECTION("flags to native") { 9 | PLH::StackCanary canary; 10 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::X) == PROT_EXEC); 11 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::R) == PROT_READ); 12 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::W) == PROT_WRITE); 13 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::R | PLH::ProtFlag::W) == (PROT_READ|PROT_WRITE)); 14 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::X | PLH::ProtFlag::R) == (PROT_EXEC|PROT_READ)); 15 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::X | PLH::ProtFlag::W) == (PROT_EXEC|PROT_WRITE)); 16 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::X | PLH::ProtFlag::W | PLH::ProtFlag::R) == (PROT_EXEC|PROT_WRITE|PROT_READ)); 17 | REQUIRE(PLH::TranslateProtection(PLH::ProtFlag::NONE) == PROT_NONE); 18 | } 19 | 20 | SECTION("native to flags") { 21 | PLH::StackCanary canary; 22 | REQUIRE(PLH::TranslateProtection(PROT_EXEC) == PLH::ProtFlag::X); 23 | REQUIRE(PLH::TranslateProtection(PROT_READ) == PLH::ProtFlag::R); 24 | REQUIRE(PLH::TranslateProtection(PROT_WRITE) == PLH::ProtFlag::W); 25 | REQUIRE(PLH::TranslateProtection(PROT_WRITE|PROT_READ) == (PLH::ProtFlag::W | PLH::ProtFlag::R)); 26 | REQUIRE(PLH::TranslateProtection(PROT_EXEC|PROT_READ) == (PLH::ProtFlag::X | PLH::ProtFlag::R)); 27 | REQUIRE(PLH::TranslateProtection(PROT_EXEC|PROT_WRITE) == (PLH::ProtFlag::X | PLH::ProtFlag::W)); 28 | REQUIRE(PLH::TranslateProtection(PROT_EXEC|PROT_WRITE|PROT_READ) == (PLH::ProtFlag::X | PLH::ProtFlag::W | PLH::ProtFlag::R)); 29 | REQUIRE(PLH::TranslateProtection(PROT_NONE) == PLH::ProtFlag::NONE); 30 | } 31 | } 32 | 33 | TEST_CASE("Test setting page protections", "[MemProtector]") { 34 | PLH::StackCanary canary; 35 | char* page = (char*)mmap(nullptr, 4*1024, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 36 | bool isGood = page != nullptr; // indirection because catch reads var, causing access violation 37 | REQUIRE(isGood); 38 | PLH::MemAccessor accessor; 39 | 40 | { 41 | PLH::MemoryProtector prot((uint64_t)page, 4 * 1024, PLH::ProtFlag::R, accessor); 42 | REQUIRE(prot.isGood()); 43 | REQUIRE(prot.originalProt() == PLH::ProtFlag::NONE); 44 | 45 | PLH::MemoryProtector prot1((uint64_t)page, 4 * 1024, PLH::ProtFlag::W, accessor); 46 | REQUIRE(prot1.isGood()); 47 | REQUIRE(prot1.originalProt() == PLH::ProtFlag::R); 48 | 49 | PLH::MemoryProtector prot2((uint64_t)page, 4 * 1024, PLH::ProtFlag::X, accessor); 50 | REQUIRE(prot2.isGood()); 51 | REQUIRE((prot2.originalProt() & PLH::ProtFlag::W)); 52 | } 53 | 54 | // protection should now be NOACCESS if destructors worked 55 | { 56 | PLH::MemoryProtector prot((uint64_t)page, 4 * 1024, PLH::ProtFlag::X | PLH::ProtFlag::R, accessor); 57 | REQUIRE(prot.isGood()); 58 | REQUIRE(prot.originalProt() == PLH::ProtFlag::NONE); 59 | 60 | PLH::MemoryProtector prot1((uint64_t)page, 4 * 1024, PLH::ProtFlag::X | PLH::ProtFlag::W, accessor); 61 | REQUIRE(prot.isGood()); 62 | REQUIRE((prot1.originalProt() == (PLH::ProtFlag::X | PLH::ProtFlag::R))); 63 | 64 | PLH::MemoryProtector prot2((uint64_t)page, 4 * 1024, PLH::ProtFlag::X | PLH::ProtFlag::R | PLH::ProtFlag::W, accessor); 65 | REQUIRE(prot.isGood()); 66 | REQUIRE(prot2.originalProt() == (PLH::ProtFlag::X | PLH::ProtFlag::W)); 67 | } 68 | munmap(page, 4*1024); 69 | } 70 | 71 | // TODO: test cases when memory slice spans multiple mappings 72 | -------------------------------------------------------------------------------- /sources/MemProtector.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/MemProtector.hpp" 2 | #include "polyhook2/Enums.hpp" 3 | #include "polyhook2/PolyHookOsIncludes.hpp" 4 | 5 | std::ostream& operator<<(std::ostream& os, const PLH::ProtFlag flags) { 6 | if (flags == PLH::ProtFlag::UNSET) { 7 | os << "UNSET"; 8 | return os; 9 | } 10 | 11 | if (flags & PLH::ProtFlag::X) 12 | os << "x"; 13 | else 14 | os << "-"; 15 | 16 | if (flags & PLH::ProtFlag::R) 17 | os << "r"; 18 | else 19 | os << "-"; 20 | 21 | if (flags & PLH::ProtFlag::W) 22 | os << "w"; 23 | else 24 | os << "-"; 25 | 26 | if (flags & PLH::ProtFlag::NONE) 27 | os << "n"; 28 | else 29 | os << "-"; 30 | 31 | if (flags & PLH::ProtFlag::P) 32 | os << " private"; 33 | else if (flags & PLH::ProtFlag::S) 34 | os << " shared"; 35 | return os; 36 | } 37 | 38 | #if defined(POLYHOOK2_OS_WINDOWS) 39 | 40 | int PLH::TranslateProtection(const PLH::ProtFlag flags) { 41 | int NativeFlag = 0; 42 | if (flags == PLH::ProtFlag::X) 43 | NativeFlag = PAGE_EXECUTE; 44 | 45 | if (flags == PLH::ProtFlag::R) 46 | NativeFlag = PAGE_READONLY; 47 | 48 | if (flags == PLH::ProtFlag::W || (flags == (PLH::ProtFlag::R | PLH::ProtFlag::W))) 49 | NativeFlag = PAGE_READWRITE; 50 | 51 | if ((flags & PLH::ProtFlag::X) && (flags & PLH::ProtFlag::R)) 52 | NativeFlag = PAGE_EXECUTE_READ; 53 | 54 | if ((flags & PLH::ProtFlag::X) && (flags & PLH::ProtFlag::W)) 55 | NativeFlag = PAGE_EXECUTE_READWRITE; 56 | 57 | if (flags & PLH::ProtFlag::NONE) 58 | NativeFlag = PAGE_NOACCESS; 59 | return NativeFlag; 60 | } 61 | 62 | PLH::ProtFlag PLH::TranslateProtection(const int prot) { 63 | PLH::ProtFlag flags = PLH::ProtFlag::UNSET; 64 | switch (prot) { 65 | case PAGE_EXECUTE: 66 | flags = flags | PLH::ProtFlag::X; 67 | break; 68 | case PAGE_READONLY: 69 | flags = flags | PLH::ProtFlag::R; 70 | break; 71 | case PAGE_READWRITE: 72 | flags = flags | PLH::ProtFlag::W; 73 | flags = flags | PLH::ProtFlag::R; 74 | break; 75 | case PAGE_EXECUTE_READWRITE: 76 | flags = flags | PLH::ProtFlag::X; 77 | flags = flags | PLH::ProtFlag::R; 78 | flags = flags | PLH::ProtFlag::W; 79 | break; 80 | case PAGE_EXECUTE_READ: 81 | flags = flags | PLH::ProtFlag::X; 82 | flags = flags | PLH::ProtFlag::R; 83 | break; 84 | case PAGE_NOACCESS: 85 | flags = flags | PLH::ProtFlag::NONE; 86 | break; 87 | } 88 | return flags; 89 | } 90 | 91 | #elif defined(POLYHOOK2_OS_LINUX) 92 | 93 | int PLH::TranslateProtection(const PLH::ProtFlag flags) { 94 | int NativeFlag = PROT_NONE; 95 | if (flags & PLH::ProtFlag::X) 96 | NativeFlag |= PROT_EXEC; 97 | 98 | if (flags & PLH::ProtFlag::R) 99 | NativeFlag |= PROT_READ; 100 | 101 | if (flags & PLH::ProtFlag::W) 102 | NativeFlag |= PROT_WRITE; 103 | 104 | if (flags & PLH::ProtFlag::NONE) 105 | NativeFlag = PROT_NONE; 106 | 107 | return NativeFlag; 108 | } 109 | 110 | PLH::ProtFlag PLH::TranslateProtection(const int prot) { 111 | PLH::ProtFlag flags = PLH::ProtFlag::UNSET; 112 | 113 | if(prot & PROT_EXEC) 114 | flags = flags | PLH::ProtFlag::X; 115 | 116 | if (prot & PROT_READ) 117 | flags = flags | PLH::ProtFlag::R; 118 | 119 | if (prot & PROT_WRITE) 120 | flags = flags | PLH::ProtFlag::W; 121 | 122 | if (prot == PROT_NONE) 123 | flags = flags | PLH::ProtFlag::NONE; 124 | 125 | return flags; 126 | } 127 | 128 | #elif defined(POLYHOOK2_OS_APPLE) 129 | 130 | int PLH::TranslateProtection(const PLH::ProtFlag flags) { 131 | int NativeFlag = VM_PROT_NONE; 132 | if (flags & PLH::ProtFlag::X) 133 | NativeFlag |= PROT_EXEC; 134 | 135 | if (flags & PLH::ProtFlag::R) 136 | NativeFlag |= PROT_READ; 137 | 138 | if (flags & PLH::ProtFlag::W) 139 | NativeFlag |= PROT_WRITE; 140 | 141 | if (flags & PLH::ProtFlag::NONE) 142 | NativeFlag = PROT_NONE; 143 | 144 | return NativeFlag; 145 | } 146 | 147 | PLH::ProtFlag PLH::TranslateProtection(const int prot) { 148 | PLH::ProtFlag flags = PLH::ProtFlag::UNSET; 149 | 150 | if (prot & VM_PROT_EXECUTE) 151 | flags = flags | PLH::ProtFlag::X; 152 | 153 | if (prot & VM_PROT_READ) 154 | flags = flags | PLH::ProtFlag::R; 155 | 156 | if (prot & VM_PROT_WRITE) 157 | flags = flags | PLH::ProtFlag::W; 158 | 159 | if (prot == VM_PROT_NONE) 160 | flags = flags | PLH::ProtFlag::NONE; 161 | 162 | return flags; 163 | } 164 | 165 | #endif -------------------------------------------------------------------------------- /sources/FBAllocator.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/FBAllocator.hpp" 2 | #include "polyhook2/PolyHookOsIncludes.hpp" 3 | 4 | void* ALLOC_NewBlock(ALLOC_Allocator* alloc); 5 | void ALLOC_Push(ALLOC_Allocator* alloc, void* pBlock); 6 | void* ALLOC_Pop(ALLOC_Allocator* alloc); 7 | 8 | //---------------------------------------------------------------------------- 9 | // ALLOC_NewBlock 10 | //---------------------------------------------------------------------------- 11 | void* ALLOC_NewBlock(ALLOC_Allocator* self) 12 | { 13 | ALLOC_Block* pBlock = NULL; 14 | 15 | // If we have not exceeded the pool maximum 16 | if (self->poolIndex < self->maxBlocks) 17 | { 18 | // Get pointer to a new fixed memory block within the pool 19 | pBlock = (ALLOC_Block*)(self->pPool + (self->poolIndex++ * self->blockSize)); 20 | } 21 | 22 | return pBlock; 23 | } 24 | 25 | //---------------------------------------------------------------------------- 26 | // ALLOC_Push 27 | //---------------------------------------------------------------------------- 28 | void ALLOC_Push(ALLOC_Allocator* self, void* pBlock) 29 | { 30 | if (!pBlock) 31 | return; 32 | 33 | // Get a pointer to the client's location within the block 34 | ALLOC_Block* pClient = (ALLOC_Block*)pBlock; 35 | 36 | // Point client block's next pointer to head 37 | pClient->pNext = self->pHead; 38 | 39 | // The client block is now the new head 40 | self->pHead = pClient; 41 | } 42 | 43 | //---------------------------------------------------------------------------- 44 | // ALLOC_Pop 45 | //---------------------------------------------------------------------------- 46 | void* ALLOC_Pop(ALLOC_Allocator* self) 47 | { 48 | ALLOC_Block* pBlock = NULL; 49 | 50 | // Is the free-list empty? 51 | if (self->pHead) 52 | { 53 | // Remove the head block 54 | pBlock = self->pHead; 55 | 56 | // Set the head to the next block 57 | self->pHead = (ALLOC_Block*)self->pHead->pNext; 58 | } 59 | 60 | return pBlock; 61 | } 62 | 63 | //---------------------------------------------------------------------------- 64 | // ALLOC_Alloc 65 | //---------------------------------------------------------------------------- 66 | void* ALLOC_Alloc(ALLOC_HANDLE hAlloc, size_t size) 67 | { 68 | ALLOC_Allocator* self = NULL; 69 | void* pBlock = NULL; 70 | 71 | assert(hAlloc); 72 | 73 | // Convert handle to an ALLOC_Allocator instance 74 | self = (ALLOC_Allocator*)hAlloc; 75 | 76 | // Ensure requested size fits within memory block 77 | assert(size <= self->blockSize); 78 | 79 | // Get a block from the free-list 80 | pBlock = ALLOC_Pop(self); 81 | 82 | // If the free-list empty? 83 | if (!pBlock) 84 | { 85 | // Get a new block from the pool 86 | pBlock = ALLOC_NewBlock(self); 87 | } 88 | 89 | if (pBlock) 90 | { 91 | // Keep track of usage statistics 92 | self->allocations++; 93 | self->blocksInUse++; 94 | if (self->blocksInUse > self->maxBlocksInUse) 95 | { 96 | self->maxBlocksInUse = self->blocksInUse; 97 | } 98 | } 99 | return pBlock; 100 | } 101 | 102 | //---------------------------------------------------------------------------- 103 | // ALLOC_Calloc 104 | //---------------------------------------------------------------------------- 105 | void* ALLOC_Calloc(ALLOC_HANDLE hAlloc, size_t num, size_t size) 106 | { 107 | void* pMem = NULL; 108 | size_t n = 0; 109 | 110 | assert(hAlloc); 111 | 112 | // Compute the total size of the block 113 | n = num * size; 114 | 115 | // Allocate the memory 116 | pMem = ALLOC_Alloc(hAlloc, n); 117 | 118 | if (pMem) 119 | { 120 | memset(pMem, 0, n); 121 | } 122 | return pMem; 123 | } 124 | 125 | //---------------------------------------------------------------------------- 126 | // ALLOC_Free 127 | //---------------------------------------------------------------------------- 128 | void ALLOC_Free(ALLOC_HANDLE hAlloc, void* pBlock) 129 | { 130 | ALLOC_Allocator* self = NULL; 131 | 132 | if (!pBlock) 133 | return; 134 | 135 | assert(hAlloc); 136 | 137 | // Cast handle to an allocator instance 138 | self = (ALLOC_Allocator*)hAlloc; 139 | 140 | // Push the block onto a stack (i.e. the free-list) 141 | ALLOC_Push(self, pBlock); 142 | 143 | // Keep track of usage statistics 144 | self->deallocations++; 145 | self->blocksInUse--; 146 | } 147 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Win MSVC, Linux GCC, Linux Clang 2 | 3 | on: [ push, pull_request, workflow_dispatch ] 4 | 5 | jobs: 6 | build-and-test: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | # We want to see all failing combinations, not just the first one. 11 | fail-fast: false 12 | 13 | # This matrix sets up 16 combinations, which are a cross product of: OS, Build Type, Bitness and Compiler 14 | # Then it excludes 4 combinations where OS is Windows and compiler is Clang. It will be included in the future. 15 | # This results in the following 12 combinations: 16 | # windows-debug-32-msvc | windows-debug-64-msvc | windows-release-32-msvc | windows-release-64-msvc 17 | # linux-debug-32-gcc | linux-debug-64-gcc | linux-release-32-gcc | linux-release-64-gcc 18 | # linux-debug-32-clang | linux-debug-64-clang | linux-release-32-clang | linux-release-64-clang 19 | matrix: 20 | os: [ ubuntu-latest, windows-latest ] 21 | build_type: [ Debug, Release ] 22 | bitness: [ 32, 64 ] 23 | compiler: [ native, clang ] 24 | exclude: 25 | # TODO: Add this in the future 26 | - os: windows-latest 27 | compiler: clang 28 | include: 29 | # Windows 30 | - os: windows-latest 31 | build_type: Debug 32 | test_bin_path: Debug/PolyHook_2.exe 33 | 34 | - os: windows-latest 35 | build_type: Release 36 | test_bin_path: Release/PolyHook_2.exe 37 | 38 | - os: windows-latest 39 | bitness: 32 40 | arch: -A Win32 41 | 42 | - os: windows-latest 43 | bitness: 64 44 | arch: -A x64 45 | 46 | ## MSVC 47 | - os: windows-latest 48 | compiler: native 49 | c_compiler: cl 50 | cpp_compiler: cl 51 | 52 | # Linux 53 | - os: ubuntu-latest 54 | test_bin_path: PolyHook_2 55 | 56 | - os: ubuntu-latest 57 | bitness: 32 58 | arch: -DCMAKE_C_FLAGS="-m32" -DCMAKE_CXX_FLAGS="-m32" 59 | 60 | - os: ubuntu-latest 61 | bitness: 64 62 | arch: -DCMAKE_C_FLAGS="-m64" -DCMAKE_CXX_FLAGS="-m64" 63 | 64 | ## GCC 65 | - os: ubuntu-latest 66 | compiler: native 67 | c_compiler: gcc 68 | cpp_compiler: g++ 69 | 70 | ## Clang 71 | - os: ubuntu-latest 72 | compiler: clang 73 | c_compiler: clang 74 | cpp_compiler: clang++ 75 | 76 | env: 77 | BUILD_DIR: "${{ github.workspace }}/build" 78 | 79 | steps: 80 | # The default GitHub runner is missing dependencies for cross-compilation 81 | - name: Install 32-bit compiler toolchain 82 | if: ${{ matrix.bitness == '32' && matrix.os == 'ubuntu-latest' }} 83 | run: sudo apt update && sudo apt install gcc-multilib g++-multilib 84 | 85 | - uses: actions/checkout@v5 86 | with: 87 | submodules: 'recursive' 88 | 89 | - name: Configure CMake for build 90 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if 91 | # you are using a single-configuration generator such as make. 92 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 93 | run: > 94 | cmake -B ${{ env.BUILD_DIR }} 95 | -DPOLYHOOK_BUILD_DLL="ON" 96 | -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} 97 | -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} 98 | -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} 99 | ${{ matrix.arch }} 100 | 101 | - name: Build 102 | # Build your program with the given configuration. Note that --config is needed because 103 | # the default Windows generator is a multi-config generator (Visual Studio generator). 104 | run: cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_type }} 105 | 106 | - name: Configure CMake for test 107 | run: > 108 | cmake -B ${{ env.BUILD_DIR }} 109 | -DPOLYHOOK_BUILD_DLL="OFF" 110 | 111 | - name: Build tests 112 | id: build-tests 113 | if: ${{ matrix.c_compiler != 'gcc' }} # See PLH_TEST_CALLBACK in TestUtils.hpp for the reason 114 | run: cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_type }} --target PolyHook_2 115 | 116 | - name: Run tests 117 | if: ${{ steps.build-tests.outcome != 'skipped' }} 118 | run: ${{ env.BUILD_DIR }}/${{ matrix.test_bin_path }} 119 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /sources/RangeAllocator.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/RangeAllocator.hpp" 2 | #include "polyhook2/PolyHookOsIncludes.hpp" 3 | 4 | PLH::FBAllocator::FBAllocator(uint64_t min, uint64_t max, uint8_t blockSize, uint8_t blockCount) : m_allocator(nullptr), m_hAllocator(0) { 5 | m_min = min; 6 | m_max = max; 7 | m_dataPool = 0; 8 | m_maxBlocks = blockCount; 9 | m_usedBlocks = 0; 10 | m_blockSize = blockSize; 11 | m_alloc2Supported = boundedAllocSupported(); 12 | } 13 | 14 | PLH::FBAllocator::~FBAllocator() 15 | { 16 | uint64_t freeSize = 0; 17 | 18 | if (m_allocator) { 19 | freeSize = m_allocator->blockSize * m_allocator->maxBlocks; 20 | delete m_allocator; 21 | m_allocator = 0; 22 | m_hAllocator = 0; 23 | } 24 | 25 | if(m_dataPool) { 26 | boundAllocFree(m_dataPool, freeSize); 27 | m_dataPool = 0; 28 | } 29 | } 30 | 31 | bool PLH::FBAllocator::initialize() 32 | { 33 | uint64_t alignment = getAllocationAlignment(); 34 | uint64_t start = (uint64_t)AlignUpwards(m_min, (size_t)alignment); 35 | uint64_t end = (uint64_t)AlignDownwards(m_max, (size_t)alignment); 36 | 37 | if (m_alloc2Supported) { 38 | // alignment shrinks area by aligning both towards middle so we don't allocate beyond the given bounds 39 | m_dataPool = boundAlloc(start, end, ALLOC_BLOCK_SIZE(m_blockSize) * (uint64_t)m_maxBlocks); 40 | if (!m_dataPool) { 41 | return false; 42 | } 43 | } else { 44 | m_dataPool = boundAllocLegacy(start, end, ALLOC_BLOCK_SIZE(m_blockSize) * (uint64_t)m_maxBlocks); 45 | if (!m_dataPool) { 46 | return false; 47 | } 48 | } 49 | 50 | m_allocator = new ALLOC_Allocator{ "PLH", (char*)m_dataPool, 51 | m_blockSize, ALLOC_BLOCK_SIZE(m_blockSize), m_maxBlocks, NULL, 0, 0, 0, 0, 0}; 52 | if (!m_allocator) { 53 | return false; 54 | } 55 | 56 | m_hAllocator = m_allocator; 57 | return true; 58 | } 59 | 60 | char* PLH::FBAllocator::allocate() 61 | { 62 | if (m_usedBlocks + 1 == m_maxBlocks) { 63 | return 0; 64 | } 65 | m_usedBlocks++; 66 | return (char*)ALLOC_Alloc(m_hAllocator, m_blockSize); 67 | } 68 | 69 | char* PLH::FBAllocator::callocate(uint8_t num) 70 | { 71 | m_usedBlocks += num; 72 | return (char*)ALLOC_Calloc(m_hAllocator, num, m_blockSize); 73 | } 74 | 75 | void PLH::FBAllocator::deallocate(char* mem) 76 | { 77 | m_usedBlocks--; 78 | ALLOC_Free(m_hAllocator, mem); 79 | } 80 | 81 | bool PLH::FBAllocator::inRange(uint64_t addr) 82 | { 83 | if (addr >= m_min && addr < m_max) { 84 | return true; 85 | } 86 | return false; 87 | } 88 | 89 | bool PLH::FBAllocator::intersectsRange(uint64_t min, uint64_t max) 90 | { 91 | uint64_t _min = std::max(m_min, min); 92 | uint64_t _max = std::min(m_max, max); 93 | if (_min <= _max) 94 | return true; 95 | return false; 96 | } 97 | 98 | uint8_t PLH::FBAllocator::intersectionLoadFactor(uint64_t min, uint64_t max) 99 | { 100 | assert(intersectsRange(min, max)); 101 | uint64_t _min = std::max(m_min, min); 102 | uint64_t _max = std::min(m_max, max); 103 | double intersectLength = (double)(_max - _min); 104 | return (uint8_t)((intersectLength / (max - min)) * 100.0); 105 | } 106 | 107 | PLH::RangeAllocator::RangeAllocator(uint8_t blockSize, uint8_t blockCount) 108 | { 109 | m_maxBlocks = blockCount; 110 | m_blockSize = blockSize; 111 | } 112 | 113 | std::shared_ptr PLH::RangeAllocator::findOrInsertAllocator(uint64_t min, uint64_t max) 114 | { 115 | for (auto& allocator : m_allocators) { 116 | if (allocator->inRange(min) && allocator->inRange(max - 1)) { 117 | return allocator; 118 | } 119 | } 120 | 121 | auto allocator = std::make_shared(min, max, m_blockSize, m_maxBlocks); 122 | if (!allocator->initialize()) 123 | return nullptr; 124 | 125 | m_allocators.push_back(allocator); 126 | return allocator; 127 | } 128 | 129 | char* PLH::RangeAllocator::allocate(uint64_t min, uint64_t max) 130 | { 131 | static bool is32 = sizeof(void*) == 4; 132 | if (is32 && max > 0x7FFFFFFF) { 133 | max = 0x7FFFFFFF; // allocator apis fail in 32bit above this range 134 | } 135 | 136 | std::lock_guard m_lock(m_mutex); 137 | auto allocator = findOrInsertAllocator(min, max); 138 | if (!allocator) { 139 | return nullptr; 140 | } 141 | 142 | char* addr = allocator->allocate(); 143 | m_allocMap[(uint64_t)addr] = allocator; 144 | return addr; 145 | } 146 | 147 | void PLH::RangeAllocator::deallocate(uint64_t addr) 148 | { 149 | std::lock_guard m_lock(m_mutex); 150 | if (auto it{ m_allocMap.find(addr) }; it != std::end(m_allocMap)) { 151 | auto allocator = it->second; 152 | allocator->deallocate((char*)addr); 153 | m_allocMap.erase(addr); 154 | 155 | // this instance + instance in m_allocators array 156 | if (allocator.use_count() == 2) { 157 | m_allocators.erase(std::remove(m_allocators.begin(), m_allocators.end(), allocator), m_allocators.end()); 158 | } 159 | } else { 160 | assert(false); 161 | } 162 | } -------------------------------------------------------------------------------- /polyhook2/ZydisDisassembler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYHOOK_2_0_ZYDISDISASSEMBLER_HPP 2 | #define POLYHOOK_2_0_ZYDISDISASSEMBLER_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "polyhook2/PolyHookOs.hpp" 8 | #include "polyhook2/Instruction.hpp" 9 | #include "polyhook2/MemAccessor.hpp" 10 | 11 | namespace PLH { 12 | typedef std::unordered_map branch_map_t; 13 | 14 | class ZydisDisassembler { 15 | public: 16 | explicit ZydisDisassembler(PLH::Mode mode); 17 | 18 | virtual ~ZydisDisassembler(); 19 | 20 | std::vector disassemble(uint64_t firstInstruction, uint64_t start, uint64_t end, const MemAccessor& accessor); 21 | 22 | // TODO: Move to accessor 23 | static void writeEncoding(const PLH::insts_t& instructions, const MemAccessor& accessor) { 24 | for (const auto& inst : instructions) 25 | writeEncoding(inst, accessor); 26 | } 27 | 28 | /**Write the raw bytes of the given instruction into the memory specified by the 29 | * instruction's address. If the address value of the instruction has been changed 30 | * since the time it was decoded this will copy the instruction to a new memory address. 31 | * This will not automatically do any code relocation, all relocation logic should 32 | * first modify the byte array, and then call write encoding, proper order to relocate 33 | * an instruction should be disasm instructions -> set relative/absolute displacement() -> 34 | **/ 35 | static void writeEncoding(const Instruction& instruction, const MemAccessor& accessor) { 36 | assert(instruction.size() <= instruction.getBytes().size()); 37 | accessor.mem_copy(instruction.getAddress(), (uint64_t)&instruction.getBytes()[0], instruction.size()); 38 | } 39 | 40 | static bool isConditionalJump(const PLH::Instruction& instruction) { 41 | // http://unixwiz.net/techtips/x86-jumps.html 42 | if (instruction.size() < 1) 43 | return false; 44 | 45 | std::vector bytes = instruction.getBytes(); 46 | if (bytes[0] == 0x0F && instruction.size() > 1) { 47 | if (bytes[1] >= 0x80 && bytes[1] <= 0x8F) 48 | return true; 49 | } 50 | 51 | if (bytes[0] >= 0x70 && bytes[0] <= 0x7F) 52 | return true; 53 | 54 | if (bytes[0] == 0xE3) 55 | return true; 56 | 57 | return false; 58 | } 59 | 60 | static bool isFuncEnd(const PLH::Instruction& instruction, const bool firstFunc = false) { 61 | std::string mnemonic = instruction.getMnemonic(); 62 | auto bytes = instruction.getBytes(); 63 | return (instruction.size() == 1 && bytes[0] == 0xCC) || 64 | (instruction.size() >= 2 && bytes[0] == 0xf3 && bytes[1] == 0xc3) || // rep ret 65 | (instruction.size() >= 2 && bytes[0] == 0xf2 && bytes[1] == 0xc3) || // bnd ret for Intel mpx 66 | (mnemonic == "jmp" && !firstFunc) || // Jump to translation 67 | mnemonic == "ret" || mnemonic.find("iret") == 0; 68 | } 69 | 70 | static bool isPadBytes(const PLH::Instruction& instruction) { 71 | // supports multi-byte nops 72 | return instruction.getMnemonic() == "nop"; 73 | } 74 | 75 | const branch_map_t& getBranchMap() const { 76 | return m_branchMap; 77 | } 78 | 79 | void addToBranchMap(PLH::insts_t& insVec, const PLH::Instruction& inst) 80 | { 81 | if (inst.isBranching()) { 82 | // search back, check if new instruction points to older ones (one to one) 83 | auto destInst = std::find_if(insVec.begin(), insVec.end(), [&](const Instruction& oldIns) { 84 | return oldIns.getAddress() == inst.getDestination(); 85 | }); 86 | 87 | if (destInst != insVec.end()) { 88 | updateBranchMap(destInst->getAddress(), inst); 89 | } 90 | } 91 | 92 | // search forward, check if old instructions now point to new one (many to one possible) 93 | for (const Instruction& oldInst : insVec) { 94 | if (oldInst.isBranching() && oldInst.hasDisplacement() && oldInst.getDestination() == inst.getAddress()) { 95 | updateBranchMap(inst.getAddress(), oldInst); 96 | } 97 | } 98 | } 99 | 100 | inline Mode getMode() const { 101 | return m_mode; 102 | } 103 | protected: 104 | 105 | bool getOpStr(ZydisDecodedInstruction* pInstruction, const ZydisDecodedOperand* decoded_operands, uint64_t addr, std::string* pOpStrOut); 106 | 107 | void setDisplacementFields(PLH::Instruction& inst, const ZydisDecodedInstruction* zydisInst, const ZydisDecodedOperand* operands) const; 108 | 109 | typename branch_map_t::mapped_type& updateBranchMap(uint64_t key, const Instruction& new_val) { 110 | auto it = m_branchMap.find(key); 111 | if (it != m_branchMap.end()) { 112 | it->second.push_back(new_val); 113 | } else { 114 | branch_map_t::mapped_type s; 115 | s.push_back(new_val); 116 | m_branchMap.emplace(key, s); 117 | return m_branchMap.at(key); 118 | } 119 | return it->second; 120 | } 121 | 122 | ZydisDecoder* m_decoder; 123 | ZydisFormatter* m_formatter; 124 | 125 | Mode m_mode; 126 | 127 | /* key = address of instruction pointed at (dest of jump). Value = set of unique instruction branching to dest 128 | Must only hold entries from the last segment disassembled. I.E clear every new call to disassemble 129 | */ 130 | branch_map_t m_branchMap; 131 | }; 132 | } 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /sources/IatHook.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/PE/IatHook.hpp" 2 | 3 | PLH::IatHook::IatHook(const std::string& dllName, const std::string& apiName, const char* fnCallback, uint64_t* userOrigVar, const std::wstring& moduleName) 4 | : IatHook(dllName, apiName, (uint64_t)fnCallback, userOrigVar, moduleName) 5 | {} 6 | 7 | PLH::IatHook::IatHook(const std::string& dllName, const std::string& apiName, const uint64_t fnCallback, uint64_t* userOrigVar, const std::wstring& moduleName) 8 | : m_dllName(dllName) 9 | , m_apiName(apiName) 10 | , m_moduleName(moduleName) 11 | , m_fnCallback(fnCallback) 12 | , m_userOrigVar(userOrigVar) 13 | , m_origFunc(0) 14 | {} 15 | 16 | bool PLH::IatHook::hook() { 17 | assert(m_userOrigVar != nullptr); 18 | IMAGE_THUNK_DATA* pThunk = FindIatThunk(m_dllName, m_apiName, m_moduleName); 19 | if (pThunk == nullptr) 20 | return false; 21 | 22 | // IAT is by default a writeable section 23 | MemoryProtector prot((uint64_t)&pThunk->u1.Function, sizeof(uintptr_t), ProtFlag::R | ProtFlag::W, *this); 24 | m_origFunc = (uint64_t)pThunk->u1.Function; 25 | pThunk->u1.Function = (uintptr_t)m_fnCallback; 26 | m_hooked = true; 27 | *m_userOrigVar = m_origFunc; 28 | return true; 29 | } 30 | 31 | bool PLH::IatHook::unHook() { 32 | assert(m_userOrigVar != nullptr); 33 | assert(m_hooked); 34 | if (!m_hooked) 35 | return false; 36 | 37 | IMAGE_THUNK_DATA* pThunk = FindIatThunk(m_dllName, m_apiName, m_moduleName); 38 | if (pThunk == nullptr) 39 | return false; 40 | 41 | MemoryProtector prot((uint64_t)&pThunk->u1.Function, sizeof(uintptr_t), ProtFlag::R | ProtFlag::W, *this); 42 | pThunk->u1.Function = (uintptr_t)m_origFunc; 43 | m_hooked = false; 44 | *m_userOrigVar = NULL; 45 | return true; 46 | } 47 | 48 | IMAGE_THUNK_DATA* PLH::IatHook::FindIatThunk(const std::string& dllName, const std::string& apiName, const std::wstring& moduleName /* = L"" */) { 49 | #if defined(_WIN64) 50 | PEB* peb = (PPEB)__readgsqword(0x60); 51 | #else 52 | PEB* peb = (PPEB)__readfsdword(0x30); 53 | #endif 54 | 55 | IMAGE_THUNK_DATA* pThunk = nullptr; 56 | auto* ldr = (PPEB_LDR_DATA)peb->Ldr; 57 | 58 | // find loaded module from peb 59 | for (auto* dte = (LDR_DATA_TABLE_ENTRY*)ldr->InLoadOrderModuleList.Flink; 60 | dte->DllBase != nullptr; 61 | dte = (LDR_DATA_TABLE_ENTRY*)dte->InLoadOrderLinks.Flink) { 62 | 63 | // TODO: create stricmp for UNICODE_STRING because this is really bad for performance 64 | std::wstring baseModuleName(dte->BaseDllName.Buffer, dte->BaseDllName.Length / sizeof(wchar_t)); 65 | 66 | // try all modules if none given, otherwise only try specified 67 | if (!moduleName.empty() && (my_wide_stricmp(baseModuleName.c_str(), moduleName.c_str()) != 0)) 68 | continue; 69 | 70 | pThunk = FindIatThunkInModule(dte->DllBase, dllName, apiName); 71 | if (pThunk != nullptr) 72 | return pThunk; 73 | } 74 | 75 | Log::log("Failed to find thunk for api from requested dll", ErrorLevel::SEV); 76 | 77 | return nullptr; 78 | } 79 | 80 | IMAGE_THUNK_DATA* PLH::IatHook::FindIatThunkInModule(void* moduleBase, const std::string& dllName, const std::string& apiName) { 81 | assert(moduleBase != nullptr); 82 | if (moduleBase == nullptr) 83 | return nullptr; 84 | 85 | auto* pDos = (IMAGE_DOS_HEADER*)moduleBase; 86 | auto* pNT = RVA2VA(IMAGE_NT_HEADERS*, moduleBase, pDos->e_lfanew); 87 | auto* pDataDir = (IMAGE_DATA_DIRECTORY*)pNT->OptionalHeader.DataDirectory; 88 | 89 | if (pDataDir[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == NULL) { 90 | Log::log("PEs without import tables are unsupported", ErrorLevel::SEV); 91 | return nullptr; 92 | } 93 | 94 | auto* pImports = (IMAGE_IMPORT_DESCRIPTOR*)RVA2VA(uintptr_t, moduleBase, pDataDir[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 95 | 96 | // import entry with null fields marks end 97 | for (uint_fast16_t i = 0; pImports[i].Name != NULL; i++) { 98 | if(my_narrow_stricmp(RVA2VA(PCHAR, moduleBase, pImports[i].Name), 99 | dllName.c_str()) != 0) 100 | continue; 101 | 102 | // Original holds the API Names 103 | auto pOriginalThunk = (PIMAGE_THUNK_DATA) 104 | RVA2VA(uintptr_t, moduleBase, pImports[i].OriginalFirstThunk); 105 | 106 | // FirstThunk is overwritten by loader with API addresses, we change this 107 | auto pThunk = (PIMAGE_THUNK_DATA) 108 | RVA2VA(uintptr_t, moduleBase, pImports[i].FirstThunk); 109 | 110 | if (!pOriginalThunk) { 111 | Log::log("IAT's without valid original thunk are un-supported", ErrorLevel::SEV); 112 | return nullptr; 113 | } 114 | 115 | // Table is null terminated, increment both tables 116 | for (; pOriginalThunk->u1.Ordinal != NULL; pOriginalThunk++, pThunk++) { 117 | if (IMAGE_SNAP_BY_ORDINAL(pOriginalThunk->u1.Ordinal)) { 118 | //printf("Import By Ordinal:[Ordinal:%d]\n", IMAGE_ORDINAL(pOriginalThunk->u1.Ordinal)); 119 | continue; 120 | } 121 | 122 | auto pImport = (PIMAGE_IMPORT_BY_NAME) 123 | RVA2VA(uintptr_t, moduleBase, pOriginalThunk->u1.AddressOfData); 124 | 125 | if(my_narrow_stricmp(pImport->Name, apiName.c_str()) != 0) 126 | continue; 127 | 128 | return pThunk; 129 | } 130 | } 131 | 132 | Log::log("Thunk not found before end of IAT", ErrorLevel::SEV); 133 | return nullptr; 134 | } -------------------------------------------------------------------------------- /UnitTests/linux/TestDetourx64.cpp: -------------------------------------------------------------------------------- 1 | // NOLINTBEGIN(*-err58-cpp) 2 | 3 | #include 4 | 5 | #include "polyhook2/Detour/x64Detour.hpp" 6 | #include "polyhook2/ZydisDisassembler.hpp" 7 | 8 | #include "polyhook2/Tests/StackCanary.hpp" 9 | #include "polyhook2/Tests/TestEffectTracker.hpp" 10 | 11 | #include "polyhook2/PolyHookOsIncludes.hpp" 12 | 13 | #include "../TestUtils.hpp" 14 | 15 | EffectTracker effects; 16 | 17 | NOINLINE void hookMe1() { 18 | PLH::StackCanary canary; 19 | std::cout << "hookMe1 called" << std::endl; 20 | volatile int var = 1; 21 | volatile int var2 = 0; 22 | var2 += 3; 23 | var2 = var + var2; 24 | var2 *= 30 / 3; 25 | var = 2; 26 | printf("%d %d\n", var, var2); // 2, 40 27 | REQUIRE(var == 2); 28 | REQUIRE(var2 == 40); 29 | } 30 | 31 | PLH_TEST_DETOUR_CALLBACK(hookMe1, { 32 | std::cout << "Hook 1 Called! Trampoline: 0x" << std::hex << hookMe1_trmp << std::endl; 33 | }); 34 | 35 | NOINLINE void hookMe2() { 36 | PLH::StackCanary canary; 37 | for (int i = 0; i < 10; i++) { 38 | printf("%d\n", i); 39 | } 40 | } 41 | 42 | PLH_TEST_DETOUR_CALLBACK(hookMe2, { 43 | std::cout << "Hook 2 Called!" << std::endl; 44 | }); 45 | 46 | unsigned char hookMe3[] = { 47 | 0x57, // push rdi 48 | 0x74, 0xf9, // je -5 49 | 0x74, 0xf0, // je -14 50 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // [x6] nop 51 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // [x6] nop 52 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // [x6] nop 53 | 0xc3 // ret 54 | }; 55 | 56 | unsigned char hookMe4[] = { 57 | 0x57, // push rdi 58 | 0x48, 0x83, 0xec, 0x30, // sub rsp, 0x30 59 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // [x6] nop 60 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // [x6] nop 61 | 0x74, 0xf2, // je 0x0 62 | 0xc3 // ret 63 | }; 64 | 65 | // test call instructions in prologue 66 | unsigned char hookMe5[] = { 67 | 0x48, 0x83, 0xEC, 0x28, // 180009240: sub rsp, 28h 68 | 0xE8, 0x96, 0xA8, 0xFF, 0xFF, // call 180003ADF 69 | 0x48, 0x83, 0xC4, 0x28, // add rsp, 28h 70 | 0x48, 0xFF, 0xA0, 0x20, 0x01, 0x00, 0x00 // jmp qword ptr[rax+120h] 71 | }; 72 | 73 | uint64_t nullTramp = 0; 74 | NOINLINE void h_nullstub() { 75 | PLH::StackCanary canary; 76 | PLH_STOP_OPTIMIZATIONS(); 77 | } 78 | 79 | PLH_TEST_DETOUR_CALLBACK(malloc); 80 | 81 | TEST_CASE("Testing 64 detours", "[x64Detour],[ADetour]") { 82 | PLH::test::registerTestLogger(); 83 | 84 | SECTION("Normal function (VALLOC2)") { 85 | PLH::StackCanary canary; 86 | PLH::x64Detour PLH_TEST_DETOUR(hookMe1); 87 | detour.setDetourScheme(PLH::x64Detour::VALLOC2); 88 | // VALLOC2 is not supported on linux so we expect hooking & unhooking to fail 89 | REQUIRE(detour.hook() == false); 90 | REQUIRE(detour.unHook() == false); 91 | } 92 | 93 | SECTION("Normal function (INPLACE)") { 94 | PLH::StackCanary canary; 95 | PLH::x64Detour PLH_TEST_DETOUR(hookMe1); 96 | detour.setDetourScheme(PLH::x64Detour::INPLACE); 97 | REQUIRE(detour.hook() == true); 98 | 99 | effects.PushEffect(); 100 | hookMe1(); 101 | REQUIRE(effects.PopEffect().didExecute()); 102 | REQUIRE(detour.unHook() == true); 103 | } 104 | 105 | SECTION("Normal function (CODE_CAVE)") { 106 | PLH::StackCanary canary; 107 | PLH::x64Detour PLH_TEST_DETOUR(hookMe1); 108 | detour.setDetourScheme(PLH::x64Detour::CODE_CAVE); 109 | REQUIRE(detour.hook() == true); 110 | 111 | effects.PushEffect(); 112 | hookMe1(); 113 | REQUIRE(effects.PopEffect().didExecute()); 114 | REQUIRE(detour.unHook() == true); 115 | } 116 | 117 | SECTION("Normal function (INPLACE_SHORT)") { 118 | PLH::StackCanary canary; 119 | PLH::x64Detour PLH_TEST_DETOUR(hookMe1); 120 | detour.setDetourScheme(PLH::x64Detour::INPLACE_SHORT); 121 | REQUIRE(detour.hook() == true); 122 | 123 | effects.PushEffect(); 124 | hookMe1(); 125 | REQUIRE(effects.PopEffect().didExecute()); 126 | REQUIRE(detour.unHook() == true); 127 | } 128 | 129 | SECTION("Normal function rehook") { 130 | PLH::StackCanary canary; 131 | PLH::x64Detour PLH_TEST_DETOUR(hookMe1); 132 | REQUIRE(detour.hook() == true); 133 | 134 | effects.PushEffect(); 135 | REQUIRE(detour.reHook() == true); // can only really test this doesn't 136 | // cause memory corruption easily 137 | hookMe1(); 138 | REQUIRE(effects.PopEffect().didExecute()); 139 | REQUIRE(detour.unHook() == true); 140 | } 141 | 142 | SECTION("Loop function") { 143 | PLH::StackCanary canary; 144 | PLH::x64Detour PLH_TEST_DETOUR(hookMe2); 145 | REQUIRE(detour.hook() == true); 146 | 147 | effects.PushEffect(); 148 | hookMe2(); 149 | REQUIRE(effects.PopEffect().didExecute()); 150 | REQUIRE(detour.unHook() == true); 151 | } 152 | 153 | SECTION("Jmp into prol w/src in range") { 154 | PLH::StackCanary canary; 155 | PLH::x64Detour detour((uint64_t)&hookMe3, (uint64_t)&h_nullstub, &nullTramp); 156 | detour.setDetourScheme(PLH::x64Detour::ALL); 157 | REQUIRE(detour.hook() == true); 158 | REQUIRE(detour.unHook() == true); 159 | } 160 | 161 | SECTION("Jmp into prol w/src out of range") { 162 | PLH::StackCanary canary; 163 | PLH::x64Detour detour((uint64_t)&hookMe4, (uint64_t)&h_nullstub, &nullTramp); 164 | 165 | REQUIRE(detour.hook() == true); 166 | REQUIRE(detour.unHook() == true); 167 | } 168 | 169 | SECTION("Call instruction early in prologue") { 170 | PLH::StackCanary canary; 171 | PLH::x64Detour detour((uint64_t)&hookMe5, (uint64_t)&h_nullstub, &nullTramp); 172 | 173 | REQUIRE(detour.hook() == true); 174 | REQUIRE(detour.unHook() == true); 175 | } 176 | 177 | SECTION("Hook malloc") { 178 | PLH::StackCanary canary; 179 | PLH::x64Detour PLH_TEST_DETOUR(malloc); 180 | effects.PushEffect(); // catch does some allocations, push effect first 181 | // so peak works 182 | bool result = detour.hook(); 183 | 184 | REQUIRE(result == true); 185 | 186 | void *pMem = malloc(16); 187 | free(pMem); 188 | detour.unHook(); // unhook so we can popeffect safely w/o catch 189 | // allocation happening again 190 | REQUIRE(effects.PopEffect().didExecute()); 191 | } 192 | } 193 | 194 | // NOLINTEND(*-err58-cpp) 195 | -------------------------------------------------------------------------------- /polyhook2/IHook.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by steve on 4/2/17. 3 | // 4 | 5 | #ifndef POLYHOOK_2_0_IHOOK_HPP 6 | #define POLYHOOK_2_0_IHOOK_HPP 7 | 8 | #include "polyhook2/PolyHookOs.hpp" 9 | #include "polyhook2/ZydisDisassembler.hpp" 10 | #include "polyhook2/Enums.hpp" 11 | #include "polyhook2/MemAccessor.hpp" 12 | #include 13 | 14 | #if defined(__clang__) 15 | #define NOINLINE __attribute__((noinline)) 16 | #define PH_ATTR_NAKED __attribute__((naked)) 17 | #elif defined(__GNUC__) || defined(__GNUG__) 18 | #define NOINLINE __attribute__((noinline)) 19 | #define PH_ATTR_NAKED __attribute__((naked)) 20 | #define OPTS_OFF _Pragma("GCC push_options") \ 21 | _Pragma("GCC optimize (\"O0\")") 22 | #define OPTS_ON #pragma GCC pop_options 23 | #elif defined(_MSC_VER) 24 | #define NOINLINE __declspec(noinline) 25 | #define PH_ATTR_NAKED __declspec(naked) 26 | #define OPTS_OFF __pragma(optimize("", off)) 27 | #define OPTS_ON __pragma(optimize("", on)) 28 | #endif 29 | 30 | // TODO: Move this to test utils 31 | #define PH_UNUSED(a) (void)a 32 | 33 | namespace PLH { 34 | class IHook : public MemAccessor { 35 | public: 36 | IHook() { 37 | m_debugSet = false; 38 | m_hooked = false; 39 | } 40 | 41 | IHook(IHook&& other) = default; //move 42 | IHook& operator=(IHook&& other) = default;//move assignment 43 | IHook(const IHook& other) = delete; //copy 44 | IHook& operator=(const IHook& other) = delete; //copy assignment 45 | virtual ~IHook() = default; 46 | 47 | virtual bool hook() = 0; 48 | 49 | virtual bool unHook() = 0; 50 | 51 | // this is allowed to be nothing by default 52 | virtual bool reHook() { 53 | return true; 54 | } 55 | 56 | virtual bool setHooked(const bool state) { 57 | if (m_hooked == state) 58 | return true; 59 | 60 | return state ? hook() : unHook(); 61 | } 62 | 63 | virtual bool isHooked() { 64 | return m_hooked; 65 | } 66 | 67 | virtual HookType getType() const = 0; 68 | 69 | virtual void setDebug(const bool state) { 70 | m_debugSet = state; 71 | } 72 | 73 | #ifdef PLH_DIAGNOSTICS 74 | bool hasDiagnostic(const Diagnostic diagnostic) const { 75 | return static_cast(m_diagnostics & diagnostic); 76 | } 77 | 78 | void setDiagnostic(const Diagnostic diagnostic) { 79 | m_diagnostics = static_cast(m_diagnostics | diagnostic); 80 | } 81 | #endif 82 | 83 | protected: 84 | bool m_debugSet; 85 | bool m_hooked = false; 86 | 87 | #ifdef PLH_DIAGNOSTICS 88 | uint32_t m_diagnostics; 89 | #endif 90 | }; 91 | 92 | // TODO: Move to these to test utils 93 | 94 | //Thanks @_can1357 for help with this. 95 | template 96 | struct callback_type { using type = T; }; 97 | 98 | // from all the overloads below return the entire function type of whichever casts successfully 99 | template 100 | using callback_type_t = typename callback_type::type; 101 | 102 | // from all the overloads below return just the return type of whichever casts successfully 103 | template 104 | using callback_return_type_t = typename callback_type::return_type; 105 | 106 | // given a value, call callback_type_t on its type 107 | template 108 | using callback_type_v = typename callback_type::type; 109 | 110 | #define MAKE_CALLBACK_IMPL(CCFROM, CCTO) template \ 111 | auto make_callback(Ret(CCFROM*)(Args...), F&& f) \ 112 | { \ 113 | Ret(CCTO * fn)(Args...) = f; \ 114 | return fn; \ 115 | } \ 116 | template \ 117 | struct callback_type \ 118 | { \ 119 | using type = Ret(CCTO*)(Args...); \ 120 | using return_type = Ret; \ 121 | }; 122 | 123 | // switch to __VA_OPT__ when C++ 2a release. MSVC removes comma before empty __VA_ARGS__ as is. 124 | // https://devblogs.microsoft.com/cppblog/msvc-preprocessor-progress-towards-conformance/ 125 | #define MAKE_CALLBACK_CLASS_IMPL(CCFROM, CCTO, ...) template \ 126 | auto make_callback(Ret(CCFROM Class::*)(Args...), F&& f) \ 127 | { \ 128 | Ret(CCTO * fn)(Class*, ## __VA_ARGS__, Args...) = f; \ 129 | return fn; \ 130 | } \ 131 | template \ 132 | struct callback_type \ 133 | { \ 134 | using type = Ret(CCTO*)(Class*, ## __VA_ARGS__, Args...); \ 135 | using return_type = Ret; \ 136 | }; 137 | 138 | #ifndef POLYHOOK2_OS_WINDOWS 139 | #define __cdecl 140 | #define __fastcall 141 | #define __stdcall 142 | #endif 143 | 144 | #if defined(POLYHOOK2_ARCH_X86) && defined(POLYHOOK2_OS_WINDOWS) 145 | MAKE_CALLBACK_IMPL(__stdcall, __stdcall) 146 | MAKE_CALLBACK_CLASS_IMPL(__stdcall, __stdcall) 147 | 148 | MAKE_CALLBACK_IMPL(__cdecl, __cdecl) 149 | MAKE_CALLBACK_CLASS_IMPL(__cdecl, __cdecl) 150 | 151 | MAKE_CALLBACK_IMPL(__thiscall, __thiscall) 152 | MAKE_CALLBACK_CLASS_IMPL(__thiscall, __fastcall, char*) 153 | #endif 154 | 155 | MAKE_CALLBACK_IMPL(__fastcall, __fastcall) 156 | MAKE_CALLBACK_CLASS_IMPL(__fastcall, __fastcall) 157 | 158 | template 159 | decltype(auto) get_pack_idx(Ts&&... ts) { 160 | return std::get(std::forward_as_tuple(ts...)); 161 | } 162 | } 163 | 164 | /** 165 | Creates a hook callback function pointer that matches the type of a given function definition. The name variable 166 | will be a pointer to the function, and the variables _args... and name_t will be created to represent the original 167 | arguments of the function and the type of the callback respectively. 168 | **/ 169 | #define HOOK_CALLBACK(pType, name, body) \ 170 | typedef PLH::callback_type_t name##_t; \ 171 | PLH::callback_type_t (name) = PLH::make_callback(pType, [](auto... _args) -> PLH::callback_return_type_t body) // NOLINT(bugprone-macro-parentheses) 172 | 173 | /** 174 | When using the HOOK_CALLBACK macro this helper utility can be used to retreive one of the original 175 | arguments by index. The type and value will exactly match that of the original function at that index. 176 | for member functions this is essentially 1's indexed because first param is this* 177 | **/ 178 | #define GET_ARG(idx) PLH::get_pack_idx(_args...) 179 | 180 | #endif //POLYHOOK_2_0_IHOOK_HPP 181 | -------------------------------------------------------------------------------- /polyhook2/PE/PEB.hpp: -------------------------------------------------------------------------------- 1 | /**This file is the minimal windows header crap needed for IAT things. Windows sucks, pollutes global namespace**/ 2 | 3 | #ifndef POLYHOOK_2_0_PEB_HPP 4 | #define POLYHOOK_2_0_PEB_HPP 5 | 6 | // copied from windows.h minwindef failes with No Target Architecture without 7 | #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_IX86) 8 | #define _X86_ 9 | #endif 10 | 11 | #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_AMD64) 12 | #define _AMD64_ 13 | #endif 14 | 15 | #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_M68K) 16 | #define _68K_ 17 | #endif 18 | 19 | #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_MPPC) 20 | #define _MPPC_ 21 | #endif 22 | 23 | #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_M_IX86) && !defined(_AMD64_) && defined(_M_IA64) 24 | #if !defined(_IA64_) 25 | #define _IA64_ 26 | #endif /* !_IA64_ */ 27 | #endif 28 | 29 | #ifndef WIN32_LEAN_AND_MEAN 30 | #define WIN32_LEAN_AND_MEAN 31 | #endif 32 | #ifndef NOMINMAX 33 | #define NOMINMAX 34 | #endif 35 | #include 36 | typedef void *PPS_POST_PROCESS_INIT_ROUTINE; 37 | 38 | typedef struct _LSA_UNICODE_STRING { 39 | USHORT Length; 40 | USHORT MaximumLength; 41 | PWSTR Buffer; 42 | } LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING; 43 | 44 | typedef struct _RTL_USER_PROCESS_PARAMETERS { 45 | BYTE Reserved1[16]; 46 | PVOID Reserved2[10]; 47 | UNICODE_STRING ImagePathName; 48 | UNICODE_STRING CommandLine; 49 | } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; 50 | 51 | // PEB defined by rewolf 52 | // http://blog.rewolf.pl/blog/?p=573 53 | typedef struct _PEB_LDR_DATA { 54 | ULONG Length; 55 | BOOL Initialized; 56 | LPVOID SsHandle; 57 | LIST_ENTRY InLoadOrderModuleList; 58 | LIST_ENTRY InMemoryOrderModuleList; 59 | LIST_ENTRY InInitializationOrderModuleList; 60 | } PEB_LDR_DATA, *PPEB_LDR_DATA; 61 | 62 | typedef struct _LDR_DATA_TABLE_ENTRY { 63 | LIST_ENTRY InLoadOrderLinks; 64 | LIST_ENTRY InMemoryOrderLinks; 65 | LIST_ENTRY InInitializationOrderLinks; 66 | LPVOID DllBase; 67 | LPVOID EntryPoint; 68 | ULONG SizeOfImage; 69 | UNICODE_STRING FullDllName; 70 | UNICODE_STRING BaseDllName; 71 | } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; 72 | 73 | typedef struct _PEB { 74 | BYTE InheritedAddressSpace; 75 | BYTE ReadImageFileExecOptions; 76 | BYTE BeingDebugged; 77 | BYTE _SYSTEM_DEPENDENT_01; 78 | 79 | LPVOID Mutant; 80 | LPVOID ImageBaseAddress; 81 | 82 | PPEB_LDR_DATA Ldr; 83 | PRTL_USER_PROCESS_PARAMETERS ProcessParameters; 84 | LPVOID SubSystemData; 85 | LPVOID ProcessHeap; 86 | LPVOID FastPebLock; 87 | LPVOID _SYSTEM_DEPENDENT_02; 88 | LPVOID _SYSTEM_DEPENDENT_03; 89 | LPVOID _SYSTEM_DEPENDENT_04; 90 | union { 91 | LPVOID KernelCallbackTable; 92 | LPVOID UserSharedInfoPtr; 93 | }; 94 | DWORD SystemReserved; 95 | DWORD _SYSTEM_DEPENDENT_05; 96 | LPVOID _SYSTEM_DEPENDENT_06; 97 | LPVOID TlsExpansionCounter; 98 | LPVOID TlsBitmap; 99 | DWORD TlsBitmapBits[2]; 100 | LPVOID ReadOnlySharedMemoryBase; 101 | LPVOID _SYSTEM_DEPENDENT_07; 102 | LPVOID ReadOnlyStaticServerData; 103 | LPVOID AnsiCodePageData; 104 | LPVOID OemCodePageData; 105 | LPVOID UnicodeCaseTableData; 106 | DWORD NumberOfProcessors; 107 | union { 108 | DWORD NtGlobalFlag; 109 | LPVOID dummy02; 110 | }; 111 | LARGE_INTEGER CriticalSectionTimeout; 112 | LPVOID HeapSegmentReserve; 113 | LPVOID HeapSegmentCommit; 114 | LPVOID HeapDeCommitTotalFreeThreshold; 115 | LPVOID HeapDeCommitFreeBlockThreshold; 116 | DWORD NumberOfHeaps; 117 | DWORD MaximumNumberOfHeaps; 118 | LPVOID ProcessHeaps; 119 | LPVOID GdiSharedHandleTable; 120 | LPVOID ProcessStarterHelper; 121 | LPVOID GdiDCAttributeList; 122 | LPVOID LoaderLock; 123 | DWORD OSMajorVersion; 124 | DWORD OSMinorVersion; 125 | WORD OSBuildNumber; 126 | WORD OSCSDVersion; 127 | DWORD OSPlatformId; 128 | DWORD ImageSubsystem; 129 | DWORD ImageSubsystemMajorVersion; 130 | LPVOID ImageSubsystemMinorVersion; 131 | union { 132 | LPVOID ImageProcessAffinityMask; 133 | LPVOID ActiveProcessAffinityMask; 134 | }; 135 | #ifdef _WIN64 136 | LPVOID GdiHandleBuffer[64]; 137 | #else 138 | LPVOID GdiHandleBuffer[32]; 139 | #endif 140 | LPVOID PostProcessInitRoutine; 141 | LPVOID TlsExpansionBitmap; 142 | DWORD TlsExpansionBitmapBits[32]; 143 | LPVOID SessionId; 144 | ULARGE_INTEGER AppCompatFlags; 145 | ULARGE_INTEGER AppCompatFlagsUser; 146 | LPVOID pShimData; 147 | LPVOID AppCompatInfo; 148 | PUNICODE_STRING CSDVersion; 149 | LPVOID ActivationContextData; 150 | LPVOID ProcessAssemblyStorageMap; 151 | LPVOID SystemDefaultActivationContextData; 152 | LPVOID SystemAssemblyStorageMap; 153 | LPVOID MinimumStackCommit; 154 | } PEB, *PPEB; 155 | 156 | #endif //POLYHOOK_2_0_PEB_HPP 157 | -------------------------------------------------------------------------------- /sources/MemAccessor.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/MemAccessor.hpp" 2 | #include "polyhook2/MemProtector.hpp" 3 | #include "polyhook2/Misc.hpp" 4 | 5 | #include "polyhook2/PolyHookOsIncludes.hpp" 6 | 7 | #if defined(POLYHOOK2_OS_WINDOWS) 8 | 9 | bool PLH::MemAccessor::mem_copy(uint64_t dest, uint64_t src, uint64_t size) const { 10 | memcpy((char*)dest, (char*)src, (SIZE_T)size); 11 | return true; 12 | } 13 | 14 | bool PLH::MemAccessor::safe_mem_write(uint64_t dest, uint64_t src, uint64_t size, size_t& written) const noexcept { 15 | written = 0; 16 | return WriteProcessMemory(GetCurrentProcess(), (char*)dest, (char*)src, (SIZE_T)size, (PSIZE_T)&written); 17 | } 18 | 19 | bool PLH::MemAccessor::safe_mem_read(uint64_t src, uint64_t dest, size_t size, size_t& read) const noexcept { 20 | HANDLE process = GetCurrentProcess(); 21 | read = 0; 22 | 23 | if (ReadProcessMemory(process, (char*)src, (char*)dest, size, (PSIZE_T)&read) && read > 0) 24 | return true; 25 | 26 | // Tries to read again on a partial copy, but limited by the end of the memory region 27 | if (GetLastError() == ERROR_PARTIAL_COPY) 28 | { 29 | MEMORY_BASIC_INFORMATION info; 30 | if (VirtualQueryEx(process, (char*)src, &info, sizeof(info)) != 0) 31 | { 32 | uint64_t end = (uint64_t)info.BaseAddress + info.RegionSize; 33 | if (src + size > end) 34 | return ReadProcessMemory(process, (char*)src, (char*)dest, (SIZE_T)(end - src), (PSIZE_T)&read) && read > 0; 35 | } 36 | } 37 | return false; 38 | } 39 | 40 | PLH::ProtFlag PLH::MemAccessor::mem_protect(uint64_t dest, uint64_t size, PLH::ProtFlag prot, bool& status) const { 41 | DWORD orig; 42 | status = VirtualProtect((char*)dest, (SIZE_T)size, TranslateProtection(prot), &orig) != 0; 43 | return TranslateProtection(orig); 44 | } 45 | 46 | #elif defined(POLYHOOK2_OS_LINUX) 47 | 48 | struct region_t { 49 | uint64_t start; 50 | uint64_t end; 51 | PLH::ProtFlag prot; 52 | }; 53 | 54 | static region_t get_region_from_addr(uint64_t addr) { 55 | region_t res{}; 56 | 57 | std::ifstream f("/proc/self/maps"); 58 | std::string s; 59 | while (std::getline(f, s)) { 60 | if (!s.empty() && s.find("vdso") == std::string::npos && s.find("vsyscall") == std::string::npos) 61 | { 62 | char* strend = &s[0]; 63 | uint64_t start = strtoul(strend , &strend, 16); 64 | uint64_t end = strtoul(strend+1, &strend, 16); 65 | if (start != 0 && end != 0 && start <= addr && addr < end) { 66 | res.start = start; 67 | res.end = end; 68 | 69 | ++strend; 70 | if (strend[0] == 'r') 71 | res.prot = res.prot | PLH::ProtFlag::R; 72 | 73 | if (strend[1] == 'w') 74 | res.prot = res.prot | PLH::ProtFlag::W; 75 | 76 | if (strend[2] == 'x') 77 | res.prot = res.prot | PLH::ProtFlag::X; 78 | 79 | if(res.prot == PLH::ProtFlag::UNSET) 80 | res.prot = PLH::ProtFlag::NONE; 81 | 82 | break; 83 | } 84 | } 85 | } 86 | 87 | // TODO: What if we fail to find the region? 88 | 89 | return res; 90 | } 91 | 92 | bool PLH::MemAccessor::mem_copy(uint64_t dest, uint64_t src, uint64_t size) const { 93 | memcpy((char*)dest, (char*)src, (size_t)size); 94 | return true; 95 | } 96 | 97 | bool PLH::MemAccessor::safe_mem_write(uint64_t dest, uint64_t src, uint64_t size, size_t& written) const noexcept { 98 | region_t region_infos = get_region_from_addr(src); 99 | 100 | // Make sure that the region we query is writable 101 | if(!(region_infos.prot & PLH::ProtFlag::W)) 102 | return false; 103 | 104 | size = std::min(region_infos.end - src, size); 105 | 106 | memcpy((void*)dest, (void*)src, (size_t)size); 107 | written = size; 108 | 109 | return true; 110 | } 111 | 112 | bool PLH::MemAccessor::safe_mem_read(uint64_t src, uint64_t dest, size_t size, size_t &read) const noexcept { 113 | if (!size) { 114 | return false; 115 | } 116 | 117 | auto address_in_region = src; 118 | size_t readable_size = 0; 119 | while (readable_size < size) { 120 | const auto region = get_region_from_addr(address_in_region); 121 | if (!(region.prot & PLH::ProtFlag::R)) { 122 | // Stop when encountering an unreadable memory mapping. 123 | break; 124 | } 125 | 126 | // Update the size of readable memory. 127 | readable_size = region.end - src; 128 | 129 | // If the memory slice in question spans multiple memory maps, 130 | // then we need to check the next adjacent memory map as well. 131 | address_in_region = region.end; 132 | } 133 | 134 | if (!readable_size) { 135 | return false; 136 | } 137 | 138 | read = std::min(size, readable_size); 139 | memcpy((void *)dest, (void *)src, read); 140 | 141 | return true; 142 | } 143 | 144 | PLH::ProtFlag PLH::MemAccessor::mem_protect(uint64_t dest, uint64_t size, PLH::ProtFlag prot, bool& status) const { 145 | auto current_address = dest; 146 | size_t protected_size = 0; 147 | region_t region{}; 148 | while (current_address < dest + size) { 149 | region = get_region_from_addr(current_address); 150 | 151 | const auto aligned_dest = region.start; 152 | const auto aligned_size = region.end - region.start; 153 | 154 | status = mprotect((void *)aligned_dest, aligned_size, TranslateProtection(prot)) == 0; 155 | 156 | if (!status) { 157 | break; 158 | } 159 | 160 | protected_size += aligned_size; 161 | 162 | // If the memory slice in question spans multiple memory maps, 163 | // then we need to protect the next adjacent memory map as well. 164 | current_address = aligned_dest + aligned_size; 165 | } 166 | 167 | // TODO: This is valid only for cases with single memory mapping. 168 | // Ideally we should store an array of protections instead. 169 | return region.prot; 170 | } 171 | 172 | #elif defined(POLYHOOK2_OS_APPLE) 173 | 174 | bool PLH::MemAccessor::mem_copy(uint64_t dest, uint64_t src, uint64_t size) const { 175 | memcpy((char*)dest, (char*)src, (size_t)size); 176 | return true; 177 | } 178 | 179 | bool PLH::MemAccessor::safe_mem_write(uint64_t dest, uint64_t src, uint64_t size, size_t& written) const noexcept { 180 | bool res = memcpy((void*)dest, (void*)src, (size_t)size) != nullptr; 181 | if (res) 182 | written = size; 183 | else 184 | written = 0; 185 | 186 | return res; 187 | } 188 | 189 | bool PLH::MemAccessor::safe_mem_read(uint64_t src, uint64_t dest, uint64_t size, size_t& read) const noexcept { 190 | bool res = memcpy((void*)dest, (void*)src, (size_t)size) != nullptr; 191 | if (res) 192 | read = size; 193 | else 194 | read = 0; 195 | 196 | return res; 197 | } 198 | 199 | PLH::ProtFlag PLH::MemAccessor::mem_protect(uint64_t dest, uint64_t size, PLH::ProtFlag prot, bool& status) const { 200 | status = mach_vm_protect(mach_task_self(), (mach_vm_address_t)MEMORY_ROUND(dest, PLH::getPageSize()), (mach_vm_size_t)MEMORY_ROUND_UP(size, PLH::getPageSize()), FALSE, TranslateProtection(prot)) == KERN_SUCCESS; 201 | return PLH::ProtFlag::R | PLH::ProtFlag::X; 202 | } 203 | 204 | #endif 205 | -------------------------------------------------------------------------------- /UnitTests/windows/TestDetourTranslationx64.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "polyhook2/Detour/x64Detour.hpp" 3 | 4 | #include "polyhook2/Tests/StackCanary.hpp" 5 | #include "polyhook2/Tests/TestEffectTracker.hpp" 6 | 7 | #include "polyhook2/PolyHookOsIncludes.hpp" 8 | 9 | #include 10 | 11 | EffectTracker ripEffects; 12 | 13 | unsigned char cmpQwordImm[] = { 14 | 0x48, 0x81, 0x3D, 0xF5, 0xFF, 0xFF, 0xFF, 0x78, 0x56, 0x34, 0x12, // cmp qword ptr ds:[rip - 11], 0x12345678 15 | 0x48, 0xC7, 0xC0, 0x37, 0x13, 0x00, 0x00, // mov rax, 0x1337 16 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // nop x8 17 | 0xC3 // ret 18 | }; 19 | 20 | 21 | unsigned char cmpDwordImm[] = { 22 | 0x81, 0x05, 0xF6, 0xFF, 0xFF, 0xFF, 0x78, 0x56, 0x34, 0x12, // add dword ptr ds:[rip - 10], 0x12345678 23 | 0x48, 0xC7, 0xC0, 0x37, 0x13, 0x00, 0x00, // mov rax, 0x1337 24 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // nop x8 25 | 0xC3 // ret 26 | }; 27 | 28 | unsigned char cmpWordImm[] = { 29 | 0x66, 0x81, 0x3D, 0xF7, 0xFF, 0xFF, 0xFF, 0x34, 0x12, // cmp word ptr ds:[rip - 9], 0x1234 30 | 0x48, 0xC7, 0xC0, 0x37, 0x13, 0x00, 0x00, // mov rax, 0x1337 31 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // nop x8 32 | 0xC3 // ret 33 | }; 34 | 35 | unsigned char cmpByteImm[] = { 36 | 0x80, 0x3D, 0xF9, 0xFF, 0xFF, 0xFF, 0x12, // cmp byte ptr ds:[rip - 7], 0x12 37 | 0x48, 0xC7, 0xC0, 0x37, 0x13, 0x00, 0x00, // mov rax, 0x1337 38 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // nop x8 39 | 0xC3 // ret 40 | }; 41 | 42 | unsigned char cmpQwordRegR10[] = { 43 | 0x4C, 0x39, 0x15, 0xF9, 0xFF, 0xFF, 0xFF, // cmp qword ptr ds:[rip - 7], r10 44 | 0xB8, 0x37, 0x13, 0x00, 0x00, // mov eax, 0x1337 45 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // nop x8 46 | 0xC3 // ret 47 | }; 48 | 49 | unsigned char cmpRegADword[] = { 50 | 0x3B, 0x05, 0xFA, 0xFF, 0xFF, 0xFF, // cmp eax, dword ptr ds:[rip - 6] 51 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // nop x8 52 | 0xC3 // ret 53 | }; 54 | 55 | unsigned char cmpWordRegB[] = { 56 | 0x66, 0x39, 0x1D, 0xF9, 0xFF, 0xFF, 0xFF, // cmp word ptr ds:[rip - 7], bx 57 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // nop x8 58 | 0xC3 // ret 59 | }; 60 | 61 | unsigned char cmpR15bByte[] = { 62 | 0x44, 0x3A, 0x3D, 0xF9, 0xFF, 0xFF, 0xFF, // cmp r15b, byte ptr ds:[rip - 7] 63 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // nop x8 64 | 0xC3 // ret 65 | }; 66 | 67 | // TODO: Translation + INPLACE scheme 68 | 69 | uint64_t oCmpQwordImm = NULL; 70 | 71 | uint64_t hookCmpQwordImm() { 72 | PLH::StackCanary canary; 73 | ripEffects.PeakEffect().trigger(); 74 | 75 | printf("Hooked %s\n", __func__); 76 | 77 | return PLH::FnCast(oCmpQwordImm, &hookCmpQwordImm)(); 78 | } 79 | 80 | uint64_t oCmpQwordReg = NULL; 81 | 82 | uint64_t hookCmpQwordReg() { 83 | PLH::StackCanary canary; 84 | ripEffects.PeakEffect().trigger(); 85 | 86 | printf("Hooked %s\n", __func__); 87 | 88 | return PLH::FnCast(oCmpQwordReg, &hookCmpQwordReg)(); 89 | } 90 | 91 | TEST_CASE("Testing Detours with Translations", "[Translation][ADetour]") { 92 | // Immediate 93 | 94 | SECTION("cmp qword & imm") { 95 | PLH::StackCanary canary; 96 | 97 | DWORD flOldProtect; 98 | VirtualProtect((void*) cmpQwordImm, (SIZE_T) sizeof(cmpQwordImm), PAGE_EXECUTE_READWRITE, &flOldProtect); 99 | 100 | PLH::x64Detour detour((uint64_t) cmpQwordImm, (uint64_t) hookCmpQwordImm, &oCmpQwordImm); 101 | 102 | REQUIRE(detour.hook()); 103 | 104 | ripEffects.PushEffect(); 105 | 106 | const auto result = PLH::FnCast(cmpQwordImm, hookCmpQwordImm)(); 107 | 108 | REQUIRE(ripEffects.PopEffect().didExecute()); 109 | REQUIRE(result == 0x1337); 110 | 111 | REQUIRE(detour.unHook()); 112 | } 113 | 114 | SECTION("cmp dword & imm") { 115 | PLH::StackCanary canary; 116 | PLH::x64Detour detour((uint64_t) cmpDwordImm, (uint64_t) hookCmpQwordImm, &oCmpQwordImm); 117 | 118 | REQUIRE(detour.hook()); 119 | REQUIRE(detour.unHook()); 120 | } 121 | 122 | 123 | SECTION("cmp word & imm") { 124 | PLH::StackCanary canary; 125 | PLH::x64Detour detour((uint64_t) cmpWordImm, (uint64_t) hookCmpQwordImm, &oCmpQwordImm); 126 | 127 | REQUIRE(detour.hook()); 128 | REQUIRE(detour.unHook()); 129 | } 130 | 131 | SECTION("cmp byte & imm") { 132 | PLH::StackCanary canary; 133 | PLH::x64Detour detour((uint64_t) cmpByteImm, (uint64_t) hookCmpQwordImm, &oCmpQwordImm); 134 | 135 | REQUIRE(detour.hook()); 136 | REQUIRE(detour.unHook()); 137 | } 138 | 139 | // Registers 140 | 141 | SECTION("cmp qword & reg") { 142 | PLH::StackCanary canary; 143 | 144 | DWORD flOldProtect; 145 | VirtualProtect((void*) cmpQwordRegR10, (SIZE_T) sizeof(cmpQwordRegR10), PAGE_EXECUTE_READWRITE, &flOldProtect); 146 | 147 | PLH::x64Detour detour((uint64_t) cmpQwordRegR10, (uint64_t) hookCmpQwordReg, &oCmpQwordReg); 148 | 149 | REQUIRE(detour.hook()); 150 | 151 | ripEffects.PushEffect(); 152 | 153 | const auto result = PLH::FnCast(cmpQwordRegR10, hookCmpQwordReg)(); 154 | 155 | REQUIRE(ripEffects.PopEffect().didExecute()); 156 | REQUIRE(result == 0x1337); 157 | 158 | REQUIRE(detour.unHook()); 159 | } 160 | 161 | // Subsequent hooks don't test trampoline calls 162 | 163 | SECTION("cmp dword & reg") { 164 | PLH::StackCanary canary; 165 | PLH::x64Detour detour((uint64_t) cmpRegADword, (uint64_t) hookCmpQwordReg, &oCmpQwordReg); 166 | 167 | REQUIRE(detour.hook()); 168 | REQUIRE(detour.unHook()); 169 | } 170 | 171 | SECTION("cmp word & reg") { 172 | PLH::StackCanary canary; 173 | PLH::x64Detour detour((uint64_t) cmpWordRegB, (uint64_t) hookCmpQwordReg, &oCmpQwordReg); 174 | 175 | REQUIRE(detour.hook()); 176 | REQUIRE(detour.unHook()); 177 | } 178 | 179 | SECTION("cmp byte & reg") { 180 | PLH::StackCanary canary; 181 | PLH::x64Detour detour((uint64_t) cmpR15bByte, (uint64_t) hookCmpQwordReg, &oCmpQwordReg); 182 | 183 | REQUIRE(detour.hook()); 184 | REQUIRE(detour.unHook()); 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /polyhook2/Misc.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by steve on 4/6/17. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "polyhook2/PolyHookOs.hpp" 8 | 9 | namespace PLH { 10 | 11 | /**First param is an address to a function that you want to 12 | cast to the type of pFnCastTo. Second param must be a pointer 13 | to function type**/ 14 | template 15 | FnCastTo FnCast(uint64_t fnToCast, FnCastTo) { 16 | return (FnCastTo) fnToCast; 17 | } 18 | 19 | template 20 | FnCastTo FnCast(void* fnToCast, FnCastTo) { 21 | return (FnCastTo) fnToCast; 22 | } 23 | 24 | enum class Platform { 25 | WIN, 26 | UNIX 27 | }; 28 | 29 | class NotImplementedException : public std::logic_error { 30 | public: 31 | NotImplementedException() : std::logic_error("Function not implemented") { 32 | 33 | } 34 | }; 35 | 36 | class ValueNotSetException : public std::logic_error { 37 | public: 38 | ValueNotSetException() : std::logic_error("Value not set in optional object") { 39 | 40 | } 41 | }; 42 | 43 | class AllocationFailure : public std::logic_error { 44 | public: 45 | AllocationFailure() : std::logic_error("Unable to allocate memory within range") { 46 | 47 | } 48 | }; 49 | 50 | //http://stackoverflow.com/questions/4840410/how-to-align-a-pointer-in-c 51 | static inline uint64_t AlignUpwards(uint64_t stack, size_t align) { 52 | assert(align > 0 && (align & (align - 1)) == 0); /* Power of 2 */ 53 | assert(stack != 0); 54 | 55 | auto addr = stack; 56 | if (addr % align != 0) 57 | addr += align - (addr % align); 58 | assert(addr >= stack); 59 | return addr; 60 | } 61 | 62 | static inline uint64_t AlignDownwards(uint64_t stack, size_t align) { 63 | assert(align > 0 && (align & (align - 1)) == 0); /* Power of 2 */ 64 | assert(stack != 0); 65 | 66 | auto addr = stack; 67 | addr -= addr % align; 68 | assert(addr <= stack); 69 | return addr; 70 | } 71 | 72 | template 73 | class FinalAction { 74 | public: 75 | FinalAction(Func f) :FinalActionFunc(std::move(f)) {} 76 | ~FinalAction() { 77 | FinalActionFunc(); 78 | } 79 | private: 80 | Func FinalActionFunc; 81 | 82 | /*Uses RAII to call a final function on destruction 83 | C++ 11 version of java's finally (kindof)*/ 84 | }; 85 | 86 | template 87 | static inline FinalAction finally(F f) { 88 | return FinalAction(f); 89 | } 90 | 91 | //Credit to Dogmatt on unknowncheats.me for IsValidPtr 92 | // and https://docs.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/virtual-address-spaces 93 | #ifdef POLYHOOK2_ARCH_X64 94 | #define _PTR_MAX_VALUE ((void*)0x000F000000000000) 95 | #else 96 | #define _PTR_MAX_VALUE ((void*)0xFFF00000) 97 | #endif 98 | 99 | inline bool IsValidPtr(void* p) { return (p >= (void*)0x10000) && (p < _PTR_MAX_VALUE) && p != nullptr; } 100 | 101 | // wtf this should be standard (stole from glibc & stackoverflow) 102 | inline int my_narrow_stricmp(const char *a, const char *b) { 103 | int ca, cb; 104 | do { 105 | ca = (unsigned char)*a++; 106 | cb = (unsigned char)*b++; 107 | ca = tolower(toupper(ca)); 108 | cb = tolower(toupper(cb)); 109 | } while (ca == cb && ca != '\0'); 110 | return ca - cb; 111 | } 112 | 113 | inline int my_wide_stricmp(const wchar_t *a, const wchar_t *b) { 114 | wint_t ca, cb; 115 | do { 116 | ca = (wint_t)*a++; 117 | cb = (wint_t)*b++; 118 | ca = towlower(towupper(ca)); 119 | cb = towlower(towupper(cb)); 120 | } while (ca == cb && ca != L'\0'); 121 | return ca - cb; 122 | } 123 | 124 | struct ci_wchar_traits : public std::char_traits { 125 | static bool eq(wchar_t c1, wchar_t c2) { return towupper(c1) == towupper(c2); } 126 | static bool ne(wchar_t c1, wchar_t c2) { return towupper(c1) != towupper(c2); } 127 | static bool lt(wchar_t c1, wchar_t c2) { return towupper(c1) < towupper(c2); } 128 | static int compare(const wchar_t* s1, const wchar_t* s2, size_t n) { 129 | while (n-- != 0) { 130 | if (towupper(*s1) < towupper(*s2)) return -1; 131 | if (towupper(*s1) > towupper(*s2)) return 1; 132 | ++s1; ++s2; 133 | } 134 | return 0; 135 | } 136 | static const wchar_t* find(const wchar_t* s, int n, wchar_t a) { 137 | while (n-- > 0 && towupper(*s) != towupper(a)) { 138 | ++s; 139 | } 140 | return s; 141 | } 142 | }; 143 | 144 | inline bool isMatch(const char* addr, const char* pat, const char* msk) 145 | { 146 | size_t n = 0; 147 | while (addr[n] == pat[n] || msk[n] == (uint8_t)'?') { 148 | if (!msk[++n]) { 149 | return true; 150 | } 151 | } 152 | return false; 153 | } 154 | 155 | #define INRANGE(x,a,b) (x >= a && x <= b) 156 | #define getBits( x ) (INRANGE(x,'0','9') ? (x - '0') : ((x&(~0x20)) - 'A' + 0xa)) 157 | #define getByte( x ) (getBits(x[0]) << 4 | getBits(x[1])) 158 | 159 | constexpr uint8_t FINDPATTERN_SCRATCH_SIZE = 64; 160 | 161 | // https://github.com/learn-more/findpattern-bench/blob/master/patterns/learn_more.h 162 | // must use space between bytes and ?? for wildcards. Do not add 0x prefix 163 | uint64_t findPattern(const uint64_t rangeStart, size_t len, const char* pattern); 164 | uint64_t findPattern_rev(const uint64_t rangeStart, size_t len, const char* pattern); 165 | uint64_t getPatternSize(const char* pattern); 166 | 167 | bool boundedAllocSupported(); 168 | uint64_t boundAlloc(uint64_t min, uint64_t max, uint64_t size); 169 | uint64_t boundAllocLegacy(uint64_t min, uint64_t max, uint64_t size); 170 | void boundAllocFree(uint64_t address, uint64_t size); 171 | size_t getAllocationAlignment(); 172 | size_t getPageSize(); 173 | 174 | uint64_t calc_2gb_below(uint64_t address); 175 | uint64_t calc_2gb_above(uint64_t address); 176 | 177 | inline std::string repeat_n(std::string s, size_t n, std::string delim = "") { 178 | std::string out = ""; 179 | for (size_t i = 0; i < n; i++) { 180 | out += s; 181 | if (i != n - 1) { 182 | out += delim; 183 | } 184 | } 185 | return out; 186 | } 187 | 188 | using ci_wstring = std::basic_string; 189 | using ci_wstring_view = std::basic_string_view; 190 | 191 | template< typename T > 192 | std::string int_to_hex(T i) 193 | { 194 | std::stringstream stream; 195 | stream << "0x" << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex 196 | << (uint64_t) i; // We cast to the highest possible int because uint8_t will be printed as char 197 | 198 | return stream.str(); 199 | } 200 | 201 | template< typename T > 202 | inline bool vector_contains(std::vector vec, T element) 203 | { 204 | return std::find(vec.begin(), vec.end(), element) != vec.end(); 205 | } 206 | 207 | inline bool string_contains(const std::string& str, const std::string& sub_str) 208 | { 209 | return str.find(sub_str) != std::string::npos; 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /UnitTests/windows/TestEatHook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "polyhook2/PE/EatHook.hpp" 4 | #include "polyhook2/Tests/StackCanary.hpp" 5 | #include "polyhook2/Tests/TestEffectTracker.hpp" 6 | #include "polyhook2/PolyHookOsIncludes.hpp" 7 | #include "polyhook2/Detour/ADetour.hpp" 8 | 9 | EffectTracker eatEffectTracker; 10 | 11 | typedef void(* tEatTestExport)(); 12 | uint64_t oEatTestExport; 13 | 14 | extern "C" __declspec(dllexport) NOINLINE void EatTestExport() 15 | { 16 | PLH::StackCanary canary; 17 | } 18 | 19 | NOINLINE void hkEatTestExport() 20 | { 21 | PLH::StackCanary canary; 22 | eatEffectTracker.PeakEffect().trigger(); 23 | } 24 | 25 | TEST_CASE("Hook internal test export", "[EatHook]") { 26 | SECTION("Verify if export is found and hooked when module name is empty") { 27 | PLH::StackCanary canary; 28 | PLH::EatHook hook("EatTestExport", L"", (char*)&hkEatTestExport, (uint64_t*)&oEatTestExport); 29 | REQUIRE(hook.hook()); 30 | 31 | auto pExport = (tEatTestExport)GetProcAddress(GetModuleHandle(nullptr), "EatTestExport"); 32 | REQUIRE(pExport); 33 | 34 | eatEffectTracker.PushEffect(); 35 | pExport(); 36 | REQUIRE(eatEffectTracker.PopEffect().didExecute()); 37 | REQUIRE(hook.unHook()); 38 | } 39 | 40 | SECTION("Verify if export is found and hooked when module name is explicitly given") { 41 | PLH::StackCanary canary; 42 | PLH::EatHook hook("EatTestExport", L"Polyhook_2.exe", (char*)&hkEatTestExport, (uint64_t*)&oEatTestExport); 43 | REQUIRE(hook.hook()); 44 | 45 | auto pExport = (tEatTestExport)GetProcAddress(GetModuleHandle(nullptr), "EatTestExport"); 46 | REQUIRE(pExport); 47 | 48 | eatEffectTracker.PushEffect(); 49 | pExport(); 50 | REQUIRE(eatEffectTracker.PopEffect().didExecute()); 51 | REQUIRE(hook.unHook()); 52 | } 53 | } 54 | 55 | typedef int(__stdcall* tEatMessageBox)(HWND, LPCTSTR, LPCTSTR, UINT); 56 | uint64_t oEatMessageBox; 57 | int __stdcall hkEatMessageBox(HWND, LPCTSTR, LPCTSTR, UINT) { 58 | PLH::StackCanary canary; 59 | auto MsgBox = (tEatMessageBox)oEatMessageBox; 60 | MsgBox(nullptr, TEXT("My Hook"), TEXT("text"), 0); 61 | eatEffectTracker.PeakEffect().trigger(); 62 | return 1; 63 | } 64 | 65 | // Disable test in CI that require GUI interactions 66 | #ifndef PLH_CI 67 | 68 | TEST_CASE("Hook User32.MessageBoxA using module name", "[EatHook]") { 69 | PLH::StackCanary canary; 70 | LoadLibrary(TEXT("User32.dll")); 71 | 72 | #ifdef UNICODE 73 | std::string apiName = "MessageBoxW"; 74 | #else 75 | std::string apiName = "MessageBoxA"; 76 | #endif 77 | PLH::EatHook hook(apiName, L"User32.dll", (char*)&hkEatMessageBox, (uint64_t*)&oEatMessageBox); 78 | REQUIRE(hook.hook()); 79 | 80 | eatEffectTracker.PushEffect(); 81 | 82 | // force walk of EAT 83 | auto MsgBox = (tEatMessageBox)GetProcAddress(GetModuleHandleA("User32.dll"), apiName.c_str()); 84 | MsgBox(nullptr, TEXT("test"), TEXT("test"), 0); 85 | REQUIRE(eatEffectTracker.PopEffect().didExecute()); 86 | hook.unHook(); 87 | } 88 | 89 | #endif 90 | 91 | typedef DWORD(__stdcall* tGetTickCount)(); 92 | uint64_t oGetTickCount = 0; 93 | DWORD WINAPI hkGetTickCount() 94 | { 95 | PLH::StackCanary canary; 96 | eatEffectTracker.PeakEffect().trigger(); 97 | 98 | auto result = ((tGetTickCount)oGetTickCount)(); 99 | PLH::Log::log("Original GetTickCount: " + std::to_string(result), PLH::ErrorLevel::INFO); 100 | 101 | return 0x1337; 102 | } 103 | 104 | TEST_CASE("Hook Kernel32.GetTickCount using module path", "[EatHook]") { 105 | PLH::StackCanary canary; 106 | 107 | const auto libHandle = LoadLibrary(TEXT("Kernel32.dll")); 108 | WCHAR libPath[MAX_PATH]; 109 | GetModuleFileNameW(libHandle, libPath, MAX_PATH); 110 | 111 | constexpr auto apiName = "GetTickCount"; 112 | 113 | PLH::EatHook hook(apiName, libPath, (char*) hkGetTickCount, &oGetTickCount); 114 | REQUIRE(hook.hook()); 115 | 116 | eatEffectTracker.PushEffect(); 117 | 118 | auto address = (void*) GetProcAddress(libHandle, apiName); 119 | auto result = ((tGetTickCount) address)(); 120 | 121 | REQUIRE(eatEffectTracker.PopEffect().didExecute()); 122 | REQUIRE(result == 0x1337); 123 | hook.unHook(); 124 | } 125 | 126 | typedef ULONGLONG(__stdcall* tGetTickCount64)(); 127 | uint64_t oGetTickCount64 = 0; 128 | ULONGLONG WINAPI hkGetTickCount64() 129 | { 130 | PLH::StackCanary canary; 131 | eatEffectTracker.PeakEffect().trigger(); 132 | 133 | auto result = ((tGetTickCount)oGetTickCount64)(); 134 | PLH::Log::log("Original GetTickCount64: " + std::to_string(result), PLH::ErrorLevel::INFO); 135 | 136 | return 0xDEADBEEF; 137 | } 138 | 139 | TEST_CASE("Hook Kernel32.GetTickCount64 using module handle", "[EatHook]") { 140 | PLH::StackCanary canary; 141 | 142 | const auto libHandle = LoadLibrary(TEXT("Kernel32.dll")); 143 | 144 | constexpr auto apiName = "GetTickCount64"; 145 | 146 | PLH::EatHook hook(apiName, libHandle, (uint64_t) hkGetTickCount64, &oGetTickCount64); 147 | REQUIRE(hook.hook()); 148 | 149 | eatEffectTracker.PushEffect(); 150 | 151 | auto address = (void*) GetProcAddress(libHandle, apiName); 152 | auto result = ((tGetTickCount64) address)(); 153 | 154 | REQUIRE(eatEffectTracker.PopEffect().didExecute()); 155 | REQUIRE(result == 0xDEADBEEF); 156 | hook.unHook(); 157 | } 158 | 159 | typedef void(__stdcall* tEatGetSystemTime)(PSYSTEMTIME systemTime); 160 | uint64_t oEatGetSystemTime; 161 | void WINAPI hkGetSystemTime(PSYSTEMTIME systemTime) 162 | { 163 | PLH::StackCanary canary; 164 | eatEffectTracker.PeakEffect().trigger(); 165 | ((tEatGetSystemTime)oEatGetSystemTime)(systemTime); 166 | } 167 | 168 | typedef void(__stdcall* tEatGetLocalTime)(PSYSTEMTIME systemTime); 169 | uint64_t oEatGetLocalTime; 170 | void WINAPI hkGetLocalTime(PSYSTEMTIME systemTime) 171 | { 172 | PLH::StackCanary canary; 173 | eatEffectTracker.PeakEffect().trigger(); 174 | ((tEatGetLocalTime)oEatGetLocalTime)(systemTime); 175 | } 176 | 177 | TEST_CASE("Hook Kernel32.[GetSystemTime,GetLocalTime]", "[EatHook]") { 178 | // These are out of module hooks that require a trampoline stub. 179 | // Multiple hooks can fail if the trampoline region isn't re-used 180 | // across multiple calls. Or if no free block is found at all 181 | PLH::StackCanary canary; 182 | PLH::EatHook hook_GST("GetSystemTime", L"kernel32.dll", (char*)&hkGetSystemTime, (uint64_t*)&oEatGetSystemTime); 183 | REQUIRE(hook_GST.hook()); 184 | eatEffectTracker.PushEffect(); 185 | 186 | auto GST = (tEatGetSystemTime)GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetSystemTime"); 187 | SYSTEMTIME t; 188 | memset(&t, 0, sizeof(t)); 189 | GST(&t); 190 | REQUIRE(eatEffectTracker.PopEffect().didExecute()); 191 | 192 | PLH::EatHook hook_GLT("GetLocalTime", L"kernel32.dll", (char*)&hkGetLocalTime, (uint64_t*)&oEatGetLocalTime); 193 | REQUIRE(hook_GLT.hook()); 194 | eatEffectTracker.PushEffect(); 195 | 196 | auto GLT = (tEatGetLocalTime)GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetLocalTime"); 197 | memset(&t, 0, sizeof(t)); 198 | GLT(&t); 199 | 200 | REQUIRE(eatEffectTracker.PopEffect().didExecute()); 201 | hook_GLT.unHook(); 202 | hook_GST.unHook(); 203 | } 204 | -------------------------------------------------------------------------------- /sources/EatHook.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/PE/EatHook.hpp" 2 | 3 | PLH::EatHook::EatHook(const std::string& apiName, const std::wstring& moduleName, const char* fnCallback, uint64_t* userOrigVar) 4 | : EatHook(apiName, moduleName, (uint64_t)fnCallback, userOrigVar) 5 | {} 6 | 7 | PLH::EatHook::EatHook(const std::string& apiName, const std::wstring& moduleName, const uint64_t fnCallback, uint64_t* userOrigVar) 8 | : EatHook(apiName, moduleName, nullptr, fnCallback, userOrigVar) 9 | {} 10 | 11 | PLH::EatHook::EatHook(const std::string& apiName, const HMODULE moduleHandle, const char* fnCallback, uint64_t* userOrigVar) 12 | : EatHook(apiName, moduleHandle, (uint64_t)fnCallback, userOrigVar) 13 | {} 14 | 15 | PLH::EatHook::EatHook(const std::string& apiName, const HMODULE moduleHandle, const uint64_t fnCallback, uint64_t* userOrigVar) 16 | : EatHook(apiName, L"", moduleHandle, fnCallback, userOrigVar) 17 | {} 18 | 19 | PLH::EatHook::EatHook(std::string apiName, std::wstring moduleName, const HMODULE moduleHandle, const uint64_t fnCallback, uint64_t* userOrigVar) 20 | : m_moduleName(std::move(moduleName)) 21 | , m_apiName(std::move(apiName)) 22 | , m_fnCallback(fnCallback) 23 | , m_userOrigVar(userOrigVar) 24 | , m_allocator(64, 64) // arbitrary, size is big enough but an overshoot 25 | , m_trampoline(0) 26 | , m_moduleBase((uint64_t)moduleHandle) 27 | , m_origFunc(0) 28 | {} 29 | 30 | bool PLH::EatHook::hook() { 31 | assert(m_userOrigVar != nullptr); 32 | uint32_t* pExport = FindEatFunction(); 33 | if (pExport == nullptr) 34 | return false; 35 | 36 | auto offset = (size_t)(m_fnCallback - m_moduleBase); 37 | 38 | /* account for when offset to our function is beyond EAT slots size. We 39 | instead allocate a small trampoline within +- 2GB which will do the full 40 | width jump to the final destination, and point the EAT to the stub.*/ 41 | if (offset > std::numeric_limits::max()) { 42 | m_trampoline = (uint64_t)m_allocator.allocate(m_moduleBase, PLH::calc_2gb_above(m_moduleBase)); 43 | if (m_trampoline == 0) { 44 | Log::log("EAT hook offset is > 32bit's. Allocation of trampoline necessary and failed to find free page within range", ErrorLevel::INFO); 45 | return false; 46 | } 47 | 48 | MemoryProtector protector(m_trampoline, 64, ProtFlag::R | ProtFlag::W | ProtFlag::X, *this, false); 49 | 50 | PLH::ZydisDisassembler::writeEncoding(makeAgnosticJmp(m_trampoline, m_fnCallback), *this); 51 | offset = (size_t)(m_trampoline - m_moduleBase); 52 | 53 | Log::log("EAT hook offset is > 32bit's. Allocation of trampoline necessary", ErrorLevel::INFO); 54 | } 55 | 56 | // Just like IAT, EAT is by default a writeable section 57 | // any EAT entry must be an offset 58 | MemoryProtector prot((uint64_t)pExport, sizeof(uintptr_t), ProtFlag::R | ProtFlag::W, *this); 59 | m_origFunc = *pExport; // original offset 60 | *pExport = (uint32_t)offset; 61 | m_hooked = true; 62 | *m_userOrigVar = m_moduleBase + m_origFunc; // original pointer (base + off) 63 | return true; 64 | } 65 | 66 | bool PLH::EatHook::unHook() { 67 | assert(m_userOrigVar != nullptr); 68 | assert(m_hooked); 69 | if (!m_hooked) { 70 | Log::log("EatHook unhook failed: no hook present", ErrorLevel::SEV); 71 | return false; 72 | } 73 | 74 | uint32_t* pExport = FindEatFunction(); 75 | if (pExport == nullptr) 76 | return false; 77 | 78 | MemoryProtector prot((uint64_t)pExport, sizeof(uintptr_t), ProtFlag::R | ProtFlag::W, *this); 79 | *pExport = (uint32_t)m_origFunc; 80 | m_hooked = false; 81 | *m_userOrigVar = NULL; 82 | 83 | // TODO: change hook to re-use existing trampoline rather than free-ing here to avoid overwrite later and dangling pointer 84 | if (m_trampoline) { 85 | m_allocator.deallocate(m_trampoline); 86 | m_trampoline = 0; 87 | } 88 | return true; 89 | } 90 | 91 | uint32_t* PLH::EatHook::FindEatFunction() { 92 | if(!m_moduleBase){ 93 | m_moduleBase = FindModule(); 94 | } 95 | 96 | if(!m_moduleBase){ 97 | Log::log("EAT | Failed to module base", ErrorLevel::SEV); 98 | return nullptr; 99 | } 100 | 101 | return FindEatFunctionInModule(); 102 | } 103 | 104 | uint64_t PLH::EatHook::FindModule() { 105 | #if defined(_WIN64) 106 | PEB* peb = (PPEB)__readgsqword(0x60); 107 | #else 108 | PEB* peb = (PPEB)__readfsdword(0x30); 109 | #endif 110 | 111 | auto* ldr = (PPEB_LDR_DATA)peb->Ldr; 112 | auto* dte = (LDR_DATA_TABLE_ENTRY*)ldr->InLoadOrderModuleList.Flink; 113 | 114 | // Empty module name implies current process 115 | if(m_moduleName.empty()){ 116 | return (uint64_t)(dte->DllBase); 117 | } 118 | 119 | const auto useFullPath = std::filesystem::path(m_moduleName).is_absolute(); 120 | 121 | // iterate over loaded modules to find the target module 122 | while (dte->DllBase != nullptr) { 123 | const auto peb_module = useFullPath ? dte->FullDllName: dte->BaseDllName; 124 | 125 | const ci_wstring_view pebModuleName{peb_module.Buffer, peb_module.Length / sizeof(wchar_t)}; 126 | 127 | // Perform case-insensitive comparison 128 | const auto maxCharCount = std::min(pebModuleName.length(), m_moduleName.length()); 129 | if(_wcsnicmp(pebModuleName.data(), m_moduleName.c_str(), maxCharCount) == 0){ 130 | // std::wcout << L"Found module: " << path_or_name << std::endl; 131 | return (uint64_t)(dte->DllBase); 132 | } 133 | 134 | dte = (LDR_DATA_TABLE_ENTRY*)dte->InLoadOrderLinks.Flink; 135 | } 136 | 137 | Log::log("EAT | Failed to automatically find module", ErrorLevel::SEV); 138 | 139 | return 0; 140 | } 141 | 142 | uint32_t* PLH::EatHook::FindEatFunctionInModule() const { 143 | if (m_moduleBase == NULL) { 144 | return nullptr; 145 | } 146 | 147 | auto* pDos = (IMAGE_DOS_HEADER*)m_moduleBase; 148 | auto* pNT = RVA2VA(IMAGE_NT_HEADERS*, m_moduleBase, pDos->e_lfanew); 149 | auto* pDataDir = (IMAGE_DATA_DIRECTORY*)pNT->OptionalHeader.DataDirectory; 150 | 151 | if (pDataDir[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == NULL) { 152 | Log::log("PEs without export tables are unsupported", ErrorLevel::SEV); 153 | return nullptr; 154 | } 155 | 156 | auto* pExports = RVA2VA(IMAGE_EXPORT_DIRECTORY*, m_moduleBase, pDataDir[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); 157 | 158 | auto* pAddressOfFunctions = RVA2VA(uint32_t*, m_moduleBase, pExports->AddressOfFunctions); 159 | auto* pAddressOfNames = RVA2VA(uint32_t*, m_moduleBase, pExports->AddressOfNames); 160 | auto* pAddressOfNameOrdinals = RVA2VA(uint16_t*, m_moduleBase, pExports->AddressOfNameOrdinals); 161 | 162 | for (uint32_t i = 0; i < pExports->NumberOfNames; i++) { 163 | if(my_narrow_stricmp(RVA2VA(char*, m_moduleBase, pAddressOfNames[i]), m_apiName.c_str()) != 0){ 164 | continue; 165 | } 166 | 167 | // std::cout << RVA2VA(char*, m_moduleBase, pAddressOfNames[i]) << std::endl; 168 | const uint16_t iExportOrdinal = pAddressOfNameOrdinals[i]; 169 | 170 | return &pAddressOfFunctions[iExportOrdinal]; 171 | } 172 | 173 | Log::log("API not found before end of EAT", ErrorLevel::SEV); 174 | return nullptr; 175 | } 176 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | build/ 27 | 28 | # Visual Studio 2015/2017 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # Visual Studio 2017 auto generated files 34 | Generated\ Files/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # Benchmark Results 50 | BenchmarkDotNet.Artifacts/ 51 | 52 | # .NET Core 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | *.sln.iml 292 | 293 | # CodeRush 294 | .cr/ 295 | 296 | # Python Tools for Visual Studio (PTVS) 297 | __pycache__/ 298 | *.pyc 299 | 300 | # Cake - Uncomment if you are using it 301 | # tools/** 302 | # !tools/packages.config 303 | 304 | # Tabs Studio 305 | *.tss 306 | 307 | # Telerik's JustMock configuration file 308 | *.jmconfig 309 | 310 | # BizTalk build output 311 | *.btp.cs 312 | *.btm.cs 313 | *.odx.cs 314 | *.xsd.cs 315 | 316 | # OpenCover UI analysis results 317 | OpenCover/ 318 | 319 | # Azure Stream Analytics local run output 320 | ASALocalRun/ 321 | 322 | # MSBuild Binary and Structured Log 323 | *.binlog 324 | 325 | # NVidia Nsight GPU debugger configuration file 326 | *.nvuser 327 | 328 | # MFractors (Xamarin productivity tool) working folder 329 | .mfractor/ 330 | build32/ 331 | build64/ 332 | 333 | CMakeCache.txt 334 | cmake-* 335 | _build/ 336 | _install/ 337 | out/ -------------------------------------------------------------------------------- /sources/ZydisDisassembler.cpp: -------------------------------------------------------------------------------- 1 | #include "polyhook2/ZydisDisassembler.hpp" 2 | #include "polyhook2/ErrorLog.hpp" 3 | 4 | PLH::ZydisDisassembler::ZydisDisassembler(PLH::Mode mode) : m_decoder(new ZydisDecoder()), m_formatter(new ZydisFormatter()) { 5 | m_mode = mode; 6 | if (ZYAN_FAILED(ZydisDecoderInit(m_decoder, 7 | (mode == PLH::Mode::x64) ? ZYDIS_MACHINE_MODE_LONG_64 : ZYDIS_MACHINE_MODE_LONG_COMPAT_32, 8 | (mode == PLH::Mode::x64) ? ZYDIS_STACK_WIDTH_64 : ZYDIS_STACK_WIDTH_32))) { 9 | Log::log("Failed to initialize zydis decoder", ErrorLevel::SEV); 10 | return; 11 | } 12 | 13 | if (ZYAN_FAILED(ZydisFormatterInit(m_formatter, ZYDIS_FORMATTER_STYLE_INTEL))) { 14 | Log::log("Failed to initialize zydis formatter", ErrorLevel::SEV); 15 | return; 16 | } 17 | 18 | ZydisFormatterSetProperty(m_formatter, ZYDIS_FORMATTER_PROP_FORCE_SEGMENT, ZYAN_TRUE); 19 | ZydisFormatterSetProperty(m_formatter, ZYDIS_FORMATTER_PROP_FORCE_SIZE, ZYAN_TRUE); 20 | } 21 | 22 | PLH::ZydisDisassembler::~ZydisDisassembler() { 23 | if (m_decoder) { 24 | delete m_decoder; 25 | m_decoder = nullptr; 26 | } 27 | 28 | if (m_formatter) { 29 | delete m_formatter; 30 | m_formatter = nullptr; 31 | } 32 | } 33 | 34 | PLH::insts_t PLH::ZydisDisassembler::disassemble( 35 | uint64_t firstInstruction, 36 | uint64_t start, 37 | uint64_t end, 38 | const MemAccessor& accessor 39 | ) { 40 | insts_t insVec; 41 | // m_branchMap.clear(); 42 | 43 | int64_t size = end - start; 44 | assert(size > 0); 45 | if (size <= 0) { 46 | return insVec; 47 | } 48 | 49 | // copy potentially remote memory to local buffer 50 | size_t read = 0; 51 | auto* buf = new uint8_t[(uint32_t)size]; 52 | if (!accessor.safe_mem_read(firstInstruction, (uint64_t)buf, size, read)) { 53 | delete[] buf; 54 | return insVec; 55 | } 56 | ZydisDecodedOperand decoded_operands[ZYDIS_MAX_OPERAND_COUNT]; 57 | ZydisDecodedInstruction insInfo; 58 | uint64_t offset = 0; 59 | bool endHit = false; 60 | while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(m_decoder, buf + offset, (ZyanUSize) (read - offset), &insInfo, decoded_operands))) { 61 | Instruction::Displacement displacement = {}; 62 | displacement.Absolute = 0; 63 | 64 | uint64_t address = start + offset; 65 | 66 | std::string opstr; 67 | if (!getOpStr(&insInfo, decoded_operands, address, &opstr)){ 68 | break; 69 | } 70 | 71 | 72 | Instruction inst(address, 73 | displacement, 74 | 0, 75 | false, 76 | false, 77 | buf + offset, 78 | insInfo.length, 79 | ZydisMnemonicGetString(insInfo.mnemonic), 80 | opstr, 81 | m_mode); 82 | 83 | setDisplacementFields(inst, &insInfo, decoded_operands); 84 | if (endHit && !isPadBytes(inst)) { 85 | break; 86 | } 87 | 88 | for (int i = 0; i < insInfo.operand_count; i++) { 89 | auto op = decoded_operands[i]; 90 | if (op.type == ZYDIS_OPERAND_TYPE_MEMORY && op.mem.type == ZYDIS_MEMOP_TYPE_MEM && op.mem.disp.has_displacement && op.mem.base == ZYDIS_REGISTER_NONE && op.mem.segment != ZYDIS_REGISTER_DS && inst.isIndirect()) { 91 | inst.setIndirect(false); 92 | } 93 | } 94 | 95 | insVec.push_back(inst); 96 | 97 | // searches instruction vector and updates references 98 | addToBranchMap(insVec, inst); 99 | if (isFuncEnd(inst, start == address)){ 100 | endHit = true; 101 | } 102 | 103 | offset += insInfo.length; 104 | } 105 | 106 | delete[] buf; 107 | return insVec; 108 | } 109 | 110 | bool PLH::ZydisDisassembler::getOpStr(ZydisDecodedInstruction* pInstruction, const ZydisDecodedOperand* decoded_operands, uint64_t addr, std::string* pOpStrOut) { 111 | char buffer[256]; 112 | if (ZYAN_SUCCESS(ZydisFormatterFormatInstruction(m_formatter, pInstruction, decoded_operands, pInstruction->operand_count, buffer, sizeof(buffer), addr, ZYAN_NULL))) { 113 | // remove mnemonic + space (op str is just the right hand side) 114 | std::string wholeInstStr(buffer); 115 | *pOpStrOut = wholeInstStr.erase(0, wholeInstStr.find(' ') + 1); 116 | return true; 117 | } 118 | return false; 119 | } 120 | 121 | void PLH::ZydisDisassembler::setDisplacementFields(PLH::Instruction& inst, const ZydisDecodedInstruction* zydisInst, const ZydisDecodedOperand* operands) const { 122 | inst.setBranching(zydisInst->meta.branch_type != ZYDIS_BRANCH_TYPE_NONE); 123 | inst.setCalling(zydisInst->mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_CALL); 124 | 125 | for (int i = 0; i < zydisInst->operand_count; i++) { 126 | const ZydisDecodedOperand* const operand = &operands[i]; 127 | 128 | // skip implicit operands (r/w effects) 129 | if (operand->visibility == ZYDIS_OPERAND_VISIBILITY_HIDDEN || 130 | operand->visibility == ZYDIS_OPERAND_VISIBILITY_INVALID) { 131 | continue; 132 | } 133 | 134 | switch (operand->type) { 135 | case ZYDIS_OPERAND_TYPE_REGISTER: { 136 | inst.setRegister(operand->reg.value); 137 | inst.addOperandType(Instruction::OperandType::Register); 138 | break; 139 | } 140 | case ZYDIS_OPERAND_TYPE_UNUSED: 141 | break; 142 | case ZYDIS_OPERAND_TYPE_MEMORY: { 143 | if (i == 1 && operand->mem.base == ZYDIS_REGISTER_ESP) { 144 | inst.setReadingSP(true); 145 | break; 146 | } 147 | 148 | // Relative to RIP/EIP 149 | inst.addOperandType(Instruction::OperandType::Displacement); 150 | 151 | if (zydisInst->attributes & ZYDIS_ATTRIB_IS_RELATIVE) { 152 | inst.setDisplacementOffset(zydisInst->raw.disp.offset); 153 | inst.setDisplacementSize((uint8_t)(zydisInst->raw.disp.size / 8)); 154 | inst.setRelativeDisplacement(operand->mem.disp.value); 155 | } 156 | 157 | if ((zydisInst->mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_JMP && inst.size() >= 2 && inst.getBytes().at(0) == 0xff && inst.getBytes().at(1) == 0x25) || 158 | (zydisInst->mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_CALL && inst.size() >= 2 && inst.getBytes().at(0) == 0xff && inst.getBytes().at(1) == 0x15) || 159 | (zydisInst->mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_CALL && inst.size() >= 3 && inst.getBytes().at(1) == 0xff && inst.getBytes().at(2) == 0x15) || 160 | (zydisInst->mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_JMP && inst.size() >= 3 && inst.getBytes().at(1) == 0xff && inst.getBytes().at(2) == 0x25) 161 | ) { 162 | 163 | // is displacement set earlier already? 164 | if (!inst.hasDisplacement()) { 165 | // displacement is absolute on x86 mode 166 | inst.setDisplacementOffset(zydisInst->raw.disp.offset); 167 | inst.setAbsoluteDisplacement(zydisInst->raw.disp.value); 168 | } 169 | inst.setIndirect(true); 170 | } 171 | 172 | break; 173 | } 174 | case ZYDIS_OPERAND_TYPE_POINTER: 175 | break; 176 | case ZYDIS_OPERAND_TYPE_IMMEDIATE: { 177 | inst.addOperandType(Instruction::OperandType::Immediate); 178 | 179 | // is displacement set earlier already? 180 | if (!inst.hasDisplacement() && zydisInst->attributes & ZYDIS_ATTRIB_IS_RELATIVE) { 181 | inst.setDisplacementOffset(zydisInst->raw.imm->offset); 182 | inst.setDisplacementSize((uint8_t)(zydisInst->raw.imm->size / 8)); 183 | inst.setRelativeDisplacement(zydisInst->raw.imm->value.s); 184 | return; 185 | } 186 | 187 | inst.setImmediate(zydisInst->raw.imm->value.s); 188 | inst.setImmediateSize(zydisInst->raw.imm->size / 8); 189 | 190 | break; 191 | } 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /UnitTests/windows/TestDetourSchemex64.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "polyhook2/Detour/x64Detour.hpp" 3 | 4 | #include "polyhook2/Tests/StackCanary.hpp" 5 | #include "polyhook2/Tests/TestEffectTracker.hpp" 6 | 7 | #include "polyhook2/PolyHookOsIncludes.hpp" 8 | 9 | EffectTracker schemeEffects; 10 | 11 | TEST_CASE("Testing detour schemes", "[DetourScheme][ADetour]") { 12 | typedef uint64_t (* IntFn)(); 13 | 14 | asmjit::JitRuntime rt; 15 | 16 | auto make_func = [&](const std::function& builder) { 17 | asmjit::CodeHolder code; 18 | code.init(rt.environment()); 19 | asmjit::x86::Assembler a(&code); 20 | builder(a); 21 | 22 | IntFn fn; 23 | auto error = rt.add(&fn, &code); 24 | 25 | if (error) { 26 | const auto message = std::string("Error generating function: ") + asmjit::DebugUtils::errorAsString(error); 27 | PLH::Log::log(message, PLH::ErrorLevel::SEV); 28 | exit(1); 29 | } 30 | 31 | return fn; 32 | }; 33 | 34 | SECTION("Validate valloc2 scheme in function with translation and back-references") { 35 | PLH::StackCanary canary; 36 | 37 | auto valloc_function = make_func([](asmjit::x86::Assembler& a) { 38 | auto SetRax = a.newLabel(); 39 | auto Exit = a.newLabel(); 40 | 41 | a.cmp(asmjit::x86::qword_ptr(asmjit::x86::rip, -11), 0x12345678); 42 | 43 | a.bind(SetRax); 44 | a.cmp(asmjit::x86::rax, 0x1337); 45 | a.je(Exit); 46 | a.mov(asmjit::x86::rax, 0x1337); 47 | a.jmp(SetRax); 48 | 49 | a.bind(Exit); 50 | a.ret(); 51 | }); 52 | 53 | static uint64_t tramp_valloc_function; 54 | IntFn hook_valloc_function = []() { 55 | PLH::StackCanary canary; 56 | schemeEffects.PeakEffect().trigger(); 57 | printf("hook_valloc_function called"); 58 | return ((IntFn) (tramp_valloc_function))(); 59 | }; 60 | 61 | PLH::x64Detour detour((uint64_t) valloc_function, (uint64_t) hook_valloc_function, &tramp_valloc_function); 62 | detour.setDetourScheme(PLH::x64Detour::detour_scheme_t::VALLOC2); 63 | REQUIRE(detour.hook()); 64 | schemeEffects.PushEffect(); 65 | REQUIRE(valloc_function() == 0x1337); 66 | REQUIRE(schemeEffects.PopEffect().didExecute()); 67 | REQUIRE(detour.unHook()); 68 | } 69 | 70 | SECTION("Validate in-place scheme in large function") { 71 | PLH::StackCanary canary; 72 | 73 | auto large_function = make_func([](auto& a) { 74 | a.mov(asmjit::x86::rax, 0x1234567890123456); 75 | a.mov(asmjit::x86::rbx, 0x6543210987654321); 76 | a.mov(asmjit::x86::rcx, 0x1234567890ABCDEF); 77 | a.mov(asmjit::x86::rcx, 0xFEDCBA0987654321); 78 | a.ret(); 79 | }); 80 | 81 | static uint64_t tramp_large_function; 82 | IntFn hook_large_function = []() { 83 | PLH::StackCanary canary; 84 | schemeEffects.PeakEffect().trigger(); 85 | printf("hook_large_function called"); 86 | return ((IntFn) (tramp_large_function))(); 87 | }; 88 | 89 | PLH::x64Detour detour((uint64_t) large_function, (uint64_t) hook_large_function, &tramp_large_function); 90 | detour.setDetourScheme(PLH::x64Detour::detour_scheme_t::INPLACE); 91 | REQUIRE(detour.hook()); 92 | schemeEffects.PushEffect(); 93 | large_function(); 94 | REQUIRE(schemeEffects.PopEffect().didExecute()); 95 | REQUIRE(detour.unHook()); 96 | } 97 | 98 | SECTION("Validate in-place scheme in medium function") { 99 | PLH::StackCanary canary; 100 | 101 | auto medium_function = make_func([](auto& a) { 102 | a.mov(asmjit::x86::rax, 0x1234567890123456); 103 | a.mov(asmjit::x86::rcx, 0x1234567890ABCDEF); 104 | a.ret(); 105 | }); 106 | 107 | static uint64_t tramp_medium_function; 108 | IntFn hook_medium_function = []() { 109 | PLH::StackCanary canary; 110 | schemeEffects.PeakEffect().trigger(); 111 | printf("hook_medium_function called"); 112 | return ((IntFn) (tramp_medium_function))(); 113 | }; 114 | 115 | PLH::x64Detour detour1((uint64_t) medium_function, (uint64_t) hook_medium_function, &tramp_medium_function); 116 | detour1.setDetourScheme(PLH::x64Detour::detour_scheme_t::INPLACE); 117 | REQUIRE(detour1.hook() == false); 118 | 119 | PLH::x64Detour detour2((uint64_t) medium_function, (uint64_t) hook_medium_function, &tramp_medium_function); 120 | detour2.setDetourScheme(PLH::x64Detour::detour_scheme_t::INPLACE_SHORT); 121 | REQUIRE(detour2.hook()); 122 | schemeEffects.PushEffect(); 123 | medium_function(); 124 | REQUIRE(schemeEffects.PopEffect().didExecute()); 125 | REQUIRE(detour2.unHook()); 126 | } 127 | 128 | SECTION("Validate in-place scheme in function with translation") { 129 | PLH::StackCanary canary; 130 | 131 | auto rip_function = make_func([](asmjit::x86::Assembler& a) { 132 | a.cmp(asmjit::x86::qword_ptr(asmjit::x86::rip, -11), 0x12345678); 133 | a.mov(asmjit::x86::rax, 0x1337); 134 | a.ret(); 135 | }); 136 | 137 | static uint64_t tramp_rip_function; 138 | IntFn hook_rip_function = []() { 139 | PLH::StackCanary canary; 140 | schemeEffects.PeakEffect().trigger(); 141 | printf("hook_rip_function called"); 142 | return ((IntFn) (tramp_rip_function))(); 143 | }; 144 | 145 | PLH::x64Detour detour((uint64_t) rip_function, (uint64_t) hook_rip_function, &tramp_rip_function); 146 | detour.setDetourScheme(PLH::x64Detour::detour_scheme_t::INPLACE_SHORT); 147 | REQUIRE(detour.hook()); 148 | schemeEffects.PushEffect(); 149 | REQUIRE(rip_function() == 0x1337); 150 | REQUIRE(schemeEffects.PopEffect().didExecute()); 151 | REQUIRE(detour.unHook()); 152 | } 153 | 154 | SECTION("Validate code-cave scheme in small function") { 155 | PLH::StackCanary canary; 156 | 157 | auto small_function = make_func([](auto& a) { 158 | a.mov(asmjit::x86::rax, 0x1234567890123456); 159 | a.ret(); 160 | }); 161 | 162 | static uint64_t tramp_small_function; 163 | IntFn hook_small_function = []() { 164 | PLH::StackCanary canary; 165 | schemeEffects.PeakEffect().trigger(); 166 | printf("tramp_small_function called"); 167 | return ((IntFn) (tramp_small_function))(); 168 | }; 169 | 170 | PLH::x64Detour detour1((uint64_t) small_function, (uint64_t) hook_small_function, &tramp_small_function); 171 | detour1.setDetourScheme(PLH::x64Detour::detour_scheme_t::INPLACE_SHORT); 172 | REQUIRE(detour1.hook() == false); 173 | 174 | // TODO: Polyhook is not guaranteed to find a cave, hence this test will often fail. 175 | // We need to find a way to deliberately reserve code cave. 176 | 177 | // PLH::x64Detour detour2((uint64_t) small_function, (uint64_t) hook_small_function, &tramp_small_function); 178 | // detour2.setDetourScheme(PLH::x64Detour::detour_scheme_t::CODE_CAVE); 179 | // REQUIRE(detour2.hook()); 180 | // schemeEffects.PushEffect(); 181 | // small_function(); 182 | // REQUIRE(schemeEffects.PopEffect().didExecute()); 183 | // REQUIRE(detour2.unHook()); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PolyHook 2.0 2 | C++ 20, x86/x64 Hooking Libary v2.0 3 | 4 | Article 1: https://www.codeproject.com/articles/1100579/polyhook-the-cplusplus-x-x-hooking-library 5 | 6 | Article 2: https://www.codeproject.com/Articles/1252212/PolyHook-2-Cplusplus17-x86-x64-Hooking-Library 7 | 8 | Article 3: https://www.fireeye.com/blog/threat-research/2020/11/wow64-subsystem-internals-and-hooking-techniques.html 9 | 10 | Dynamic Re-Writing: https://twitter.com/stevemk14ebr/status/1518621861692817409 11 | 12 | # Community 13 | Ask for help, chat with others, talk to me here 14 | * [Official Gitter Chat](https://gitter.im/PolyHook/Lobby) 15 | 16 | # Packaging 17 | PolyHook2 is available on vcpkg. Consider trying that installation method if you prefer. Just install vcpkg from microsofts directions: 18 | 19 | Commands: 20 | ``` 21 | λ git clone https://github.com/Microsoft/vcpkg.git 22 | λ cd vcpkg 23 | λ .\bootstrap-vcpkg.bat -disableMetrics 24 | λ (as admin) .\vcpkg integrate install 25 | ``` 26 | For x86: 27 | ``` 28 | λ vcpkg.exe install polyhook2:x86-windows-static polyhook2:x86-windows 29 | ``` 30 | For x64: 31 | ``` 32 | λ vcpkg.exe install polyhook2:x64-windows-static polyhook2:x64-windows 33 | ``` 34 | 35 | You then simply include the polyhook headers, be sure to link the generated .lib. 36 | 37 | # Build Manually 38 | See: https://github.com/stevemk14ebr/PolyHook_2_0/pull/59#issuecomment-619223616 39 | ``` 40 | λ git clone --recursive https://github.com/stevemk14ebr/PolyHook_2_0.git 41 | λ cd PolyHook_2_0 42 | λ git submodule update --init --recursive 43 | λ (dynamic build) cmake -B"./_build" -DCMAKE_INSTALL_PREFIX="./_install/" -DPOLYHOOK_BUILD_SHARED_LIB=ON 44 | λ (static build) cmake -B"./_build" -DCMAKE_INSTALL_PREFIX="./_install/" -DPOLYHOOK_BUILD_SHARED_LIB=OFF 45 | λ cmake --build "./_build" --config Release --target install 46 | ``` 47 | I provide directions below for how to setup the visual studio cmake environment only. If you don't want to use visual studio that's fine, this is a standard cmake project and will build from command line just fine. 48 | 49 | ### Visual Studio 2022 50 | An up to date visual studio is required. First clone the project and perform submodule init as above. Do not run the cmake commands, instead: 51 | 52 | Open VS 2022, go to file->open->cmake.. this will load the project and start cmake generation. Next goto cmake->build all or cmake->build, you can also set a startup item and release mode to use the play button (do not use the install target). Capstone, Zydis, and asmjit are set to automatically build and link, you DO NOT need to build them seperately. 53 | 54 | ### Documentation 55 | https://stevemk14ebr.github.io/PolyHook_2_0/ & Read the Tests! 56 | 57 | I've setup an example project to show how to use this as a static library. You should clear your cmake cache between changing these options. The dll is built with the cmake option to export all symbols. This is different from the typical windows DLL where things are manually exported via declspec(dllexport), instead it behaves how linux dlls do with all symbols exported by default. This style should make it easier to maintain the code, the downside is there are many exports but i don't care. 58 | 59 | # Features 60 | 0) Both capstone and zydis are supported as disassembly backends and are fully abstracted. 61 | 1) Inline hook (x86/x64 Detour) 62 | - Places a jmp to a callback at the prologue, and then allocates a trampoline to continue execution of the original function 63 | - Operates entirely on an intermediate instruction object, disassembler engine is swappable, capstone included by default 64 | - Can JIT callback for when calling conv is unknown at compile time (see ILCallback.cpp) 65 | - Follows already hooked functions 66 | - Resolves indirect calls such as through the iat and hooks underlying function 67 | - Relocates prologue and resolves all position dependent code 68 | - Branches into overwritten section are resolved to the new moved location 69 | - Jmps from moved prologue back to original section are resolved through a jmp table 70 | - Relocations inside the moved section are resolved (not using relocation table, disassembles using engine) 71 | - Non relocatable instructions are re-written by dynamic binary re-writing and replaced with semantically equivalent instructions 72 | - x64 trampoline is not restricted to +- 2GB, can be anywhere, avoids shadow space + no registers spoiled (depending on detour scheme). 73 | - Overwriting code caves and padding bytes may be set as a primary strategy instead, or as a fallback scheme 74 | - If inline hook fails at an intermediate step the original function will not be malformed. All writes are batched until after we know later steps succeed. 75 | - Cross-Architecture hooking is _fully_ supported. Including the overriding of memory acccess routines to allow read/write of 64bit memory from 32bit process. You can hook 64bit from 32bit process if you're clever enough to write the shellcode required for the callbacks. 76 | - Effecient reHook-ing logic is implemented. This can be used to combat third parties overwriting prologues back to original bytes. This is optimized into a few simple memcpy's rather than re-executing the entire logic in hook(). 77 | 78 | 2) Runtime Inline Hook 79 | - All the goodness of normal inline hooks, but JIT's a translation stub compatible with the given typedef and ABI. The translation stub will move arguments into a small struct, which is passed as pointer to a callback and allow the spoofing of return value. This allows tools to generate hook translation stubs at runtime, allowing for the full inline hooking of functions where the typedef is not known until runtime. 80 | 81 | 3) Virtual Function Swap (VFuncSwap) 82 | * Swaps the pointers at given indexs in a C++ VTable to point to a callbacks 83 | 4) Virtual Table Swap (VTableSwap) 84 | * Performs a deep copy on a c++ VTable and replaces the pointer to the table with the newly allocated copy. Then swaps the pointer entries in the copy to point to callbacks 85 | 5) Software Breakpoint Hook (BreakpointHook) 86 | * Overwrites the first byte of a function with 0xCC and calls the callback in the exception handler. Provides the user with an automatic method to restore the original overwritten byte 87 | 6) Hardware Breakpoint Hook (HWBreakpointHook) 88 | * Sets the debug registers of the CPU to add a HW execution BP for the calling thread. The callback is called in the exception handler. **Remember HW BP's are per thread, the thread calling hook() must be the same as the one that is being hooked. You may find a quick detour, then setting up the HWBP in the detour callback, then unhooking to be a useful construct.** 89 | 7) Import Address Table Hook (IatHook) 90 | * Resolves loaded modules through PEB, finds IAT, then swaps the thunk pointer to the callback. 91 | 8) Export Address Table Hook (EatHook) 92 | * Resolves loaded modules through PEB, finds EAT, then swaps pointer to export to the callback. Since this is a 32bit offset we optionally allocate a trampoline stub to do the full transfer to callback if it's beyond 32bits. 93 | 94 | # Extras 95 | - THOROUGHLY unit tested, hundreds of tests, using the fantastic library Catch 96 | - Unix compatible 97 | 98 | # Notes 99 | - Breakpoint tests must not be run under a debugger. They are commented out by default now. 100 | 101 | # Future 102 | Linux support. There is a partial unix implementation, but it is not well tested. Please contribute or report bugs. 103 | 104 | # License 105 | MIT - Please consider contributing 106 | 107 | # Resource &/| references 108 | evolution536, DarthTon, IChooseYou on Unknowncheats.me 109 | 110 | @Ochii & https://www.unknowncheats.me/forum/c-and-c/50426-eat-hooking-dlls.html for EAT implementation 111 | 112 | https://github.com/DarthTon/Blackbone 113 | 114 | https://www.codeproject.com/Articles/44326/MinHook-The-Minimalistic-x-x-API-Hooking-Libra 115 | 116 | https://wiki.osdev.org/CPU_Registers_x86#Debug_Registers 117 | 118 | https://reverseengineering.stackexchange.com/questions/14992/what-are-the-vectored-continue-handlers 119 | 120 | https://web.archive.org/web/20170126064234/https://modexp.wordpress.com/2017/01/15/shellcode-resolving-api-addresses/ 121 | 122 | https://github.com/odzhan/shellcode/blob/master/os/win/getapi/dynamic/getapi.c 123 | -------------------------------------------------------------------------------- /UnitTests/linux/TestDetourx86.cpp: -------------------------------------------------------------------------------- 1 | // NOLINTBEGIN(*-err58-cpp) 2 | #include 3 | 4 | #include 5 | 6 | #include "polyhook2/Detour/x86Detour.hpp" 7 | 8 | #include "polyhook2/Tests/StackCanary.hpp" 9 | #include "polyhook2/Tests/TestEffectTracker.hpp" 10 | 11 | #include "../TestUtils.hpp" 12 | 13 | namespace { 14 | 15 | EffectTracker effects; 16 | 17 | constexpr uint8_t JUMP_SIZE = 5; 18 | 19 | } 20 | 21 | NOINLINE int __cdecl hookMe1() { 22 | volatile int var = 1; 23 | volatile int var2 = 0; 24 | var2 += 3; 25 | var2 = var + var2; 26 | var2 *= 30 / 3; 27 | var = 2; 28 | printf("%d %d\n", var, var2); // 2, 40 29 | return var; 30 | } 31 | 32 | PLH_TEST_DETOUR_CALLBACK(hookMe1, { 33 | std::cout << "Hook 1 Called! Trampoline: 0x" << std::hex << hookMe1_trmp << std::endl; 34 | }); 35 | 36 | unsigned char hookMe2[] = { 37 | 0x55, // [00] push ebp 38 | 0x8b, 0xec, // [01] mov ebp,esp 39 | 0x74, 0xfb, // [03] je 0x0 40 | 0x74, 0xfa, // [05] je 0x1 41 | 0x8b, 0xec, // [07] mov ebp,esp 42 | 0x8b, 0xec, // [09] mov ebp,esp 43 | 0x8b, 0xec, // [0B] mov ebp,esp 44 | 0x90, // [0D] nop 45 | 0x90, // [0E] nop 46 | 0x90, // [0F] nop 47 | 0x90, // [10] nop 48 | 0x90, // [11] nop 49 | 0x90, // [12] nop 50 | }; 51 | 52 | uint64_t nullTramp = 0; 53 | NOINLINE void h_nullstub() { 54 | PLH::StackCanary canary; 55 | PLH_STOP_OPTIMIZATIONS(); 56 | } 57 | 58 | unsigned char hookMe3[] = { 59 | 0x55, // [00] push ebp 60 | 0x89, 0xe5, // [01] mov ebp,esp 61 | 0x89, 0xe5, // [03] mov ebp,esp 62 | 0x89, 0xe5, // [05] mov ebp,esp 63 | 0x89, 0xe5, // [07] mov ebp,esp 64 | 0x90, // [09] nop 65 | 0x90, // [0A] nop 66 | 0x7f, 0xf4, // [0B] jg 0x1 67 | 0x90, // [0D] nop 68 | 0x90, // [0E] nop 69 | 0x90, // [0F] nop 70 | 0x90, // [10] nop 71 | 0x90, // [11] nop 72 | 0x90, // [12] nop 73 | }; 74 | 75 | uint8_t hookMe4[] = { 76 | 0x55, // push ebp 77 | 0x8b, 0xec, // mov ebp, esp 78 | 0x56, // push esi 79 | 0x8b, 0x75, 0x08, // mov esi, [ebp+8] 80 | 0xf6, 0x46, 0x30, 0x02, // test byte ptr ds:[esi+0x30], 0x2 81 | 0x90, 0x90, 0x90, 0x90, // nop x4 82 | 0x90, 0x90, 0x90, 0x90, // nop x4 83 | 0xc3 // ret 84 | }; 85 | 86 | // old NtQueueApcThread, call fs:0xC0 was weird 87 | unsigned char hookMe5[] = { 88 | 0xB8, 0X44, 0X00, 0X00, 0X00, // mov eax, 0x44 89 | 0x64, 0xff, 0x15, 0xc0, 0x00, 0x00, 0x00, // call dword ptr fs:0xc0 90 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // nop x7 91 | 0xc2, 0x14, 0x00 // retn 0x14 92 | }; 93 | 94 | NOINLINE void PH_ATTR_NAKED hookMeLoop() { 95 | asm("xor %eax, %eax;\n" 96 | "START: inc %eax;\n" 97 | "cmp $5, %eax;\n" 98 | "jle START;\n" 99 | "ret;"); 100 | } 101 | 102 | extern "C" NOINLINE uintptr_t PH_ATTR_NAKED returnESP() { 103 | __asm__ __volatile__ ( 104 | "movl (%esp), %eax\n" 105 | "ret" 106 | ); 107 | } 108 | 109 | NOINLINE uintptr_t PH_ATTR_NAKED readESP() { 110 | __asm__ __volatile__ ( 111 | "call returnESP\n" 112 | "ret" 113 | ); 114 | } 115 | PLH_TEST_DETOUR_CALLBACK(readESP); 116 | 117 | NOINLINE uintptr_t PH_ATTR_NAKED inlineReadESP() { 118 | __asm__ __volatile__ ( 119 | "call 0f\n" 120 | "0: pop %%eax\n" 121 | "ret" 122 | ::: "eax" 123 | ); 124 | } 125 | PLH_TEST_DETOUR_CALLBACK(inlineReadESP); 126 | 127 | PLH_TEST_DETOUR_CALLBACK(hookMeLoop); 128 | 129 | // PLH_TEST_DETOUR_CALLBACK doesn't support variadic functions yet. 130 | uint64_t hookPrintfTramp = 0; 131 | NOINLINE int h_hookPrintf(const char *format, ...) { 132 | char buffer[512]; 133 | va_list args; 134 | va_start(args, format); 135 | const auto written = vsnprintf(buffer, sizeof(buffer), format, args); 136 | va_end(args); 137 | 138 | const std::string message = {buffer, static_cast(written)}; 139 | 140 | effects.PeakEffect().trigger(); 141 | return PLH::FnCast(hookPrintfTramp, &printf)("INTERCEPTED YO:%s", message.c_str()); 142 | } 143 | 144 | // must specify specific overload of std::pow by assigning to pFn of type 145 | const auto &pow_double = std::pow; 146 | PLH_TEST_DETOUR_CALLBACK(pow_double); 147 | 148 | PLH_TEST_DETOUR_CALLBACK(malloc); 149 | 150 | #include 151 | PLH_TEST_DETOUR_CALLBACK(recv); 152 | 153 | TEST_CASE("Testing x86 detours", "[x86Detour][ADetour]") { 154 | PLH::test::registerTestLogger(); 155 | 156 | SECTION("Normal function") { 157 | PLH::StackCanary canary; 158 | PLH::x86Detour PLH_TEST_DETOUR(hookMe1); 159 | REQUIRE(detour.hook() == true); 160 | 161 | effects.PushEffect(); 162 | volatile auto result = hookMe1(); 163 | PH_UNUSED(result); 164 | REQUIRE(effects.PopEffect().didExecute()); 165 | REQUIRE(detour.unHook() == true); 166 | } 167 | 168 | SECTION("Normal function rehook") { 169 | PLH::StackCanary canary; 170 | PLH::x86Detour PLH_TEST_DETOUR(hookMe1); 171 | REQUIRE(detour.hook() == true); 172 | 173 | effects.PushEffect(); 174 | REQUIRE(detour.reHook() == true); // can only really test this doesn't cause memory corruption easily 175 | volatile auto result = hookMe1(); 176 | REQUIRE(result == 2); 177 | REQUIRE(effects.PopEffect().didExecute()); 178 | REQUIRE(detour.unHook() == true); 179 | } 180 | 181 | SECTION("Jmp into prologue w/ src in range") { 182 | PLH::x86Detour detour((uint64_t)&hookMe2, (uint64_t)&h_nullstub, &nullTramp); 183 | 184 | REQUIRE(detour.hook() == true); 185 | REQUIRE(detour.unHook() == true); 186 | } 187 | 188 | SECTION("Jmp into prologue w/ src out of range") { 189 | PLH::x86Detour detour((uint64_t)&hookMe3, (uint64_t)&h_nullstub, &nullTramp); 190 | REQUIRE(detour.hook() == true); 191 | REQUIRE(detour.unHook() == true); 192 | } 193 | 194 | SECTION("Test instruction in prologue") { 195 | PLH::x86Detour detour((uint64_t)&hookMe4, (uint64_t)&h_nullstub, &nullTramp); 196 | REQUIRE(detour.hook() == true); 197 | REQUIRE(detour.unHook() == true); 198 | } 199 | 200 | SECTION("Call with fs base") { 201 | PLH::x86Detour detour((uint64_t)&hookMe5, (uint64_t)&h_nullstub, &nullTramp); 202 | REQUIRE(detour.hook() == true); 203 | REQUIRE(detour.unHook() == true); 204 | } 205 | 206 | SECTION("Loop") { 207 | PLH::x86Detour PLH_TEST_DETOUR(hookMeLoop); 208 | REQUIRE(detour.hook() == true); 209 | 210 | effects.PushEffect(); 211 | hookMeLoop(); 212 | REQUIRE(effects.PopEffect().didExecute()); 213 | REQUIRE(detour.unHook() == true); 214 | } 215 | 216 | SECTION("Test #215 (call to routine returning ESP)") { 217 | PLH::x86Detour PLH_TEST_DETOUR(readESP); 218 | REQUIRE(detour.hook() == true); 219 | 220 | effects.PushEffect(); 221 | const auto esp = readESP(); 222 | REQUIRE(esp == (uintptr_t)readESP + JUMP_SIZE); 223 | REQUIRE(detour.hasDiagnostic(PLH::Diagnostic::FixedCallToRoutineReadingSP)); 224 | REQUIRE(effects.PopEffect().didExecute()); 225 | REQUIRE(detour.unHook() == true); 226 | } 227 | 228 | SECTION("Test #217 (inline call to read ESP)") { 229 | PLH::x86Detour PLH_TEST_DETOUR(inlineReadESP); 230 | REQUIRE(detour.hook() == true); 231 | 232 | effects.PushEffect(); 233 | const auto esp = inlineReadESP(); 234 | REQUIRE(esp == (uintptr_t)inlineReadESP + JUMP_SIZE); 235 | REQUIRE(detour.hasDiagnostic(PLH::Diagnostic::FixedInlineCallToReadSP)); 236 | REQUIRE(effects.PopEffect().didExecute()); 237 | REQUIRE(detour.unHook() == true); 238 | } 239 | 240 | #ifndef NDEBUG 241 | // This test is disabled in Release builds due to aggressive optimization of compilers. 242 | // Specifically with clang on Linux the std::pow function is always inlined. 243 | // Hence, the hooked function is never called. 244 | 245 | // it's a pun... 246 | // ^ what pun? nothing found on the web >.< 247 | SECTION("hook pow") { 248 | 249 | PLH::x86Detour PLH_TEST_DETOUR(pow_double); 250 | REQUIRE(detour.hook() == true); 251 | 252 | effects.PushEffect(); 253 | volatile double result = pow_double(2, 2); 254 | REQUIRE(result == 4.0); 255 | detour.unHook(); 256 | REQUIRE(effects.PopEffect().didExecute()); 257 | } 258 | #endif 259 | 260 | SECTION("hook malloc") { 261 | PLH::x86Detour PLH_TEST_DETOUR(malloc); 262 | effects.PushEffect(); // catch does some allocations, push effect first so peak works 263 | REQUIRE(detour.hook() == true); 264 | 265 | void *pMem = malloc(16); 266 | free(pMem); 267 | detour.unHook(); // unhook so we can popeffect safely w/o catch allocation happening again 268 | REQUIRE(effects.PopEffect().didExecute()); 269 | } 270 | 271 | SECTION("hook recv") { 272 | PLH::x86Detour PLH_TEST_DETOUR(recv); 273 | REQUIRE(detour.hook() == true); 274 | } 275 | 276 | SECTION("hook printf") { 277 | PLH::x86Detour detour((uint64_t)&printf, (uint64_t)h_hookPrintf, &hookPrintfTramp); 278 | REQUIRE(detour.hook() == true); 279 | 280 | effects.PushEffect(); 281 | printf("%s %f\n", "hi", .5f); 282 | detour.unHook(); 283 | REQUIRE(effects.PopEffect().didExecute()); 284 | } 285 | } 286 | 287 | // NOLINTEND(*-err58-cpp) --------------------------------------------------------------------------------