diff --git a/src/client/component/arxan.cpp b/src/client/component/arxan.cpp index b864c91a..0c248b31 100644 --- a/src/client/component/arxan.cpp +++ b/src/client/component/arxan.cpp @@ -5,6 +5,8 @@ #include "steam/steam.hpp" #include +#include "utils/string.hpp" + #define ProcessDebugPort 7 #define ProcessDebugObjectHandle 30 // WinXP source says 31? #define ProcessDebugFlags 31 // WinXP source says 32? @@ -277,21 +279,6 @@ namespace arxan return STATUS_INVALID_HANDLE; } - LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS info) - { - if (info->ExceptionRecord->ExceptionCode == STATUS_INVALID_HANDLE) - { - return EXCEPTION_CONTINUE_EXECUTION; - } - - if (info->ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) - { - //MessageBoxA(0, 0, "AV", 0); - } - - return EXCEPTION_CONTINUE_SEARCH; - } - void hide_being_debugged() { auto* const peb = reinterpret_cast(__readgsqword(0x60)); @@ -340,6 +327,22 @@ namespace arxan loaded = true; } + LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS info) + { + if (info->ExceptionRecord->ExceptionCode == STATUS_INVALID_HANDLE) + { + return EXCEPTION_CONTINUE_EXECUTION; + } + + if (info->ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) + { + //MessageBoxA(0, 0, "AV", 0); + //restore_debug_functions(); + } + + return EXCEPTION_CONTINUE_SEARCH; + } + const char* get_command_line_a_stub() { static auto cmd = [] @@ -357,6 +360,247 @@ namespace arxan } } + uint64_t get_integrity_data_qword(const uint8_t* address) + { + const auto og_data = utils::hook::query_original_data(address, 8); + return *reinterpret_cast(og_data.data()); + } + + uint32_t get_integrity_data_dword(const uint8_t* address) + { + const auto og_data = utils::hook::query_original_data(address, 4); + return *reinterpret_cast(og_data.data()); + } + + uint8_t get_integrity_data_byte(const uint8_t* address) + { + const auto og_data = utils::hook::query_original_data(address, 1); + return og_data[0]; + } + + void patch_check_type_1_xor() + { + const auto checks = "8B 00 33 45 ??"_sig; + for(size_t i = 0; i < checks.count(); ++i) + { + auto* addr = checks.get(i); + + utils::hook::jump(addr, utils::hook::assemble([addr](utils::hook::assembler& a) + { + a.push(rax); + a.pushad64(); + + a.mov(rcx, rax); + a.call_aligned(get_integrity_data_dword); + + a.mov(rcx, qword_ptr(rsp, 128)); + a.movzx(ecx, eax); + a.mov(qword_ptr(rsp, 128), rcx); + + a.popad64(); + a.pop(rax); + + // xor eax, [rbp+??h] + a.embedUInt8(addr[3]); + a.embedUInt8(addr[4]); + a.embedUInt8(addr[5]); + + a.jmp(addr + 5); + })); + } + } + + void patch_check_type_1_direct() + { + auto patch_addr = [](uint8_t* addr) + { + // Skip false positives + // Prefixed 0x41 encodes a different instruction + if (addr[-1] == 0x41) + { + return; + } + + utils::hook::jump(addr, utils::hook::assemble([addr](utils::hook::assembler& a) + { + a.push(rax); + a.pushad64(); + + a.mov(rcx, rax); + a.call_aligned(get_integrity_data_dword); + + a.mov(rcx, qword_ptr(rsp, 128)); + a.mov(ecx, eax); + a.mov(qword_ptr(rsp, 128), rcx); + + a.popad64(); + a.pop(rax); + + a.embedUInt8(addr[3]); + a.embedUInt8(addr[4]); + a.embedUInt8(addr[5]); + + a.jmp(addr + 5); + })); + }; + + // mov [rbp+??h], eax + auto checks = "8B 00 89 45 ??"_sig; + for(size_t i = 0; i < checks.count(); ++i) + { + auto* addr = checks.get(i); + patch_addr(addr); + } + + // xor eax, [rbp+??h] + checks = "8B 00 33 45 ??"_sig; + for (size_t i = 0; i < checks.count(); ++i) + { + auto* addr = checks.get(i); + patch_addr(addr); + } + } + + void patch_check_type_1_indirect() + { + auto patch_addr = [](uint8_t* addr) + { + const auto rex_prefixed = *addr == 0x48; + const auto jump_target = utils::hook::follow_branch(addr + (rex_prefixed ? 3 : 2)); + + utils::hook::jump(addr, utils::hook::assemble([addr, jump_target, rex_prefixed](utils::hook::assembler& a) + { + a.push(rax); + a.pushad64(); + + a.mov(rcx, rax); + + if(rex_prefixed) + { + a.call_aligned(get_integrity_data_dword); + + a.mov(rcx, qword_ptr(rsp, 128)); + a.mov(ecx, eax); + a.mov(qword_ptr(rsp, 128), rcx); + } + else + { + a.mov(qword_ptr(rsp, 128), rax); + } + + a.popad64(); + a.pop(rax); + + a.jmp(jump_target); + })); + }; + + // mov rax, [rax]; jmp ... + auto checks = "48 8B 00 E9"_sig; + for(size_t i = 0; i < checks.count(); ++i) + { + auto* addr = checks.get(i); + patch_addr(addr); + } + + // mov eax, [rax]; jmp ... + checks = "8B 00 E9"_sig; + for (size_t i = 0; i < checks.count(); ++i) + { + auto* addr = checks.get(i); + patch_addr(addr); + } + } + + void patch_check_type_2() + { + const auto checks = "0F B6 00 0F B6 C0 33 45 50 89 45 50"_sig; + for (size_t i = 0; i < checks.count(); ++i) + { + auto* addr = checks.get(i); + + utils::hook::jump(addr, utils::hook::assemble([addr](utils::hook::assembler& a) + { + a.push(rax); + a.pushad64(); + + a.mov(rcx, rax); + a.call_aligned(get_integrity_data_byte); + + a.mov(rcx, qword_ptr(rsp, 128)); + a.movzx(ecx, al); + a.mov(qword_ptr(rsp, 128), rcx); + + a.popad64(); + a.pop(rax); + + a.movzx(eax, al); + a.jmp(addr + 6); + })); + } + } + + void patch_check_type_4() + { + const auto checks = "48 8B 04 10 48 89 45 20"_sig; + for (size_t i = 0; i < checks.count(); ++i) + { + auto* addr = checks.get(i); + + utils::hook::jump(addr, utils::hook::assemble([addr](utils::hook::assembler& a) + { + a.mov(rax, qword_ptr(rax, rdx)); + a.push(rax); + a.pushad64(); + + a.mov(rcx, rax); + a.call_aligned(get_integrity_data_qword); + a.mov(qword_ptr(rsp, 128), rax); + + a.popad64(); + a.pop(rax); + + a.mov(qword_ptr(rbp, 0x20), rax); + a.jmp(addr + 8); + })); + } + } + + void patch_check_type_5() + { + const auto checks = "0F B6 00 88 02"_sig; + for (size_t i = 0; i < checks.count(); ++i) + { + auto* addr = checks.get(i); + + // Skip false positives + // Prefixed 0x41 encodes a different instruction + if(addr[-1] == 0x41) + { + continue; + } + + utils::hook::jump(addr, utils::hook::assemble([addr](utils::hook::assembler& a) + { + a.push(rax); + a.pushad64(); + + a.mov(rcx, rax); + a.call_aligned(get_integrity_data_byte); + + a.mov(rcx, qword_ptr(rsp, 128)); + a.movzx(ecx, al); + a.mov(qword_ptr(rsp, 128), rcx); + + a.popad64(); + a.pop(rax); + + a.mov(byte_ptr(rdx), al); + a.jmp(addr + 5); + })); + } + } + class component final : public component_interface { public: @@ -365,11 +609,8 @@ namespace arxan hide_being_debugged(); scheduler::loop(hide_being_debugged, scheduler::pipeline::async); - //restore_debug_functions(); - create_mutex_ex_a_hook.create(CreateMutexExA, create_mutex_ex_a_stub); - const utils::nt::library ntdll("ntdll.dll"); nt_close_hook.create(ntdll.get_proc("NtClose"), nt_close_stub); @@ -397,6 +638,15 @@ namespace arxan } } + void post_unpack() override + { + patch_check_type_1_direct(); + patch_check_type_1_indirect(); + patch_check_type_2(); + patch_check_type_4(); + patch_check_type_5(); + } + void pre_destroy() override { utils::hook::copy(GetWindowTextA, this->window_text_buffer_, sizeof(this->window_text_buffer_)); diff --git a/src/client/component/console.cpp b/src/client/component/console.cpp index 1f6efe59..3b671e51 100644 --- a/src/client/component/console.cpp +++ b/src/client/component/console.cpp @@ -28,12 +28,12 @@ namespace console { utils::hook::detour d; d.create(0x142333B40_g, utils::hook::assemble([](utils::hook::assembler& a) - { - a.mov(r8, "BOIII Console"); - a.mov(r9d, 0x80CA0000); - a.sub(eax, edx); - a.jmp(0x142333B4F_g); - })); + { + a.mov(r8, "BOIII Console"); + a.mov(r9d, 0x80CA0000); + a.sub(eax, edx); + a.jmp(0x142333B4F_g); + })); create_game_console(); } diff --git a/src/client/main.cpp b/src/client/main.cpp index bdafc3d8..ded1eddc 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -110,16 +110,114 @@ namespace return true; } + + class patch + { + public: + patch() = default; + patch(void* source, void* target) + : source_(source) + { + memcpy(this->data_, source, sizeof(this->data_)); + utils::hook::jump(this->source_, target, true, true); + } + + ~patch() + { + if (source_) + { + utils::hook::copy(this->source_, this->data_, sizeof(this->data_)); + } + } + + patch(patch&& obj) noexcept + : patch() + { + this->operator=(std::move(obj)); + } + + patch& operator=(patch&& obj) noexcept + { + if (this != &obj) + { + this->~patch(); + + this->source_ = obj.source_; + memcpy(this->data_, obj.data_, sizeof(this->data_)); + + obj.source_ = nullptr; + } + + return *this; + } + + private: + void* source_{ nullptr }; + uint8_t data_[15]{}; + }; + + std::vector initialization_hooks{}; + + uint8_t* get_entry_point() + { + const utils::nt::library game{}; + return game.get_ptr() + game.get_optional_header()->AddressOfEntryPoint; + } + + std::vector get_tls_callbacks() + { + const utils::nt::library game{}; + const auto& entry = game.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]; + if(!entry.VirtualAddress || !entry.Size) + { + return {}; + } + + const auto* tls_dir = reinterpret_cast(game.get_ptr() + entry.VirtualAddress); + auto* callback = reinterpret_cast(tls_dir->AddressOfCallBacks); + + std::vector addresses{}; + while(callback && *callback) + { + addresses.emplace_back(*callback); + ++callback; + } + + return addresses; + } + + int patch_main() + { + initialization_hooks.clear(); + + if(!run()) + { + return 1; + } + + return reinterpret_cast(get_entry_point())(); + } + + void nullsub() + { + + } + + void patch_entry_point() + { + initialization_hooks.emplace_back(get_entry_point(), patch_main); + + for(auto* tls_callback : get_tls_callbacks()) { + initialization_hooks.emplace_back(tls_callback, nullsub); + } + } } BOOL WINAPI DllMain(HINSTANCE, const DWORD reason, LPVOID) { if (reason == DLL_PROCESS_ATTACH) { - if (!run()) - { - return FALSE; - } + patch_entry_point(); } return TRUE; } diff --git a/src/common/utils/hook.cpp b/src/common/utils/hook.cpp index c14f936f..19debe39 100644 --- a/src/common/utils/hook.cpp +++ b/src/common/utils/hook.cpp @@ -1,12 +1,162 @@ #include "hook.hpp" + +#include +#include + +#include "concurrency.hpp" #include "string.hpp" -#include +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif namespace utils::hook { namespace { + 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(base_address) - offset; + if (is_relatively_far(base_address, target_address)) + { + return nullptr; + } + + const auto res = VirtualAlloc(const_cast(target_address), size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (res) + { + if (is_relatively_far(base_address, target_address)) + { + VirtualFree(res, 0, MEM_RELEASE); + return nullptr; + } + + return static_cast(res); + } + } + } + + class memory + { + public: + memory() = default; + memory(const void* ptr) + : memory() + { + this->length_ = 0x1000; + this->buffer_ = allocate_somewhere_near(ptr, this->length_); + if (!this->buffer_) + { + throw std::runtime_error("Failed to allocate"); + } + } + + ~memory() + { + 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> memory_container{}; + + return memory_container.access([&](std::vector& memories) + { + for(auto& memory : memories) + { + if(!is_relatively_far(address, memory.get_ptr())) + { + return memory.allocate(size); + } + } + + memories.emplace_back(address); + return memories.back().allocate(size); + }); + } + + concurrency::container>& get_original_data_map() + { + static concurrency::container> og_data{}; + return og_data; + } + + void store_original_data(const void* data, size_t length) + { + get_original_data_map().access([data, length](std::map& og_map) + { + const auto data_ptr = static_cast(data); + for(size_t i = 0; i < length; ++i) + { + const auto pos = data_ptr + i; + if(!og_map.contains(pos)) + { + og_map[pos] = *pos; + } + } + }); + } + void* initialize_min_hook() { static class min_hook_init @@ -140,6 +290,7 @@ namespace utils::hook { this->clear(); this->place_ = place; + store_original_data(place, 14); if (MH_CreateHook(this->place_, target, &this->original_) != MH_OK) { @@ -192,6 +343,8 @@ namespace utils::hook auto* const ptr = library.get_iat_entry(target_library, process); if (!ptr) return false; + store_original_data(ptr, sizeof(*ptr)); + DWORD protect; VirtualProtect(ptr, sizeof(*ptr), PAGE_EXECUTE_READWRITE, &protect); @@ -203,6 +356,8 @@ namespace utils::hook void nop(void* place, const size_t length) { + store_original_data(place, length); + DWORD old_protect{}; VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect); @@ -219,6 +374,8 @@ namespace utils::hook void copy(void* place, const void* data, const size_t length) { + store_original_data(place, length); + DWORD old_protect{}; VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect); @@ -244,7 +401,15 @@ namespace utils::hook { 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); @@ -274,7 +439,14 @@ namespace utils::hook 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); + jump(trampoline, data, true, true); + return; } auto* patch_pointer = PBYTE(pointer); @@ -386,4 +558,26 @@ namespace utils::hook return extract(data + 1); } + + std::vector query_original_data(const void* data, const size_t length) + { + std::vector og_data{}; + og_data.resize(length); + memcpy(og_data.data(), data, length); + + get_original_data_map().access([data, length, &og_data](const std::map& og_map) + { + auto* ptr = static_cast(data); + for(size_t i = 0; i < length; ++i) + { + auto entry = og_map.find(ptr + i); + if(entry != og_map.end()) + { + og_data[i] = entry->second; + } + } + }); + + return og_data; + } } diff --git a/src/common/utils/hook.hpp b/src/common/utils/hook.hpp index 788d2605..0ac3b569 100644 --- a/src/common/utils/hook.hpp +++ b/src/common/utils/hook.hpp @@ -186,13 +186,7 @@ namespace utils::hook template static void set(void* place, T value) { - DWORD old_protect; - VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &old_protect); - - *static_cast(place) = value; - - VirtualProtect(place, sizeof(T), old_protect, &old_protect); - FlushInstructionCache(GetCurrentProcess(), place, sizeof(T)); + copy(place, &value, sizeof(value)); } template @@ -212,4 +206,6 @@ namespace utils::hook { return static_cast(func)(args...); } + + std::vector query_original_data(const void* data, size_t length); } diff --git a/src/common/utils/signature.cpp b/src/common/utils/signature.cpp index 9bb8c621..28c2a1d1 100644 --- a/src/common/utils/signature.cpp +++ b/src/common/utils/signature.cpp @@ -4,6 +4,14 @@ #include +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + namespace utils::hook { void signature::load_pattern(const std::string& pattern)