diff --git a/CMakeLists.txt b/CMakeLists.txt index ffc72d6..1137482 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required (VERSION 3.8) -project(MHS2Loader VERSION "0.0.1") +project(MHS2Loader VERSION "4.0.0") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -11,7 +11,7 @@ option(LOADER_NEXUS_CHECK "A check only for the nexus version to future-proof ag add_library(dinput8 SHARED "src/dinput_proxy/dinput8_proxy.cpp" - ) +) add_library(LoaderCore SHARED "src/loader/entry.cpp" @@ -25,6 +25,8 @@ add_library(LoaderCore SHARED "src/loader/Plugin.hpp" "src/loader/PluginConfig.hpp" "src/loader/PluginConfig.cpp" + #"src/loader/Win32Internals.hpp" + #"src/loader/Win32Internals.cpp" ) target_include_directories (LoaderCore PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) target_include_directories (LoaderCore PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/extern) diff --git a/src/dinput_proxy/dinput8_proxy.cpp b/src/dinput_proxy/dinput8_proxy.cpp index ff13119..2d5c0c3 100644 --- a/src/dinput_proxy/dinput8_proxy.cpp +++ b/src/dinput_proxy/dinput8_proxy.cpp @@ -25,6 +25,7 @@ extern "C" { BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { if (fdwReason == DLL_PROCESS_ATTACH) { + //MessageBoxA(NULL, "Paused pre-graphics initialization.\nInject RenderDoc then press OK.", "MHS2Loader renderdoc pause", MB_OK); LoadLibraryA("LoaderCore.dll"); } diff --git a/src/loader/Win32Internals.cpp b/src/loader/Win32Internals.cpp new file mode 100644 index 0000000..9cb8ac1 --- /dev/null +++ b/src/loader/Win32Internals.cpp @@ -0,0 +1,41 @@ +#include "Win32Internals.hpp" + +#define ThreadQuerySetWin32StartAddress 9 +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) + +typedef NTSTATUS(WINAPI* pNtQueryInformationThread)( + HANDLE ThreadHandle, + LONG ThreadInformationClass, + PVOID ThreadInformation, + ULONG ThreadInformationLength, + PULONG ReturnLength +); + +namespace Win32Internals { + uint64_t GetThreadStartAddress(uint64_t thread_id) { + // Resolve the internal NtQueryInformationThread function (non-public). + pNtQueryInformationThread NtQueryInformationThread = (pNtQueryInformationThread)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryInformationThread"); + if (NtQueryInformationThread == NULL) { + return 0; + } + + // Get a duplicate thread query handle. + HANDLE hCurrentProcess = GetCurrentProcess(); + HANDLE hThreadQueryHandle = (HANDLE)0; + if (!DuplicateHandle(hCurrentProcess, (HANDLE)thread_id, hCurrentProcess, &hThreadQueryHandle, THREAD_QUERY_INFORMATION, FALSE, 0)) { + SetLastError(ERROR_ACCESS_DENIED); + return 0; + } + + // Query for the thread entry point address (ThreadQuerySetWin32StartAddress). + DWORD_PTR dwStartAddress; + NTSTATUS ntStatus = NtQueryInformationThread(hThreadQueryHandle, ThreadQuerySetWin32StartAddress, &dwStartAddress, sizeof(DWORD_PTR), NULL); + CloseHandle(hThreadQueryHandle); + + if (ntStatus != STATUS_SUCCESS) { + return 0; + } + + return (uint64_t)dwStartAddress; + } +} diff --git a/src/loader/Win32Internals.hpp b/src/loader/Win32Internals.hpp new file mode 100644 index 0000000..85e0d3a --- /dev/null +++ b/src/loader/Win32Internals.hpp @@ -0,0 +1,7 @@ +#pragma once +#include +#include + +namespace Win32Internals { + uint64_t GetThreadStartAddress(uint64_t thread_id); +} \ No newline at end of file diff --git a/src/loader/entry.cpp b/src/loader/entry.cpp index 8d948f2..82b3962 100644 --- a/src/loader/entry.cpp +++ b/src/loader/entry.cpp @@ -1,5 +1,7 @@ #define WIN32_LEAN_AND_MEAN #include +#include +#include #include #include #include @@ -17,12 +19,17 @@ #include "Log.hpp" #include "PluginManager.hpp" #include "SigScan.hpp" +#include "Win32Internals.hpp" -#define VERSION_MESSAGE "MHS2Loader v3.0.0" +#pragma intrinsic(_ReturnAddress) + +#define VERSION_MESSAGE "MHS2Loader v4.0.0" std::mutex g_initialized_mutex; bool g_initialized; +typedef void(*GetSystemTimeAsFileTime_t)(LPFILETIME lpSystemTimeAsFileTime); +GetSystemTimeAsFileTime_t fpGetSystemTimeAsFileTime = NULL; typedef int(*main_t)(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd); main_t fpMain = NULL; typedef int64_t(*mainCRTStartup_t)(); @@ -132,7 +139,7 @@ int EnableCoreHooks() { return 1; } - void* mainStartAddr = (void*)SigScan::Scan(image_base, "48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B E9 41 8B F9 B9 0B 00 00 00"); + void* mainStartAddr = (void*)SigScan::Scan(image_base, "48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B E9 41 8B F9 B9 ?? ?? ?? ??"); if (mainStartAddr == nullptr) { spdlog::get("PreLoader")->critical("Failed to scan for mainStartAddr"); return 1; @@ -179,12 +186,6 @@ int EnableCoreHooks() { void* sObserverManagerNoteRandAddr = (void*)0x140995990; */ - if (MH_Initialize() != MH_OK) - { - spdlog::get("PreLoader")->error("Failed to initialize minhook!"); - return 1; - } - if (MH_CreateHook(anticheatDispatchAddr, &hookedAnticheatDispatch, reinterpret_cast(&fpAnticheatDispatch)) != MH_OK) { spdlog::get("PreLoader")->error("Failed to create anticheatDispatch hook"); return 1; @@ -244,23 +245,72 @@ int EnableCoreHooks() { return 1; } + return 0; +} +// GetSystemTimeAsFileTime gets called pre-WinMain in the MSVCRT setup. +void hookedGetSystemTimeAsFileTime(LPFILETIME lpSystemTimeAsFileTime) { + // Get return address of this hooked call. + uint64_t retAddress = (uint64_t)_ReturnAddress(); - return 0; + // Get the start and end of the base process module (the main .exe). + MODULEINFO moduleInfo; + GetModuleInformation(GetCurrentProcess(), GetModuleHandle(NULL), &moduleInfo, sizeof(MODULEINFO)); + uint64_t baseModStart = (uint64_t)moduleInfo.lpBaseOfDll; + uint64_t baseModEnd = (uint64_t)moduleInfo.lpBaseOfDll + (uint64_t)moduleInfo.lpBaseOfDll; + + // If we are being called from within the module memory (e.g. not a random library), + // then we can assume this is likely the real code post-unpack. + if (retAddress >= baseModStart && retAddress <= baseModEnd) { + spdlog::get("PreLoader")->info("Got GetSystemTimeAsFileTime call with valid return address. Base module start: {0:x}, Base module end: {1:x}, GetSystemTimeAsFileTime Return address: {2:x}", + baseModStart, + baseModEnd, + retAddress); + + // Enable our hooks and, if successful, disable the GetSystemTimeAsFileTime as we will not need it anymore. + if (EnableCoreHooks()) { + spdlog::get("PreLoader")->error("Failed to enable core hooks!"); + return; + } + + else { + spdlog::get("PreLoader")->info("Enabled core hooks"); + + if (MH_DisableHook(GetSystemTimeAsFileTime) != MH_OK) { + spdlog::get("PreLoader")->error("Failed to disable GetSystemTimeAsFileTime hook"); + return; + } + } + } + + fpGetSystemTimeAsFileTime(lpSystemTimeAsFileTime); } // This function is called from the loader-locked DllMain, // does the bare-minimum to get control flow in the main thread -// by hooking the CRT startup and bypassing capcom's anti-tamper code. +// by hooking a function called in the CRT startup (GetSystemTimeAsFileTime) to +// bypass capcom's anti-tamper code early before any C++ static initalizers are called. int LoaderLockedInitialize() { Log::InitializePreLogger(); - spdlog::get("PreLoader")->info("Begin enabling core hooks on thread: {0}", GetCurrentThreadId()); - if (EnableCoreHooks()) { - spdlog::get("PreLoader")->error("Failed to enable core hooks!"); + spdlog::get("PreLoader")->info("Begin enabling post-unpack hooks on thread: {0}", GetCurrentThreadId()); + + if (MH_Initialize() != MH_OK) + { + spdlog::get("PreLoader")->error("Failed to initialize minhook!"); + return 1; + } + + // Hook GetSystemTimeAsFileTime in order to get control of execution after the .exe is unpacked in memory. + if (MH_CreateHook(GetSystemTimeAsFileTime, &hookedGetSystemTimeAsFileTime, reinterpret_cast(&fpGetSystemTimeAsFileTime)) != MH_OK) { + spdlog::get("PreLoader")->error("Failed to create GetSystemTimeAsFileTime hook"); return 1; } - spdlog::get("PreLoader")->info("Enabled core hooks"); + if (MH_EnableHook(GetSystemTimeAsFileTime) != MH_OK) { + spdlog::get("PreLoader")->error("Failed to enable GetSystemTimeAsFileTime hook"); + return 1; + } + return 0; }