Skip to content

Commit e1f53ea

Browse files
committed
MHWilds: Nuke various checks + anti-debugger from orbit
1 parent c81024d commit e1f53ea

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

src/mods/IntegrityCheckBypass.cpp

+110
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ void IntegrityCheckBypass::ignore_application_entries() {
307307
Hooks::get()->ignore_application_entry(0x00c0ab9309584734);
308308
Hooks::get()->ignore_application_entry(0xa474f1d3a294e6a4);
309309
#endif
310+
#if TDB_VER >= 74
311+
Hooks::get()->ignore_application_entry(0x00ec4793097cd833);
312+
Hooks::get()->ignore_application_entry(0x00d85893096c4c0c);
313+
#endif
310314
}
311315

312316
void IntegrityCheckBypass::immediate_patch_re8() {
@@ -519,6 +523,110 @@ void* IntegrityCheckBypass::renderer_create_blas_hook(void* a1, void* a2, void*
519523
return s_renderer_create_blas_hook->get_original<decltype(renderer_create_blas_hook)>()(a1, a2, a3, a4, a5);
520524
}
521525

526+
// This is used to nuke the heap allocated code that causes crashes
527+
// when debuggers are attached and other integrity checks.
528+
// They happen to be in the same (heap allocated) executable section, so we can just
529+
// replace every byte with a RET instruction.
530+
void IntegrityCheckBypass::nuke_heap_allocated_code(uintptr_t addr) {
531+
// Get the base of the memory region.
532+
MEMORY_BASIC_INFORMATION mbi{};
533+
if (VirtualQuery((LPCVOID)addr, &mbi, sizeof(mbi)) == 0) {
534+
spdlog::error("[IntegrityCheckBypass]: VirtualQuery failed!");
535+
return;
536+
}
537+
538+
// Get the end of the memory region.
539+
const auto start = (uintptr_t)mbi.BaseAddress;
540+
const auto end = (uintptr_t)mbi.BaseAddress + mbi.RegionSize;
541+
542+
spdlog::info("[IntegrityCheckBypass]: Nuking heap allocated code at 0x{:X} - 0x{:X}", start, end);
543+
544+
// Fix the protection of the memory region.
545+
ProtectionOverride _{(void*)start, mbi.RegionSize, PAGE_EXECUTE_READWRITE};
546+
547+
// Replace every single byte with a RET (C3) instruction.
548+
std::memset((void*)start, 0xC3, mbi.RegionSize);
549+
550+
spdlog::info("[IntegrityCheckBypass]: Nuked heap allocated code at 0x{:X}", start);
551+
}
552+
553+
void IntegrityCheckBypass::anti_debug_watcher() try {
554+
static const auto ntdll = GetModuleHandleW(L"ntdll.dll");
555+
static const auto dbg_ui_remote_breakin = ntdll != nullptr ? GetProcAddress(ntdll, "DbgUiRemoteBreakin") : nullptr;
556+
static auto original_dbg_ui_remote_breakin_bytes = dbg_ui_remote_breakin != nullptr ? utility::get_original_bytes(dbg_ui_remote_breakin) : std::optional<std::vector<uint8_t>>{};
557+
558+
if (dbg_ui_remote_breakin == nullptr) {
559+
return;
560+
}
561+
562+
// We can generally assume it's not hooked at this point if the original bytes are empty.
563+
if (!original_dbg_ui_remote_breakin_bytes || original_dbg_ui_remote_breakin_bytes->empty()) {
564+
spdlog::info("[IntegrityCheckBypass]: Manually copying original bytes for DbgUiRemoteBreakin.");
565+
if (!original_dbg_ui_remote_breakin_bytes) {
566+
original_dbg_ui_remote_breakin_bytes = std::vector<uint8_t>{};
567+
}
568+
569+
if (original_dbg_ui_remote_breakin_bytes->size() < 32) {
570+
std::copy_n((uint8_t*)dbg_ui_remote_breakin + original_dbg_ui_remote_breakin_bytes->size(), 32 - original_dbg_ui_remote_breakin_bytes->size(), std::back_inserter(*original_dbg_ui_remote_breakin_bytes));
571+
}
572+
}
573+
574+
const uint64_t* first_8_bytes = (uint64_t*)dbg_ui_remote_breakin;
575+
const uint8_t* first_8_bytes_ptr = (uint8_t*)dbg_ui_remote_breakin;
576+
577+
if (*(uint64_t*)original_dbg_ui_remote_breakin_bytes->data() != *first_8_bytes) {
578+
spdlog::info("[IntegrityCheckBypass]: DbgUiRemoteBreakin was hooked, restoring original bytes.");
579+
580+
if (first_8_bytes_ptr[0] == 0xE9) {
581+
spdlog::info("[IntegrityCheckBypass]: DbgUiRemoteBreakin was directly hooked, resolving...");
582+
const auto resolved_jmp = utility::calculate_absolute((uintptr_t)dbg_ui_remote_breakin + 1);
583+
const auto is_heap_allocated = utility::get_module_within(resolved_jmp).value_or(nullptr) == nullptr;
584+
585+
if (is_heap_allocated && !IsBadReadPtr((void*)resolved_jmp, 32)) {
586+
spdlog::info("[IntegrityCheckBypass]: Nuking heap allocated code at 0x{:X}", resolved_jmp);
587+
nuke_heap_allocated_code(resolved_jmp);
588+
}
589+
} else if (first_8_bytes_ptr[0] == 0xFF && first_8_bytes_ptr[1] == 0x25) {
590+
spdlog::info("[IntegrityCheckBypass]: DbgUiRemoteBreakin was indirectly hooked, resolving...");
591+
const auto resolved_ptr = utility::calculate_absolute((uintptr_t)dbg_ui_remote_breakin + 2);
592+
const auto resolved_jmp = *(uintptr_t*)resolved_ptr;
593+
const auto is_heap_allocated = utility::get_module_within(resolved_jmp).value_or(nullptr) == nullptr;
594+
595+
if (is_heap_allocated && !IsBadReadPtr((void*)resolved_jmp, 32)) {
596+
spdlog::info("[IntegrityCheckBypass]: Nuking heap allocated code at 0x{:X}", resolved_jmp);
597+
nuke_heap_allocated_code(resolved_jmp);
598+
}
599+
}
600+
601+
ProtectionOverride _{dbg_ui_remote_breakin, original_dbg_ui_remote_breakin_bytes->size(), PAGE_EXECUTE_READWRITE};
602+
std::copy(original_dbg_ui_remote_breakin_bytes->begin(), original_dbg_ui_remote_breakin_bytes->end(), (uint8_t*)dbg_ui_remote_breakin);
603+
604+
spdlog::info("[IntegrityCheckBypass]: Restored DbgUiRemoteBreakin.");
605+
}
606+
} catch (const std::exception& e) {
607+
spdlog::error("[IntegrityCheckBypass]: Exception in anti_debug_watcher: {}", e.what());
608+
} catch (...) {
609+
spdlog::error("[IntegrityCheckBypass]: Unknown exception in anti_debug_watcher!");
610+
}
611+
612+
void IntegrityCheckBypass::init_anti_debug_watcher() {
613+
if (s_anti_anti_debug_thread != nullptr) {
614+
return;
615+
}
616+
617+
// Run the original watcher once so we get it at least without creating a thread first.
618+
anti_debug_watcher();
619+
620+
s_anti_anti_debug_thread = std::make_unique<std::jthread>([](std::stop_token stop_token) {
621+
spdlog::info("[IntegrityCheckBypass]: Hello from anti_debug_watcher!");
622+
623+
while (!stop_token.stop_requested()) {
624+
anti_debug_watcher();
625+
std::this_thread::sleep_for(std::chrono::milliseconds(500));
626+
}
627+
});
628+
}
629+
522630
void IntegrityCheckBypass::immediate_patch_dd2() {
523631
// Just like RE4, this deals with the scans that are done every frame on the game's memory.
524632
// The scans are still performed, but the crash will be avoided.
@@ -533,6 +641,8 @@ void IntegrityCheckBypass::immediate_patch_dd2() {
533641
const auto game = utility::get_executable();
534642

535643
#if TDB_VER >= 74
644+
init_anti_debug_watcher();
645+
536646
const auto query_performance_frequency = &QueryPerformanceFrequency;
537647
const auto query_performance_counter = &QueryPerformanceCounter;
538648

src/mods/IntegrityCheckBypass.hpp

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ class IntegrityCheckBypass : public Mod {
4343
static inline uint32_t* s_corruption_when_zero{ nullptr };
4444
static inline uint32_t s_last_non_zero_corruption{ 8 }; // What I've seen it default to
4545

46+
static void anti_debug_watcher();
47+
static void init_anti_debug_watcher();
48+
static void nuke_heap_allocated_code(uintptr_t addr);
49+
static inline std::unique_ptr<std::jthread> s_anti_anti_debug_thread{nullptr};
50+
4651
static BOOL WINAPI virtual_protect_impl(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
4752
static BOOL WINAPI virtual_protect_hook(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
4853

0 commit comments

Comments
 (0)