diff --git a/tools/steamclient_loader/win/extra_protection/dllmain.cpp b/tools/steamclient_loader/win/extra_protection/dllmain.cpp index 1441098a..598ccebe 100644 --- a/tools/steamclient_loader/win/extra_protection/dllmain.cpp +++ b/tools/steamclient_loader/win/extra_protection/dllmain.cpp @@ -7,7 +7,11 @@ #include #include #include +#include +#if defined(DEBUG) || defined(_DEBUG) + #define STUB_EXTRA_DEBUG +#endif static std::mutex dll_unload_mtx{}; static std::condition_variable dll_unload_cv{}; @@ -29,7 +33,7 @@ static void send_unload_signal() DWORD WINAPI self_unload(LPVOID lpParameter) { constexpr const auto UNLOAD_TIMEOUT = -#ifdef _DEBUG +#ifdef STUB_EXTRA_DEBUG std::chrono::minutes(5) #else std::chrono::seconds(5) @@ -37,8 +41,21 @@ DWORD WINAPI self_unload(LPVOID lpParameter) ; { +#ifdef STUB_EXTRA_DEBUG + auto t1 = std::chrono::high_resolution_clock::now(); +#endif + std::unique_lock lock(dll_unload_mtx); - dll_unload_cv.wait_for(lock, UNLOAD_TIMEOUT, [](){ return unload_dll; }); + dll_unload_cv.wait_for(lock, UNLOAD_TIMEOUT, []{ return unload_dll; }); + +#ifdef STUB_EXTRA_DEBUG + if (!unload_dll) { // flag was not raised, means we timed out + auto t2 = std::chrono::high_resolution_clock::now(); + auto dd = std::chrono::duration_cast(t2 - t1); + std::string msg = "Unloading after " + std::to_string(dd.count()) + " seconds, due to timeout"; + MessageBoxA(nullptr, msg.c_str(), "Self-unload thread", MB_OK | MB_ICONERROR); + } +#endif } unload_thread_handle = INVALID_HANDLE_VALUE; FreeLibraryAndExitThread(my_hModule, 0); @@ -52,19 +69,27 @@ BOOL APIENTRY DllMain( switch (reason) { case DLL_PROCESS_ATTACH: + stubdrm::set_cleanup_cb(send_unload_signal); + my_hModule = hModule; if (!stubdrm::patch()) { +#ifdef STUB_EXTRA_DEBUG + MessageBoxA(nullptr, "Failed to detect .bind", "Main", MB_OK | MB_ICONERROR); +#endif + // https://learn.microsoft.com/en-us/windows/win32/dlls/dllmain // "The system immediately calls your entry-point function with DLL_PROCESS_DETACH and unloads the DLL" unload_dll = true; return FALSE; } - my_hModule = hModule; - stubdrm::set_cleanup_cb(send_unload_signal); unload_thread_handle = CreateThread(nullptr, 0, self_unload, nullptr, 0, nullptr); break; case DLL_PROCESS_DETACH: if (!unload_dll) { // not unloaded yet, just an early exit, or thread timed out +#ifdef STUB_EXTRA_DEBUG + MessageBoxA(nullptr, "Unclean exit", "Main", MB_OK | MB_ICONERROR); +#endif + stubdrm::restore(); if (unload_thread_handle != INVALID_HANDLE_VALUE && unload_thread_handle != NULL) { TerminateThread(unload_thread_handle, 0); diff --git a/tools/steamclient_loader/win/extra_protection/stubdrm.cpp b/tools/steamclient_loader/win/extra_protection/stubdrm.cpp index b1308062..609c3435 100644 --- a/tools/steamclient_loader/win/extra_protection/stubdrm.cpp +++ b/tools/steamclient_loader/win/extra_protection/stubdrm.cpp @@ -436,60 +436,79 @@ static bool restore_win32_apis() } -static std::vector get_pe_header_disk() -{ - const std::string filepath = pe_helpers::get_current_exe_path() + pe_helpers::get_current_exe_name(); - try { - std::ifstream file(std::filesystem::u8path(filepath), std::ios::in | std::ios::binary); - if (!file) return {}; - - // 2MB is enough - std::vector data(2 * 1024 * 1024, 0); - file.read((char *)&data[0], data.size()); - file.close(); - - return data; - } catch(...) { } - - return {}; -} - static bool calc_bind_section_boundaries() { - auto bind_section = pe_helpers::get_section_header_with_name(((HMODULE)exe_addr_base), ".bind"); - if (bind_section) { - bind_addr_base = exe_addr_base + bind_section->VirtualAddress; + constexpr static auto calc_bind_section_boundaries_from_mem = [] { + auto bind_section = pe_helpers::get_section_header_with_name(reinterpret_cast(exe_addr_base), ".bind"); + if (!bind_section || !bind_section->VirtualAddress) return false; + + uint8_t * const bind_start = exe_addr_base + bind_section->VirtualAddress; + uint8_t *bind_end = nullptr; + MEMORY_BASIC_INFORMATION mbi{}; - if (VirtualQuery((LPVOID)bind_addr_base, &mbi, sizeof(mbi)) && mbi.RegionSize > 0) { - bind_addr_end = bind_addr_base + mbi.RegionSize; + if (VirtualQuery((LPVOID)bind_start, &mbi, sizeof(mbi)) && mbi.RegionSize > 0) { + bind_end = bind_start + mbi.RegionSize; } else if (bind_section->Misc.VirtualSize > 0) { - bind_addr_end = bind_addr_base + bind_section->Misc.VirtualSize; + bind_end = bind_start + bind_section->Misc.VirtualSize; } else { return false; } + + bind_addr_base = bind_start; + bind_addr_end = bind_end; + return true; + }; + + constexpr static auto calc_bind_section_boundaries_from_disk = [] { + constexpr static auto get_pe_header_from_disk = []() -> std::vector { + try { + const std::string filepath = pe_helpers::get_current_exe_path() + pe_helpers::get_current_exe_name(); + std::ifstream file(std::filesystem::u8path(filepath), std::ios::in | std::ios::binary); + if (!file) return {}; + + // 2MB is enough + std::vector data(2 * 1024 * 1024, 0); + file.read((char *)&data[0], data.size()); + file.close(); + + return data; + } catch(...) { } + + return {}; + }; + + auto disk_header = get_pe_header_from_disk(); + if (disk_header.empty()) return false; + + auto bind_section = pe_helpers::get_section_header_with_name(reinterpret_cast(&disk_header[0]), ".bind"); + if (!bind_section || !bind_section->VirtualAddress) return false; + if (!bind_section->Misc.VirtualSize) return false; + + bind_addr_base = exe_addr_base + bind_section->VirtualAddress; + bind_addr_end = bind_addr_base + bind_section->Misc.VirtualSize; + return true; + }; + + + // appid 2677660 (build 16659541) changes the PIMAGE_OPTIONAL_HEADER->SizeOfHeaders to a size less than the actual, + // so that the ".bind" section *looks* as if it exists in memory (IMAGE_SECTION_HEADER->Name is valid), but its data is 0/nulled + if (calc_bind_section_boundaries_from_mem()) { return true; } - // we don't *seem* to have .bind section *in memory* - // appid 1732190 changes the PIMAGE_OPTIONAL_HEADER->SizeOfHeaders to a size less than the actual, - // subtracting the size of the last section, i.e ".bind" section (original size = 0x600 >>> decreased to 0x400) + // otherwise we *seem* to be missing the .bind section *in memory*, but not necessarily + // appid 1732190 also changes the PIMAGE_OPTIONAL_HEADER->SizeOfHeaders to a size less than the actual + // by subtracting the size of the last section, i.e ".bind" section (original size = 0x600 >>> decreased to 0x400) // that way whenever the .exe is loaded in memory, the Windows loader will ignore populating the PE header with the info // of that section *in memory* since it is not taken into consideration, but the PE header *on disk* still contains the info // // also the PIMAGE_FILE_HEADER->NumberOfSections is kept intact, otherwise the PIMAGE_OPTIONAL_HEADER->AddressOfEntryPoint // would be pointing at a non-existent section and the .exe won't work - auto disk_header = get_pe_header_disk(); - if (disk_header.empty()) return false; - - bind_section = pe_helpers::get_section_header_with_name(((HMODULE)&disk_header[0]), ".bind"); - if (!bind_section) return false; - - bind_addr_base = exe_addr_base + bind_section->VirtualAddress; - if (!bind_section->Misc.VirtualSize) return false; - - bind_addr_end = bind_addr_base + bind_section->Misc.VirtualSize; - - return true; + if (calc_bind_section_boundaries_from_disk()) { + return true; + } + + return false; }