Skip to content

Commit

Permalink
Feat/vmt hook (#34)
Browse files Browse the repository at this point in the history
* Refactor: Replace uintptr_t addresses with uint8_t*
This fixes instances where unsigned integer overflow could occur when calculating new displacements and uint8_t*'s are generally just easier to work with.

* Fix: Improve memory store safety by avoiding unaligned stores

* VmtHook: Initial unittest concept

* VmtHook: Start stubbing out the implementation

* VmtHook: Initial MVP

* VmtHook: Use a concept for function pointers
NOTE: 32-bit MSVC doesn't like static __thiscall function pointers

* VmtHook: Maintain RTTI information when hooked

* VmtHook: Have hooks inherit from their targets

* VmtHook: Use a shared Allocation to improve safety

* VmtHook: Add apply & remove to hook/unhook other object instances

* VmtHook: Improve virtual method counting

* Utility: Add is_executable utility to replace IsBadCodePtr

* VmtHook: Add easy API for hooking methods

* VmtHook: Add documentation
  • Loading branch information
cursey authored Oct 6, 2023
1 parent 39dff2c commit 8f482ef
Show file tree
Hide file tree
Showing 13 changed files with 748 additions and 33 deletions.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ set(safetyhook_SOURCES
"src/inline_hook.cpp"
"src/mid_hook.cpp"
"src/thread_freezer.cpp"
"src/utility.cpp"
"src/vmt_hook.cpp"
cmake.toml
)

Expand Down Expand Up @@ -489,6 +491,7 @@ if(SAFETYHOOK_BUILD_TESTS) # build-tests
"unittest/inline_hook.cpp"
"unittest/inline_hook.x86_64.cpp"
"unittest/mid_hook.cpp"
"unittest/vmt_hook.cpp"
cmake.toml
)

Expand Down Expand Up @@ -557,6 +560,7 @@ if(SAFETYHOOK_BUILD_TESTS) # build-tests
"unittest/inline_hook.cpp"
"unittest/inline_hook.x86_64.cpp"
"unittest/mid_hook.cpp"
"unittest/vmt_hook.cpp"
"amalgamated-dist/safetyhook.cpp"
cmake.toml
)
Expand Down
3 changes: 3 additions & 0 deletions include/safetyhook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
#include <safetyhook/inline_hook.hpp>
#include <safetyhook/mid_hook.hpp>
#include <safetyhook/thread_freezer.hpp>
#include <safetyhook/vmt_hook.hpp>

using SafetyHookContext = safetyhook::Context;
using SafetyHookInline = safetyhook::InlineHook;
using SafetyHookMid = safetyhook::MidHook;
using SafetyInlineHook [[deprecated("Use SafetyHookInline instead.")]] = safetyhook::InlineHook;
using SafetyMidHook [[deprecated("Use SafetyHookMid instead.")]] = safetyhook::MidHook;
using SafetyHookVmt = safetyhook::VmtHook;
using SafetyHookVm = safetyhook::VmHook;
2 changes: 1 addition & 1 deletion include/safetyhook/allocator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,4 @@ class Allocator final : public std::enable_shared_from_this<Allocator> {
[[nodiscard]] static bool in_range(
uint8_t* address, const std::vector<uint8_t*>& desired_addresses, size_t max_distance);
};
} // namespace safetyhook
} // namespace safetyhook
31 changes: 23 additions & 8 deletions include/safetyhook/easy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <safetyhook/inline_hook.hpp>
#include <safetyhook/mid_hook.hpp>
#include <safetyhook/utility.hpp>
#include <safetyhook/vmt_hook.hpp>

namespace safetyhook {
/// @brief Easy to use API for creating an InlineHook.
Expand All @@ -14,13 +16,10 @@ namespace safetyhook {
[[nodiscard]] InlineHook create_inline(void* target, void* destination);

/// @brief Easy to use API for creating an InlineHook.
/// @tparam T The type of the function to hook.
/// @param target The address of the function to hook.
/// @param destination The address of the destination function.
/// @return The InlineHook object.
template <typename T>
requires std::is_function_v<T>
[[nodiscard]] InlineHook create_inline(T* target, T* destination) {
[[nodiscard]] InlineHook create_inline(FnPtr auto target, FnPtr auto destination) {
return create_inline(reinterpret_cast<void*>(target), reinterpret_cast<void*>(destination));
}

Expand All @@ -31,13 +30,29 @@ template <typename T>
[[nodiscard]] MidHook create_mid(void* target, MidHookFn destination);

/// @brief Easy to use API for creating a MidHook.
/// @tparam T The type of the function to hook.
/// @param target the address of the function to hook.
/// @param destination The destination function.
/// @return The MidHook object.
template <typename T>
requires std::is_function_v<T>
[[nodiscard]] MidHook create_mid(T* target, MidHookFn destination) {
[[nodiscard]] MidHook create_mid(FnPtr auto target, MidHookFn destination) {
return create_mid(reinterpret_cast<void*>(target), destination);
}

/// @brief Easy to use API for creating a VmtHook.
/// @param object The object to hook.
/// @return The VmtHook object.
[[nodiscard]] VmtHook create_vmt(void* object);

/// @brief Easy to use API for creating a VmHook.
/// @param vmt The VmtHook to use to create the VmHook.
/// @param index The index of the method to hook.
/// @param destination The destination function.
/// @return The VmHook object.
[[nodiscard]] VmHook create_vm(VmtHook& vmt, size_t index, FnPtr auto destination) {
if (auto hook = vmt.hook_method(index, destination)) {
return std::move(*hook);
} else {
return {};
}
}

} // namespace safetyhook
23 changes: 9 additions & 14 deletions include/safetyhook/inline_hook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <vector>

#include <safetyhook/allocator.hpp>
#include <safetyhook/utility.hpp>

namespace safetyhook {
/// @brief An inline hook.
Expand Down Expand Up @@ -78,15 +79,12 @@ class InlineHook final {
[[nodiscard]] static std::expected<InlineHook, Error> create(void* target, void* destination);

/// @brief Create an inline hook.
/// @tparam T The type of the function to hook.
/// @param target The address of the function to hook.
/// @param destination The destination address.
/// @return The InlineHook or an InlineHook::Error if an error occurred.
/// @note This will use the default global Allocator.
/// @note If you don't care about error handling, use the easy API (safetyhook::create_inline).
template <typename T>
requires std::is_function_v<T>
[[nodiscard]] static std::expected<InlineHook, Error> create(T* target, T* destination) {
[[nodiscard]] static std::expected<InlineHook, Error> create(FnPtr auto target, FnPtr auto destination) {
return create(reinterpret_cast<void*>(target), reinterpret_cast<void*>(destination));
}

Expand All @@ -100,16 +98,13 @@ class InlineHook final {
const std::shared_ptr<Allocator>& allocator, void* target, void* destination);

/// @brief Create an inline hook with a given Allocator.
/// @tparam T The type of the function to hook.
/// @param allocator The allocator to use.
/// @param target The address of the function to hook.
/// @param destination The destination address.
/// @return The InlineHook or an InlineHook::Error if an error occurred.
/// @note If you don't care about error handling, use the easy API (safetyhook::create_inline).
template <typename T>
requires std::is_function_v<T>
[[nodiscard]] static std::expected<InlineHook, Error> create(
const std::shared_ptr<Allocator>& allocator, T* target, T* destination) {
const std::shared_ptr<Allocator>& allocator, FnPtr auto target, FnPtr auto destination) {
return create(allocator, reinterpret_cast<void*>(target), reinterpret_cast<void*>(destination));
}

Expand Down Expand Up @@ -216,7 +211,7 @@ class InlineHook final {
/// @return The result of calling the original function.
/// @note This function will use the default calling convention set by your compiler.
/// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook
// safety or are worried about the performance cost of locking the mutex.
/// safety or are worried about the performance cost of locking the mutex.
template <typename RetT = void, typename... Args> RetT unsafe_call(Args... args) {
return original<RetT (*)(Args...)>()(args...);
}
Expand All @@ -228,7 +223,7 @@ class InlineHook final {
/// @return The result of calling the original function.
/// @note This function will use the __cdecl calling convention.
/// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook
// safety or are worried about the performance cost of locking the mutex.
/// safety or are worried about the performance cost of locking the mutex.
template <typename RetT = void, typename... Args> RetT unsafe_ccall(Args... args) {
return original<RetT(__cdecl*)(Args...)>()(args...);
}
Expand All @@ -240,7 +235,7 @@ class InlineHook final {
/// @return The result of calling the original function.
/// @note This function will use the __thiscall calling convention.
/// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook
// safety or are worried about the performance cost of locking the mutex.
/// safety or are worried about the performance cost of locking the mutex.
template <typename RetT = void, typename... Args> RetT unsafe_thiscall(Args... args) {
return original<RetT(__thiscall*)(Args...)>()(args...);
}
Expand All @@ -252,7 +247,7 @@ class InlineHook final {
/// @return The result of calling the original function.
/// @note This function will use the __stdcall calling convention.
/// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook
// safety or are worried about the performance cost of locking the mutex.
/// safety or are worried about the performance cost of locking the mutex.
template <typename RetT = void, typename... Args> RetT unsafe_stdcall(Args... args) {
return original<RetT(__stdcall*)(Args...)>()(args...);
}
Expand All @@ -264,7 +259,7 @@ class InlineHook final {
/// @return The result of calling the original function.
/// @note This function will use the __fastcall calling convention.
/// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook
// safety or are worried about the performance cost of locking the mutex.
/// safety or are worried about the performance cost of locking the mutex.
template <typename RetT = void, typename... Args> RetT unsafe_fastcall(Args... args) {
return original<RetT(__fastcall*)(Args...)>()(args...);
}
Expand All @@ -287,4 +282,4 @@ class InlineHook final {

void destroy();
};
} // namespace safetyhook
} // namespace safetyhook
12 changes: 4 additions & 8 deletions include/safetyhook/mid_hook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <safetyhook/allocator.hpp>
#include <safetyhook/context.hpp>
#include <safetyhook/inline_hook.hpp>
#include <safetyhook/utility.hpp>

namespace safetyhook {

Expand Down Expand Up @@ -56,15 +57,12 @@ class MidHook final {
[[nodiscard]] static std::expected<MidHook, Error> create(void* target, MidHookFn destination);

/// @brief Creates a new MidHook object.
/// @tparam T The type of the function to hook.
/// @param target The address of the function to hook.
/// @param destination The destination function.
/// @return The MidHook object or a MidHook::Error if an error occurred.
/// @note This will use the default global Allocator.
/// @note If you don't care about error handling, use the easy API (safetyhook::create_mid).
template <typename T>
requires std::is_function_v<T>
[[nodiscard]] static std::expected<MidHook, Error> create(T* target, MidHookFn destination) {
[[nodiscard]] static std::expected<MidHook, Error> create(FnPtr auto target, MidHookFn destination) {
return create(reinterpret_cast<void*>(target), destination);
}

Expand All @@ -84,10 +82,8 @@ class MidHook final {
/// @param destination The destination function.
/// @return The MidHook object or a MidHook::Error if an error occurred.
/// @note If you don't care about error handling, use the easy API (safetyhook::create_mid).
template <typename T>
requires std::is_function_v<T>
[[nodiscard]] static std::expected<MidHook, Error> create(
const std::shared_ptr<Allocator>& allocator, T* target, MidHookFn destination) {
const std::shared_ptr<Allocator>& allocator, FnPtr auto target, MidHookFn destination) {
return create(allocator, reinterpret_cast<void*>(target), destination);
}

Expand Down Expand Up @@ -128,4 +124,4 @@ class MidHook final {
std::expected<void, Error> setup(
const std::shared_ptr<Allocator>& allocator, uint8_t* target, MidHookFn destination);
};
} // namespace safetyhook
} // namespace safetyhook
1 change: 0 additions & 1 deletion include/safetyhook/thread_freezer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

#include <cstdint>
#include <functional>
#include <vector>

#include <Windows.h>

Expand Down
8 changes: 7 additions & 1 deletion include/safetyhook/utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

#include <algorithm>
#include <cstdint>
#include <type_traits>

namespace safetyhook {
template <typename T> constexpr void store(uint8_t* address, const T& value) {
std::copy_n(reinterpret_cast<const uint8_t*>(&value), sizeof(T), address);
}
} // namespace safetyhook

template <typename T>
concept FnPtr = requires(T f) { std::is_pointer_v<T>&& std::is_function_v<std::remove_pointer_t<T>>; };

bool is_executable(uint8_t* address);
} // namespace safetyhook
Loading

0 comments on commit 8f482ef

Please sign in to comment.