a working stub patcher + lots of changes to cold client loader

This commit is contained in:
otavepto 2024-01-14 02:29:02 +02:00
parent 00ace6727d
commit 91aac61e23
16 changed files with 551 additions and 117 deletions

View File

@ -148,7 +148,7 @@ jobs:
- name: Build release mode
shell: cmd
working-directory: ${{ github.workspace }}
run: build_win.bat release
run: build_win.bat +exclient-extra-32 +exclient-extra-64 release
### package (release mode)
- name: Package build (release)
@ -170,7 +170,7 @@ jobs:
- name: Build debug mode
shell: cmd
working-directory: ${{ github.workspace }}
run: build_win.bat debug
run: build_win.bat +exclient-extra-32 +exclient-extra-64 debug
### package (debug mode)
- name: Package build (debug)

View File

@ -149,7 +149,13 @@ Arguments you can pass to this script:
* `-exclient-32`: prevent building steamclient `steamclient.dll`
* `-exclient-64`: prevent building steamclient `steamclient64.dll`
* `-exclient-ldr`: prevent building steamclient `steamclient_loader.exe`
* `-exclient-ldr-32`: prevent building steamclient loader (32) `steamclient_loader_32.exe`
* `-exclient-ldr-64`: prevent building steamclient loader (64) `steamclient_loader_64.exe`
>>>>>>>>> ___
* `+exclient-extra-32`: build the 32 bit version of the additional dll `steamclient_extra.dll` which is injected by the client loader
* `+exclient-extra-64`: build the 64 bit version of the additional dll `steamclient_extra64.dll` which is injected by the client loader
>>>>>>>>> ___

View File

@ -30,6 +30,9 @@ set /a BUILD_EXPCLIENT64=1
set /a BUILD_EXPCLIENT_LDR_32=1
set /a BUILD_EXPCLIENT_LDR_64=1
set /a BUILD_EXPCLIENT_EXTRA_32=0
set /a BUILD_EXPCLIENT_EXTRA_64=0
set /a BUILD_TOOL_FIND_ITFS=1
set /a BUILD_TOOL_LOBBY=1
@ -67,6 +70,10 @@ set /a VERBOSE=0
set /a BUILD_EXPCLIENT_LDR_32=0
) else if "%~1"=="-exclient-ldr-64" (
set /a BUILD_EXPCLIENT_LDR_64=0
) else if "%~1"=="+exclient-extra-32" (
set /a BUILD_EXPCLIENT_EXTRA_32=1
) else if "%~1"=="+exclient-extra-64" (
set /a BUILD_EXPCLIENT_EXTRA_64=1
) else if "%~1"=="-tool-itf" (
set /a BUILD_TOOL_FIND_ITFS=0
) else if "%~1"=="-tool-lobby" (
@ -308,9 +315,6 @@ call :build_rsrc "%win_resources_src_dir%\launcher\32\resources.rc" "%win_resour
echo: & echo:
if %BUILD_LIB32% equ 1 (
if not exist "%build_root_dir%\x32" (
mkdir "%build_root_dir%\x32"
)
call :compile_lib32 || (
set /a last_code+=1
)
@ -318,9 +322,6 @@ if %BUILD_LIB32% equ 1 (
)
if %BUILD_EXP_LIB32% equ 1 (
if not exist "%experimental_dir%\x32" (
mkdir "%experimental_dir%\x32"
)
call :compile_experimental_lib32 || (
set /a last_code+=1
)
@ -328,9 +329,6 @@ if %BUILD_EXP_LIB32% equ 1 (
)
if %BUILD_EXP_CLIENT32% equ 1 (
if not exist "%experimental_dir%\x32" (
mkdir "%experimental_dir%\x32"
)
call :compile_experimental_client32 || (
set /a last_code+=1
)
@ -338,9 +336,6 @@ if %BUILD_EXP_CLIENT32% equ 1 (
)
if %BUILD_EXPCLIENT32% equ 1 (
if not exist "%steamclient_dir%" (
mkdir "%steamclient_dir%"
)
call :compile_experimentalclient_32 || (
set /a last_code+=1
)
@ -349,19 +344,14 @@ if %BUILD_EXPCLIENT32% equ 1 (
:: steamclient_loader
if %BUILD_EXPCLIENT_LDR_32% equ 1 (
if not exist "%steamclient_dir%" (
mkdir "%steamclient_dir%"
)
call :compile_experimentalclient_ldr_32 || (
set /a last_code+=1
)
echo: & echo:
)
if not exist "%steamclient_dir%" (
mkdir "%steamclient_dir%"
)
call :compile_experimentalclient_ldr || (
if %BUILD_EXPCLIENT_EXTRA_32% equ 1 (
call :compile_experimentalclient_extra_32 || (
set /a last_code+=1
)
echo: & echo:
@ -369,18 +359,12 @@ if %BUILD_EXPCLIENT_LDR_32% equ 1 (
:: tools (x32)
if %BUILD_TOOL_FIND_ITFS% equ 1 (
if not exist "%find_interfaces_dir%" (
mkdir "%find_interfaces_dir%"
)
call :compile_tool_itf || (
set /a last_code+=1
)
echo: & echo:
)
if %BUILD_TOOL_LOBBY% equ 1 (
if not exist "%lobby_connect_dir%" (
mkdir "%lobby_connect_dir%"
)
call :compile_tool_lobby || (
set /a last_code+=1
)
@ -440,9 +424,6 @@ call :build_rsrc "%win_resources_src_dir%\launcher\64\resources.rc" "%win_resour
echo: & echo:
if %BUILD_LIB64% equ 1 (
if not exist "%build_root_dir%\x64" (
mkdir "%build_root_dir%\x64"
)
call :compile_lib64 || (
set /a last_code+=1
)
@ -450,9 +431,6 @@ if %BUILD_LIB64% equ 1 (
)
if %BUILD_EXP_LIB64% equ 1 (
if not exist "%experimental_dir%\x64" (
mkdir "%experimental_dir%\x64"
)
call :compile_experimental_lib64 || (
set /a last_code+=1
)
@ -460,9 +438,6 @@ if %BUILD_EXP_LIB64% equ 1 (
)
if %BUILD_EXP_CLIENT64% equ 1 (
if not exist "%experimental_dir%\x64" (
mkdir "%experimental_dir%\x64"
)
call :compile_experimental_client64 || (
set /a last_code+=1
)
@ -470,9 +445,6 @@ if %BUILD_EXP_CLIENT64% equ 1 (
)
if %BUILD_EXPCLIENT64% equ 1 (
if not exist "%steamclient_dir%" (
mkdir "%steamclient_dir%"
)
call :compile_experimentalclient_64 || (
set /a last_code+=1
)
@ -481,15 +453,19 @@ if %BUILD_EXPCLIENT64% equ 1 (
:: steamclient_loader
if %BUILD_EXPCLIENT_LDR_64% equ 1 (
if not exist "%steamclient_dir%" (
mkdir "%steamclient_dir%"
)
call :compile_experimentalclient_ldr_64 || (
set /a last_code+=1
)
echo: & echo:
)
if %BUILD_EXPCLIENT_EXTRA_64% equ 1 (
call :compile_experimentalclient_extra_64 || (
set /a last_code+=1
)
echo: & echo:
)
endlocal & set /a last_code=%last_code%
@ -596,6 +572,16 @@ endlocal & exit /b %_exit%
)
endlocal & exit /b %_exit%
:compile_experimentalclient_extra_32
setlocal
echo // building library steamclient_extra.dll - 32
set src_files="%win_resources_out_dir%\rsrc-client-32.res" "%tools_src_dir%\steamclient_loader\win\extra_protection\*.cpp" "helpers\pe_helpers.cpp" "helpers\common_helpers.cpp" "%libs_dir%\detours\*.cpp"
set extra_inc_dirs=/I"%tools_src_dir%\steamclient_loader\win\extra_protection" /I"pe_helpers"
call :build_for 1 0 "%steamclient_dir%\extra_dlls\steamclient_extra.dll" src_files extra_inc_dirs
set /a _exit=%errorlevel%
if %_exit% equ 0 (
call :change_dos_stub 1 "%steamclient_dir%\extra_dlls\steamclient_extra.dll"
call "%signer_tool%" "%steamclient_dir%\extra_dlls\steamclient_extra.dll"
)
endlocal & exit /b %_exit%
@ -689,6 +675,19 @@ endlocal & exit /b %_exit%
)
endlocal & exit /b %_exit%
:compile_experimentalclient_extra_64
setlocal
echo // building library steamclient_extra64.dll - 64
set src_files="%win_resources_out_dir%\rsrc-client-64.res" "%tools_src_dir%\steamclient_loader\win\extra_protection\*.cpp" "helpers\pe_helpers.cpp" "helpers\common_helpers.cpp" "%libs_dir%\detours\*.cpp"
set extra_inc_dirs=/I"%tools_src_dir%\steamclient_loader\win\extra_protection" /I"pe_helpers"
call :build_for 0 0 "%steamclient_dir%\extra_dlls\steamclient_extra64.dll" src_files extra_inc_dirs
set /a _exit=%errorlevel%
if %_exit% equ 0 (
call :change_dos_stub 0 "%steamclient_dir%\extra_dlls\steamclient_extra64.dll"
call "%signer_tool%" "%steamclient_dir%\extra_dlls\steamclient_extra64.dll"
)
endlocal & exit /b %_exit%
:err_msg
@ -799,6 +798,12 @@ exit /b 1
exit /b 1
)
for /f "usebackq tokens=* delims=" %%A in ('"%_out_filepath%"') do (
if not exist "%%~dpA" (
mkdir "%%~dpA"
)
)
if "%VERBOSE%" equ "1" (
echo cl.exe %_target_args% /Fo%_build_tmp%\ /Fe%_build_tmp%\ %debug_info% %debug_info_format% %optimization_level% %release_defs% %_extra_defs% %_runtime_type% %_target_inc_dirs% %_extra_inc_dirs% %_all_src% %_target_libs% %_extra_libs% /link %_target_linker_args% /OUT:"%_out_filepath%"
echo:

View File

@ -106,7 +106,7 @@ bool common_helpers::ends_with_i(const std::wstring &target, const std::wstring
}
std::filesystem::path to_absolute_impl(std::filesystem::path &path, std::filesystem::path &base)
std::filesystem::path to_absolute_impl(const std::filesystem::path &path, const std::filesystem::path &base)
{
if (path.is_absolute()) {
return path;

View File

@ -20,7 +20,7 @@ static inline uint8_t char_to_byte(const char c)
return (uint8_t)c;
}
static inline PIMAGE_NT_HEADERS get_nt_header(HMODULE hModule)
PIMAGE_NT_HEADERS pe_helpers::get_nt_header(HMODULE hModule)
{
// https://dev.to/wireless90/validating-the-pe-signature-my-av-flagged-me-windows-pe-internals-2m5o/
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)(char*)hModule;
@ -29,12 +29,12 @@ static inline PIMAGE_NT_HEADERS get_nt_header(HMODULE hModule)
return (PIMAGE_NT_HEADERS)((char*)hModule + newExeHeaderOffset);
}
static inline PIMAGE_FILE_HEADER get_file_header(HMODULE hModule)
PIMAGE_FILE_HEADER pe_helpers::get_file_header(HMODULE hModule)
{
return &get_nt_header(hModule)->FileHeader;
}
static inline PIMAGE_OPTIONAL_HEADER get_optional_header(HMODULE hModule)
PIMAGE_OPTIONAL_HEADER pe_helpers::get_optional_header(HMODULE hModule)
{
return &get_nt_header(hModule)->OptionalHeader;
}
@ -190,7 +190,10 @@ bool pe_helpers::replace_memory(uint8_t *mem, size_t size, const std::string &re
}
for (auto &rp : replace_bytes) {
if (rp.first == 0x00) continue;
if (rp.first == 0x00) {
++mem;
continue;
}
const uint8_t b_mem = (uint8_t)(*mem & (uint8_t)~rp.first);
const uint8_t b_replace = (uint8_t)(rp.second & rp.first);
@ -231,12 +234,12 @@ std::string pe_helpers::get_err_string(DWORD code)
bool pe_helpers::is_module_64(HMODULE hModule)
{
return !!(get_file_header(hModule)->Machine == IMAGE_FILE_MACHINE_AMD64);
return (get_file_header(hModule)->Machine == IMAGE_FILE_MACHINE_AMD64);
}
bool pe_helpers::is_module_32(HMODULE hModule)
{
return !!(get_file_header(hModule)->Machine == IMAGE_FILE_MACHINE_I386);
return (get_file_header(hModule)->Machine == IMAGE_FILE_MACHINE_I386);
}
pe_helpers::SectionHeadersResult pe_helpers::get_section_headers(HMODULE hModule)
@ -339,6 +342,7 @@ DWORD pe_helpers::loadlib_remote(HANDLE hProcess, const std::wstring &lib_fullpa
}
WaitForSingleObject(remote_thread, INFINITE);
CloseHandle(remote_thread);
// cleanup allcoated page
VirtualFreeEx(
@ -419,3 +423,43 @@ bool pe_helpers::ends_with_i(PUNICODE_STRING target, const std::wstring &query)
query
);
}
MEMORY_BASIC_INFORMATION pe_helpers::get_mem_page_details(const void* mem)
{
MEMORY_BASIC_INFORMATION mbi{};
if (VirtualQuery(mem, &mbi, sizeof(mbi))) {
return mbi;
} else {
return {};
}
}
size_t pe_helpers::get_current_exe_mem_size()
{
auto hmod = GetModuleHandleW(NULL);
size_t size = 0;
{
MEMORY_BASIC_INFORMATION mbi{};
if (!VirtualQuery((LPVOID)hmod, &mbi, sizeof(mbi))) {
return 0;
}
size = mbi.RegionSize; // PE header
}
auto sections = get_section_headers(hmod);
if (!sections.count) {
return 0;
}
for (size_t i = 0; i < sections.count; ++i) {
auto section = sections.ptr[i];
MEMORY_BASIC_INFORMATION mbi{};
if (!VirtualQuery((LPVOID)((uint8_t *)hmod + section.VirtualAddress), &mbi, sizeof(mbi))) {
return 0;
}
size = mbi.RegionSize; // actual section size in mem
}
return size;
}

View File

@ -16,6 +16,12 @@ typedef struct SectionHeadersResult
} SectionHeadersResult_t;
PIMAGE_NT_HEADERS get_nt_header(HMODULE hModule);
PIMAGE_FILE_HEADER get_file_header(HMODULE hModule);
PIMAGE_OPTIONAL_HEADER get_optional_header(HMODULE hModule);
uint8_t* search_memory(uint8_t *mem, size_t size, const std::string &search_patt);
bool replace_memory(uint8_t *mem, size_t size, const std::string &replace_patt, HANDLE hProcess);
@ -40,4 +46,8 @@ const std::wstring get_current_exe_path_w();
bool ends_with_i(PUNICODE_STRING target, const std::wstring &query);
MEMORY_BASIC_INFORMATION get_mem_page_details(const void* mem);
size_t get_current_exe_mem_size();
}

View File

@ -19,15 +19,18 @@ You do not need to create a `steam_interfaces.txt` file for the `steamclient` ve
* `steamclient_loader.exe`
2. Edit `ColdClientLoader.ini` and specify:
* `AppId`: the app ID
* `Exe`: the path to the game's executable/launcher, either full path or relative to this `.ini` file
* `ExeRunDir`: geenrally this must be set to the folder containing the game's exe
* `Exe`: the path to the game's executable/launcher, either full path or relative to this loader
* `ExeRunDir`: generally this must be set to the folder containing the game's exe, if left empty then it will be automatically set to the folder containing the game's exe.
* `ExeCommandLine` additional args to pass to the exe, example: `-dx11 -windowed`
Optionally you can specify a different location for `steamclient(64).dll`:
* `SteamClientDll`: path to `steamclient.dll`, either full path or relative to this `.ini` file
* `SteamClientDll`: path to `steamclient(64).dll`, either full path or relative to this `.ini` file
* For debug **build** only:
* `ResumeByDebugger`: setting this to `1` or 'y' or `true` will prevent the loader from calling `ResumeThread` on the main thread after spawning the .exe, and it will display a mesage with the process ID (PID) so you attach your debugger on it.
* `SteamClientDll`: path to `steamclient.dll`, either full path or relative to this loader
* `SteamClientDll`: path to `steamclient(64).dll`, either full path or relative to this loader
* `ForceInjectSteamClient`: force inject `steamclient(64).dll` instead of letting the app load it automatically
* `ResumeByDebugger`: setting this to `1` or `y` or `true` will prevent the loader from calling `ResumeThread()` on the main thread after spawning the .exe, and it will display a mesage with the process ID (PID) so you attach your debugger on it.
Note that you have to resume the main thread from the debugger after attaching, also the entry breakpoint may not be set automatically, but you can do that manually.
* `DllsToInjectFolder`: path to a folder containing dlls to force inject into the app upon start,
the loader will attempt to detect the dll architecture (32 or 64 bit), if it didn't match the architecture of the exe, then it will ignored
* `IgnoreInjectionError`: setting this to `1` or `y` or `true` will prevent the loader from displaying an error message when a dll injection fails
**Note** that any arguments passed to `steamclient_loader.exe` via command line will be passed to the target `.exe`.

View File

@ -13,4 +13,5 @@ This directory contains additional resources used during build.
* [api](./win/api/): contains an immitation of the resources found in `steam_api(64).dll`
* [client](./win/client/): contains an immitation of the resources found in `steamclient(64).dll`
* [launcher](./win/launcher/): contains an immitation of the resources found in `steam.exe`
* [file_dos_stub](./win/file_dos_stub/): contains an immitation of how the DOS stub is manipulated after build

View File

@ -28,8 +28,8 @@ static std::vector<uint8_t> load_file_partial(std::fstream &file)
auto org_pos = file.tellg();
file.seekg(0, std::ios::beg);
// 2MB is enough
std::vector<uint8_t> data(2 * 1024 * 1024, 0);
// 1MB is enough
std::vector<uint8_t> data(1 * 1024 * 1024, 0);
file.read((char *)&data[0], data.size());
file.seekg(org_pos, std::ios::beg);

View File

@ -37,6 +37,28 @@ std::wstring get_ini_value(LPCWSTR section, LPCWSTR key, LPCWSTR default_val = N
return std::wstring(&buff[0], read_chars);
}
static std::vector<uint8_t> get_pe_header(const std::wstring &filepath)
{
try
{
std::ifstream file(filepath, std::ios::binary);
if (!file.is_open()) throw;
file.seekg(0, std::ios::beg);
// 2MB is enough
std::vector<uint8_t> data(2 * 1024 * 1024, 0);
file.read((char *)&data[0], data.size());
file.close();
return data;
}
catch(const std::exception& e)
{
return std::vector<uint8_t>();
}
}
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
dbg_log::init(dbg_file.c_str());
@ -67,14 +89,28 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
);
std::wstring ExeCommandLine = get_ini_value(L"SteamClient", L"ExeCommandLine");
std::wstring AppId = get_ini_value(L"SteamClient", L"AppId");
std::wstring InjectClient = get_ini_value(L"SteamClient", L"ForceInjectSteamClient");
std::wstring resume_by_dbg = get_ini_value(L"Debug", L"ResumeByDebugger");
// dlls to inject
std::wstring extra_dlls_folder = common_helpers::to_absolute(
get_ini_value(L"Extra", L"DllsToInjectFolder"),
pe_helpers::get_current_exe_path_w()
);
std::wstring IgnoreInjectionError = get_ini_value(L"Extra", L"IgnoreInjectionError", L"1");
// log everything
dbg_log::write(L"SteamClient64Dll: " + Client64Path);
dbg_log::write(L"SteamClient: " + ClientPath);
dbg_log::write(L"Exe: " + ExeFile);
dbg_log::write(L"ExeRunDir: " + ExeRunDir);
dbg_log::write(L"ExeCommandLine: " + ExeCommandLine);
dbg_log::write(L"AppId: " + AppId);
dbg_log::write(L"SteamClient::Exe: " + ExeFile);
dbg_log::write(L"SteamClient::ExeRunDir: " + ExeRunDir);
dbg_log::write(L"SteamClient::ExeCommandLine: " + ExeCommandLine);
dbg_log::write(L"SteamClient::AppId: " + AppId);
dbg_log::write(L"SteamClient::SteamClient: " + ClientPath);
dbg_log::write(L"SteamClient::SteamClient64Dll: " + Client64Path);
dbg_log::write(L"SteamClient::ForceInjectSteamClient: " + InjectClient);
dbg_log::write(L"Debug::ResumeByDebugger: " + resume_by_dbg);
dbg_log::write(L"Extra::DllsToInjectFolder: " + extra_dlls_folder);
dbg_log::write(L"Extra::IgnoreInjectionError: " + IgnoreInjectionError);
if (AppId.size() && AppId[0]) {
SetEnvironmentVariableW(L"SteamAppId", AppId.c_str());
@ -86,6 +122,24 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
return 1;
}
if (!common_helpers::file_exist(ExeFile)) {
dbg_log::write("Couldn't find the requested Exe file");
MessageBoxA(NULL, "Couldn't find the requested Exe file.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
if (ExeRunDir.empty()) {
ExeRunDir = std::filesystem::path(ExeFile).parent_path().wstring();
dbg_log::write(L"Setting exe run dir to: " + ExeRunDir);
}
if (!common_helpers::dir_exist(ExeRunDir)) {
dbg_log::write("Couldn't find the requested Exe run dir");
MessageBoxA(NULL, "Couldn't find the requested Exe run dir.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
if (!common_helpers::file_exist(Client64Path)) {
dbg_log::write("Couldn't find the requested SteamClient64Dll");
MessageBoxA(NULL, "Couldn't find the requested SteamClient64Dll.", "ColdClientLoader", MB_ICONERROR);
@ -100,19 +154,115 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
return 1;
}
if (!common_helpers::file_exist(ExeFile)) {
dbg_log::write("Couldn't find the requested Exe file");
MessageBoxA(NULL, "Couldn't find the requested Exe file.", "ColdClientLoader", MB_ICONERROR);
for (auto &c : resume_by_dbg) {
c = (wchar_t)std::tolower((int)c);
}
if (resume_by_dbg != L"1" && resume_by_dbg != L"y" && resume_by_dbg != L"yes" && resume_by_dbg != L"true") {
resume_by_dbg.clear();
}
for (auto &c : IgnoreInjectionError) {
c = (wchar_t)std::tolower((int)c);
}
if (IgnoreInjectionError != L"1" && IgnoreInjectionError != L"y" && IgnoreInjectionError != L"yes" && IgnoreInjectionError != L"true") {
IgnoreInjectionError.clear();
}
for (auto &c : InjectClient) {
c = (wchar_t)std::tolower((int)c);
}
if (InjectClient != L"1" && InjectClient != L"y" && InjectClient != L"yes" && InjectClient != L"true") {
InjectClient.clear();
}
if (extra_dlls_folder.size()) {
if (!common_helpers::dir_exist(extra_dlls_folder)) {
dbg_log::write("Couldn't find the requested folder of dlls to inject");
MessageBoxA(NULL, "Couldn't find the requested folder of dlls to inject.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
}
auto exe_header = get_pe_header(ExeFile);
if (exe_header.empty()) {
dbg_log::write("Couldn't read the exe header");
MessageBoxA(NULL, "Couldn't read the exe header.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
if (!common_helpers::dir_exist(ExeRunDir)) {
dbg_log::write("Couldn't find the requested Exe run dir");
MessageBoxA(NULL, "Couldn't find the requested Exe run dir.", "ColdClientLoader", MB_ICONERROR);
bool is_exe_32 = pe_helpers::is_module_32((HMODULE)&exe_header[0]);
bool is_exe_64 = pe_helpers::is_module_64((HMODULE)&exe_header[0]);
if ((!is_exe_32 && !is_exe_64) || (is_exe_32 && is_exe_64)) { // ARM, or just a regular file
dbg_log::write("The requested exe is invalid (neither 32 nor 64 bit)");
MessageBoxA(NULL, "The requested exe is invalid (neither 32 nor 64 bit)", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
if (is_exe_32) {
dbg_log::write("Detected exe arch: x32");
} else {
dbg_log::write("Detected exe arch: x64");
}
bool loader_is_32 = pe_helpers::is_module_32(GetModuleHandleW(NULL));
if (pe_helpers::is_module_32(GetModuleHandleW(NULL))) {
dbg_log::write("Detected loader arch: x32");
} else {
dbg_log::write("Detected loader arch: x64");
}
if (loader_is_32 != is_exe_32) {
dbg_log::write("Arch of loader and requested exe are different, it is advised to use the appropriate one");
MessageBoxA(NULL, "Arch of loader and requested exe are different,\nit is advised to use the appropriate one.", "ColdClientLoader", MB_OK);
}
std::vector<std::wstring> dlls_to_inject{};
if (extra_dlls_folder.size()) {
std::wstring failed_dlls = std::wstring{};
for (auto const& dir_entry :
std::filesystem::recursive_directory_iterator(extra_dlls_folder, std::filesystem::directory_options::follow_directory_symlink)) {
if (std::filesystem::is_directory(dir_entry.path())) continue;
auto dll_path = dir_entry.path().wstring();
auto dll_header = get_pe_header(dll_path);
if (dll_header.empty()) {
dbg_log::write(L"Failed to get PE header of dll: " + dll_path);
failed_dlls += dll_path + L"\n";
continue;
}
bool is_dll_32 = pe_helpers::is_module_32((HMODULE)&dll_header[0]);
bool is_dll_64 = pe_helpers::is_module_64((HMODULE)&dll_header[0]);
if ((!is_dll_32 && !is_dll_64) || (is_dll_32 && is_dll_64)) { // ARM, or just a regular file
dbg_log::write(L"Dll " + dll_path + L" is neither 32 nor 64 bit and will be ignored");
failed_dlls += dll_path + L"\n";
continue;
}
if ((is_dll_32 && is_exe_32) || (is_dll_64 && is_exe_64)) {
dlls_to_inject.push_back(dll_path);
dbg_log::write(L"Dll " + dll_path + L" will be injected");
} else {
dbg_log::write(L"Dll " + dll_path + L" has a different arch than the exe and will be ignored");
failed_dlls += dll_path + L"\n";
}
}
if (failed_dlls.size() && IgnoreInjectionError.empty()) {
int choice = MessageBoxW(
NULL,
(L"The following dlls cannot be injected:\n" + failed_dlls + L"\nContinue ?").c_str(),
L"ColdClientLoader",
MB_YESNO);
if (choice != IDYES) {
dbg_log::close();
return 1;
}
}
}
HKEY Registrykey = { 0 };
// Declare some variables to be used for Steam registry.
@ -152,23 +302,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
// Close the HKEY Handle.
RegCloseKey(Registrykey);
// dll to inject
bool inject_extra_dll = false;
std::wstring extra_dll = common_helpers::to_absolute(
get_ini_value(L"Extra", L"InjectDll"),
pe_helpers::get_current_exe_path_w()
);
if (extra_dll.size()) {
dbg_log::write(L"InjectDll: " + extra_dll);
if (!common_helpers::file_exist(extra_dll)) {
dbg_log::write("Couldn't find the requested dll to inject");
MessageBoxA(NULL, "Couldn't find the requested dll to inject.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
inject_extra_dll = true;
}
// spawn the exe
STARTUPINFOW info = { 0 };
SecureZeroMemory(&info, sizeof(info));
@ -187,47 +320,50 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
return 1;
}
if (inject_extra_dll) {
if (InjectClient.size()) {
if (is_exe_32) {
dlls_to_inject.insert(dlls_to_inject.begin(), ClientPath);
} else {
dlls_to_inject.insert(dlls_to_inject.begin(), Client64Path);
}
}
for (const auto &dll_path : dlls_to_inject) {
const char *err_inject = nullptr;
DWORD code = pe_helpers::loadlib_remote(processInfo.hProcess, extra_dll, &err_inject);
DWORD code = pe_helpers::loadlib_remote(processInfo.hProcess, dll_path, &err_inject);
if (code != ERROR_SUCCESS) {
TerminateProcess(processInfo.hProcess, 1);
std::string err_full =
"Failed to inject the requested dll:\n" +
std::string(err_inject) + "\n" +
pe_helpers::get_err_string(code) + "\n" +
"Error code = " + std::to_string(code) + "\n";
std::wstring err_full =
L"Failed to inject the dll: " + dll_path + L"\n" +
common_helpers::str_to_w(err_inject) + L"\n" +
common_helpers::str_to_w(pe_helpers::get_err_string(code)) + L"\n" +
L"Error code = " + std::to_wstring(code) + L"\n";
dbg_log::write(err_full);
MessageBoxA(NULL, err_full.c_str(), "ColdClientLoader", MB_ICONERROR);
if (IgnoreInjectionError.empty()) {
TerminateProcess(processInfo.hProcess, 1);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
MessageBoxW(NULL, err_full.c_str(), L"ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
} else {
}
}
bool run_exe = true;
#ifndef EMU_RELEASE_BUILD
std::wstring resume_by_dbg = get_ini_value(L"Debug", L"ResumeByDebugger");
dbg_log::write(L"Debug::ResumeByDebugger: " + resume_by_dbg);
for (auto &c : resume_by_dbg) {
c = (wchar_t)std::tolower((int)c);
}
if (resume_by_dbg == L"1" || resume_by_dbg == L"y" || resume_by_dbg == L"yes" || resume_by_dbg == L"true") {
run_exe = false;
// run
if (resume_by_dbg.empty()) {
ResumeThread(processInfo.hThread);
} else {
std::string msg = "Attach a debugger now to PID " + std::to_string(processInfo.dwProcessId) + " and resume its main thread";
dbg_log::write(msg);
MessageBoxA(NULL, msg.c_str(), "ColdClientLoader", MB_OK);
}
#endif
// run
if (run_exe) {
ResumeThread(processInfo.hThread);
}
// wait
WaitForSingleObject(processInfo.hThread, INFINITE);
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
if (orig_steam) {
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam\\ActiveProcess", 0, KEY_ALL_ACCESS, &Registrykey) == ERROR_SUCCESS)

View File

@ -1,7 +1,7 @@
#My own modified version of ColdClientLoader originally by Rat431
# modified version of ColdClientLoader originally by Rat431
[SteamClient]
Exe=game.exe
ExeRunDir=.
ExeRunDir=
ExeCommandLine=
#IMPORTANT:
AppId=
@ -9,8 +9,16 @@ AppId=
SteamClientDll=steamclient.dll
SteamClient64Dll=steamclient64.dll
# inject `steamclient(64).dll`
ForceInjectSteamClient=0
[Debug]
# don't call `ResumeThread()` on the main thread after spawning the .exe
ResumeByDebugger=0
[Extra]
InjectDll=
; path to a folder containing dlls to force inject into the app upon start
; extra_dlls
DllsToInjectFolder=
; don't display an error message when a dll injection fails
IgnoreInjectionError=1

View File

@ -0,0 +1,23 @@
#include "pe_helpers/pe_helpers.hpp"
#include "extra_protection/stubdrm_v3.hpp"
BOOL APIENTRY DllMain(
HMODULE hModule,
DWORD reason,
LPVOID lpReserved)
{
switch (reason)
{
case DLL_PROCESS_ATTACH:
stubdrm_v3::patch();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
stubdrm_v3::restore();
break;
}
return TRUE;
}

View File

@ -0,0 +1,8 @@
#pragma once
namespace stubdrm_v3
{
bool patch();
bool restore();
}

View File

@ -0,0 +1,190 @@
#include "pe_helpers/pe_helpers.hpp"
#include "common_helpers/common_helpers.hpp"
#include "extra_protection/stubdrm_v3.hpp"
#include "detours/detours.h"
#include <vector>
#include <tuple>
#include <mutex>
#include <intrin.h>
// 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()
#ifdef _WIN64
const static std::string stub_detection_patt_v31 = "FF 94 24 ?? ?? ?? ?? 88 44 24 ?? 0F BE 44 24 ?? 83 ?? 30 74 ?? E9";
const static std::string stub_search_patt_1_v31 = "E8 ?? ?? ?? ?? 84 C0 75 ?? B0 33 E9";
const static std::string stub_replace_patt_1_v31 = "B8 01 00 00 00 ?? ?? EB";
const static std::string stub_search_patt_2_v31 = "E8 ?? ?? ?? ?? 44 0F B6 ?? 3C 30 0F 84 ?? ?? ?? ?? 3C 35 0F 85 ?? ?? ?? ?? 8B ?? ?? FF 15";
const static std::string stub_replace_patt_2_v31 = "B8 30 00 00 00 ?? ?? ?? ?? ?? ?? 90 E9";
#else // _WIN64
const static std::string stub_detection_patt_v31 = "FF 95 ?? ?? ?? ?? 88 45 ?? 0F BE 4D ?? 83 ?? 30 74 ?? E9";
const static std::string stub_search_patt_1_v31 = "5? 5? E8 ?? ?? ?? ?? 83 C4 08 84 C0 75 ?? B0 33";
const static std::string stub_replace_patt_1_v31 = "?? ?? B8 01 00 00 00 ?? ?? ?? ?? ?? EB";
const static std::string stub_search_patt_2_v31 = "E8 ?? ?? ?? ?? 83 C4 04 88 45 ?? 3C 30 0F 84 ?? ?? ?? ?? 3C 35 75 ?? 8B ?? ?? FF 15";
const static std::string stub_replace_patt_2_v31 = "B8 30 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? 90 E9";
#endif // _WIN64
static std::recursive_mutex mtx_win32_api{};
static uint8_t *proc_addr_base = (uint8_t *)GetModuleHandleW(NULL);
static uint8_t *bind_addr_base = nullptr;
static uint8_t *bind_addr_end = nullptr;
bool restore_win32_apis();
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;
auto mem = pe_helpers::search_memory(
(uint8_t *)page_details.BaseAddress,
page_details.RegionSize,
stub_search_patt_1_v31);
if (!mem) return;
auto size_until_match = (uint8_t *)mem - (uint8_t *)page_details.BaseAddress;
bool ok = pe_helpers::replace_memory(
(uint8_t *)mem,
page_details.RegionSize - size_until_match,
stub_replace_patt_1_v31,
GetCurrentProcess());
if (!ok) return;
mem = pe_helpers::search_memory(
(uint8_t *)page_details.BaseAddress,
page_details.RegionSize,
stub_search_patt_2_v31);
if (!mem) return;
size_until_match = (uint8_t *)mem - (uint8_t *)page_details.BaseAddress;
pe_helpers::replace_memory(
(uint8_t *)mem,
page_details.RegionSize - size_until_match,
stub_replace_patt_2_v31,
GetCurrentProcess());
restore_win32_apis();
// MessageBoxA(NULL, ("ret addr = " + std::to_string((size_t)ret_addr)).c_str(), "Patched", MB_OK);
}
// https://learn.microsoft.com/en-us/cpp/intrinsics/addressofreturnaddress
static bool GetTickCount_hooked = false;
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
void* *ret_ptr = (void**)_AddressOfReturnAddress();
patch_if_possible(*ret_ptr);
}
return actual_GetTickCount();
}
static bool GetModuleHandleA_hooked = false;
static decltype(GetModuleHandleA) *actual_GetModuleHandleA = GetModuleHandleA;
__declspec(noinline)
static HMODULE WINAPI GetModuleHandleA_hook(
LPCSTR lpModuleName
)
{
std::lock_guard lk(mtx_win32_api);
if (GetModuleHandleA_hooked &&
lpModuleName &&
common_helpers::ends_with_i(lpModuleName, "ntdll.dll")) {
void* *ret_ptr = (void**)_AddressOfReturnAddress();
patch_if_possible(*ret_ptr);
}
return actual_GetModuleHandleA(lpModuleName);
}
static bool GetModuleHandleExA_hooked = false;
static decltype(GetModuleHandleExA) *actual_GetModuleHandleExA = GetModuleHandleExA;
__declspec(noinline)
static BOOL WINAPI GetModuleHandleExA_hook(
DWORD dwFlags,
LPCSTR lpModuleName,
HMODULE *phModule
)
{
std::lock_guard lk(mtx_win32_api);
if (GetModuleHandleExA_hooked &&
(dwFlags == (GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT)) &&
((uint8_t *)lpModuleName >= bind_addr_base && (uint8_t *)lpModuleName < bind_addr_end)) {
void* *ret_ptr = (void**)_AddressOfReturnAddress();
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, GetTickCount_hook) != NO_ERROR) return false;
if (DetourAttach((PVOID *)&actual_GetModuleHandleA, GetModuleHandleA_hook) != NO_ERROR) return false;
if (DetourAttach((PVOID *)&actual_GetModuleHandleExA, 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, GetTickCount_hook);
DetourDetach((PVOID *)&actual_GetModuleHandleA, GetModuleHandleA_hook);
DetourDetach((PVOID *)&actual_GetModuleHandleExA, GetModuleHandleExA_hook);
return DetourTransactionCommit() == NO_ERROR;
}
bool stubdrm_v3::patch()
{
auto bind_section = pe_helpers::get_section_header_with_name(((HMODULE)proc_addr_base), ".bind");
if (!bind_section) return false; // we don't have .bind section
bind_addr_base = proc_addr_base + bind_section->VirtualAddress;
MEMORY_BASIC_INFORMATION mbi{};
if (!VirtualQuery((LPVOID)bind_addr_base, &mbi, sizeof(mbi))) return false;
bind_addr_end = bind_addr_base + mbi.RegionSize;
auto addrOfEntry = proc_addr_base + pe_helpers::get_optional_header((HMODULE)proc_addr_base)->AddressOfEntryPoint;
if (addrOfEntry < bind_addr_base || addrOfEntry >= bind_addr_end) return false; // entry addr is not inside .bind
auto mem = pe_helpers::search_memory(
bind_addr_base,
bind_section->Misc.VirtualSize,
stub_detection_patt_v31);
if (!mem) return false; // known sequence of code wasn't found
return redirect_win32_apis();
}
bool stubdrm_v3::restore()
{
return restore_win32_apis();
}