228 lines
10 KiB
C++
228 lines
10 KiB
C++
// AgentProcessControl.cpp — hand-lifted C++ reconstruction of Agent_Release_x64.exe
|
|
// sha256 6f2be7b8eb799ba51b359d1958720aac5baee5b7ee0d1d36684d3cbd72d45469
|
|
//
|
|
// Source of truth: Ghidra pseudocode in ../0x<addr>.pseudo.c and full/functions/.
|
|
// This file is a READABLE RECONSTRUCTION, not recovered source:
|
|
// - control flow and Win32 calls are observed in the decompiled code;
|
|
// - all type/field/function names are inferred (no PDB) and marked so;
|
|
// - std::wstring temporaries, stack cookies and inlined string code are elided.
|
|
// Static analysis only; runtime behaviour has not been observed.
|
|
|
|
#include <windows.h>
|
|
#include <tlhelp32.h>
|
|
#include <process.h>
|
|
#include <cstdio>
|
|
#include <cwchar>
|
|
#include <string>
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ServerEntry: per-managed-server record held in a hash map keyed by server
|
|
// code (global at 0x140162d60, bucket array +0x38, bucket count +0x40,
|
|
// element count +0x58). Offsets observed; names inferred.
|
|
// ---------------------------------------------------------------------------
|
|
struct ServerEntry {
|
|
/* +0x04 */ int32_t code; // hash key; sent in RMI notifications
|
|
/* +0x0c */ uint8_t type; // compared against server-type table; ==1 triggers extra step on exit
|
|
/* +0xa0 */ int32_t unk_a0; // zeroed when the process exits (purpose unknown)
|
|
/* +0xa4 */ DWORD pid; // set from CreateProcessW result
|
|
/* +0xb8..0xd7 */ // 32 bytes copied into the RMI "server state" payload
|
|
/* +0xc4 */ int32_t running; // 1 = launched/launching, 0 = stopped
|
|
/* +0xd0 */ int32_t unk_d0; // >0 together with type==1 triggers FUN_140009f20 on exit
|
|
/* +0xd5 */ uint8_t revive; // relaunch on exit (inferred: XML key "revive_server")
|
|
};
|
|
// NOTE: layout shown as comments; real struct has unrecovered gaps.
|
|
|
|
// Server-type table (global at 0x140162d58): vector of 0x30-byte records
|
|
// +0x00 uint8_t type; +0x08 std::wstring exePath (MSVC SSO, cap at +0x20)
|
|
struct ServerTypeInfo { uint8_t type; std::wstring exePath; };
|
|
|
|
// Agent options singleton (global at 0x140162d68). Observed fields:
|
|
// +0x030 std::wstring agentName (used in "FAILED SERVER ON %s")
|
|
// +0x152 bool useCodeAsArgument (true: arg = decimal server code)
|
|
// +0x171 bool reviveAll (global relaunch-on-exit)
|
|
// +0x178 double autoServerOnDelay (seconds after start; <=0 disables)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 0x1400100b0 AcquireSingleInstanceMutex (observed)
|
|
// ---------------------------------------------------------------------------
|
|
bool AcquireSingleInstanceMutex(HANDLE* outMutex)
|
|
{
|
|
*outMutex = CreateMutexW(nullptr, TRUE, L"Global\\RustyHearts_Agent");
|
|
DWORD err = GetLastError();
|
|
if (err == ERROR_ACCESS_DENIED) wprintf(L"ERROR_ACCESS_DENIED!!! %s \n"); // format arg missing in binary
|
|
else if (err == ERROR_ALREADY_EXISTS) wprintf(L"ERROR_ALREADY_EXISTS!!! %s \n");
|
|
else return true;
|
|
Sleep(3000);
|
|
return false;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 0x140009780 KillProcessByName (observed)
|
|
// Terminates only the FIRST process whose image name matches (case-insensitive).
|
|
// ---------------------------------------------------------------------------
|
|
bool KillProcessByName(const wchar_t* exeName)
|
|
{
|
|
if (!exeName) return false;
|
|
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
PROCESSENTRY32W pe = { sizeof(pe) };
|
|
for (BOOL ok = Process32FirstW(snap, &pe); ok; ok = Process32NextW(snap, &pe)) {
|
|
if (_wcsicmp(exeName, pe.szExeFile) != 0) continue;
|
|
HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pe.th32ProcessID);
|
|
if (h != INVALID_HANDLE_VALUE) { // binary compares to -1, not NULL
|
|
wprintf(L"Process Kill.!!! [%s] \n"); // format arg missing in binary
|
|
TerminateProcess(h, 0);
|
|
CloseHandle(h);
|
|
return true; // note: snapshot handle leaked on this path
|
|
}
|
|
}
|
|
CloseHandle(snap);
|
|
return false;
|
|
}
|
|
|
|
unsigned __stdcall ProcessWatchThread(void* arg); // below
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 0x1400094e0 LaunchProcess (observed)
|
|
// Returns PID, or 0 on failure. Working directory = directory of the exe
|
|
// (inferred: derived from cmdline by FUN_14002fe90).
|
|
// ---------------------------------------------------------------------------
|
|
DWORD LaunchProcess(const wchar_t* commandLine)
|
|
{
|
|
wchar_t cmd[MAX_PATH] = {};
|
|
wchar_t workDir[MAX_PATH] = {};
|
|
wcscpy_s(cmd, commandLine);
|
|
/* workDir <- FUN_14002fe90(commandLine) (inferred: path-of-exe helper) */
|
|
|
|
STARTUPINFOW si = {};
|
|
si.cb = sizeof(si);
|
|
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESIZE | STARTF_USEFILLATTRIBUTE; // 0x13
|
|
si.dwXSize = 700; si.dwYSize = 110;
|
|
si.dwFillAttribute = 0x17; // white on blue
|
|
si.wShowWindow = SW_SHOW;
|
|
PROCESS_INFORMATION pi = {};
|
|
|
|
DWORD pid = 0;
|
|
if (!CreateProcessW(nullptr, cmd, nullptr, nullptr, FALSE, CREATE_NEW_CONSOLE,
|
|
nullptr, workDir, &si, &pi)) {
|
|
Log(L"=======Server Fail=======\n");
|
|
Log(L"Path\t: [%s] \n", workDir);
|
|
Log(L"File\t: [%s] \n", cmd);
|
|
} else {
|
|
CloseHandle(pi.hThread);
|
|
// passes a pointer to the STACK PROCESS_INFORMATION; the thread reads it
|
|
// immediately, so this relies on the 1 s Sleep below (latent race).
|
|
unsigned tid;
|
|
CloseHandle((HANDLE)_beginthreadex(nullptr, 0, ProcessWatchThread, &pi, 0, &tid));
|
|
Log(L"=======ServerOn=======\n");
|
|
pid = pi.dwProcessId;
|
|
}
|
|
printf("//////////////////////////////////////////////////////////////////////////\n");
|
|
Sleep(1000);
|
|
return pid;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 0x140008ea0 ServerOn(request) (observed flow; names inferred)
|
|
// request: +0x04 server code, +0x0c server type.
|
|
// ---------------------------------------------------------------------------
|
|
void ServerOn(void* self, const ServerRequest* req)
|
|
{
|
|
ServerEntry* e = g_servers.Find(req->code); // hash lookup
|
|
if (e->running == 0) {
|
|
e->running = 1;
|
|
AgentManagerSession* mgr = g_agentServer->managerSession; // 0x1400626f8 +0xa8
|
|
|
|
std::wstring cmd = g_serverTypes.ExePath(req->type) + L" ";
|
|
if (!g_options->useCodeAsArgument)
|
|
cmd += Widen(BuildLaunchArgs(g_options, req)); // FUN_140027540 — contents UNKNOWN
|
|
else
|
|
cmd += std::to_wstring(req->code);
|
|
|
|
DWORD pid = 0;
|
|
if (cmd.size() < MAX_PATH && (pid = LaunchProcess(cmd.c_str())) > 0) {
|
|
e->pid = pid;
|
|
e->running = 1;
|
|
// RMI to AgentManager, vtable slot +0x40 on proxy at session+0x68:
|
|
mgr->proxy.NotifyServerState(/*host*/1, /*rmiCtx*/g_rmiCtx, e->code, e->state, e->Serialize());
|
|
} else {
|
|
e->running = 0;
|
|
}
|
|
if (mgr && pid == 0)
|
|
mgr->SendLog(1, Format(L"FAILED SERVER ON %s ", g_options->agentName.c_str()));
|
|
}
|
|
ReleaseRequest(req); // FUN_140004e10
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 0x140008c90 ProcessWatchThread (observed)
|
|
// ---------------------------------------------------------------------------
|
|
unsigned __stdcall ProcessWatchThread(void* arg)
|
|
{
|
|
auto* pi = static_cast<PROCESS_INFORMATION*>(arg);
|
|
HANDLE hProcess = pi->hProcess;
|
|
DWORD pid = pi->dwProcessId;
|
|
DWORD code;
|
|
do { Sleep(10); GetExitCodeProcess(hProcess, &code); } while (code == STILL_ACTIVE);
|
|
// note: hProcess is never closed (handle leak per launch)
|
|
|
|
ServerEntry* e = g_servers.FindByPid(pid); // FUN_14002c9a0
|
|
if (!e) { wprintf(L"NOT FIND PROCESS ID \n"); return 0; }
|
|
|
|
AgentManagerSession* mgr = g_agentServer->managerSession;
|
|
if (mgr) {
|
|
e->unk_a0 = 0;
|
|
e->running = 0;
|
|
mgr->proxy.NotifyServerState(1, g_rmiCtx, e->code, e->state, e->Serialize()); // slot +0x40
|
|
mgr->proxy.NotifyServerStopped(1, g_rmiCtx, /*flag*/1, e->code, /*?*/1); // slot +0x50
|
|
if (e->revive || g_options->reviveAll) {
|
|
ServerOn(g_agentProcess, MakeRequest(e));
|
|
if (e->type == 1 && e->unk_d0 > 0) FUN_140009f20(g_agentProcess, e->code);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 0x1400037a0 MagiAgentServer::Run (observed; simplified)
|
|
// ---------------------------------------------------------------------------
|
|
int MagiAgentServer::Run()
|
|
{
|
|
LogBanner(L"# Agent Startup #\n\n");
|
|
CoInitialize(nullptr);
|
|
Log(L"+ Prepare Startup\n");
|
|
|
|
network_ = new MagiAgentNetwork(); // 0xb8 bytes, FUN_140005020
|
|
managerSession_ = new MGAgentManagerSession(network_); // 0x68 bytes
|
|
/* if options map has entry type 0x15 (AgentManager address):
|
|
connect and pump until connected (FUN_1400023e0 loop) */
|
|
network_->Start(); // FUN_140005360
|
|
LogBanner(L"\n # Agent Running... #\n");
|
|
|
|
timer_ = new Magi::Timer(new TimeSourceHRWin32()); // QueryPerformanceCounter
|
|
g_options->Load(); // FUN_1400271e0
|
|
KillProcessByName(g_serverTypes.ExePath(0x18)); // kill stale process of type 0x18 (UNKNOWN which server)
|
|
StartConsoleThread(); // FUN_140001dc0 (inferred: keyboard menu)
|
|
|
|
bool autoOnPending = true;
|
|
double lastSec = 0, lastTick = 0;
|
|
while (running_) { // +0x60
|
|
clock_.Update();
|
|
double now = timer_->Now();
|
|
if (g_options->autoServerOnDelay > 0 && now > g_options->autoServerOnDelay && autoOnPending) {
|
|
autoOnPending = false;
|
|
printf("Start Auto Server On. \n");
|
|
AllServerOn(); // FUN_140009bd0
|
|
}
|
|
managerSession_->Update(now); // FUN_140007da0
|
|
if (now - lastSec >= 1.0) { /* optional "[ %s ] FPS[ %d ]" print */ lastSec = now; }
|
|
if (now - lastTick >= 0.1) { Tick(); network_->Update(now); lastTick = now; } // 10 Hz
|
|
lastTime_ = now;
|
|
Sleep(1);
|
|
}
|
|
Log(L"+ Prepare Stop\n");
|
|
managerSession_->Stop();
|
|
delete network_->netServer_;
|
|
CoUninitialize();
|
|
LogBanner(L"# Agent Stoped... #\n");
|
|
return 0;
|
|
}
|