boiii arxan bypass

https://github.com/momo5502/boiii
This commit is contained in:
quaK 2022-09-17 20:25:07 +03:00
parent 58dc53850b
commit 7b1a343014
5 changed files with 471 additions and 80 deletions

View File

@ -3,9 +3,11 @@
#include "game/game.hpp"
#include "game_module.hpp"
#include "scheduler.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace arxan
{
@ -18,6 +20,11 @@ namespace arxan
#define ProcessDebugObjectHandle 30 // WinXP source says 31?
#define ProcessDebugFlags 31 // WinXP source says 32?
HANDLE process_id_to_handle(const DWORD pid)
{
return reinterpret_cast<HANDLE>(static_cast<DWORD64>(pid));
}
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)
@ -71,6 +78,13 @@ namespace arxan
return STATUS_INVALID_HANDLE;
}
void hide_being_debugged()
{
auto* const peb = PPEB(__readgsqword(0x60));
peb->BeingDebugged = false;
*reinterpret_cast<PDWORD>(LPSTR(peb) + 0xBC) &= ~0x70;
}
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS info)
{
if (info->ExceptionRecord->ExceptionCode == STATUS_INVALID_HANDLE)
@ -78,19 +92,185 @@ namespace arxan
return EXCEPTION_CONTINUE_EXECUTION;
}
if (info->ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION)
{
//MessageBoxA(0, 0, "AV", 0);
}
return EXCEPTION_CONTINUE_SEARCH;
}
void hide_being_debugged()
struct integrity_handler_context
{
auto* const peb = PPEB(__readgsqword(0x60));
peb->BeingDebugged = false;
*reinterpret_cast<PDWORD>(LPSTR(peb) + 0xBC) &= ~0x70;
uint32_t* computed_checksum;
uint32_t* original_checksum;
};
// Pretty trashy, but working, heuristic to search integrity the handler context
bool is_handler_context(uint8_t* stack_frame, const uint32_t computed_checksum, const uint32_t frame_offset)
{
auto* potential_address = *reinterpret_cast<uint32_t**>(stack_frame + frame_offset);
int64_t diff = reinterpret_cast<uint64_t>(stack_frame) - reinterpret_cast<uint64_t>(potential_address);
diff = std::abs(diff);
return diff < 0x1000 && *potential_address == computed_checksum;
}
integrity_handler_context* search_handler_context(uint8_t* stack_frame, const uint32_t computed_checksum)
{
for (uint32_t frame_offset = 24; frame_offset < 64; 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)
{
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 _DEBUG
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);
}
void search_and_patch_integrity_checks()
{
// There seem to be 670 results.
// Searching them is quite slow.
// Maybe precomputing that might be better?
const auto intact_results = "89 04 8A 83 45 ? FF"_sig;
const auto split_results = "89 04 8A E9"_sig;
int results = 0;
for (auto* i : intact_results)
{
patch_intact_basic_block_integrity_check(i);
results++;
}
for (auto* i : split_results)
{
patch_split_basic_block_integrity_check(i);
results++;
}
OutputDebugStringA(utils::string::va("integrity check amount: %d\n", results));
}
}
@ -103,12 +283,10 @@ namespace arxan
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);
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.create(nt_query_information_process, nt_query_information_process_stub);
nt_query_information_process_hook.move();
AddVectoredExceptionHandler(1, exception_filter);
@ -116,7 +294,12 @@ namespace arxan
void post_unpack() override
{
search_and_patch_integrity_checks();
}
int priority() override
{
return COMPONENT_MAX_PRIORITY;
}
};
}

View File

@ -1,12 +1,171 @@
#include "hook.hpp"
#include "string.hpp"
#include <map>
#include <MinHook.h>
#include "concurrency.hpp"
#include "string.hpp"
#include "nt.hpp"
#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<const uint8_t*>(base_address) - offset;
if (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 (is_relatively_far(base_address, target_address))
{
VirtualFree(res, 0, MEM_RELEASE);
return nullptr;
}
return static_cast<uint8_t*>(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<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);
});
}
concurrency::container<std::map<const void*, uint8_t>>& get_original_data_map()
{
static concurrency::container<std::map<const void*, uint8_t>> og_data{};
return og_data;
}
void store_original_data(const void* /*data*/, size_t /*length*/)
{
/*get_original_data_map().access([data, length](std::map<const void*, uint8_t>& og_map)
{
const auto data_ptr = static_cast<const uint8_t*>(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 +299,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 +352,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 +365,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 +383,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);
@ -233,6 +399,16 @@ namespace utils::hook
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)
{
const int64_t diff = size_t(data) - (size_t(pointer) + offset);
@ -244,12 +420,23 @@ 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;
}
uint8_t copy_data[5];
copy_data[0] = 0xE8;
*reinterpret_cast<int32_t*>(&copy_data[1]) = int32_t(size_t(data) - (size_t(pointer) + 5));
auto* patch_pointer = PBYTE(pointer);
set<uint8_t>(patch_pointer, 0xE8);
set<int32_t>(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5)));
copy(patch_pointer, copy_data, sizeof(copy_data));
}
void call(const size_t pointer, void* data)
@ -274,7 +461,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, false, false);
jump(trampoline, data, true, true);
return;
}
auto* patch_pointer = PBYTE(pointer);
@ -283,19 +477,28 @@ namespace utils::hook
{
if (use_safe)
{
copy(patch_pointer, jump_data_safe, sizeof(jump_data_safe));
copy(patch_pointer + sizeof(jump_data_safe), &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
{
copy(patch_pointer, jump_data, sizeof(jump_data));
copy(patch_pointer + 2, &data, sizeof(data));
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
{
set<uint8_t>(patch_pointer, 0xE9);
set<int32_t>(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5)));
uint8_t copy_data[5];
copy_data[0] = 0xE9;
*reinterpret_cast<int32_t*>(&copy_data[1]) = int32_t(size_t(data) - (size_t(pointer) + 5));
copy(patch_pointer, copy_data, sizeof(copy_data));
}
}
@ -386,4 +589,26 @@ namespace utils::hook
return extract<void*>(data + 1);
}
std::vector<uint8_t> query_original_data(const void* data, const size_t length)
{
std::vector<uint8_t> og_data{};
og_data.resize(length);
memcpy(og_data.data(), data, length);
get_original_data_map().access([data, length, &og_data](const std::map<const void*, uint8_t>& og_map)
{
auto* ptr = static_cast<const uint8_t*>(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;
}
}

View File

@ -22,9 +22,9 @@ namespace utils::hook
{
auto functions = get_iota_functions<Entries - 1>();
functions.emplace_back([]()
{
return Entries - 1;
});
{
return Entries - 1;
});
return functions;
}
}
@ -155,6 +155,9 @@ namespace utils::hook
void copy(void* 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);
void call(void* pointer, void* data);
@ -184,19 +187,13 @@ namespace utils::hook
void* follow_branch(void* address);
template <typename T>
static void set(void* place, T value)
static void set(void* place, T value = false)
{
DWORD old_protect;
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));
copy(place, &value, sizeof(value));
}
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);
}
@ -212,4 +209,6 @@ namespace utils::hook
{
return static_cast<T(*)(Args ...)>(func)(args...);
}
std::vector<uint8_t> query_original_data(const void* data, size_t length);
}

View File

@ -4,6 +4,14 @@
#include <intrin.h>
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
namespace utils::hook
{
void signature::load_pattern(const std::string& pattern)
@ -29,7 +37,7 @@ namespace utils::hook
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));
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);
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)
{
@ -93,17 +101,17 @@ namespace utils::hook
if (j == this->mask_.size())
{
result.push_back(size_t(address));
result.push_back(address);
}
}
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;
__declspec(align(16)) char desired_mask[16] = {0};
std::vector<uint8_t*> result;
__declspec(align(16)) char desired_mask[16] = { 0 };
for (size_t i = 0; i < this->mask_.size(); i++)
{
@ -118,14 +126,14 @@ namespace utils::hook
const auto address = start + i;
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()),
_SIDD_CMP_EQUAL_EACH);
_SIDD_CMP_EQUAL_EACH);
const auto matches = _mm_and_si128(mask, comparison);
const auto equivalence = _mm_xor_si128(mask, matches);
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
{
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
@ -156,7 +164,7 @@ namespace utils::hook
const auto grid = range / cores;
std::mutex mutex;
std::vector<size_t> result;
std::vector<uint8_t*> result;
std::vector<std::thread> threads;
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;
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;
std::lock_guard _(mutex);
@ -185,7 +193,7 @@ namespace utils::hook
}
std::sort(result.begin(), result.end());
return {std::move(result)};
return { std::move(result) };
}
bool signature::has_sse_support() const

View File

@ -7,33 +7,9 @@ namespace utils::hook
class signature final
{
public:
class signature_result
{
public:
signature_result(std::vector<size_t>&& matches) : matches_(std::move(matches))
{
}
using signature_result = std::vector<uint8_t*>;
[[nodiscard]] uint8_t* get(const size_t index) const
{
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 = {})
explicit signature(const std::string& pattern, const nt::library& library = {})
: 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_serial() const;
std::vector<size_t> process_range(uint8_t* start, size_t length) const;
std::vector<size_t> 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(uint8_t* start, size_t length) const;
signature_result process_range_linear(uint8_t* start, size_t length) const;
signature_result process_range_vectorized(uint8_t* start, size_t length) const;
bool has_sse_support() const;
};