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

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