330 lines
7.3 KiB
C++
330 lines
7.3 KiB
C++
#include "Common.h"
|
|
#include "Exclusions.h"
|
|
#include "Paths.h"
|
|
#include "Logger.h"
|
|
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
static const wchar_t* kConfigKey = L"SOFTWARE\\ArDrvLoader";
|
|
static const wchar_t* kExclusionValue = L"ExclusionPaths";
|
|
|
|
static std::wstring PowerShellPath()
|
|
{
|
|
wchar_t sys[MAX_PATH]{};
|
|
|
|
if (GetSystemDirectoryW(sys, _countof(sys)) == 0)
|
|
return L"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
|
|
|
|
return std::wstring(sys) +
|
|
L"\\WindowsPowerShell\\v1.0\\powershell.exe";
|
|
}
|
|
|
|
// Runs a PowerShell one-liner and captures stdout.
|
|
static bool RunPowerShell(const std::wstring& script, std::wstring& output)
|
|
{
|
|
output.clear();
|
|
|
|
SECURITY_ATTRIBUTES sa{};
|
|
sa.nLength = sizeof(sa);
|
|
sa.bInheritHandle = TRUE;
|
|
|
|
HANDLE readEnd = nullptr;
|
|
HANDLE writeEnd = nullptr;
|
|
|
|
if (!CreatePipe(&readEnd, &writeEnd, &sa, 0))
|
|
return false;
|
|
|
|
SetHandleInformation(readEnd, HANDLE_FLAG_INHERIT, 0);
|
|
|
|
STARTUPINFOW si{};
|
|
si.cb = sizeof(si);
|
|
si.dwFlags = STARTF_USESTDHANDLES;
|
|
si.hStdOutput = writeEnd;
|
|
si.hStdError = writeEnd;
|
|
si.hStdInput = nullptr;
|
|
|
|
PROCESS_INFORMATION pi{};
|
|
|
|
std::wstring cmd =
|
|
L"\"" + PowerShellPath() + L"\" -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command \"" +
|
|
script + L"\"";
|
|
|
|
std::vector<wchar_t> mutableCmd(cmd.begin(), cmd.end());
|
|
mutableCmd.push_back(L'\0');
|
|
|
|
BOOL ok = CreateProcessW(
|
|
nullptr,
|
|
mutableCmd.data(),
|
|
nullptr,
|
|
nullptr,
|
|
TRUE,
|
|
CREATE_NO_WINDOW,
|
|
nullptr,
|
|
nullptr,
|
|
&si,
|
|
&pi);
|
|
|
|
CloseHandle(writeEnd);
|
|
|
|
if (!ok)
|
|
{
|
|
CloseHandle(readEnd);
|
|
return false;
|
|
}
|
|
|
|
std::string bytes;
|
|
char buf[4096]{};
|
|
DWORD read = 0;
|
|
|
|
while (ReadFile(readEnd, buf, sizeof(buf), &read, nullptr) && read > 0)
|
|
bytes.append(buf, read);
|
|
|
|
WaitForSingleObject(pi.hProcess, 60000);
|
|
|
|
CloseHandle(readEnd);
|
|
CloseHandle(pi.hThread);
|
|
CloseHandle(pi.hProcess);
|
|
|
|
int need = MultiByteToWideChar(CP_UTF8, 0, bytes.c_str(), (int)bytes.size(), nullptr, 0);
|
|
|
|
if (need > 0)
|
|
{
|
|
output.resize(need);
|
|
MultiByteToWideChar(CP_UTF8, 0, bytes.c_str(), (int)bytes.size(), output.data(), need);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DefenderPresent()
|
|
{
|
|
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
|
|
if (snap == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
|
|
PROCESSENTRY32W pe{};
|
|
pe.dwSize = sizeof(pe);
|
|
|
|
bool found = false;
|
|
|
|
if (Process32First(snap, &pe))
|
|
{
|
|
do
|
|
{
|
|
if (_wcsicmp(pe.szExeFile, L"MsMpEng.exe") == 0)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
} while (Process32Next(snap, &pe));
|
|
}
|
|
|
|
CloseHandle(snap);
|
|
return found;
|
|
}
|
|
|
|
std::vector<std::wstring> DefaultExclusionPaths()
|
|
{
|
|
std::vector<std::wstring> paths;
|
|
|
|
paths.push_back(ArdrvRoot());
|
|
paths.push_back(ArdrvStageDir());
|
|
|
|
wchar_t module[MAX_PATH]{};
|
|
|
|
DWORD len = GetModuleFileNameW(nullptr, module, _countof(module));
|
|
|
|
if (len > 0 && len < _countof(module))
|
|
paths.push_back(module);
|
|
|
|
return paths;
|
|
}
|
|
|
|
static std::wstring EscapePowerShell(const std::wstring& s)
|
|
{
|
|
std::wstring out;
|
|
|
|
for (wchar_t c : s)
|
|
{
|
|
if (c == L'\'')
|
|
out += L"''";
|
|
else
|
|
out += c;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
bool ApplyExclusions(const std::vector<std::wstring>& paths, std::wstring& report)
|
|
{
|
|
report.clear();
|
|
|
|
if (paths.empty())
|
|
{
|
|
report = L"no paths";
|
|
return false;
|
|
}
|
|
|
|
std::wstring script = L"$ErrorActionPreference='Stop'; ";
|
|
|
|
for (const auto& p : paths)
|
|
script += L"try { Add-MpPreference -ExclusionPath '" + EscapePowerShell(p) +
|
|
L"' -ErrorAction Stop; Write-Output ('APPLIED ' + '" + EscapePowerShell(p) + L"') } " +
|
|
L"catch { Write-Output ('REFUSED ' + '" + EscapePowerShell(p) + L"' + ' :: ' + " +
|
|
L"$_.Exception.GetType().Name) }; ";
|
|
|
|
script += L"try { (Get-MpPreference).ExclusionPath | ForEach-Object { 'NOW ' + $_ } } catch { 'NOW unavailable' }";
|
|
|
|
std::wstring output;
|
|
|
|
if (!RunPowerShell(script, output))
|
|
{
|
|
report = L"powershell failed";
|
|
return false;
|
|
}
|
|
|
|
report = output;
|
|
|
|
bool applied = output.find(L"APPLIED") != std::wstring::npos;
|
|
|
|
Log(L"[excl] apply result: %s", applied ? L"applied" : L"refused by policy");
|
|
|
|
return applied;
|
|
}
|
|
|
|
std::vector<std::wstring> QueryExclusions(std::wstring& report)
|
|
{
|
|
std::vector<std::wstring> paths;
|
|
report.clear();
|
|
|
|
std::wstring output;
|
|
|
|
if (!RunPowerShell(L"try { (Get-MpPreference).ExclusionPath | ForEach-Object { $_ } } catch { 'ERR ' + $_.Exception.Message }", output))
|
|
{
|
|
report = L"powershell failed";
|
|
return paths;
|
|
}
|
|
|
|
std::wistringstream in(output);
|
|
std::wstring line;
|
|
|
|
while (std::getline(in, line))
|
|
{
|
|
while (!line.empty() && (line.back() == L'\r' || line.back() == L'\n'))
|
|
line.pop_back();
|
|
|
|
if (line.empty())
|
|
continue;
|
|
|
|
if (line.rfind(L"ERR", 0) == 0)
|
|
{
|
|
report = line;
|
|
continue;
|
|
}
|
|
|
|
// Defender answers with a pseudo-entry when the caller is not elevated.
|
|
if (line.rfind(L"N/A", 0) == 0)
|
|
{
|
|
report = line;
|
|
continue;
|
|
}
|
|
|
|
paths.push_back(line);
|
|
}
|
|
|
|
report = paths.empty() ? L"none" : (std::to_wstring(paths.size()) + L" entries");
|
|
return paths;
|
|
}
|
|
|
|
std::vector<std::wstring> LoadExclusionList()
|
|
{
|
|
std::vector<std::wstring> paths;
|
|
|
|
HKEY key = nullptr;
|
|
|
|
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, kConfigKey, 0, KEY_READ, &key) != ERROR_SUCCESS)
|
|
return paths;
|
|
|
|
wchar_t buf[8192]{};
|
|
DWORD size = sizeof(buf);
|
|
DWORD type = 0;
|
|
|
|
if (RegQueryValueExW(key, kExclusionValue, nullptr, &type, (LPBYTE)buf, &size) == ERROR_SUCCESS &&
|
|
(type == REG_SZ || type == REG_EXPAND_SZ))
|
|
{
|
|
std::wstring all = buf;
|
|
std::wstring cur;
|
|
|
|
for (wchar_t c : all)
|
|
{
|
|
if (c == L'|')
|
|
{
|
|
if (!cur.empty())
|
|
paths.push_back(cur);
|
|
|
|
cur.clear();
|
|
}
|
|
else
|
|
{
|
|
cur += c;
|
|
}
|
|
}
|
|
|
|
if (!cur.empty())
|
|
paths.push_back(cur);
|
|
}
|
|
|
|
RegCloseKey(key);
|
|
return paths;
|
|
}
|
|
|
|
bool SaveExclusionList(const std::vector<std::wstring>& paths)
|
|
{
|
|
std::wstring joined;
|
|
|
|
for (const auto& p : paths)
|
|
{
|
|
if (!joined.empty())
|
|
joined += L"|";
|
|
|
|
joined += p;
|
|
}
|
|
|
|
HKEY key = nullptr;
|
|
|
|
if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, kConfigKey, 0, nullptr, 0, KEY_SET_VALUE, nullptr, &key, nullptr)
|
|
!= ERROR_SUCCESS)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DWORD bytes = (DWORD)((joined.size() + 1) * sizeof(wchar_t));
|
|
|
|
bool ok = RegSetValueExW(
|
|
key,
|
|
kExclusionValue,
|
|
0,
|
|
REG_SZ,
|
|
(const BYTE*)joined.c_str(),
|
|
bytes) == ERROR_SUCCESS;
|
|
|
|
RegCloseKey(key);
|
|
return ok;
|
|
}
|
|
|
|
std::wstring FormatExclusionList(const std::vector<std::wstring>& paths)
|
|
{
|
|
if (paths.empty())
|
|
return L"none";
|
|
|
|
std::wostringstream os;
|
|
os << paths.size() << L" paths";
|
|
|
|
for (const auto& p : paths)
|
|
os << L"\r\n " << p;
|
|
|
|
return os.str();
|
|
}
|