/* * MIT License * * Copyright (c) 2017 Rokas Kupstys * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #pragma once #include #include #include namespace hooker { namespace detail { extern "C" { #include "hooker.h" } struct Helper { }; } #if __cplusplus >= 201402L /// Pattern for find_pattern() function. template struct pattern { /// Bytes to find. Value of wildcard byte or byte half can be anything. uint8_t pattern[N]; /// Wildcard pattern. Byte value may be one of: 0? = 1, ?0 = 2, ?? = 3. uint8_t wildcard[N]; }; # define CPP14(x) x namespace detail { // Convert hex character to a number. constexpr uint8_t char_to_byte(char c) { if (c >= '0' && c <= '9') return c - '0'; else if (c >= 'a' && c <= 'f') return 0x0A + c - 'a'; else if (c >= 'A' && c <= 'F') return 0x0A + c - 'A'; else if (c == '?') return 0; else throw std::runtime_error("Not a hex character."); } // Convert text hex byte at `idx` to binary. template constexpr uint8_t get_pattern_byte(const char(&s)[N], size_t idx) { if (s[idx * 3 + 2] != ' ' && s[idx * 3 + 2] != '\0') throw std::runtime_error("Improperly formatted pattern."); else return (char_to_byte(s[idx * 3]) << 4) | char_to_byte(s[idx * 3 + 1]); } // Convert text wildcard to binary mask. template constexpr uint8_t get_wildcard_byte(const char(&s)[N], size_t idx) { return (s[idx * 3] == '?' ? 2 : 0) | (s[idx * 3 + 1] == '?' ? 1 : 0); } // Convert a character array to binary version and wildcard array. template constexpr pattern decode_string_pattern(const char(&s)[N], std::index_sequence) { return { { get_pattern_byte(s, Is)... }, { get_wildcard_byte(s, Is)... }, }; } } // Create a binary pattern from raw string litteral in format: "AB C? ?D ??". template constexpr const pattern mkpat(const char(&s)[N]) { if ((N % 3) == 0) return detail::decode_string_pattern(s, std::make_index_sequence()); else throw std::runtime_error("Improperly formatted pattern."); } #else # define CPP14(x) #endif #if __cplusplus > 201703L using bit_cast = std::bit_cast; #else template #if __cplusplus >= 201703L [[nodiscard]] #endif To bit_cast(From &&from) #if __cplusplus >= 201402L noexcept(std::is_nothrow_constructible::value) #endif { static_assert(std::is_trivially_copyable::type>::type>::value, "From type must be trivially copable."); static_assert(std::is_trivially_copyable::type>::type>::value, "To type must be trivially copiable."); static_assert(sizeof(From) == sizeof(To), "Sizes of From and To types must be the same."); static_assert(std::is_default_constructible::value, "To type must be default constructible."); #if __cplusplus >= 201402L auto result = (typename std::aligned_storage_t){}; #else To result{}; #endif return *static_cast(memcpy(&result, &from, sizeof(To))); } #endif namespace detail { template typename std::enable_if::value, void*>::type any_to_voidp(Addr addr) { return reinterpret_cast(addr); } template typename std::enable_if::value, void*>::type any_to_voidp(Addr addr) { return bit_cast(addr); } } #if _WIN32 /// Calls specified address as __stdcall function passing any amount of arguments. Return type is specified as first template argument. Available only on windows. /// \param address of function to call. /// \param ... any amount of arguments with any types. /// \returns value of type specified as first template argument, or none if no type is specified. template Result stdcall(Addr address, Args... arguments) { typedef Result(__stdcall*UniversalCall)(Args...); return UniversalCall(address)(arguments...); } # define HOOKER_CDECL __cdecl #else # define HOOKER_CDECL #endif /// Calls specified address as __cdecl function passing any amount of arguments. Return type is specified as first template argument. /// \param address of function to call. /// \param ... any amount of arguments with any types. /// \returns value of type specified as first template argument, or none if no type is specified. template Result ccall(Addr address, Args... arguments) { typedef Result(HOOKER_CDECL*UniversalCall)(Args...); return reinterpret_cast(address)(arguments...); } #undef HOOKER_CDECL /// Calls specified address as __thiscall function with provided address as 'this' pointer, passing any amount of arguments. Return type is specified as first template argument. /// \param address of function to call. /// \param ... any amount of arguments with any types. /// \returns value of type specified as first template argument, or none if no type is specified. template Result thiscall(Addr address, This thisPtr, Args... arguments) { detail::Helper* thisHelper = bit_cast(thisPtr); typedef Result(detail::Helper::*TUniversalCall)(Args...); TUniversalCall UniversalCall = bit_cast(address); return (thisHelper->*UniversalCall)(arguments...); } /// Return object of specified `Type` which is located at `base + offset`. template Type at_offset(Base base, unsigned offset) { return bit_cast(bit_cast(base) + offset); } /// Return object of specified `Type` which is located at `base + offset`. template Type from_offset(Base base, unsigned offset) { return *at_offset(base, offset); } /// Change protection of memory range. /// \param p memory address. /// \param size of memory at address p. /// \param protection a combination of HOOKER_MEM_* flags. /// \param original_protection on supported platforms will be set to current memory protection mode. May be null. If not null - always initialize to a best-guess current protection flags value, because on some platforms (like linux) this variable will not be set. template bool mem_protect(Addr p, size_t size, size_t protection, size_t* original_protection=nullptr) { return detail::hooker_mem_protect(detail::any_to_voidp(p), size, protection, original_protection) == HOOKER_SUCCESS; } /// Get mnemonic size of current platform. template size_t get_mnemonic_size(Addr address, size_t min_size) { return detail::hooker_get_mnemonic_size(detail::any_to_voidp(address), min_size); } /// Hotpatch a call. template OriginalProc hotpatch(Addr location, ProcAddr new_proc) { return bit_cast(detail::hooker_hotpatch(detail::any_to_voidp(location), detail::any_to_voidp(new_proc))); } /// Unhotpatch a call. template bool unhotpatch(Addr location) { return detail::hooker_unhotpatch(detail::any_to_voidp(location)) == HOOKER_SUCCESS; } /// Writes a jump or call hook from `address` to `new_proc`. /// \param address a pointer where hook should be written /// \param new_proc a pointer where hook should point to. /// \param flags any of HOOKER_HOOK_* flags. They may not be combined. /// \param nops of bytes to nop after hook instruction. Specify -1 to autocalculate. /// \returns null on failure or non-null on success. template bool write_instruction(Addr address, ProcAddr new_proc, size_t flags, size_t nops=-1) { return detail::hooker_write_instruction(detail::any_to_voidp(address), detail::any_to_voidp(new_proc), flags, nops) == HOOKER_SUCCESS; } /// Writes a 5 byte jump with E9 opcode. Difference between pointers is limited to 32bit. /// \param address a pointer where hook should be written /// \param new_proc a pointer where hook should point to. /// \returns null on failure or non-null on success. template bool write_jmp(Addr address, ProcAddr new_proc) { return detail::hooker_write_jmp(detail::any_to_voidp(address), detail::any_to_voidp(new_proc)) == HOOKER_SUCCESS; } /// Writes a 5 byte call with E8 opcode. Difference between pointers is limited to 32bit. /// \param address a pointer where hook should be written /// \param new_proc a pointer where hook should point to. /// \returns null on failure or non-null on success. template bool write_call(Addr address, ProcAddr new_proc) { return detail::hooker_write_call(detail::any_to_voidp(address), detail::any_to_voidp(new_proc)) == HOOKER_SUCCESS; } /// Redirect call to custom proc. /// \param address a start of original call. Warning: It should not contain any relatively-addressed instructions like calls or jumps. /// \param new_proc a proc that will be called instead of original one. /// \returns pointer, calling which will invoke original proc. It is user's responsibility to call original code when necessary. template OriginalProc hook(Addr address, ProcAddr new_proc, size_t flags=0) { return bit_cast(detail::hooker_hook(detail::any_to_voidp(address), detail::any_to_voidp(new_proc), flags)); } /// Unhook a hook created by hooker::hook(.., .., HOOKER_HOOK_REDIRECT, ..). /// \param address where hook was written to. /// \param original result of hooker::redirect() call. template void unhook(Addr address, OriginalProc original) { detail::hooker_unhook(detail::any_to_voidp(address), detail::any_to_voidp(original)); } /// Return address in object's vmt which is pointing to specified method. /// \param object is a pointer to a c++ object. /// \param method is a pointer to a c++ object method. template Proc& get_vmt_address(Addr object, ProcAddr method) { return *bit_cast(detail::hooker_get_vmt_address(reinterpret_cast(object), detail::any_to_voidp(method))); } /// Find a first occurrence of memory pattern. /// \param start a pointer to beginning of memory range. /// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards. /// \param pattern a array of bytes to search for. /// \param pattern_len a length of pattern array. /// \param a wildcard byte in the pattern array. template Type find_pattern(Addr start, int size, const Pattern* pattern, size_t pattern_len, uint8_t wildcard) { return bit_cast(detail::hooker_find_pattern(detail::any_to_voidp(start), size, reinterpret_cast(pattern), pattern_len, wildcard)); } #if __cplusplus >= 201402L /// Find a first occurrence of memory pattern. /// \param start a pointer to beginning of memory range. /// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards. /// \param pattern and wildcard mask. template Type find_pattern(Addr start, int size, const pattern& pattern) { return bit_cast(detail::hooker_find_pattern_ex(detail::any_to_voidp(start), size, pattern.pattern, N, pattern.wildcard)); } #endif /// Find a first occurrence of string pattern. /// \param start a pointer to beginning of memory range. /// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards. /// \param pattern a string to search. /// \param wildcard a wildcard character. template Type find_pattern(Addr start, int size, const char* pattern, char wildcard='?') { return bit_cast(detail::hooker_find_pattern(detail::any_to_voidp(start), size, reinterpret_cast(pattern), strlen(pattern), static_cast(wildcard))); } /// Fill memory with nops (0x90 opcode). /// \param start of the memory address. /// \param size of the memory that will be filled. Nops a single instruction when not specified. /// \returns true on success or false on failure. template bool nop(Addr start, size_t size=-1) { return detail::hooker_nop(detail::any_to_voidp(start), size) == HOOKER_SUCCESS; } /// Write a value to specified memory address. /// \param start of the memory address. /// \param value to be written. /// \returns true on success or false on failure. template bool write(Addr address, const Type value) { return detail::hooker_write(detail::any_to_voidp(address), (void*)&value, sizeof(value)) == HOOKER_SUCCESS; } /// Write an array to specified memory address. /// \param start of the memory address. /// \param value to be written. /// \param count of elements in the array. template bool write(Addr address, const Type* value, size_t count) { return detail::hooker_write(detail::any_to_voidp(address), (void*)value, sizeof(Type) * count) == HOOKER_SUCCESS; } /// Write bytes to specified memory address. /// \param start of the memory address. /// \param data to be written. /// \param size of data. /// \returns true on success or false on failure. template bool write(Addr address, const void* data, size_t size) { return detail::hooker_write(detail::any_to_voidp(address), data, size) == HOOKER_SUCCESS; } /// Searches for symbol in specified library. On Windows LoadLibrary() will be called if its not loaded yet, otherwise GetModuleHandle() will be used. /// On linux dlopen(RTLD_NODELETE) and dlclose() will always be called. /// \param lib_name string with dynamic library name. /// \param sym_name string with exported symbol name. /// \returns pointer to resolved dynamic symbol. template Proc dlsym(const char* lib_name, const char* sym_name) { return bit_cast(detail::hooker_dlsym(lib_name, sym_name)); } #if _WIN32 /// Replaces entry in import address table of specified module. /// \param mod_name string with name of module whose import table is to be modified. /// \param imp_mod_name string with name of module which module specified in `mod_name` imports. /// \param imp_proc_name string with name of symbol imported from module specified in `imp_mod_name`. /// \param new_proc a pointer that should replace old entry in import address table. /// \returns original value that was in import address table or null pointer on failure. template OrigProc hook_iat(const char* mod_name, const char* imp_mod_name, const char* imp_proc_name, NewProc new_proc) { return bit_cast(detail::hooker_hook_iat(mod_name, imp_mod_name, imp_proc_name, detail::any_to_voidp(new_proc))); } /// Replaces entry in import address table of specified module. /// \param mod_name string with name of module whose import table is to be modified. /// \param imp_mod_name string with name of module which module specified in `mod_name` imports. /// \param imp_proc_name string with name of symbol imported from module specified in `imp_mod_name`. /// \param new_proc a pointer that should replace old entry in import address table. /// \param old_proc a pointer which will receive Can be null. /// \returns true on success or false on failure. template bool hook_iat(const char* mod_name, const char* imp_mod_name, const char* imp_proc_name, NewProc new_proc, OrigProc* old_proc) { auto result = hook_iat(mod_name, imp_mod_name, imp_proc_name, detail::any_to_voidp(new_proc)); if (old_proc) *old_proc = result; return result != HOOKER_ERROR; } #endif }; #if HOOKER_USE_SHORT_NAMESPACE # ifndef HOOKER_SHORT_NAMESPACE # define HOOKER_SHORT_NAMESPACE hk # endif namespace HOOKER_SHORT_NAMESPACE = hooker; #endif