mirror of
https://github.com/Detanup01/gbe_fork.git
synced 2024-11-27 05:04:01 +08:00
support older variant + auto unload on success or timeout
This commit is contained in:
parent
d40d306ccd
commit
ed5b745e09
@ -1,22 +1,77 @@
|
|||||||
#include "pe_helpers/pe_helpers.hpp"
|
#include "extra_protection/stubdrm.hpp"
|
||||||
#include "extra_protection/stubdrm.hpp"
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
BOOL APIENTRY DllMain(
|
#include <Windows.h>
|
||||||
HMODULE hModule,
|
|
||||||
DWORD reason,
|
#include <condition_variable>
|
||||||
LPVOID lpReserved)
|
#include <mutex>
|
||||||
{
|
#include <chrono>
|
||||||
switch (reason)
|
#include <thread>
|
||||||
{
|
|
||||||
case DLL_PROCESS_ATTACH:
|
|
||||||
stubdrm::patch();
|
static std::mutex dll_unload_mtx{};
|
||||||
break;
|
static std::condition_variable dll_unload_cv{};
|
||||||
case DLL_THREAD_ATTACH:
|
static bool unload_dll = false;
|
||||||
case DLL_THREAD_DETACH:
|
|
||||||
break;
|
static HMODULE my_hModule = nullptr;
|
||||||
case DLL_PROCESS_DETACH:
|
static HANDLE unload_thread_handle = INVALID_HANDLE_VALUE;
|
||||||
stubdrm::restore();
|
|
||||||
break;
|
|
||||||
}
|
static void send_unload_signal()
|
||||||
return TRUE;
|
{
|
||||||
}
|
{
|
||||||
|
std::lock_guard lock(dll_unload_mtx);
|
||||||
|
unload_dll = true;
|
||||||
|
}
|
||||||
|
dll_unload_cv.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI self_unload(LPVOID lpParameter)
|
||||||
|
{
|
||||||
|
constexpr const auto UNLOAD_TIMEOUT =
|
||||||
|
#ifdef _DEBUG
|
||||||
|
std::chrono::minutes(5)
|
||||||
|
#else
|
||||||
|
std::chrono::seconds(5)
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock lock(dll_unload_mtx);
|
||||||
|
dll_unload_cv.wait_for(lock, UNLOAD_TIMEOUT, [](){ return unload_dll; });
|
||||||
|
}
|
||||||
|
unload_thread_handle = INVALID_HANDLE_VALUE;
|
||||||
|
FreeLibraryAndExitThread(my_hModule, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL APIENTRY DllMain(
|
||||||
|
HMODULE hModule,
|
||||||
|
DWORD reason,
|
||||||
|
LPVOID lpReserved)
|
||||||
|
{
|
||||||
|
switch (reason)
|
||||||
|
{
|
||||||
|
case DLL_PROCESS_ATTACH:
|
||||||
|
if (!stubdrm::patch()) {
|
||||||
|
// 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
|
||||||
|
stubdrm::restore();
|
||||||
|
if (unload_thread_handle != INVALID_HANDLE_VALUE && unload_thread_handle != NULL) {
|
||||||
|
TerminateThread(unload_thread_handle, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
namespace stubdrm
|
||||||
#include <vector>
|
{
|
||||||
|
bool patch();
|
||||||
namespace stubdrm
|
bool restore();
|
||||||
{
|
|
||||||
bool patch();
|
void set_cleanup_cb(void (*fn)());
|
||||||
|
}
|
||||||
bool restore();
|
|
||||||
}
|
|
||||||
|
@ -1,379 +1,514 @@
|
|||||||
#include "pe_helpers/pe_helpers.hpp"
|
#include "extra_protection/stubdrm.hpp"
|
||||||
#include "common_helpers/common_helpers.hpp"
|
#include "pe_helpers/pe_helpers.hpp"
|
||||||
#include "extra_protection/stubdrm.hpp"
|
#include "common_helpers/common_helpers.hpp"
|
||||||
#include "detours/detours.h"
|
#include "detours/detours.h"
|
||||||
#include <tuple>
|
#include <string>
|
||||||
#include <mutex>
|
#include <vector>
|
||||||
#include <intrin.h>
|
#include <tuple>
|
||||||
|
#include <mutex>
|
||||||
// MinGW doesn't implement _AddressOfReturnAddress(), throws linker error
|
#include <intrin.h>
|
||||||
// https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html
|
|
||||||
#if defined(__GNUC__) && (defined(__MINGW32__) || defined(__MINGW64__))
|
// MinGW doesn't implement _AddressOfReturnAddress(), throws linker error
|
||||||
#define ADDR_OF_RET_ADDR() ((void*)((char*)__builtin_frame_address(0) + sizeof(void*)))
|
// https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html
|
||||||
#else // regular windows
|
// https://learn.microsoft.com/en-us/cpp/intrinsics/addressofreturnaddress
|
||||||
#define ADDR_OF_RET_ADDR() _AddressOfReturnAddress()
|
#if defined(__GNUC__) && (defined(__MINGW32__) || defined(__MINGW64__))
|
||||||
#endif
|
#define ADDR_OF_RET_ADDR() ((void*)((char*)__builtin_frame_address(0) + sizeof(void*)))
|
||||||
|
#else // regular windows
|
||||||
typedef struct SnrUnit {
|
#define ADDR_OF_RET_ADDR() _AddressOfReturnAddress()
|
||||||
std::string search_patt{};
|
#endif
|
||||||
std::string replace_patt{};
|
|
||||||
} SnrUnit_t;
|
typedef struct _SnrUnit_t {
|
||||||
|
std::string search_patt{};
|
||||||
typedef struct SnrDetails {
|
std::string replace_patt{};
|
||||||
std::string detection_patt{};
|
} SnrUnit_t;
|
||||||
bool change_mem_access = false;
|
|
||||||
std::vector<SnrUnit_t> snr_units{};
|
typedef struct _StubSnrDetails_t {
|
||||||
} SnrDetails_t;
|
std::string stub_detection_patt{}; // inside the dynamically allocated stub
|
||||||
|
bool change_mem_access = false;
|
||||||
// x64
|
std::vector<SnrUnit_t> stub_snr_units{};
|
||||||
#if defined(_WIN64)
|
} StubSnrDetails_t;
|
||||||
static const std::vector<SnrDetails> snr_patts {
|
|
||||||
{
|
typedef struct _BindSnrDetails_t {
|
||||||
// detection_patt
|
std::string bind_detection_patt{}; // inside .bind
|
||||||
"FF 94 24 ?? ?? ?? ?? 88 44 24 ?? 0F BE 44 24 ?? 83 ?? 30 74 ?? E9",
|
std::vector<StubSnrDetails_t> stub_details{};
|
||||||
// change memory pages access to r/w/e
|
} BindSnrDetails_t;
|
||||||
false,
|
|
||||||
// snr_units
|
static const std::vector<BindSnrDetails_t> all_bind_details {
|
||||||
{
|
|
||||||
// patt 1 is a bunch of checks for registry + files validity (including custom DOS stub)
|
// x64
|
||||||
// patt 2 is again a bunch of checks + creates some interfaces via steamclient + calls getappownershipticket()
|
#if defined(_WIN64)
|
||||||
{
|
{
|
||||||
"E8 ?? ?? ?? ?? 84 C0 75 ?? B0 33 E9",
|
// bind_detection_patt
|
||||||
"B8 01 00 00 00 ?? ?? EB",
|
"FF 94 24 ?? ?? ?? ?? 88 44 24 ?? 0F BE 44 24 ?? 83 ?? 30 74 ?? E9", // appid 1684350
|
||||||
},
|
// stub_details
|
||||||
{
|
{
|
||||||
"E8 ?? ?? ?? ?? 44 0F B6 ?? 3C 30 0F 84 ?? ?? ?? ?? 3C 35 0F 85",
|
{
|
||||||
"B8 30 00 00 00 ?? ?? ?? ?? ?? ?? 90 E9",
|
// stub_detection_patt
|
||||||
},
|
"??",
|
||||||
},
|
// change memory pages access to r/w/e
|
||||||
},
|
false,
|
||||||
|
// stub_snr_units
|
||||||
{
|
{
|
||||||
// detection_patt
|
// patt 1 is a bunch of checks for registry + files validity (including custom DOS stub)
|
||||||
"FF D? 44 0F B6 ?? 3C 30 0F 85",
|
// patt 2 is again a bunch of checks + creates some interfaces via steamclient + calls getappownershipticket()
|
||||||
// change memory pages access to r/w/e
|
{
|
||||||
false,
|
"E8 ?? ?? ?? ?? 84 C0 75 ?? B0 3?",
|
||||||
// snr_units
|
"B8 01 00 00 00 ?? ?? EB",
|
||||||
{
|
},
|
||||||
{
|
{
|
||||||
"E8 ?? ?? ?? ?? 44 0F B6 ?? 3C 30 0F 84 ?? ?? ?? ?? 3C ?? 0F 85",
|
"E8 ?? ?? ?? ?? 44 0F B6 ?? 3C 30 0F 84 ?? ?? ?? ?? 3C",
|
||||||
"B8 30 00 00 00 ?? ?? ?? ?? ?? ?? 90 E9 ?? ?? ?? ?? ?? ?? ?? ??",
|
"B8 30 00 00 00 ?? ?? ?? ?? ?? ?? 90 E9",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
|
},
|
||||||
#endif
|
|
||||||
|
{
|
||||||
// x32
|
// bind_detection_patt
|
||||||
#if !defined(_WIN64)
|
"FF D? 44 0F B6 ?? 3C 30 0F 85", // appid: 537450 (rare, only found in this appid!)
|
||||||
|
// stub_details
|
||||||
static const std::vector<SnrDetails> snr_patts {
|
{
|
||||||
{
|
{
|
||||||
// detection_patt
|
// stub_detection_patt
|
||||||
"FF 95 ?? ?? ?? ?? 88 45 ?? 0F BE 4D ?? 83 ?? 30 74 ?? E9",
|
"??",
|
||||||
// change memory pages access to r/w/e
|
// change memory pages access to r/w/e
|
||||||
false,
|
false,
|
||||||
// snr_units
|
// stub_snr_units
|
||||||
{
|
{
|
||||||
// patt 1 is a bunch of checks for registry + files validity (including custom DOS stub)
|
// patt 1 is a bunch of checks for registry + files validity (including custom DOS stub)
|
||||||
// patt 2 is again a bunch of checks + creates some interfaces via steamclient + calls getappownershipticket()
|
// patt 2 is again a bunch of checks + creates some interfaces via steamclient + calls getappownershipticket()
|
||||||
{
|
{
|
||||||
"5? 5? E8 ?? ?? ?? ?? 83 C4 08 84 C0 75 ?? B0 33",
|
"E8 ?? ?? ?? ?? 84 C0 75 ?? B0 3?",
|
||||||
"?? ?? B8 01 00 00 00 ?? ?? ?? ?? ?? EB",
|
"B8 01 00 00 00 ?? ?? EB",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"E8 ?? ?? ?? ?? 83 C4 ?? 88 45 ?? 3C 30 0F 84 ?? ?? ?? ?? 3C 35 75",
|
"E8 ?? ?? ?? ?? 44 0F B6 ?? 3C 30 0F 84 ?? ?? ?? ?? 3C",
|
||||||
"B8 30 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? 90 E9",
|
"B8 30 00 00 00 ?? ?? ?? ?? ?? ?? 90 E9",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
},
|
||||||
// detection_patt
|
|
||||||
"FF 95 ?? ?? ?? ?? 89 85 ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 89 ?? ?? ?? ?? ?? 8B ?? ?? 89 ?? ?? ?? ?? ?? 83 A5 ?? ?? ?? ?? ?? EB",
|
#endif // x64
|
||||||
// change memory pages access to r/w/e
|
|
||||||
true,
|
// x32
|
||||||
// snr_units
|
#if !defined(_WIN64)
|
||||||
{
|
{
|
||||||
{
|
// bind_detection_patt
|
||||||
"F6 C? 02 0F 85 ?? ?? ?? ?? 5? FF ?? 6?",
|
"FF 95 ?? ?? ?? ?? 88 45 ?? 0F BE 4D ?? 83 ?? 30 74 ?? E9", // appid 588650
|
||||||
"?? ?? ?? 90 E9 00 03",
|
// stub_details
|
||||||
},
|
{
|
||||||
{
|
{
|
||||||
"F6 C? 02 89 ?? ?? ?? ?? ?? A3 ?? ?? ?? ?? 0F 85",
|
// stub_detection_patt
|
||||||
"?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 90 E9 00 03 00 00",
|
"??",
|
||||||
},
|
// change memory pages access to r/w/e
|
||||||
{
|
false,
|
||||||
"F6 05 ?? ?? ?? ?? 02 89 ?? ?? 0F 85 ?? ?? ?? ?? 5? FF ?? 6?",
|
// stub_snr_units
|
||||||
"?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 90 E9 03 03",
|
{
|
||||||
},
|
// patt 1 is a bunch of checks for registry + files validity (including custom DOS stub)
|
||||||
},
|
// patt 2 is again a bunch of checks + creates some interfaces via steamclient + calls getappownershipticket()
|
||||||
},
|
{
|
||||||
|
"5? 5? E8 ?? ?? ?? ?? 83 C4 08 84 C0 75",
|
||||||
{
|
"?? ?? B8 01 00 00 00 ?? ?? ?? ?? ?? EB",
|
||||||
// detection_patt
|
},
|
||||||
"FF D? 88 45 ?? 3C 30 0F 85 ?? ?? ?? ?? B8 4D 5A",
|
{
|
||||||
// change memory pages access to r/w/e
|
"E8 ?? ?? ?? ?? 83 C4 ?? 88 45 ?? 3C 30 0F 84 ?? ?? ?? ?? 3C 3?",
|
||||||
false,
|
"B8 30 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? 90 E9",
|
||||||
// snr_units
|
},
|
||||||
{
|
},
|
||||||
{
|
},
|
||||||
"5? E8 ?? ?? ?? ?? 83 C4 ?? 88 45 ?? 3C 30 0F 84",
|
},
|
||||||
"?? B8 30 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? 90 E9",
|
},
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"5? E8 ?? ?? ?? ?? 8? ?? 83 C4 ?? 8? F? 30 74 ?? 8?",
|
// bind_detection_patt
|
||||||
"?? B8 30 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? EB",
|
"FF 95 ?? ?? ?? ?? 89 85 ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 89 ?? ?? ?? ?? ?? 8B ?? ?? 89 ?? ?? ?? ?? ?? 83 A5 ?? ?? ?? ?? ?? EB", // appid 201790
|
||||||
},
|
// stub_details
|
||||||
},
|
{
|
||||||
},
|
{
|
||||||
|
// stub_detection_patt
|
||||||
};
|
"??",
|
||||||
|
// change memory pages access to r/w/e
|
||||||
#endif // _WIN64
|
true, // appid 48000
|
||||||
|
// stub_snr_units
|
||||||
|
{
|
||||||
static size_t current_snr_details = static_cast<size_t>(-1);
|
{
|
||||||
|
"F6 C? 02 0F 85 ?? ?? ?? ?? 5? FF ?? 6?",
|
||||||
static std::recursive_mutex mtx_win32_api{};
|
"?? ?? ?? 90 E9 00 03",
|
||||||
static uint8_t *exe_addr_base = (uint8_t *)GetModuleHandleW(NULL);
|
},
|
||||||
static uint8_t *bind_addr_base = nullptr;
|
{
|
||||||
static uint8_t *bind_addr_end = nullptr;
|
"F6 C? 02 89 ?? ?? ?? ?? ?? A3 ?? ?? ?? ?? 0F 85",
|
||||||
|
"?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 90 E9 00 03 00 00",
|
||||||
static bool restore_win32_apis();
|
},
|
||||||
|
{ // appid 250180
|
||||||
// stub v2 needs manual change for .text, section must have write access
|
"F6 05 ?? ?? ?? ?? 02 89 ?? ?? 0F 85 ?? ?? ?? ?? 5? FF ?? 6?",
|
||||||
static void change_mem_pages_access()
|
"?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 90 E9 03 03",
|
||||||
{
|
},
|
||||||
auto sections = pe_helpers::get_section_headers((HMODULE)exe_addr_base);
|
},
|
||||||
if (!sections.count) return;
|
},
|
||||||
|
},
|
||||||
for (size_t i = 0; i < sections.count; ++i) {
|
},
|
||||||
auto section = sections.ptr[i];
|
|
||||||
uint8_t *section_base_addr = exe_addr_base + section.VirtualAddress;
|
{
|
||||||
MEMORY_BASIC_INFORMATION mbi{};
|
// bind_detection_patt
|
||||||
constexpr const static auto ANY_EXECUTE_RIGHT = PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
|
"FF D? 88 45 ?? 3C 30 0F 85 ?? ?? ?? ?? B8 4D 5A",
|
||||||
if (VirtualQuery((LPCVOID)section_base_addr, &mbi, sizeof(mbi)) && // function succeeded
|
// stub_details
|
||||||
(mbi.Protect & ANY_EXECUTE_RIGHT)) { // this page (not entire section) has execute rights
|
{
|
||||||
DWORD current_protection = 0;
|
{
|
||||||
auto res = VirtualProtect(section_base_addr, mbi.RegionSize, PAGE_EXECUTE_READWRITE, ¤t_protection);
|
// stub_detection_patt
|
||||||
// if (!res) {
|
"??",
|
||||||
// MessageBoxA(
|
// change memory pages access to r/w/e
|
||||||
// nullptr,
|
false,
|
||||||
// (std::string("Failed to change access of page '") + (char *)section.Name + "' ").c_str(),
|
// stub_snr_units
|
||||||
// "Failed",
|
{
|
||||||
// MB_OK
|
{
|
||||||
// );
|
"5? E8 ?? ?? ?? ?? 83 C4 ?? 88 45 ?? 3C 30 0F 84",
|
||||||
// }
|
"?? B8 30 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? 90 E9",
|
||||||
}
|
},
|
||||||
}
|
{
|
||||||
|
"5? E8 ?? ?? ?? ?? 8? ?? 83 C4 ?? 8? F? 30 74 ?? 8?",
|
||||||
|
"?? B8 30 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? EB",
|
||||||
}
|
},
|
||||||
|
},
|
||||||
static void patch_if_possible(void *ret_addr)
|
},
|
||||||
{
|
},
|
||||||
if (!ret_addr) return;
|
},
|
||||||
|
|
||||||
auto page_details = pe_helpers::get_mem_page_details(ret_addr);
|
{
|
||||||
if (!page_details.BaseAddress || page_details.AllocationProtect != PAGE_READWRITE) return;
|
// bind_detection_patt
|
||||||
|
"FF 95 ?? ?? ?? ?? 89 85 ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 89 ?? ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 89 ?? ?? ?? ?? ?? 83 A5 ?? ?? ?? ?? ?? EB", // appids: 31290, 94530, 37010
|
||||||
bool anything_found = false;
|
// stub_details
|
||||||
for (const auto &snr_unit : snr_patts[current_snr_details].snr_units) {
|
{
|
||||||
auto mem = pe_helpers::search_memory(
|
{ // appid 31290, 37010
|
||||||
(uint8_t *)page_details.BaseAddress,
|
// stub_detection_patt
|
||||||
page_details.RegionSize,
|
"F6 05 ?? ?? ?? ?? 04 0F 85 ?? ?? ?? ?? A1 ?? ?? ?? ?? 89",
|
||||||
snr_unit.search_patt);
|
// change memory pages access to r/w/e
|
||||||
|
false,
|
||||||
if (mem) {
|
// stub_snr_units
|
||||||
anything_found = true;
|
{
|
||||||
|
{
|
||||||
auto size_until_match = (uint8_t *)mem - (uint8_t *)page_details.BaseAddress;
|
"F6 C? 02 89 ?? ?? ?? ?? ?? A3 ?? ?? ?? ?? 0F 85",
|
||||||
bool ok = pe_helpers::replace_memory(
|
"?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 90 E9 57 02 00 00",
|
||||||
(uint8_t *)mem,
|
},
|
||||||
page_details.RegionSize - size_until_match,
|
},
|
||||||
snr_unit.replace_patt,
|
},
|
||||||
GetCurrentProcess());
|
|
||||||
// if (!ok) return;
|
{ // 94530
|
||||||
}
|
// stub_detection_patt
|
||||||
}
|
"84 ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? A1 ?? ?? ?? ?? 89",
|
||||||
|
// change memory pages access to r/w/e
|
||||||
if (anything_found) {
|
false,
|
||||||
restore_win32_apis();
|
// stub_snr_units
|
||||||
if (snr_patts[current_snr_details].change_mem_access) change_mem_pages_access();
|
{
|
||||||
}
|
{
|
||||||
|
"F6 C? 02 89 ?? ?? ?? ?? ?? A3 ?? ?? ?? ?? 0F 85",
|
||||||
// MessageBoxA(NULL, ("ret addr = " + std::to_string((size_t)ret_addr)).c_str(), "Patched", MB_OK);
|
"?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 90 E9 2A 02 00 00",
|
||||||
}
|
},
|
||||||
|
{
|
||||||
// https://learn.microsoft.com/en-us/cpp/intrinsics/addressofreturnaddress
|
"6A 04 5? 5? 8D",
|
||||||
static bool GetTickCount_hooked = false;
|
"?? ?? ?? E9 BF 00 00 00",
|
||||||
static decltype(GetTickCount) *actual_GetTickCount = GetTickCount;
|
},
|
||||||
__declspec(noinline)
|
},
|
||||||
static DWORD WINAPI GetTickCount_hook()
|
},
|
||||||
{
|
},
|
||||||
std::lock_guard lk(mtx_win32_api);
|
},
|
||||||
|
|
||||||
if (GetTickCount_hooked) { // american truck doesn't call GetModuleHandleA
|
#endif // x32
|
||||||
void* *ret_ptr = (void**)ADDR_OF_RET_ADDR();
|
|
||||||
patch_if_possible(*ret_ptr);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return actual_GetTickCount();
|
static size_t current_bind_idx = static_cast<size_t>(-1);
|
||||||
}
|
|
||||||
|
static uint8_t *exe_addr_base = (uint8_t *)GetModuleHandleW(nullptr);
|
||||||
static bool GetModuleHandleA_hooked = false;
|
static uint8_t *bind_addr_base = nullptr;
|
||||||
static decltype(GetModuleHandleA) *actual_GetModuleHandleA = GetModuleHandleA;
|
static uint8_t *bind_addr_end = nullptr;
|
||||||
__declspec(noinline)
|
|
||||||
static HMODULE WINAPI GetModuleHandleA_hook(
|
// this mutex is used to halt/defer the execution of threads if they tried to use the hooked functions at the same time
|
||||||
LPCSTR lpModuleName
|
// just in case
|
||||||
)
|
static std::recursive_mutex mtx_win32_api{};
|
||||||
{
|
// these flags are used as a fallback in case Detours lib failed to restore the hooks
|
||||||
std::lock_guard lk(mtx_win32_api);
|
static bool GetTickCount_hooked = false;
|
||||||
|
static bool GetModuleHandleA_hooked = false;
|
||||||
if (GetModuleHandleA_hooked &&
|
static bool GetModuleHandleExA_hooked = false;
|
||||||
lpModuleName &&
|
void (*cleanup_cb)() = nullptr;
|
||||||
common_helpers::ends_with_i(lpModuleName, "ntdll.dll")) {
|
|
||||||
void* *ret_ptr = (void**)ADDR_OF_RET_ADDR();
|
|
||||||
patch_if_possible(*ret_ptr);
|
// old stub variant (found in appid 201790) needs manual change for .text section, it must have write access
|
||||||
}
|
static void change_mem_pages_access()
|
||||||
|
{
|
||||||
return actual_GetModuleHandleA(lpModuleName);
|
auto sections = pe_helpers::get_section_headers((HMODULE)exe_addr_base);
|
||||||
}
|
if (!sections.count) return;
|
||||||
|
|
||||||
static bool GetModuleHandleExA_hooked = false;
|
constexpr const static unsigned ANY_EXECUTE_RIGHT = PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
|
||||||
static decltype(GetModuleHandleExA) *actual_GetModuleHandleExA = GetModuleHandleExA;
|
for (size_t i = 0; i < sections.count; ++i) {
|
||||||
__declspec(noinline)
|
auto section = sections.ptr[i];
|
||||||
static BOOL WINAPI GetModuleHandleExA_hook(
|
uint8_t *section_base_addr = exe_addr_base + section.VirtualAddress;
|
||||||
DWORD dwFlags,
|
MEMORY_BASIC_INFORMATION mbi{};
|
||||||
LPCSTR lpModuleName,
|
if (VirtualQuery((LPCVOID)section_base_addr, &mbi, sizeof(mbi)) && // function succeeded
|
||||||
HMODULE *phModule
|
(mbi.Protect & ANY_EXECUTE_RIGHT)) { // this page (not entire section) has execute rights
|
||||||
)
|
DWORD current_protection = 0;
|
||||||
{
|
auto res = VirtualProtect(section_base_addr, mbi.RegionSize, PAGE_EXECUTE_READWRITE, ¤t_protection);
|
||||||
std::lock_guard lk(mtx_win32_api);
|
// if (!res) {
|
||||||
|
// MessageBoxA(
|
||||||
if (GetModuleHandleExA_hooked &&
|
// nullptr,
|
||||||
(dwFlags == (GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT)) &&
|
// (std::string("Failed to change access of page '") + (char *)section.Name + "' ").c_str(),
|
||||||
((uint8_t *)lpModuleName >= bind_addr_base && (uint8_t *)lpModuleName < bind_addr_end)) {
|
// "Failed",
|
||||||
void* *ret_ptr = (void**)ADDR_OF_RET_ADDR();
|
// MB_OK
|
||||||
patch_if_possible(*ret_ptr);
|
// );
|
||||||
}
|
// }
|
||||||
|
}
|
||||||
return actual_GetModuleHandleExA(dwFlags, lpModuleName, phModule);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static bool redirect_win32_apis()
|
}
|
||||||
{
|
|
||||||
if (DetourTransactionBegin() != NO_ERROR) return false;
|
static void call_cleanup_cb()
|
||||||
if (DetourUpdateThread(GetCurrentThread()) != NO_ERROR) return false;
|
{
|
||||||
|
if (cleanup_cb) {
|
||||||
if (DetourAttach((PVOID *)&actual_GetTickCount, (PVOID)GetTickCount_hook) != NO_ERROR) return false;
|
cleanup_cb();
|
||||||
if (DetourAttach((PVOID *)&actual_GetModuleHandleA, (PVOID)GetModuleHandleA_hook) != NO_ERROR) return false;
|
}
|
||||||
if (DetourAttach((PVOID *)&actual_GetModuleHandleExA, (PVOID)GetModuleHandleExA_hook) != NO_ERROR) return false;
|
}
|
||||||
bool ret = DetourTransactionCommit() == NO_ERROR;
|
|
||||||
if (ret) {
|
static bool restore_win32_apis();
|
||||||
GetTickCount_hooked = true;
|
static void patch_if_possible(void *ret_addr)
|
||||||
GetModuleHandleA_hooked = true;
|
{
|
||||||
GetModuleHandleExA_hooked = true;
|
if (!ret_addr) return;
|
||||||
}
|
|
||||||
return ret;
|
auto page_details = pe_helpers::get_mem_page_details(ret_addr);
|
||||||
}
|
if (!page_details.BaseAddress || page_details.AllocationProtect != PAGE_READWRITE) return;
|
||||||
|
|
||||||
static bool restore_win32_apis()
|
// find stub variant
|
||||||
{
|
const StubSnrDetails_t *current_stub = nullptr;
|
||||||
GetTickCount_hooked = false;
|
const auto &bind_details = all_bind_details[current_bind_idx];
|
||||||
GetModuleHandleA_hooked = false;
|
for (const auto &stub_details : bind_details.stub_details) {
|
||||||
GetModuleHandleExA_hooked = false;
|
auto mem = pe_helpers::search_memory(
|
||||||
|
(uint8_t *)page_details.BaseAddress,
|
||||||
if (DetourTransactionBegin() != NO_ERROR) return false;
|
page_details.RegionSize,
|
||||||
if (DetourUpdateThread(GetCurrentThread()) != NO_ERROR) return false;
|
stub_details.stub_detection_patt);
|
||||||
|
|
||||||
DetourDetach((PVOID *)&actual_GetTickCount, (PVOID)GetTickCount_hook);
|
if (mem) {
|
||||||
DetourDetach((PVOID *)&actual_GetModuleHandleA, (PVOID)GetModuleHandleA_hook);
|
current_stub = &stub_details;
|
||||||
DetourDetach((PVOID *)&actual_GetModuleHandleExA, (PVOID)GetModuleHandleExA_hook);
|
break;
|
||||||
return DetourTransactionCommit() == NO_ERROR;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<uint8_t> get_pe_header_disk()
|
if (!current_stub) {
|
||||||
{
|
// we can't remove hooks here, the drm allocates many pages with read/write access to decrypt other parts
|
||||||
const std::string filepath = pe_helpers::get_current_exe_path() + pe_helpers::get_current_exe_name();
|
// and their code also gets here, if we restore hooks then we can't patch the actual stub page (which comes much later)
|
||||||
try {
|
return;
|
||||||
std::ifstream file(std::filesystem::u8path(filepath), std::ios::in | std::ios::binary);
|
}
|
||||||
if (!file) return {};
|
|
||||||
|
// patch all snr units inside stub
|
||||||
// 2MB is enough
|
bool anything_found = false;
|
||||||
std::vector<uint8_t> data(2 * 1024 * 1024, 0);
|
for (const auto &snr_unit : current_stub->stub_snr_units) {
|
||||||
file.read((char *)&data[0], data.size());
|
auto mem = pe_helpers::search_memory(
|
||||||
file.close();
|
(uint8_t *)page_details.BaseAddress,
|
||||||
|
page_details.RegionSize,
|
||||||
return data;
|
snr_unit.search_patt);
|
||||||
} catch(...) { }
|
|
||||||
|
if (mem) {
|
||||||
return {};
|
anything_found = true;
|
||||||
}
|
|
||||||
|
auto size_until_match = (uint8_t *)mem - (uint8_t *)page_details.BaseAddress;
|
||||||
static bool calc_bind_section_boundaries()
|
bool ok = pe_helpers::replace_memory(
|
||||||
{
|
(uint8_t *)mem,
|
||||||
auto bind_section = pe_helpers::get_section_header_with_name(((HMODULE)exe_addr_base), ".bind");
|
page_details.RegionSize - size_until_match,
|
||||||
if (bind_section) {
|
snr_unit.replace_patt,
|
||||||
bind_addr_base = exe_addr_base + bind_section->VirtualAddress;
|
GetCurrentProcess());
|
||||||
MEMORY_BASIC_INFORMATION mbi{};
|
// if (!ok) return;
|
||||||
if (VirtualQuery((LPVOID)bind_addr_base, &mbi, sizeof(mbi)) && mbi.RegionSize > 0) {
|
}
|
||||||
bind_addr_end = bind_addr_base + mbi.RegionSize;
|
}
|
||||||
} else if (bind_section->Misc.VirtualSize > 0) {
|
|
||||||
bind_addr_end = bind_addr_base + bind_section->Misc.VirtualSize;
|
if (anything_found) {
|
||||||
} else {
|
// MessageBoxA(NULL, ("ret addr = " + std::to_string((size_t)ret_addr)).c_str(), "Patched", MB_OK);
|
||||||
return false;
|
restore_win32_apis();
|
||||||
}
|
if (current_stub->change_mem_access) {
|
||||||
return true;
|
change_mem_pages_access();
|
||||||
}
|
}
|
||||||
|
call_cleanup_cb();
|
||||||
// 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)
|
|
||||||
// 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
|
static decltype(GetTickCount) *actual_GetTickCount = GetTickCount;
|
||||||
//
|
__declspec(noinline)
|
||||||
// also the PIMAGE_FILE_HEADER->NumberOfSections is kept intact, otherwise the PIMAGE_OPTIONAL_HEADER->AddressOfEntryPoint
|
static DWORD WINAPI GetTickCount_hook()
|
||||||
// would be pointing at a non-existent section and the .exe won't work
|
{
|
||||||
auto disk_header = get_pe_header_disk();
|
if (GetTickCount_hooked) { // unencrypted apps (like 270880 american truck) don't call GetModuleHandleA()
|
||||||
if (disk_header.empty()) return false;
|
std::lock_guard lk(mtx_win32_api);
|
||||||
|
if (GetTickCount_hooked) { // if we win arbitration and hooks are still intact
|
||||||
bind_section = pe_helpers::get_section_header_with_name(((HMODULE)&disk_header[0]), ".bind");
|
void* *ret_ptr = (void**)ADDR_OF_RET_ADDR();
|
||||||
if (!bind_section) return false;
|
patch_if_possible(*ret_ptr);
|
||||||
|
}
|
||||||
bind_addr_base = exe_addr_base + bind_section->VirtualAddress;
|
}
|
||||||
if (!bind_section->Misc.VirtualSize) return false;
|
|
||||||
|
return actual_GetTickCount();
|
||||||
bind_addr_end = bind_addr_base + bind_section->Misc.VirtualSize;
|
}
|
||||||
|
|
||||||
return true;
|
static decltype(GetModuleHandleA) *actual_GetModuleHandleA = GetModuleHandleA;
|
||||||
}
|
__declspec(noinline)
|
||||||
|
static HMODULE WINAPI GetModuleHandleA_hook(
|
||||||
bool stubdrm::patch()
|
LPCSTR lpModuleName
|
||||||
{
|
)
|
||||||
if (!calc_bind_section_boundaries()) return false;
|
{
|
||||||
|
if (GetModuleHandleA_hooked &&
|
||||||
auto addrOfEntry = exe_addr_base + pe_helpers::get_optional_header((HMODULE)exe_addr_base)->AddressOfEntryPoint;
|
lpModuleName && lpModuleName[0] &&
|
||||||
if (addrOfEntry < bind_addr_base || addrOfEntry >= bind_addr_end) return false; // entry addr is not inside .bind
|
common_helpers::ends_with_i(lpModuleName, "ntdll.dll")) {
|
||||||
|
std::lock_guard lk(mtx_win32_api);
|
||||||
for (const auto &patt : snr_patts) {
|
if (GetModuleHandleA_hooked) { // if we win arbitration and hooks are still intact
|
||||||
auto mem = pe_helpers::search_memory(
|
void* *ret_ptr = (void**)ADDR_OF_RET_ADDR();
|
||||||
bind_addr_base,
|
patch_if_possible(*ret_ptr);
|
||||||
static_cast<size_t>(bind_addr_end - bind_addr_base),
|
}
|
||||||
patt.detection_patt);
|
}
|
||||||
|
|
||||||
if (mem) {
|
return actual_GetModuleHandleA(lpModuleName);
|
||||||
current_snr_details = &patt - &snr_patts[0];
|
}
|
||||||
return redirect_win32_apis();
|
|
||||||
}
|
static decltype(GetModuleHandleExA) *actual_GetModuleHandleExA = GetModuleHandleExA;
|
||||||
}
|
__declspec(noinline)
|
||||||
|
static BOOL WINAPI GetModuleHandleExA_hook(
|
||||||
return false;
|
DWORD dwFlags,
|
||||||
}
|
LPCSTR lpModuleName,
|
||||||
|
HMODULE *phModule
|
||||||
bool stubdrm::restore()
|
)
|
||||||
{
|
{
|
||||||
return restore_win32_apis();
|
constexpr const static unsigned HANDLE_FROM_ADDR = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
|
||||||
}
|
if (GetModuleHandleExA_hooked &&
|
||||||
|
(dwFlags & HANDLE_FROM_ADDR) &&
|
||||||
|
((uint8_t *)lpModuleName >= bind_addr_base && (uint8_t *)lpModuleName < bind_addr_end)) {
|
||||||
|
std::lock_guard lk(mtx_win32_api);
|
||||||
|
if (GetModuleHandleExA_hooked) { // if we win arbitration and hooks are still intact
|
||||||
|
void* *ret_ptr = (void**)ADDR_OF_RET_ADDR();
|
||||||
|
patch_if_possible(*ret_ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return actual_GetModuleHandleExA(dwFlags, lpModuleName, phModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool redirect_win32_apis()
|
||||||
|
{
|
||||||
|
if (DetourTransactionBegin() != NO_ERROR) return false;
|
||||||
|
if (DetourUpdateThread(GetCurrentThread()) != NO_ERROR) return false;
|
||||||
|
|
||||||
|
if (DetourAttach((PVOID *)&actual_GetTickCount, (PVOID)GetTickCount_hook) != NO_ERROR) return false;
|
||||||
|
if (DetourAttach((PVOID *)&actual_GetModuleHandleA, (PVOID)GetModuleHandleA_hook) != NO_ERROR) return false;
|
||||||
|
if (DetourAttach((PVOID *)&actual_GetModuleHandleExA, (PVOID)GetModuleHandleExA_hook) != NO_ERROR) return false;
|
||||||
|
bool ret = DetourTransactionCommit() == NO_ERROR;
|
||||||
|
if (ret) {
|
||||||
|
GetTickCount_hooked = true;
|
||||||
|
GetModuleHandleA_hooked = true;
|
||||||
|
GetModuleHandleExA_hooked = true;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool restore_win32_apis()
|
||||||
|
{
|
||||||
|
GetTickCount_hooked = false;
|
||||||
|
GetModuleHandleA_hooked = false;
|
||||||
|
GetModuleHandleExA_hooked = false;
|
||||||
|
|
||||||
|
if (DetourTransactionBegin() != NO_ERROR) return false;
|
||||||
|
if (DetourUpdateThread(GetCurrentThread()) != NO_ERROR) return false;
|
||||||
|
|
||||||
|
DetourDetach((PVOID *)&actual_GetTickCount, (PVOID)GetTickCount_hook);
|
||||||
|
DetourDetach((PVOID *)&actual_GetModuleHandleA, (PVOID)GetModuleHandleA_hook);
|
||||||
|
DetourDetach((PVOID *)&actual_GetModuleHandleExA, (PVOID)GetModuleHandleExA_hook);
|
||||||
|
return DetourTransactionCommit() == NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static std::vector<uint8_t> 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<uint8_t> 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;
|
||||||
|
MEMORY_BASIC_INFORMATION mbi{};
|
||||||
|
if (VirtualQuery((LPVOID)bind_addr_base, &mbi, sizeof(mbi)) && mbi.RegionSize > 0) {
|
||||||
|
bind_addr_end = bind_addr_base + mbi.RegionSize;
|
||||||
|
} else if (bind_section->Misc.VirtualSize > 0) {
|
||||||
|
bind_addr_end = bind_addr_base + bind_section->Misc.VirtualSize;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool stubdrm::patch()
|
||||||
|
{
|
||||||
|
if (!calc_bind_section_boundaries()) return false;
|
||||||
|
|
||||||
|
auto addrOfEntry = exe_addr_base + pe_helpers::get_optional_header((HMODULE)exe_addr_base)->AddressOfEntryPoint;
|
||||||
|
if (addrOfEntry < bind_addr_base || addrOfEntry >= bind_addr_end) return false; // entry addr is not inside .bind
|
||||||
|
|
||||||
|
// find .bind variant
|
||||||
|
for (const auto &patt : all_bind_details) {
|
||||||
|
auto mem = pe_helpers::search_memory(
|
||||||
|
bind_addr_base,
|
||||||
|
static_cast<size_t>(bind_addr_end - bind_addr_base),
|
||||||
|
patt.bind_detection_patt);
|
||||||
|
|
||||||
|
if (mem) {
|
||||||
|
current_bind_idx = static_cast<size_t>(&patt - &all_bind_details[0]);
|
||||||
|
return redirect_win32_apis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool stubdrm::restore()
|
||||||
|
{
|
||||||
|
return restore_win32_apis();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stubdrm::set_cleanup_cb(void (*fn)())
|
||||||
|
{
|
||||||
|
cleanup_cb = fn;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user