Merge pull request #52 from otavepto/dev

support older bind variants + auto unload on success or timeout
This commit is contained in:
Detanup01 2024-10-06 13:52:51 +02:00 committed by GitHub
commit 3a05ef636f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 600 additions and 412 deletions

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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, &current_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, &current_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;
}