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 - name: Build release mode
shell: cmd shell: cmd
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
run: build_win.bat release run: build_win.bat +exclient-extra-32 +exclient-extra-64 release
### package (release mode) ### package (release mode)
- name: Package build (release) - name: Package build (release)
@ -170,7 +170,7 @@ jobs:
- name: Build debug mode - name: Build debug mode
shell: cmd shell: cmd
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
run: build_win.bat debug run: build_win.bat +exclient-extra-32 +exclient-extra-64 debug
### package (debug mode) ### package (debug mode)
- name: Package build (debug) - 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-32`: prevent building steamclient `steamclient.dll`
* `-exclient-64`: prevent building steamclient `steamclient64.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_32=1
set /a BUILD_EXPCLIENT_LDR_64=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_FIND_ITFS=1
set /a BUILD_TOOL_LOBBY=1 set /a BUILD_TOOL_LOBBY=1
@ -67,6 +70,10 @@ set /a VERBOSE=0
set /a BUILD_EXPCLIENT_LDR_32=0 set /a BUILD_EXPCLIENT_LDR_32=0
) else if "%~1"=="-exclient-ldr-64" ( ) else if "%~1"=="-exclient-ldr-64" (
set /a BUILD_EXPCLIENT_LDR_64=0 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" ( ) else if "%~1"=="-tool-itf" (
set /a BUILD_TOOL_FIND_ITFS=0 set /a BUILD_TOOL_FIND_ITFS=0
) else if "%~1"=="-tool-lobby" ( ) else if "%~1"=="-tool-lobby" (
@ -308,9 +315,6 @@ call :build_rsrc "%win_resources_src_dir%\launcher\32\resources.rc" "%win_resour
echo: & echo: echo: & echo:
if %BUILD_LIB32% equ 1 ( if %BUILD_LIB32% equ 1 (
if not exist "%build_root_dir%\x32" (
mkdir "%build_root_dir%\x32"
)
call :compile_lib32 || ( call :compile_lib32 || (
set /a last_code+=1 set /a last_code+=1
) )
@ -318,9 +322,6 @@ if %BUILD_LIB32% equ 1 (
) )
if %BUILD_EXP_LIB32% equ 1 ( if %BUILD_EXP_LIB32% equ 1 (
if not exist "%experimental_dir%\x32" (
mkdir "%experimental_dir%\x32"
)
call :compile_experimental_lib32 || ( call :compile_experimental_lib32 || (
set /a last_code+=1 set /a last_code+=1
) )
@ -328,9 +329,6 @@ if %BUILD_EXP_LIB32% equ 1 (
) )
if %BUILD_EXP_CLIENT32% equ 1 ( if %BUILD_EXP_CLIENT32% equ 1 (
if not exist "%experimental_dir%\x32" (
mkdir "%experimental_dir%\x32"
)
call :compile_experimental_client32 || ( call :compile_experimental_client32 || (
set /a last_code+=1 set /a last_code+=1
) )
@ -338,9 +336,6 @@ if %BUILD_EXP_CLIENT32% equ 1 (
) )
if %BUILD_EXPCLIENT32% equ 1 ( if %BUILD_EXPCLIENT32% equ 1 (
if not exist "%steamclient_dir%" (
mkdir "%steamclient_dir%"
)
call :compile_experimentalclient_32 || ( call :compile_experimentalclient_32 || (
set /a last_code+=1 set /a last_code+=1
) )
@ -349,19 +344,14 @@ if %BUILD_EXPCLIENT32% equ 1 (
:: steamclient_loader :: steamclient_loader
if %BUILD_EXPCLIENT_LDR_32% equ 1 ( if %BUILD_EXPCLIENT_LDR_32% equ 1 (
if not exist "%steamclient_dir%" (
mkdir "%steamclient_dir%"
)
call :compile_experimentalclient_ldr_32 || ( call :compile_experimentalclient_ldr_32 || (
set /a last_code+=1 set /a last_code+=1
) )
echo: & echo: echo: & echo:
) )
if not exist "%steamclient_dir%" ( if %BUILD_EXPCLIENT_EXTRA_32% equ 1 (
mkdir "%steamclient_dir%" call :compile_experimentalclient_extra_32 || (
)
call :compile_experimentalclient_ldr || (
set /a last_code+=1 set /a last_code+=1
) )
echo: & echo: echo: & echo:
@ -369,18 +359,12 @@ if %BUILD_EXPCLIENT_LDR_32% equ 1 (
:: tools (x32) :: tools (x32)
if %BUILD_TOOL_FIND_ITFS% equ 1 ( if %BUILD_TOOL_FIND_ITFS% equ 1 (
if not exist "%find_interfaces_dir%" (
mkdir "%find_interfaces_dir%"
)
call :compile_tool_itf || ( call :compile_tool_itf || (
set /a last_code+=1 set /a last_code+=1
) )
echo: & echo: echo: & echo:
) )
if %BUILD_TOOL_LOBBY% equ 1 ( if %BUILD_TOOL_LOBBY% equ 1 (
if not exist "%lobby_connect_dir%" (
mkdir "%lobby_connect_dir%"
)
call :compile_tool_lobby || ( call :compile_tool_lobby || (
set /a last_code+=1 set /a last_code+=1
) )
@ -440,9 +424,6 @@ call :build_rsrc "%win_resources_src_dir%\launcher\64\resources.rc" "%win_resour
echo: & echo: echo: & echo:
if %BUILD_LIB64% equ 1 ( if %BUILD_LIB64% equ 1 (
if not exist "%build_root_dir%\x64" (
mkdir "%build_root_dir%\x64"
)
call :compile_lib64 || ( call :compile_lib64 || (
set /a last_code+=1 set /a last_code+=1
) )
@ -450,9 +431,6 @@ if %BUILD_LIB64% equ 1 (
) )
if %BUILD_EXP_LIB64% equ 1 ( if %BUILD_EXP_LIB64% equ 1 (
if not exist "%experimental_dir%\x64" (
mkdir "%experimental_dir%\x64"
)
call :compile_experimental_lib64 || ( call :compile_experimental_lib64 || (
set /a last_code+=1 set /a last_code+=1
) )
@ -460,9 +438,6 @@ if %BUILD_EXP_LIB64% equ 1 (
) )
if %BUILD_EXP_CLIENT64% equ 1 ( if %BUILD_EXP_CLIENT64% equ 1 (
if not exist "%experimental_dir%\x64" (
mkdir "%experimental_dir%\x64"
)
call :compile_experimental_client64 || ( call :compile_experimental_client64 || (
set /a last_code+=1 set /a last_code+=1
) )
@ -470,9 +445,6 @@ if %BUILD_EXP_CLIENT64% equ 1 (
) )
if %BUILD_EXPCLIENT64% equ 1 ( if %BUILD_EXPCLIENT64% equ 1 (
if not exist "%steamclient_dir%" (
mkdir "%steamclient_dir%"
)
call :compile_experimentalclient_64 || ( call :compile_experimentalclient_64 || (
set /a last_code+=1 set /a last_code+=1
) )
@ -481,15 +453,19 @@ if %BUILD_EXPCLIENT64% equ 1 (
:: steamclient_loader :: steamclient_loader
if %BUILD_EXPCLIENT_LDR_64% equ 1 ( if %BUILD_EXPCLIENT_LDR_64% equ 1 (
if not exist "%steamclient_dir%" (
mkdir "%steamclient_dir%"
)
call :compile_experimentalclient_ldr_64 || ( call :compile_experimentalclient_ldr_64 || (
set /a last_code+=1 set /a last_code+=1
) )
echo: & echo: 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% endlocal & set /a last_code=%last_code%
@ -596,6 +572,16 @@ endlocal & exit /b %_exit%
) )
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% endlocal & exit /b %_exit%
@ -689,6 +675,19 @@ endlocal & exit /b %_exit%
) )
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 :err_msg
@ -799,6 +798,12 @@ exit /b 1
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" ( 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 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: 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()) { if (path.is_absolute()) {
return path; return path;

View File

@ -20,7 +20,7 @@ static inline uint8_t char_to_byte(const char c)
return (uint8_t)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/ // 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; 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); 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; 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; 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) { 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_mem = (uint8_t)(*mem & (uint8_t)~rp.first);
const uint8_t b_replace = (uint8_t)(rp.second & 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) 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) 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) 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); WaitForSingleObject(remote_thread, INFINITE);
CloseHandle(remote_thread);
// cleanup allcoated page // cleanup allcoated page
VirtualFreeEx( VirtualFreeEx(
@ -419,3 +423,43 @@ bool pe_helpers::ends_with_i(PUNICODE_STRING target, const std::wstring &query)
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; } 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); 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); 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); 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` * `steamclient_loader.exe`
2. Edit `ColdClientLoader.ini` and specify: 2. Edit `ColdClientLoader.ini` and specify:
* `AppId`: the app ID * `AppId`: the app ID
* `Exe`: the path to the game's executable/launcher, either full path or relative to this `.ini` file * `Exe`: the path to the game's executable/launcher, either full path or relative to this loader
* `ExeRunDir`: geenrally this must be set to the folder containing the game's exe * `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` * `ExeCommandLine` additional args to pass to the exe, example: `-dx11 -windowed`
Optionally you can specify a different location for `steamclient(64).dll`: 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.dll`, either full path or relative to this loader
* `SteamClientDll`: path to `steamclient(64).dll`, either full path or relative to this `.ini` file * `SteamClientDll`: path to `steamclient(64).dll`, either full path or relative to this loader
* For debug **build** only: * `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. * `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. 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`. **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` * [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` * [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` * [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(); auto org_pos = file.tellg();
file.seekg(0, std::ios::beg); file.seekg(0, std::ios::beg);
// 2MB is enough // 1MB is enough
std::vector<uint8_t> data(2 * 1024 * 1024, 0); std::vector<uint8_t> data(1 * 1024 * 1024, 0);
file.read((char *)&data[0], data.size()); file.read((char *)&data[0], data.size());
file.seekg(org_pos, std::ios::beg); 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); 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) int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{ {
dbg_log::init(dbg_file.c_str()); 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 ExeCommandLine = get_ini_value(L"SteamClient", L"ExeCommandLine");
std::wstring AppId = get_ini_value(L"SteamClient", L"AppId"); 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 // log everything
dbg_log::write(L"SteamClient64Dll: " + Client64Path); dbg_log::write(L"SteamClient::Exe: " + ExeFile);
dbg_log::write(L"SteamClient: " + ClientPath); dbg_log::write(L"SteamClient::ExeRunDir: " + ExeRunDir);
dbg_log::write(L"Exe: " + ExeFile); dbg_log::write(L"SteamClient::ExeCommandLine: " + ExeCommandLine);
dbg_log::write(L"ExeRunDir: " + ExeRunDir); dbg_log::write(L"SteamClient::AppId: " + AppId);
dbg_log::write(L"ExeCommandLine: " + ExeCommandLine); dbg_log::write(L"SteamClient::SteamClient: " + ClientPath);
dbg_log::write(L"AppId: " + AppId); 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]) { if (AppId.size() && AppId[0]) {
SetEnvironmentVariableW(L"SteamAppId", AppId.c_str()); SetEnvironmentVariableW(L"SteamAppId", AppId.c_str());
@ -86,6 +122,24 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
return 1; 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)) { if (!common_helpers::file_exist(Client64Path)) {
dbg_log::write("Couldn't find the requested SteamClient64Dll"); dbg_log::write("Couldn't find the requested SteamClient64Dll");
MessageBoxA(NULL, "Couldn't find the requested SteamClient64Dll.", "ColdClientLoader", MB_ICONERROR); 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; return 1;
} }
if (!common_helpers::file_exist(ExeFile)) { for (auto &c : resume_by_dbg) {
dbg_log::write("Couldn't find the requested Exe file"); c = (wchar_t)std::tolower((int)c);
MessageBoxA(NULL, "Couldn't find the requested Exe file.", "ColdClientLoader", MB_ICONERROR); }
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(); dbg_log::close();
return 1; return 1;
} }
if (!common_helpers::dir_exist(ExeRunDir)) { bool is_exe_32 = pe_helpers::is_module_32((HMODULE)&exe_header[0]);
dbg_log::write("Couldn't find the requested Exe run dir"); bool is_exe_64 = pe_helpers::is_module_64((HMODULE)&exe_header[0]);
MessageBoxA(NULL, "Couldn't find the requested Exe run dir.", "ColdClientLoader", MB_ICONERROR); 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(); dbg_log::close();
return 1; 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 }; HKEY Registrykey = { 0 };
// Declare some variables to be used for Steam registry. // 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. // Close the HKEY Handle.
RegCloseKey(Registrykey); 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 // spawn the exe
STARTUPINFOW info = { 0 }; STARTUPINFOW info = { 0 };
SecureZeroMemory(&info, sizeof(info)); SecureZeroMemory(&info, sizeof(info));
@ -187,47 +320,50 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
return 1; 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; 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) { if (code != ERROR_SUCCESS) {
TerminateProcess(processInfo.hProcess, 1); std::wstring err_full =
std::string err_full = L"Failed to inject the dll: " + dll_path + L"\n" +
"Failed to inject the requested dll:\n" + common_helpers::str_to_w(err_inject) + L"\n" +
std::string(err_inject) + "\n" + common_helpers::str_to_w(pe_helpers::get_err_string(code)) + L"\n" +
pe_helpers::get_err_string(code) + "\n" + L"Error code = " + std::to_wstring(code) + L"\n";
"Error code = " + std::to_string(code) + "\n";
dbg_log::write(err_full); 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(); dbg_log::close();
return 1; return 1;
} }
} else {
}
} }
bool run_exe = true; // run
#ifndef EMU_RELEASE_BUILD if (resume_by_dbg.empty()) {
std::wstring resume_by_dbg = get_ini_value(L"Debug", L"ResumeByDebugger"); ResumeThread(processInfo.hThread);
dbg_log::write(L"Debug::ResumeByDebugger: " + resume_by_dbg); } else {
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;
std::string msg = "Attach a debugger now to PID " + std::to_string(processInfo.dwProcessId) + " and resume its main thread"; std::string msg = "Attach a debugger now to PID " + std::to_string(processInfo.dwProcessId) + " and resume its main thread";
dbg_log::write(msg); dbg_log::write(msg);
MessageBoxA(NULL, msg.c_str(), "ColdClientLoader", MB_OK); MessageBoxA(NULL, msg.c_str(), "ColdClientLoader", MB_OK);
} }
#endif
// run
if (run_exe) {
ResumeThread(processInfo.hThread);
}
// wait // wait
WaitForSingleObject(processInfo.hThread, INFINITE); WaitForSingleObject(processInfo.hThread, INFINITE);
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
if (orig_steam) { if (orig_steam) {
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam\\ActiveProcess", 0, KEY_ALL_ACCESS, &Registrykey) == ERROR_SUCCESS) 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] [SteamClient]
Exe=game.exe Exe=game.exe
ExeRunDir=. ExeRunDir=
ExeCommandLine= ExeCommandLine=
#IMPORTANT: #IMPORTANT:
AppId= AppId=
@ -9,8 +9,16 @@ AppId=
SteamClientDll=steamclient.dll SteamClientDll=steamclient.dll
SteamClient64Dll=steamclient64.dll SteamClient64Dll=steamclient64.dll
# inject `steamclient(64).dll`
ForceInjectSteamClient=0
[Debug] [Debug]
# don't call `ResumeThread()` on the main thread after spawning the .exe
ResumeByDebugger=0 ResumeByDebugger=0
[Extra] [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();
}