Skip to content

Commit

Permalink
Hooking: Replace minhook with safetyhook (thread trapping branch)
Browse files Browse the repository at this point in the history
It's faster, safer, better.
  • Loading branch information
praydog committed Mar 29, 2024
1 parent 6dd7b47 commit 4948ab9
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 54 deletions.
18 changes: 16 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
set(ASMJIT_STATIC ON CACHE BOOL "" FORCE)
set(DYNAMIC_LOADER ON CACHE BOOL "" FORCE) # OpenXR
set(BUILD_TOOLS OFF CACHE BOOL "" FORCE) # DirectXTK
set(SAFETYHOOK_FETCH_ZYDIS ON)

if ("${CMAKE_BUILD_TYPE}" MATCHES "Release")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MT")
Expand Down Expand Up @@ -113,6 +114,16 @@ FetchContent_Declare(
)
FetchContent_MakeAvailable(directxtk12)

message(STATUS "Fetching safetyhook (44200343bf803f78862426e301e9382e5b28ea2c)...")
FetchContent_Declare(
safetyhook
GIT_REPOSITORY
https://github.com/cursey/safetyhook
GIT_TAG
44200343bf803f78862426e301e9382e5b28ea2c
)
FetchContent_MakeAvailable(safetyhook)

message(STATUS "Fetching bddisasm (v1.34.10)...")
FetchContent_Declare(
bddisasm
Expand Down Expand Up @@ -492,9 +503,11 @@ set(utility_SOURCES "")
list(APPEND utility_SOURCES
"shared/utility/Exceptions.cpp"
"shared/utility/FunctionHook.cpp"
"shared/utility/FunctionHookMinHook.cpp"
"shared/utility/Relocate.cpp"
"shared/utility/Exceptions.hpp"
"shared/utility/FunctionHook.hpp"
"shared/utility/FunctionHookMinHook.hpp"
"shared/utility/Relocate.hpp"
)

Expand Down Expand Up @@ -522,6 +535,7 @@ target_compile_options(utility PUBLIC
target_link_libraries(utility PUBLIC
spdlog
minhook
safetyhook
kananlib
)

Expand Down Expand Up @@ -12806,7 +12820,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${example_plugin_SOURCES})

target_compile_features(example_plugin PUBLIC
cxx_std_20
cxx_std_23
)

target_include_directories(example_plugin PUBLIC
Expand Down Expand Up @@ -12861,7 +12875,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${weapon_stay_big_plugin_SOURCES})

target_compile_features(weapon_stay_big_plugin PUBLIC
cxx_std_20
cxx_std_23
)

target_include_directories(weapon_stay_big_plugin PUBLIC
Expand Down
7 changes: 6 additions & 1 deletion cmake.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
set(ASMJIT_STATIC ON CACHE BOOL "" FORCE)
set(DYNAMIC_LOADER ON CACHE BOOL "" FORCE) # OpenXR
set(BUILD_TOOLS OFF CACHE BOOL "" FORCE) # DirectXTK
set(SAFETYHOOK_FETCH_ZYDIS ON)
if ("${CMAKE_BUILD_TYPE}" MATCHES "Release")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MT")
Expand Down Expand Up @@ -111,6 +112,9 @@ include-directories = [
]
condition = "build-framework-dependencies"

[fetch-content.safetyhook]
git = "https://github.com/cursey/safetyhook"
tag = "44200343bf803f78862426e301e9382e5b28ea2c"

[target.imgui]
type = "static"
Expand Down Expand Up @@ -182,6 +186,7 @@ compile-features = ["cxx_std_23"]
link-libraries = [
"spdlog",
"minhook",
"safetyhook",
"kananlib"
]

Expand Down Expand Up @@ -342,7 +347,7 @@ type = "game"
[template.plugin]
type = "shared"
include-directories = ["include/"]
compile-features = ["cxx_std_20"]
compile-features = ["cxx_std_23"]
condition = "build-framework"

[template.plugin.properties]
Expand Down
90 changes: 57 additions & 33 deletions shared/utility/FunctionHook.cpp
Original file line number Diff line number Diff line change
@@ -1,76 +1,100 @@
#include <spdlog/spdlog.h>
#include <MinHook.h>

#include <safetyhook/inline_hook.hpp>

#include "FunctionHook.hpp"

using namespace std;


bool g_isMinHookInitialized{ false };

FunctionHook::FunctionHook(Address target, Address destination)
: m_target{ 0 },
m_destination{ 0 },
m_original{ 0 }
: m_target{ target },
m_destination{ destination }
{
spdlog::info("Attempting to hook {:p}->{:p}", target.ptr(), destination.ptr());

// Initialize MinHook if it hasn't been already.
if (!g_isMinHookInitialized && MH_Initialize() == MH_OK) {
g_isMinHookInitialized = true;
}

// Create the hook. Call create afterwards to prevent race conditions accessing FunctionHook before it leaves its constructor.
if (auto status = MH_CreateHook(target.as<LPVOID>(), destination.as<LPVOID>(), (LPVOID*)&m_original); status == MH_OK) {
/*if (auto status = MH_CreateHook(target.as<LPVOID>(), destination.as<LPVOID>(), (LPVOID*)&m_original); status == MH_OK) {
m_target = target;
m_destination = destination;
spdlog::info("Hook init successful {:p}->{:p}", target.ptr(), destination.ptr());
}
else {
spdlog::error("Failed to hook {:p}: {}", target.ptr(), MH_StatusToString(status));
}
}*/
}

FunctionHook::~FunctionHook() {
remove();
}

bool FunctionHook::create() {
if (m_target == 0 || m_destination == 0 || m_original == 0) {
if (m_target == 0 || m_destination == 0 ) {
spdlog::error("FunctionHook not initialized");
return false;
}

if (auto status = MH_EnableHook((LPVOID)m_target); status != MH_OK) {
/*if (auto status = MH_EnableHook((LPVOID)m_target); status != MH_OK) {
m_original = 0;
m_destination = 0;
m_target = 0;
spdlog::error("Failed to hook {:x}: {}", m_target, MH_StatusToString(status));
return false;
}*/

try {
auto expect = safetyhook::InlineHook::create(safetyhook::Allocator::global(), m_target, m_destination);

if (!expect) {
std::string error = "";
switch (expect.error().type) {
case safetyhook::InlineHook::Error::BAD_ALLOCATION:
error = "bad allocation";
break;
case safetyhook::InlineHook::Error::FAILED_TO_DECODE_INSTRUCTION:
error = "failed to decode instruction";
break;
case safetyhook::InlineHook::Error::SHORT_JUMP_IN_TRAMPOLINE:
error = "short jump in trampoline";
break;
case safetyhook::InlineHook::Error::IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE:
error = "IP relative instruction out of range";
break;
case safetyhook::InlineHook::Error::UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE:
error = "unsupported instruction in trampoline";
break;
case safetyhook::InlineHook::Error::FAILED_TO_UNPROTECT:
error = "failed to unprotect memory";
break;
case safetyhook::InlineHook::Error::NOT_ENOUGH_SPACE:
error = "not enough space";
break;
default:
error = std::format("unknown error {}", (int32_t)expect.error().type);
break;
};

spdlog::error("Failed to hook {:x}: {}", m_target, error);
return false;
}

m_inline_hook = std::move(*expect);
} catch (const std::exception& e) {
spdlog::error("Failed to hook {:x}: {}", m_target, e.what());
return false;
} catch (...) {
spdlog::error("Failed to hook {:x}: unknown exception", m_target);
return false;
}

spdlog::info("Hooked {:x}->{:x}", m_target, m_destination);
return true;
}

bool FunctionHook::remove() {
// Don't try to remove invalid hooks.
if (m_original == 0) {
return true;
}

// Disable then remove the hook.
if (MH_DisableHook((LPVOID)m_target) != MH_OK ||
MH_RemoveHook((LPVOID)m_target) != MH_OK) {
if (m_inline_hook) {
spdlog::info("Hooked {:x}->{:x}", m_target, m_destination);
} else {
spdlog::error("Failed to hook {:x}", m_target);
return false;
}

// Invalidate the members.
m_target = 0;
m_destination = 0;
m_original = 0;

return true;
}
15 changes: 7 additions & 8 deletions shared/utility/FunctionHook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <utility/Address.hpp>

#include <safetyhook.hpp>

class FunctionHook {
public:
FunctionHook() = delete;
Expand All @@ -15,28 +17,25 @@ class FunctionHook {

bool create();

// Called automatically by the destructor, but you can call it explicitly
// if you need to remove the hook.
bool remove();

auto get_original() const {
return m_original;
return m_inline_hook.trampoline().address();
}

template <typename T>
T* get_original() const {
return (T*)m_original;
return m_inline_hook.original<T*>();
}

auto is_valid() const {
return m_original != 0;
return m_inline_hook.operator bool();
}

FunctionHook& operator=(const FunctionHook& other) = delete;
FunctionHook& operator=(FunctionHook&& other) = delete;

private:
SafetyHookInline m_inline_hook;

uintptr_t m_target{ 0 };
uintptr_t m_destination{ 0 };
uintptr_t m_original{ 0 };
};
76 changes: 76 additions & 0 deletions shared/utility/FunctionHookMinHook.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include <spdlog/spdlog.h>
#include <MinHook.h>

#include "FunctionHookMinHook.hpp"

using namespace std;


bool g_isMinHookInitialized{ false };

FunctionHookMinHook::FunctionHookMinHook(Address target, Address destination)
: m_target{ 0 },
m_destination{ 0 },
m_original{ 0 }
{
spdlog::info("Attempting to hook {:p}->{:p}", target.ptr(), destination.ptr());

// Initialize MinHook if it hasn't been already.
if (!g_isMinHookInitialized && MH_Initialize() == MH_OK) {
g_isMinHookInitialized = true;
}

// Create the hook. Call create afterwards to prevent race conditions accessing FunctionHookMinHook before it leaves its constructor.
if (auto status = MH_CreateHook(target.as<LPVOID>(), destination.as<LPVOID>(), (LPVOID*)&m_original); status == MH_OK) {
m_target = target;
m_destination = destination;

spdlog::info("Hook init successful {:p}->{:p}", target.ptr(), destination.ptr());
}
else {
spdlog::error("Failed to hook {:p}: {}", target.ptr(), MH_StatusToString(status));
}
}

FunctionHookMinHook::~FunctionHookMinHook() {
remove();
}

bool FunctionHookMinHook::create() {
if (m_target == 0 || m_destination == 0 || m_original == 0) {
spdlog::error("FunctionHookMinHook not initialized");
return false;
}

if (auto status = MH_EnableHook((LPVOID)m_target); status != MH_OK) {
m_original = 0;
m_destination = 0;
m_target = 0;

spdlog::error("Failed to hook {:x}: {}", m_target, MH_StatusToString(status));
return false;
}

spdlog::info("Hooked {:x}->{:x}", m_target, m_destination);
return true;
}

bool FunctionHookMinHook::remove() {
// Don't try to remove invalid hooks.
if (m_original == 0) {
return true;
}

// Disable then remove the hook.
if (MH_DisableHook((LPVOID)m_target) != MH_OK ||
MH_RemoveHook((LPVOID)m_target) != MH_OK) {
return false;
}

// Invalidate the members.
m_target = 0;
m_destination = 0;
m_original = 0;

return true;
}
42 changes: 42 additions & 0 deletions shared/utility/FunctionHookMinHook.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

#include <windows.h>
#include <cstdint>

#include <utility/Address.hpp>

class FunctionHookMinHook {
public:
FunctionHookMinHook() = delete;
FunctionHookMinHook(const FunctionHookMinHook& other) = delete;
FunctionHookMinHook(FunctionHookMinHook&& other) = delete;
FunctionHookMinHook(Address target, Address destination);
virtual ~FunctionHookMinHook();

bool create();

// Called automatically by the destructor, but you can call it explicitly
// if you need to remove the hook.
bool remove();

auto get_original() const {
return m_original;
}

template <typename T>
T* get_original() const {
return (T*)m_original;
}

auto is_valid() const {
return m_original != 0;
}

FunctionHookMinHook& operator=(const FunctionHookMinHook& other) = delete;
FunctionHookMinHook& operator=(FunctionHookMinHook&& other) = delete;

private:
uintptr_t m_target{ 0 };
uintptr_t m_destination{ 0 };
uintptr_t m_original{ 0 };
};
Loading

0 comments on commit 4948ab9

Please sign in to comment.