Code: Select all
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <TlHelp32.h>
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")
#include <vector>
using namespace std;
#define UNREF_PARAMETER(x) (void) x
typedef unsigned long long int UInt64;
typedef unsigned long int UInt32;
typedef unsigned short int UInt16;
typedef unsigned char UInt8;
struct BREAKPOINT;
typedef void (__stdcall * BreakpointCallback)(HANDLE process_handle, CONTEXT thread_context);
struct BREAKPOINT
{
UInt64 address;
bool installed;
UInt8 backup;
BreakpointCallback callback;
};
UInt64 createmutexa_address, createfilew_address;
BREAKPOINT createmutexa_breakpoint, createfilew_breakpoint;
bool detach_from_process;
int main();
void Error(const char *message);
bool InstallBreakpoint(HANDLE process, BREAKPOINT & breakpoint);
bool UninstallBreakpoint(HANDLE process, BREAKPOINT & breakpoint);
bool Initialize(DWORD process_id);
void __stdcall CreateMutexACallback(HANDLE process_handle, CONTEXT thread_context);
void __stdcall CreateFileWCallback(HANDLE process_handle, CONTEXT thread_context);
bool DetachFromProcess(HANDLE process_handle, UInt32 process_id);
bool PauseAllThreads(UInt32 process_id);
bool ResumeAllThreads(UInt32 process_id);
int main()
{
_tprintf(_T("ReadMe!\n"));
_tprintf(_T("This software is provided \"as is\" without warranty of any kind, either express or implied. USE AT YOUR OWN RISK.\n\n"));
_tprintf(_T("Place this program inside the folder where you installed Guild Wars 2 (i.e.: C:\\Program Files (x86)\\Guild Wars 2)\n"));
_tprintf(_T("If you need more than once game window open, launch all the instances with this program.\n"));
_tprintf(_T("Note that you will *NOT* be able to update the game if it has been launched with this loader.\n"));
_tprintf(_T("To avoid data corruption, the configuration file is duplicated each time you launch the game. To change the default configuration, start the game normally.\n"));
_tprintf(_T("Please start the game at least ONCE without this loader.\n\n"));
_tprintf(_T("Press the return key if you accept the above terms, or click close if you don't...\n"));
getchar();
const TCHAR executable_name[] = _T("Gw2.exe");
STARTUPINFO startup_info;
startup_info.cb = sizeof (STARTUPINFO);
GetStartupInfo(&startup_info);
_tprintf(_T("Creating the process...\n"));
PROCESS_INFORMATION process_info;
if (!CreateProcess(executable_name, NULL, NULL, NULL, FALSE, DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &startup_info, &process_info))
Error("Could not create the process");
if (!DebugSetProcessKillOnExit(FALSE))
Error("Could not change the kill on detach debug option");
bool system_breakpoint = true;
bool terminated = false;
detach_from_process = false;
while (!terminated)
{
DEBUG_EVENT debug_event;
if (!WaitForDebugEvent(&debug_event, INFINITE))
Error("An error has occurred while trying to fetch the debug event");
bool exception_handled = false;
switch (debug_event.dwDebugEventCode)
{
case CREATE_PROCESS_DEBUG_EVENT:
{
exception_handled = true;
break;
}
case EXIT_PROCESS_DEBUG_EVENT:
{
exception_handled = true;
terminated = true;
_tprintf(_T("The process is terminating...\n"));
break;
}
case EXCEPTION_DEBUG_EVENT:
{
if (debug_event.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT)
{
if (system_breakpoint)
{
_tprintf(_T("Initializing...\n"));
if (!Initialize(process_info.dwProcessId))
Error("Could not initialize the debugger");
_tprintf(_T("Installing breakpoints...\n"));
createmutexa_breakpoint.address = createmutexa_address;
createmutexa_breakpoint.installed = false;
createmutexa_breakpoint.callback = CreateMutexACallback;
if (!InstallBreakpoint(process_info.hProcess, createmutexa_breakpoint))
Error("Could not install the breakpoint");
createfilew_breakpoint.address = createfilew_address;
createfilew_breakpoint.installed = false;
createfilew_breakpoint.callback = CreateFileWCallback;
if (!InstallBreakpoint(process_info.hProcess, createfilew_breakpoint))
Error("Could not install the breakpoint");
exception_handled = true;
system_breakpoint = false;
break;
}
UInt64 exception_address = (UInt64) debug_event.u.Exception.ExceptionRecord.ExceptionAddress;
HANDLE thread_handle = OpenThread(THREAD_ALL_ACCESS, FALSE, debug_event.dwThreadId);
if (thread_handle == NULL)
Error("Could not access the thread");
CONTEXT thread_context;
thread_context.ContextFlags = CONTEXT_ALL;
if (!GetThreadContext(thread_handle, &thread_context))
Error("Could not obtain the thread context");
if (exception_address == createfilew_breakpoint.address)
{
if (!UninstallBreakpoint(process_info.hProcess, createfilew_breakpoint))
Error("Could not uninstall the breakpoint");
if (createfilew_breakpoint.callback != NULL)
createfilew_breakpoint.callback(process_info.hProcess, thread_context);
exception_handled = true;
}
else if (exception_address == createmutexa_breakpoint.address)
{
if (!UninstallBreakpoint(process_info.hProcess, createmutexa_breakpoint))
Error("Could not uninstall the breakpoint");
if (createmutexa_breakpoint.callback != NULL)
createmutexa_breakpoint.callback(process_info.hProcess, thread_context);
exception_handled = true;
}
if (exception_handled)
{
if (!detach_from_process)
thread_context.EFlags |= 0x0100;
thread_context.Eip = (DWORD) exception_address;
if (!SetThreadContext(thread_handle, &thread_context))
Error("Could not set the thread context");
}
if (!CloseHandle(thread_handle))
Error("Could not release the thread handle");
break;
}
if (debug_event.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP)
{
exception_handled = true;
if (!detach_from_process)
{
if (!createmutexa_breakpoint.installed)
{
if (!InstallBreakpoint(process_info.hProcess, createmutexa_breakpoint))
Error("Could not restore the breakpoint");
}
if (!createfilew_breakpoint.installed)
{
if (!InstallBreakpoint(process_info.hProcess, createfilew_breakpoint))
Error("Could not restore the breakpoint");
}
}
break;
}
}
default:
break;
}
if (detach_from_process)
{
if (!PauseAllThreads(process_info.dwProcessId))
Error("Could not pause the threads");
}
if (!ContinueDebugEvent(debug_event.dwProcessId, debug_event.dwThreadId, exception_handled ? DBG_CONTINUE : DBG_EXCEPTION_NOT_HANDLED))
Error("The debug event could not be continued");
if (detach_from_process)
{
if (!DetachFromProcess(process_info.hProcess, process_info.dwProcessId))
Error("Could not detach from the process");
if (!ResumeAllThreads(process_info.dwProcessId))
Error("Could not resume the threads");
break;
}
}
if (!CloseHandle(process_info.hProcess))
Error("Could not release the process handle");
if (!CloseHandle(process_info.hThread))
Error("Could not release the thread handle");
_tprintf(_T("The game has been launched...\n"));
_tprintf(_T("Exiting in 3 seconds...\n"));
Sleep(3000);
return 0;
}
void Error(const char *message)
{
fprintf(stderr, "An error has occurred and the program must terminate.\nError message: %s\nPress return to quit...", message);
getchar();
ExitProcess(1);
}
bool InstallBreakpoint(HANDLE process, BREAKPOINT & breakpoint)
{
if (breakpoint.installed)
return true;
DWORD old_protection;
if (!VirtualProtectEx(process, (LPVOID) (breakpoint.address), 1, PAGE_EXECUTE_READWRITE, &old_protection))
{
fprintf(stderr, "Memory protection error\n");
return false;
}
SIZE_T bytes_processed;
if (!ReadProcessMemory(process, (LPCVOID) (breakpoint.address), &breakpoint.backup, 1, &bytes_processed))
{
fprintf(stderr, "Could not access the process\n");
return false;
}
UInt8 breakpoint_opcode = 0xCC;
if (!WriteProcessMemory(process, (LPVOID) (breakpoint.address), (LPCVOID) &breakpoint_opcode, 1, &bytes_processed))
{
fprintf(stderr, "Could not access the process\n");
return false;
}
if (!VirtualProtectEx(process, (LPVOID) (breakpoint.address), 1, old_protection, &old_protection))
{
fprintf(stderr, "Memory protection error\n");
return false;
}
breakpoint.installed = true;
if (!FlushInstructionCache(process, (LPCVOID) (breakpoint.address), sizeof (breakpoint.backup)))
{
fprintf(stderr, "Failed to flush the instruction cache\n");
return false;
}
return true;
}
bool UninstallBreakpoint(HANDLE process, BREAKPOINT & breakpoint)
{
if (!breakpoint.installed)
return true;
DWORD old_protection;
if (!VirtualProtectEx(process, (LPVOID) (breakpoint.address), 1, PAGE_EXECUTE_READWRITE, &old_protection))
{
fprintf(stderr, "Memory protection error\n");
return false;
}
SIZE_T bytes_written;
if (!WriteProcessMemory(process, (LPVOID) (breakpoint.address), (LPCVOID) &breakpoint.backup, 1, &bytes_written))
{
fprintf(stderr, "Could not access the process\n");
return false;
}
if (!VirtualProtectEx(process, (LPVOID) (breakpoint.address), 1, old_protection, &old_protection))
{
fprintf(stderr, "Memory protection error\n");
return false;
}
breakpoint.installed = false;
breakpoint.backup = 0x00;
if (!FlushInstructionCache(process, (LPCVOID) (breakpoint.address), sizeof (breakpoint.backup)))
{
fprintf(stderr, "Failed to flush the instruction cache\n");
return false;
}
return true;
}
bool Initialize(DWORD process_id)
{
HANDLE process_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, process_id);
if (process_snapshot == INVALID_HANDLE_VALUE)
Error("Could not obtain a snapshot of the process");
MODULEENTRY32 current_module;
current_module.dwSize = sizeof (MODULEENTRY32);
if (!Module32First(process_snapshot, ¤t_module))
Error("The process snapshot could not be accessed");
UInt64 kernel32_address = 0;
while (true)
{
if (StrStrI(current_module.szExePath, _T("kernel32.dll")) != NULL)
{
kernel32_address = (UInt64) current_module.modBaseAddr;
break;
}
if (!Module32Next(process_snapshot, ¤t_module))
{
if (GetLastError() == ERROR_NO_MORE_FILES)
break;
Error("The process snapshot could not be accessed");
}
}
if (!CloseHandle(process_snapshot))
Error("Could not release the process snapshot");
if (kernel32_address == 0)
Error("Could not determine the module's base address");
HMODULE kernel32_module = GetModuleHandle(_T("Kernel32.dll"));
if (kernel32_module == NULL)
Error("Could not locate the required module");
createmutexa_address = (UInt64) GetProcAddress(kernel32_module, "CreateMutexA");
if (createmutexa_address == NULL)
Error("Could not locate the required procedure");
createfilew_address = (UInt64) GetProcAddress(kernel32_module, "CreateFileW");
if (createfilew_address == NULL)
Error("Could not locate the required procedure");
createmutexa_address = (createmutexa_address - ((UInt64) kernel32_module)) + kernel32_address;
createfilew_address = (createfilew_address - ((UInt64) kernel32_module)) + kernel32_address;
_tprintf(_T("CreateMutexA found at address 0x%llx\n"), (UInt64) createmutexa_address);
_tprintf(_T("CreateFileW found at address 0x%llx\n"), (UInt64) createfilew_address);
return true;
}
void __stdcall CreateMutexACallback(HANDLE process_handle, CONTEXT thread_context)
{
// the mutex name is the third parameter
UInt32 mutex_name_address;
SIZE_T bytes_processed;
if (!ReadProcessMemory(process_handle, (LPVOID) (thread_context.Esp + 0x0C), &mutex_name_address, sizeof (mutex_name_address), &bytes_processed))
Error("Could not access the remote process");
if (mutex_name_address == 0)
return;
// mutexes are in the following form "AN-Mutex-xxxxx"
// the first bytes are enough to determine the ones being created by Guild Wars 2
UInt8 mutex_name[9];
if (!ReadProcessMemory(process_handle, (LPVOID) mutex_name_address, mutex_name, 8, &bytes_processed))
Error("Could not access the remote process");
mutex_name[8] = 0;
if (StrStrIA((char *) mutex_name, "AN-Mutex") == NULL)
return;
UInt8 new_mutex_name[32];
if (FAILED(StringCchPrintfA((char *) new_mutex_name, 32, "%d", GetTickCount())))
Error("Could not generate the mutex name");
if (!WriteProcessMemory(process_handle, (LPVOID) mutex_name_address, (LPCVOID) new_mutex_name, 8, &bytes_processed))
Error("Could not access the remote process");
return;
}
void __stdcall CreateFileWCallback(HANDLE process_handle, CONTEXT thread_context)
{
// the file name is the first parameter
UInt32 file_name_address;
SIZE_T bytes_processed;
if (!ReadProcessMemory(process_handle, (LPVOID) (thread_context.Esp + 0x04), &file_name_address, sizeof (file_name_address), &bytes_processed))
Error("Could not access the remote process");
if (file_name_address == 0)
return;
// we need to get the whole path
WCHAR file_name[MAX_PATH + 1] = { 0 };
if (!ReadProcessMemory(process_handle, (LPVOID) file_name_address, file_name, MAX_PATH * 2, &bytes_processed))
Error("Could not access the remote process");
if (StrStrIW((wchar_t *) file_name, L"Local.dat") != NULL)
{
WCHAR temp_folder[MAX_PATH + 1] = { 0 };
if (!GetTempPathW(MAX_PATH, temp_folder))
Error("Could not determine where the TEMP folder is located");
WCHAR temp_name[32];
if (FAILED(StringCchPrintfW(temp_name, 32, L"%d", GetTickCount())))
Error("Could not generate the temporary file name");
WCHAR temp_file_name[MAX_PATH + 1] = { 0 };
if (FAILED(StringCchPrintfW(temp_file_name, MAX_PATH, L"%s%s", temp_folder, temp_name)))
Error("Could not generate the temporary file path");
_tprintf(_T("Creating a temporary configuration file...\n"));
if (!CopyFile(file_name, temp_file_name, TRUE))
Error("Could not duplicate the configuration file");
// it is highly possible that our path is longer than the space we have in the remote string
// it is easier to just overwrite the pointer
LPVOID remote_buffer = VirtualAllocEx(process_handle, NULL, (MAX_PATH + 1) * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (remote_buffer == NULL)
Error("Could not access the remote process");
if (!WriteProcessMemory(process_handle, (LPVOID) remote_buffer, (LPCVOID) temp_file_name, sizeof (temp_file_name), &bytes_processed))
Error("Could not access the remote process");
if (!WriteProcessMemory(process_handle, (LPVOID) (thread_context.Esp + 0x04), (LPCVOID) &remote_buffer, sizeof (remote_buffer), &bytes_processed))
Error("Could not access the remote process");
// use the FILE_FLAG_DELETE_ON_CLOSE attribute flag, so that we don't leave stuff in the TEMP directory
UInt32 flags_and_attributes;
if (!ReadProcessMemory(process_handle, (LPVOID) (thread_context.Esp + 0x18), (LPVOID) &flags_and_attributes, sizeof (flags_and_attributes), &bytes_processed))
Error("Could not access the remote process");
flags_and_attributes |= FILE_FLAG_DELETE_ON_CLOSE;
if (!WriteProcessMemory(process_handle, (LPVOID) (thread_context.Esp + 0x18), (LPCVOID) &flags_and_attributes, sizeof (flags_and_attributes), &bytes_processed))
Error("Could not access the remote process");
}
else if (StrStrIW((wchar_t *) file_name, L"Gw2.dat") != NULL)
{
_tprintf(_T("Changing permissions for \"Gw2.dat\"..\n"));
// change the permissions so that it's opened with GENERIC_READ and FILE_SHARE_READ
UInt32 desired_access = GENERIC_READ;
if (!WriteProcessMemory(process_handle, (LPVOID) (thread_context.Esp + 0x08), (LPCVOID) &desired_access, sizeof (desired_access), &bytes_processed))
Error("Could not access the remote process");
UInt32 file_share_mode = FILE_SHARE_READ;
if (!WriteProcessMemory(process_handle, (LPVOID) (thread_context.Esp + 0x0C), (LPCVOID) &file_share_mode, sizeof (file_share_mode), &bytes_processed))
Error("Could not access the remote process");
detach_from_process = true;
}
return;
}
bool DetachFromProcess(HANDLE process_handle, UInt32 process_id)
{
_tprintf(_T("Uninstalling breakpoints...\n"));
if (!UninstallBreakpoint(process_handle, createfilew_breakpoint))
Error("Could not uninstall the breakpoint");
if (!UninstallBreakpoint(process_handle, createmutexa_breakpoint))
Error("Could not uninstall the breakpoint");
while (true)
{
DEBUG_EVENT debug_event;
if (!WaitForDebugEvent(&debug_event, 0))
{
if (GetLastError() != ERROR_SEM_TIMEOUT)
Error("An error has occurred while trying to fetch the debug event");
break;
}
bool exception_handled = false;
if (debug_event.dwDebugEventCode == EXCEPTION_BREAKPOINT)
{
UInt64 exception_address = (UInt64) debug_event.u.Exception.ExceptionRecord.ExceptionAddress;
if (exception_address == createfilew_breakpoint.address || exception_address == createmutexa_breakpoint.address)
{
HANDLE thread_handle = OpenThread(THREAD_ALL_ACCESS, FALSE, debug_event.dwThreadId);
if (thread_handle == NULL)
Error("Could not access the thread");
CONTEXT thread_context;
thread_context.ContextFlags = CONTEXT_ALL;
if (!GetThreadContext(thread_handle, &thread_context))
Error("Could not obtain the thread context");
thread_context.Eip = (DWORD) exception_address;
if (!SetThreadContext(thread_handle, &thread_context))
Error("The thread context could not be set");
if (!CloseHandle(thread_handle))
Error("Could not release the thread handle");
exception_handled = true;
}
}
else if (debug_event.dwDebugEventCode == EXCEPTION_SINGLE_STEP)
exception_handled = true;
printf("on detaching: %d\n", debug_event.dwDebugEventCode);
if (!ContinueDebugEvent(debug_event.dwProcessId, debug_event.dwThreadId, exception_handled ? DBG_CONTINUE : DBG_EXCEPTION_NOT_HANDLED))
Error("The debug event could not be continued");
}
_tprintf(_T("Detaching from the process...\n"));
if (!DebugActiveProcessStop(process_id))
Error("Could not detach from the process");
return true;
}
bool PauseAllThreads(UInt32 process_id)
{
HANDLE process_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (process_snapshot == INVALID_HANDLE_VALUE)
return false;
THREADENTRY32 current_thread;
current_thread.dwSize = sizeof (THREADENTRY32);
if (!Thread32First(process_snapshot, ¤t_thread))
return false;
while (true)
{
if (current_thread.th32OwnerProcessID == process_id)
{
HANDLE thread = OpenThread(THREAD_ALL_ACCESS, FALSE, current_thread.th32ThreadID);
if (thread == NULL)
return false;
if (SuspendThread(thread) == (DWORD) -1)
return false;
if (!CloseHandle(thread))
return false;
}
if (!Thread32Next(process_snapshot, ¤t_thread))
{
if (GetLastError() == ERROR_NO_MORE_FILES)
break;
return false;
}
}
if (!CloseHandle(process_snapshot))
return false;
return true;
}
bool ResumeAllThreads(UInt32 process_id)
{
HANDLE process_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, process_id);
if (process_snapshot == INVALID_HANDLE_VALUE)
return false;
THREADENTRY32 current_thread;
current_thread.dwSize = sizeof (THREADENTRY32);
if (!Thread32First(process_snapshot, ¤t_thread))
return false;
while (true)
{
if (current_thread.th32OwnerProcessID == process_id)
{
HANDLE thread = OpenThread(THREAD_ALL_ACCESS, FALSE, current_thread.th32ThreadID);
if (thread == NULL)
return false;
if (ResumeThread(thread) == (DWORD) -1)
return false;
if (!CloseHandle(thread))
return false;
}
if (!Thread32Next(process_snapshot, ¤t_thread))
{
if (GetLastError() == ERROR_NO_MORE_FILES)
break;
return false;
}
}
if (!CloseHandle(process_snapshot))
return false;
return true;
}