Skip to content

Commit

Permalink
Refactor: Replace uintptr_t addresses with uint8_t* (#33)
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

* Utility: Simplify store

* Refactor: Add *address() methods that return uintptr_t

* Refactor: Add template methods for hook creation
  • Loading branch information
cursey authored Apr 30, 2023
1 parent 4d3d080 commit e0ea316
Show file tree
Hide file tree
Showing 16 changed files with 249 additions and 221 deletions.
2 changes: 1 addition & 1 deletion format.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Get-ChildItem -Path .\src, .\include, .\tests -Include *.hpp, *.cpp -Recurse |
Get-ChildItem -Path .\src, .\include, .\tests, .\unittest -Include *.hpp, *.cpp -Recurse |
ForEach-Object {
Write-Output $_.FullName
&clang-format -i -style=file $_.FullName
Expand Down
34 changes: 19 additions & 15 deletions include/safetyhook/allocator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,30 @@ class Allocation final {
/// @note This is called automatically when the Allocation object is destroyed.
void free();

/// @brief Returns a pointer to the data of the allocation.
/// @return Pointer to the data of the allocation.
[[nodiscard]] uint8_t* data() const noexcept { return m_address; }

/// @brief Returns the address of the allocation.
/// @return The address of the allocation.
[[nodiscard]] uintptr_t address() const noexcept { return m_address; }
[[nodiscard]] uintptr_t address() const noexcept { return (uintptr_t)m_address; }

/// @brief Returns the size of the allocation.
/// @return The size of the allocation.
[[nodiscard]] size_t size() const noexcept { return m_size; }

/// @brief Tests if the allocation is valid.
/// @return True if the allocation is valid, false otherwise.
explicit operator bool() const noexcept { return m_address != 0 && m_size != 0; }
explicit operator bool() const noexcept { return m_address != nullptr && m_size != 0; }

protected:
friend Allocator;

Allocation(std::shared_ptr<Allocator> allocator, uintptr_t address, size_t size) noexcept;
Allocation(std::shared_ptr<Allocator> allocator, uint8_t* address, size_t size) noexcept;

private:
std::shared_ptr<Allocator> m_allocator{};
uintptr_t m_address{};
uint8_t* m_address{};
size_t m_size{};
};

Expand Down Expand Up @@ -78,27 +82,27 @@ class Allocator final : public std::enable_shared_from_this<Allocator> {
[[nodiscard]] std::expected<Allocation, Error> allocate(size_t size);

/// @brief Allocates memory near a target address.
/// @param desired_address The target address.
/// @param desired_addresses The target address.
/// @param size The size of the allocation.
/// @param max_distance The maximum distance from the target address.
/// @return The Allocation or an Allocator::Error if the allocation failed.
[[nodiscard]] std::expected<Allocation, Error> allocate_near(
const std::vector<uintptr_t>& desired_addresses, size_t size, size_t max_distance = 0x7FFF'FFFF);
const std::vector<uint8_t*>& desired_addresses, size_t size, size_t max_distance = 0x7FFF'FFFF);

protected:
friend Allocation;

void free(uintptr_t address, size_t size);
void free(uint8_t* address, size_t size);

private:
struct FreeNode {
std::unique_ptr<FreeNode> next{};
uintptr_t start{};
uintptr_t end{};
uint8_t* start{};
uint8_t* end{};
};

struct Memory {
uintptr_t address{};
uint8_t* address{};
size_t size{};
std::unique_ptr<FreeNode> freelist{};

Expand All @@ -111,13 +115,13 @@ class Allocator final : public std::enable_shared_from_this<Allocator> {
Allocator() = default;

[[nodiscard]] std::expected<Allocation, Error> internal_allocate_near(
const std::vector<uintptr_t>& desired_addresses, size_t size, size_t max_distance = 0x7FFF'FFFF);
void internal_free(uintptr_t address, size_t size);
const std::vector<uint8_t*>& desired_addresses, size_t size, size_t max_distance = 0x7FFF'FFFF);
void internal_free(uint8_t* address, size_t size);

static void combine_adjacent_freenodes(Memory& memory);
[[nodiscard]] static std::expected<uintptr_t, Error> allocate_nearby_memory(
const std::vector<uintptr_t>& desired_addresses, size_t size, size_t max_distance);
[[nodiscard]] static std::expected<uint8_t*, Error> allocate_nearby_memory(
const std::vector<uint8_t*>& desired_addresses, size_t size, size_t max_distance);
[[nodiscard]] static bool in_range(
uintptr_t address, const std::vector<uintptr_t>& desired_addresses, size_t max_distance);
uint8_t* address, const std::vector<uint8_t*>& desired_addresses, size_t max_distance);
};
} // namespace safetyhook
18 changes: 14 additions & 4 deletions include/safetyhook/easy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,33 @@ namespace safetyhook {
/// @param target The address of the function to hook.
/// @param destination The address of the destination function.
/// @return The InlineHook object.
InlineHook create_inline(void* target, void* destination);
[[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.
InlineHook create_inline(uintptr_t target, uintptr_t destination);
template <typename T>
requires std::is_function_v<T>
[[nodiscard]] InlineHook create_inline(T* target, T* destination) {
return create_inline(reinterpret_cast<void*>(target), reinterpret_cast<void*>(destination));
}

/// @brief Easy to use API for creating a MidHook.
/// @param target the address of the function to hook.
/// @param destination The destination function.
/// @return The MidHook object.
MidHook create_mid(void* target, MidHookFn destination);
[[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.
MidHook create_mid(uintptr_t target, MidHookFn destination);
template <typename T>
requires std::is_function_v<T>
[[nodiscard]] MidHook create_mid(T* target, MidHookFn destination) {
return create_mid(reinterpret_cast<void*>(target), destination);
}
} // namespace safetyhook
42 changes: 30 additions & 12 deletions include/safetyhook/inline_hook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class InlineHook final {
/// @brief Extra information about the error.
union {
Allocator::Error allocator_error; ///< Allocator error information.
uintptr_t ip; ///< IP of the problematic instruction.
uint8_t* ip; ///< IP of the problematic instruction.
};

/// @brief Create a BAD_ALLOCATION error.
Expand All @@ -43,28 +43,28 @@ class InlineHook final {
/// @brief Create a FAILED_TO_DECODE_INSTRUCTION error.
/// @param ip The IP of the problematic instruction.
/// @return The new FAILED_TO_DECODE_INSTRUCTION error.
[[nodiscard]] static Error failed_to_decode_instruction(uintptr_t ip) {
[[nodiscard]] static Error failed_to_decode_instruction(uint8_t* ip) {
return {.type = FAILED_TO_DECODE_INSTRUCTION, .ip = ip};
}

/// @brief Create a SHORT_JUMP_IN_TRAMPOLINE error.
/// @param ip The IP of the problematic instruction.
/// @return The new SHORT_JUMP_IN_TRAMPOLINE error.
[[nodiscard]] static Error short_jump_in_trampoline(uintptr_t ip) {
[[nodiscard]] static Error short_jump_in_trampoline(uint8_t* ip) {
return {.type = SHORT_JUMP_IN_TRAMPOLINE, .ip = ip};
}

/// @brief Create a IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE error.
/// @param ip The IP of the problematic instruction.
/// @return The new IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE error.
[[nodiscard]] static Error ip_relative_instruction_out_of_range(uintptr_t ip) {
[[nodiscard]] static Error ip_relative_instruction_out_of_range(uint8_t* ip) {
return {.type = IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE, .ip = ip};
}

/// @brief Create a UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE error.
/// @param ip The IP of the problematic instruction.
/// @return The new UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE error.
[[nodiscard]] static Error unsupported_instruction_in_trampoline(uintptr_t ip) {
[[nodiscard]] static Error unsupported_instruction_in_trampoline(uint8_t* ip) {
return {.type = UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE, .ip = ip};
}
};
Expand All @@ -78,12 +78,17 @@ 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).
[[nodiscard]] static std::expected<InlineHook, Error> create(uintptr_t target, uintptr_t destination);
template <typename T>
requires std::is_function_v<T>
[[nodiscard]] static std::expected<InlineHook, Error> create(T* target, T* destination) {
return create(reinterpret_cast<void*>(target), reinterpret_cast<void*>(destination));
}

/// @brief Create an inline hook with a given Allocator.
/// @param allocator The allocator to use.
Expand All @@ -95,13 +100,18 @@ 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, uintptr_t target, uintptr_t destination);
const std::shared_ptr<Allocator>& allocator, T* target, T* destination) {
return create(allocator, reinterpret_cast<void*>(target), reinterpret_cast<void*>(destination));
}

InlineHook() = default;
InlineHook(const InlineHook&) = delete;
Expand All @@ -115,13 +125,21 @@ class InlineHook final {
/// @note This is called automatically in the destructor.
void reset();

/// @brief Get a pointer to the target.
/// @return A pointer to the target.
[[nodiscard]] uint8_t* target() const { return m_target; }

/// @brief Get the target address.
/// @return The target address.
[[nodiscard]] uintptr_t target() const { return m_target; }
[[nodiscard]] uintptr_t target_address() const { return reinterpret_cast<uintptr_t>(m_target); }

/// @brief Get a pointer ot the destination.
/// @return A pointer to the destination.
[[nodiscard]] uint8_t* destination() const { return m_destination; }

/// @brief Get the destination address.
/// @return The destination address.
[[nodiscard]] size_t destination() const { return m_destination; }
[[nodiscard]] uintptr_t destination_address() const { return reinterpret_cast<uintptr_t>(m_destination); }

/// @brief Get the trampoline Allocation.
/// @return The trampoline Allocation.
Expand Down Expand Up @@ -252,15 +270,15 @@ class InlineHook final {
}

private:
uintptr_t m_target{};
uintptr_t m_destination{};
uint8_t* m_target{};
uint8_t* m_destination{};
Allocation m_trampoline{};
std::vector<uint8_t> m_original_bytes{};
uintptr_t m_trampoline_size{};
std::recursive_mutex m_mutex{};

std::expected<void, Error> setup(
const std::shared_ptr<Allocator>& allocator, uintptr_t target, uintptr_t destination);
const std::shared_ptr<Allocator>& allocator, uint8_t* target, uint8_t* destination);
std::expected<void, Error> e9_hook(const std::shared_ptr<Allocator>& allocator);

#ifdef _M_X64
Expand Down
28 changes: 21 additions & 7 deletions include/safetyhook/mid_hook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,17 @@ 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).
[[nodiscard]] static std::expected<MidHook, Error> create(uintptr_t target, MidHookFn destination);
template <typename T>
requires std::is_function_v<T>
[[nodiscard]] static std::expected<MidHook, Error> create(T* target, MidHookFn destination) {
return create(reinterpret_cast<void*>(target), destination);
}

/// @brief Creates a new MidHook object with a given Allocator.
/// @param allocator The Allocator to use.
Expand All @@ -73,13 +78,18 @@ class MidHook final {
const std::shared_ptr<Allocator>& allocator, void* target, MidHookFn destination);

/// @brief Creates a new MidHook object 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 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, uintptr_t target, MidHookFn destination);
const std::shared_ptr<Allocator>& allocator, T* target, MidHookFn destination) {
return create(allocator, reinterpret_cast<void*>(target), destination);
}

MidHook() = default;
MidHook(const MidHook&) = delete;
Expand All @@ -93,9 +103,13 @@ class MidHook final {
/// @note This is called automatically in the destructor.
void reset();

/// @brief Get the target address.
/// @return The target address.
[[nodiscard]] uintptr_t target() const { return m_target; }
/// @brief Get a pointer to the target.
/// @return A pointer to the target.
[[nodiscard]] uint8_t* target() const { return m_target; }

/// @brief Get the address of the target.
/// @return The address of the target.
[[nodiscard]] uintptr_t target_address() const { return reinterpret_cast<uintptr_t>(m_target); }

/// @brief Get the destination function.
/// @return The destination function.
Expand All @@ -107,11 +121,11 @@ class MidHook final {

private:
InlineHook m_hook{};
uintptr_t m_target{};
uint8_t* m_target{};
Allocation m_stub{};
MidHookFn m_destination{};

std::expected<void, Error> setup(
const std::shared_ptr<Allocator>& allocator, uintptr_t target, MidHookFn destination);
const std::shared_ptr<Allocator>& allocator, uint8_t* target, MidHookFn destination);
};
} // namespace safetyhook
2 changes: 1 addition & 1 deletion include/safetyhook/thread_freezer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ void execute_while_frozen(
/// @param ctx The thread context to modify.
/// @param old_ip The old IP address.
/// @param new_ip The new IP address.
void fix_ip(CONTEXT& ctx, uintptr_t old_ip, uintptr_t new_ip);
void fix_ip(CONTEXT& ctx, uint8_t* old_ip, uint8_t* new_ip);
} // namespace safetyhook
10 changes: 10 additions & 0 deletions include/safetyhook/utility.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

#include <algorithm>
#include <cstdint>

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
Loading

0 comments on commit e0ea316

Please sign in to comment.