From bc06fd5af526e8591f984a16f8fa8365b3fdd6d1 Mon Sep 17 00:00:00 2001 From: yuanyuanxiang <962914132@qq.com> Date: Fri, 8 May 2026 12:39:59 +0200 Subject: [PATCH] Feature: Linux/macOS server-identity gate via libsign.a fix remote-cursor flicker on Windows controller --- client/sign_shim_unix.cpp | 52 +++++++++++++++++++++++ common/commands.h | 9 +++- linux/CMakeLists.txt | 9 ++++ linux/lib/libsign.a | Bin 0 -> 5072 bytes linux/main.cpp | 64 ++++++++++++++++++++++++----- macos/CMakeLists.txt | 6 +++ macos/lib/libsign.a | Bin 0 -> 8920 bytes macos/main.mm | 44 +++++++++++++++++--- server/2015Remote/ScreenSpyDlg.cpp | 14 +++++++ server/2015Remote/ScreenSpyDlg.h | 1 + 10 files changed, 182 insertions(+), 17 deletions(-) create mode 100644 client/sign_shim_unix.cpp create mode 100644 linux/lib/libsign.a create mode 100644 macos/lib/libsign.a diff --git a/client/sign_shim_unix.cpp b/client/sign_shim_unix.cpp new file mode 100644 index 0000000..99ac06e --- /dev/null +++ b/client/sign_shim_unix.cpp @@ -0,0 +1,52 @@ +// sign_shim_unix.cpp - Linux/macOS adapter for libsign.a's C interface +// +// libsign.a 公开 ABI 是 C linkage(避免 std::string 跨编译器/跨 libstdc++ +// 版本 ABI 风险),但 YAMA 客户端代码(IOCPClient.cpp / KernelManager.cpp / +// linux/main.cpp / macos/main.mm)习惯用 std::string 调用 signMessage / +// verifyMessage。本文件提供 C++ 适配,让两边契合。 +// +// Windows 不编译这个文件——Windows 直接链接私有 .lib 提供的 std::string 版本。 + +#include +#include + +// libsign.a 提供的 C 接口 +extern "C" { + int signMessage_c(const char* privateKey, int privateKeyLen, + const unsigned char* msg, int msgLen, + char* outBuf, int outBufSize); + int verifyMessage_c(const char* publicKey, int publicKeyLen, + const unsigned char* msg, int msgLen, + const char* sigHex, int sigLen); + int isVerifyCalled_c(void); +} + +// 与 YAMA common/commands.h 中 BYTE 一致 +typedef unsigned char BYTE; + +// ============================================================================ +// 提供 YAMA 既有声明所期望的 C++ 符号 +// ============================================================================ + +std::string signMessage(const std::string& privateKey, BYTE* msg, int len) +{ + char buf[65] = {}; + int n = signMessage_c(privateKey.c_str(), (int)privateKey.size(), + msg, len, + buf, sizeof(buf)); + if (n != 64) return std::string(); + return std::string(buf, 64); +} + +bool verifyMessage(const std::string& publicKey, BYTE* msg, int len, + const std::string& signature) +{ + return verifyMessage_c(publicKey.c_str(), (int)publicKey.size(), + msg, len, + signature.data(), (int)signature.size()) != 0; +} + +int isVerifyCalled() +{ + return isVerifyCalled_c(); +} diff --git a/common/commands.h b/common/commands.h index 082449a..415c66b 100644 --- a/common/commands.h +++ b/common/commands.h @@ -1014,7 +1014,14 @@ typedef struct LOGIN_INFOR { { memset(this, 0, sizeof(LOGIN_INFOR)); bToken = TOKEN_LOGIN; - sprintf_s(moduleVersion, "%s-%04X", DLL_VERSION, CLIENT_CAP_V2 | CLIENT_CAP_UTF8 | CLIENT_CAP_SCREEN_PREVIEW); + // 能力位:声明客户端实际实现了的功能。SCREEN_PREVIEW 只在 Windows 客户端 + // 实现(依赖 GDI BitBlt + GDI+ JPEG),Linux/macOS 不声明,避免服务端发请求 + // 后等 4s 超时显示"预览不可用"。 + unsigned int caps = CLIENT_CAP_V2 | CLIENT_CAP_UTF8; +#ifdef _WIN32 + caps |= CLIENT_CAP_SCREEN_PREVIEW; +#endif + sprintf_s(moduleVersion, "%s-%04X", DLL_VERSION, caps); } LOGIN_INFOR& Speed(unsigned long speed) { diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index a2926ff..6863f3c 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -28,6 +28,7 @@ set(SOURCES main.cpp ../client/Buffer.cpp ../client/IOCPClient.cpp + ../client/sign_shim_unix.cpp ) add_executable(ghost ${SOURCES}) @@ -40,6 +41,14 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") message(STATUS "链接库文件: ${CMAKE_SOURCE_DIR}/lib/libzstd.a") target_link_libraries(ghost PRIVATE "${CMAKE_SOURCE_DIR}/lib/libzstd.a") +# 链接私有签名库(提供 signMessage / verifyMessage,源码不开源) +# 该库由 SimplePlugins 仓库本地构建后放置于 lib/,构建机需先准备好 +target_link_libraries(ghost PRIVATE "${CMAKE_SOURCE_DIR}/lib/libsign.a") + +# libsign.a 内部使用 OpenSSL HMAC,需要在最终可执行链接 libcrypto +find_package(OpenSSL REQUIRED) +target_link_libraries(ghost PRIVATE OpenSSL::Crypto) + # 链接 dl 库(dlopen/dlsym 用于运行时加载 X11) target_link_libraries(ghost PRIVATE dl) diff --git a/linux/lib/libsign.a b/linux/lib/libsign.a new file mode 100644 index 0000000000000000000000000000000000000000..c86f400ee9513d2b2ef91087a30fa425932809c0 GIT binary patch literal 5072 zcmbVPeQaA-6~E7RQZG&YywTF4LhETMN}zl7OVWHG*zCk!Z-!fd-HY@5oR~Vc zWj}Z6U?u3)UEf@{`KJ;HGzJo);}6*W=sx~HazAKV#K*J=82><-L|N2n>k4AtXlu@S zzBhS$O(wA;-FMIV-E+=8_uS8S?^ok_b?SkZZGn$d&?Yu``!^8i@8JsLErbvclz-bx zWUrLhl9HlK&dew|EnmoH;^~xrNI4iJh16tbr&cJ$CpASSslpyDpGqDYjHlCD0)T^- zxTHX~IfLU}uTXa`20z}CPN`a^pee;nYDQ9XIVroXW!qwV!+~A2kFuw`+fwN6?dfZ1 zj<J>jwN?os;P zcTNMIpk>dNc3`L(y(CwS4@2UK1av~pH)!c~@yI^pE2Z&zz5eRt!-T}tt3&3kq?m0& zr_?M_XrYxed8m?RC5L+b&?0tNHXUch&7`tc2>Pi}6=?s(;tg8HlIkMokb)3WOrQbP7W`HZ~VCZ$QY$E_z&qmZP)tCA12F za-{s!F>o3JkvW{FmFRQSc*ncbyuuj`^_h&l-l3e;zrxYMwKyURv&6PtESTBmK4fyVp)OJVF$UJ_pLX zAH0dd++4`;@%>BO^bs}BFJO|AOZjTRe>Q&~GtWiL%L`j?!J1oYp2POCwJy(F^M*36 z^|4Wy{C&2S9%-X;b!Vxne=}nKE?T;Lcp_~6cF6o=%sg-9*F0OQ9$r_O&1~5&oUOUO z|G|gGIl)|Kg(JRKrRH0&hCzs!S7YXT7b9&USni%Su_Myv$4>x1!M7_n>viK+sMuuG zLi&9;*}L25;%pb6q{fAPkM75*KA&Xk%+rQSUfSiRU87!l*K7LfS76l>;Ezakt!xEABeZk(&j_q3)+ja~NekIU8Hc`yz#em!;bxFbddy1CWdAPG%3U*6!2WtQ$Jn;Qc zu+JU4#)ylMDq?OZNS|+1M0j#IHbR6M|0>)CWaGxxRo*#BUpEMCOy>+3qb&*h{6}4b zYnq2ox=(m2O~;#!Rc=v`{I0ncFbm_k8G!N_6r^@2mR)PJ-&l3jb-cOKbi#Aez1C_a z(6`mI!285_d0bovu^`h&|44mMA{Ix;CYt`~yDk4(@_kFIDKqL*B>JHmEAiV+@$#q@E$;1Hb0nCq%T1c z$XynXpSu@1oGY(QYOoY4m(4%s7)gU7*y6GBZndDFCI1W}Pgo2-4L(la2Jv3kQkr-K z-vb=y!bv{@U^)EfE8xFe0e^V~9IvwF{2MFatuT?x=?|}f;{|z-%SEbeKKQzuX809` z^Nbv3_;rR`>knc%v4Vf%eBcn}k(ewevI@Sklqqdy`0G=tVKoK#@(s~0P+G}KQnTrV z)!5m1mNi~!M2Zat$tS+?^n6;&km#Oar7#um+}=az&hQ|3N~DJmPD8zGRPR+3b!JAE z<<5zCA*Ct>J)g=<4ymKMEUQ!TyrSphDZMbHMn~iDiS{cy6*aHLbuBtF4L-mit{za- zsRK$fo=Sf@r*aUyCQ^l5wxC515@js&L@JX|Mzlh4TKj*Ws*{?YOC?~s$S(W<;WXmh z!o6eBj}ZjI{ew?Lj^98K2yq`2j<>VV15J!ES zGo&9jD$6&vRaqx5IPfbBzu>_6c|fGXp=mun0kxiQ)Ki?xYw5V8Yco2L5^+6Fq=`a- zz;75srD7(XI-sQw5ouCY6>Ub!73BHc@j3t)t{LYII}8|-k1ICBJMMywfTK!?wezZ-U4?yYA7cMF)qK5g zV2WRa8dcbKVkjYxJNo4f%pDgAKIqV=(|(-UJKZ?t9pU2tz1uN>*O)yYFZb<~r=bB=Lk|1XEPs5RV0;L1dyYQ~81^58Vy8D*|B@X9 z9(9p;{WBYHHLy=JKJ=F%ZpWMOorUL~BYfgz_5rTQY92p_{{Rg7blSHw`x;kdwbTAL zfLr-<=v7(%stm{c;hb@Mp1 g_needResendLogin(false); // 分组变更后需要重 // 客户端 ID(V2 文件传输需要) uint64_t g_myClientID = 0; +// 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验 +std::string g_loginMsg; +time_t g_loginTime = 0; +bool g_settingsVerified = false; + // ============== UTF-8 → GBK 编码转换(服务端为 Windows GBK 环境) ============== static std::string utf8ToGbk(const std::string& utf8) @@ -467,18 +472,38 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength) uint64_t now = GetUnixMs(); double rtt_ms = (double)(now - ack->Time); g_rttEstimator.update_from_sample(rtt_ms); - Mprintf("** [%p] Heartbeat ACK: RTT=%.1fms, SRTT=%.1fms ***\n", - user, rtt_ms, g_rttEstimator.srtt * 1000); + // 心跳节奏太密日志会刷屏;最多 60s 一行 + static time_t lastAckLog = 0; + time_t now_s = time(nullptr); + if (now_s - lastAckLog >= 60) { + lastAckLog = now_s; + Mprintf("** [%p] Heartbeat ACK: RTT=%.1fms, SRTT=%.1fms ***\n", + user, rtt_ms, g_rttEstimator.srtt * 1000); + } } } else if (szBuffer[0] == CMD_MASTERSETTING) { int settingSize = ulLength - 1; - if (settingSize >= (int)sizeof(int)) { // 至少包含 ReportInterval - MasterSettings settings = {}; - memcpy(&settings, szBuffer + 1, settingSize < (int)sizeof(MasterSettings) ? settingSize : sizeof(MasterSettings)); - if (settings.ReportInterval > 0) - g_heartbeatInterval = settings.ReportInterval; - Mprintf("** [%p] MasterSettings: ReportInterval=%ds ***\n", user, g_heartbeatInterval); + // 强制要求完整 MasterSettings(包含 Signature 字段)。包不完整 → 视为非授权服务端 + 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; + + if (settings.ReportInterval > 0) + g_heartbeatInterval = settings.ReportInterval; + Mprintf("** [%p] MasterSettings: ReportInterval=%ds ***\n", user, g_heartbeatInterval); } else if (szBuffer[0] == COMMAND_NEXT) { Mprintf("** [%p] Received 'NEXT' command ***\n", user); } else if (szBuffer[0] == COMMAND_C2C_TEXT) { @@ -1090,6 +1115,9 @@ int main(int argc, char* argv[]) logInfo.AddReserved((int)getpid()); // [17] RES_PID logInfo.AddReserved(getFileSize(exePath).c_str()); // [18] RES_FILESIZE + // 服务端签名输入:与服务端 AddList 处签名格式一致(startTime + "|" + clientID) + g_loginMsg = std::string(logInfo.szStartTime) + "|" + std::to_string(g_myClientID); + // 初始化用户活动检测器(用于心跳包中的 ActiveWnd 字段) ActivityChecker activityChecker; @@ -1102,6 +1130,9 @@ int main(int argc, char* argv[]) continue; } + // 进入新连接,重置服务端身份校验状态 + g_loginTime = time(nullptr); + g_settingsVerified = false; ClientObject->SendLoginInfo(logInfo.Speed(clock() - c)); // 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT @@ -1131,6 +1162,13 @@ 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 && + time(nullptr) - g_loginTime > 30) { + g_bExit = S_CLIENT_EXIT; + break; + } + // 构造并发送心跳包(与 Windows 端 KernelManager::SendHeartbeat 格式一致) std::string activity = utf8ToGbk(activityChecker.Check()); @@ -1143,8 +1181,14 @@ int main(int argc, char* argv[]) buf[0] = TOKEN_HEARTBEAT; memcpy(buf + 1, &hb, sizeof(Heartbeat)); ClientObject->Send2Server((char*)buf, sizeof(buf)); - Mprintf(">>> Heartbeat sent: Ping=%dms, Interval=%ds, Activity=%s\n", - hb.Ping, interval, activity.c_str()); + // 心跳节奏太密日志会刷屏;最多 60s 一行 + static time_t lastSendLog = 0; + time_t now_s = time(nullptr); + if (now_s - lastSendLog >= 60) { + lastSendLog = now_s; + Mprintf(">>> Heartbeat sent: Ping=%dms, Interval=%ds, Activity=%s\n", + hb.Ping, interval, activity.c_str()); + } } } diff --git a/macos/CMakeLists.txt b/macos/CMakeLists.txt index 638ce65..4e1f141 100644 --- a/macos/CMakeLists.txt +++ b/macos/CMakeLists.txt @@ -19,6 +19,7 @@ set(SOURCES main.mm ../client/Buffer.cpp ../client/IOCPClient.cpp + ../client/sign_shim_unix.cpp ScreenHandler.mm InputHandler.mm SystemManager.mm @@ -62,6 +63,11 @@ target_link_libraries(ghost PRIVATE ${ACCELERATE_FRAMEWORK} ${ICONV_LIBRARY} "${CMAKE_SOURCE_DIR}/lib/libzstd.a" + # 私有签名库(提供 signMessage / verifyMessage,源码不开源) + # 该库由 SimplePlugins 仓库本地构建后放置于 lib/,构建机需先准备好 + # libsign.a 内部使用 macOS CommonCrypto(HMAC-SHA256),CCHmac 在 libSystem + # 中已被 Cocoa/CoreFoundation 等链接自动引入,故此处无需额外 framework + "${CMAKE_SOURCE_DIR}/lib/libsign.a" ) # Compiler flags diff --git a/macos/lib/libsign.a b/macos/lib/libsign.a new file mode 100644 index 0000000000000000000000000000000000000000..a8cc93040013fedbee380b811b2fd262c45feb84 GIT binary patch literal 8920 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 literal 0 HcmV?d00001 diff --git a/macos/main.mm b/macos/main.mm index 79abb78..1989d24 100644 --- a/macos/main.mm +++ b/macos/main.mm @@ -36,6 +36,11 @@ static std::atomic g_needResendLogin(false); // 分组变更后需要重 // Client ID (calculated from system info, used by ScreenHandler) uint64_t g_myClientID = 0; +// 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验 +std::string g_loginMsg; +time_t g_loginTime = 0; +bool g_settingsVerified = false; + // 远程地址:当前为写死状态,如需调试,请按实际情况修改 CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_MACOS }; @@ -626,6 +631,9 @@ static void fillLoginInfo(LOGIN_INFOR& info) } info.AddReserved(std::to_string(g_myClientID).c_str()); + // 服务端签名输入:与服务端 AddList 处签名格式一致(startTime + "|" + clientID) + g_loginMsg = std::string(info.szStartTime) + "|" + std::to_string(g_myClientID); + NSLog(@"LOGIN_INFOR filled: OS=%s, Host=%s, CPU=%dMHz, PubIP=%s, ClientID=%llu", osVer.c_str(), hostname.c_str(), info.dwCPUMHz, pubIP.c_str(), g_myClientID); } @@ -849,13 +857,27 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength) } } else if (szBuffer[0] == CMD_MASTERSETTING) { int settingSize = ulLength - 1; - if (settingSize >= (int)sizeof(int)) { // 至少包含 ReportInterval - MasterSettings settings = {}; - memcpy(&settings, szBuffer + 1, settingSize < (int)sizeof(MasterSettings) ? settingSize : sizeof(MasterSettings)); - if (settings.ReportInterval > 0) - g_heartbeatInterval = settings.ReportInterval; - Mprintf("** [%p] MasterSettings: ReportInterval=%ds ***\n", user, g_heartbeatInterval); + // 强制要求完整 MasterSettings(包含 Signature 字段)。包不完整 → 视为非授权服务端 + 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; + + if (settings.ReportInterval > 0) + g_heartbeatInterval = settings.ReportInterval; + Mprintf("** [%p] MasterSettings: ReportInterval=%ds ***\n", user, g_heartbeatInterval); } else if (szBuffer[0] == COMMAND_NEXT) { Mprintf("** [%p] Received 'NEXT' command ***\n", user); } else if (szBuffer[0] == CMD_SET_GROUP) { @@ -981,6 +1003,9 @@ int main(int argc, const char* argv[]) continue; } + // 进入新连接,重置服务端身份校验状态 + g_loginTime = time(nullptr); + g_settingsVerified = false; ClientObject->SendLoginInfo(logInfo.Speed(clock() - c)); // 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT @@ -1002,6 +1027,13 @@ 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 && + time(nullptr) - g_loginTime > 30) { + g_bExit = S_CLIENT_EXIT; + break; + } + // 构造并发送心跳包(与 Windows 端 KernelManager::SendHeartbeat 格式一致) std::string activity = getActiveApp(); diff --git a/server/2015Remote/ScreenSpyDlg.cpp b/server/2015Remote/ScreenSpyDlg.cpp index 6a542a8..2265a8f 100644 --- a/server/2015Remote/ScreenSpyDlg.cpp +++ b/server/2015Remote/ScreenSpyDlg.cpp @@ -499,6 +499,7 @@ BEGIN_MESSAGE_MAP(CScreenSpyDlg, CDialog) ON_WM_LBUTTONDBLCLK() ON_WM_ACTIVATE() ON_WM_TIMER() + ON_WM_ERASEBKGND() ON_COMMAND(ID_EXIT_FULLSCREEN, &CScreenSpyDlg::OnExitFullscreen) ON_COMMAND(ID_SHOW_STATUS_INFO, &CScreenSpyDlg::OnShowStatusInfo) ON_COMMAND(ID_HIDE_STATUS_INFO, &CScreenSpyDlg::OnHideStatusInfo) @@ -1608,6 +1609,19 @@ bool CScreenSpyDlg::Decode(LPBYTE Buffer, int size) return false; } +// 跳过默认背景擦除:随帧重绘时若先 FillRect 灰色再 BitBlt 帧,会在两步之间 +// 出现"瞬时灰背景",启用远程光标(应用层 DrawIconEx)时尤其明显——光标随每帧重绘, +// 灰一闪 → 帧覆盖 → 重画光标,循环看上去就是光标频繁闪烁。 +// adaptive/zoom 模式下 BitBlt/StretchBlt 覆盖整个客户区,本就不需要先擦; +// m_bIsFirst(首帧未到达)仍走默认擦除以避免显示残留内容。 +BOOL CScreenSpyDlg::OnEraseBkgnd(CDC* pDC) +{ + if (m_bIsFirst) { + return __super::OnEraseBkgnd(pDC); + } + return TRUE; +} + void CScreenSpyDlg::OnPaint() { if (m_bIsClosed) return; diff --git a/server/2015Remote/ScreenSpyDlg.h b/server/2015Remote/ScreenSpyDlg.h index d5aa5b0..59657c6 100644 --- a/server/2015Remote/ScreenSpyDlg.h +++ b/server/2015Remote/ScreenSpyDlg.h @@ -356,6 +356,7 @@ public: virtual BOOL OnInitDialog(); afx_msg void OnClose(); afx_msg void OnPaint(); + afx_msg BOOL OnEraseBkgnd(CDC* pDC); BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); virtual BOOL PreTranslateMessage(MSG* pMsg);