From 444ccad844918aa683889473007020ba8715b6a0 Mon Sep 17 00:00:00 2001 From: NukedBart Date: Wed, 10 Dec 2025 23:07:02 +0800 Subject: [PATCH] injector refactoring --- operator/core/FilePath.h | 55 +++ operator/core/Logger.cpp | 3 + operator/core/Logger.h | 63 ++++ operator/core/Types.h | 45 +++ operator/game/BattlEyeBypass.h | 123 +++++++ operator/game/GameLauncher.h | 66 ++++ operator/includes.h | 14 + operator/operator.cpp | 548 ++--------------------------- operator/operator.vcxproj | 1 + operator/operator.vcxproj.filters | 3 + operator/system/DllInjector.h | 94 +++++ operator/system/PrivilegeManager.h | 51 +++ operator/system/ProcessHandler.h | 119 +++++++ operator/system/ServiceManager.h | 174 +++++++++ 14 files changed, 845 insertions(+), 514 deletions(-) create mode 100644 operator/core/FilePath.h create mode 100644 operator/core/Logger.cpp create mode 100644 operator/core/Logger.h create mode 100644 operator/core/Types.h create mode 100644 operator/game/BattlEyeBypass.h create mode 100644 operator/game/GameLauncher.h create mode 100644 operator/includes.h create mode 100644 operator/system/DllInjector.h create mode 100644 operator/system/PrivilegeManager.h create mode 100644 operator/system/ProcessHandler.h create mode 100644 operator/system/ServiceManager.h diff --git a/operator/core/FilePath.h b/operator/core/FilePath.h new file mode 100644 index 0000000..d2abbb3 --- /dev/null +++ b/operator/core/FilePath.h @@ -0,0 +1,55 @@ +// core/FilePath.h +#pragma once +#include +#include + +namespace FilePath +{ + /// @brief Gets the current executable's directory in UTF-8. + inline std::string GetExecutableDirectory() + { + wchar_t wpath[MAX_PATH] = { 0 }; + if (!GetModuleFileNameW(nullptr, wpath, MAX_PATH)) + return {}; + + std::wstring ws(wpath); + size_t pos = ws.rfind(L'\\'); + if (pos == std::wstring::npos) + return {}; + + ws.resize(pos + 1); // keep the \ + + int size = WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, nullptr, 0, nullptr, nullptr); + if (size <= 1) return {}; + + std::string result(size - 1, 0); + WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, result.data(), size, nullptr, nullptr); + return result; + } + + /// @brief Converts UTF-8 to std::wstring + inline std::wstring Utf8ToWide(const std::string_view utf8) + { + if (utf8.empty()) return {}; + + int n = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.length()), nullptr, 0); + if (n == 0) return {}; + + std::wstring wide(n, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.length()), wide.data(), n); + return wide; + } + + /// @brief Converts std::wstring to UTF-8 + inline std::string WideToUtf8(std::wstring_view wide) + { + if (wide.empty()) return {}; + + int n = WideCharToMultiByte(CP_UTF8, 0, wide.data(), static_cast(wide.length()), nullptr, 0, nullptr, nullptr); + if (n == 0) return {}; + + std::string utf8(n, 0); + WideCharToMultiByte(CP_UTF8, 0, wide.data(), static_cast(wide.length()), utf8.data(), n, nullptr, nullptr); + return utf8; + } +} \ No newline at end of file diff --git a/operator/core/Logger.cpp b/operator/core/Logger.cpp new file mode 100644 index 0000000..d496429 --- /dev/null +++ b/operator/core/Logger.cpp @@ -0,0 +1,3 @@ +#include "Logger.h" + +std::mutex Logger::mtx; \ No newline at end of file diff --git a/operator/core/Logger.h b/operator/core/Logger.h new file mode 100644 index 0000000..efa202a --- /dev/null +++ b/operator/core/Logger.h @@ -0,0 +1,63 @@ +// core/Logger.h +#pragma once +#include +#include +#include +#include +#include +#include +#include + +enum class LogLevel { Debug, Info, Warning, Error, Fatal }; + +#define LOG_D(msg) Logger::Log(LogLevel::Debug, CURRENT_MODULE, __func__, msg) +#define LOG_I(msg) Logger::Log(LogLevel::Info, CURRENT_MODULE, __func__, msg) +#define LOG_W(msg) Logger::Log(LogLevel::Warning, CURRENT_MODULE, __func__, msg) +#define LOG_E(msg) Logger::Log(LogLevel::Error, CURRENT_MODULE, __func__, msg) +#define LOG_F(msg) Logger::Log(LogLevel::Fatal, CURRENT_MODULE, __func__, msg) + +class Logger +{ + static std::mutex mtx; + + static std::string GetTime() + { + auto now = std::chrono::system_clock::now(); + auto tt = std::chrono::system_clock::to_time_t(now); + auto ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; + + tm local{}; + localtime_s(&local, &tt); + + std::ostringstream oss; + oss << std::put_time(&local, "%Y-%m-%d %H:%M:%S") + << '.' << std::setfill('0') << std::setw(3) << ms.count(); + return oss.str(); + } + + static char LevelChar(LogLevel lv) + { + switch (lv) + { + case LogLevel::Debug: return 'D'; + case LogLevel::Info: return 'I'; + case LogLevel::Warning: return 'W'; + case LogLevel::Error: return 'E'; + case LogLevel::Fatal: return 'F'; + default: return '?'; + } + } + +public: + template + static void Log(LogLevel level, const char* module, const char* func, const std::string& message) + { + std::lock_guard lock(mtx); + std::ostringstream oss; + oss << "[" << GetTime() << "] " + << std::left << std::setw(40) << (std::string(module) + "::" + func + "()") + << LevelChar(level) << " : " << message; + std::cout << oss.str() << std::endl; + OutputDebugStringA((oss.str() + "\n").c_str()); + } +}; \ No newline at end of file diff --git a/operator/core/Types.h b/operator/core/Types.h new file mode 100644 index 0000000..b34d725 --- /dev/null +++ b/operator/core/Types.h @@ -0,0 +1,45 @@ +// core/Types.h +#pragma once +#include + +typedef int PROCESSINFOCLASS; + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING, *PUNICODE_STRING; + +typedef struct _RTL_USER_PROCESS_PARAMETERS { + BYTE Reserved1[16]; + PVOID Reserved2[10]; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; +} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; + +typedef struct _PEB { + BYTE Reserved1[2]; + BYTE BeingDebugged; + BYTE Reserved2[1]; + PVOID Reserved3[2]; + PVOID Ldr; + PRTL_USER_PROCESS_PARAMETERS ProcessParameters; +} PEB, *PPEB; + +typedef struct _PROCESS_BASIC_INFORMATION { + PVOID Reserved1; + PPEB PebBaseAddress; + PVOID Reserved2[2]; + ULONG_PTR UniqueProcessId; + PVOID Reserved3; +} PROCESS_BASIC_INFORMATION; + +#define ProcessBasicInformation 0 + +typedef LONG(NTAPI* NtQueryInformationProcess_t)( + HANDLE ProcessHandle, + PROCESSINFOCLASS ProcessInformationClass, + PVOID ProcessInformation, + ULONG ProcessInformationLength, + PULONG ReturnLength + ); \ No newline at end of file diff --git a/operator/game/BattlEyeBypass.h b/operator/game/BattlEyeBypass.h new file mode 100644 index 0000000..d6aa963 --- /dev/null +++ b/operator/game/BattlEyeBypass.h @@ -0,0 +1,123 @@ +// game/BattlEyeBypass.h +#pragma once + +#ifdef CURRENT_MODULE +#undef CURRENT_MODULE +#endif +#define CURRENT_MODULE "BattlEyeBypass" + +#include "core/Logger.h" +#include "system/ProcessHandler.h" +#include "core/FilePath.h" +#include +#include +#include + +namespace BattlEyeBypass +{ + inline bool StealCommandLine(std::wstring& outCmdLine) + { + LOG_I("Waiting for EscapeFromTarkov_BE.exe to appear"); + + for(;;) + { + std::vector pids = ProcessHandler::FindProcessesByName(L"EscapeFromTarkov_BE.exe"); + if (pids.empty()) + { + std::cout << "."; + std::this_thread::sleep_for(std::chrono::seconds(1)); + continue; + } + + std::cout << std::endl; + LOG_I("Found " + std::to_string(pids.size()) + + (pids.size() > 1 ? " BE instances" : " BE instance")); + + bool stolen = false; + for (DWORD pid : pids) + { + LOG_I("Reading command line from PID " + std::to_string(pid)); + std::wstring cmd = ProcessHandler::detail::GetCommandLineFromPid(pid); + + if (!cmd.empty()) + { + outCmdLine = std::move(cmd); + stolen = true; + LOG_I("Command line stolen successfully"); + } + else + { + LOG_W("Failed to read command line from PID " + std::to_string(pid)); + } + + if (ProcessHandler::TerminateProcessByPid(pid)) + LOG_I("BE process terminated (PID " + std::to_string(pid) + ")"); + else + LOG_E("Failed to terminate PID " + std::to_string(pid)); + } + + return stolen; + } + } + + inline std::wstring CleanCommandLine(const std::wstring& stolen) + { + std::wregex rx(L"EscapeFromTarkov_BE\\.exe", std::regex_constants::icase); + return std::regex_replace(stolen, rx, L"EscapeFromTarkov.exe"); + } + + inline std::wstring ExtractExePath(const std::wstring& cmdline) + { + std::wsmatch m; + if (std::regex_search(cmdline, m, std::wregex(L"\"([^\"]+)\""))) + return m[1].str(); + + size_t space = cmdline.find(L' '); + if (space != std::wstring::npos) + return cmdline.substr(0, space); + + return cmdline; + } + + inline std::wstring ExtractWorkingDir(const std::wstring& exePath) + { + size_t pos = exePath.find_last_of(L"\\/"); + if (pos == std::wstring::npos) + return L"."; + return exePath.substr(0, pos); + } + + struct GameLaunchInfo + { + std::wstring ExePath; + std::wstring WorkingDir; + std::wstring CleanCmdLine; + }; + + inline bool PrepareLaunchInfo(GameLaunchInfo& outInfo) + { + std::wstring stolenCmdLine; + if (!StealCommandLine(stolenCmdLine)) + { + LOG_E("Failed to steal any BE command line"); + return false; + } + + std::wstring clean = CleanCommandLine(stolenCmdLine); + std::wstring exePath = ExtractExePath(clean); + std::wstring workDir = ExtractWorkingDir(exePath); + + LOG_I("Game executable : " + std::string(exePath.begin(), exePath.end())); + LOG_I("Working directory : " + std::string(workDir.begin(), workDir.end())); + +#ifdef _DEBUG + LOG_I("Command line: " + std::string(clean.begin(), clean.end())); +#endif + + outInfo.ExePath = std::move(exePath); + outInfo.WorkingDir = std::move(workDir); + outInfo.CleanCmdLine = std::move(clean); + + return true; + } +} \ No newline at end of file diff --git a/operator/game/GameLauncher.h b/operator/game/GameLauncher.h new file mode 100644 index 0000000..e56c944 --- /dev/null +++ b/operator/game/GameLauncher.h @@ -0,0 +1,66 @@ +// game/GameLauncher.h +#pragma once + +#ifdef CURRENT_MODULE +#undef CURRENT_MODULE +#endif +#define CURRENT_MODULE "GameLauncher" + +#include "game/BattlEyeBypass.h" +#include "system/DllInjector.h" +#include "core/Logger.h" +#include + + + +namespace GameLauncher +{ + /// @brief Suspend the game and inject iOperator.dll then resume + /// @return true = Game started with DLL injected + inline bool LaunchAndInject(const BattlEyeBypass::GameLaunchInfo& info) + { + LOG_I("Launching game in suspended mode..."); + + STARTUPINFOW si{ sizeof(si) }; + PROCESS_INFORMATION pi{}; + + wchar_t* cmd = const_cast(info.CleanCmdLine.c_str()); + + BOOL created = CreateProcessW( + nullptr, // lpApplicationName + cmd, // lpCommandLine (mutable!) + nullptr, nullptr, // security + FALSE, // inherit handles + CREATE_SUSPENDED, // suspend the game + nullptr, // environment + info.WorkingDir.c_str(), // ensure that the game's running at its own dir + &si, &pi + ); + + if (!created) + { + LOG_E("CreateProcessW failed: " + std::to_string(GetLastError())); + return false; + } + + LOG_I("Game started (suspended) - PID " + std::to_string(pi.dwProcessId)); + + ResumeThread(pi.hThread); + + if (!DllInjector::InjectLocalDll(pi.dwProcessId, "iOperator.dll")) + { + LOG_E("DLL injection failed - game will run without cheat"); + Sleep(20000); + } + else + { + LOG_I("iOperator.dll injected successfully!"); + LOG_I("Bootstrap complete. Enjoy your raids!"); + } + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + return true; + } +} \ No newline at end of file diff --git a/operator/includes.h b/operator/includes.h new file mode 100644 index 0000000..d6d505a --- /dev/null +++ b/operator/includes.h @@ -0,0 +1,14 @@ +#include "core/Types.h" +#include "core/Logger.h" +#include "core/FilePath.h" +#include "system/PrivilegeManager.h" +#include "system/ProcessHandler.h" +#include "system/ServiceManager.h" +#include "system/DllInjector.h" +#include "game/BattlEyeBypass.h" +#include "game/GameLauncher.h" + +#ifdef CURRENT_MODULE +#undef CURRENT_MODULE +#endif +#define CURRENT_MODULE "EFTOperator" \ No newline at end of file diff --git a/operator/operator.cpp b/operator/operator.cpp index 6ac99a0..a1a4770 100644 --- a/operator/operator.cpp +++ b/operator/operator.cpp @@ -1,526 +1,46 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#pragma comment(lib, "shlwapi.lib") - -typedef struct _UNICODE_STRING { - USHORT Length; - USHORT MaximumLength; - PWSTR Buffer; -} UNICODE_STRING, * PUNICODE_STRING; - -typedef struct _RTL_USER_PROCESS_PARAMETERS { - BYTE Reserved1[16]; - PVOID Reserved2[10]; - UNICODE_STRING ImagePathName; - UNICODE_STRING CommandLine; -} RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS; - -typedef struct _PEB { - BYTE Reserved1[2]; - BYTE BeingDebugged; - BYTE Reserved2[1]; - PVOID Reserved3[2]; - PVOID Ldr; - PRTL_USER_PROCESS_PARAMETERS ProcessParameters; -} PEB, * PPEB; - -typedef struct _PROCESS_BASIC_INFORMATION { - PVOID Reserved1; - PPEB PebBaseAddress; - PVOID Reserved2[2]; - ULONG_PTR UniqueProcessId; - PVOID Reserved3; -} PROCESS_BASIC_INFORMATION; - -// ProcessBasicInformation = 0 -#ifndef ProcessBasicInformation -#define ProcessBasicInformation 0 -#endif - - -std::wstring GetCommandLine(DWORD pid) -{ - std::wstring result; - HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, pid); - if (!hProcess) return result; - - // Load ntdll - auto ntdll = GetModuleHandleA("ntdll.dll"); - auto NtQueryInformationProcess = (LONG(WINAPI*)(HANDLE, int, PVOID, ULONG, PULONG))GetProcAddress(ntdll, "NtQueryInformationProcess"); - - if (!NtQueryInformationProcess) { - CloseHandle(hProcess); - return result; - } - - PROCESS_BASIC_INFORMATION pbi{}; - ULONG len; - if (NtQueryInformationProcess(hProcess, 0, &pbi, sizeof(pbi), &len) == 0) - { - PEB peb{}; - RTL_USER_PROCESS_PARAMETERS upp{}; - if (ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), nullptr)) - { - if (ReadProcessMemory(hProcess, peb.ProcessParameters, &upp, sizeof(upp), nullptr)) - { - wchar_t* buffer = new wchar_t[8192]; - if (ReadProcessMemory(hProcess, upp.CommandLine.Buffer, buffer, upp.CommandLine.Length, nullptr)) - { - result = std::wstring(buffer, upp.CommandLine.Length / sizeof(wchar_t)); - } - delete[] buffer; - } - } - } - - CloseHandle(hProcess); - return result; -} -std::string GetExeDirectory() -{ - wchar_t path[MAX_PATH] = { 0 }; - GetModuleFileNameW(nullptr, path, MAX_PATH); - - std::wstring wpath(path); - size_t pos = wpath.rfind(L'\\'); - if (pos != std::wstring::npos) { - wpath.resize(pos + 1); - } - else { - wpath.clear(); - } - - int size = WideCharToMultiByte(CP_UTF8, 0, wpath.c_str(), -1, nullptr, 0, nullptr, nullptr); - std::string result(size - 1, 0); - WideCharToMultiByte(CP_UTF8, 0, wpath.c_str(), -1, result.data(), size, nullptr, nullptr); - return result; -} - -static std::wstring Utf8ToW(const std::string& s) -{ - if (s.empty()) return {}; - int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0); - if (n == 0) return {}; - std::wstring w(n - 1, L'\0'); - MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, w.data(), n); - return w; -} - -bool isProcessRunning(const std::string& processName) { - std::string command = "tasklist | findstr " + processName; - - FILE* pipe = _popen(command.c_str(), "r"); - if (!pipe) { - std::cerr << "Failed to run command\n"; - return false; - } - - char buffer[128]; - bool found = false; - while (fgets(buffer, sizeof(buffer), pipe)) { - if (strstr(buffer, processName.c_str())) { - found = true; - break; - } - } - - _pclose(pipe); - - return found; -} - -BOOL IsRunAsAdministrator() -{ - BOOL fIsRunAsAdmin = FALSE; - PSID pAdministratorsGroup = NULL; - SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; - - if (!AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, - DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdministratorsGroup)) - { - return FALSE; - } - - if (!CheckTokenMembership(NULL, pAdministratorsGroup, &fIsRunAsAdmin)) - { - FreeSid(pAdministratorsGroup); - return FALSE; - } - - FreeSid(pAdministratorsGroup); - return fIsRunAsAdmin; -} - -BOOL ElevateNow() -{ - if (IsRunAsAdministrator()) - return true; - - WCHAR szPath[MAX_PATH]; - if (!GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath))) - return false; - - SHELLEXECUTEINFO sei = { sizeof(sei) }; - sei.lpVerb = L"runas"; - sei.lpFile = szPath; - sei.hwnd = NULL; - sei.nShow = SW_SHOWDEFAULT; - - if (ShellExecuteEx(&sei)) - exit(1); - - DWORD dwError = GetLastError(); - if (dwError == ERROR_CANCELLED) - return false; - - return true; -} - -static bool WaitForServiceDeletion(SC_HANDLE scm, const std::wstring& svcName, DWORD timeoutMs = 10000) -{ - const DWORD interval = 500; - DWORD waited = 0; - std::cout << "Waiting for service deletion"; - while (waited < timeoutMs) { - std::cout << "."; - SC_HANDLE h = OpenServiceW(scm, svcName.c_str(), SERVICE_QUERY_STATUS); - if (!h) { - DWORD err = GetLastError(); - if (err == ERROR_SERVICE_DOES_NOT_EXIST) - return true; - } - else { - CloseServiceHandle(h); - } - Sleep(interval); - waited += interval; - } - std::cout << std::endl; - return false; -} - -void verifyServiceDeletion(const std::wstring& svcName) -{ - SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE); - if (!scm) { - std::cerr << "OpenSCManager failed: " << GetLastError() << std::endl; - exit(-1); - } - - SC_HANDLE hExisting = OpenServiceW(scm, svcName.c_str(), SERVICE_STOP | DELETE | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS); - if (!hExisting) - return; - - SERVICE_STATUS status{}; - if (ControlService(hExisting, SERVICE_CONTROL_STOP, &status)) { - long long start = GetTickCount64(); - std::cout << "Waiting for BEService to stop"; - while (status.dwCurrentState != SERVICE_STOPPED && GetTickCount64() - start < 10000) { - Sleep(500); - std::cout << "."; - if (!QueryServiceStatus(hExisting, &status)) break; - } - std::cout << std::endl; - } - - if (!DeleteService(hExisting)) { - DWORD err = GetLastError(); - std::cerr << "DeleteService failed: " << err << std::endl; - } - CloseServiceHandle(hExisting); - - if (!WaitForServiceDeletion(scm, svcName, 10000)) { - std::cerr << "Service still marked for deletion after waiting. Error 1072 may occur if you try to recreate it immediately.\n"; - CloseServiceHandle(scm); - exit(-2); - } -} - -BOOL InjectDLL(DWORD processId, const char* dllPath) -{ - HANDLE hProcess = OpenProcess( - PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | - PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, - FALSE, processId); - - if (!hProcess) { - std::cout << "[-] OpenProcess failed: " << GetLastError() << std::endl; - return FALSE; - } - - SIZE_T pathLen = strlen(dllPath) + 1; - LPVOID remoteMem = VirtualAllocEx(hProcess, nullptr, pathLen, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - if (!remoteMem) { - std::cout << "[-] VirtualAllocEx failed: " << GetLastError() << std::endl; - CloseHandle(hProcess); - return FALSE; - } - - if (!WriteProcessMemory(hProcess, remoteMem, dllPath, pathLen, nullptr)) { - std::cout << "[-] WriteProcessMemory failed: " << GetLastError() << std::endl; - VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE); - CloseHandle(hProcess); - return FALSE; - } - - HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); - if (!hKernel32) { - VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE); - CloseHandle(hProcess); - return FALSE; - } - - LPTHREAD_START_ROUTINE pLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, "LoadLibraryA"); - - HANDLE hThread = CreateRemoteThread(hProcess, nullptr, 0, pLoadLibrary, remoteMem, 0, nullptr); - if (!hThread) { - std::cout << "[-] CreateRemoteThread failed: " << GetLastError() << std::endl; - VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE); - CloseHandle(hProcess); - return FALSE; - } - - WaitForSingleObject(hThread, 10000); - - DWORD exitCode = 0; - GetExitCodeThread(hThread, &exitCode); - - CloseHandle(hThread); - VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE); - CloseHandle(hProcess); - - if (exitCode != 0) { - std::cout << "[+] Injected successfully! (hModule = 0x" - << std::hex << exitCode << std::dec << ")\n"; - return TRUE; - } - - std::cout << "[-] LoadLibraryA returned NULL in target process\n"; - return FALSE; -} - -BOOL StealAndKillBE(std::wstring& outCmdLine, DWORD& outPid) -{ - HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hSnap == INVALID_HANDLE_VALUE) return false; - - PROCESSENTRY32 pe{ .dwSize = sizeof(pe) }; - bool found = false; - - if (!Process32First(hSnap, &pe)) - goto not_found; - - do { - if (_wcsicmp(pe.szExeFile, L"EscapeFromTarkov_BE.exe") != 0) - continue; - std::cout << std::endl; - found = true; - outPid = pe.th32ProcessID; - std::wcout << L"[+] Found BE process (PID: " << outPid << L") - Stealing command line...\n"; - - // 1. STEAL COMMAND LINE FIRST - outCmdLine = GetCommandLine(outPid); - if (outCmdLine.empty()) { - std::wcout << L"[-] Failed to read command line from PID " << outPid << L"\n"; - continue; // try next instance - } - - std::wcout << L"[+] Command line stolen successfully!\n"; - - // 2. NOW KILL IT - HANDLE hProc = OpenProcess(PROCESS_TERMINATE, FALSE, outPid); - if (hProc) { - TerminateProcess(hProc, 0); - CloseHandle(hProc); - std::cout << "[+] BE process terminated.\n"; - } - - // Don't break — kill ALL BE instances (in case of multiple) - } while (Process32Next(hSnap, &pe)); -not_found: - CloseHandle(hSnap); - return found && !outCmdLine.empty(); -} - -BOOL Battleyent() -{ - std::wstring stolenCmdLine; - DWORD bePid = 0; - - // ── 1. Wait for BE and steal command line ─────────────────────── - std::cout << "[.] Waiting for EscapeFromTarkov_BE.exe to appear"; - while (!StealAndKillBE(stolenCmdLine, bePid)) - { - std::cout << "."; - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - - // ── 2. Replace BE executable with clean one ─────────────────── - std::wstring cleanCmdLine = std::regex_replace( - stolenCmdLine, - std::wregex(L"EscapeFromTarkov_BE\\.exe", std::regex_constants::icase), - L"EscapeFromTarkov.exe"); - - // ── 3. Extract real executable path (remove quotes if any) ───── - std::wsmatch match; - std::wstring exeFullPath; - - if (std::regex_search(cleanCmdLine, match, std::wregex(L"\"([^\"]+)\""))) - { - exeFullPath = match[1].str(); - cleanCmdLine = std::regex_replace(cleanCmdLine, - std::wregex(L"\"[^\"]+\""), - exeFullPath, - std::regex_constants::format_first_only); - } - else if (cleanCmdLine.find(L' ') != std::wstring::npos) - { - exeFullPath = cleanCmdLine.substr(0, cleanCmdLine.find(L' ')); - } - else - { - exeFullPath = cleanCmdLine; - } - - // ── 4. Build correct working directory ─────────────────────── - const std::wstring gameWorkingDir = exeFullPath.substr(0, exeFullPath.find_last_of(L"\\/")); - - std::wcout << L"[+] Game path : " << exeFullPath << L'\n'; - std::wcout << L"[+] Working directory: " << gameWorkingDir << L'\n'; - std::wcout << L"[+] Final cmdline : " << cleanCmdLine << L'\n'; - - // ── 5. Launch the clean game (suspended) ───────────────────── - STARTUPINFOW si = { sizeof(si) }; - PROCESS_INFORMATION pi = { }; - - const BOOL created = CreateProcessW( - nullptr, - cleanCmdLine.data(), - nullptr, nullptr, - FALSE, - CREATE_SUSPENDED, // start suspended -> safe injection - nullptr, - gameWorkingDir.c_str(), // avoid consistency crashes - &si, - &pi - ); - - if (!created) - { - std::cerr << "[-] CreateProcessW failed: " << GetLastError() << std::endl; - return FALSE; - } - - std::cout << "[+] EscapeFromTarkov.exe launched (PID: " << pi.dwProcessId << ")\n"; - - // ── 6. Resume main thread ───── - ResumeThread(pi.hThread); - - // ── 7. Inject our DLL ─────────────────────────────────────── - const std::string dllPath = GetExeDirectory() + "\\iOperator.dll"; - - if (!InjectDLL(pi.dwProcessId, dllPath.c_str())) - { - std::cout << "[-] Injection failed!\n"; - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - return FALSE; - } - - // ── 8. Success ─────────────────────────────────────────────── - std::cout << "[+] iOperator.dll injected successfully!\n"; - std::cout << "[+] Bootstrap done. Enjoy your raids!\n"; - - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - - Sleep(3000); - return TRUE; -} +#include "includes.h" int main() { - std::cout << "EFT PvE Operator for il2cpp\n\n" - << "Credits:\nBEService bypass - rqhz\nbattleyen't for il2cpp - Suchi96\n\n" - << "This cheat is free on UnknownCheats.me . If you payed for it, you've been scammed!\n" - << "此作弊程序在UnknownCheats.me上免费发布。如果您付费购买此作弊程序,您被骗了。请检举卖家。\n"; - while (!ElevateNow()) + LOG_I("=== EFT PvE Operator for il2cpp ==="); + LOG_I("Credits:"); + LOG_I("BEService bypass - rqhz"); + LOG_I("Battleyen't - hollow"); + LOG_I("Battleyen't for il2cpp - Suchi96"); + LOG_I("This cheat is free on UnknownCheats.me . If you payed for it, you've been scammed!"); + LOG_I("此作弊程序在UnknownCheats.me上免费发布。如果您付费购买此作弊程序,您被骗了。请检举卖家。"); + + while (!PrivilegeManager::ElevateNow()) { - std::cout << "This cheat requires administrator permissions to work. Press Enter to try again or close this window and run operator as administrator.\n"; + LOG_W("This cheat requires administrator permissions to work."); + LOG_W("Press any key to try again or close this window and run operator as administrator."); system("pause"); } + LOG_I("We're running as Administrator"); Sleep(2000); - std::cout << "\nSetting up services.\n"; - system("taskkill /f /im service.exe"); - system("sc stop BEService"); - system("sc delete BEService"); - system("sc stop BEDaisy"); - system("sc delete BEDaisy"); - - std::string exeDir = GetExeDirectory(); - std::wstring svcName = L"BEService"; - std::wstring svcDisplay = L"BattlEye Service"; - std::wstring svcExePathW = Utf8ToW(exeDir) + L"service.exe"; - - verifyServiceDeletion(svcName); - - SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE); - if (!scm) { - std::cerr << "OpenSCManager failed: " << GetLastError() << std::endl; - exit(-1); - } - SC_HANDLE hNew = CreateServiceW( - scm, - svcName.c_str(), - svcDisplay.c_str(), - SERVICE_ALL_ACCESS, - SERVICE_WIN32_OWN_PROCESS, - SERVICE_AUTO_START, - SERVICE_ERROR_NORMAL, - svcExePathW.c_str(), - nullptr, - nullptr, - nullptr, - nullptr, - nullptr); - - if (!hNew) { - DWORD err = GetLastError(); - std::cerr << "CreateServiceW failed: " << err << std::endl; - CloseServiceHandle(scm); - return -3; - } - - if (!StartServiceW(hNew, 0, nullptr)) { - DWORD err = GetLastError(); - if (err != ERROR_SERVICE_ALREADY_RUNNING) { - std::cerr << "StartServiceW failed: " << err << std::endl; - } - } - - CloseServiceHandle(hNew); - CloseServiceHandle(scm); - std::cout << std::endl; - - std::cout << "Spinning up. Launch EFT from launcher.\n"; - - while (!Battleyent()); - - std::cout << "Waiting for the game to start"; - while (!isProcessRunning("EscapeFromTarkov.exe")) + LOG_I("Setting up services."); + std::wstring fakeServicePath = FilePath::Utf8ToWide(FilePath::GetExecutableDirectory() + "service.exe"); + if (!ServiceManager::DeployFakeBEService(fakeServicePath)) { - std::cout << "."; - Sleep(500); + LOG_F("Failed to deploy fake BEService - exiting"); + system("pause"); + return -1; } + + LOG_I("Preparing for Battleyen't."); + LOG_I("Launch EFT from launcher now."); + BattlEyeBypass::GameLaunchInfo launchInfo; + if (!BattlEyeBypass::PrepareLaunchInfo(launchInfo)) + { + LOG_F("BattlEye bypass failed - cannot continue"); + system("pause"); + return -1; + } + + LOG_I("Launching game."); + GameLauncher::LaunchAndInject(launchInfo); + Sleep(6000); + return 0; } \ No newline at end of file diff --git a/operator/operator.vcxproj b/operator/operator.vcxproj index e662b1f..d349d8e 100644 --- a/operator/operator.vcxproj +++ b/operator/operator.vcxproj @@ -128,6 +128,7 @@ + diff --git a/operator/operator.vcxproj.filters b/operator/operator.vcxproj.filters index 93cab34..abfa680 100644 --- a/operator/operator.vcxproj.filters +++ b/operator/operator.vcxproj.filters @@ -18,5 +18,8 @@ 源文件 + + 源文件 + \ No newline at end of file diff --git a/operator/system/DllInjector.h b/operator/system/DllInjector.h new file mode 100644 index 0000000..8a112f6 --- /dev/null +++ b/operator/system/DllInjector.h @@ -0,0 +1,94 @@ +// system/DllInjector.h +#pragma once + +#ifdef CURRENT_MODULE +#undef CURRENT_MODULE +#endif +#define CURRENT_MODULE "DllInjector" + +#include "core/FilePath.h" +#include "core/Logger.h" +#include +#include +#include + +namespace DllInjector +{ + struct HandleCloser { void operator()(HANDLE h) const noexcept { if (h && h != INVALID_HANDLE_VALUE) CloseHandle(h); } }; + using UniqueHandle = std::unique_ptr; + + /// @brief Inject a specific DLL to a given process identified by PID using LoadLibraryA + /// @param processId Target process PID + /// @param dllPath Full DLL path in ANSI + /// @return true = Injected successfully with a non-zero handle + inline bool Inject(DWORD processId, const std::string& dllPath) + { + UniqueHandle hProcess{ OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | + PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, + FALSE, processId) }; + + if (!hProcess) + { + LOG_E("OpenProcess failed, PID=" + std::to_string(processId) + ", Error=" + std::to_string(GetLastError())); + return false; + } + + SIZE_T pathBytes = dllPath.size() + 1; + LPVOID remoteMem = VirtualAllocEx(hProcess.get(), nullptr, pathBytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (!remoteMem) + { + LOG_E("VirtualAllocEx failed, Error=" + std::to_string(GetLastError())); + return false; + } + + if (!WriteProcessMemory(hProcess.get(), remoteMem, dllPath.c_str(), pathBytes, nullptr)) + { + LOG_E("WriteProcessMemory failed, Error=" + std::to_string(GetLastError())); + VirtualFreeEx(hProcess.get(), remoteMem, 0, MEM_RELEASE); + return false; + } + + HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll"); + if (!hKernel32) + { + VirtualFreeEx(hProcess.get(), remoteMem, 0, MEM_RELEASE); + LOG_E("GetModuleHandleW(kernel32.dll) failed"); + return false; + } + + auto pLoadLibraryA = reinterpret_cast(GetProcAddress(hKernel32, "LoadLibraryA")); + + UniqueHandle hThread{ CreateRemoteThread(hProcess.get(), nullptr, 0, pLoadLibraryA, remoteMem, 0, nullptr) }; + if (!hThread) + { + LOG_E("CreateRemoteThread failed, Error=" + std::to_string(GetLastError())); + VirtualFreeEx(hProcess.get(), remoteMem, 0, MEM_RELEASE); + return false; + } + + WaitForSingleObject(hThread.get(), 8000); + + DWORD exitCode = 0; + GetExitCodeThread(hThread.get(), &exitCode); + VirtualFreeEx(hProcess.get(), remoteMem, 0, MEM_RELEASE); + + if (exitCode == 0) + { + LOG_E("LoadLibraryA returned NULL in target process"); + return false; + } + + LOG_I("Injected successfully! hModule=0x" + std::to_string(exitCode)); + return true; + } + + /// @brief Injects a DLL next to the current process's executable into a given process identified by PID + /// @param processId Target process PID + /// @param dllFileName Just the file name e.g. "iOperator.dll" + inline bool InjectLocalDll(DWORD processId, const std::string& dllFileName) + { + std::string fullPath = FilePath::GetExecutableDirectory() + dllFileName; + return Inject(processId, fullPath); + } +} \ No newline at end of file diff --git a/operator/system/PrivilegeManager.h b/operator/system/PrivilegeManager.h new file mode 100644 index 0000000..7e24249 --- /dev/null +++ b/operator/system/PrivilegeManager.h @@ -0,0 +1,51 @@ +// system/PrivilegeMgr.h +#pragma once +#include +#include + +namespace PrivilegeManager +{ + inline bool IsRunAsAdministrator() + { + BOOL isAdmin = FALSE; + PSID adminGroup = nullptr; + SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY; + + BOOL success = AllocateAndInitializeSid( + &ntAuthority, 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &adminGroup); + + if (success) + { + if (!CheckTokenMembership(nullptr, adminGroup, &isAdmin)) + isAdmin = FALSE; + FreeSid(adminGroup); + } + + return isAdmin == TRUE; + } + + inline bool ElevateNow() + { + if (IsRunAsAdministrator()) + return true; + + wchar_t exePath[MAX_PATH] = { 0 }; + if (!GetModuleFileNameW(nullptr, exePath, MAX_PATH)) + return false; + + SHELLEXECUTEINFOW sei{ sizeof(sei) }; + sei.lpVerb = L"runas"; + sei.lpFile = exePath; + sei.nShow = SW_NORMAL; + sei.fMask = SEE_MASK_NOCLOSEPROCESS; + + if (ShellExecuteExW(&sei)) + exit(0); + + return false; + } +} \ No newline at end of file diff --git a/operator/system/ProcessHandler.h b/operator/system/ProcessHandler.h new file mode 100644 index 0000000..ea5406a --- /dev/null +++ b/operator/system/ProcessHandler.h @@ -0,0 +1,119 @@ +// system/ProcessHandler.h +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "core/Types.h" +#include +#include +#include +#include +#include +#include + +template +struct scope_exit +{ + F f; + explicit scope_exit(F&& func) : f(std::forward(func)) {} + ~scope_exit() { f(); } +}; + +namespace ProcessHandler +{ + // RAII Handles + struct HandleCloser + { + void operator()(HANDLE h) const noexcept + { + if (h && h != INVALID_HANDLE_VALUE) + CloseHandle(h); + } + }; + using UniqueHandle = std::unique_ptr; + + // Process detail structures + namespace detail + { + inline std::wstring GetCommandLineFromPid(DWORD pid) + { + std::wstring result; + HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, pid); + if (!hProc) return result; + auto closeProc = scope_exit([&] { CloseHandle(hProc); }); + + HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); + if (!ntdll) return result; + + // ޸ʹԼ + NtQueryInformationProcess_t NtQuery = + (NtQueryInformationProcess_t)GetProcAddress(ntdll, "NtQueryInformationProcess"); + if (!NtQuery) return result; + + PROCESS_BASIC_INFORMATION pbi{}; + if (NtQuery(hProc, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr) != 0) + return result; + + PEB peb{}; + if (!ReadProcessMemory(hProc, pbi.PebBaseAddress, &peb, sizeof(peb), nullptr)) + return result; + + RTL_USER_PROCESS_PARAMETERS upp{}; + if (!ReadProcessMemory(hProc, peb.ProcessParameters, &upp, sizeof(upp), nullptr)) + return result; + + if (upp.CommandLine.Length == 0 || !upp.CommandLine.Buffer) + return result; + + std::unique_ptr buffer(new wchar_t[upp.CommandLine.Length / 2 + 1]); + if (!ReadProcessMemory(hProc, upp.CommandLine.Buffer, buffer.get(), upp.CommandLine.Length, nullptr)) + return result; + + result.assign(buffer.get(), upp.CommandLine.Length / 2); + return result; + } + } + + /// @brief Find all pids with given process name and return them in a list. + inline std::vector FindProcessesByName(const std::wstring& exeName) + { + std::vector pids; + + UniqueHandle hSnap{ CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }; + if (!hSnap || hSnap.get() == INVALID_HANDLE_VALUE) + return pids; + + PROCESSENTRY32W pe{ sizeof(pe) }; + if (!Process32FirstW(hSnap.get(), &pe)) + return pids; + + do { + if (_wcsicmp(pe.szExeFile, exeName.c_str()) == 0) + pids.push_back(pe.th32ProcessID); + } while (Process32NextW(hSnap.get(), &pe)); + + return pids; + } + + /// @brief Kill specific process by its PID + inline bool TerminateProcessByPid(DWORD pid) + { + UniqueHandle hProc{ OpenProcess(PROCESS_TERMINATE, FALSE, pid) }; + if (!hProc) + return false; + + bool ok = ::TerminateProcess(hProc.get(), 0) == TRUE; + return ok; + } + + /// @brief Check if a process with the given executable name is running + inline bool IsProcessRunning(const std::string& exeName) + { + return !FindProcessesByName(FilePath::Utf8ToWide(exeName)).empty(); + } +} \ No newline at end of file diff --git a/operator/system/ServiceManager.h b/operator/system/ServiceManager.h new file mode 100644 index 0000000..6d24206 --- /dev/null +++ b/operator/system/ServiceManager.h @@ -0,0 +1,174 @@ +// system/ServiceManager.h +#pragma once + +#ifdef CURRENT_MODULE +#undef CURRENT_MODULE +#endif +#define CURRENT_MODULE "ServiceManager" + +#include "core/Logger.h" +#include +#include + +namespace ServiceManager +{ + struct ServiceHandleCloser { void operator()(SC_HANDLE h) const noexcept { if (h) CloseServiceHandle(h); } }; + using UniqueServiceHandle = std::unique_ptr::type, ServiceHandleCloser>; + + inline bool WaitUntilGone(SC_HANDLE scm, const std::wstring& name, DWORD timeoutMs = 30000) + { + const DWORD step = 2000; + DWORD elapsed = 0; + + LOG_I("Waiting for service deletion: " + std::string(name.begin(), name.end())); + + while (elapsed < timeoutMs) + { + elapsed += step; + Sleep(step); + + system("sc query BEService >nul 2>&1"); + + UniqueServiceHandle hSvc{ OpenServiceW(scm, name.c_str(), SERVICE_QUERY_STATUS) }; + if (hSvc) + { + CloseServiceHandle(hSvc.get()); + continue; + } + + if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + { + CloseServiceHandle(hSvc.get()); + LOG_I("Service fully deleted from SCM"); + return true; + } + } + + LOG_W("Timeout waiting for service deletion (ERROR 1072 may occur)"); + return false; + } + + inline bool StopService(SC_HANDLE hSvc) + { + SERVICE_STATUS status{}; + if (!ControlService(hSvc, SERVICE_CONTROL_STOP, &status)) + return false; + + LOG_I("Sent stop command to service"); + + for (int i = 0; i < 40; ++i) + { + Sleep(300); + if (!QueryServiceStatus(hSvc, &status)) + return false; + if (status.dwCurrentState == SERVICE_STOPPED) + return true; + } + + LOG_W("Service did not stop in time"); + return false; + } + + inline bool Remove(const std::wstring& serviceName) + { + UniqueServiceHandle scm{ OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT) }; + if (!scm) + { + LOG_E("OpenSCManager failed: " + std::to_string(GetLastError())); + return false; + } + + UniqueServiceHandle hSvc{ OpenServiceW(scm.get(), serviceName.c_str(), + SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE) }; + + if (!hSvc) + { + if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + { + LOG_I("Service does not exist, nothing to delete"); + return true; + } + + LOG_E("OpenService failed: " + std::to_string(GetLastError())); + return false; + } + + system("taskkill /f /im service.exe >nul 2>&1"); + + StopService(hSvc.get()); + + if (DeleteService(hSvc.get())) + { + LOG_I("DeleteService command sent"); + CloseServiceHandle(hSvc.get()); + return WaitUntilGone(scm.get(), serviceName); + } + + DWORD err = GetLastError(); + if (err != ERROR_SERVICE_MARKED_FOR_DELETE) + { + LOG_E("DeleteService failed: " + std::to_string(err)); + return false; + } + LOG_I("Service already marked for delete"); + } + + inline bool DeployFakeBEService(const std::wstring& fakeExePath) + { + UniqueServiceHandle scm{ OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CREATE_SERVICE) }; + if (!scm) + { + LOG_E("OpenSCManager(create) failed: " + std::to_string(GetLastError())); + return false; + } + + // Just create and start the service + UniqueServiceHandle hSvc{ CreateServiceW( + scm.get(), L"BEService", L"BattlEye Service", + SERVICE_START | SERVICE_QUERY_STATUS, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + fakeExePath.c_str(), + nullptr, nullptr, nullptr, nullptr, nullptr) }; + + if (hSvc) + return StartServiceW(hSvc.get(), 0, nullptr) || + GetLastError() == ERROR_SERVICE_ALREADY_RUNNING; + + // if failed to create the service, delete the service by force and then create + DWORD err = GetLastError(); + if (err != ERROR_SERVICE_EXISTS && err != ERROR_SERVICE_MARKED_FOR_DELETE) + { + LOG_E("CreateService failed: " + std::to_string(err)); + return false; + } + + LOG_W("Service conflict detected, forcing cleanup..."); + if (!Remove(L"BEService")) + return false; + + // retry + hSvc.reset(CreateServiceW(scm.get(), L"BEService", L"BattlEye Service", + SERVICE_START | SERVICE_QUERY_STATUS, + SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, fakeExePath.c_str(), + nullptr, nullptr, nullptr, nullptr, nullptr)); + + if (!hSvc) + { + LOG_E("Retry CreateService failed: " + std::to_string(GetLastError())); + return false; + } + + bool started = StartServiceW(hSvc.get(), 0, nullptr); + if (!started && GetLastError() != ERROR_SERVICE_ALREADY_RUNNING) + { + LOG_W("StartService failed: " + std::to_string(GetLastError())); + return false; + } + + LOG_I("Fake BEService is running"); + return true; + } +} \ No newline at end of file