update arxan component
includes anti anti debug
This commit is contained in:
parent
24a8c68abf
commit
820aa6788e
@ -1,151 +0,0 @@
|
|||||||
#include <std_include.hpp>
|
|
||||||
#include "loader/component_loader.hpp"
|
|
||||||
|
|
||||||
#include "arxan.hpp"
|
|
||||||
#include "scheduler.hpp"
|
|
||||||
|
|
||||||
#include "game/game.hpp"
|
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
|
||||||
|
|
||||||
namespace arxan
|
|
||||||
{
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
utils::hook::detour nt_close_hook;
|
|
||||||
utils::hook::detour nt_query_information_process_hook;
|
|
||||||
|
|
||||||
NTSTATUS WINAPI nt_query_information_process_stub(const HANDLE handle, const PROCESSINFOCLASS info_class,
|
|
||||||
const PVOID info,
|
|
||||||
const ULONG info_length, const PULONG ret_length)
|
|
||||||
{
|
|
||||||
auto* orig = static_cast<decltype(NtQueryInformationProcess)*>(nt_query_information_process_hook.
|
|
||||||
get_original());
|
|
||||||
const auto status = orig(handle, info_class, info, info_length, ret_length);
|
|
||||||
|
|
||||||
if (NT_SUCCESS(status))
|
|
||||||
{
|
|
||||||
if (info_class == ProcessBasicInformation)
|
|
||||||
{
|
|
||||||
static DWORD explorer_pid = 0;
|
|
||||||
if (!explorer_pid)
|
|
||||||
{
|
|
||||||
auto* const shell_window = GetShellWindow();
|
|
||||||
GetWindowThreadProcessId(shell_window, &explorer_pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
static_cast<PPROCESS_BASIC_INFORMATION>(info)->Reserved3 = PVOID(DWORD64(explorer_pid));
|
|
||||||
}
|
|
||||||
else if (info_class == 30) // ProcessDebugObjectHandle
|
|
||||||
{
|
|
||||||
*static_cast<HANDLE*>(info) = nullptr;
|
|
||||||
|
|
||||||
return 0xC0000353;
|
|
||||||
}
|
|
||||||
else if (info_class == 7) // ProcessDebugPort
|
|
||||||
{
|
|
||||||
*static_cast<HANDLE*>(info) = nullptr;
|
|
||||||
}
|
|
||||||
else if (info_class == 31)
|
|
||||||
{
|
|
||||||
*static_cast<ULONG*>(info) = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
NTSTATUS NTAPI nt_close_stub(const HANDLE handle)
|
|
||||||
{
|
|
||||||
char info[16];
|
|
||||||
if (NtQueryObject(handle, OBJECT_INFORMATION_CLASS(4), &info, 2, nullptr) >= 0 && size_t(handle) != 0x12345)
|
|
||||||
{
|
|
||||||
auto* orig = static_cast<decltype(NtClose)*>(nt_close_hook.get_original());
|
|
||||||
return orig(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return STATUS_INVALID_HANDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS info)
|
|
||||||
{
|
|
||||||
if (info->ExceptionRecord->ExceptionCode == STATUS_INVALID_HANDLE)
|
|
||||||
{
|
|
||||||
return EXCEPTION_CONTINUE_EXECUTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
void hide_being_debugged()
|
|
||||||
{
|
|
||||||
auto* const peb = PPEB(__readgsqword(0x60));
|
|
||||||
peb->BeingDebugged = false;
|
|
||||||
*reinterpret_cast<PDWORD>(LPSTR(peb) + 0xBC) &= ~0x70;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove_hardware_breakpoints()
|
|
||||||
{
|
|
||||||
CONTEXT context;
|
|
||||||
ZeroMemory(&context, sizeof(context));
|
|
||||||
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
|
||||||
|
|
||||||
auto* const thread = GetCurrentThread();
|
|
||||||
GetThreadContext(thread, &context);
|
|
||||||
|
|
||||||
context.Dr0 = 0;
|
|
||||||
context.Dr1 = 0;
|
|
||||||
context.Dr2 = 0;
|
|
||||||
context.Dr3 = 0;
|
|
||||||
context.Dr6 = 0;
|
|
||||||
context.Dr7 = 0;
|
|
||||||
|
|
||||||
SetThreadContext(thread, &context);
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL WINAPI set_thread_context_stub(const HANDLE thread, CONTEXT* context)
|
|
||||||
{
|
|
||||||
return SetThreadContext(thread, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class component final : public component_interface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void* load_import(const std::string& library, const std::string& function) override
|
|
||||||
{
|
|
||||||
if (function == "SetThreadContext")
|
|
||||||
{
|
|
||||||
//return set_thread_context_stub;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void post_load() override
|
|
||||||
{
|
|
||||||
hide_being_debugged();
|
|
||||||
scheduler::loop(hide_being_debugged, scheduler::pipeline::async);
|
|
||||||
|
|
||||||
const utils::nt::library ntdll("ntdll.dll");
|
|
||||||
nt_close_hook.create(ntdll.get_proc<void*>("NtClose"), nt_close_stub);
|
|
||||||
nt_query_information_process_hook.create(ntdll.get_proc<void*>("NtQueryInformationProcess"),
|
|
||||||
nt_query_information_process_stub);
|
|
||||||
// https://www.geoffchappell.com/studies/windows/win32/ntdll/api/index.htm
|
|
||||||
AddVectoredExceptionHandler(1, exception_filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
void post_unpack() override
|
|
||||||
{
|
|
||||||
// cba to implement sp, not sure if it's even needed
|
|
||||||
if (game::environment::is_sp())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
REGISTER_COMPONENT(arxan::component)
|
|
587
src/client/component/arxan/arxan.cpp
Normal file
587
src/client/component/arxan/arxan.cpp
Normal file
@ -0,0 +1,587 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
|
#include "game/game.hpp"
|
||||||
|
|
||||||
|
#include "component/game_module.hpp"
|
||||||
|
#include "component/scheduler.hpp"
|
||||||
|
|
||||||
|
#include <utils/hook.hpp>
|
||||||
|
#include <utils/string.hpp>
|
||||||
|
|
||||||
|
#include "integrity.hpp"
|
||||||
|
#include "breakpoints.hpp"
|
||||||
|
|
||||||
|
#define PRECOMPUTED_INTEGRITY_CHECKS
|
||||||
|
#define PRECOMPUTED_BREAKPOINTS
|
||||||
|
|
||||||
|
#define ProcessDebugPort 7
|
||||||
|
#define ProcessDebugObjectHandle 30
|
||||||
|
#define ProcessDebugFlags 31
|
||||||
|
|
||||||
|
namespace arxan
|
||||||
|
{
|
||||||
|
namespace integrity
|
||||||
|
{
|
||||||
|
const std::vector<std::pair<uint8_t*, size_t>>& get_text_sections()
|
||||||
|
{
|
||||||
|
static const std::vector<std::pair<uint8_t*, size_t>> text = []
|
||||||
|
{
|
||||||
|
std::vector<std::pair<uint8_t*, size_t>> texts{};
|
||||||
|
|
||||||
|
const utils::nt::library game{ game_module::get_game_module() };
|
||||||
|
for (const auto& section : game.get_section_headers())
|
||||||
|
{
|
||||||
|
if (section->Characteristics & IMAGE_SCN_MEM_EXECUTE)
|
||||||
|
{
|
||||||
|
texts.emplace_back(game.get_ptr() + section->VirtualAddress, section->Misc.VirtualSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return texts;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_in_texts(const uint64_t addr)
|
||||||
|
{
|
||||||
|
const auto& texts = get_text_sections();
|
||||||
|
for (const auto& text : texts)
|
||||||
|
{
|
||||||
|
const auto start = reinterpret_cast<ULONG_PTR>(text.first);
|
||||||
|
if (addr >= start && addr <= (start + text.second))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_in_texts(const void* addr)
|
||||||
|
{
|
||||||
|
return is_in_texts(reinterpret_cast<uint64_t>(addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct integrity_handler_context
|
||||||
|
{
|
||||||
|
uint32_t* computed_checksum;
|
||||||
|
uint32_t* original_checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool is_on_stack(uint8_t* stack_frame, const void* pointer)
|
||||||
|
{
|
||||||
|
const auto stack_value = reinterpret_cast<uint64_t>(stack_frame);
|
||||||
|
const auto pointer_value = reinterpret_cast<uint64_t>(pointer);
|
||||||
|
|
||||||
|
const auto diff = static_cast<int64_t>(stack_value - pointer_value);
|
||||||
|
return std::abs(diff) < 0x1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty trashy, but working, heuristic to search the integrity handler context
|
||||||
|
bool is_handler_context(uint8_t* stack_frame, const uint32_t computed_checksum, const uint32_t frame_offset)
|
||||||
|
{
|
||||||
|
const auto* potential_context = reinterpret_cast<integrity_handler_context*>(stack_frame + frame_offset);
|
||||||
|
return is_on_stack(stack_frame, potential_context->computed_checksum)
|
||||||
|
&& *potential_context->computed_checksum == computed_checksum
|
||||||
|
&& is_in_texts(potential_context->original_checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
integrity_handler_context* search_handler_context(uint8_t* stack_frame, const uint32_t computed_checksum)
|
||||||
|
{
|
||||||
|
for (uint32_t frame_offset = 0; frame_offset < 0x90; frame_offset += 8)
|
||||||
|
{
|
||||||
|
if (is_handler_context(stack_frame, computed_checksum, frame_offset))
|
||||||
|
{
|
||||||
|
return reinterpret_cast<integrity_handler_context*>(stack_frame + frame_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t adjust_integrity_checksum(const uint64_t return_address, uint8_t* stack_frame,
|
||||||
|
const uint32_t current_checksum)
|
||||||
|
{
|
||||||
|
[[maybe_unused]] const auto handler_address = return_address - 5;
|
||||||
|
const auto* context = search_handler_context(stack_frame, current_checksum);
|
||||||
|
|
||||||
|
if (!context)
|
||||||
|
{
|
||||||
|
OutputDebugStringA(utils::string::va("Unable to find frame offset for: %llX", return_address));
|
||||||
|
return current_checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto correct_checksum = *context->original_checksum;
|
||||||
|
*context->computed_checksum = correct_checksum;
|
||||||
|
|
||||||
|
if (current_checksum != correct_checksum)
|
||||||
|
{
|
||||||
|
#ifdef DEV_BUILD
|
||||||
|
OutputDebugStringA(utils::string::va("Adjusting checksum (%llX): %X -> %X", handler_address,
|
||||||
|
current_checksum, correct_checksum));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return correct_checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_intact_basic_block_integrity_check(void* address)
|
||||||
|
{
|
||||||
|
const auto game_address = reinterpret_cast<uint64_t>(address);
|
||||||
|
constexpr auto inst_len = 3;
|
||||||
|
|
||||||
|
const auto next_inst_addr = game_address + inst_len;
|
||||||
|
const auto next_inst = *reinterpret_cast<uint32_t*>(next_inst_addr);
|
||||||
|
|
||||||
|
if ((next_inst & 0xFF00FFFF) != 0xFF004583)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(utils::string::va("Unable to patch intact basic block: %llX", game_address));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto other_frame_offset = static_cast<uint8_t>(next_inst >> 16);
|
||||||
|
static const auto stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||||
|
{
|
||||||
|
a.push(rax);
|
||||||
|
|
||||||
|
a.mov(rax, qword_ptr(rsp, 8));
|
||||||
|
a.sub(rax, 2); // Skip the push we inserted
|
||||||
|
|
||||||
|
a.push(rax);
|
||||||
|
a.pushad64();
|
||||||
|
|
||||||
|
a.mov(r8, qword_ptr(rsp, 0x88));
|
||||||
|
a.mov(rcx, rax);
|
||||||
|
a.mov(rdx, rbp);
|
||||||
|
a.call_aligned(adjust_integrity_checksum);
|
||||||
|
|
||||||
|
a.mov(qword_ptr(rsp, 0x80), rax);
|
||||||
|
|
||||||
|
a.popad64();
|
||||||
|
a.pop(rax);
|
||||||
|
|
||||||
|
a.add(rsp, 8);
|
||||||
|
|
||||||
|
a.mov(dword_ptr(rdx, rcx, 4), eax);
|
||||||
|
|
||||||
|
a.pop(rax); // return addr
|
||||||
|
a.xchg(rax, qword_ptr(rsp)); // switch with push
|
||||||
|
|
||||||
|
a.add(dword_ptr(rbp, rax), 0xFFFFFFFF);
|
||||||
|
|
||||||
|
a.mov(rax, dword_ptr(rdx, rcx, 4)); // restore rax
|
||||||
|
|
||||||
|
a.ret();
|
||||||
|
});
|
||||||
|
|
||||||
|
// push other_frame_offset
|
||||||
|
utils::hook::set<uint16_t>(game_address, static_cast<uint16_t>(0x6A | (other_frame_offset << 8)));
|
||||||
|
utils::hook::call(game_address + 2, stub);
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_split_basic_block_integrity_check(void* address)
|
||||||
|
{
|
||||||
|
const auto game_address = reinterpret_cast<uint64_t>(address);
|
||||||
|
constexpr auto inst_len = 3;
|
||||||
|
|
||||||
|
const auto next_inst_addr = game_address + inst_len;
|
||||||
|
|
||||||
|
if (*reinterpret_cast<uint8_t*>(next_inst_addr) != 0xE9)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(utils::string::va("Unable to patch split basic block: %llX", game_address));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto jump_target = utils::hook::extract<void*>(reinterpret_cast<void*>(next_inst_addr + 1));
|
||||||
|
const auto stub = utils::hook::assemble([jump_target](utils::hook::assembler& a)
|
||||||
|
{
|
||||||
|
a.push(rax);
|
||||||
|
|
||||||
|
a.mov(rax, qword_ptr(rsp, 8));
|
||||||
|
a.push(rax);
|
||||||
|
|
||||||
|
a.pushad64();
|
||||||
|
|
||||||
|
a.mov(r8, qword_ptr(rsp, 0x88));
|
||||||
|
a.mov(rcx, rax);
|
||||||
|
a.mov(rdx, rbp);
|
||||||
|
a.call_aligned(adjust_integrity_checksum);
|
||||||
|
|
||||||
|
a.mov(qword_ptr(rsp, 0x80), rax);
|
||||||
|
|
||||||
|
a.popad64();
|
||||||
|
a.pop(rax);
|
||||||
|
|
||||||
|
a.add(rsp, 8);
|
||||||
|
|
||||||
|
a.mov(dword_ptr(rdx, rcx, 4), eax);
|
||||||
|
|
||||||
|
a.add(rsp, 8);
|
||||||
|
|
||||||
|
a.jmp(jump_target);
|
||||||
|
});
|
||||||
|
|
||||||
|
utils::hook::call(game_address, stub);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PRECOMPUTED_INTEGRITY_CHECKS
|
||||||
|
void search_and_patch_integrity_checks_precomputed()
|
||||||
|
{
|
||||||
|
if (game::environment::is_sp())
|
||||||
|
{
|
||||||
|
for (const auto i : sp::intact_integrity_check_blocks)
|
||||||
|
{
|
||||||
|
patch_intact_basic_block_integrity_check(reinterpret_cast<void*>(i));
|
||||||
|
}
|
||||||
|
for (const auto i : sp::split_integrity_check_blocks)
|
||||||
|
{
|
||||||
|
patch_split_basic_block_integrity_check(reinterpret_cast<void*>(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (const auto i : mp::intact_integrity_check_blocks)
|
||||||
|
{
|
||||||
|
patch_intact_basic_block_integrity_check(reinterpret_cast<void*>(i));
|
||||||
|
}
|
||||||
|
for (const auto i : mp::split_integrity_check_blocks)
|
||||||
|
{
|
||||||
|
patch_split_basic_block_integrity_check(reinterpret_cast<void*>(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void search_and_patch_integrity_checks()
|
||||||
|
{
|
||||||
|
#ifdef PRECOMPUTED_INTEGRITY_CHECKS
|
||||||
|
assert(game::base_address == 0x140000000);
|
||||||
|
search_and_patch_integrity_checks_precomputed();
|
||||||
|
#else
|
||||||
|
const auto intact_results = "89 04 8A 83 45 ? FF"_sig;
|
||||||
|
const auto split_results = "89 04 8A E9"_sig;
|
||||||
|
|
||||||
|
for (auto* i : intact_results)
|
||||||
|
{
|
||||||
|
patch_intact_basic_block_integrity_check(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* i : split_results)
|
||||||
|
{
|
||||||
|
patch_split_basic_block_integrity_check(i);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using namespace integrity;
|
||||||
|
|
||||||
|
namespace anti_debug
|
||||||
|
{
|
||||||
|
utils::hook::detour nt_close_hook;
|
||||||
|
utils::hook::detour nt_query_information_process_hook;
|
||||||
|
|
||||||
|
NTSTATUS WINAPI nt_query_information_process_stub(const HANDLE handle, const PROCESSINFOCLASS info_class,
|
||||||
|
const PVOID info,
|
||||||
|
const ULONG info_length, const PULONG ret_length)
|
||||||
|
{
|
||||||
|
auto* orig = static_cast<decltype(NtQueryInformationProcess)*>(nt_query_information_process_hook.get_original());
|
||||||
|
auto status = orig(handle, info_class, info, info_length, ret_length);
|
||||||
|
|
||||||
|
if (NT_SUCCESS(status))
|
||||||
|
{
|
||||||
|
if (info_class == ProcessDebugObjectHandle)
|
||||||
|
{
|
||||||
|
*static_cast<HANDLE*>(info) = nullptr;
|
||||||
|
return static_cast<LONG>(0xC0000353);
|
||||||
|
}
|
||||||
|
else if (info_class == ProcessDebugPort)
|
||||||
|
{
|
||||||
|
*static_cast<HANDLE*>(info) = nullptr;
|
||||||
|
}
|
||||||
|
else if (info_class == ProcessDebugFlags)
|
||||||
|
{
|
||||||
|
*static_cast<ULONG*>(info) = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS NTAPI nt_close_stub(const HANDLE handle)
|
||||||
|
{
|
||||||
|
char info[16];
|
||||||
|
if (NtQueryObject(handle, OBJECT_INFORMATION_CLASS(4), &info, 2, nullptr) >= 0 && size_t(handle) != 0x12345)
|
||||||
|
{
|
||||||
|
auto* orig = static_cast<decltype(NtClose)*>(nt_close_hook.get_original());
|
||||||
|
return orig(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return STATUS_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hide_being_debugged()
|
||||||
|
{
|
||||||
|
auto* const peb = PPEB(__readgsqword(0x60));
|
||||||
|
peb->BeingDebugged = false;
|
||||||
|
*reinterpret_cast<PDWORD>(LPSTR(peb) + 0xBC) &= ~0x70; // NtGlobalFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_hardware_breakpoints()
|
||||||
|
{
|
||||||
|
CONTEXT context;
|
||||||
|
ZeroMemory(&context, sizeof(context));
|
||||||
|
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||||
|
|
||||||
|
auto* const thread = GetCurrentThread();
|
||||||
|
GetThreadContext(thread, &context);
|
||||||
|
|
||||||
|
context.Dr0 = 0;
|
||||||
|
context.Dr1 = 0;
|
||||||
|
context.Dr2 = 0;
|
||||||
|
context.Dr3 = 0;
|
||||||
|
context.Dr6 = 0;
|
||||||
|
context.Dr7 = 0;
|
||||||
|
|
||||||
|
SetThreadContext(thread, &context);
|
||||||
|
}
|
||||||
|
|
||||||
|
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS info)
|
||||||
|
{
|
||||||
|
if (info->ExceptionRecord->ExceptionCode == STATUS_INVALID_HANDLE)
|
||||||
|
{
|
||||||
|
return EXCEPTION_CONTINUE_EXECUTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL WINAPI set_thread_context_stub(const HANDLE thread, CONTEXT* context)
|
||||||
|
{
|
||||||
|
if (context->ContextFlags == CONTEXT_DEBUG_REGISTERS)
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SetThreadContext(thread, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum dbg_funcs_e
|
||||||
|
{
|
||||||
|
DbgBreakPoint,
|
||||||
|
DbgUserBreakPoint,
|
||||||
|
DbgUiConnectToDbg,
|
||||||
|
DbgUiContinue,
|
||||||
|
DbgUiConvertStateChangeStructure,
|
||||||
|
DbgUiDebugActiveProcess,
|
||||||
|
DbgUiGetThreadDebugObject,
|
||||||
|
DbgUiIssueRemoteBreakin,
|
||||||
|
DbgUiRemoteBreakin,
|
||||||
|
DbgUiSetThreadDebugObject,
|
||||||
|
DbgUiStopDebugging,
|
||||||
|
DbgUiWaitStateChange,
|
||||||
|
DbgPrintReturnControlC,
|
||||||
|
DbgPrompt,
|
||||||
|
DBG_FUNCS_COUNT,
|
||||||
|
};
|
||||||
|
const char* dbg_funcs_names[] =
|
||||||
|
{
|
||||||
|
"DbgBreakPoint",
|
||||||
|
"DbgUserBreakPoint",
|
||||||
|
"DbgUiConnectToDbg",
|
||||||
|
"DbgUiContinue",
|
||||||
|
"DbgUiConvertStateChangeStructure",
|
||||||
|
"DbgUiDebugActiveProcess",
|
||||||
|
"DbgUiGetThreadDebugObject",
|
||||||
|
"DbgUiIssueRemoteBreakin",
|
||||||
|
"DbgUiRemoteBreakin",
|
||||||
|
"DbgUiSetThreadDebugObject",
|
||||||
|
"DbgUiStopDebugging",
|
||||||
|
"DbgUiWaitStateChange",
|
||||||
|
"DbgPrintReturnControlC",
|
||||||
|
"DbgPrompt",
|
||||||
|
};
|
||||||
|
struct dbg_func_bytes_s
|
||||||
|
{
|
||||||
|
std::uint8_t buffer[15];
|
||||||
|
};
|
||||||
|
dbg_func_bytes_s dbg_func_bytes[DBG_FUNCS_COUNT];
|
||||||
|
void* dbg_func_procs[DBG_FUNCS_COUNT]{};
|
||||||
|
|
||||||
|
void store_debug_functions()
|
||||||
|
{
|
||||||
|
const utils::nt::library ntdll("ntdll.dll");
|
||||||
|
|
||||||
|
for (auto i = 0; i < DBG_FUNCS_COUNT; i++)
|
||||||
|
{
|
||||||
|
dbg_func_procs[i] = ntdll.get_proc<void*>(dbg_funcs_names[i]);
|
||||||
|
memcpy(dbg_func_bytes[i].buffer, dbg_func_procs[i], sizeof(dbg_func_bytes[i].buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void restore_debug_functions()
|
||||||
|
{
|
||||||
|
for (auto i = 0; i < DBG_FUNCS_COUNT; i++)
|
||||||
|
{
|
||||||
|
utils::hook::copy(dbg_func_procs[i], dbg_func_bytes[i].buffer, sizeof(dbg_func_bytes[i].buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace breakpoints
|
||||||
|
{
|
||||||
|
std::unordered_map<PVOID, void*> handle_handler;
|
||||||
|
|
||||||
|
void fake_breakpoint_trigger(void* address, _CONTEXT* fake_context)
|
||||||
|
{
|
||||||
|
_EXCEPTION_POINTERS fake_info{};
|
||||||
|
_EXCEPTION_RECORD fake_record{};
|
||||||
|
fake_info.ExceptionRecord = &fake_record;
|
||||||
|
fake_info.ContextRecord = fake_context;
|
||||||
|
|
||||||
|
fake_record.ExceptionAddress = reinterpret_cast<void*>(reinterpret_cast<std::uint64_t>(address) + 3);
|
||||||
|
fake_record.ExceptionCode = EXCEPTION_BREAKPOINT;
|
||||||
|
|
||||||
|
for (auto handler : handle_handler)
|
||||||
|
{
|
||||||
|
if (handler.second)
|
||||||
|
{
|
||||||
|
auto result = utils::hook::invoke<LONG>(handler.second, &fake_info);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
memset(fake_context, 0, sizeof(_CONTEXT));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_int2d_trap(void* address)
|
||||||
|
{
|
||||||
|
const auto game_address = reinterpret_cast<std::uint64_t>(address);
|
||||||
|
|
||||||
|
const auto jump_target = utils::hook::extract<void*>(reinterpret_cast<void*>(game_address + 3));
|
||||||
|
|
||||||
|
_CONTEXT* fake_context = new _CONTEXT{};
|
||||||
|
const auto stub = utils::hook::assemble([address, jump_target, fake_context](utils::hook::assembler& a)
|
||||||
|
{
|
||||||
|
a.push(rcx);
|
||||||
|
a.mov(rcx, fake_context);
|
||||||
|
a.call_aligned(RtlCaptureContext);
|
||||||
|
a.pop(rcx);
|
||||||
|
|
||||||
|
a.pushad64();
|
||||||
|
a.mov(rcx, address);
|
||||||
|
a.mov(rdx, fake_context);
|
||||||
|
a.call_aligned(fake_breakpoint_trigger);
|
||||||
|
a.popad64();
|
||||||
|
|
||||||
|
a.jmp(jump_target);
|
||||||
|
});
|
||||||
|
|
||||||
|
utils::hook::nop(game_address, 7);
|
||||||
|
utils::hook::jump(game_address, stub, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PRECOMPUTED_BREAKPOINTS
|
||||||
|
void patch_breakpoints_precomputed()
|
||||||
|
{
|
||||||
|
for (const auto i : mp::int2d_breakpoint_addresses)
|
||||||
|
{
|
||||||
|
patch_int2d_trap(reinterpret_cast<void*>(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void patch_breakpoints()
|
||||||
|
{
|
||||||
|
static bool once = false;
|
||||||
|
if (once)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
once = true;
|
||||||
|
|
||||||
|
// sp has no breakpoints
|
||||||
|
if (game::environment::is_sp())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PRECOMPUTED_BREAKPOINTS
|
||||||
|
assert(game::base_address == 0x140000000);
|
||||||
|
patch_breakpoints_precomputed();
|
||||||
|
#else
|
||||||
|
const auto int2d_results = utils::hook::signature("CD 2D E9 ? ? ? ?", game_module::get_game_module()).process();
|
||||||
|
for (auto* i : int2d_results)
|
||||||
|
{
|
||||||
|
patch_int2d_trap(i);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID WINAPI add_vectored_exception_handler_stub(ULONG first, PVECTORED_EXCEPTION_HANDLER handler)
|
||||||
|
{
|
||||||
|
breakpoints::patch_breakpoints();
|
||||||
|
|
||||||
|
auto handle = AddVectoredExceptionHandler(first, handler);
|
||||||
|
handle_handler[handle] = handler;
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG WINAPI remove_vectored_exception_handler_stub(PVOID handle)
|
||||||
|
{
|
||||||
|
handle_handler[handle] = nullptr;
|
||||||
|
return RemoveVectoredExceptionHandler(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using namespace anti_debug;
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void* load_import(const std::string& library, const std::string& function) override
|
||||||
|
{
|
||||||
|
if (function == "SetThreadContext")
|
||||||
|
{
|
||||||
|
return set_thread_context_stub;
|
||||||
|
}
|
||||||
|
else if (function == "AddVectoredExceptionHandler")
|
||||||
|
{
|
||||||
|
return breakpoints::add_vectored_exception_handler_stub;
|
||||||
|
}
|
||||||
|
else if (function == "RemoveVectoredExceptionHandler")
|
||||||
|
{
|
||||||
|
return breakpoints::remove_vectored_exception_handler_stub;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void post_load() override
|
||||||
|
{
|
||||||
|
remove_hardware_breakpoints();
|
||||||
|
hide_being_debugged();
|
||||||
|
scheduler::loop(hide_being_debugged, scheduler::pipeline::async);
|
||||||
|
store_debug_functions();
|
||||||
|
|
||||||
|
const utils::nt::library ntdll("ntdll.dll");
|
||||||
|
nt_close_hook.create(ntdll.get_proc<void*>("NtClose"), nt_close_stub);
|
||||||
|
|
||||||
|
const auto nt_query_information_process = ntdll.get_proc<void*>("NtQueryInformationProcess");
|
||||||
|
nt_query_information_process_hook.create(nt_query_information_process, nt_query_information_process_stub);
|
||||||
|
nt_query_information_process_hook.move();
|
||||||
|
|
||||||
|
AddVectoredExceptionHandler(1, exception_filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
remove_hardware_breakpoints();
|
||||||
|
search_and_patch_integrity_checks();
|
||||||
|
restore_debug_functions();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_COMPONENT(arxan::component)
|
165
src/client/component/arxan/breakpoints.hpp
Normal file
165
src/client/component/arxan/breakpoints.hpp
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace mp
|
||||||
|
{
|
||||||
|
constexpr uint64_t int2d_breakpoint_addresses[] =
|
||||||
|
{
|
||||||
|
0x1400837C9,
|
||||||
|
0x140088C28,
|
||||||
|
0x140089CB9,
|
||||||
|
0x1400922E9,
|
||||||
|
0x140119A39,
|
||||||
|
0x140199679,
|
||||||
|
0x1401CBB59,
|
||||||
|
0x140205DD9,
|
||||||
|
0x140244A39,
|
||||||
|
0x1402BCFC9,
|
||||||
|
0x140346C39,
|
||||||
|
0x14037E2A9,
|
||||||
|
0x1404EB459,
|
||||||
|
0x1404EB7C9,
|
||||||
|
0x1405FCD09,
|
||||||
|
0x140612AB3,
|
||||||
|
0x140622D2E,
|
||||||
|
0x1407D82DB,
|
||||||
|
0x1407DC18C,
|
||||||
|
0x140847869,
|
||||||
|
0x140867E53,
|
||||||
|
0x15143E836,
|
||||||
|
0x15144B18C,
|
||||||
|
0x15144BE27,
|
||||||
|
0x1515E11E0,
|
||||||
|
0x1515F18C9,
|
||||||
|
0x1515F2ADE,
|
||||||
|
0x15161E477,
|
||||||
|
0x15162189F,
|
||||||
|
0x151623FC5,
|
||||||
|
0x151637C0D,
|
||||||
|
0x151640170,
|
||||||
|
0x151641309,
|
||||||
|
0x1516514DA,
|
||||||
|
0x1516528BC,
|
||||||
|
0x151653351,
|
||||||
|
0x15165627D,
|
||||||
|
0x151656436,
|
||||||
|
0x151683F6C,
|
||||||
|
0x151686F24,
|
||||||
|
0x1516881C5,
|
||||||
|
0x151695BAD,
|
||||||
|
0x15169714D,
|
||||||
|
0x1516977E9,
|
||||||
|
0x1516B6978,
|
||||||
|
0x1516B933F,
|
||||||
|
0x1516D75C7,
|
||||||
|
0x1516D8B61,
|
||||||
|
0x1516DDA42,
|
||||||
|
0x1517064EC,
|
||||||
|
0x151706519,
|
||||||
|
0x151712445,
|
||||||
|
0x151735FFA,
|
||||||
|
0x151737872,
|
||||||
|
0x151738660,
|
||||||
|
0x151748404,
|
||||||
|
0x151748DB3,
|
||||||
|
0x15174D408,
|
||||||
|
0x15174F7D2,
|
||||||
|
0x151763BDE,
|
||||||
|
0x151778988,
|
||||||
|
0x1517886DB,
|
||||||
|
0x15178DDAB,
|
||||||
|
0x151793BB6,
|
||||||
|
0x15179DC17,
|
||||||
|
0x1517A6BC5,
|
||||||
|
0x1517ACBA3,
|
||||||
|
0x1517B3502,
|
||||||
|
0x1517BAE0E,
|
||||||
|
0x1517BB039,
|
||||||
|
0x1517BB049,
|
||||||
|
0x1517BC58E,
|
||||||
|
0x1517BC7A0,
|
||||||
|
0x1517BCF51,
|
||||||
|
0x1517BFF71,
|
||||||
|
0x1517C0761,
|
||||||
|
0x1517C33F1,
|
||||||
|
0x1517C44C4,
|
||||||
|
0x1517C75AE,
|
||||||
|
0x1517CDB3B,
|
||||||
|
0x1517CE48A,
|
||||||
|
0x1517D0559,
|
||||||
|
0x1517D8E0F,
|
||||||
|
0x1517D95C0,
|
||||||
|
0x1517DA7FF,
|
||||||
|
0x1517DD407,
|
||||||
|
0x1517E11D2,
|
||||||
|
0x1517E5C08,
|
||||||
|
0x1517ED03C,
|
||||||
|
0x1517EE9CC,
|
||||||
|
0x1517EFB56,
|
||||||
|
0x1517F31AC,
|
||||||
|
0x1517F73D1,
|
||||||
|
0x1517FB1CA,
|
||||||
|
0x1518050D4,
|
||||||
|
0x15180EFA2,
|
||||||
|
0x15181A3BE,
|
||||||
|
0x15181A5C4,
|
||||||
|
0x151823361,
|
||||||
|
0x1518233AC,
|
||||||
|
0x151862C5B,
|
||||||
|
0x151863913,
|
||||||
|
0x15188E396,
|
||||||
|
0x15188EA8D,
|
||||||
|
0x15189760A,
|
||||||
|
0x1518977B0,
|
||||||
|
0x1518B14C9,
|
||||||
|
0x1518B7227,
|
||||||
|
0x1518B73AF,
|
||||||
|
0x1518B84F9,
|
||||||
|
0x1518C562A,
|
||||||
|
0x1518EA89B,
|
||||||
|
0x1518EFC20,
|
||||||
|
0x1518F10A0,
|
||||||
|
0x1518F4D00,
|
||||||
|
0x1518FD8B3,
|
||||||
|
0x1519033DA,
|
||||||
|
0x151904C1E,
|
||||||
|
0x151908F69,
|
||||||
|
0x15190D0DB,
|
||||||
|
0x151988E27,
|
||||||
|
0x15198FDD5,
|
||||||
|
0x151994C76,
|
||||||
|
0x151995C6B,
|
||||||
|
0x1519A66F1,
|
||||||
|
0x1519A7438,
|
||||||
|
0x1519A9B02,
|
||||||
|
0x1519C4269,
|
||||||
|
0x1519C4C75,
|
||||||
|
0x1519EE7BB,
|
||||||
|
0x1519EFFA8,
|
||||||
|
0x1519F0368,
|
||||||
|
0x1519F286A,
|
||||||
|
0x151A04D7E,
|
||||||
|
0x151A35E78,
|
||||||
|
0x151A38F2F,
|
||||||
|
0x151A450E4,
|
||||||
|
0x151A76754,
|
||||||
|
0x151A89BD1,
|
||||||
|
0x151A89C0A,
|
||||||
|
0x151AAB0EA,
|
||||||
|
0x151AB3E09,
|
||||||
|
0x151ABB764,
|
||||||
|
0x151ABCC16,
|
||||||
|
0x151ABF3F0,
|
||||||
|
0x151AC0659,
|
||||||
|
0x151AC5C1B,
|
||||||
|
0x151AC8705,
|
||||||
|
0x151AC88E2,
|
||||||
|
0x151ACA788,
|
||||||
|
0x151ACED96,
|
||||||
|
0x151AD7F8A,
|
||||||
|
0x151ADA318,
|
||||||
|
0x151ADC9BE,
|
||||||
|
0x151AE1BAF,
|
||||||
|
};
|
||||||
|
}
|
92
src/client/component/arxan/integrity.hpp
Normal file
92
src/client/component/arxan/integrity.hpp
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace mp
|
||||||
|
{
|
||||||
|
constexpr uint64_t intact_integrity_check_blocks[] =
|
||||||
|
{
|
||||||
|
0x140000519,
|
||||||
|
0x1407DB02E,
|
||||||
|
0x140AFF08D,
|
||||||
|
0x15143E06E,
|
||||||
|
0x151440DC4,
|
||||||
|
0x151455F3B,
|
||||||
|
0x151489363,
|
||||||
|
0x1515F10AB,
|
||||||
|
0x1515F66D0,
|
||||||
|
0x15161DB84,
|
||||||
|
0x15161EAA8,
|
||||||
|
0x151622530,
|
||||||
|
0x151687D47,
|
||||||
|
0x151690107,
|
||||||
|
0x1516979D7,
|
||||||
|
0x1516D3ED0,
|
||||||
|
0x1516E2FC2,
|
||||||
|
0x15173F3E9,
|
||||||
|
0x15174B9FA,
|
||||||
|
0x15175EAE1,
|
||||||
|
0x15176E778,
|
||||||
|
0x151787948,
|
||||||
|
0x15179956D,
|
||||||
|
0x1517B36C8,
|
||||||
|
0x1517DE653,
|
||||||
|
0x1517E2CE0,
|
||||||
|
0x1517EEAE2,
|
||||||
|
0x1517F92E1,
|
||||||
|
0x1517FB858,
|
||||||
|
0x151809719,
|
||||||
|
0x151868985,
|
||||||
|
0x1518BB832,
|
||||||
|
0x1518EE362,
|
||||||
|
0x1518FE162,
|
||||||
|
0x1519024C3,
|
||||||
|
0x151908639,
|
||||||
|
0x151985713,
|
||||||
|
0x15199565C,
|
||||||
|
0x1519C6C5B,
|
||||||
|
0x1519EB05E,
|
||||||
|
0x1519F0EE1,
|
||||||
|
0x1519F4A25,
|
||||||
|
0x151A0503F,
|
||||||
|
0x151A45752,
|
||||||
|
0x151A7E83E,
|
||||||
|
0x151A87C67,
|
||||||
|
0x151AAF804,
|
||||||
|
0x151AB3E72,
|
||||||
|
0x151AC3482,
|
||||||
|
0x151AC5B0F,
|
||||||
|
0x151ACBE3C,
|
||||||
|
};
|
||||||
|
constexpr uint64_t split_integrity_check_blocks[] =
|
||||||
|
{
|
||||||
|
0x1514B9FC8,
|
||||||
|
0x15161D235,
|
||||||
|
0x1516DE9FF,
|
||||||
|
0x151754691,
|
||||||
|
0x151772B7F,
|
||||||
|
0x1517B3CA9,
|
||||||
|
0x1517DB0D0,
|
||||||
|
0x1517E65DF,
|
||||||
|
0x15181407C,
|
||||||
|
0x151832847,
|
||||||
|
0x15183B664,
|
||||||
|
0x15199ABAA,
|
||||||
|
0x1519AA163,
|
||||||
|
0x1519C5852,
|
||||||
|
0x1519C7B0E,
|
||||||
|
0x1519C7F63,
|
||||||
|
0x1519F3291,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
namespace sp
|
||||||
|
{
|
||||||
|
constexpr uint64_t intact_integrity_check_blocks[] =
|
||||||
|
{
|
||||||
|
0x15263308B,
|
||||||
|
};
|
||||||
|
constexpr uint64_t split_integrity_check_blocks[] =
|
||||||
|
{
|
||||||
|
0x152630C3F,
|
||||||
|
};
|
||||||
|
}
|
@ -1,8 +1,20 @@
|
|||||||
#include "hook.hpp"
|
#include "hook.hpp"
|
||||||
#include "string.hpp"
|
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <MinHook.h>
|
#include <MinHook.h>
|
||||||
|
|
||||||
|
#include "concurrency.hpp"
|
||||||
|
#include "string.hpp"
|
||||||
|
#include "nt.hpp"
|
||||||
|
|
||||||
|
#ifdef max
|
||||||
|
#undef max
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef min
|
||||||
|
#undef min
|
||||||
|
#endif
|
||||||
|
|
||||||
Mem seg_ptr(const SReg& segment, const uint64_t off)
|
Mem seg_ptr(const SReg& segment, const uint64_t off)
|
||||||
{
|
{
|
||||||
auto mem = ptr_abs(off);
|
auto mem = ptr_abs(off);
|
||||||
@ -14,22 +26,164 @@ namespace utils::hook
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
[[maybe_unused]] class _
|
size_t get_allocation_granularity()
|
||||||
|
{
|
||||||
|
SYSTEM_INFO info{};
|
||||||
|
GetSystemInfo(&info);
|
||||||
|
|
||||||
|
return info.dwAllocationGranularity;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* allocate_somewhere_near(const void* base_address, const size_t granularity, const size_t size)
|
||||||
|
{
|
||||||
|
size_t target_address = reinterpret_cast<size_t>(base_address) - (1ull << 31);
|
||||||
|
target_address &= ~(granularity - 1);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
target_address += granularity;
|
||||||
|
|
||||||
|
auto* target_ptr = reinterpret_cast<uint8_t*>(target_address);
|
||||||
|
if (is_relatively_far(base_address, target_ptr))
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto res = VirtualAlloc(target_ptr, size, MEM_RESERVE | MEM_COMMIT,
|
||||||
|
PAGE_EXECUTE_READWRITE);
|
||||||
|
if (res)
|
||||||
|
{
|
||||||
|
if (is_relatively_far(base_address, target_ptr))
|
||||||
|
{
|
||||||
|
VirtualFree(res, 0, MEM_RELEASE);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<uint8_t*>(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class memory
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
_()
|
memory() = default;
|
||||||
|
|
||||||
|
memory(const void* ptr)
|
||||||
|
: memory()
|
||||||
{
|
{
|
||||||
if (MH_Initialize() != MH_OK)
|
static const auto allocation_granularity = get_allocation_granularity();
|
||||||
|
this->length_ = allocation_granularity;
|
||||||
|
|
||||||
|
this->buffer_ = allocate_somewhere_near(ptr, allocation_granularity, this->length_);
|
||||||
|
if (!this->buffer_)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Failed to initialize MinHook");
|
throw std::runtime_error("Failed to allocate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~_()
|
~memory()
|
||||||
{
|
{
|
||||||
MH_Uninitialize();
|
if (this->buffer_)
|
||||||
|
{
|
||||||
|
VirtualFree(this->buffer_, 0, MEM_RELEASE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} __;
|
|
||||||
|
memory(memory&& obj) noexcept
|
||||||
|
: memory()
|
||||||
|
{
|
||||||
|
this->operator=(std::move(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
memory& operator=(memory&& obj) noexcept
|
||||||
|
{
|
||||||
|
if (this != &obj)
|
||||||
|
{
|
||||||
|
this->~memory();
|
||||||
|
this->buffer_ = obj.buffer_;
|
||||||
|
this->length_ = obj.length_;
|
||||||
|
this->offset_ = obj.offset_;
|
||||||
|
|
||||||
|
obj.buffer_ = nullptr;
|
||||||
|
obj.length_ = 0;
|
||||||
|
obj.offset_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* allocate(const size_t length)
|
||||||
|
{
|
||||||
|
if (!this->buffer_)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->offset_ + length > this->length_)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ptr = this->get_ptr();
|
||||||
|
this->offset_ += length;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* get_ptr() const
|
||||||
|
{
|
||||||
|
return this->buffer_ + this->offset_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t* buffer_{};
|
||||||
|
size_t length_{};
|
||||||
|
size_t offset_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
void* get_memory_near(const void* address, const size_t size)
|
||||||
|
{
|
||||||
|
static concurrency::container<std::vector<memory>> memory_container{};
|
||||||
|
|
||||||
|
return memory_container.access<void*>([&](std::vector<memory>& memories)
|
||||||
|
{
|
||||||
|
for (auto& memory : memories)
|
||||||
|
{
|
||||||
|
if (!is_relatively_far(address, memory.get_ptr()))
|
||||||
|
{
|
||||||
|
const auto buffer = memory.allocate(size);
|
||||||
|
if (buffer)
|
||||||
|
{
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memories.emplace_back(address);
|
||||||
|
return memories.back().allocate(size);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void* initialize_min_hook()
|
||||||
|
{
|
||||||
|
static class min_hook_init
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
min_hook_init()
|
||||||
|
{
|
||||||
|
if (MH_Initialize() != MH_OK)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to initialize MinHook");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~min_hook_init()
|
||||||
|
{
|
||||||
|
MH_Uninitialize();
|
||||||
|
}
|
||||||
|
} min_hook_init;
|
||||||
|
return &min_hook_init;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void assembler::pushad64()
|
void assembler::pushad64()
|
||||||
@ -43,12 +197,26 @@ namespace utils::hook
|
|||||||
this->push(rsi);
|
this->push(rsi);
|
||||||
this->push(rdi);
|
this->push(rdi);
|
||||||
|
|
||||||
this->sub(rsp, 0x40);
|
this->push(r8);
|
||||||
|
this->push(r9);
|
||||||
|
this->push(r10);
|
||||||
|
this->push(r11);
|
||||||
|
this->push(r12);
|
||||||
|
this->push(r13);
|
||||||
|
this->push(r14);
|
||||||
|
this->push(r15);
|
||||||
}
|
}
|
||||||
|
|
||||||
void assembler::popad64()
|
void assembler::popad64()
|
||||||
{
|
{
|
||||||
this->add(rsp, 0x40);
|
this->pop(r15);
|
||||||
|
this->pop(r14);
|
||||||
|
this->pop(r13);
|
||||||
|
this->pop(r12);
|
||||||
|
this->pop(r11);
|
||||||
|
this->pop(r10);
|
||||||
|
this->pop(r9);
|
||||||
|
this->pop(r8);
|
||||||
|
|
||||||
this->pop(rdi);
|
this->pop(rdi);
|
||||||
this->pop(rsi);
|
this->pop(rsi);
|
||||||
@ -94,19 +262,26 @@ namespace utils::hook
|
|||||||
|
|
||||||
asmjit::Error assembler::call(void* target)
|
asmjit::Error assembler::call(void* target)
|
||||||
{
|
{
|
||||||
return Assembler::call(size_t(target));
|
return Assembler::call(reinterpret_cast<size_t>(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
asmjit::Error assembler::jmp(void* target)
|
asmjit::Error assembler::jmp(void* target)
|
||||||
{
|
{
|
||||||
return Assembler::jmp(size_t(target));
|
return Assembler::jmp(reinterpret_cast<size_t>(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
detour::detour(const size_t place, void* target) : detour(reinterpret_cast<void*>(place), target)
|
detour::detour()
|
||||||
|
{
|
||||||
|
(void)initialize_min_hook();
|
||||||
|
}
|
||||||
|
|
||||||
|
detour::detour(const size_t place, void* target)
|
||||||
|
: detour(reinterpret_cast<void*>(place), target)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
detour::detour(void* place, void* target)
|
detour::detour(void* place, void* target)
|
||||||
|
: detour()
|
||||||
{
|
{
|
||||||
this->create(place, target);
|
this->create(place, target);
|
||||||
}
|
}
|
||||||
@ -116,13 +291,19 @@ namespace utils::hook
|
|||||||
this->clear();
|
this->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void detour::enable() const
|
void detour::enable()
|
||||||
{
|
{
|
||||||
MH_EnableHook(this->place_);
|
MH_EnableHook(this->place_);
|
||||||
|
|
||||||
|
if (!this->moved_data_.empty())
|
||||||
|
{
|
||||||
|
this->move();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void detour::disable() const
|
void detour::disable()
|
||||||
{
|
{
|
||||||
|
this->un_move();
|
||||||
MH_DisableHook(this->place_);
|
MH_DisableHook(this->place_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,11 +329,23 @@ namespace utils::hook
|
|||||||
{
|
{
|
||||||
if (this->place_)
|
if (this->place_)
|
||||||
{
|
{
|
||||||
|
this->un_move();
|
||||||
MH_RemoveHook(this->place_);
|
MH_RemoveHook(this->place_);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->place_ = nullptr;
|
this->place_ = nullptr;
|
||||||
this->original_ = nullptr;
|
this->original_ = nullptr;
|
||||||
|
this->moved_data_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void detour::move()
|
||||||
|
{
|
||||||
|
this->moved_data_ = move_hook(this->place_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* detour::get_place() const
|
||||||
|
{
|
||||||
|
return this->place_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* detour::get_original() const
|
void* detour::get_original() const
|
||||||
@ -160,20 +353,29 @@ namespace utils::hook
|
|||||||
return this->original_;
|
return this->original_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool iat(const nt::library& library, const std::string& target_library, const std::string& process, void* stub)
|
void detour::un_move()
|
||||||
{
|
{
|
||||||
if (!library.is_valid()) return false;
|
if (!this->moved_data_.empty())
|
||||||
|
{
|
||||||
|
copy(this->place_, this->moved_data_.data(), this->moved_data_.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::pair<void*, void*>> iat(const nt::library& library, const std::string& target_library,
|
||||||
|
const std::string& process, void* stub)
|
||||||
|
{
|
||||||
|
if (!library.is_valid()) return {};
|
||||||
|
|
||||||
auto* const ptr = library.get_iat_entry(target_library, process);
|
auto* const ptr = library.get_iat_entry(target_library, process);
|
||||||
if (!ptr) return false;
|
if (!ptr) return {};
|
||||||
|
|
||||||
DWORD protect;
|
DWORD protect;
|
||||||
VirtualProtect(ptr, sizeof(*ptr), PAGE_EXECUTE_READWRITE, &protect);
|
VirtualProtect(ptr, sizeof(*ptr), PAGE_EXECUTE_READWRITE, &protect);
|
||||||
|
|
||||||
*ptr = stub;
|
std::swap(*ptr, stub);
|
||||||
|
|
||||||
VirtualProtect(ptr, sizeof(*ptr), protect, &protect);
|
VirtualProtect(ptr, sizeof(*ptr), protect, &protect);
|
||||||
return true;
|
return { {ptr, stub} };
|
||||||
}
|
}
|
||||||
|
|
||||||
void nop(void* place, const size_t length)
|
void nop(void* place, const size_t length)
|
||||||
@ -208,23 +410,50 @@ namespace utils::hook
|
|||||||
copy(reinterpret_cast<void*>(place), data, length);
|
copy(reinterpret_cast<void*>(place), data, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void copy_string(void* place, const char* str)
|
||||||
|
{
|
||||||
|
copy(reinterpret_cast<void*>(place), str, strlen(str) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy_string(const size_t place, const char* str)
|
||||||
|
{
|
||||||
|
copy_string(reinterpret_cast<void*>(place), str);
|
||||||
|
}
|
||||||
|
|
||||||
bool is_relatively_far(const void* pointer, const void* data, const int offset)
|
bool is_relatively_far(const void* pointer, const void* data, const int offset)
|
||||||
{
|
{
|
||||||
const int64_t diff = size_t(data) - (size_t(pointer) + offset);
|
return is_relatively_far(reinterpret_cast<size_t>(pointer), reinterpret_cast<size_t>(data), offset);
|
||||||
const auto small_diff = int32_t(diff);
|
}
|
||||||
return diff != int64_t(small_diff);
|
|
||||||
|
bool is_relatively_far(const size_t pointer, const size_t data, const int offset)
|
||||||
|
{
|
||||||
|
const auto diff = static_cast<int64_t>(data - (pointer + offset));
|
||||||
|
const auto small_diff = static_cast<int32_t>(diff);
|
||||||
|
return diff != static_cast<int64_t>(small_diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
void call(void* pointer, void* data)
|
void call(void* pointer, void* data)
|
||||||
{
|
{
|
||||||
if (is_relatively_far(pointer, data))
|
if (is_relatively_far(pointer, data))
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Too far away to create 32bit relative branch");
|
auto* trampoline = get_memory_near(pointer, 14);
|
||||||
|
if (!trampoline)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Too far away to create 32bit relative branch");
|
||||||
|
}
|
||||||
|
|
||||||
|
call(pointer, trampoline);
|
||||||
|
jump(trampoline, data, true, true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* patch_pointer = PBYTE(pointer);
|
uint8_t copy_data[5];
|
||||||
set<uint8_t>(patch_pointer, 0xE8);
|
copy_data[0] = 0xE8;
|
||||||
set<int32_t>(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5)));
|
*reinterpret_cast<int32_t*>(©_data[1]) = static_cast<int32_t>(reinterpret_cast<size_t>(data) - (
|
||||||
|
reinterpret_cast<size_t>(pointer) + 5));
|
||||||
|
|
||||||
|
auto* patch_pointer = static_cast<PBYTE>(pointer);
|
||||||
|
copy(patch_pointer, copy_data, sizeof(copy_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
void call(const size_t pointer, void* data)
|
void call(const size_t pointer, void* data)
|
||||||
@ -237,39 +466,67 @@ namespace utils::hook
|
|||||||
return call(pointer, reinterpret_cast<void*>(data));
|
return call(pointer, reinterpret_cast<void*>(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
void jump(void* pointer, void* data, const bool use_far)
|
void jump(void* pointer, void* data, const bool use_far, const bool use_safe)
|
||||||
{
|
{
|
||||||
static const unsigned char jump_data[] = {
|
static const unsigned char jump_data[] = {
|
||||||
0x48, 0xb8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0xff, 0xe0
|
0x48, 0xb8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0xff, 0xe0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const unsigned char jump_data_safe[] = {
|
||||||
|
0xFF, 0x25, 0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
if (!use_far && is_relatively_far(pointer, data))
|
if (!use_far && is_relatively_far(pointer, data))
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Too far away to create 32bit relative branch");
|
auto* trampoline = get_memory_near(pointer, 14);
|
||||||
|
if (!trampoline)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Too far away to create 32bit relative branch");
|
||||||
|
}
|
||||||
|
jump(pointer, trampoline, false, false);
|
||||||
|
jump(trampoline, data, true, true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* patch_pointer = PBYTE(pointer);
|
auto* patch_pointer = PBYTE(pointer);
|
||||||
|
|
||||||
if (use_far)
|
if (use_far)
|
||||||
{
|
{
|
||||||
copy(patch_pointer, jump_data, sizeof(jump_data));
|
if (use_safe)
|
||||||
copy(patch_pointer + 2, &data, sizeof(data));
|
{
|
||||||
|
uint8_t copy_data[sizeof(jump_data_safe) + sizeof(data)];
|
||||||
|
memcpy(copy_data, jump_data_safe, sizeof(jump_data_safe));
|
||||||
|
memcpy(copy_data + sizeof(jump_data_safe), &data, sizeof(data));
|
||||||
|
|
||||||
|
copy(patch_pointer, copy_data, sizeof(copy_data));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint8_t copy_data[sizeof(jump_data)];
|
||||||
|
memcpy(copy_data, jump_data, sizeof(jump_data));
|
||||||
|
memcpy(copy_data + 2, &data, sizeof(data));
|
||||||
|
|
||||||
|
copy(patch_pointer, copy_data, sizeof(copy_data));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
set<uint8_t>(patch_pointer, 0xE9);
|
uint8_t copy_data[5];
|
||||||
set<int32_t>(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5)));
|
copy_data[0] = 0xE9;
|
||||||
|
*reinterpret_cast<int32_t*>(©_data[1]) = int32_t(size_t(data) - (size_t(pointer) + 5));
|
||||||
|
|
||||||
|
copy(patch_pointer, copy_data, sizeof(copy_data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void jump(const size_t pointer, void* data, const bool use_far)
|
void jump(const size_t pointer, void* data, const bool use_far, const bool use_safe)
|
||||||
{
|
{
|
||||||
return jump(reinterpret_cast<void*>(pointer), data, use_far);
|
return jump(reinterpret_cast<void*>(pointer), data, use_far, use_safe);
|
||||||
}
|
}
|
||||||
|
|
||||||
void jump(const size_t pointer, const size_t data, const bool use_far)
|
void jump(const size_t pointer, const size_t data, const bool use_far, const bool use_safe)
|
||||||
{
|
{
|
||||||
return jump(pointer, reinterpret_cast<void*>(data), use_far);
|
return jump(pointer, reinterpret_cast<void*>(data), use_far, use_safe);
|
||||||
}
|
}
|
||||||
|
|
||||||
void* assemble(const std::function<void(assembler&)>& asm_function)
|
void* assemble(const std::function<void(assembler&)>& asm_function)
|
||||||
@ -284,24 +541,70 @@ namespace utils::hook
|
|||||||
asm_function(a);
|
asm_function(a);
|
||||||
|
|
||||||
void* result = nullptr;
|
void* result = nullptr;
|
||||||
runtime.add(&result, &code);
|
auto err_result = runtime.add(&result, &code);
|
||||||
|
|
||||||
|
if (err_result != asmjit::ErrorCode::kErrorOk)
|
||||||
|
{
|
||||||
|
printf("ASMJIT ERROR: %s\n", asmjit::DebugUtils::errorAsString(err_result));
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void inject(void* pointer, const void* data)
|
void inject(size_t pointer, size_t data)
|
||||||
{
|
{
|
||||||
if (is_relatively_far(pointer, data, 4))
|
if (is_relatively_far(pointer, data, 4))
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Too far away to create 32bit relative branch");
|
throw std::runtime_error("Too far away to create 32bit relative branch");
|
||||||
}
|
}
|
||||||
|
|
||||||
set<int32_t>(pointer, int32_t(size_t(data) - (size_t(pointer) + 4)));
|
set<int32_t>(
|
||||||
|
pointer, static_cast<int32_t>(data - (pointer + 4)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void inject(void* pointer, const void* data)
|
||||||
|
{
|
||||||
|
return inject(reinterpret_cast<size_t>(pointer), reinterpret_cast<size_t>(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
void inject(const size_t pointer, const void* data)
|
void inject(const size_t pointer, const void* data)
|
||||||
{
|
{
|
||||||
return inject(reinterpret_cast<void*>(pointer), data);
|
return inject(pointer, reinterpret_cast<size_t>(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> move_hook(void* pointer)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> original_data{};
|
||||||
|
|
||||||
|
auto* data_ptr = static_cast<uint8_t*>(pointer);
|
||||||
|
if (data_ptr[0] == 0xE9)
|
||||||
|
{
|
||||||
|
original_data.resize(6);
|
||||||
|
memmove(original_data.data(), pointer, original_data.size());
|
||||||
|
|
||||||
|
auto* target = follow_branch(data_ptr);
|
||||||
|
nop(data_ptr, 1);
|
||||||
|
jump(data_ptr + 1, target);
|
||||||
|
}
|
||||||
|
else if (data_ptr[0] == 0xFF && data_ptr[1] == 0x25)
|
||||||
|
{
|
||||||
|
original_data.resize(15);
|
||||||
|
memmove(original_data.data(), pointer, original_data.size());
|
||||||
|
|
||||||
|
copy(data_ptr + 1, data_ptr, 14);
|
||||||
|
nop(data_ptr, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("No branch instruction found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return original_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> move_hook(const size_t pointer)
|
||||||
|
{
|
||||||
|
return move_hook(reinterpret_cast<void*>(pointer));
|
||||||
}
|
}
|
||||||
|
|
||||||
void* follow_branch(void* address)
|
void* follow_branch(void* address)
|
||||||
@ -314,31 +617,4 @@ namespace utils::hook
|
|||||||
|
|
||||||
return extract<void*>(data + 1);
|
return extract<void*>(data + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* allocate_somewhere_near(const void* base_address, const size_t size)
|
|
||||||
{
|
|
||||||
size_t offset = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
offset += size;
|
|
||||||
auto* target_address = static_cast<const uint8_t*>(base_address) - offset;
|
|
||||||
if (utils::hook::is_relatively_far(base_address, target_address))
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto res = VirtualAlloc(const_cast<uint8_t*>(target_address), size, MEM_RESERVE | MEM_COMMIT,
|
|
||||||
PAGE_EXECUTE_READWRITE);
|
|
||||||
if (res)
|
|
||||||
{
|
|
||||||
if (utils::hook::is_relatively_far(base_address, target_address))
|
|
||||||
{
|
|
||||||
VirtualFree(res, 0, MEM_RELEASE);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return static_cast<uint8_t*>(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -13,20 +13,20 @@ namespace utils::hook
|
|||||||
{
|
{
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
template<size_t entries>
|
template <size_t Entries>
|
||||||
std::vector<size_t(*)()> get_iota_functions()
|
std::vector<size_t(*)()> get_iota_functions()
|
||||||
{
|
{
|
||||||
if constexpr (entries == 0)
|
if constexpr (Entries == 0)
|
||||||
{
|
{
|
||||||
std::vector<size_t(*)()> functions;
|
std::vector<size_t(*)()> functions;
|
||||||
return functions;
|
return functions;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto functions = get_iota_functions<entries - 1>();
|
auto functions = get_iota_functions<Entries - 1>();
|
||||||
functions.emplace_back([]()
|
functions.emplace_back([]()
|
||||||
{
|
{
|
||||||
return entries - 1;
|
return Entries - 1;
|
||||||
});
|
});
|
||||||
return functions;
|
return functions;
|
||||||
}
|
}
|
||||||
@ -39,8 +39,8 @@ namespace utils::hook
|
|||||||
// Example:
|
// Example:
|
||||||
// ID3D11Device* device = ...
|
// ID3D11Device* device = ...
|
||||||
// auto entry = get_vtable_entry(device, &ID3D11Device::CreateTexture2D);
|
// auto entry = get_vtable_entry(device, &ID3D11Device::CreateTexture2D);
|
||||||
template <size_t entries = 100, typename Class, typename T, typename... Args>
|
template <size_t Entries = 100, typename Class, typename T, typename... Args>
|
||||||
void** get_vtable_entry(Class* obj, T (Class::* entry)(Args ...))
|
void** get_vtable_entry(Class* obj, T(Class::* entry)(Args ...))
|
||||||
{
|
{
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
@ -50,11 +50,11 @@ namespace utils::hook
|
|||||||
|
|
||||||
func = entry;
|
func = entry;
|
||||||
|
|
||||||
auto iota_functions = detail::get_iota_functions<entries>();
|
auto iota_functions = detail::get_iota_functions<Entries>();
|
||||||
auto* object = iota_functions.data();
|
auto* object = iota_functions.data();
|
||||||
|
|
||||||
using FakeFunc = size_t(__thiscall*)(void* self);
|
using fake_func = size_t(__thiscall*)(void* self);
|
||||||
auto index = static_cast<FakeFunc>(pointer)(&object);
|
auto index = static_cast<fake_func>(pointer)(&object);
|
||||||
|
|
||||||
void** obj_v_table = *reinterpret_cast<void***>(obj);
|
void** obj_v_table = *reinterpret_cast<void***>(obj);
|
||||||
return &obj_v_table[index];
|
return &obj_v_table[index];
|
||||||
@ -88,7 +88,7 @@ namespace utils::hook
|
|||||||
class detour
|
class detour
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
detour() = default;
|
detour();
|
||||||
detour(void* place, void* target);
|
detour(void* place, void* target);
|
||||||
detour(size_t place, void* target);
|
detour(size_t place, void* target);
|
||||||
~detour();
|
~detour();
|
||||||
@ -102,13 +102,15 @@ namespace utils::hook
|
|||||||
{
|
{
|
||||||
if (this != &other)
|
if (this != &other)
|
||||||
{
|
{
|
||||||
this->~detour();
|
this->clear();
|
||||||
|
|
||||||
this->place_ = other.place_;
|
this->place_ = other.place_;
|
||||||
this->original_ = other.original_;
|
this->original_ = other.original_;
|
||||||
|
this->moved_data_ = other.moved_data_;
|
||||||
|
|
||||||
other.place_ = nullptr;
|
other.place_ = nullptr;
|
||||||
other.original_ = nullptr;
|
other.original_ = nullptr;
|
||||||
|
other.moved_data_ = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
@ -117,13 +119,17 @@ namespace utils::hook
|
|||||||
detour(const detour&) = delete;
|
detour(const detour&) = delete;
|
||||||
detour& operator=(const detour&) = delete;
|
detour& operator=(const detour&) = delete;
|
||||||
|
|
||||||
void enable() const;
|
void enable();
|
||||||
void disable() const;
|
void disable();
|
||||||
|
|
||||||
void create(void* place, void* target);
|
void create(void* place, void* target);
|
||||||
void create(size_t place, void* target);
|
void create(size_t place, void* target);
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
void move();
|
||||||
|
|
||||||
|
void* get_place() const;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T* get() const
|
T* get() const
|
||||||
{
|
{
|
||||||
@ -139,11 +145,15 @@ namespace utils::hook
|
|||||||
[[nodiscard]] void* get_original() const;
|
[[nodiscard]] void* get_original() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::vector<uint8_t> moved_data_{};
|
||||||
void* place_{};
|
void* place_{};
|
||||||
void* original_{};
|
void* original_{};
|
||||||
|
|
||||||
|
void un_move();
|
||||||
};
|
};
|
||||||
|
|
||||||
bool iat(const nt::library& library, const std::string& target_library, const std::string& process, void* stub);
|
std::optional<std::pair<void*, void*>> iat(const nt::library& library, const std::string& target_library,
|
||||||
|
const std::string& process, void* stub);
|
||||||
|
|
||||||
void nop(void* place, size_t length);
|
void nop(void* place, size_t length);
|
||||||
void nop(size_t place, size_t length);
|
void nop(size_t place, size_t length);
|
||||||
@ -151,20 +161,28 @@ namespace utils::hook
|
|||||||
void copy(void* place, const void* data, size_t length);
|
void copy(void* place, const void* data, size_t length);
|
||||||
void copy(size_t place, const void* data, size_t length);
|
void copy(size_t place, const void* data, size_t length);
|
||||||
|
|
||||||
|
void copy_string(void* place, const char* str);
|
||||||
|
void copy_string(size_t place, const char* str);
|
||||||
|
|
||||||
bool is_relatively_far(const void* pointer, const void* data, int offset = 5);
|
bool is_relatively_far(const void* pointer, const void* data, int offset = 5);
|
||||||
|
bool is_relatively_far(size_t pointer, size_t data, int offset = 5);
|
||||||
|
|
||||||
void call(void* pointer, void* data);
|
void call(void* pointer, void* data);
|
||||||
void call(size_t pointer, void* data);
|
void call(size_t pointer, void* data);
|
||||||
void call(size_t pointer, size_t data);
|
void call(size_t pointer, size_t data);
|
||||||
|
|
||||||
void jump(void* pointer, void* data, bool use_far = false);
|
void jump(void* pointer, void* data, bool use_far = false, bool use_safe = false);
|
||||||
void jump(size_t pointer, void* data, bool use_far = false);
|
void jump(size_t pointer, void* data, bool use_far = false, bool use_safe = false);
|
||||||
void jump(size_t pointer, size_t data, bool use_far = false);
|
void jump(size_t pointer, size_t data, bool use_far = false, bool use_safe = false);
|
||||||
|
|
||||||
void* assemble(const std::function<void(assembler&)>& asm_function);
|
void* assemble(const std::function<void(assembler&)>& asm_function);
|
||||||
|
|
||||||
void inject(void* pointer, const void* data);
|
void inject(void* pointer, const void* data);
|
||||||
void inject(size_t pointer, const void* data);
|
void inject(size_t pointer, const void* data);
|
||||||
|
void inject(size_t pointer, size_t data);
|
||||||
|
|
||||||
|
std::vector<uint8_t> move_hook(void* pointer);
|
||||||
|
std::vector<uint8_t> move_hook(size_t pointer);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T extract(void* address)
|
T extract(void* address)
|
||||||
@ -177,19 +195,13 @@ namespace utils::hook
|
|||||||
void* follow_branch(void* address);
|
void* follow_branch(void* address);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static void set(void* place, T value)
|
static void set(void* place, T value = false)
|
||||||
{
|
{
|
||||||
DWORD old_protect;
|
copy(place, &value, sizeof(value));
|
||||||
VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &old_protect);
|
|
||||||
|
|
||||||
*static_cast<T*>(place) = value;
|
|
||||||
|
|
||||||
VirtualProtect(place, sizeof(T), old_protect, &old_protect);
|
|
||||||
FlushInstructionCache(GetCurrentProcess(), place, sizeof(T));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static void set(const size_t place, T value)
|
static void set(const size_t place, T value = false)
|
||||||
{
|
{
|
||||||
return set<T>(reinterpret_cast<void*>(place), value);
|
return set<T>(reinterpret_cast<void*>(place), value);
|
||||||
}
|
}
|
||||||
@ -206,8 +218,6 @@ namespace utils::hook
|
|||||||
return static_cast<T(*)(Args ...)>(func)(args...);
|
return static_cast<T(*)(Args ...)>(func)(args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* allocate_somewhere_near(const void* base_address, const size_t size);
|
|
||||||
|
|
||||||
template <size_t Base>
|
template <size_t Base>
|
||||||
void* allocate_far_jump()
|
void* allocate_far_jump()
|
||||||
{
|
{
|
||||||
@ -255,11 +265,4 @@ namespace utils::hook
|
|||||||
const auto pos = create_far_jump<Base>(dest);
|
const auto pos = create_far_jump<Base>(dest);
|
||||||
jump(address, pos, false);
|
jump(address, pos, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <size_t Base, typename T>
|
|
||||||
void far_call(const size_t address, const T dest)
|
|
||||||
{
|
|
||||||
const auto pos = create_far_jump<Base>(dest);
|
|
||||||
call(address, pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -4,6 +4,14 @@
|
|||||||
|
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
|
|
||||||
|
#ifdef max
|
||||||
|
#undef max
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef min
|
||||||
|
#undef min
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace utils::hook
|
namespace utils::hook
|
||||||
{
|
{
|
||||||
void signature::load_pattern(const std::string& pattern)
|
void signature::load_pattern(const std::string& pattern)
|
||||||
@ -29,7 +37,7 @@ namespace utils::hook
|
|||||||
throw std::runtime_error("Invalid pattern");
|
throw std::runtime_error("Invalid pattern");
|
||||||
}
|
}
|
||||||
|
|
||||||
char str[] = {val, 0};
|
char str[] = { val, 0 };
|
||||||
const auto current_nibble = static_cast<uint8_t>(strtol(str, nullptr, 16));
|
const auto current_nibble = static_cast<uint8_t>(strtol(str, nullptr, 16));
|
||||||
|
|
||||||
if (!has_nibble)
|
if (!has_nibble)
|
||||||
@ -68,15 +76,15 @@ namespace utils::hook
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<size_t> signature::process_range(uint8_t* start, const size_t length) const
|
signature::signature_result signature::process_range(uint8_t* start, const size_t length) const
|
||||||
{
|
{
|
||||||
if (this->has_sse_support()) return this->process_range_vectorized(start, length);
|
if (this->has_sse_support()) return this->process_range_vectorized(start, length);
|
||||||
return this->process_range_linear(start, length);
|
return this->process_range_linear(start, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<size_t> signature::process_range_linear(uint8_t* start, const size_t length) const
|
signature::signature_result signature::process_range_linear(uint8_t* start, const size_t length) const
|
||||||
{
|
{
|
||||||
std::vector<size_t> result;
|
std::vector<uint8_t*> result;
|
||||||
|
|
||||||
for (size_t i = 0; i < length; ++i)
|
for (size_t i = 0; i < length; ++i)
|
||||||
{
|
{
|
||||||
@ -93,17 +101,17 @@ namespace utils::hook
|
|||||||
|
|
||||||
if (j == this->mask_.size())
|
if (j == this->mask_.size())
|
||||||
{
|
{
|
||||||
result.push_back(size_t(address));
|
result.push_back(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<size_t> signature::process_range_vectorized(uint8_t* start, const size_t length) const
|
signature::signature_result signature::process_range_vectorized(uint8_t* start, const size_t length) const
|
||||||
{
|
{
|
||||||
std::vector<size_t> result;
|
std::vector<uint8_t*> result;
|
||||||
__declspec(align(16)) char desired_mask[16] = {0};
|
__declspec(align(16)) char desired_mask[16] = { 0 };
|
||||||
|
|
||||||
for (size_t i = 0; i < this->mask_.size(); i++)
|
for (size_t i = 0; i < this->mask_.size(); i++)
|
||||||
{
|
{
|
||||||
@ -118,14 +126,14 @@ namespace utils::hook
|
|||||||
const auto address = start + i;
|
const auto address = start + i;
|
||||||
const auto value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(address));
|
const auto value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(address));
|
||||||
const auto comparison = _mm_cmpestrm(value, 16, comparand, static_cast<int>(this->mask_.size()),
|
const auto comparison = _mm_cmpestrm(value, 16, comparand, static_cast<int>(this->mask_.size()),
|
||||||
_SIDD_CMP_EQUAL_EACH);
|
_SIDD_CMP_EQUAL_EACH);
|
||||||
|
|
||||||
const auto matches = _mm_and_si128(mask, comparison);
|
const auto matches = _mm_and_si128(mask, comparison);
|
||||||
const auto equivalence = _mm_xor_si128(mask, matches);
|
const auto equivalence = _mm_xor_si128(mask, matches);
|
||||||
|
|
||||||
if (_mm_test_all_zeros(equivalence, equivalence))
|
if (_mm_test_all_zeros(equivalence, equivalence))
|
||||||
{
|
{
|
||||||
result.push_back(size_t(address));
|
result.push_back(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +152,7 @@ namespace utils::hook
|
|||||||
signature::signature_result signature::process_serial() const
|
signature::signature_result signature::process_serial() const
|
||||||
{
|
{
|
||||||
const auto sub = this->has_sse_support() ? 16 : this->mask_.size();
|
const auto sub = this->has_sse_support() ? 16 : this->mask_.size();
|
||||||
return {this->process_range(this->start_, this->length_ - sub)};
|
return { this->process_range(this->start_, this->length_ - sub) };
|
||||||
}
|
}
|
||||||
|
|
||||||
signature::signature_result signature::process_parallel() const
|
signature::signature_result signature::process_parallel() const
|
||||||
@ -156,7 +164,7 @@ namespace utils::hook
|
|||||||
const auto grid = range / cores;
|
const auto grid = range / cores;
|
||||||
|
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
std::vector<size_t> result;
|
std::vector<uint8_t*> result;
|
||||||
std::vector<std::thread> threads;
|
std::vector<std::thread> threads;
|
||||||
|
|
||||||
for (auto i = 0u; i < cores; ++i)
|
for (auto i = 0u; i < cores; ++i)
|
||||||
@ -165,7 +173,7 @@ namespace utils::hook
|
|||||||
const auto length = (i + 1 == cores) ? (this->start_ + this->length_ - sub) - start : grid;
|
const auto length = (i + 1 == cores) ? (this->start_ + this->length_ - sub) - start : grid;
|
||||||
threads.emplace_back([&, start, length]()
|
threads.emplace_back([&, start, length]()
|
||||||
{
|
{
|
||||||
auto local_result = this->process_range(start, length);
|
const auto local_result = this->process_range(start, length);
|
||||||
if (local_result.empty()) return;
|
if (local_result.empty()) return;
|
||||||
|
|
||||||
std::lock_guard _(mutex);
|
std::lock_guard _(mutex);
|
||||||
@ -185,7 +193,7 @@ namespace utils::hook
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::sort(result.begin(), result.end());
|
std::sort(result.begin(), result.end());
|
||||||
return {std::move(result)};
|
return { std::move(result) };
|
||||||
}
|
}
|
||||||
|
|
||||||
bool signature::has_sse_support() const
|
bool signature::has_sse_support() const
|
||||||
|
@ -7,33 +7,9 @@ namespace utils::hook
|
|||||||
class signature final
|
class signature final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
class signature_result
|
using signature_result = std::vector<uint8_t*>;
|
||||||
{
|
|
||||||
public:
|
|
||||||
signature_result(std::vector<size_t>&& matches) : matches_(std::move(matches))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] uint8_t* get(const size_t index) const
|
explicit signature(const std::string& pattern, const nt::library& library = {})
|
||||||
{
|
|
||||||
if (index >= this->count())
|
|
||||||
{
|
|
||||||
throw std::runtime_error("Invalid index");
|
|
||||||
}
|
|
||||||
|
|
||||||
return reinterpret_cast<uint8_t*>(this->matches_[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] size_t count() const
|
|
||||||
{
|
|
||||||
return this->matches_.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<size_t> matches_;
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit signature(const std::string& pattern, const nt::library library = {})
|
|
||||||
: signature(pattern, library.get_ptr(), library.get_optional_header()->SizeOfImage)
|
: signature(pattern, library.get_ptr(), library.get_optional_header()->SizeOfImage)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -62,9 +38,9 @@ namespace utils::hook
|
|||||||
|
|
||||||
signature_result process_parallel() const;
|
signature_result process_parallel() const;
|
||||||
signature_result process_serial() const;
|
signature_result process_serial() const;
|
||||||
std::vector<size_t> process_range(uint8_t* start, size_t length) const;
|
signature_result process_range(uint8_t* start, size_t length) const;
|
||||||
std::vector<size_t> process_range_linear(uint8_t* start, size_t length) const;
|
signature_result process_range_linear(uint8_t* start, size_t length) const;
|
||||||
std::vector<size_t> process_range_vectorized(uint8_t* start, size_t length) const;
|
signature_result process_range_vectorized(uint8_t* start, size_t length) const;
|
||||||
|
|
||||||
bool has_sse_support() const;
|
bool has_sse_support() const;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user