Files
archived-Reclass/plugins/RemoteProcessMemory/RemoteProcessMemoryPlugin.cpp
IChooseYou ed8a44917b feat: 32-bit process support, scanner rescan filtering, suppress flash on navigate
- Add pointerSize() to Provider base; WoW64/ELF detection in ProcessMemory,
  WinDbg, and RemoteProcessMemory plugins
- Wire pointer size through NodeTree, source/XML imports, C++ generator,
  controller, compose, address parser, and RPC protocol header
- Add is32Bit to PluginProcessInfo and ProcessInfo; show (32-bit) in picker
- Scanner rescan now filters results against the current input value
- Go-to-address from scanner resets change tracking to prevent false flashing
2026-03-01 07:42:40 -07:00

940 lines
33 KiB
C++

#include "RemoteProcessMemoryPlugin.h"
#include "rcx_rpc_protocol.h"
#include "../../src/processpicker.h"
#include <QStyle>
#include <QApplication>
#include <QMessageBox>
#include <QPushButton>
#include <QDir>
#include <QFileInfo>
#include <QPixmap>
#include <QImage>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && defined(_WIN32)
#include <QtWin>
#endif
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <tlhelp32.h>
# include <psapi.h>
# include <shellapi.h>
#else
# include <unistd.h>
# include <fcntl.h>
# include <dlfcn.h>
# include <sys/mman.h>
# include <sys/wait.h>
# include <sys/ptrace.h>
# include <sys/user.h>
# include <semaphore.h>
# include <signal.h>
# include <link.h>
# include <climits>
# include <cstring>
# include <fstream>
# include <sstream>
#endif
/* ══════════════════════════════════════════════════════════════════════
* IPC Client
* ══════════════════════════════════════════════════════════════════════ */
struct IpcClient {
#ifdef _WIN32
HANDLE hShm = nullptr;
HANDLE hReqEvent = nullptr;
HANDLE hRspEvent = nullptr;
#else
int shmFd = -1;
sem_t* reqSem = SEM_FAILED;
sem_t* rspSem = SEM_FAILED;
char shmNameBuf[128] = {};
char reqNameBuf[128] = {};
char rspNameBuf[128] = {};
#endif
void* mappedView = nullptr;
QMutex mutex;
bool connected = false;
RcxRpcHeader* header() const {
return mappedView ? reinterpret_cast<RcxRpcHeader*>(mappedView) : nullptr;
}
~IpcClient() { disconnect(); }
/* ── connect / disconnect ──────────────────────────────────────── */
bool connect(uint32_t pid, int timeoutMs = 5000)
{
char shmName[128], reqName[128], rspName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
rcx_rpc_req_name(reqName, sizeof(reqName), pid);
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid);
#ifdef _WIN32
/* poll for shared memory to appear (payload creating it) */
auto deadline = GetTickCount64() + (uint64_t)timeoutMs;
while (!(hShm = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, shmName))) {
if (GetTickCount64() >= deadline) return false;
Sleep(10);
}
mappedView = MapViewOfFile(hShm, FILE_MAP_ALL_ACCESS, 0, 0, RCX_RPC_SHM_SIZE);
if (!mappedView) { CloseHandle(hShm); hShm = nullptr; return false; }
hReqEvent = OpenEventA(EVENT_ALL_ACCESS, FALSE, reqName);
hRspEvent = OpenEventA(EVENT_ALL_ACCESS, FALSE, rspName);
if (!hReqEvent || !hRspEvent) { disconnect(); return false; }
#else
strncpy(shmNameBuf, shmName, sizeof(shmNameBuf) - 1);
strncpy(reqNameBuf, reqName, sizeof(reqNameBuf) - 1);
strncpy(rspNameBuf, rspName, sizeof(rspNameBuf) - 1);
/* poll for shared memory */
auto start = std::chrono::steady_clock::now();
while (true) {
shmFd = shm_open(shmName, O_RDWR, 0);
if (shmFd >= 0) break;
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
if (elapsed >= timeoutMs) return false;
usleep(10000);
}
mappedView = mmap(nullptr, RCX_RPC_SHM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, shmFd, 0);
if (mappedView == MAP_FAILED) { mappedView = nullptr; close(shmFd); shmFd = -1; return false; }
reqSem = sem_open(reqName, 0);
rspSem = sem_open(rspName, 0);
if (reqSem == SEM_FAILED || rspSem == SEM_FAILED) { disconnect(); return false; }
#endif
/* wait for payloadReady */
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
#ifdef _WIN32
while (!hdr->payloadReady) {
if (GetTickCount64() >= deadline) { disconnect(); return false; }
Sleep(5);
}
#else
while (!__atomic_load_n(&hdr->payloadReady, __ATOMIC_ACQUIRE)) {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
if (elapsed >= timeoutMs) { disconnect(); return false; }
usleep(5000);
}
#endif
connected = true;
return true;
}
void disconnect()
{
#ifdef _WIN32
if (mappedView) { UnmapViewOfFile(mappedView); mappedView = nullptr; }
if (hShm) { CloseHandle(hShm); hShm = nullptr; }
if (hReqEvent) { CloseHandle(hReqEvent); hReqEvent = nullptr; }
if (hRspEvent) { CloseHandle(hRspEvent); hRspEvent = nullptr; }
#else
if (mappedView) { munmap(mappedView, RCX_RPC_SHM_SIZE); mappedView = nullptr; }
if (shmFd >= 0) { close(shmFd); shmFd = -1; }
if (reqSem != SEM_FAILED) { sem_close(reqSem); reqSem = SEM_FAILED; }
if (rspSem != SEM_FAILED) { sem_close(rspSem); rspSem = SEM_FAILED; }
#endif
connected = false;
}
/* ── low-level RPC round-trip ──────────────────────────────────── */
bool signalAndWait(int timeoutMs = 2000)
{
#ifdef _WIN32
SetEvent(hReqEvent);
return WaitForSingleObject(hRspEvent, (DWORD)timeoutMs) == WAIT_OBJECT_0;
#else
sem_post(reqSem);
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeoutMs / 1000;
ts.tv_nsec += (timeoutMs % 1000) * 1000000L;
if (ts.tv_nsec >= 1000000000L) { ts.tv_sec++; ts.tv_nsec -= 1000000000L; }
return sem_timedwait(rspSem, &ts) == 0;
#endif
}
/* ── public API ────────────────────────────────────────────────── */
bool readSingle(uint64_t addr, void* buf, int len)
{
QMutexLocker lock(&mutex);
if (!connected || len <= 0) return false;
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
auto* data = static_cast<uint8_t*>(mappedView) + RCX_RPC_DATA_OFFSET;
hdr->command = RPC_CMD_READ_BATCH;
hdr->requestCount = 1;
hdr->status = RCX_RPC_STATUS_OK;
auto* entry = reinterpret_cast<RcxRpcReadEntry*>(data);
entry->address = addr;
entry->length = (uint32_t)len;
entry->dataOffset = sizeof(RcxRpcReadEntry);
if (!signalAndWait()) { connected = false; return false; }
memcpy(buf, data + entry->dataOffset, len);
return true;
}
bool writeSingle(uint64_t addr, const void* buf, int len)
{
QMutexLocker lock(&mutex);
if (!connected || len <= 0) return false;
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
auto* data = static_cast<uint8_t*>(mappedView) + RCX_RPC_DATA_OFFSET;
hdr->command = RPC_CMD_WRITE;
hdr->writeAddress = addr;
hdr->writeLength = (uint32_t)len;
hdr->status = RCX_RPC_STATUS_OK;
memcpy(data, buf, len);
if (!signalAndWait()) { connected = false; return false; }
return hdr->status == RCX_RPC_STATUS_OK;
}
QVector<RemoteProcessProvider::ModuleInfo> enumerateModules()
{
QVector<RemoteProcessProvider::ModuleInfo> result;
QMutexLocker lock(&mutex);
if (!connected) return result;
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
auto* data = static_cast<uint8_t*>(mappedView) + RCX_RPC_DATA_OFFSET;
hdr->command = RPC_CMD_ENUM_MODULES;
hdr->status = RCX_RPC_STATUS_OK;
if (!signalAndWait()) { connected = false; return result; }
if (hdr->status != RCX_RPC_STATUS_OK) return result;
uint32_t count = hdr->responseCount;
result.reserve((int)count);
for (uint32_t i = 0; i < count; ++i) {
auto* entry = reinterpret_cast<const RcxRpcModuleEntry*>(
data + i * sizeof(RcxRpcModuleEntry));
QString modName;
#ifdef _WIN32
modName = QString::fromWCharArray(
reinterpret_cast<const wchar_t*>(data + entry->nameOffset),
(int)(entry->nameLength / sizeof(wchar_t)));
#else
modName = QString::fromUtf8(
reinterpret_cast<const char*>(data + entry->nameOffset),
(int)entry->nameLength);
#endif
result.append({modName, entry->base, entry->size});
}
return result;
}
bool ping()
{
QMutexLocker lock(&mutex);
if (!connected) return false;
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
hdr->command = RPC_CMD_PING;
hdr->status = RCX_RPC_STATUS_OK;
if (!signalAndWait()) { connected = false; return false; }
return true;
}
void shutdown()
{
QMutexLocker lock(&mutex);
if (!connected) return;
auto* hdr = static_cast<RcxRpcHeader*>(mappedView);
hdr->command = RPC_CMD_SHUTDOWN;
hdr->status = RCX_RPC_STATUS_OK;
signalAndWait(500);
connected = false;
}
};
/* ══════════════════════════════════════════════════════════════════════
* RemoteProcessProvider
* ══════════════════════════════════════════════════════════════════════ */
RemoteProcessProvider::RemoteProcessProvider(
uint32_t pid, const QString& processName,
std::shared_ptr<IpcClient> ipc)
: m_pid(pid)
, m_processName(processName)
, m_connected(ipc && ipc->connected)
, m_base(0)
, m_ipc(std::move(ipc))
{
if (m_connected) {
cacheModules();
// Read pointer size from payload's SHM header (0 means not set → default 8)
auto* hdr = m_ipc ? m_ipc->header() : nullptr;
if (hdr) {
uint32_t ps = hdr->pointerSize;
if (ps == 4 || ps == 8)
m_pointerSize = (int)ps;
}
}
}
RemoteProcessProvider::~RemoteProcessProvider() = default;
bool RemoteProcessProvider::read(uint64_t addr, void* buf, int len) const
{
if (!m_connected || len <= 0) return false;
bool ok = m_ipc->readSingle(addr, buf, len);
if (!ok) {
memset(buf, 0, (size_t)len);
/* update connectivity flag through mutable ipc */
const_cast<RemoteProcessProvider*>(this)->m_connected = m_ipc->connected;
}
return ok;
}
int RemoteProcessProvider::size() const
{
return m_connected ? 0x10000 : 0;
}
bool RemoteProcessProvider::write(uint64_t addr, const void* buf, int len)
{
if (!m_connected || len <= 0) return false;
bool ok = m_ipc->writeSingle(addr, buf, len);
if (!ok) m_connected = m_ipc->connected;
return ok;
}
QString RemoteProcessProvider::getSymbol(uint64_t addr) const
{
for (const auto& mod : m_modules) {
if (addr >= mod.base && addr < mod.base + mod.size) {
uint64_t off = addr - mod.base;
return QStringLiteral("%1+0x%2")
.arg(mod.name)
.arg(off, 0, 16, QChar('0'));
}
}
return {};
}
uint64_t RemoteProcessProvider::symbolToAddress(const QString& n) const
{
for (const auto& mod : m_modules) {
if (mod.name.compare(n, Qt::CaseInsensitive) == 0)
return mod.base;
}
return 0;
}
void RemoteProcessProvider::cacheModules()
{
m_modules = m_ipc->enumerateModules();
if (!m_modules.isEmpty())
m_base = m_modules.first().base;
}
/* ══════════════════════════════════════════════════════════════════════
* Injection helpers
* ══════════════════════════════════════════════════════════════════════ */
namespace {
/* Resolve payload DLL/SO path next to this plugin DLL/SO */
static QString payloadPath()
{
#ifdef _WIN32
HMODULE hSelf = nullptr;
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(&payloadPath), &hSelf);
WCHAR buf[MAX_PATH];
GetModuleFileNameW(hSelf, buf, MAX_PATH);
QFileInfo fi(QString::fromWCharArray(buf));
return fi.absolutePath() + QStringLiteral("/rcx_payload.dll");
#else
Dl_info info;
dladdr(reinterpret_cast<void*>(&payloadPath), &info);
QFileInfo fi(QString::fromUtf8(info.dli_fname));
return fi.absolutePath() + QStringLiteral("/rcx_payload.so");
#endif
}
#ifdef _WIN32
/* ── Windows injection: CreateRemoteThread + LoadLibraryA ─────────── */
static bool injectPayload(uint32_t pid, QString* errorMsg)
{
QString path = payloadPath();
QByteArray pathUtf8 = QDir::toNativeSeparators(path).toLocal8Bit();
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProc) {
if (errorMsg)
*errorMsg = QStringLiteral("OpenProcess failed (error %1).\n"
"Try running as Administrator.")
.arg(GetLastError());
return false;
}
/* allocate + write path string in target */
SIZE_T pathLen = (SIZE_T)(pathUtf8.size() + 1);
void* remotePath = VirtualAllocEx(hProc, nullptr, pathLen,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!remotePath) {
if (errorMsg) *errorMsg = QStringLiteral("VirtualAllocEx failed.");
CloseHandle(hProc);
return false;
}
WriteProcessMemory(hProc, remotePath, pathUtf8.constData(), pathLen, nullptr);
/* Step 1: LoadLibraryA — loads the DLL (DllMain is minimal) */
HMODULE hK32 = GetModuleHandleA("kernel32.dll");
auto pLoadLib = reinterpret_cast<LPTHREAD_START_ROUTINE>(
GetProcAddress(hK32, "LoadLibraryA"));
HANDLE hThread = CreateRemoteThread(hProc, nullptr, 0,
pLoadLib, remotePath, 0, nullptr);
if (!hThread) {
if (errorMsg) *errorMsg = QStringLiteral("CreateRemoteThread failed (error %1).")
.arg(GetLastError());
VirtualFreeEx(hProc, remotePath, 0, MEM_RELEASE);
CloseHandle(hProc);
return false;
}
WaitForSingleObject(hThread, 10000);
DWORD exitCode = 0;
GetExitCodeThread(hThread, &exitCode);
CloseHandle(hThread);
VirtualFreeEx(hProc, remotePath, 0, MEM_RELEASE);
if (exitCode == 0) {
CloseHandle(hProc);
if (errorMsg) *errorMsg = QStringLiteral("LoadLibrary returned NULL in target.\n"
"Ensure rcx_payload.dll is in: %1").arg(path);
return false;
}
/* Step 2: Call RcxPayloadInit() — safe to create timer queues now
(loader lock is no longer held after LoadLibrary returned) */
HMODULE hPayloadRemote = (HMODULE)(uintptr_t)exitCode;
auto pGetProcAddr = reinterpret_cast<FARPROC(WINAPI*)(HMODULE, LPCSTR)>(
GetProcAddress(hK32, "GetProcAddress"));
/* Write "RcxPayloadInit\0" into target, call GetProcAddress remotely */
const char initName[] = "RcxPayloadInit";
void* remoteInitName = VirtualAllocEx(hProc, nullptr, sizeof(initName),
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (remoteInitName) {
WriteProcessMemory(hProc, remoteInitName, initName, sizeof(initName), nullptr);
/* We need to call GetProcAddress(hPayload, "RcxPayloadInit") then call the result.
Simpler approach: write small shellcode that does both calls. */
uint8_t shellcode[128];
int off = 0;
/* sub rsp, 40 ; shadow space + alignment */
shellcode[off++] = 0x48; shellcode[off++] = 0x83; shellcode[off++] = 0xEC; shellcode[off++] = 0x28;
/* mov rcx, hPayloadRemote ; first arg = module handle */
shellcode[off++] = 0x48; shellcode[off++] = 0xB9;
uint64_t hMod = (uint64_t)(uintptr_t)hPayloadRemote;
memcpy(shellcode + off, &hMod, 8); off += 8;
/* mov rdx, remoteInitName ; second arg = "RcxPayloadInit" */
shellcode[off++] = 0x48; shellcode[off++] = 0xBA;
uint64_t pName = (uint64_t)(uintptr_t)remoteInitName;
memcpy(shellcode + off, &pName, 8); off += 8;
/* mov rax, GetProcAddress */
shellcode[off++] = 0x48; shellcode[off++] = 0xB8;
uint64_t pGPA = (uint64_t)(uintptr_t)pGetProcAddr;
memcpy(shellcode + off, &pGPA, 8); off += 8;
/* call rax ; rax = RcxPayloadInit */
shellcode[off++] = 0xFF; shellcode[off++] = 0xD0;
/* test rax, rax */
shellcode[off++] = 0x48; shellcode[off++] = 0x85; shellcode[off++] = 0xC0;
/* jz skip (jump over the call if null) */
shellcode[off++] = 0x74; shellcode[off++] = 0x02;
/* call rax ; RcxPayloadInit() */
shellcode[off++] = 0xFF; shellcode[off++] = 0xD0;
/* skip: add rsp, 40 */
shellcode[off++] = 0x48; shellcode[off++] = 0x83; shellcode[off++] = 0xC4; shellcode[off++] = 0x28;
/* ret */
shellcode[off++] = 0xC3;
void* remoteCode = VirtualAllocEx(hProc, nullptr, (SIZE_T)off,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (remoteCode) {
WriteProcessMemory(hProc, remoteCode, shellcode, (SIZE_T)off, nullptr);
HANDLE hThread2 = CreateRemoteThread(hProc, nullptr, 0,
(LPTHREAD_START_ROUTINE)remoteCode, nullptr, 0, nullptr);
if (hThread2) {
WaitForSingleObject(hThread2, 10000);
CloseHandle(hThread2);
}
VirtualFreeEx(hProc, remoteCode, 0, MEM_RELEASE);
}
VirtualFreeEx(hProc, remoteInitName, 0, MEM_RELEASE);
}
CloseHandle(hProc);
return true;
}
#else
/* ── Linux injection: ptrace + dlopen ─────────────────────────────── */
static uint64_t findLibBase(pid_t pid, const char* libName)
{
char mapsPath[64];
snprintf(mapsPath, sizeof(mapsPath), "/proc/%d/maps", pid);
FILE* f = fopen(mapsPath, "r");
if (!f) return 0;
char line[1024];
while (fgets(line, sizeof(line), f)) {
if (strstr(line, libName)) {
uint64_t base;
if (sscanf(line, "%lx-", &base) == 1) {
fclose(f);
return base;
}
}
}
fclose(f);
return 0;
}
static uint64_t findSyscallInsn(pid_t pid)
{
char mapsPath[64];
snprintf(mapsPath, sizeof(mapsPath), "/proc/%d/maps", pid);
FILE* f = fopen(mapsPath, "r");
if (!f) return 0;
char line[1024];
while (fgets(line, sizeof(line), f)) {
if (strstr(line, "libc") && strstr(line, "r-xp")) {
uint64_t start, end;
if (sscanf(line, "%lx-%lx", &start, &end) != 2) continue;
fclose(f);
/* scan for 0F 05 (syscall) */
char memPath[64];
snprintf(memPath, sizeof(memPath), "/proc/%d/mem", pid);
int memFd = open(memPath, O_RDONLY);
if (memFd < 0) return 0;
uint8_t buf[4096];
for (uint64_t off = start; off < end; off += sizeof(buf)) {
ssize_t n = pread(memFd, buf, sizeof(buf), (off_t)off);
if (n <= 1) break;
for (ssize_t i = 0; i + 1 < n; ++i) {
if (buf[i] == 0x0F && buf[i + 1] == 0x05) {
close(memFd);
return off + (uint64_t)i;
}
}
}
close(memFd);
return 0;
}
}
fclose(f);
return 0;
}
static bool writeTargetMem(pid_t pid, uint64_t addr, const void* src, size_t len)
{
const uint8_t* p = static_cast<const uint8_t*>(src);
for (size_t i = 0; i < len; i += sizeof(long)) {
long val = 0;
size_t chunk = (len - i < sizeof(long)) ? (len - i) : sizeof(long);
if (chunk < sizeof(long)) {
errno = 0;
val = ptrace(PTRACE_PEEKDATA, pid, (void*)(addr + i), nullptr);
if (errno) return false;
}
memcpy(&val, p + i, chunk);
if (ptrace(PTRACE_POKEDATA, pid, (void*)(addr + i), (void*)val) < 0)
return false;
}
return true;
}
static bool injectPayload(uint32_t pid, QString* errorMsg)
{
QString path = payloadPath();
QByteArray pathUtf8 = path.toUtf8();
if (ptrace(PTRACE_ATTACH, (pid_t)pid, nullptr, nullptr) < 0) {
if (errorMsg)
*errorMsg = QStringLiteral("ptrace attach failed: %1\n"
"Check /proc/sys/kernel/yama/ptrace_scope or run as root.")
.arg(strerror(errno));
return false;
}
int status;
waitpid((pid_t)pid, &status, 0);
/* save registers */
struct user_regs_struct savedRegs, regs;
ptrace(PTRACE_GETREGS, (pid_t)pid, nullptr, &savedRegs);
regs = savedRegs;
/* find syscall instruction in target's libc */
uint64_t syscallAddr = findSyscallInsn((pid_t)pid);
if (!syscallAddr) {
ptrace(PTRACE_DETACH, (pid_t)pid, nullptr, nullptr);
if (errorMsg) *errorMsg = QStringLiteral("Could not find syscall instruction in target.");
return false;
}
/* find dlopen in target via libc offset technique */
void* ourDlopen = dlsym(RTLD_DEFAULT, "dlopen");
uint64_t ourLibcBase = findLibBase(getpid(), "libc");
uint64_t targetLibcBase = findLibBase((pid_t)pid, "libc");
if (!ourDlopen || !ourLibcBase || !targetLibcBase) {
ptrace(PTRACE_DETACH, (pid_t)pid, nullptr, nullptr);
if (errorMsg) *errorMsg = QStringLiteral("Could not resolve dlopen address.");
return false;
}
uint64_t targetDlopen = targetLibcBase + ((uint64_t)ourDlopen - ourLibcBase);
/* call mmap in target via syscall: mmap(0, 4096, RWX, MAP_PRIVATE|MAP_ANON, -1, 0) */
regs.rax = 9; /* __NR_mmap */
regs.rdi = 0;
regs.rsi = 4096;
regs.rdx = 7; /* PROT_READ|PROT_WRITE|PROT_EXEC */
regs.r10 = 0x22; /* MAP_PRIVATE|MAP_ANONYMOUS */
regs.r8 = (uint64_t)-1;
regs.r9 = 0;
regs.rip = syscallAddr;
ptrace(PTRACE_SETREGS, (pid_t)pid, nullptr, &regs);
ptrace(PTRACE_SINGLESTEP, (pid_t)pid, nullptr, nullptr);
waitpid((pid_t)pid, &status, 0);
ptrace(PTRACE_GETREGS, (pid_t)pid, nullptr, &regs);
uint64_t mmapPage = regs.rax;
if ((int64_t)mmapPage < 0 || mmapPage == 0) {
ptrace(PTRACE_SETREGS, (pid_t)pid, nullptr, &savedRegs);
ptrace(PTRACE_DETACH, (pid_t)pid, nullptr, nullptr);
if (errorMsg) *errorMsg = QStringLiteral("mmap in target failed.");
return false;
}
/* write path string at start of page */
writeTargetMem((pid_t)pid, mmapPage, pathUtf8.constData(), (size_t)(pathUtf8.size() + 1));
/* write shellcode after path:
* mov rdi, pathAddr (48 BF xxxxxxxx)
* mov rsi, 2 (48 BE 02000000 00000000)
* mov rax, dlopenAddr (48 B8 xxxxxxxx)
* call rax (FF D0)
* int3 (CC)
*/
uint64_t pathAddr = mmapPage;
uint64_t codeAddr = mmapPage + ((pathUtf8.size() + 1 + 15) & ~15ULL);
uint8_t sc[64];
int len = 0;
/* mov rdi, imm64 */
sc[len++] = 0x48; sc[len++] = 0xBF;
memcpy(sc + len, &pathAddr, 8); len += 8;
/* mov rsi, 2 (RTLD_NOW) */
sc[len++] = 0x48; sc[len++] = 0xBE;
uint64_t rtldNow = 2;
memcpy(sc + len, &rtldNow, 8); len += 8;
/* mov rax, dlopen */
sc[len++] = 0x48; sc[len++] = 0xB8;
memcpy(sc + len, &targetDlopen, 8); len += 8;
/* call rax */
sc[len++] = 0xFF; sc[len++] = 0xD0;
/* int3 */
sc[len++] = 0xCC;
writeTargetMem((pid_t)pid, codeAddr, sc, (size_t)len);
/* execute shellcode */
regs = savedRegs;
regs.rip = codeAddr;
regs.rsp = (mmapPage + 4096) & ~0xFULL;
ptrace(PTRACE_SETREGS, (pid_t)pid, nullptr, &regs);
ptrace(PTRACE_CONT, (pid_t)pid, nullptr, nullptr);
waitpid((pid_t)pid, &status, 0);
bool ok = false;
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
ptrace(PTRACE_GETREGS, (pid_t)pid, nullptr, &regs);
ok = (regs.rax != 0);
}
/* clean up: munmap the page via syscall */
struct user_regs_struct cleanRegs = savedRegs;
cleanRegs.rax = 11; /* __NR_munmap */
cleanRegs.rdi = mmapPage;
cleanRegs.rsi = 4096;
cleanRegs.rip = syscallAddr;
ptrace(PTRACE_SETREGS, (pid_t)pid, nullptr, &cleanRegs);
ptrace(PTRACE_SINGLESTEP, (pid_t)pid, nullptr, nullptr);
waitpid((pid_t)pid, &status, 0);
/* restore and detach */
ptrace(PTRACE_SETREGS, (pid_t)pid, nullptr, &savedRegs);
ptrace(PTRACE_DETACH, (pid_t)pid, nullptr, nullptr);
if (!ok && errorMsg)
*errorMsg = QStringLiteral("dlopen failed in target.\n"
"Ensure payload is at: %1").arg(path);
return ok;
}
#endif /* _WIN32 / linux injection */
} /* anonymous namespace */
/* ══════════════════════════════════════════════════════════════════════
* RemoteProcessMemoryPlugin
* ══════════════════════════════════════════════════════════════════════ */
RemoteProcessMemoryPlugin::RemoteProcessMemoryPlugin() = default;
RemoteProcessMemoryPlugin::~RemoteProcessMemoryPlugin() = default;
QIcon RemoteProcessMemoryPlugin::Icon() const
{
return qApp->style()->standardIcon(QStyle::SP_DriveNetIcon);
}
bool RemoteProcessMemoryPlugin::canHandle(const QString& target) const
{
return target.startsWith(QStringLiteral("rpm:"));
}
std::unique_ptr<rcx::Provider>
RemoteProcessMemoryPlugin::createProvider(const QString& target, QString* errorMsg)
{
/* target = "rpm:{pid}:{name}" */
QStringList parts = target.split(':');
if (parts.size() < 3 || parts[0] != QStringLiteral("rpm")) {
if (errorMsg) *errorMsg = QStringLiteral("Invalid target: ") + target;
return nullptr;
}
bool ok;
uint32_t pid = parts[1].toUInt(&ok);
QString name = parts.mid(2).join(':'); /* name may contain colons */
if (!ok || pid == 0) {
if (errorMsg) *errorMsg = QStringLiteral("Invalid PID in target.");
return nullptr;
}
auto ipc = getOrCreateConnection(pid, errorMsg);
if (!ipc) return nullptr;
return std::make_unique<RemoteProcessProvider>(pid, name, ipc);
}
uint64_t RemoteProcessMemoryPlugin::getInitialBaseAddress(const QString& target) const
{
/* Read imageBase directly from the shared-memory header -- zero IPC cost.
The payload filled it at init from PEB->Ldr (Win) / /proc/self/maps (Linux). */
QStringList parts = target.split(':');
if (parts.size() < 2 || parts[0] != QStringLiteral("rpm"))
return 0;
bool ok;
uint32_t pid = parts[1].toUInt(&ok);
if (!ok) return 0;
QMutexLocker lock(&m_connectionsMutex);
auto it = m_connections.constFind(pid);
if (it == m_connections.constEnd() || !(*it)->connected)
return 0;
auto* hdr = static_cast<const RcxRpcHeader*>((*it)->mappedView);
return hdr->imageBase;
}
bool RemoteProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target)
{
/* ── 1. pick a process ── */
QVector<PluginProcessInfo> pluginProcs = enumerateProcesses();
QList<ProcessInfo> procs;
for (const auto& pi : pluginProcs) {
ProcessInfo info;
info.pid = pi.pid;
info.name = pi.name;
info.path = pi.path;
info.icon = pi.icon;
procs.append(info);
}
ProcessPicker picker(procs, parent);
if (picker.exec() != QDialog::Accepted) return false;
uint32_t pid = picker.selectedProcessId();
QString name = picker.selectedProcessName();
/* ── 2. ask inject or connect ── */
QMessageBox box(parent);
box.setWindowTitle(QStringLiteral("Remote Process Memory"));
box.setText(QStringLiteral("Connect to %1 (PID %2)").arg(name).arg(pid));
box.setInformativeText(QStringLiteral("Choose how to connect to the target:"));
QAbstractButton* injectBtn = box.addButton(QStringLiteral("Inject Payload"), QMessageBox::ActionRole);
QAbstractButton* connectBtn = box.addButton(QStringLiteral("Already Injected"), QMessageBox::ActionRole);
box.addButton(QMessageBox::Cancel);
box.exec();
QAbstractButton* clicked = box.clickedButton();
if (clicked == injectBtn) {
QString injectErr;
if (!injectPayload(pid, &injectErr)) {
QMessageBox::critical(parent, QStringLiteral("Injection Failed"), injectErr);
return false;
}
*target = QStringLiteral("rpm:%1:%2").arg(pid).arg(name);
return true;
}
else if (clicked == connectBtn) {
*target = QStringLiteral("rpm:%1:%2").arg(pid).arg(name);
return true;
}
return false;
}
QVector<PluginProcessInfo> RemoteProcessMemoryPlugin::enumerateProcesses()
{
QVector<PluginProcessInfo> procs;
#ifdef _WIN32
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap == INVALID_HANDLE_VALUE) return procs;
PROCESSENTRY32W entry;
entry.dwSize = sizeof(entry);
if (Process32FirstW(snap, &entry)) {
do {
PluginProcessInfo info;
info.pid = entry.th32ProcessID;
info.name = QString::fromWCharArray(entry.szExeFile);
HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
FALSE, entry.th32ProcessID);
if (hProc) {
wchar_t path[MAX_PATH * 2];
DWORD pathLen = sizeof(path) / sizeof(wchar_t);
if (QueryFullProcessImageNameW(hProc, 0, path, &pathLen)) {
info.path = QString::fromWCharArray(path);
SHFILEINFOW sfi = {};
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi),
SHGFI_ICON | SHGFI_SMALLICON) && sfi.hIcon) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
info.icon = QIcon(QPixmap::fromImage(QImage::fromHICON(sfi.hIcon)));
#else
info.icon = QIcon(QtWin::fromHICON(sfi.hIcon));
#endif
DestroyIcon(sfi.hIcon);
}
}
CloseHandle(hProc);
}
procs.append(info);
} while (Process32NextW(snap, &entry));
}
CloseHandle(snap);
#else
QDir procDir(QStringLiteral("/proc"));
QIcon defIcon = qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
for (const QString& entry : procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
bool ok;
uint32_t pid = entry.toUInt(&ok);
if (!ok || pid == 0) continue;
QFile commFile(QStringLiteral("/proc/%1/comm").arg(pid));
if (!commFile.open(QIODevice::ReadOnly)) continue;
QString procName = QString::fromUtf8(commFile.readAll()).trimmed();
commFile.close();
if (procName.isEmpty()) continue;
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
if (::access(memPath.toUtf8().constData(), R_OK) != 0) continue;
QFileInfo exeInfo(QStringLiteral("/proc/%1/exe").arg(pid));
PluginProcessInfo info;
info.pid = pid;
info.name = procName;
info.path = exeInfo.exists() ? exeInfo.symLinkTarget() : QString();
info.icon = defIcon;
procs.append(info);
}
#endif
return procs;
}
std::shared_ptr<IpcClient>
RemoteProcessMemoryPlugin::getOrCreateConnection(
uint32_t pid, QString* errorMsg)
{
QMutexLocker lock(&m_connectionsMutex);
auto it = m_connections.find(pid);
if (it != m_connections.end() && (*it)->connected)
return *it;
auto ipc = std::make_shared<IpcClient>();
if (!ipc->connect(pid)) {
if (errorMsg)
*errorMsg = QStringLiteral("Failed to connect IPC to PID %1.\n"
"Is the payload running?").arg(pid);
return nullptr;
}
m_connections[pid] = ipc;
return ipc;
}
/* ── Plugin factory ───────────────────────────────────────────────── */
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
{
return new RemoteProcessMemoryPlugin();
}