Init commit
This commit is contained in:
+418
@@ -0,0 +1,418 @@
|
||||
#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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user