diff --git a/plugins/RemoteProcessMemory/RemoteProcessMemoryPlugin.cpp b/plugins/RemoteProcessMemory/RemoteProcessMemoryPlugin.cpp index 4ee7f78..22f78f8 100644 --- a/plugins/RemoteProcessMemory/RemoteProcessMemoryPlugin.cpp +++ b/plugins/RemoteProcessMemory/RemoteProcessMemoryPlugin.cpp @@ -400,7 +400,7 @@ static bool injectPayload(uint32_t pid, QString* errorMsg) WriteProcessMemory(hProc, remotePath, pathUtf8.constData(), pathLen, nullptr); - /* create remote thread calling LoadLibraryA(path) */ + /* Step 1: LoadLibraryA — loads the DLL (DllMain is minimal) */ HMODULE hK32 = GetModuleHandleA("kernel32.dll"); auto pLoadLib = reinterpret_cast( GetProcAddress(hK32, "LoadLibraryA")); @@ -417,19 +417,81 @@ static bool injectPayload(uint32_t pid, QString* errorMsg) WaitForSingleObject(hThread, 10000); - /* check if LoadLibrary returned non-null */ DWORD exitCode = 0; GetExitCodeThread(hThread, &exitCode); CloseHandle(hThread); VirtualFreeEx(hProc, remotePath, 0, MEM_RELEASE); - CloseHandle(hProc); 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( + 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; } diff --git a/plugins/RemoteProcessMemory/payload/rcx_payload.cpp b/plugins/RemoteProcessMemory/payload/rcx_payload.cpp index 68d7c8d..d123ce5 100644 --- a/plugins/RemoteProcessMemory/payload/rcx_payload.cpp +++ b/plugins/RemoteProcessMemory/payload/rcx_payload.cpp @@ -17,13 +17,13 @@ #include /* ── globals ──────────────────────────────────────────────────────── */ -static HANDLE g_hShm = nullptr; -static void* g_mappedView = nullptr; -static HANDLE g_hReqEvent = nullptr; -static HANDLE g_hRspEvent = nullptr; -static HANDLE g_hTimerQueue = nullptr; -static HANDLE g_hTimer = nullptr; -static volatile LONG g_shutdown = 0; +static HANDLE g_hShm = nullptr; +static void* g_mappedView = nullptr; +static HANDLE g_hReqEvent = nullptr; +static HANDLE g_hRspEvent = nullptr; +static HANDLE g_hTimerQueue = nullptr; +static HANDLE g_hPollTimer = nullptr; +static volatile LONG g_initialized = 0; /* ── memory safety via VirtualQuery ────────────────────────────────── */ @@ -167,13 +167,17 @@ static void handle_enum_modules(RcxRpcHeader* hdr, uint8_t* data) hdr->status = RCX_RPC_STATUS_OK; } -/* ── timer callback (replaces server thread) ─────────────────────── */ +/* forward declaration */ +void RcxPayloadCleanup(); -static VOID CALLBACK PollCallback(PVOID, BOOLEAN) +/* ── timer callback (non-blocking poll) ───────────────────────────── */ + +static VOID CALLBACK RcxPollTimerCallback(PVOID, BOOLEAN) { - if (InterlockedCompareExchange(&g_shutdown, 0, 0)) + if (!g_mappedView || !g_hReqEvent || !g_hRspEvent) return; + /* non-blocking check: is there a pending request? */ DWORD rc = WaitForSingleObject(g_hReqEvent, 0); if (rc != WAIT_OBJECT_0) return; @@ -189,8 +193,8 @@ static VOID CALLBACK PollCallback(PVOID, BOOLEAN) 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; + RcxPayloadCleanup(); + return; default: hdr->status = RCX_RPC_STATUS_ERROR; break; @@ -201,86 +205,109 @@ static VOID CALLBACK PollCallback(PVOID, BOOLEAN) /* ── cleanup ──────────────────────────────────────────────────────── */ -static void Cleanup() +void RcxPayloadCleanup() { - InterlockedExchange(&g_shutdown, 1); + if (!InterlockedCompareExchange(&g_initialized, 0, 0)) + return; - if (g_hTimer) { - DeleteTimerQueueTimer(g_hTimerQueue, g_hTimer, INVALID_HANDLE_VALUE); - g_hTimer = nullptr; - } + /* stop the poll timer first */ if (g_hTimerQueue) { - DeleteTimerQueueEx(g_hTimerQueue, INVALID_HANDLE_VALUE); + DeleteTimerQueueEx(g_hTimerQueue, INVALID_HANDLE_VALUE); /* waits for callbacks */ g_hTimerQueue = nullptr; + g_hPollTimer = nullptr; } + /* mark not-ready */ if (g_mappedView) { auto* hdr = static_cast(g_mappedView); InterlockedExchange(reinterpret_cast(&hdr->payloadReady), 0); - UnmapViewOfFile(g_mappedView); - g_mappedView = nullptr; } - if (g_hShm) { CloseHandle(g_hShm); g_hShm = nullptr; } - if (g_hReqEvent) { CloseHandle(g_hReqEvent); g_hReqEvent = nullptr; } - if (g_hRspEvent) { CloseHandle(g_hRspEvent); g_hRspEvent = nullptr; } + + if (g_mappedView) { UnmapViewOfFile(g_mappedView); g_mappedView = nullptr; } + if (g_hShm) { CloseHandle(g_hShm); g_hShm = nullptr; } + if (g_hReqEvent) { CloseHandle(g_hReqEvent); g_hReqEvent = nullptr; } + if (g_hRspEvent) { CloseHandle(g_hRspEvent); g_hRspEvent = nullptr; } + + InterlockedExchange(&g_initialized, 0); } -/* ── DllMain ──────────────────────────────────────────────────────── */ +/* ── init (called AFTER DllMain returns — safe for timer queues) ── */ + +extern "C" __declspec(dllexport) +bool RcxPayloadInit() +{ + if (InterlockedCompareExchange(&g_initialized, 1, 0) != 0) + return true; /* already initialized */ + + uint32_t pid = GetCurrentProcessId(); + + 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); + + g_hShm = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, + PAGE_READWRITE, 0, RCX_RPC_SHM_SIZE, shmName); + if (!g_hShm) { + InterlockedExchange(&g_initialized, 0); + return false; + } + + g_mappedView = MapViewOfFile(g_hShm, FILE_MAP_ALL_ACCESS, 0, 0, RCX_RPC_SHM_SIZE); + if (!g_mappedView) { + CloseHandle(g_hShm); g_hShm = nullptr; + InterlockedExchange(&g_initialized, 0); + return false; + } + + memset(g_mappedView, 0, RCX_RPC_HEADER_SIZE); + auto* hdr = static_cast(g_mappedView); + hdr->version = RCX_RPC_VERSION; + + /* image base from PEB */ + { + uint64_t peb; + asm volatile("mov %%gs:0x60, %0" : "=r"(peb)); + uint64_t ldr = *reinterpret_cast(peb + 0x18); + uint64_t firstLink = *reinterpret_cast(ldr + 0x10); + hdr->imageBase = *reinterpret_cast(firstLink + 0x30); + } + + g_hReqEvent = CreateEventA(nullptr, FALSE, FALSE, reqName); + g_hRspEvent = CreateEventA(nullptr, FALSE, FALSE, rspName); + if (!g_hReqEvent || !g_hRspEvent) { + RcxPayloadCleanup(); + return false; + } + + /* create dedicated timer queue + fast poll timer (10ms interval) */ + g_hTimerQueue = CreateTimerQueue(); + if (!g_hTimerQueue) { + RcxPayloadCleanup(); + return false; + } + + if (!CreateTimerQueueTimer(&g_hPollTimer, g_hTimerQueue, + RcxPollTimerCallback, nullptr, + 0, /* start immediately */ + 10, /* 10ms repeat */ + WT_EXECUTEDEFAULT)) { + RcxPayloadCleanup(); + return false; + } + + /* mark ready */ + InterlockedExchange(reinterpret_cast(&hdr->payloadReady), 1); + return true; +} + +/* ── DllMain — minimal, no heavy work under loader lock ───────────── */ BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) { - if (reason == DLL_PROCESS_ATTACH) { - uint32_t pid = GetCurrentProcessId(); - - /* ── create main shared memory (PID-only naming) ── */ - 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); - - g_hShm = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, - PAGE_READWRITE, 0, RCX_RPC_SHM_SIZE, shmName); - if (!g_hShm) return TRUE; - - g_mappedView = MapViewOfFile(g_hShm, FILE_MAP_ALL_ACCESS, 0, 0, RCX_RPC_SHM_SIZE); - if (!g_mappedView) { CloseHandle(g_hShm); g_hShm = nullptr; return TRUE; } - - memset(g_mappedView, 0, RCX_RPC_HEADER_SIZE); - auto* hdr = static_cast(g_mappedView); - hdr->version = RCX_RPC_VERSION; - - /* image base from PEB: gs:[0x60] -> PEB, +0x18 -> Ldr, Flink -> first entry, +0x30 -> DllBase */ - { - uint64_t peb; - asm volatile("mov %%gs:0x60, %0" : "=r"(peb)); - uint64_t ldr = *reinterpret_cast(peb + 0x18); - uint64_t firstLink = *reinterpret_cast(ldr + 0x10); - hdr->imageBase = *reinterpret_cast(firstLink + 0x30); - } - - /* ── create events ── */ - g_hReqEvent = CreateEventA(nullptr, FALSE, FALSE, reqName); - g_hRspEvent = CreateEventA(nullptr, FALSE, FALSE, rspName); - if (!g_hReqEvent || !g_hRspEvent) { Cleanup(); return TRUE; } - - /* ── start timer queue (10ms poll interval) ── */ - g_hTimerQueue = CreateTimerQueue(); - 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(&hdr->payloadReady), 1); + if (reason == DLL_PROCESS_DETACH) { + RcxPayloadCleanup(); } - else if (reason == DLL_PROCESS_DETACH) { - Cleanup(); - } - return TRUE; } diff --git a/plugins/RemoteProcessMemory/tests/test_rpc_host.cpp b/plugins/RemoteProcessMemory/tests/test_rpc_host.cpp index 066e805..d5537f2 100644 --- a/plugins/RemoteProcessMemory/tests/test_rpc_host.cpp +++ b/plugins/RemoteProcessMemory/tests/test_rpc_host.cpp @@ -125,6 +125,15 @@ int main(int, char**) plPath, GetLastError()); return 1; } + + /* Call RcxPayloadInit() — DllMain is minimal, init must be explicit */ + typedef bool (*RcxPayloadInitFn)(); + auto pfnInit = (RcxPayloadInitFn)GetProcAddress(hPayload, "RcxPayloadInit"); + if (!pfnInit || !pfnInit()) { + fprintf(stderr, "ERROR: RcxPayloadInit() failed or not found\n"); + FreeLibrary(hPayload); + return 1; + } #else void* hPayload = dlopen(plPath, RTLD_NOW); if (!hPayload) {