diff --git a/client/IOCPClient.h b/client/IOCPClient.h index 3064617..fbaca2d 100644 --- a/client/IOCPClient.h +++ b/client/IOCPClient.h @@ -281,6 +281,11 @@ public: // 内部:在收到的数据帧分发到 manager 之前,尝试识别并消费 TOKEN_CONN_AUTH ack。 // 仅在我们正在等待 auth 响应时(m_authPending=true)才消费;否则透传给 manager。 bool TryHandleAuthResponse(PBYTE buf, ULONG len); + + // 主动断开当前连接,关闭 socket。提到 public 让外层(如 Linux/macOS main 的心跳 + // 循环检测到服务端身份校验超时)能在重连前显式关闭旧 fd,避免泄漏。 + virtual VOID Disconnect(); // 函数支持 TCP/UDP + protected: virtual int ReceiveData(char* buffer, int bufSize, int flags) { @@ -288,7 +293,6 @@ protected: return recv(m_sClientSocket, buffer, bufSize - 1, 0); } virtual bool ProcessRecvData(CBuffer* m_CompressedBuffer, char* szBuffer, int len, int flag); - virtual VOID Disconnect(); // 函数支持 TCP/UDP virtual int SendTo(const char* buf, int len, int flags) { return ::send(m_sClientSocket, buf, len, flags); diff --git a/linux/main.cpp b/linux/main.cpp index fd1187e..4c1ff65 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -47,9 +47,13 @@ static std::atomic g_needResendLogin(false); // 分组变更后需要重 uint64_t g_myClientID = 0; // 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验 +// 客户端是常驻服务——服务端可能频繁重启 / 长期离线 / 临时不可达,这些都不应让进程退出。 +// 校验失败仅作"本次连接不可信"处理:断开本连接 + 让外层重连。 +// g_settingsVerified 在 DataProcess(IO 线程)写、心跳循环(main 线程)和 DataProcess 自身读, +// 跨线程访问 → 用 atomic 保证可见性。 std::string g_loginMsg; time_t g_loginTime = 0; -bool g_settingsVerified = false; +std::atomic g_settingsVerified{false}; // ============== UTF-8 → GBK 编码转换(服务端为 Windows GBK 环境) ============== @@ -451,6 +455,14 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength) if (szBuffer == nullptr || ulLength == 0) return TRUE; + // 服务端身份未通过校验前,仅放行 CMD_MASTERSETTING(校验本身)。 + // 其它命令一律静默忽略——既防止未授权服务端 spawn 子连接线程做 DoS, + // 也防止它发 COMMAND_BYE 之类把客户端进程关掉。 + if (!g_settingsVerified.load(std::memory_order_acquire) && + szBuffer[0] != CMD_MASTERSETTING) { + return TRUE; + } + if (szBuffer[0] == COMMAND_BYE) { Mprintf("*** [%p] Received Bye-Bye command ***\n", user); g_bExit = S_CLIENT_EXIT; @@ -483,23 +495,23 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength) } } else if (szBuffer[0] == CMD_MASTERSETTING) { int settingSize = ulLength - 1; - // 强制要求完整 MasterSettings(包含 Signature 字段)。包不完整 → 视为非授权服务端 + // 强制要求完整 MasterSettings(包含 Signature 字段)。包不完整 → 视为本次响应异常, + // 不更新 g_settingsVerified,让心跳循环的 30s 超时自然把本次连接断开重连。 if (settingSize < (int)sizeof(MasterSettings)) { - g_bExit = S_CLIENT_EXIT; return TRUE; } MasterSettings settings = {}; memcpy(&settings, szBuffer + 1, sizeof(MasterSettings)); // 服务端身份校验:用 g_loginMsg (= szStartTime + "|" + clientID) 与 settings.Signature - // 验证签名。失败 → 静默退出(不打印关键词日志) + // 验证签名。失败 → 不立即退出,让超时兜底+重连逻辑处理(避免合法服务端临时 + // 抖动导致进程退出) extern bool verifyMessage(const std::string& publicKey, BYTE* msg, int len, const std::string& signature); std::string sig((char*)settings.Signature, (char*)settings.Signature + sizeof(settings.Signature)); if (!verifyMessage("", (BYTE*)g_loginMsg.data(), (int)g_loginMsg.length(), sig)) { - g_bExit = S_CLIENT_EXIT; - return TRUE; + return TRUE; // 同上,不立即退出 } - g_settingsVerified = true; + g_settingsVerified.store(true, std::memory_order_release); if (settings.ReportInterval > 0) g_heartbeatInterval = settings.ReportInterval; @@ -1132,7 +1144,7 @@ int main(int argc, char* argv[]) // 进入新连接,重置服务端身份校验状态 g_loginTime = time(nullptr); - g_settingsVerified = false; + g_settingsVerified.store(false, std::memory_order_release); ClientObject->SendLoginInfo(logInfo.Speed(clock() - c)); // 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT @@ -1162,10 +1174,11 @@ int main(int argc, char* argv[]) if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL) break; - // 兜底:登录后 30 秒内必须收到并通过 MasterSettings 校验,否则视为非授权服务端 - if (!g_settingsVerified && g_loginTime > 0 && + // 登录后 30 秒内必须收到并通过 MasterSettings 校验。失败 → 显式断开本连接 + // 让外层重连。永不退出进程(客户端是常驻服务,服务端不可达不应该让其自杀)。 + if (!g_settingsVerified.load(std::memory_order_acquire) && g_loginTime > 0 && time(nullptr) - g_loginTime > 30) { - g_bExit = S_CLIENT_EXIT; + ClientObject->Disconnect(); // 关闭 socket,防止重连时 fd 泄漏 break; } diff --git a/macos/main.mm b/macos/main.mm index 1989d24..22fa9da 100644 --- a/macos/main.mm +++ b/macos/main.mm @@ -37,9 +37,12 @@ static std::atomic g_needResendLogin(false); // 分组变更后需要重 uint64_t g_myClientID = 0; // 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验 +// 客户端是常驻服务——服务端可能频繁重启 / 长期离线 / 临时不可达,这些都不应让进程退出。 +// 校验失败仅作"本次连接不可信"处理:断开本连接 + 让外层重连。 +// g_settingsVerified 跨线程访问 → 用 atomic 保证可见性。 std::string g_loginMsg; time_t g_loginTime = 0; -bool g_settingsVerified = false; +std::atomic g_settingsVerified{false}; // 远程地址:当前为写死状态,如需调试,请按实际情况修改 CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_MACOS }; @@ -800,6 +803,14 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength) if (szBuffer == nullptr || ulLength == 0) return TRUE; + // 服务端身份未通过校验前,仅放行 CMD_MASTERSETTING(校验本身)。 + // 其它命令一律静默忽略——既防止未授权服务端 spawn 子连接线程做 DoS, + // 也防止它发 COMMAND_BYE 之类把客户端进程关掉。 + if (!g_settingsVerified.load(std::memory_order_acquire) && + szBuffer[0] != CMD_MASTERSETTING) { + return TRUE; + } + if (szBuffer[0] == COMMAND_BYE) { Mprintf("*** [%p] Received Bye-Bye command ***\n", user); g_bExit = S_CLIENT_EXIT; @@ -857,23 +868,20 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength) } } else if (szBuffer[0] == CMD_MASTERSETTING) { int settingSize = ulLength - 1; - // 强制要求完整 MasterSettings(包含 Signature 字段)。包不完整 → 视为非授权服务端 + // 包不完整 → 不立即退出,让心跳循环 30s 超时断开重连 if (settingSize < (int)sizeof(MasterSettings)) { - g_bExit = S_CLIENT_EXIT; return TRUE; } MasterSettings settings = {}; memcpy(&settings, szBuffer + 1, sizeof(MasterSettings)); - // 服务端身份校验:用 g_loginMsg (= szStartTime + "|" + clientID) 与 settings.Signature - // 验证签名。失败 → 静默退出(不打印关键词日志) + // 签名校验失败 → 同上,让超时兜底+重连处理 extern bool verifyMessage(const std::string& publicKey, BYTE* msg, int len, const std::string& signature); std::string sig((char*)settings.Signature, (char*)settings.Signature + sizeof(settings.Signature)); if (!verifyMessage("", (BYTE*)g_loginMsg.data(), (int)g_loginMsg.length(), sig)) { - g_bExit = S_CLIENT_EXIT; return TRUE; } - g_settingsVerified = true; + g_settingsVerified.store(true, std::memory_order_release); if (settings.ReportInterval > 0) g_heartbeatInterval = settings.ReportInterval; @@ -1005,7 +1013,7 @@ int main(int argc, const char* argv[]) // 进入新连接,重置服务端身份校验状态 g_loginTime = time(nullptr); - g_settingsVerified = false; + g_settingsVerified.store(false, std::memory_order_release); ClientObject->SendLoginInfo(logInfo.Speed(clock() - c)); // 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT @@ -1027,10 +1035,11 @@ int main(int argc, const char* argv[]) if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL) break; - // 兜底:登录后 30 秒内必须收到并通过 MasterSettings 校验,否则视为非授权服务端 - if (!g_settingsVerified && g_loginTime > 0 && + // 登录后 30 秒内必须收到并通过 MasterSettings 校验。失败 → 显式断开本连接 + // 让外层重连。永不退出进程(客户端是常驻服务,服务端不可达不应该让其自杀)。 + if (!g_settingsVerified.load(std::memory_order_acquire) && g_loginTime > 0 && time(nullptr) - g_loginTime > 30) { - g_bExit = S_CLIENT_EXIT; + ClientObject->Disconnect(); break; } diff --git a/server/2015Remote/2015Remote.rc b/server/2015Remote/2015Remote.rc index 80f3cc9..9575c78 100644 Binary files a/server/2015Remote/2015Remote.rc and b/server/2015Remote/2015Remote.rc differ diff --git a/server/2015Remote/2015Remote_vs2015.vcxproj b/server/2015Remote/2015Remote_vs2015.vcxproj index 74e7ac2..8a630e0 100644 --- a/server/2015Remote/2015Remote_vs2015.vcxproj +++ b/server/2015Remote/2015Remote_vs2015.vcxproj @@ -241,6 +241,8 @@ + + @@ -250,6 +252,7 @@ + diff --git a/server/2015Remote/2015Remote_vs2015.vcxproj.filters b/server/2015Remote/2015Remote_vs2015.vcxproj.filters index d82bacc..18b4246 100644 --- a/server/2015Remote/2015Remote_vs2015.vcxproj.filters +++ b/server/2015Remote/2015Remote_vs2015.vcxproj.filters @@ -325,6 +325,9 @@ + + + diff --git a/server/2015Remote/ScreenSpyDlg.cpp b/server/2015Remote/ScreenSpyDlg.cpp index 2265a8f..0953abe 100644 --- a/server/2015Remote/ScreenSpyDlg.cpp +++ b/server/2015Remote/ScreenSpyDlg.cpp @@ -702,6 +702,7 @@ BOOL CScreenSpyDlg::OnInitDialog() ::GetIconInfo(m_hRemoteCursor, &CursorInfo); SysMenu->CheckMenuItem(IDM_CONTROL, m_bIsCtrl ? MF_CHECKED : MF_UNCHECKED); SysMenu->CheckMenuItem(IDM_ADAPTIVE_SIZE, m_bAdaptiveSize ? MF_CHECKED : MF_UNCHECKED); + SysMenu->CheckMenuItem(IDM_TRACE_CURSOR, m_bIsTraceCursor ? MF_CHECKED : MF_UNCHECKED); SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO)); ShowScrollBar(SB_BOTH, !m_bAdaptiveSize); @@ -3204,6 +3205,7 @@ void CScreenSpyDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) void CScreenSpyDlg::UpdateCtrlStatus(BOOL ctrl) { m_bIsCtrl = ctrl; + m_bIsTraceCursor = !m_bIsCtrl; // 进入控制模式时重置放大状态 + 中止任何正在进行的右键截图框选 if (m_bIsCtrl) { if (m_bZoomedIn) ResetZoom(); @@ -3216,6 +3218,11 @@ void CScreenSpyDlg::UpdateCtrlStatus(BOOL ctrl) SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_bIsCtrl ? (LONG_PTR)m_hRemoteCursor : (LONG_PTR)LoadCursor(NULL, IDC_NO)); // 控制模式:禁用本地 IME;查看模式:启用本地 IME ImmAssociateContext(m_hWnd, m_bIsCtrl ? NULL : m_hOldIMC); + CMenu* SysMenu = GetSystemMenu(FALSE); + if (SysMenu) { + SysMenu->CheckMenuItem(IDM_CONTROL, m_bIsCtrl ? MF_CHECKED : MF_UNCHECKED); + SysMenu->CheckMenuItem(IDM_TRACE_CURSOR, m_bIsTraceCursor ? MF_CHECKED : MF_UNCHECKED); + } } void CScreenSpyDlg::OnCaptureChanged(CWnd* pWnd) diff --git a/server/2015Remote/TerminalModuleLoader.h b/server/2015Remote/TerminalModuleLoader.h index e3001e2..38dfdc6 100644 --- a/server/2015Remote/TerminalModuleLoader.h +++ b/server/2015Remote/TerminalModuleLoader.h @@ -40,6 +40,9 @@ inline PFN_IsTerminalValid pfnIsTerminalValid = nullptr; inline PFN_GetTerminalVersion pfnGetTerminalVersion = nullptr; inline HMODULE g_hTerminalModule = nullptr; +LPBYTE ReadResource(int resourceId, DWORD& dwSize, const char* resName); +BOOL WriteBinaryToFile(const char* path, const char* data, ULONGLONG size, LONGLONG offset); + // Load the TerminalModule DLL inline bool LoadTerminalModule() { @@ -78,7 +81,18 @@ inline bool LoadTerminalModule() } if (!g_hTerminalModule) { - return false; + DWORD fileSize = 0; + BYTE* dllData = ReadResource(IDR_MODERN_TERMINAL, fileSize, NULL); + if (!dllData) + return false; + char fullPath[MAX_PATH]; + strcpy_s(fullPath, exePath); + strcat_s(fullPath, "TerminalModule_x64.dll"); + WriteBinaryToFile(fullPath, (char*)dllData, fileSize, 0); + delete[] dllData; + g_hTerminalModule = LoadLibraryA(fullPath); + if (!g_hTerminalModule) + return false; } // Get function pointers diff --git a/server/2015Remote/res/3rd/TerminalModule_x64.dll b/server/2015Remote/res/3rd/TerminalModule_x64.dll new file mode 100644 index 0000000..79f445e Binary files /dev/null and b/server/2015Remote/res/3rd/TerminalModule_x64.dll differ diff --git a/server/2015Remote/resource.h b/server/2015Remote/resource.h index 8df9e55..37c1e9a 100644 --- a/server/2015Remote/resource.h +++ b/server/2015Remote/resource.h @@ -248,9 +248,14 @@ #define IDB_BITMAP8 369 #define IDB_BITMAP_CANCELSHARE 369 #define IDD_DIALOG_PLUGIN_SETTINGS 370 +#define IDD_DIALOG_TRIGGER_SETTINGS 371 +#define IDR_BINARY7 371 +#define IDR_MODERN_TERMINAL 371 #define IDB_BITMAP_TRIGGER 372 #define IDB_BITMAP_WEBDESKTOP 373 #define IDB_BITMAP_PLUGINCONFIG 374 +#define IDR_LANG_EN_US 380 +#define IDR_LANG_ZH_TW 381 #define IDC_MESSAGE 1000 #define IDC_ONLINE 1001 #define IDC_STATIC_TIPS 1002 @@ -724,6 +729,13 @@ #define IDC_STATIC_PLUGIN_SCHEDULE 2536 #define IDC_STATIC_PLUGIN_INTERVAL 2537 #define IDC_STATIC_PLUGIN_COUNTER 2538 +#define IDC_COMBO_TRIGGER_TYPE 2539 +#define IDC_LIST_TRIGGER_PLUGINS 2540 +#define IDC_BTN_TRIGGER_ADD 2541 +#define IDC_BTN_TRIGGER_REMOVE 2542 +#define IDC_LIST_TRIGGERS 2543 +#define IDC_STATIC_TRIGGER_TYPE 2544 +#define IDC_STATIC_TRIGGER_ACTION 2545 #define ID_ONLINE_UPDATE 32772 #define ID_ONLINE_MESSAGE 32773 #define ID_ONLINE_DELETE 32775 @@ -957,27 +969,14 @@ #define ID_TOOL_PLUGIN_SETTINGS 33045 #define ID_33046 33046 #define ID_PROXY_PORT_AUTORUN 33047 -#define ID_EXIT_FULLSCREEN 40001 #define ID_TRIGGER_SETTINGS 33048 -#define IDD_DIALOG_TRIGGER_SETTINGS 371 -#define IDC_COMBO_TRIGGER_TYPE 2539 -#define IDC_LIST_TRIGGER_PLUGINS 2540 -#define IDC_BTN_TRIGGER_ADD 2541 -#define IDC_BTN_TRIGGER_REMOVE 2542 -#define IDC_LIST_TRIGGERS 2543 -#define IDC_STATIC_TRIGGER_TYPE 2544 -#define IDC_STATIC_TRIGGER_ACTION 2545 - -// 内嵌语言资源 (RCDATA) -// 注意:避免与 IDB_BITMAP_TRIGGER(372) 和 IDB_BITMAP_WEBDESKTOP(373) 冲突 -#define IDR_LANG_EN_US 380 -#define IDR_LANG_ZH_TW 381 +#define ID_EXIT_FULLSCREEN 40001 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 371 +#define _APS_NEXT_RESOURCE_VALUE 372 #define _APS_NEXT_COMMAND_VALUE 33048 #define _APS_NEXT_CONTROL_VALUE 2539 #define _APS_NEXT_SYMED_VALUE 105