Add initial project files (excluding ignored content)

This commit is contained in:
2025-07-16 20:50:34 +02:00
parent e772f348d0
commit 4d3963ed03
270 changed files with 67495 additions and 0 deletions

276
external/safetyhook/src/allocator.cpp vendored Normal file
View File

@@ -0,0 +1,276 @@
#include <algorithm>
#include <functional>
#include <limits>
#include "safetyhook/os.hpp"
#include "safetyhook/utility.hpp"
#include "safetyhook/allocator.hpp"
namespace safetyhook {
Allocation::Allocation(Allocation&& other) noexcept {
*this = std::move(other);
}
Allocation& Allocation::operator=(Allocation&& other) noexcept {
if (this != &other) {
free();
m_allocator = std::move(other.m_allocator);
m_address = other.m_address;
m_size = other.m_size;
other.m_address = nullptr;
other.m_size = 0;
}
return *this;
}
Allocation::~Allocation() {
free();
}
void Allocation::free() {
if (m_allocator && m_address != nullptr && m_size != 0) {
m_allocator->free(m_address, m_size);
m_address = nullptr;
m_size = 0;
m_allocator.reset();
}
}
Allocation::Allocation(std::shared_ptr<Allocator> allocator, uint8_t* address, size_t size) noexcept
: m_allocator{std::move(allocator)}, m_address{address}, m_size{size} {
}
std::shared_ptr<Allocator> Allocator::global() {
static std::weak_ptr<Allocator> global_allocator{};
static std::mutex global_allocator_mutex{};
std::scoped_lock lock{global_allocator_mutex};
if (auto allocator = global_allocator.lock()) {
return allocator;
}
auto allocator = Allocator::create();
global_allocator = allocator;
return allocator;
}
std::shared_ptr<Allocator> Allocator::create() {
return std::shared_ptr<Allocator>{new Allocator{}};
}
std::expected<Allocation, Allocator::Error> Allocator::allocate(size_t size) {
return allocate_near({}, size, std::numeric_limits<size_t>::max());
}
std::expected<Allocation, Allocator::Error> Allocator::allocate_near(
const std::vector<uint8_t*>& desired_addresses, size_t size, size_t max_distance) {
std::scoped_lock lock{m_mutex};
return internal_allocate_near(desired_addresses, size, max_distance);
}
void Allocator::free(uint8_t* address, size_t size) {
std::scoped_lock lock{m_mutex};
return internal_free(address, size);
}
std::expected<Allocation, Allocator::Error> Allocator::internal_allocate_near(
const std::vector<uint8_t*>& desired_addresses, size_t size, size_t max_distance) {
// Align to 2 bytes to pass MFP virtual method check
// See https://itanium-cxx-abi.github.io/cxx-abi/abi.html#member-function-pointers
size_t aligned_size = align_up(size, 2);
// First search through our list of allocations for a free block that is large
// enough.
for (const auto& allocation : m_memory) {
if (allocation->size < aligned_size) {
continue;
}
for (auto node = allocation->freelist.get(); node != nullptr; node = node->next.get()) {
// Enough room?
if (static_cast<size_t>(node->end - node->start) < aligned_size) {
continue;
}
const auto address = node->start;
// Close enough?
if (!in_range(address, desired_addresses, max_distance)) {
continue;
}
node->start += aligned_size;
return Allocation{shared_from_this(), address, size};
}
}
// If we didn't find a free block, we need to allocate a new one.
auto allocation_size = align_up(aligned_size, system_info().allocation_granularity);
auto allocation_address = allocate_nearby_memory(desired_addresses, allocation_size, max_distance);
if (!allocation_address) {
return std::unexpected{allocation_address.error()};
}
auto& allocation = m_memory.emplace_back(new Memory);
allocation->address = *allocation_address;
allocation->size = allocation_size;
allocation->freelist = std::make_unique<FreeNode>();
allocation->freelist->start = *allocation_address + aligned_size;
allocation->freelist->end = *allocation_address + allocation_size;
return Allocation{shared_from_this(), *allocation_address, size};
}
void Allocator::internal_free(uint8_t* address, size_t size) {
// See internal_allocate_near
size = align_up(size, 2);
for (const auto& allocation : m_memory) {
if (allocation->address > address || allocation->address + allocation->size < address) {
continue;
}
// Find the right place for our new freenode.
FreeNode* prev{};
for (auto node = allocation->freelist.get(); node != nullptr; prev = node, node = node->next.get()) {
if (node->start > address) {
break;
}
}
// Add new freenode.
auto free_node = std::make_unique<FreeNode>();
free_node->start = address;
free_node->end = address + size;
if (prev == nullptr) {
free_node->next.swap(allocation->freelist);
allocation->freelist.swap(free_node);
} else {
free_node->next.swap(prev->next);
prev->next.swap(free_node);
}
combine_adjacent_freenodes(*allocation);
break;
}
}
void Allocator::combine_adjacent_freenodes(Memory& memory) {
for (auto prev = memory.freelist.get(), node = prev; node != nullptr; node = node->next.get()) {
if (prev->end == node->start) {
prev->end = node->end;
prev->next.swap(node->next);
node->next.reset();
node = prev;
} else {
prev = node;
}
}
}
std::expected<uint8_t*, Allocator::Error> Allocator::allocate_nearby_memory(
const std::vector<uint8_t*>& desired_addresses, size_t size, size_t max_distance) {
if (desired_addresses.empty()) {
if (auto result = vm_allocate(nullptr, size, VM_ACCESS_RWX)) {
return result.value();
}
return std::unexpected{Error::BAD_VIRTUAL_ALLOC};
}
auto attempt_allocation = [&](uint8_t* p) -> uint8_t* {
if (!in_range(p, desired_addresses, max_distance)) {
return nullptr;
}
if (auto result = vm_allocate(p, size, VM_ACCESS_RWX)) {
return result.value();
}
return nullptr;
};
auto si = system_info();
auto desired_address = desired_addresses[0];
auto search_start = si.min_address;
auto search_end = si.max_address;
if (static_cast<size_t>(desired_address - search_start) > max_distance) {
search_start = desired_address - max_distance;
}
if (static_cast<size_t>(search_end - desired_address) > max_distance) {
search_end = desired_address + max_distance;
}
search_start = std::max(search_start, si.min_address);
search_end = std::min(search_end, si.max_address);
desired_address = align_up(desired_address, si.allocation_granularity);
VmBasicInfo mbi{};
// Search backwards from the desired_address.
for (auto p = desired_address; p > search_start && in_range(p, desired_addresses, max_distance);
p = align_down(mbi.address - 1, si.allocation_granularity)) {
auto result = vm_query(p);
if (!result) {
break;
}
mbi = result.value();
if (!mbi.is_free) {
continue;
}
if (auto allocation_address = attempt_allocation(p); allocation_address != nullptr) {
return allocation_address;
}
}
// Search forwards from the desired_address.
for (auto p = desired_address; p < search_end && in_range(p, desired_addresses, max_distance); p += mbi.size) {
auto result = vm_query(p);
if (!result) {
break;
}
mbi = result.value();
if (!mbi.is_free) {
continue;
}
if (auto allocation_address = attempt_allocation(p); allocation_address != nullptr) {
return allocation_address;
}
}
return std::unexpected{Error::NO_MEMORY_IN_RANGE};
}
bool Allocator::in_range(uint8_t* address, const std::vector<uint8_t*>& desired_addresses, size_t max_distance) {
return std::all_of(desired_addresses.begin(), desired_addresses.end(), [&](const auto& desired_address) {
const size_t delta = (address > desired_address) ? address - desired_address : desired_address - address;
return delta <= max_distance;
});
}
Allocator::Memory::~Memory() {
vm_free(address);
}
} // namespace safetyhook

27
external/safetyhook/src/easy.cpp vendored Normal file
View File

@@ -0,0 +1,27 @@
#include "safetyhook/easy.hpp"
namespace safetyhook {
InlineHook create_inline(void* target, void* destination, InlineHook::Flags flags) {
if (auto hook = InlineHook::create(target, destination, flags)) {
return std::move(*hook);
} else {
return {};
}
}
MidHook create_mid(void* target, MidHookFn destination, MidHook::Flags flags) {
if (auto hook = MidHook::create(target, destination, flags)) {
return std::move(*hook);
} else {
return {};
}
}
VmtHook create_vmt(void* object) {
if (auto hook = VmtHook::create(object)) {
return std::move(*hook);
} else {
return {};
}
}
} // namespace safetyhook

440
external/safetyhook/src/inline_hook.cpp vendored Normal file
View File

@@ -0,0 +1,440 @@
#include <iterator>
#if __has_include("Zydis/Zydis.h")
#include "Zydis/Zydis.h"
#elif __has_include("Zydis.h")
#include "Zydis.h"
#else
#error "Zydis not found"
#endif
#include "safetyhook/allocator.hpp"
#include "safetyhook/common.hpp"
#include "safetyhook/os.hpp"
#include "safetyhook/utility.hpp"
#include "safetyhook/inline_hook.hpp"
namespace safetyhook {
#pragma pack(push, 1)
struct JmpE9 {
uint8_t opcode{0xE9};
uint32_t offset{0};
};
#if SAFETYHOOK_ARCH_X86_64
struct JmpFF {
uint8_t opcode0{0xFF};
uint8_t opcode1{0x25};
uint32_t offset{0};
};
struct TrampolineEpilogueE9 {
JmpE9 jmp_to_original{};
JmpFF jmp_to_destination{};
uint64_t destination_address{};
};
struct TrampolineEpilogueFF {
JmpFF jmp_to_original{};
uint64_t original_address{};
};
#elif SAFETYHOOK_ARCH_X86_32
struct TrampolineEpilogueE9 {
JmpE9 jmp_to_original{};
JmpE9 jmp_to_destination{};
};
#endif
#pragma pack(pop)
#if SAFETYHOOK_ARCH_X86_64
static auto make_jmp_ff(uint8_t* src, uint8_t* dst, uint8_t* data) {
JmpFF jmp{};
jmp.offset = static_cast<uint32_t>(data - src - sizeof(jmp));
store(data, dst);
return jmp;
}
[[nodiscard]] static std::expected<void, InlineHook::Error> emit_jmp_ff(
uint8_t* src, uint8_t* dst, uint8_t* data, size_t size = sizeof(JmpFF)) {
if (size < sizeof(JmpFF)) {
return std::unexpected{InlineHook::Error::not_enough_space(dst)};
}
if (size > sizeof(JmpFF)) {
std::fill_n(src, size, static_cast<uint8_t>(0x90));
}
store(src, make_jmp_ff(src, dst, data));
return {};
}
#endif
constexpr auto make_jmp_e9(uint8_t* src, uint8_t* dst) {
JmpE9 jmp{};
jmp.offset = static_cast<uint32_t>(dst - src - sizeof(jmp));
return jmp;
}
[[nodiscard]] static std::expected<void, InlineHook::Error> emit_jmp_e9(
uint8_t* src, uint8_t* dst, size_t size = sizeof(JmpE9)) {
if (size < sizeof(JmpE9)) {
return std::unexpected{InlineHook::Error::not_enough_space(dst)};
}
if (size > sizeof(JmpE9)) {
std::fill_n(src, size, static_cast<uint8_t>(0x90));
}
store(src, make_jmp_e9(src, dst));
return {};
}
static bool decode(ZydisDecodedInstruction* ix, uint8_t* ip) {
ZydisDecoder decoder{};
ZyanStatus status;
#if SAFETYHOOK_ARCH_X86_64
status = ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
#elif SAFETYHOOK_ARCH_X86_32
status = ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LEGACY_32, ZYDIS_STACK_WIDTH_32);
#endif
if (!ZYAN_SUCCESS(status)) {
return false;
}
return ZYAN_SUCCESS(ZydisDecoderDecodeInstruction(&decoder, nullptr, ip, 15, ix));
}
std::expected<InlineHook, InlineHook::Error> InlineHook::create(void* target, void* destination, Flags flags) {
return create(Allocator::global(), target, destination, flags);
}
std::expected<InlineHook, InlineHook::Error> InlineHook::create(
const std::shared_ptr<Allocator>& allocator, void* target, void* destination, Flags flags) {
InlineHook hook{};
if (const auto setup_result =
hook.setup(allocator, reinterpret_cast<uint8_t*>(target), reinterpret_cast<uint8_t*>(destination));
!setup_result) {
return std::unexpected{setup_result.error()};
}
if (!(flags & StartDisabled)) {
if (auto enable_result = hook.enable(); !enable_result) {
return std::unexpected{enable_result.error()};
}
}
return hook;
}
InlineHook::InlineHook(InlineHook&& other) noexcept {
*this = std::move(other);
}
InlineHook& InlineHook::operator=(InlineHook&& other) noexcept {
if (this != &other) {
destroy();
std::scoped_lock lock{m_mutex, other.m_mutex};
m_target = other.m_target;
m_destination = other.m_destination;
m_trampoline = std::move(other.m_trampoline);
m_trampoline_size = other.m_trampoline_size;
m_original_bytes = std::move(other.m_original_bytes);
m_enabled = other.m_enabled;
m_type = other.m_type;
other.m_target = nullptr;
other.m_destination = nullptr;
other.m_trampoline_size = 0;
other.m_enabled = false;
other.m_type = Type::Unset;
}
return *this;
}
InlineHook::~InlineHook() {
destroy();
}
void InlineHook::reset() {
*this = {};
}
std::expected<void, InlineHook::Error> InlineHook::setup(
const std::shared_ptr<Allocator>& allocator, uint8_t* target, uint8_t* destination) {
m_target = target;
m_destination = destination;
if (auto e9_result = e9_hook(allocator); !e9_result) {
#if SAFETYHOOK_ARCH_X86_64
if (auto ff_result = ff_hook(allocator); !ff_result) {
return ff_result;
}
#elif SAFETYHOOK_ARCH_X86_32
return e9_result;
#endif
}
return {};
}
std::expected<void, InlineHook::Error> InlineHook::e9_hook(const std::shared_ptr<Allocator>& allocator) {
m_original_bytes.clear();
m_trampoline_size = sizeof(TrampolineEpilogueE9);
std::vector<uint8_t*> desired_addresses{m_target};
ZydisDecodedInstruction ix{};
for (auto ip = m_target; ip < m_target + sizeof(JmpE9); ip += ix.length) {
if (!decode(&ix, ip)) {
return std::unexpected{Error::failed_to_decode_instruction(ip)};
}
m_trampoline_size += ix.length;
m_original_bytes.insert(m_original_bytes.end(), ip, ip + ix.length);
const auto is_relative = (ix.attributes & ZYDIS_ATTRIB_IS_RELATIVE) != 0;
if (is_relative) {
if (ix.raw.disp.size == 32) {
const auto target_address = ip + ix.length + static_cast<int32_t>(ix.raw.disp.value);
desired_addresses.emplace_back(target_address);
} else if (ix.raw.imm[0].size == 32) {
const auto target_address = ip + ix.length + static_cast<int32_t>(ix.raw.imm[0].value.s);
desired_addresses.emplace_back(target_address);
} else if (ix.meta.category == ZYDIS_CATEGORY_COND_BR && ix.meta.branch_type == ZYDIS_BRANCH_TYPE_SHORT) {
const auto target_address = ip + ix.length + static_cast<int32_t>(ix.raw.imm[0].value.s);
desired_addresses.emplace_back(target_address);
m_trampoline_size += 4; // near conditional branches are 4 bytes larger.
} else if (ix.meta.category == ZYDIS_CATEGORY_UNCOND_BR && ix.meta.branch_type == ZYDIS_BRANCH_TYPE_SHORT) {
const auto target_address = ip + ix.length + static_cast<int32_t>(ix.raw.imm[0].value.s);
desired_addresses.emplace_back(target_address);
m_trampoline_size += 3; // near unconditional branches are 3 bytes larger.
} else {
return std::unexpected{Error::unsupported_instruction_in_trampoline(ip)};
}
}
}
auto trampoline_allocation = allocator->allocate_near(desired_addresses, m_trampoline_size);
if (!trampoline_allocation) {
return std::unexpected{Error::bad_allocation(trampoline_allocation.error())};
}
m_trampoline = std::move(*trampoline_allocation);
for (auto ip = m_target, tramp_ip = m_trampoline.data(); ip < m_target + m_original_bytes.size(); ip += ix.length) {
if (!decode(&ix, ip)) {
m_trampoline.free();
return std::unexpected{Error::failed_to_decode_instruction(ip)};
}
const auto is_relative = (ix.attributes & ZYDIS_ATTRIB_IS_RELATIVE) != 0;
if (is_relative && ix.raw.disp.size == 32) {
std::copy_n(ip, ix.length, tramp_ip);
const auto target_address = ip + ix.length + ix.raw.disp.value;
const auto new_disp = target_address - (tramp_ip + ix.length);
store(tramp_ip + ix.raw.disp.offset, static_cast<int32_t>(new_disp));
tramp_ip += ix.length;
} else if (is_relative && ix.raw.imm[0].size == 32) {
std::copy_n(ip, ix.length, tramp_ip);
const auto target_address = ip + ix.length + ix.raw.imm[0].value.s;
const auto new_disp = target_address - (tramp_ip + ix.length);
store(tramp_ip + ix.raw.imm[0].offset, static_cast<int32_t>(new_disp));
tramp_ip += ix.length;
} else if (ix.meta.category == ZYDIS_CATEGORY_COND_BR && ix.meta.branch_type == ZYDIS_BRANCH_TYPE_SHORT) {
const auto target_address = ip + ix.length + ix.raw.imm[0].value.s;
auto new_disp = target_address - (tramp_ip + 6);
// Handle the case where the target is now in the trampoline.
if (target_address >= m_target && target_address < m_target + m_original_bytes.size()) {
new_disp = static_cast<ptrdiff_t>(ix.raw.imm[0].value.s);
}
*tramp_ip = 0x0F;
*(tramp_ip + 1) = 0x10 + ix.opcode;
store(tramp_ip + 2, static_cast<int32_t>(new_disp));
tramp_ip += 6;
} else if (ix.meta.category == ZYDIS_CATEGORY_UNCOND_BR && ix.meta.branch_type == ZYDIS_BRANCH_TYPE_SHORT) {
const auto target_address = ip + ix.length + ix.raw.imm[0].value.s;
auto new_disp = target_address - (tramp_ip + 5);
// Handle the case where the target is now in the trampoline.
if (target_address >= m_target && target_address < m_target + m_original_bytes.size()) {
new_disp = static_cast<ptrdiff_t>(ix.raw.imm[0].value.s);
}
*tramp_ip = 0xE9;
store(tramp_ip + 1, static_cast<int32_t>(new_disp));
tramp_ip += 5;
} else {
std::copy_n(ip, ix.length, tramp_ip);
tramp_ip += ix.length;
}
}
auto trampoline_epilogue = reinterpret_cast<TrampolineEpilogueE9*>(
m_trampoline.address() + m_trampoline_size - sizeof(TrampolineEpilogueE9));
// jmp from trampoline to original.
auto src = reinterpret_cast<uint8_t*>(&trampoline_epilogue->jmp_to_original);
auto dst = m_target + m_original_bytes.size();
if (auto result = emit_jmp_e9(src, dst); !result) {
return std::unexpected{result.error()};
}
// jmp from trampoline to destination.
src = reinterpret_cast<uint8_t*>(&trampoline_epilogue->jmp_to_destination);
dst = m_destination;
#if SAFETYHOOK_ARCH_X86_64
auto data = reinterpret_cast<uint8_t*>(&trampoline_epilogue->destination_address);
if (auto result = emit_jmp_ff(src, dst, data); !result) {
return std::unexpected{result.error()};
}
#elif SAFETYHOOK_ARCH_X86_32
if (auto result = emit_jmp_e9(src, dst); !result) {
return std::unexpected{result.error()};
}
#endif
m_type = Type::E9;
return {};
}
#if SAFETYHOOK_ARCH_X86_64
std::expected<void, InlineHook::Error> InlineHook::ff_hook(const std::shared_ptr<Allocator>& allocator) {
m_original_bytes.clear();
m_trampoline_size = sizeof(TrampolineEpilogueFF);
ZydisDecodedInstruction ix{};
for (auto ip = m_target; ip < m_target + sizeof(JmpFF) + sizeof(uintptr_t); ip += ix.length) {
if (!decode(&ix, ip)) {
return std::unexpected{Error::failed_to_decode_instruction(ip)};
}
// We can't support any instruction that is IP relative here because
// ff_hook should only be called if e9_hook failed indicating that
// we're likely outside the +- 2GB range.
if (ix.attributes & ZYDIS_ATTRIB_IS_RELATIVE) {
return std::unexpected{Error::ip_relative_instruction_out_of_range(ip)};
}
m_original_bytes.insert(m_original_bytes.end(), ip, ip + ix.length);
m_trampoline_size += ix.length;
}
auto trampoline_allocation = allocator->allocate(m_trampoline_size);
if (!trampoline_allocation) {
return std::unexpected{Error::bad_allocation(trampoline_allocation.error())};
}
m_trampoline = std::move(*trampoline_allocation);
std::copy(m_original_bytes.begin(), m_original_bytes.end(), m_trampoline.data());
const auto trampoline_epilogue =
reinterpret_cast<TrampolineEpilogueFF*>(m_trampoline.data() + m_trampoline_size - sizeof(TrampolineEpilogueFF));
// jmp from trampoline to original.
auto src = reinterpret_cast<uint8_t*>(&trampoline_epilogue->jmp_to_original);
auto dst = m_target + m_original_bytes.size();
auto data = reinterpret_cast<uint8_t*>(&trampoline_epilogue->original_address);
if (auto result = emit_jmp_ff(src, dst, data); !result) {
return std::unexpected{result.error()};
}
m_type = Type::FF;
return {};
}
#endif
std::expected<void, InlineHook::Error> InlineHook::enable() {
std::scoped_lock lock{m_mutex};
if (m_enabled) {
return {};
}
std::optional<Error> error;
// jmp from original to trampoline.
trap_threads(m_target, m_trampoline.data(), m_original_bytes.size(), [this, &error] {
if (m_type == Type::E9) {
auto trampoline_epilogue = reinterpret_cast<TrampolineEpilogueE9*>(
m_trampoline.address() + m_trampoline_size - sizeof(TrampolineEpilogueE9));
if (auto result = emit_jmp_e9(m_target,
reinterpret_cast<uint8_t*>(&trampoline_epilogue->jmp_to_destination), m_original_bytes.size());
!result) {
error = result.error();
}
}
#if SAFETYHOOK_ARCH_X86_64
if (m_type == Type::FF) {
if (auto result = emit_jmp_ff(m_target, m_destination, m_target + sizeof(JmpFF), m_original_bytes.size());
!result) {
error = result.error();
}
}
#endif
});
if (error) {
return std::unexpected{*error};
}
m_enabled = true;
return {};
}
std::expected<void, InlineHook::Error> InlineHook::disable() {
std::scoped_lock lock{m_mutex};
if (!m_enabled) {
return {};
}
trap_threads(m_trampoline.data(), m_target, m_original_bytes.size(),
[this] { std::copy(m_original_bytes.begin(), m_original_bytes.end(), m_target); });
m_enabled = false;
return {};
}
void InlineHook::destroy() {
[[maybe_unused]] auto disable_result = disable();
std::scoped_lock lock{m_mutex};
if (!m_trampoline) {
return;
}
m_trampoline.free();
}
} // namespace safetyhook

173
external/safetyhook/src/mid_hook.cpp vendored Normal file
View File

@@ -0,0 +1,173 @@
#include <algorithm>
#include <array>
#include "safetyhook/allocator.hpp"
#include "safetyhook/inline_hook.hpp"
#include "safetyhook/utility.hpp"
#include "safetyhook/mid_hook.hpp"
namespace safetyhook {
#if SAFETYHOOK_ARCH_X86_64
#if SAFETYHOOK_OS_WINDOWS
constexpr std::array<uint8_t, 391> asm_data = {0xFF, 0x35, 0x79, 0x01, 0x00, 0x00, 0x54, 0x54, 0x55, 0x50, 0x53, 0x51,
0x52, 0x56, 0x57, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57,
0x9C, 0x48, 0x81, 0xEC, 0x00, 0x01, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0xBC, 0x24, 0xF0, 0x00, 0x00, 0x00, 0xF3,
0x44, 0x0F, 0x7F, 0xB4, 0x24, 0xE0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0xAC, 0x24, 0xD0, 0x00, 0x00, 0x00,
0xF3, 0x44, 0x0F, 0x7F, 0xA4, 0x24, 0xC0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x9C, 0x24, 0xB0, 0x00, 0x00,
0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x94, 0x24, 0xA0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x8C, 0x24, 0x90, 0x00,
0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x0F, 0x7F, 0x7C, 0x24, 0x70, 0xF3,
0x0F, 0x7F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x7F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x7F, 0x64, 0x24, 0x40, 0xF3, 0x0F,
0x7F, 0x5C, 0x24, 0x30, 0xF3, 0x0F, 0x7F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x7F, 0x4C, 0x24, 0x10, 0xF3, 0x0F, 0x7F,
0x04, 0x24, 0x48, 0x8B, 0x8C, 0x24, 0x80, 0x01, 0x00, 0x00, 0x48, 0x83, 0xC1, 0x10, 0x48, 0x89, 0x8C, 0x24, 0x80,
0x01, 0x00, 0x00, 0x48, 0x8D, 0x0C, 0x24, 0x48, 0x89, 0xE3, 0x48, 0x83, 0xEC, 0x30, 0x48, 0x83, 0xE4, 0xF0, 0xFF,
0x15, 0xA8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xDC, 0xF3, 0x0F, 0x6F, 0x04, 0x24, 0xF3, 0x0F, 0x6F, 0x4C, 0x24, 0x10,
0xF3, 0x0F, 0x6F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x6F, 0x5C, 0x24, 0x30, 0xF3, 0x0F, 0x6F, 0x64, 0x24, 0x40, 0xF3,
0x0F, 0x6F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x6F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x6F, 0x7C, 0x24, 0x70, 0xF3, 0x44,
0x0F, 0x6F, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0x8C, 0x24, 0x90, 0x00, 0x00, 0x00, 0xF3,
0x44, 0x0F, 0x6F, 0x94, 0x24, 0xA0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0x9C, 0x24, 0xB0, 0x00, 0x00, 0x00,
0xF3, 0x44, 0x0F, 0x6F, 0xA4, 0x24, 0xC0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xAC, 0x24, 0xD0, 0x00, 0x00,
0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xB4, 0x24, 0xE0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xBC, 0x24, 0xF0, 0x00,
0x00, 0x00, 0x48, 0x81, 0xC4, 0x00, 0x01, 0x00, 0x00, 0x9D, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41,
0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58, 0x5D, 0x48, 0x8D, 0x64, 0x24, 0x08,
0x5C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
#elif SAFETYHOOK_OS_LINUX
constexpr std::array<uint8_t, 391> asm_data = {0xFF, 0x35, 0x79, 0x01, 0x00, 0x00, 0x54, 0x54, 0x55, 0x50, 0x53, 0x51,
0x52, 0x56, 0x57, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57,
0x9C, 0x48, 0x81, 0xEC, 0x00, 0x01, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0xBC, 0x24, 0xF0, 0x00, 0x00, 0x00, 0xF3,
0x44, 0x0F, 0x7F, 0xB4, 0x24, 0xE0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0xAC, 0x24, 0xD0, 0x00, 0x00, 0x00,
0xF3, 0x44, 0x0F, 0x7F, 0xA4, 0x24, 0xC0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x9C, 0x24, 0xB0, 0x00, 0x00,
0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x94, 0x24, 0xA0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x8C, 0x24, 0x90, 0x00,
0x00, 0x00, 0xF3, 0x44, 0x0F, 0x7F, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x0F, 0x7F, 0x7C, 0x24, 0x70, 0xF3,
0x0F, 0x7F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x7F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x7F, 0x64, 0x24, 0x40, 0xF3, 0x0F,
0x7F, 0x5C, 0x24, 0x30, 0xF3, 0x0F, 0x7F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x7F, 0x4C, 0x24, 0x10, 0xF3, 0x0F, 0x7F,
0x04, 0x24, 0x48, 0x8B, 0xBC, 0x24, 0x80, 0x01, 0x00, 0x00, 0x48, 0x83, 0xC7, 0x10, 0x48, 0x89, 0xBC, 0x24, 0x80,
0x01, 0x00, 0x00, 0x48, 0x8D, 0x3C, 0x24, 0x48, 0x89, 0xE3, 0x48, 0x83, 0xEC, 0x30, 0x48, 0x83, 0xE4, 0xF0, 0xFF,
0x15, 0xA8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xDC, 0xF3, 0x0F, 0x6F, 0x04, 0x24, 0xF3, 0x0F, 0x6F, 0x4C, 0x24, 0x10,
0xF3, 0x0F, 0x6F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x6F, 0x5C, 0x24, 0x30, 0xF3, 0x0F, 0x6F, 0x64, 0x24, 0x40, 0xF3,
0x0F, 0x6F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x6F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x6F, 0x7C, 0x24, 0x70, 0xF3, 0x44,
0x0F, 0x6F, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0x8C, 0x24, 0x90, 0x00, 0x00, 0x00, 0xF3,
0x44, 0x0F, 0x6F, 0x94, 0x24, 0xA0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0x9C, 0x24, 0xB0, 0x00, 0x00, 0x00,
0xF3, 0x44, 0x0F, 0x6F, 0xA4, 0x24, 0xC0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xAC, 0x24, 0xD0, 0x00, 0x00,
0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xB4, 0x24, 0xE0, 0x00, 0x00, 0x00, 0xF3, 0x44, 0x0F, 0x6F, 0xBC, 0x24, 0xF0, 0x00,
0x00, 0x00, 0x48, 0x81, 0xC4, 0x00, 0x01, 0x00, 0x00, 0x9D, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41,
0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58, 0x5D, 0x48, 0x8D, 0x64, 0x24, 0x08,
0x5C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
#endif
#elif SAFETYHOOK_ARCH_X86_32
constexpr std::array<uint8_t, 171> asm_data = {0xFF, 0x35, 0xA7, 0x00, 0x00, 0x00, 0x54, 0x54, 0x55, 0x50, 0x53, 0x51,
0x52, 0x56, 0x57, 0x9C, 0x81, 0xEC, 0x80, 0x00, 0x00, 0x00, 0xF3, 0x0F, 0x7F, 0x7C, 0x24, 0x70, 0xF3, 0x0F, 0x7F,
0x74, 0x24, 0x60, 0xF3, 0x0F, 0x7F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x7F, 0x64, 0x24, 0x40, 0xF3, 0x0F, 0x7F, 0x5C,
0x24, 0x30, 0xF3, 0x0F, 0x7F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x7F, 0x4C, 0x24, 0x10, 0xF3, 0x0F, 0x7F, 0x04, 0x24,
0x8B, 0x8C, 0x24, 0xA0, 0x00, 0x00, 0x00, 0x83, 0xC1, 0x08, 0x89, 0x8C, 0x24, 0xA0, 0x00, 0x00, 0x00, 0x54, 0xFF,
0x15, 0xA3, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x04, 0xF3, 0x0F, 0x6F, 0x04, 0x24, 0xF3, 0x0F, 0x6F, 0x4C, 0x24, 0x10,
0xF3, 0x0F, 0x6F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x6F, 0x5C, 0x24, 0x30, 0xF3, 0x0F, 0x6F, 0x64, 0x24, 0x40, 0xF3,
0x0F, 0x6F, 0x6C, 0x24, 0x50, 0xF3, 0x0F, 0x6F, 0x74, 0x24, 0x60, 0xF3, 0x0F, 0x6F, 0x7C, 0x24, 0x70, 0x81, 0xC4,
0x80, 0x00, 0x00, 0x00, 0x9D, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58, 0x5D, 0x8D, 0x64, 0x24, 0x04, 0x5C, 0xC3, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
#endif
std::expected<MidHook, MidHook::Error> MidHook::create(void* target, MidHookFn destination, Flags flags) {
return create(Allocator::global(), target, destination, flags);
}
std::expected<MidHook, MidHook::Error> MidHook::create(
const std::shared_ptr<Allocator>& allocator, void* target, MidHookFn destination, Flags flags) {
MidHook hook{};
if (const auto setup_result = hook.setup(allocator, reinterpret_cast<uint8_t*>(target), destination);
!setup_result) {
return std::unexpected{setup_result.error()};
}
if (!(flags & StartDisabled)) {
if (auto enable_result = hook.enable(); !enable_result) {
return std::unexpected{enable_result.error()};
}
}
return hook;
}
MidHook::MidHook(MidHook&& other) noexcept {
*this = std::move(other);
}
MidHook& MidHook::operator=(MidHook&& other) noexcept {
if (this != &other) {
m_hook = std::move(other.m_hook);
m_target = other.m_target;
m_stub = std::move(other.m_stub);
m_destination = other.m_destination;
other.m_target = 0;
other.m_destination = nullptr;
}
return *this;
}
void MidHook::reset() {
*this = {};
}
std::expected<void, MidHook::Error> MidHook::setup(
const std::shared_ptr<Allocator>& allocator, uint8_t* target, MidHookFn destination_fn) {
m_target = target;
m_destination = destination_fn;
auto stub_allocation = allocator->allocate(asm_data.size());
if (!stub_allocation) {
return std::unexpected{Error::bad_allocation(stub_allocation.error())};
}
m_stub = std::move(*stub_allocation);
std::copy(asm_data.begin(), asm_data.end(), m_stub.data());
#if SAFETYHOOK_ARCH_X86_64
store(m_stub.data() + sizeof(asm_data) - 16, m_destination);
#elif SAFETYHOOK_ARCH_X86_32
store(m_stub.data() + sizeof(asm_data) - 8, m_destination);
// 32-bit has some relocations we need to fix up as well.
store(m_stub.data() + 0x02, m_stub.data() + m_stub.size() - 4);
store(m_stub.data() + 0x59, m_stub.data() + m_stub.size() - 8);
#endif
auto hook_result = InlineHook::create(allocator, m_target, m_stub.data(), InlineHook::StartDisabled);
if (!hook_result) {
m_stub.free();
return std::unexpected{Error::bad_inline_hook(hook_result.error())};
}
m_hook = std::move(*hook_result);
#if SAFETYHOOK_ARCH_X86_64
store(m_stub.data() + sizeof(asm_data) - 8, m_hook.trampoline().data());
#elif SAFETYHOOK_ARCH_X86_32
store(m_stub.data() + sizeof(asm_data) - 4, m_hook.trampoline().data());
#endif
return {};
}
std::expected<void, MidHook::Error> MidHook::enable() {
if (auto enable_result = m_hook.enable(); !enable_result) {
return std::unexpected{Error::bad_inline_hook(enable_result.error())};
}
return {};
}
std::expected<void, MidHook::Error> MidHook::disable() {
if (auto disable_result = m_hook.disable(); !disable_result) {
return std::unexpected{Error::bad_inline_hook(disable_result.error())};
}
return {};
}
} // namespace safetyhook

View File

@@ -0,0 +1,60 @@
bits 32
; save context
push dword [trampoline]
push esp ; push trampoline esp
push esp ; push original esp (this gets fixed later)
push ebp
push eax
push ebx
push ecx
push edx
push esi
push edi
pushfd
sub esp, 128
movdqu [esp+112], xmm7
movdqu [esp+96], xmm6
movdqu [esp+80], xmm5
movdqu [esp+64], xmm4
movdqu [esp+48], xmm3
movdqu [esp+32], xmm2
movdqu [esp+16], xmm1
movdqu [esp], xmm0
; fix stored esp.
mov ecx, [esp+160]
add ecx, 8
mov [esp+160], ecx
; call destination
push esp
call [destination]
add esp, 4
; restore context
movdqu xmm0, [esp]
movdqu xmm1, [esp+16]
movdqu xmm2, [esp+32]
movdqu xmm3, [esp+48]
movdqu xmm4, [esp+64]
movdqu xmm5, [esp+80]
movdqu xmm6, [esp+96]
movdqu xmm7, [esp+112]
add esp, 128
popfd
pop edi
pop esi
pop edx
pop ecx
pop ebx
pop eax
pop ebp
lea esp, [esp+4] ; skip original esp
pop esp
ret
destination:
dd 0
trampoline:
dd 0

View File

@@ -0,0 +1,101 @@
bits 64
; save context
push qword [rel trampoline]
push rsp ; push trampoline rsp
push rsp ; push original rsp (this gets fixed later)
push rbp
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
pushfq
sub rsp, 256
movdqu [rsp+240], xmm15
movdqu [rsp+224], xmm14
movdqu [rsp+208], xmm13
movdqu [rsp+192], xmm12
movdqu [rsp+176], xmm11
movdqu [rsp+160], xmm10
movdqu [rsp+144], xmm9
movdqu [rsp+128], xmm8
movdqu [rsp+112], xmm7
movdqu [rsp+96], xmm6
movdqu [rsp+80], xmm5
movdqu [rsp+64], xmm4
movdqu [rsp+48], xmm3
movdqu [rsp+32], xmm2
movdqu [rsp+16], xmm1
movdqu [rsp], xmm0
; fix stored rsp.
mov rdi, [rsp+384]
add rdi, 16
mov [rsp+384], rdi
; set destination parameter
lea rdi, [rsp]
; align stack, save original
mov rbx, rsp
sub rsp, 48
and rsp, -16
; call destination
call [rel destination]
; restore stack
mov rsp, rbx
; restore context
movdqu xmm0, [rsp]
movdqu xmm1, [rsp+16]
movdqu xmm2, [rsp+32]
movdqu xmm3, [rsp+48]
movdqu xmm4, [rsp+64]
movdqu xmm5, [rsp+80]
movdqu xmm6, [rsp+96]
movdqu xmm7, [rsp+112]
movdqu xmm8, [rsp+128]
movdqu xmm9, [rsp+144]
movdqu xmm10, [rsp+160]
movdqu xmm11, [rsp+176]
movdqu xmm12, [rsp+192]
movdqu xmm13, [rsp+208]
movdqu xmm14, [rsp+224]
movdqu xmm15, [rsp+240]
add rsp, 256
popfq
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
pop rbp
lea rsp, [rsp+8] ; skip original rsp
pop rsp
ret
destination:
dq 0
trampoline:
dq 0

View File

@@ -0,0 +1,101 @@
bits 64
; save context
push qword [rel trampoline]
push rsp ; push trampoline rsp
push rsp ; push original rsp (this gets fixed later)
push rbp
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
pushfq
sub rsp, 256
movdqu [rsp+240], xmm15
movdqu [rsp+224], xmm14
movdqu [rsp+208], xmm13
movdqu [rsp+192], xmm12
movdqu [rsp+176], xmm11
movdqu [rsp+160], xmm10
movdqu [rsp+144], xmm9
movdqu [rsp+128], xmm8
movdqu [rsp+112], xmm7
movdqu [rsp+96], xmm6
movdqu [rsp+80], xmm5
movdqu [rsp+64], xmm4
movdqu [rsp+48], xmm3
movdqu [rsp+32], xmm2
movdqu [rsp+16], xmm1
movdqu [rsp], xmm0
; fix stored rsp.
mov rcx, [rsp+384]
add rcx, 16
mov [rsp+384], rcx
; set destination parameter
lea rcx, [rsp]
; align stack, save original
mov rbx, rsp
sub rsp, 48
and rsp, -16
; call destination
call [rel destination]
; restore stack
mov rsp, rbx
; restore context
movdqu xmm0, [rsp]
movdqu xmm1, [rsp+16]
movdqu xmm2, [rsp+32]
movdqu xmm3, [rsp+48]
movdqu xmm4, [rsp+64]
movdqu xmm5, [rsp+80]
movdqu xmm6, [rsp+96]
movdqu xmm7, [rsp+112]
movdqu xmm8, [rsp+128]
movdqu xmm9, [rsp+144]
movdqu xmm10, [rsp+160]
movdqu xmm11, [rsp+176]
movdqu xmm12, [rsp+192]
movdqu xmm13, [rsp+208]
movdqu xmm14, [rsp+224]
movdqu xmm15, [rsp+240]
add rsp, 256
popfq
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
pop rbp
lea rsp, [rsp+8] ; skip original rsp
pop rsp
ret
destination:
dq 0
trampoline:
dq 0

194
external/safetyhook/src/os.linux.cpp vendored Normal file
View File

@@ -0,0 +1,194 @@
#include "safetyhook/common.hpp"
#if SAFETYHOOK_OS_LINUX
#include <cstdio>
#include <sys/mman.h>
#include <unistd.h>
#include "safetyhook/utility.hpp"
#include "safetyhook/os.hpp"
namespace safetyhook {
std::expected<uint8_t*, OsError> vm_allocate(uint8_t* address, size_t size, VmAccess access) {
int prot = 0;
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
if (access == VM_ACCESS_R) {
prot = PROT_READ;
} else if (access == VM_ACCESS_RW) {
prot = PROT_READ | PROT_WRITE;
} else if (access == VM_ACCESS_RX) {
prot = PROT_READ | PROT_EXEC;
} else if (access == VM_ACCESS_RWX) {
prot = PROT_READ | PROT_WRITE | PROT_EXEC;
} else {
return std::unexpected{OsError::FAILED_TO_ALLOCATE};
}
auto* result = mmap(address, size, prot, flags, -1, 0);
if (result == MAP_FAILED) {
return std::unexpected{OsError::FAILED_TO_ALLOCATE};
}
return static_cast<uint8_t*>(result);
}
void vm_free(uint8_t* address) {
munmap(address, 0);
}
std::expected<uint32_t, OsError> vm_protect(uint8_t* address, size_t size, VmAccess access) {
int prot = 0;
if (access == VM_ACCESS_R) {
prot = PROT_READ;
} else if (access == VM_ACCESS_RW) {
prot = PROT_READ | PROT_WRITE;
} else if (access == VM_ACCESS_RX) {
prot = PROT_READ | PROT_EXEC;
} else if (access == VM_ACCESS_RWX) {
prot = PROT_READ | PROT_WRITE | PROT_EXEC;
} else {
return std::unexpected{OsError::FAILED_TO_PROTECT};
}
return vm_protect(address, size, prot);
}
std::expected<uint32_t, OsError> vm_protect(uint8_t* address, size_t size, uint32_t protect) {
auto mbi = vm_query(address);
if (!mbi.has_value()) {
return std::unexpected{OsError::FAILED_TO_PROTECT};
}
uint32_t old_protect = 0;
if (mbi->access.read) {
old_protect |= PROT_READ;
}
if (mbi->access.write) {
old_protect |= PROT_WRITE;
}
if (mbi->access.execute) {
old_protect |= PROT_EXEC;
}
auto* addr = align_down(address, static_cast<size_t>(sysconf(_SC_PAGESIZE)));
if (mprotect(addr, size, static_cast<int>(protect)) == -1) {
return std::unexpected{OsError::FAILED_TO_PROTECT};
}
return old_protect;
}
std::expected<VmBasicInfo, OsError> vm_query(uint8_t* address) {
auto* maps = fopen("/proc/self/maps", "r");
if (maps == nullptr) {
return std::unexpected{OsError::FAILED_TO_QUERY};
}
char line[512];
unsigned long start;
unsigned long end;
char perms[5];
unsigned long offset;
int dev_major;
int dev_minor;
unsigned long inode;
char path[256];
unsigned long last_end =
reinterpret_cast<unsigned long>(system_info().min_address); // Track the end address of the last mapping.
auto addr = reinterpret_cast<unsigned long>(address);
std::optional<VmBasicInfo> info = std::nullopt;
while (fgets(line, sizeof(line), maps) != nullptr) {
path[0] = '\0';
sscanf(line, "%lx-%lx %4s %lx %x:%x %lu %255[^\n]", &start, &end, perms, &offset, &dev_major, &dev_minor,
&inode, path);
if (last_end < start && addr >= last_end && addr < start) {
info = std::make_optional<VmBasicInfo>(
{reinterpret_cast<uint8_t*>(last_end), start - last_end, VmAccess{}, true});
break;
}
last_end = end;
if (addr >= start && addr < end) {
info = std::make_optional<VmBasicInfo>({reinterpret_cast<uint8_t*>(start), end - start, VmAccess{}, false});
if (perms[0] == 'r') {
info->access.read = true;
}
if (perms[1] == 'w') {
info->access.write = true;
}
if (perms[2] == 'x') {
info->access.execute = true;
}
break;
}
}
fclose(maps);
if (!info.has_value()) {
return std::unexpected{OsError::FAILED_TO_QUERY};
}
return info.value();
}
bool vm_is_readable(uint8_t* address, [[maybe_unused]] size_t size) {
return vm_query(address).value_or(VmBasicInfo{}).access.read;
}
bool vm_is_writable(uint8_t* address, [[maybe_unused]] size_t size) {
return vm_query(address).value_or(VmBasicInfo{}).access.write;
}
bool vm_is_executable(uint8_t* address) {
return vm_query(address).value_or(VmBasicInfo{}).access.execute;
}
SystemInfo system_info() {
auto page_size = static_cast<uint32_t>(sysconf(_SC_PAGESIZE));
SystemInfo info{};
info.page_size = page_size;
info.allocation_granularity = page_size;
info.min_address = reinterpret_cast<uint8_t*>(0x10000);
info.max_address = reinterpret_cast<uint8_t*>(1ull << 47);
return info;
}
void trap_threads([[maybe_unused]] uint8_t* from, [[maybe_unused]] uint8_t* to, [[maybe_unused]] size_t len,
const std::function<void()>& run_fn) {
auto from_protect = vm_protect(from, len, VM_ACCESS_RWX).value_or(0);
auto to_protect = vm_protect(to, len, VM_ACCESS_RWX).value_or(0);
run_fn();
vm_protect(to, len, to_protect);
vm_protect(from, len, from_protect);
}
void fix_ip([[maybe_unused]] ThreadContext ctx, [[maybe_unused]] uint8_t* old_ip, [[maybe_unused]] uint8_t* new_ip) {
}
} // namespace safetyhook
#endif

342
external/safetyhook/src/os.windows.cpp vendored Normal file
View File

@@ -0,0 +1,342 @@
#include <map>
#include <memory>
#include <mutex>
#include "safetyhook/common.hpp"
#include "safetyhook/utility.hpp"
#if SAFETYHOOK_OS_WINDOWS
#define NOMINMAX
#if __has_include(<Windows.h>)
#include <Windows.h>
#elif __has_include(<windows.h>)
#include <windows.h>
#else
#error "Windows.h not found"
#endif
#include "safetyhook/os.hpp"
namespace safetyhook {
std::expected<uint8_t*, OsError> vm_allocate(uint8_t* address, size_t size, VmAccess access) {
DWORD protect = 0;
if (access == VM_ACCESS_R) {
protect = PAGE_READONLY;
} else if (access == VM_ACCESS_RW) {
protect = PAGE_READWRITE;
} else if (access == VM_ACCESS_RX) {
protect = PAGE_EXECUTE_READ;
} else if (access == VM_ACCESS_RWX) {
protect = PAGE_EXECUTE_READWRITE;
} else {
return std::unexpected{OsError::FAILED_TO_ALLOCATE};
}
auto* result = VirtualAlloc(address, size, MEM_COMMIT | MEM_RESERVE, protect);
if (result == nullptr) {
return std::unexpected{OsError::FAILED_TO_ALLOCATE};
}
return static_cast<uint8_t*>(result);
}
void vm_free(uint8_t* address) {
VirtualFree(address, 0, MEM_RELEASE);
}
std::expected<uint32_t, OsError> vm_protect(uint8_t* address, size_t size, VmAccess access) {
DWORD protect = 0;
if (access == VM_ACCESS_R) {
protect = PAGE_READONLY;
} else if (access == VM_ACCESS_RW) {
protect = PAGE_READWRITE;
} else if (access == VM_ACCESS_RX) {
protect = PAGE_EXECUTE_READ;
} else if (access == VM_ACCESS_RWX) {
protect = PAGE_EXECUTE_READWRITE;
} else {
return std::unexpected{OsError::FAILED_TO_PROTECT};
}
return vm_protect(address, size, protect);
}
std::expected<uint32_t, OsError> vm_protect(uint8_t* address, size_t size, uint32_t protect) {
DWORD old_protect = 0;
if (VirtualProtect(address, size, protect, &old_protect) == FALSE) {
return std::unexpected{OsError::FAILED_TO_PROTECT};
}
return old_protect;
}
std::expected<VmBasicInfo, OsError> vm_query(uint8_t* address) {
MEMORY_BASIC_INFORMATION mbi{};
auto result = VirtualQuery(address, &mbi, sizeof(mbi));
if (result == 0) {
return std::unexpected{OsError::FAILED_TO_QUERY};
}
VmAccess access{};
access.read = (mbi.Protect & (PAGE_READONLY | PAGE_READWRITE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) != 0;
access.write = (mbi.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)) != 0;
access.execute = (mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) != 0;
VmBasicInfo info{};
info.address = static_cast<uint8_t*>(mbi.AllocationBase);
info.size = mbi.RegionSize;
info.access = access;
info.is_free = mbi.State == MEM_FREE;
return info;
}
bool vm_is_readable(uint8_t* address, size_t size) {
return IsBadReadPtr(address, size) == FALSE;
}
bool vm_is_writable(uint8_t* address, size_t size) {
return IsBadWritePtr(address, size) == FALSE;
}
bool vm_is_executable(uint8_t* address) {
// Check if the address is in a valid module allowing us to potentially skip a heavier memory query.
HMODULE image{};
if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPTSTR>(address), &image) ||
image == nullptr) {
return vm_query(address).value_or(VmBasicInfo{}).access.execute;
}
// Just check if the section is executable.
const auto* image_base = reinterpret_cast<uint8_t*>(image);
const auto* dos_hdr = reinterpret_cast<const IMAGE_DOS_HEADER*>(image_base);
if (dos_hdr->e_magic != IMAGE_DOS_SIGNATURE) {
return vm_query(address).value_or(VmBasicInfo{}).access.execute;
}
const auto* nt_hdr = reinterpret_cast<const IMAGE_NT_HEADERS*>(image_base + dos_hdr->e_lfanew);
if (nt_hdr->Signature != IMAGE_NT_SIGNATURE) {
return vm_query(address).value_or(VmBasicInfo{}).access.execute;
}
const auto* section = IMAGE_FIRST_SECTION(nt_hdr);
for (auto i = 0; i < nt_hdr->FileHeader.NumberOfSections; ++i, ++section) {
if (address >= image_base + section->VirtualAddress &&
address < image_base + section->VirtualAddress + section->Misc.VirtualSize) {
return (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
}
}
return vm_query(address).value_or(VmBasicInfo{}).access.execute;
}
SystemInfo system_info() {
SystemInfo info{};
SYSTEM_INFO si{};
GetSystemInfo(&si);
info.page_size = si.dwPageSize;
info.allocation_granularity = si.dwAllocationGranularity;
info.min_address = static_cast<uint8_t*>(si.lpMinimumApplicationAddress);
info.max_address = static_cast<uint8_t*>(si.lpMaximumApplicationAddress);
return info;
}
struct TrapInfo {
uint8_t* from_page_start;
uint8_t* from_page_end;
uint8_t* from;
uint8_t* to_page_start;
uint8_t* to_page_end;
uint8_t* to;
size_t len;
};
class TrapManager final {
public:
static std::mutex mutex;
static std::unique_ptr<TrapManager> instance;
static bool is_destructed;
TrapManager() { m_trap_veh = AddVectoredExceptionHandler(1, trap_handler); }
~TrapManager() {
if (m_trap_veh != nullptr) {
RemoveVectoredExceptionHandler(m_trap_veh);
}
is_destructed = true;
}
TrapInfo* find_trap(uint8_t* address) {
auto search = std::find_if(m_traps.begin(), m_traps.end(), [address](auto& trap) {
return address >= trap.second.from && address < trap.second.from + trap.second.len;
});
if (search == m_traps.end()) {
return nullptr;
}
return &search->second;
}
TrapInfo* find_trap_page(uint8_t* address) {
auto search = std::find_if(m_traps.begin(), m_traps.end(), [address](auto& trap) {
return address >= trap.second.from_page_start && address < trap.second.from_page_end;
});
if (search != m_traps.end()) {
return &search->second;
}
search = std::find_if(m_traps.begin(), m_traps.end(), [address](auto& trap) {
return address >= trap.second.to_page_start && address < trap.second.to_page_end;
});
if (search != m_traps.end()) {
return &search->second;
}
return nullptr;
}
void add_trap(uint8_t* from, uint8_t* to, size_t len) {
TrapInfo info{};
info.from_page_start = align_down(from, 0x1000);
info.from_page_end = align_up(from + len, 0x1000);
info.from = from;
info.to_page_start = align_down(to, 0x1000);
info.to_page_end = align_up(to + len, 0x1000);
info.to = to;
info.len = len;
m_traps.insert_or_assign(from, std::move(info));
}
private:
std::map<uint8_t*, TrapInfo> m_traps;
PVOID m_trap_veh{};
static LONG CALLBACK trap_handler(PEXCEPTION_POINTERS exp) {
auto exception_code = exp->ExceptionRecord->ExceptionCode;
if (exception_code != EXCEPTION_ACCESS_VIOLATION) {
return EXCEPTION_CONTINUE_SEARCH;
}
std::scoped_lock lock{mutex};
auto* faulting_address = reinterpret_cast<uint8_t*>(exp->ExceptionRecord->ExceptionInformation[1]);
auto* trap = instance->find_trap(faulting_address);
if (trap == nullptr) {
if (instance->find_trap_page(faulting_address) != nullptr) {
return EXCEPTION_CONTINUE_EXECUTION;
} else {
return EXCEPTION_CONTINUE_SEARCH;
}
}
auto* ctx = exp->ContextRecord;
for (size_t i = 0; i < trap->len; i++) {
fix_ip(ctx, trap->from + i, trap->to + i);
}
return EXCEPTION_CONTINUE_EXECUTION;
}
};
std::mutex TrapManager::mutex;
std::unique_ptr<TrapManager> TrapManager::instance;
bool TrapManager::is_destructed = false;
void find_me() {
}
static std::mutex virtual_protect_mutex;
void trap_threads(uint8_t* from, uint8_t* to, size_t len, const std::function<void()>& run_fn) {
MEMORY_BASIC_INFORMATION find_me_mbi{};
MEMORY_BASIC_INFORMATION from_mbi{};
MEMORY_BASIC_INFORMATION to_mbi{};
VirtualQuery(reinterpret_cast<void*>(find_me), &find_me_mbi, sizeof(find_me_mbi));
VirtualQuery(from, &from_mbi, sizeof(from_mbi));
VirtualQuery(to, &to_mbi, sizeof(to_mbi));
auto new_protect = PAGE_READWRITE;
if (from_mbi.AllocationBase == find_me_mbi.AllocationBase || to_mbi.AllocationBase == find_me_mbi.AllocationBase) {
new_protect = PAGE_EXECUTE_READWRITE;
}
auto si = system_info();
auto* from_page_start = align_down(from, si.page_size);
auto* from_page_end = align_up(from + len, si.page_size);
auto* vp_start = reinterpret_cast<uint8_t*>(&VirtualProtect);
auto* vp_end = vp_start + 0x20;
if (!(from_page_end < vp_start || vp_end < from_page_start)) {
new_protect = PAGE_EXECUTE_READWRITE;
}
if (!TrapManager::is_destructed) {
std::scoped_lock lock{TrapManager::mutex};
if (TrapManager::instance == nullptr) {
TrapManager::instance = std::make_unique<TrapManager>();
}
TrapManager::instance->add_trap(from, to, len);
}
// Make sure we aren't working on a different address in the same memory page on a different thread.
std::scoped_lock vp_lock{virtual_protect_mutex};
DWORD from_protect;
DWORD to_protect;
VirtualProtect(from, len, new_protect, &from_protect);
VirtualProtect(to, len, new_protect, &to_protect);
if (run_fn) {
run_fn();
}
VirtualProtect(to, len, to_protect, &to_protect);
VirtualProtect(from, len, from_protect, &from_protect);
}
void fix_ip(ThreadContext thread_ctx, uint8_t* old_ip, uint8_t* new_ip) {
auto* ctx = reinterpret_cast<CONTEXT*>(thread_ctx);
#if SAFETYHOOK_ARCH_X86_64
auto ip = ctx->Rip;
#elif SAFETYHOOK_ARCH_X86_32
auto ip = ctx->Eip;
#endif
if (ip == reinterpret_cast<uintptr_t>(old_ip)) {
ip = reinterpret_cast<uintptr_t>(new_ip);
}
#if SAFETYHOOK_ARCH_X86_64
ctx->Rip = ip;
#elif SAFETYHOOK_ARCH_X86_32
ctx->Eip = ip;
#endif
}
} // namespace safetyhook
#endif

43
external/safetyhook/src/utility.cpp vendored Normal file
View File

@@ -0,0 +1,43 @@
#include "safetyhook/os.hpp"
#include "safetyhook/utility.hpp"
namespace safetyhook {
bool is_executable(uint8_t* address) {
return vm_is_executable(address);
}
UnprotectMemory::~UnprotectMemory() {
if (m_address != nullptr) {
vm_protect(m_address, m_size, m_original_protection);
}
}
UnprotectMemory::UnprotectMemory(UnprotectMemory&& other) noexcept {
*this = std::move(other);
}
UnprotectMemory& UnprotectMemory::operator=(UnprotectMemory&& other) noexcept {
if (this != &other) {
m_address = other.m_address;
m_size = other.m_size;
m_original_protection = other.m_original_protection;
other.m_address = nullptr;
other.m_size = 0;
other.m_original_protection = 0;
}
return *this;
}
std::optional<UnprotectMemory> unprotect(uint8_t* address, size_t size) {
auto old_protection = vm_protect(address, size, VM_ACCESS_RWX);
if (!old_protection) {
return std::nullopt;
}
return UnprotectMemory{address, size, old_protection.value()};
}
} // namespace safetyhook

143
external/safetyhook/src/vmt_hook.cpp vendored Normal file
View File

@@ -0,0 +1,143 @@
#include "safetyhook/os.hpp"
#include "safetyhook/vmt_hook.hpp"
namespace safetyhook {
VmHook::VmHook(VmHook&& other) noexcept {
*this = std::move(other);
}
VmHook& VmHook::operator=(VmHook&& other) noexcept {
destroy();
m_original_vm = other.m_original_vm;
m_new_vm = other.m_new_vm;
m_vmt_entry = other.m_vmt_entry;
m_new_vmt_allocation = std::move(other.m_new_vmt_allocation);
other.m_original_vm = nullptr;
other.m_new_vm = nullptr;
other.m_vmt_entry = nullptr;
return *this;
}
VmHook::~VmHook() {
destroy();
}
void VmHook::reset() {
*this = {};
}
void VmHook::destroy() {
if (m_original_vm != nullptr) {
*m_vmt_entry = m_original_vm;
m_original_vm = nullptr;
m_new_vm = nullptr;
m_vmt_entry = nullptr;
m_new_vmt_allocation.reset();
}
}
std::expected<VmtHook, VmtHook::Error> VmtHook::create(void* object) {
VmtHook hook{};
const auto original_vmt = *reinterpret_cast<uint8_t***>(object);
hook.m_objects.emplace(object, original_vmt);
// Count the number of virtual method pointers. We start at one to account for the RTTI pointer.
auto num_vmt_entries = 1;
for (auto vm = original_vmt; is_executable(*vm); ++vm) {
++num_vmt_entries;
}
// Allocate memory for the new VMT.
auto allocation = Allocator::global()->allocate(num_vmt_entries * sizeof(uint8_t*));
if (!allocation) {
return std::unexpected{Error::bad_allocation(allocation.error())};
}
hook.m_new_vmt_allocation = std::make_shared<Allocation>(std::move(*allocation));
hook.m_new_vmt = reinterpret_cast<uint8_t**>(hook.m_new_vmt_allocation->data());
// Copy pointer to RTTI.
hook.m_new_vmt[0] = original_vmt[-1];
// Copy virtual method pointers.
for (auto i = 0; i < num_vmt_entries - 1; ++i) {
hook.m_new_vmt[i + 1] = original_vmt[i];
}
*reinterpret_cast<uint8_t***>(object) = &hook.m_new_vmt[1];
return hook;
}
VmtHook::VmtHook(VmtHook&& other) noexcept {
*this = std::move(other);
}
VmtHook& VmtHook::operator=(VmtHook&& other) noexcept {
destroy();
m_objects = std::move(other.m_objects);
m_new_vmt_allocation = std::move(other.m_new_vmt_allocation);
m_new_vmt = other.m_new_vmt;
other.m_new_vmt = nullptr;
return *this;
}
VmtHook::~VmtHook() {
destroy();
}
void VmtHook::apply(void* object) {
m_objects.emplace(object, *reinterpret_cast<uint8_t***>(object));
*reinterpret_cast<uint8_t***>(object) = &m_new_vmt[1];
}
void VmtHook::remove(void* object) {
const auto search = m_objects.find(object);
if (search == m_objects.end()) {
return;
}
const auto original_vmt = search->second;
if (!vm_is_writable(reinterpret_cast<uint8_t*>(object), sizeof(void*))) {
m_objects.erase(search);
return;
}
if (*reinterpret_cast<uint8_t***>(object) != &m_new_vmt[1]) {
m_objects.erase(search);
return;
}
*reinterpret_cast<uint8_t***>(object) = original_vmt;
m_objects.erase(search);
}
void VmtHook::reset() {
*this = {};
}
void VmtHook::destroy() {
for (const auto [object, original_vmt] : m_objects) {
if (!vm_is_writable(reinterpret_cast<uint8_t*>(object), sizeof(void*))) {
continue;
}
if (*reinterpret_cast<uint8_t***>(object) != &m_new_vmt[1]) {
continue;
}
*reinterpret_cast<uint8_t***>(object) = original_vmt;
}
m_objects.clear();
m_new_vmt_allocation.reset();
m_new_vmt = nullptr;
}
} // namespace safetyhook