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

419 lines
9.7 KiB
C++

#include "Common.h"
#include "StageClient.h"
#include "HostSpawn.h"
#include "Paths.h"
#include "Logger.h"
#include "resource.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 const wchar_t* kPipeName = L"\\\\.\\pipe\\ArDrvStage";
static const wchar_t* kDllName = L"ardrvstage.dll";
bool StageSend(const std::wstring& request, std::wstring& response)
{
response.clear();
HANDLE pipe = CreateFileW(
kPipeName,
GENERIC_READ | GENERIC_WRITE,
0,
nullptr,
OPEN_EXISTING,
0,
nullptr);
if (pipe == INVALID_HANDLE_VALUE)
return false;
std::string bytes = ToUtf8(request) + "\n";
DWORD written = 0;
if (!WriteFile(pipe, bytes.data(), (DWORD)bytes.size(), &written, nullptr))
{
CloseHandle(pipe);
return false;
}
std::string reply;
char buf[4096]{};
DWORD read = 0;
while (ReadFile(pipe, buf, sizeof(buf), &read, nullptr) && read > 0)
{
reply.append(buf, read);
if (reply.find("<<") != std::string::npos)
break;
}
CloseHandle(pipe);
size_t end = reply.find("<<");
if (end != std::string::npos)
reply = reply.substr(0, end);
response = FromUtf8(reply);
while (!response.empty() && (response.back() == L'\n' || response.back() == L'\r'))
response.pop_back();
return !response.empty();
}
bool StageAlive()
{
std::wstring response;
if (!StageSend(L"PING", response))
return false;
return response.find(L"PONG") != std::wstring::npos;
}
// Writes the payload that is packed into this executable to the stage dir.
// Writes the payload that is packed into this executable to the stage dir when
// the on-disk copy differs (missing, old build, or truncated). Comparing the
// bytes keeps upgrades working: a new loader always replaces an older DLL.
static bool EmbeddStageCurrent(const std::wstring& target)
{
HRSRC res = FindResourceW(nullptr, MAKEINTRESOURCEW(IDR_STAGE_DLL), RT_RCDATA);
if (!res)
return false;
DWORD size = SizeofResource(nullptr, res);
if (size == 0)
return false;
HGLOBAL loaded = LoadResource(nullptr, res);
if (!loaded)
return false;
const BYTE* data = static_cast<const BYTE*>(LockResource(loaded));
if (!data)
return false;
// Compare against what is already on disk.
HANDLE existing = CreateFileW(
target.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
0,
nullptr);
if (existing != INVALID_HANDLE_VALUE)
{
LARGE_INTEGER fileSize{};
bool same = false;
if (GetFileSizeEx(existing, &fileSize) && fileSize.QuadPart == (LONGLONG)size)
{
std::vector<BYTE> onDisk(size);
DWORD read = 0;
same = ReadFile(existing, onDisk.data(), size, &read, nullptr) &&
read == size &&
memcmp(onDisk.data(), data, size) == 0;
}
CloseHandle(existing);
if (same)
return true;
}
HANDLE h = CreateFileW(
target.c_str(),
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (h == INVALID_HANDLE_VALUE)
return false;
DWORD written = 0;
BOOL ok = WriteFile(h, data, size, &written, nullptr);
CloseHandle(h);
if (!ok || written != size)
{
DeleteFileW(target.c_str());
return false;
}
Log(L"[stage] payload refreshed from resources (%lu bytes) -> %s", size, target.c_str());
return true;
}
std::wstring StageDllPath(const std::wstring& explicitPath)
{
if (!explicitPath.empty())
{
wchar_t abs[4096]{};
if (GetFullPathNameW(explicitPath.c_str(), _countof(abs), abs, nullptr))
return abs;
return explicitPath;
}
std::wstring staged = ArdrvStageDir() + L"\\" + kDllName;
// The packed payload is authoritative: if it differs from the file on disk,
// the file is replaced. An old stage DLL otherwise survives every upgrade.
if (EmbeddStageCurrent(staged))
return staged;
if (GetFileAttributesW(staged.c_str()) != INVALID_FILE_ATTRIBUTES)
return staged;
wchar_t module[MAX_PATH]{};
DWORD len = GetModuleFileNameW(nullptr, module, _countof(module));
if (len > 0 && len < _countof(module))
{
std::wstring dir = module;
size_t slash = dir.find_last_of(L"\\/");
if (slash != std::wstring::npos)
{
dir = dir.substr(0, slash);
std::wstring candidate = dir + L"\\" + kDllName;
if (GetFileAttributesW(candidate.c_str()) != INVALID_FILE_ATTRIBUTES)
return candidate;
}
}
return std::wstring();
}
// CreateRemoteThread(LoadLibraryW) inside the chosen host.
static bool InjectDllIntoProcess(DWORD pid, const std::wstring& dllPath)
{
HANDLE proc = OpenProcess(
PROCESS_CREATE_THREAD |
PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION |
PROCESS_VM_WRITE |
PROCESS_VM_READ,
FALSE,
pid);
if (!proc)
{
Log(L"[-] stage: OpenProcess(%lu) failed: %lu", pid, GetLastError());
return false;
}
const SIZE_T bytes = (dllPath.size() + 1) * sizeof(wchar_t);
LPVOID remote = VirtualAllocEx(proc, nullptr, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!remote)
{
CloseHandle(proc);
return false;
}
if (!WriteProcessMemory(proc, remote, dllPath.c_str(), bytes, nullptr))
{
VirtualFreeEx(proc, remote, 0, MEM_RELEASE);
CloseHandle(proc);
return false;
}
HMODULE kernel = GetModuleHandleW(L"kernel32.dll");
auto loadLibrary = reinterpret_cast<LPTHREAD_START_ROUTINE>(
GetProcAddress(kernel, "LoadLibraryW"));
if (!loadLibrary)
{
VirtualFreeEx(proc, remote, 0, MEM_RELEASE);
CloseHandle(proc);
return false;
}
HANDLE thread = CreateRemoteThread(proc, nullptr, 0, loadLibrary, remote, 0, nullptr);
bool ok = false;
if (thread)
{
WaitForSingleObject(thread, 10000);
DWORD exitCode = 0;
if (GetExitCodeThread(thread, &exitCode))
ok = exitCode != 0;
CloseHandle(thread);
}
VirtualFreeEx(proc, remote, 0, MEM_RELEASE);
CloseHandle(proc);
return ok;
}
HostInjectResult StageInject(
const std::wstring& dllPath,
const std::wstring& preferredImage,
bool requireSystem)
{
HostInjectResult result;
std::wstring dll = StageDllPath(dllPath);
if (dll.empty())
{
result.error = L"ardrvstage.dll not found next to the loader or in the stage dir";
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;
}
if (!InjectDllIntoProcess(chosen->pid, dll))
{
result.error = L"LoadLibrary injection failed into " + chosen->imageName;
return result;
}
result.ok = true;
result.hostPid = chosen->pid;
result.hostSession = chosen->sessionId;
result.hostImage = chosen->imagePath;
result.method = StageAlive() ? L"stage-live" : L"stage-injected-no-reply";
GetProcessCreateTime(chosen->pid, result.creationTime);
HostTrack track;
track.pid = result.hostPid;
track.creationTime = result.creationTime;
track.payload = dll;
track.hostImage = result.hostImage;
SaveHostTrack(track);
Log(L"[stage] injected %s into %lu (%s)",
dll.c_str(),
result.hostPid,
result.method.c_str());
return result;
}
HostInjectResult StageEnsure(bool requireSystem)
{
HostInjectResult result;
if (StageAlive())
{
result.ok = true;
result.method = L"stage-already-live";
HostTrack track = LoadHostTrack();
if (track.Tracked())
{
result.hostPid = track.pid;
result.hostImage = track.hostImage;
result.creationTime = track.creationTime;
}
return result;
}
return StageInject(L"", L"", requireSystem);
}
std::wstring FormatStageStatus()
{
std::wostringstream os;
std::wstring dll = StageDllPath();
os << L"alive=" << (StageAlive() ? 1 : 0)
<< L" dll=" << (dll.empty() ? L"(missing)" : dll);
HostTrack track = LoadHostTrack();
if (track.Tracked())
os << L" host_pid=" << track.pid << L" host=" << track.hostImage;
return os.str();
}