Skip to content

Commit

Permalink
Feature/Linux support (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
cursey authored Feb 17, 2024
1 parent 3103d4b commit 91fbc79
Show file tree
Hide file tree
Showing 29 changed files with 885 additions and 364 deletions.
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

0 comments on commit 91fbc79

Please sign in to comment.