Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/Linux support #62

Merged
merged 12 commits into from
Feb 17, 2024
6 changes: 3 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ set(safetyhook_SOURCES
"src/easy.cpp"
"src/inline_hook.cpp"
"src/mid_hook.cpp"
"src/thread_freezer.cpp"
"src/os.linux.cpp"
"src/os.windows.cpp"
"src/utility.cpp"
"src/vmt_hook.cpp"
cmake.toml
Expand Down Expand Up @@ -143,7 +144,6 @@ target_include_directories(safetyhook PUBLIC

target_link_libraries(safetyhook PUBLIC
Zydis
ntdll
)

# Target: docs
Expand Down Expand Up @@ -305,7 +305,7 @@ if(SAFETYHOOK_BUILD_EXAMPLES) # build-examples

endif()
# Target: example-dll
if(SAFETYHOOK_BUILD_EXAMPLES) # build-examples
if(SAFETYHOOK_BUILD_EXAMPLES AND WIN32) # windows-only-example
set(example-dll_SOURCES
"example/dll.cpp"
cmake.toml
Expand Down
5 changes: 3 additions & 2 deletions cmake.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ SAFETYHOOK_FETCH_ZYDIS = false
build-docs = "SAFETYHOOK_BUILD_DOCS"
build-test = "SAFETYHOOK_BUILD_TEST"
build-examples = "SAFETYHOOK_BUILD_EXAMPLES"
windows-only-example = "SAFETYHOOK_BUILD_EXAMPLES AND WIN32"
amalgamate = "SAFETYHOOK_AMALGAMATE"
build-amalgamate-test = "SAFETYHOOK_BUILD_TEST AND SAFETYHOOK_AMALGAMATE"
fetch-zydis = "SAFETYHOOK_FETCH_ZYDIS"
Expand Down Expand Up @@ -54,7 +55,7 @@ sources = ["src/*.cpp"]
include-directories = ["include/"]
compile-features = ["cxx_std_23"]
alias = "safetyhook::safetyhook"
link-libraries = ["Zydis", "ntdll"]
link-libraries = ["Zydis"]
msvc.private-compile-options = ["/permissive-", "/W4", "/w14640"]
clang.private-compile-options = ["-Wall", "-Wextra", "-Wshadow", "-Wnon-virtual-dtor", "-pedantic"]
gcc.private-compile-options = ["-Wall", "-Wextra", "-Wshadow", "-Wnon-virtual-dtor", "-pedantic"]
Expand Down Expand Up @@ -104,7 +105,7 @@ link-libraries = ["safetyhook::safetyhook"]
compile-features = ["cxx_std_23"]

[template.example-dll]
condition = "build-examples"
condition = "windows-only-example"
type = "shared"
link-libraries = ["safetyhook::safetyhook"]
compile-features = ["cxx_std_23"]
Expand Down
4 changes: 3 additions & 1 deletion example/midhook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

#include <safetyhook.hpp>

__declspec(noinline) int add_42(int a) {
SAFETYHOOK_NOINLINE int add_42(int a) {
return a + 42;
}

Expand All @@ -28,6 +28,8 @@ int main() {
std::println("unhooked add_42(2) = {}", add_42(2));

// Let's disassemble add_42 and hook its RET.
// NOTE: On Linux we should specify -falign-functions=32 to add some padding to the function otherwise we'll
// end up hooking the next function's prologue. This is pretty hacky in general but it's just an example.
ZydisDecoder decoder{};

#if SAFETYHOOK_ARCH_X86_64
Expand Down
2 changes: 1 addition & 1 deletion example/minimal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include <safetyhook.hpp>

__declspec(noinline) int add(int x, int y) {
SAFETYHOOK_NOINLINE int add(int x, int y) {
return x + y;
}

Expand Down
2 changes: 1 addition & 1 deletion example/multiple.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

SafetyHookInline hook0, hook1, hook2, hook3;

__declspec(noinline) void say_hi(const std::string& name) {
SAFETYHOOK_NOINLINE void say_hi(const std::string& name) {
std::println("hello {}", name);
}

Expand Down
2 changes: 1 addition & 1 deletion example/threadsafe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

SafetyHookInline g_hook{};

__declspec(noinline) void SayHello(int times) {
SAFETYHOOK_NOINLINE void SayHello(int times) {
std::println("Hello #{}", times);
}

Expand Down
9 changes: 7 additions & 2 deletions example/vmthook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,18 @@ int main() {
std::println("unhooked target->add_42(1) = {}", target->add_42(1));

g_target_hook = safetyhook::create_vmt(target.get());

#if SAFETYHOOK_OS_WINDOWS
g_add_42_hook = safetyhook::create_vm(g_target_hook, 1, &Hook::hooked_add_42);
#elif SAFETYHOOK_OS_LINUX
g_add_42_hook = safetyhook::create_vm(g_target_hook, 2, &Hook::hooked_add_42);
#endif

std::println("hooked target->add_42(2) = {}", target->add_42(1));
std::println("hooked target->add_42(2) = {}", target->add_42(2));

g_target_hook = {};

std::println("unhooked target->add_42(3) = {}", target->add_42(1));
std::println("unhooked target->add_42(3) = {}", target->add_42(3));

return 0;
}
1 change: 0 additions & 1 deletion include/safetyhook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#include "safetyhook/easy.hpp"
#include "safetyhook/inline_hook.hpp"
#include "safetyhook/mid_hook.hpp"
#include "safetyhook/thread_freezer.hpp"
#include "safetyhook/vmt_hook.hpp"

using SafetyHookContext = safetyhook::Context;
Expand Down
35 changes: 35 additions & 0 deletions include/safetyhook/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,38 @@
#error "Unsupported architecture"
#endif
#endif

#if defined(WIN32)
#define SAFETYHOOK_OS_WINDOWS 1
#define SAFETYHOOK_OS_LINUX 0
#elif defined(__linux__)
#define SAFETYHOOK_OS_WINDOWS 0
#define SAFETYHOOK_OS_LINUX 1
#else
#error "Unsupported OS"
#endif

#if SAFETYHOOK_OS_WINDOWS
#if SAFETYHOOK_COMPILER_MSVC
#define SAFETYHOOK_CCALL __cdecl
#define SAFETYHOOK_STDCALL __stdcall
#define SAFETYHOOK_FASTCALL __fastcall
#define SAFETYHOOK_THISCALL __thiscall
#elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG
#define SAFETYHOOK_CCALL __attribute__((cdecl))
#define SAFETYHOOK_STDCALL __attribute__((stdcall))
#define SAFETYHOOK_FASTCALL __attribute__((fastcall))
#define SAFETYHOOK_THISCALL __attribute__((thiscall))
#endif
#else
#define SAFETYHOOK_CCALL
#define SAFETYHOOK_STDCALL
#define SAFETYHOOK_FASTCALL
#define SAFETYHOOK_THISCALL
#endif

#if SAFETYHOOK_COMPILER_MSVC
#define SAFETYHOOK_NOINLINE __declspec(noinline)
#elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG
#define SAFETYHOOK_NOINLINE __attribute__((noinline))
#endif
16 changes: 8 additions & 8 deletions include/safetyhook/inline_hook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ class InlineHook final {
/// @note This function will use the __cdecl calling convention.
template <typename RetT = void, typename... Args> RetT ccall(Args... args) {
std::scoped_lock lock{m_mutex};
return m_trampoline ? original<RetT(__cdecl*)(Args...)>()(args...) : RetT();
return m_trampoline ? original<RetT(SAFETYHOOK_CCALL*)(Args...)>()(args...) : RetT();
}

/// @brief Calls the original function.
Expand All @@ -196,7 +196,7 @@ class InlineHook final {
/// @note This function will use the __thiscall calling convention.
template <typename RetT = void, typename... Args> RetT thiscall(Args... args) {
std::scoped_lock lock{m_mutex};
return m_trampoline ? original<RetT(__thiscall*)(Args...)>()(args...) : RetT();
return m_trampoline ? original<RetT(SAFETYHOOK_THISCALL*)(Args...)>()(args...) : RetT();
}

/// @brief Calls the original function.
Expand All @@ -207,7 +207,7 @@ class InlineHook final {
/// @note This function will use the __stdcall calling convention.
template <typename RetT = void, typename... Args> RetT stdcall(Args... args) {
std::scoped_lock lock{m_mutex};
return m_trampoline ? original<RetT(__stdcall*)(Args...)>()(args...) : RetT();
return m_trampoline ? original<RetT(SAFETYHOOK_STDCALL*)(Args...)>()(args...) : RetT();
}

/// @brief Calls the original function.
Expand All @@ -218,7 +218,7 @@ class InlineHook final {
/// @note This function will use the __fastcall calling convention.
template <typename RetT = void, typename... Args> RetT fastcall(Args... args) {
std::scoped_lock lock{m_mutex};
return m_trampoline ? original<RetT(__fastcall*)(Args...)>()(args...) : RetT();
return m_trampoline ? original<RetT(SAFETYHOOK_FASTCALL*)(Args...)>()(args...) : RetT();
}

/// @brief Calls the original function.
Expand All @@ -242,7 +242,7 @@ class InlineHook final {
/// @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.
template <typename RetT = void, typename... Args> RetT unsafe_ccall(Args... args) {
return original<RetT(__cdecl*)(Args...)>()(args...);
return original<RetT(SAFETYHOOK_CCALL*)(Args...)>()(args...);
}

/// @brief Calls the original function.
Expand All @@ -254,7 +254,7 @@ class InlineHook final {
/// @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.
template <typename RetT = void, typename... Args> RetT unsafe_thiscall(Args... args) {
return original<RetT(__thiscall*)(Args...)>()(args...);
return original<RetT(SAFETYHOOK_THISCALL*)(Args...)>()(args...);
}

/// @brief Calls the original function.
Expand All @@ -266,7 +266,7 @@ class InlineHook final {
/// @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.
template <typename RetT = void, typename... Args> RetT unsafe_stdcall(Args... args) {
return original<RetT(__stdcall*)(Args...)>()(args...);
return original<RetT(SAFETYHOOK_STDCALL*)(Args...)>()(args...);
}

/// @brief Calls the original function.
Expand All @@ -278,7 +278,7 @@ class InlineHook final {
/// @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.
template <typename RetT = void, typename... Args> RetT unsafe_fastcall(Args... args) {
return original<RetT(__fastcall*)(Args...)>()(args...);
return original<RetT(SAFETYHOOK_FASTCALL*)(Args...)>()(args...);
}

private:
Expand Down
82 changes: 82 additions & 0 deletions include/safetyhook/os.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// This is the OS abstraction layer.
#pragma once

#include <cstdint>
#include <expected>
#include <functional>

namespace safetyhook {

enum class OsError {
FAILED_TO_ALLOCATE,
FAILED_TO_PROTECT,
FAILED_TO_QUERY,
FAILED_TO_GET_NEXT_THREAD,
FAILED_TO_GET_THREAD_CONTEXT,
FAILED_TO_SET_THREAD_CONTEXT,
FAILED_TO_FREEZE_THREAD,
FAILED_TO_UNFREEZE_THREAD,
FAILED_TO_GET_THREAD_ID,
};

struct VmAccess {
bool read : 1;
bool write : 1;
bool execute : 1;

constexpr bool operator==(const VmAccess& other) const {
return read == other.read && write == other.write && execute == other.execute;
}
};

constexpr VmAccess VM_ACCESS_R{.read = true, .write = false, .execute = false};
constexpr VmAccess VM_ACCESS_RW{.read = true, .write = true, .execute = false};
constexpr VmAccess VM_ACCESS_RX{.read = true, .write = false, .execute = true};
constexpr VmAccess VM_ACCESS_RWX{.read = true, .write = true, .execute = true};

struct VmBasicInfo {
uint8_t* address;
size_t size;
VmAccess access;
bool is_free;
};

std::expected<uint8_t*, OsError> vm_allocate(uint8_t* address, size_t size, VmAccess access);
void vm_free(uint8_t* address);
std::expected<uint32_t, OsError> vm_protect(uint8_t* address, size_t size, VmAccess access);
std::expected<uint32_t, OsError> vm_protect(uint8_t* address, size_t size, uint32_t access);
std::expected<VmBasicInfo, OsError> vm_query(uint8_t* address);
bool vm_is_readable(uint8_t* address, size_t size);
bool vm_is_writable(uint8_t* address, size_t size);
bool vm_is_executable(uint8_t* address);

struct SystemInfo {
uint32_t page_size;
uint32_t allocation_granularity;
uint8_t* min_address;
uint8_t* max_address;
};

SystemInfo system_info();

using ThreadId = uint32_t;
using ThreadHandle = void*;
using ThreadContext = void*;

/// @brief Executes a function while all other threads are frozen. Also allows for visiting each frozen thread and
/// modifying it's context.
/// @param run_fn The function to run while all other threads are frozen.
/// @param visit_fn The function that will be called for each frozen thread.
/// @note The visit function will be called in the order that the threads were frozen.
/// @note The visit function will be called before the run function.
/// @note Keep the logic inside run_fn and visit_fn as simple as possible to avoid deadlocks.
void execute_while_frozen(const std::function<void()>& run_fn,
const std::function<void(ThreadId, ThreadHandle, ThreadContext)>& visit_fn = {});

/// @brief Will modify the context of a thread's IP to point to a new address if its IP is at the old address.
/// @param ctx The thread context to modify.
/// @param old_ip The old IP address.
/// @param new_ip The new IP address.
void fix_ip(ThreadContext ctx, uint8_t* old_ip, uint8_t* new_ip);

} // namespace safetyhook
29 changes: 0 additions & 29 deletions include/safetyhook/thread_freezer.hpp

This file was deleted.

13 changes: 13 additions & 0 deletions include/safetyhook/utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

namespace safetyhook {
Expand Down Expand Up @@ -35,4 +36,16 @@ class UnprotectMemory {
};

[[nodiscard]] std::optional<UnprotectMemory> unprotect(uint8_t* address, size_t size);

template <typename T> constexpr T align_up(T address, size_t align) {
const auto unaligned_address = (uintptr_t)address;
const auto aligned_address = (unaligned_address + align - 1) & ~(align - 1);
return (T)aligned_address;
}

template <typename T> constexpr T align_down(T address, size_t align) {
const auto unaligned_address = (uintptr_t)address;
const auto aligned_address = unaligned_address & ~(align - 1);
return (T)aligned_address;
}
} // namespace safetyhook
Loading
Loading