feat: remove nonce/bootstrap from remote process IPC, use PID-only naming

Shared memory names simplified to Local\RCX_SHM_<pid>, no bootstrap
handshake needed. Payload uses CreateTimerQueueTimer (10ms poll) instead
of a dedicated server thread.
This commit is contained in:
IChooseYou
2026-02-22 11:36:24 -07:00
parent 25aaace382
commit f651edd740
6 changed files with 124 additions and 292 deletions

View File

@@ -5,9 +5,7 @@
#include <QStyle> #include <QStyle>
#include <QApplication> #include <QApplication>
#include <QMessageBox> #include <QMessageBox>
#include <QInputDialog>
#include <QPushButton> #include <QPushButton>
#include <QUuid>
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QPixmap> #include <QPixmap>
@@ -65,12 +63,12 @@ struct IpcClient {
/* ── connect / disconnect ──────────────────────────────────────── */ /* ── connect / disconnect ──────────────────────────────────────── */
bool connect(uint32_t pid, const QByteArray& nonce, int timeoutMs = 5000) bool connect(uint32_t pid, int timeoutMs = 5000)
{ {
char shmName[128], reqName[128], rspName[128]; char shmName[128], reqName[128], rspName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid, nonce.constData()); rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
rcx_rpc_req_name(reqName, sizeof(reqName), pid, nonce.constData()); rcx_rpc_req_name(reqName, sizeof(reqName), pid);
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid, nonce.constData()); rcx_rpc_rsp_name(rspName, sizeof(rspName), pid);
#ifdef _WIN32 #ifdef _WIN32
/* poll for shared memory to appear (payload creating it) */ /* poll for shared memory to appear (payload creating it) */
@@ -373,51 +371,6 @@ static QString payloadPath()
#endif #endif
} }
/* Create bootstrap shared memory with the nonce */
static bool createBootstrapShm(uint32_t pid, const QByteArray& nonce)
{
char bootName[128];
rcx_rpc_boot_name(bootName, sizeof(bootName), pid);
#ifdef _WIN32
HANDLE hBoot = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr,
PAGE_READWRITE, 0, RCX_RPC_BOOT_SIZE,
bootName);
if (!hBoot) return false;
auto* view = static_cast<RcxRpcBootHeader*>(
MapViewOfFile(hBoot, FILE_MAP_WRITE, 0, 0, RCX_RPC_BOOT_SIZE));
if (!view) { CloseHandle(hBoot); return false; }
memset(view, 0, RCX_RPC_BOOT_SIZE);
view->nonceLength = (uint32_t)nonce.size();
memcpy(view->nonce, nonce.constData(), qMin(nonce.size(), 59));
UnmapViewOfFile(view);
/* keep hBoot open until payload reads it (payload unlinks after reading) */
/* leak intentional: closed when process exits or payload consumes it */
return true;
#else
int fd = shm_open(bootName, O_CREAT | O_RDWR, 0600);
if (fd < 0) return false;
if (ftruncate(fd, RCX_RPC_BOOT_SIZE) != 0) { close(fd); return false; }
void* view = mmap(nullptr, RCX_RPC_BOOT_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);
if (view == MAP_FAILED) return false;
auto* boot = static_cast<RcxRpcBootHeader*>(view);
memset(boot, 0, RCX_RPC_BOOT_SIZE);
boot->nonceLength = (uint32_t)nonce.size();
memcpy(boot->nonce, nonce.constData(), qMin(nonce.size(), 59));
munmap(view, RCX_RPC_BOOT_SIZE);
/* payload unlinks after consuming */
return true;
#endif
}
#ifdef _WIN32 #ifdef _WIN32
/* ── Windows injection: CreateRemoteThread + LoadLibraryA ─────────── */ /* ── Windows injection: CreateRemoteThread + LoadLibraryA ─────────── */
@@ -717,24 +670,23 @@ bool RemoteProcessMemoryPlugin::canHandle(const QString& target) const
std::unique_ptr<rcx::Provider> std::unique_ptr<rcx::Provider>
RemoteProcessMemoryPlugin::createProvider(const QString& target, QString* errorMsg) RemoteProcessMemoryPlugin::createProvider(const QString& target, QString* errorMsg)
{ {
/* target = "rpm:{pid}:{nonce}:{name}" */ /* target = "rpm:{pid}:{name}" */
QStringList parts = target.split(':'); QStringList parts = target.split(':');
if (parts.size() < 4 || parts[0] != QStringLiteral("rpm")) { if (parts.size() < 3 || parts[0] != QStringLiteral("rpm")) {
if (errorMsg) *errorMsg = QStringLiteral("Invalid target: ") + target; if (errorMsg) *errorMsg = QStringLiteral("Invalid target: ") + target;
return nullptr; return nullptr;
} }
bool ok; bool ok;
uint32_t pid = parts[1].toUInt(&ok); uint32_t pid = parts[1].toUInt(&ok);
QString nonce = parts[2]; QString name = parts.mid(2).join(':'); /* name may contain colons */
QString name = parts.mid(3).join(':'); /* name may contain colons */
if (!ok || pid == 0) { if (!ok || pid == 0) {
if (errorMsg) *errorMsg = QStringLiteral("Invalid PID in target."); if (errorMsg) *errorMsg = QStringLiteral("Invalid PID in target.");
return nullptr; return nullptr;
} }
auto ipc = getOrCreateConnection(pid, nonce, errorMsg); auto ipc = getOrCreateConnection(pid, errorMsg);
if (!ipc) return nullptr; if (!ipc) return nullptr;
return std::make_unique<RemoteProcessProvider>(pid, name, ipc); return std::make_unique<RemoteProcessProvider>(pid, name, ipc);
@@ -745,7 +697,7 @@ uint64_t RemoteProcessMemoryPlugin::getInitialBaseAddress(const QString& target)
/* Read imageBase directly from the shared-memory header -- zero IPC cost. /* 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). */ The payload filled it at init from PEB->Ldr (Win) / /proc/self/maps (Linux). */
QStringList parts = target.split(':'); QStringList parts = target.split(':');
if (parts.size() < 3 || parts[0] != QStringLiteral("rpm")) if (parts.size() < 2 || parts[0] != QStringLiteral("rpm"))
return 0; return 0;
bool ok; bool ok;
@@ -793,35 +745,17 @@ bool RemoteProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target)
QAbstractButton* clicked = box.clickedButton(); QAbstractButton* clicked = box.clickedButton();
if (clicked == injectBtn) { if (clicked == injectBtn) {
/* generate nonce */
QString nonce = QUuid::createUuid().toString(QUuid::Id128).left(16);
QByteArray nonceUtf8 = nonce.toUtf8();
/* create bootstrap, inject */
if (!createBootstrapShm(pid, nonceUtf8)) {
QMessageBox::critical(parent, QStringLiteral("Error"),
QStringLiteral("Failed to create bootstrap shared memory."));
return false;
}
QString injectErr; QString injectErr;
if (!injectPayload(pid, &injectErr)) { if (!injectPayload(pid, &injectErr)) {
QMessageBox::critical(parent, QStringLiteral("Injection Failed"), injectErr); QMessageBox::critical(parent, QStringLiteral("Injection Failed"), injectErr);
return false; return false;
} }
*target = QStringLiteral("rpm:%1:%2:%3").arg(pid).arg(nonce, name); *target = QStringLiteral("rpm:%1:%2").arg(pid).arg(name);
return true; return true;
} }
else if (clicked == connectBtn) { else if (clicked == connectBtn) {
bool ok; *target = QStringLiteral("rpm:%1:%2").arg(pid).arg(name);
QString nonce = QInputDialog::getText(parent,
QStringLiteral("Connect to Payload"),
QStringLiteral("Enter the payload nonce:"),
QLineEdit::Normal, QString(), &ok);
if (!ok || nonce.isEmpty()) return false;
*target = QStringLiteral("rpm:%1:%2:%3").arg(pid).arg(nonce, name);
return true; return true;
} }
@@ -903,7 +837,7 @@ QVector<PluginProcessInfo> RemoteProcessMemoryPlugin::enumerateProcesses()
std::shared_ptr<IpcClient> std::shared_ptr<IpcClient>
RemoteProcessMemoryPlugin::getOrCreateConnection( RemoteProcessMemoryPlugin::getOrCreateConnection(
uint32_t pid, const QString& nonce, QString* errorMsg) uint32_t pid, QString* errorMsg)
{ {
QMutexLocker lock(&m_connectionsMutex); QMutexLocker lock(&m_connectionsMutex);
@@ -912,7 +846,7 @@ RemoteProcessMemoryPlugin::getOrCreateConnection(
return *it; return *it;
auto ipc = std::make_shared<IpcClient>(); auto ipc = std::make_shared<IpcClient>();
if (!ipc->connect(pid, nonce.toUtf8())) { if (!ipc->connect(pid)) {
if (errorMsg) if (errorMsg)
*errorMsg = QStringLiteral("Failed to connect IPC to PID %1.\n" *errorMsg = QStringLiteral("Failed to connect IPC to PID %1.\n"
"Is the payload running?").arg(pid); "Is the payload running?").arg(pid);

View File

@@ -77,7 +77,7 @@ public:
private: private:
std::shared_ptr<IpcClient> getOrCreateConnection( std::shared_ptr<IpcClient> getOrCreateConnection(
uint32_t pid, const QString& nonce, QString* errorMsg); uint32_t pid, QString* errorMsg);
mutable QMutex m_connectionsMutex; mutable QMutex m_connectionsMutex;
QHash<uint32_t, std::shared_ptr<IpcClient>> m_connections; QHash<uint32_t, std::shared_ptr<IpcClient>> m_connections;

View File

@@ -2,9 +2,8 @@
* rcx_payload -- injected into target process. * rcx_payload -- injected into target process.
* *
* Pure Win32 / POSIX, NO Qt, minimal footprint. * Pure Win32 / POSIX, NO Qt, minimal footprint.
* Reads a nonce from bootstrap shared memory, creates the main IPC * Creates the main IPC channel (shared memory + events/semaphores)
* channel (shared memory + events/semaphores), and runs a server * using PID-only naming and uses a timer queue for polling.
* thread that handles RPC commands from the editor plugin.
*/ */
#include "../rcx_rpc_protocol.h" #include "../rcx_rpc_protocol.h"
@@ -18,11 +17,12 @@
#include <psapi.h> #include <psapi.h>
/* ── globals ──────────────────────────────────────────────────────── */ /* ── globals ──────────────────────────────────────────────────────── */
static HANDLE g_hShm = nullptr; static HANDLE g_hShm = nullptr;
static void* g_mappedView = nullptr; static void* g_mappedView = nullptr;
static HANDLE g_hReqEvent = nullptr; static HANDLE g_hReqEvent = nullptr;
static HANDLE g_hRspEvent = nullptr; static HANDLE g_hRspEvent = nullptr;
static HANDLE g_hThread = nullptr; static HANDLE g_hTimerQueue = nullptr;
static HANDLE g_hTimer = nullptr;
static volatile LONG g_shutdown = 0; static volatile LONG g_shutdown = 0;
/* ── memory safety via VirtualQuery ────────────────────────────────── */ /* ── memory safety via VirtualQuery ────────────────────────────────── */
@@ -167,63 +167,59 @@ static void handle_enum_modules(RcxRpcHeader* hdr, uint8_t* data)
hdr->status = RCX_RPC_STATUS_OK; hdr->status = RCX_RPC_STATUS_OK;
} }
/* ── server thread ────────────────────────────────────────────────── */ /* ── timer callback (replaces server thread) ─────────────────────── */
static DWORD WINAPI ServerThread(LPVOID) static VOID CALLBACK PollCallback(PVOID, BOOLEAN)
{ {
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView); if (InterlockedCompareExchange(&g_shutdown, 0, 0))
return;
DWORD rc = WaitForSingleObject(g_hReqEvent, 0);
if (rc != WAIT_OBJECT_0)
return;
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView);
auto* data = reinterpret_cast<uint8_t*>(g_mappedView) + RCX_RPC_DATA_OFFSET; auto* data = reinterpret_cast<uint8_t*>(g_mappedView) + RCX_RPC_DATA_OFFSET;
/* signal readiness */ hdr->status = RCX_RPC_STATUS_OK;
InterlockedExchange(reinterpret_cast<volatile LONG*>(&hdr->payloadReady), 1);
while (!InterlockedCompareExchange(&g_shutdown, 0, 0)) { switch (static_cast<RcxRpcCommand>(hdr->command)) {
DWORD rc = WaitForSingleObject(g_hReqEvent, 250); case RPC_CMD_READ_BATCH: handle_read_batch(hdr, data); break;
if (rc == WAIT_TIMEOUT) case RPC_CMD_WRITE: handle_write(hdr, data); break;
continue; case RPC_CMD_ENUM_MODULES: handle_enum_modules(hdr, data); break;
if (rc != WAIT_OBJECT_0) case RPC_CMD_PING: break;
break; case RPC_CMD_SHUTDOWN:
InterlockedExchange(&g_shutdown, 1);
hdr->status = RCX_RPC_STATUS_OK; break;
default:
switch (static_cast<RcxRpcCommand>(hdr->command)) { hdr->status = RCX_RPC_STATUS_ERROR;
case RPC_CMD_READ_BATCH: handle_read_batch(hdr, data); break; break;
case RPC_CMD_WRITE: handle_write(hdr, data); break;
case RPC_CMD_ENUM_MODULES: handle_enum_modules(hdr, data); break;
case RPC_CMD_PING: break;
case RPC_CMD_SHUTDOWN:
InterlockedExchange(&g_shutdown, 1);
break;
default:
hdr->status = RCX_RPC_STATUS_ERROR;
break;
}
SetEvent(g_hRspEvent);
if (static_cast<RcxRpcCommand>(hdr->command) == RPC_CMD_SHUTDOWN)
break;
} }
/* mark not-ready so the host process can detect shutdown */ SetEvent(g_hRspEvent);
InterlockedExchange(reinterpret_cast<volatile LONG*>(&hdr->payloadReady), 0);
return 0;
} }
/* ── cleanup ──────────────────────────────────────────────────────── */ /* ── cleanup ──────────────────────────────────────────────────────── */
static void Cleanup(bool waitThread) static void Cleanup()
{ {
InterlockedExchange(&g_shutdown, 1); InterlockedExchange(&g_shutdown, 1);
/* wake the thread if it's blocked on REQ */ if (g_hTimer) {
if (g_hReqEvent) SetEvent(g_hReqEvent); DeleteTimerQueueTimer(g_hTimerQueue, g_hTimer, INVALID_HANDLE_VALUE);
g_hTimer = nullptr;
if (waitThread && g_hThread) { }
WaitForSingleObject(g_hThread, 2000); if (g_hTimerQueue) {
DeleteTimerQueueEx(g_hTimerQueue, INVALID_HANDLE_VALUE);
g_hTimerQueue = nullptr;
}
if (g_mappedView) {
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView);
InterlockedExchange(reinterpret_cast<volatile LONG*>(&hdr->payloadReady), 0);
UnmapViewOfFile(g_mappedView);
g_mappedView = nullptr;
} }
if (g_hThread) { CloseHandle(g_hThread); g_hThread = nullptr; }
if (g_mappedView){ UnmapViewOfFile(g_mappedView); g_mappedView = nullptr; }
if (g_hShm) { CloseHandle(g_hShm); g_hShm = nullptr; } if (g_hShm) { CloseHandle(g_hShm); g_hShm = nullptr; }
if (g_hReqEvent) { CloseHandle(g_hReqEvent); g_hReqEvent = nullptr; } if (g_hReqEvent) { CloseHandle(g_hReqEvent); g_hReqEvent = nullptr; }
if (g_hRspEvent) { CloseHandle(g_hRspEvent); g_hRspEvent = nullptr; } if (g_hRspEvent) { CloseHandle(g_hRspEvent); g_hRspEvent = nullptr; }
@@ -231,36 +227,16 @@ static void Cleanup(bool waitThread)
/* ── DllMain ──────────────────────────────────────────────────────── */ /* ── DllMain ──────────────────────────────────────────────────────── */
BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID reserved) BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID)
{ {
if (reason == DLL_PROCESS_ATTACH) { if (reason == DLL_PROCESS_ATTACH) {
uint32_t pid = GetCurrentProcessId(); uint32_t pid = GetCurrentProcessId();
/* ── read nonce from bootstrap shm ── */ /* ── create main shared memory (PID-only naming) ── */
char bootName[128];
rcx_rpc_boot_name(bootName, sizeof(bootName), pid);
HANDLE hBoot = OpenFileMappingA(FILE_MAP_READ, FALSE, bootName);
if (!hBoot) return TRUE; /* no bootstrap = nothing to do */
auto* bootView = static_cast<const RcxRpcBootHeader*>(
MapViewOfFile(hBoot, FILE_MAP_READ, 0, 0, RCX_RPC_BOOT_SIZE));
if (!bootView) { CloseHandle(hBoot); return TRUE; }
char nonce[64] = {};
uint32_t nLen = bootView->nonceLength;
if (nLen > 59) nLen = 59;
memcpy(nonce, bootView->nonce, nLen);
nonce[nLen] = '\0';
UnmapViewOfFile(bootView);
CloseHandle(hBoot);
/* ── create main shared memory ── */
char shmName[128], reqName[128], rspName[128]; char shmName[128], reqName[128], rspName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid, nonce); rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
rcx_rpc_req_name(reqName, sizeof(reqName), pid, nonce); rcx_rpc_req_name(reqName, sizeof(reqName), pid);
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid, nonce); rcx_rpc_rsp_name(rspName, sizeof(rspName), pid);
g_hShm = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, g_hShm = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr,
PAGE_READWRITE, 0, RCX_RPC_SHM_SIZE, shmName); PAGE_READWRITE, 0, RCX_RPC_SHM_SIZE, shmName);
@@ -273,27 +249,36 @@ BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID reserved)
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView); auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView);
hdr->version = RCX_RPC_VERSION; hdr->version = RCX_RPC_VERSION;
/* image base from PEB: gs:[0x60] PEB, +0x18 Ldr, Flink first entry, +0x30 DllBase */ /* image base from PEB: gs:[0x60] -> PEB, +0x18 -> Ldr, Flink -> first entry, +0x30 -> DllBase */
{ {
uint64_t peb; uint64_t peb;
asm volatile("mov %%gs:0x60, %0" : "=r"(peb)); asm volatile("mov %%gs:0x60, %0" : "=r"(peb));
uint64_t ldr = *reinterpret_cast<uint64_t*>(peb + 0x18); uint64_t ldr = *reinterpret_cast<uint64_t*>(peb + 0x18);
uint64_t firstLink = *reinterpret_cast<uint64_t*>(ldr + 0x10); /* InLoadOrderModuleList.Flink */ uint64_t firstLink = *reinterpret_cast<uint64_t*>(ldr + 0x10);
hdr->imageBase = *reinterpret_cast<uint64_t*>(firstLink + 0x30); /* DllBase */ hdr->imageBase = *reinterpret_cast<uint64_t*>(firstLink + 0x30);
} }
/* ── create events ── */ /* ── create events ── */
g_hReqEvent = CreateEventA(nullptr, FALSE, FALSE, reqName); g_hReqEvent = CreateEventA(nullptr, FALSE, FALSE, reqName);
g_hRspEvent = CreateEventA(nullptr, FALSE, FALSE, rspName); g_hRspEvent = CreateEventA(nullptr, FALSE, FALSE, rspName);
if (!g_hReqEvent || !g_hRspEvent) { Cleanup(false); return TRUE; } if (!g_hReqEvent || !g_hRspEvent) { Cleanup(); return TRUE; }
/* ── start server thread (payloadReady set by the thread) ── */ /* ── start timer queue (10ms poll interval) ── */
g_hThread = CreateThread(nullptr, 0, ServerThread, nullptr, 0, nullptr); g_hTimerQueue = CreateTimerQueue();
if (!g_hThread) { Cleanup(false); return TRUE; } if (!g_hTimerQueue) { Cleanup(); return TRUE; }
if (!CreateTimerQueueTimer(&g_hTimer, g_hTimerQueue,
PollCallback, nullptr, 0, 10,
WT_EXECUTEDEFAULT)) {
Cleanup();
return TRUE;
}
/* signal readiness */
InterlockedExchange(reinterpret_cast<volatile LONG*>(&hdr->payloadReady), 1);
} }
else if (reason == DLL_PROCESS_DETACH) { else if (reason == DLL_PROCESS_DETACH) {
/* reserved != NULL → process is terminating (threads already dead) */ Cleanup();
Cleanup(reserved == nullptr);
} }
return TRUE; return TRUE;
@@ -529,37 +514,14 @@ static void payload_init()
{ {
uint32_t pid = (uint32_t)getpid(); uint32_t pid = (uint32_t)getpid();
/* ── read nonce from bootstrap shm ── */
char bootName[128];
rcx_rpc_boot_name(bootName, sizeof(bootName), pid);
int bootFd = shm_open(bootName, O_RDONLY, 0);
if (bootFd < 0) return;
void* bootView = mmap(nullptr, RCX_RPC_BOOT_SIZE, PROT_READ,
MAP_SHARED, bootFd, 0);
close(bootFd);
if (bootView == MAP_FAILED) return;
auto* boot = static_cast<const RcxRpcBootHeader*>(bootView);
char nonce[64] = {};
uint32_t nLen = boot->nonceLength;
if (nLen > 59) nLen = 59;
memcpy(nonce, boot->nonce, nLen);
nonce[nLen] = '\0';
munmap(bootView, RCX_RPC_BOOT_SIZE);
/* one-shot, unlink bootstrap */
shm_unlink(bootName);
/* ── open /proc/self/mem for safe access ── */ /* ── open /proc/self/mem for safe access ── */
g_memFd = open("/proc/self/mem", O_RDWR); g_memFd = open("/proc/self/mem", O_RDWR);
if (g_memFd < 0) return; if (g_memFd < 0) return;
/* ── create main shared memory ── */ /* ── create main shared memory (PID-only naming) ── */
rcx_rpc_shm_name(g_shmName, sizeof(g_shmName), pid, nonce); rcx_rpc_shm_name(g_shmName, sizeof(g_shmName), pid);
rcx_rpc_req_name(g_reqName, sizeof(g_reqName), pid, nonce); rcx_rpc_req_name(g_reqName, sizeof(g_reqName), pid);
rcx_rpc_rsp_name(g_rspName, sizeof(g_rspName), pid, nonce); rcx_rpc_rsp_name(g_rspName, sizeof(g_rspName), pid);
g_shmFd = shm_open(g_shmName, O_CREAT | O_RDWR, 0600); g_shmFd = shm_open(g_shmName, O_CREAT | O_RDWR, 0600);
if (g_shmFd < 0) return; if (g_shmFd < 0) return;

View File

@@ -15,7 +15,6 @@
#define RCX_RPC_HEADER_SIZE 4096 #define RCX_RPC_HEADER_SIZE 4096
#define RCX_RPC_DATA_OFFSET RCX_RPC_HEADER_SIZE #define RCX_RPC_DATA_OFFSET RCX_RPC_HEADER_SIZE
#define RCX_RPC_DATA_SIZE (RCX_RPC_SHM_SIZE - RCX_RPC_DATA_OFFSET) #define RCX_RPC_DATA_SIZE (RCX_RPC_SHM_SIZE - RCX_RPC_DATA_OFFSET)
#define RCX_RPC_BOOT_SIZE 64
/* status codes */ /* status codes */
#define RCX_RPC_STATUS_OK 0 #define RCX_RPC_STATUS_OK 0
@@ -83,47 +82,32 @@ struct RcxRpcHeader {
uint8_t _pad[RCX_RPC_HEADER_SIZE - 48]; uint8_t _pad[RCX_RPC_HEADER_SIZE - 48];
}; };
/* Bootstrap shm -- 64 bytes, carries the nonce from plugin to payload */ /* ── name formatting helpers (PID-only, no nonce) ─────────────────── */
struct RcxRpcBootHeader {
uint32_t nonceLength;
char nonce[60];
};
/* ── name formatting helpers ───────────────────────────────────────── */ static inline void rcx_rpc_shm_name(char* buf, int n, uint32_t pid) {
static inline void rcx_rpc_boot_name(char* buf, int n, uint32_t pid) {
#ifdef _WIN32 #ifdef _WIN32
snprintf(buf, n, "Local\\RCX_BOOT_%u", pid); snprintf(buf, n, "Local\\RCX_SHM_%u", pid);
#else #else
snprintf(buf, n, "/rcx_boot_%u", pid); snprintf(buf, n, "/rcx_shm_%u", pid);
#endif #endif
} }
static inline void rcx_rpc_shm_name(char* buf, int n, uint32_t pid, const char* nonce) { static inline void rcx_rpc_req_name(char* buf, int n, uint32_t pid) {
#ifdef _WIN32 #ifdef _WIN32
snprintf(buf, n, "Local\\RCX_SHM_%u_%s", pid, nonce); snprintf(buf, n, "Local\\RCX_REQ_%u", pid);
#else #else
snprintf(buf, n, "/rcx_shm_%u_%s", pid, nonce); snprintf(buf, n, "/rcx_req_%u", pid);
#endif #endif
} }
static inline void rcx_rpc_req_name(char* buf, int n, uint32_t pid, const char* nonce) { static inline void rcx_rpc_rsp_name(char* buf, int n, uint32_t pid) {
#ifdef _WIN32 #ifdef _WIN32
snprintf(buf, n, "Local\\RCX_REQ_%u_%s", pid, nonce); snprintf(buf, n, "Local\\RCX_RSP_%u", pid);
#else #else
snprintf(buf, n, "/rcx_req_%u_%s", pid, nonce); snprintf(buf, n, "/rcx_rsp_%u", pid);
#endif
}
static inline void rcx_rpc_rsp_name(char* buf, int n, uint32_t pid, const char* nonce) {
#ifdef _WIN32
snprintf(buf, n, "Local\\RCX_RSP_%u_%s", pid, nonce);
#else
snprintf(buf, n, "/rcx_rsp_%u_%s", pid, nonce);
#endif #endif
} }
#ifdef __cplusplus #ifdef __cplusplus
static_assert(sizeof(RcxRpcHeader) == RCX_RPC_HEADER_SIZE, "Header must be 4096 bytes"); static_assert(sizeof(RcxRpcHeader) == RCX_RPC_HEADER_SIZE, "Header must be 4096 bytes");
static_assert(sizeof(RcxRpcBootHeader) <= RCX_RPC_BOOT_SIZE, "Boot header must fit 64 bytes");
#endif #endif

View File

@@ -4,7 +4,7 @@
* *
* Usage: * Usage:
* test_rpc_client (auto-spawn host) * test_rpc_client (auto-spawn host)
* test_rpc_client <pid> <nonce> [testbuf_hex testlen] * test_rpc_client <pid> [testbuf_hex testlen]
*/ */
#include "../rcx_rpc_protocol.h" #include "../rcx_rpc_protocol.h"
@@ -45,12 +45,12 @@ struct TestIpcClient {
void* view = nullptr; void* view = nullptr;
bool ok = false; bool ok = false;
bool connect(uint32_t pid, const char* nonce, int timeoutMs = 5000) bool connect(uint32_t pid, int timeoutMs = 5000)
{ {
char shmName[128], reqName[128], rspName[128]; char shmName[128], reqName[128], rspName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid, nonce); rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
rcx_rpc_req_name(reqName, sizeof(reqName), pid, nonce); rcx_rpc_req_name(reqName, sizeof(reqName), pid);
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid, nonce); rcx_rpc_rsp_name(rspName, sizeof(rspName), pid);
#ifdef _WIN32 #ifdef _WIN32
ULONGLONG deadline = GetTickCount64() + (ULONGLONG)timeoutMs; ULONGLONG deadline = GetTickCount64() + (ULONGLONG)timeoutMs;
@@ -268,7 +268,7 @@ static pid_t g_hostPid = 0;
#endif #endif
static FILE* g_hostPipe = nullptr; static FILE* g_hostPipe = nullptr;
static bool spawn_host(uint32_t* outPid, char* outNonce, static bool spawn_host(uint32_t* outPid,
uint64_t* outTestBuf, uint32_t* outTestLen) uint64_t* outTestBuf, uint32_t* outTestLen)
{ {
/* resolve path to test_rpc_host next to ourselves */ /* resolve path to test_rpc_host next to ourselves */
@@ -302,11 +302,11 @@ static bool spawn_host(uint32_t* outPid, char* outNonce,
return false; return false;
} }
/* parse: READY pid=X nonce=Y testbuf=0xZ testlen=N */ /* parse: READY pid=X testbuf=0xZ testlen=N */
unsigned long long tbuf = 0; unsigned long long tbuf = 0;
unsigned tlen = 0; unsigned tlen = 0;
if (sscanf(line, "READY pid=%u nonce=%63s testbuf=0x%llx testlen=%u", if (sscanf(line, "READY pid=%u testbuf=0x%llx testlen=%u",
outPid, outNonce, &tbuf, &tlen) < 2) { outPid, &tbuf, &tlen) < 1) {
fprintf(stderr, "ERROR: cannot parse host output: %s\n", line); fprintf(stderr, "ERROR: cannot parse host output: %s\n", line);
return false; return false;
} }
@@ -341,30 +341,28 @@ static void print_fail(const char* name) { printf(" [FAIL] %s\n", name); exit(1
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
uint32_t pid = 0; uint32_t pid = 0;
char nonce[64] = {};
uint64_t testBuf = 0; uint64_t testBuf = 0;
uint32_t testLen = 0; uint32_t testLen = 0;
bool autoMode = false; bool autoMode = false;
if (argc >= 3) { if (argc >= 2) {
pid = (uint32_t)atoi(argv[1]); pid = (uint32_t)atoi(argv[1]);
strncpy(nonce, argv[2], 63); if (argc >= 4) {
if (argc >= 5) { testBuf = (uint64_t)strtoull(argv[2], nullptr, 0);
testBuf = (uint64_t)strtoull(argv[3], nullptr, 0); testLen = (uint32_t)atoi(argv[3]);
testLen = (uint32_t)atoi(argv[4]);
} }
} else { } else {
autoMode = true; autoMode = true;
printf("Auto-spawning test_rpc_host...\n"); printf("Auto-spawning test_rpc_host...\n");
if (!spawn_host(&pid, nonce, &testBuf, &testLen)) return 1; if (!spawn_host(&pid, &testBuf, &testLen)) return 1;
} }
printf("Connecting to PID=%u nonce=%s testbuf=0x%llx testlen=%u\n\n", printf("Connecting to PID=%u testbuf=0x%llx testlen=%u\n\n",
pid, nonce, (unsigned long long)testBuf, testLen); pid, (unsigned long long)testBuf, testLen);
/* ── connect ── */ /* ── connect ── */
TestIpcClient ipc; TestIpcClient ipc;
if (!ipc.connect(pid, nonce)) { if (!ipc.connect(pid)) {
fprintf(stderr, "ERROR: IPC connect failed\n"); fprintf(stderr, "ERROR: IPC connect failed\n");
if (autoMode) cleanup_host(); if (autoMode) cleanup_host();
return 1; return 1;

View File

@@ -1,7 +1,7 @@
/* /*
* test_rpc_host -- loads rcx_payload in-process, acts as the "target". * test_rpc_host -- loads rcx_payload in-process, acts as the "target".
* *
* Usage: test_rpc_host [nonce] * Usage: test_rpc_host
* *
* Prints a READY line (machine-parseable), then waits for the payload * Prints a READY line (machine-parseable), then waits for the payload
* to shut down (RPC_CMD_SHUTDOWN from the client). * to shut down (RPC_CMD_SHUTDOWN from the client).
@@ -68,50 +68,11 @@ static int payload_path(char* out, int outLen)
return 0; return 0;
} }
/* Create bootstrap shared memory with the nonce */
static int create_bootstrap(uint32_t pid, const char* nonce)
{
char bootName[128];
rcx_rpc_boot_name(bootName, sizeof(bootName), pid);
#ifdef _WIN32
HANDLE h = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr,
PAGE_READWRITE, 0, RCX_RPC_BOOT_SIZE, bootName);
if (!h) return -1;
void* v = MapViewOfFile(h, FILE_MAP_WRITE, 0, 0, RCX_RPC_BOOT_SIZE);
if (!v) { CloseHandle(h); return -1; }
RcxRpcBootHeader* boot = (RcxRpcBootHeader*)v;
memset(boot, 0, RCX_RPC_BOOT_SIZE);
boot->nonceLength = (uint32_t)strlen(nonce);
strncpy(boot->nonce, nonce, 59);
UnmapViewOfFile(v);
/* keep h open for payload to read */
return 0;
#else
int fd = shm_open(bootName, O_CREAT | O_RDWR, 0600);
if (fd < 0) return -1;
if (ftruncate(fd, RCX_RPC_BOOT_SIZE) != 0) { close(fd); return -1; }
void* v = mmap(nullptr, RCX_RPC_BOOT_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);
if (v == MAP_FAILED) return -1;
RcxRpcBootHeader* boot = (RcxRpcBootHeader*)v;
memset(boot, 0, RCX_RPC_BOOT_SIZE);
boot->nonceLength = (uint32_t)strlen(nonce);
strncpy(boot->nonce, nonce, 59);
munmap(v, RCX_RPC_BOOT_SIZE);
return 0;
#endif
}
/* Open the main shared memory (read-only, just to monitor payloadReady) */ /* Open the main shared memory (read-only, just to monitor payloadReady) */
static void* open_main_shm(uint32_t pid, const char* nonce) static void* open_main_shm(uint32_t pid)
{ {
char shmName[128]; char shmName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid, nonce); rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
#ifdef _WIN32 #ifdef _WIN32
HANDLE h = nullptr; HANDLE h = nullptr;
@@ -142,21 +103,14 @@ static uint8_t g_testBuf[65536];
/* ── main ─────────────────────────────────────────────────────────── */ /* ── main ─────────────────────────────────────────────────────────── */
int main(int argc, char** argv) int main(int, char**)
{ {
const char* nonce = (argc > 1) ? argv[1] : "test0001";
uint32_t pid = current_pid(); uint32_t pid = current_pid();
/* fill test buffer with known pattern */ /* fill test buffer with known pattern */
for (int i = 0; i < (int)sizeof(g_testBuf); ++i) for (int i = 0; i < (int)sizeof(g_testBuf); ++i)
g_testBuf[i] = (uint8_t)(i & 0xFF); g_testBuf[i] = (uint8_t)(i & 0xFF);
/* create bootstrap shm */
if (create_bootstrap(pid, nonce) != 0) {
fprintf(stderr, "ERROR: failed to create bootstrap shm\n");
return 1;
}
/* load payload */ /* load payload */
char plPath[1024]; char plPath[1024];
if (payload_path(plPath, sizeof(plPath)) != 0) { if (payload_path(plPath, sizeof(plPath)) != 0) {
@@ -180,7 +134,7 @@ int main(int argc, char** argv)
#endif #endif
/* open main shm and wait for payloadReady */ /* open main shm and wait for payloadReady */
void* shmView = open_main_shm(pid, nonce); void* shmView = open_main_shm(pid);
if (!shmView) { if (!shmView) {
fprintf(stderr, "ERROR: failed to open main shared memory\n"); fprintf(stderr, "ERROR: failed to open main shared memory\n");
return 1; return 1;
@@ -197,8 +151,8 @@ int main(int argc, char** argv)
} }
/* print READY line for the client to parse */ /* print READY line for the client to parse */
printf("READY pid=%u nonce=%s testbuf=0x%llx testlen=%u\n", printf("READY pid=%u testbuf=0x%llx testlen=%u\n",
pid, nonce, pid,
(unsigned long long)(uintptr_t)g_testBuf, (unsigned long long)(uintptr_t)g_testBuf,
(unsigned)sizeof(g_testBuf)); (unsigned)sizeof(g_testBuf));
fflush(stdout); fflush(stdout);
@@ -210,7 +164,7 @@ int main(int argc, char** argv)
printf("Payload shut down, exiting.\n"); printf("Payload shut down, exiting.\n");
#ifdef _WIN32 #ifdef _WIN32
/* give the server thread a moment to exit */ /* give the timer queue a moment to drain */
Sleep(200); Sleep(200);
FreeLibrary(hPayload); FreeLibrary(hPayload);
if (shmView) UnmapViewOfFile(shmView); if (shmView) UnmapViewOfFile(shmView);