diff --git a/CMakeLists.txt b/CMakeLists.txt index a367891..f53ea78 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.18) +cmake_minimum_required(VERSION 3.18..3.28) project(FunctionHook) set(CMAKE_CXX_STANDARD 20) diff --git a/include/FunctionHook/Hook.h b/include/FunctionHook/Hook.h index 2b3cfeb..f964622 100644 --- a/include/FunctionHook/Hook.h +++ b/include/FunctionHook/Hook.h @@ -4,33 +4,46 @@ #include namespace FunctionHooking { - class Detour { - private: - void* source = nullptr; //The function to be hooked. - void* destination = nullptr; //The function we are redirecting execution to. + class Detour; +} - std::vector overwritten_bytes{}; - std::vector hook_bytes{}; - void createHook(void* source_address, void* destination_address); - public: - void removeHook(); - Detour(void* source_address, void* destination_address); - Detour() = default; +class FunctionHooking::Detour { +private: + void* source = nullptr; + void* destination = nullptr; + std::vector overwritten_bytes{}; + std::vector hook_bytes{}; +private: + void CreateHook(void* source_address, void* destination_address); + void RemoveHook(); +public: + /// @returns True if our hook is present. + /// @note This allows you to know if something has overwritten our hook. + [[nodiscard]] bool Valid(); - //TODO This is *technically* not thread safe. - template - inline typename std::enable_if::value, T>::type callOriginal(Args... args) { - removeHook(); - const T result = reinterpret_cast(source)(args...); - createHook(source, destination); - return result; - } + template + inline typename std::enable_if::value, T>::type CallOriginal(Args... args) { + RemoveHook(); + const T result = reinterpret_cast(source)(args...); + CreateHook(source, destination); + return result; + } - template - inline typename std::enable_if::value, void>::type callOriginal(Args... args) { - removeHook(); - reinterpret_cast(source)(args...); - createHook(source, destination); - } - }; -} \ No newline at end of file + template + inline typename std::enable_if::value, void>::type CallOriginal(Args... args) { + RemoveHook(); + reinterpret_cast(source)(args...); + CreateHook(source, destination); + } + +public: + /// @returns True if the function is detour hooked. + /// @param function_address The function to check. + static bool Hooked(void* function_address); +public: + /// Create a new detour hook. + /// @param source_address The function to be hooked. + /// @param destination_address The function we are redirecting execution to. + Detour(void* source_address, void* destination_address); + ~Detour(); +}; \ No newline at end of file diff --git a/main.cpp b/main.cpp index 4363f78..22a19ff 100644 --- a/main.cpp +++ b/main.cpp @@ -3,8 +3,8 @@ using namespace FunctionHooking; -Detour voidDetour; -Detour stringDetour; +Detour* voidDetour; +Detour* stringDetour; void original_void() { std::cout << "Original void function." << std::endl; @@ -12,7 +12,7 @@ void original_void() { void hook_void() { std::cout << "Void hook function." << std::endl; - voidDetour.callOriginal(); + voidDetour->CallOriginal(); } std::string original_string(const std::string& string) { @@ -23,16 +23,15 @@ std::string original_string(const std::string& string) { std::string hook_string(const std::string& string) { std::cout << string + " hook function." << std::endl; - return stringDetour.callOriginal(string); + return stringDetour->CallOriginal(string); } int main() { //Set up hooks. - voidDetour = FunctionHooking::Detour((void*) original_void, (void*) hook_void); - stringDetour = FunctionHooking::Detour((void*) original_string, (void*) hook_string); + voidDetour = new Detour((void*) original_void, (void*) hook_void); + stringDetour = new Detour((void*) original_string, (void*) hook_string); original_void(); std::cout << std::endl; original_string("String"); - return 0; } \ No newline at end of file diff --git a/src/linux64/Hook.cpp b/src/linux64/Hook.cpp index ef63518..6499efc 100644 --- a/src/linux64/Hook.cpp +++ b/src/linux64/Hook.cpp @@ -2,12 +2,15 @@ #include #include #include +#include -void FunctionHooking::Detour::createHook(void* source_address, void* destination_address) { - source = source_address; - destination = destination_address; +using namespace FunctionHooking; +constexpr std::array empty_hook = { 0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0xFF, 0xE2 }; + +void Detour::CreateHook(void* source_address, void* destination_address) { + hook_bytes.resize(empty_hook.size()); + memcpy(hook_bytes.data(), empty_hook.data(), hook_bytes.size()); - hook_bytes.insert(hook_bytes.begin(), {0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x41, 0xFF, 0xE2}); overwritten_bytes.resize(hook_bytes.size()); //Save the bytes to be overwritten from the function prelude. @@ -24,4 +27,14 @@ void FunctionHooking::Detour::createHook(void* source_address, void* destination //Make the memory page non-writable again. mprotect((void*)((uintptr_t)source_address & ~(sysconf(_SC_PAGE_SIZE)-1)), sysconf(_SC_PAGE_SIZE), PROT_READ|PROT_EXEC); +} + +bool Detour::Hooked(void* function_address) { + std::vector current_data(empty_hook.size()); + memcpy(current_data.data(), function_address, current_data.size()); + + if (current_data[0] == empty_hook[0] && current_data[1] == empty_hook[1] && current_data[10] == empty_hook[10] + && current_data[11] == empty_hook[11] && current_data[12] == empty_hook[12]) + return true; + return false; } \ No newline at end of file diff --git a/src/linuxCommon/Hook.cpp b/src/linuxCommon/Hook.cpp index 5cc9b37..30aeecb 100644 --- a/src/linuxCommon/Hook.cpp +++ b/src/linuxCommon/Hook.cpp @@ -3,7 +3,8 @@ #include #include -void FunctionHooking::Detour::removeHook() { +using namespace FunctionHooking; +void Detour::RemoveHook() { //Make the target memory page writable. mprotect((void*)((uintptr_t)source & ~(sysconf(_SC_PAGE_SIZE)-1)), sysconf(_SC_PAGE_SIZE), PROT_READ|PROT_WRITE|PROT_EXEC); @@ -15,6 +16,24 @@ void FunctionHooking::Detour::removeHook() { mprotect((void*)((uintptr_t)source & ~(sysconf(_SC_PAGE_SIZE)-1)), sysconf(_SC_PAGE_SIZE), PROT_READ|PROT_EXEC); } -FunctionHooking::Detour::Detour(void *source_address, void *destination_address) { - createHook(source_address, destination_address); -} \ No newline at end of file +Detour::Detour(void* source_address, void* destination_address) : source(source_address), destination(destination_address) { + CreateHook(source, destination); +} + +bool FunctionHooking::Detour::Valid() { + if (source == nullptr || destination == nullptr) + return false; + + if (hook_bytes.empty() || overwritten_bytes.empty()) + return false; + + std::vector current_data(hook_bytes.size()); + memcpy(current_data.data(), source, current_data.size()); + + return current_data == hook_bytes; +} + +Detour::~Detour() { + if (Valid()) + RemoveHook(); +} diff --git a/src/linuxRV64/Hook.cpp b/src/linuxRV64/Hook.cpp index b13cc75..4f25eee 100644 --- a/src/linuxRV64/Hook.cpp +++ b/src/linuxRV64/Hook.cpp @@ -3,38 +3,39 @@ #include #include -void FunctionHooking::Detour::createHook(void* source_address, void* destination_address) { - source = source_address; - destination = destination_address; +constexpr uint32_t JAL_OPCODE = 0b1101111; +constexpr int JAL_OFFSET_SHIFT = 12; +constexpr int JAL_MASK = 0xfff; - hook_bytes.insert(hook_bytes.begin(), { 0x17, 0x05, 0x00, 0x00, 0x03, 0x25, 0x05, 0x00, 0x67, 0x00, 0x0a, 0x00} ); +void FunctionHooking::Detour::CreateHook(void* source_address, void* destination_address) { + intptr_t target_addr = reinterpret_cast(source_address); + intptr_t hook_addr = reinterpret_cast(destination_address); + int32_t offset = (hook_addr - target_addr) >> 1; - overwritten_bytes.resize(hook_bytes.size() + 8); + // Encode the offset for the JAL instruction + uint32_t instruction = (JAL_OPCODE & JAL_MASK) | + ((offset & 0x7fe) << 20) | // bits 1 to 10 of offset + ((offset & 0x800) << 9) | // bit 11 of offset + ((offset & 0xff000) << 9); // bits 12 to 19 of offset - //Save the bytes to be overwritten from the function prelude - memcpy(overwritten_bytes.data(), source_address, hook_bytes.size()); + hook_bytes.resize(sizeof(instruction)); + std::memcpy(hook_bytes.data(), &instruction, sizeof(instruction)); - //Calculate the offset. - intptr_t offset = (intptr_t)destination_address - (intptr_t)source_address; + overwritten_bytes.resize(sizeof(instruction)); + std::memcpy(overwritten_bytes.data(), source_address, sizeof(instruction)); - //Place the offset into the auipc instruction - memcpy(&hook_bytes[1], &offset, sizeof(uint32_t)); + // Determine the page size and page containing the target function + long page_size = sysconf(_SC_PAGESIZE); + void* page_start = reinterpret_cast(target_addr & ~(page_size - 1)); - //Append the destination address after the instructions - hook_bytes.insert(hook_bytes.end(), (uint8_t*)&destination_address, (uint8_t*)&destination_address + sizeof(void*)); + // Change memory protection to allow writing + if (mprotect(page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) + return; - //Save the bytes to be overwritten from the function prelude. - memcpy(overwritten_bytes.data(), source_address, hook_bytes.size()); + // Write the instruction to the target function + std::memcpy(source_address, &instruction, sizeof(instruction)); - //Put the destination address into the jump. - memcpy(&hook_bytes[2], &destination_address, sizeof(void*)); - - //Make the target memory page writable. - mprotect((void*)((uintptr_t)source_address & ~(sysconf(_SC_PAGE_SIZE)-1)), sysconf(_SC_PAGE_SIZE), PROT_READ|PROT_WRITE|PROT_EXEC); - - //Write the jmp to the beginning of the function prelude. - memcpy(source_address, hook_bytes.data(), hook_bytes.size()); - - //Make the memory page non-writable again. - mprotect((void*)((uintptr_t)source_address & ~(sysconf(_SC_PAGE_SIZE)-1)), sysconf(_SC_PAGE_SIZE), PROT_READ|PROT_EXEC); -} + // Restore original memory protection + if (mprotect(page_start, page_size, PROT_READ | PROT_EXEC) == -1) + return; + }