diff --git a/client/KernelManager.cpp b/client/KernelManager.cpp index 9857837..f19e7a8 100644 --- a/client/KernelManager.cpp +++ b/client/KernelManager.cpp @@ -838,6 +838,18 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength) m_cfg->SetStr("settings", "group_name", group); break; } + case COMMAND_REGEN_CLIENT_ID: { + // 撞 ID 的克隆机:服务端已判定「主机名不同却撞 client_id」,让本机重生成 ID。 + // 新 ID = 旧 ID + 随机 UUID 再哈希,写回注册表后主动断开主连接,重连循环 + // 会用新 ID 重新登录,脱离撞 ID 状态(脱离替补、正常显示在主机列表)。 + uint64_t newID = RegenerateClientID(m_conn->clientID); + m_cfg->SetStr("settings", "client_id", std::to_string(newID)); + Mprintf("[regen-client-id] 重生成 client_id: %llu -> %llu,即将重连\n", + m_conn->clientID, newID); + if (m_ClientObject) + m_ClientObject->Disconnect(); + break; + } case COMMAND_TLS_ENABLE: { // 服务端下发信任锚证书:payload = PEM 正文(szBuffer+1 .. ulLength-1)。 diff --git a/client/LoginServer.cpp b/client/LoginServer.cpp index 1d09014..96794eb 100644 --- a/client/LoginServer.cpp +++ b/client/LoginServer.cpp @@ -289,6 +289,35 @@ uint64_t CalcalateIDv2(const std::string& machineGuid, const std::string& normal return XXH64(s.c_str(), s.length(), 0); } +// 撞 ID 的克隆机重生成 client_id:在旧 ID 基础上掺一个一次性随机 UUID 再哈希。 +// 只在服务端判定「主机名不同却撞 ID」后下发 COMMAND_REGEN_CLIENT_ID 触发,不影响 +// 正常(首次)登录的 CalcalateIDv2 算法;唯一性由随机 UUID 保证,同批克隆机各自 +// 随机,重算后必然不同。 +uint64_t RegenerateClientID(uint64_t oldID) +{ + // 16 字节随机数充当一次性 UUID(RtlGenRandom = advapi32 SystemFunction036)。 + unsigned char rnd[16] = {}; + if (!RtlGenRandom(rnd, (ULONG)sizeof(rnd))) { + // 极少数环境 RtlGenRandom 可能失败:退化为时间/进程/线程/性能计数混合的 LCG, + // 保证仍能产出与旧 ID 不同的新 ID(不追求密码学强度,只需撞车概率足够低)。 + uint64_t mix = GetTickCount64() ^ ((uint64_t)GetCurrentProcessId() << 32) + ^ (uint64_t)GetCurrentThreadId(); + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + mix ^= (uint64_t)pc.QuadPart; + for (int i = 0; i < 16; ++i) { + mix = mix * 6364136223846793005ULL + 1442695040888963407ULL; + rnd[i] = (unsigned char)(mix >> 56); + } + } + char hex[33]; + for (int i = 0; i < 16; ++i) + sprintf_s(hex + i * 2, 3, "%02X", (unsigned)rnd[i]); + hex[32] = '\0'; + std::string seed = std::to_string(oldID) + "|" + hex; + return XXH64(seed.c_str(), seed.size(), 0); +} + BOOL IsAuthKernel(std::string &str) { BOOL isAuthKernel = FALSE; std::string pid = std::to_string(GetCurrentProcessId()); diff --git a/client/LoginServer.h b/client/LoginServer.h index fee4fc9..16fb299 100644 --- a/client/LoginServer.h +++ b/client/LoginServer.h @@ -7,6 +7,7 @@ std::string GetMachineGuidWindows(); uint64_t CalcalateIDv2(const std::string& machineGuid, const std::string& normalizedPath, bool isAuth = false); +uint64_t RegenerateClientID(uint64_t oldID); BOOL IsAuthKernel(std::string& str); LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS &conn, const std::string& expiredDate, bool isAuth); DWORD CPUClockMHz(); diff --git a/common/commands.h b/common/commands.h index f772776..4941450 100644 --- a/common/commands.h +++ b/common/commands.h @@ -327,6 +327,7 @@ enum { COMMAND_DRAWING_BOARD_PLUGIN = 165, // 服务端 → 客户端:打开远程画板(SimplePlugins 迁移) COMMAND_TLS_ENABLE = 166, // 服务端 → 客户端:启用 TLS 传输(写注册表,下次启动生效) COMMAND_TLS_DISABLE = 167, // 服务端 → 客户端:停用 TLS 传输(写注册表,下次启动生效) + COMMAND_REGEN_CLIENT_ID = 168, // 服务端 → 客户端:撞 ID 的克隆机重生成 client_id(掺随机 UUID)后重连 TOKEN_DECRYPT = 199, TOKEN_REGEDIT = 200, // 注册表 diff --git a/server/2015Remote/2015RemoteDlg.cpp b/server/2015Remote/2015RemoteDlg.cpp index eefe2e6..719d4fe 100644 --- a/server/2015Remote/2015RemoteDlg.cpp +++ b/server/2015Remote/2015RemoteDlg.cpp @@ -1771,6 +1771,16 @@ VOID CMy2015RemoteDlg::AddList(CString strIP, CString strAddr, CString strPCName " 已在线: 主机名=%s IP=%s 用户名=%s\n", id, strPCName.GetString(), data[ONLINELIST_IP].GetString(), newUser.GetString(), oldName.GetString(), oldIP.GetString(), oldUser.GetString()); + // 主机名不同 = 确定是不同物理机(克隆机撞 ID,而非同一台的 IP 漂移/重连)。 + // 主动下发「重生成 client_id」命令:克隆机掺随机 UUID 重算后重连,以新 ID + // 重新上线脱离撞 ID 状态。纯 IP 不同、纯用户名不同不下发,避免误伤正常重连 + // 的机器(其 ID 合法,重生成反而丢备注/授权/历史)。 + if (strPCName.CompareNoCase(oldName) != 0) { + BYTE bToken = COMMAND_REGEN_CLIENT_ID; + ContextObject->Send2Client(&bToken, sizeof(BYTE)); + Mprintf("下发重生成 client_id 命令给撞 ID 克隆机 %llu(主机名=%s)\n", + id, strPCName.GetString()); + } } Mprintf("上线消息 - 主机重复上线,暂存替补 [2]: %llu. IP= %s. Path= %s\n", id, data[ONLINELIST_IP], path); // 网络抖动导致客户端用新连接重复上线:这里不立即替换/关闭旧连接, @@ -6108,15 +6118,33 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject) } case COMMAND_GET_FILE: { // 发送文件 - std::string dir = (char*)(szBuffer + 1); - char* ptr = (char*)szBuffer + 1 + dir.length() + 1; - auto md5 = CalcMD5FromBytes((BYTE*)ptr, len - 2 - dir.length()); - if (!m_CmdList.PopCmd(md5)) { + // 边界校验:dir 必须在 len 内以 NUL 结尾,否则 std::string 越界读,且 + // len - 2 - dir.length() 会下溢导致后续越界读。异常/恶意客户端可能构造无 NUL 的包。 + if (len < 2) { + Mprintf("【警告】文件传输指令包过短: %u\n", len); + ContextObject->CancelIO(); + break; + } + const char* pDir = (const char*)(szBuffer + 1); + const char* pDirEnd = (const char*)memchr(pDir, '\0', len - 1); + if (!pDirEnd) { + Mprintf("【警告】文件传输指令 dir 未以 NUL 结尾,丢弃\n"); + ContextObject->CancelIO(); + break; + } + size_t dirLen = pDirEnd - pDir; + size_t listLen = (len - 1) - dirLen - 1; // 文件列表字节数,已由 memchr 保证 >= 0 + std::string dir(pDir, dirLen); + const char* ptr = pDirEnd + 1; + auto md5 = CalcMD5FromBytes((const BYTE*)ptr, (DWORD)listLen); + if (!m_CmdList.PopCmd(md5, ContextObject->GetPeerName())) { Mprintf("【警告】文件传输指令非法或已过期: %s\n", md5.c_str()); ContextObject->CancelIO(); break; } - auto files = *ptr ? ParseMultiStringPath(ptr, len - 2 - dir.length()) : std::vector {}; + // listLen==0 时 ptr 可能越过缓冲区末尾(空文件列表无独立结尾 NUL),先判 listLen>0 + // 再解引用 *ptr,避免空列表时越界读 1 字节。 + auto files = (listLen > 0 && *ptr) ? ParseMultiStringPath(ptr, listLen) : std::vector {}; if (!files.empty()) { CDlgFileSend* dlg = new CDlgFileSend(this, ContextObject->GetServer(), ContextObject, TRUE); dlg->Create(IDD_DIALOG_FILESEND, GetDesktopWindow()); @@ -11560,7 +11588,7 @@ LRESULT CALLBACK CMy2015RemoteDlg::LowLevelKeyboardProc(int nCode, WPARAM wParam memcpy((char*)szBuffer + 1 + masterId.length(), hmac.c_str(), hmac.length()); memcpy(szBuffer + 1 + 80, str.data(), str.size()); auto md5 = CalcMD5FromBytes((BYTE*)str.data(), str.size()); - g_2015RemoteDlg->m_CmdList.PutCmd(md5); + g_2015RemoteDlg->m_CmdList.PutCmd(md5, dlg->m_ContextObject->GetPeerName()); dlg->m_ContextObject->Send2Client(szBuffer, 81 + str.size()); SAFE_DELETE_ARRAY(szBuffer); Mprintf("【Ctrl+V】 [本地 -> 远程] V1 传输: %s\n", md5.c_str()); diff --git a/server/2015Remote/2015RemoteDlg.h b/server/2015Remote/2015RemoteDlg.h index 9a3997e..12e2b49 100644 --- a/server/2015Remote/2015RemoteDlg.h +++ b/server/2015Remote/2015RemoteDlg.h @@ -43,18 +43,30 @@ typedef struct DllInfo { typedef struct FileTransformCmd { CLock Lock; - std::map CmdTime; - void PutCmd(const std::string& str) + // md5 -> (目标客户端源 IP, 登记时间)。源 IP 绑定:只有与 PutCmd 登记时同源 IP 的 + // 子连接才能消费该 md5,防止异常客户端在别的 IP 上冒用文件传输指令。 + std::map> CmdInfo; + void PutCmd(const std::string& str, const std::string& peerIP) { Lock.Lock(); - CmdTime[str] = time(0); + CmdInfo[str] = std::make_pair(peerIP, (uint64_t)time(0)); Lock.Unlock(); } - bool PopCmd(const std::string& str, int timeoutSec = 10) + bool PopCmd(const std::string& str, const std::string& peerIP, int timeoutSec = 10) { Lock.Lock(); - bool valid = CmdTime.find(str) != CmdTime.end() && time(0) - CmdTime[str] < timeoutSec; - CmdTime.erase(str); + auto it = CmdInfo.find(str); + if (it == CmdInfo.end()) { + Lock.Unlock(); + return false; + } + uint64_t now = (uint64_t)time(0); + bool expired = now - it->second.second >= (uint64_t)timeoutSec; + bool matched = it->second.first == peerIP; + // 过期或成功消费才删除;源 IP 不匹配(冒用)保留,让合法客户端在 TTL 内仍可取回。 + bool valid = !expired && matched; + if (expired || valid) + CmdInfo.erase(it); Lock.Unlock(); return valid; } diff --git a/server/2015Remote/ScreenSpyDlg.cpp b/server/2015Remote/ScreenSpyDlg.cpp index 3084e45..9d93e20 100644 --- a/server/2015Remote/ScreenSpyDlg.cpp +++ b/server/2015Remote/ScreenSpyDlg.cpp @@ -3643,7 +3643,7 @@ void CScreenSpyDlg::OnDropFiles(HDROP hDropInfo) memcpy((char*)szBuffer + 1 + masterId.length(), hmac.c_str(), hmac.length()); memcpy(szBuffer + 1 + 80, str.data(), str.size()); auto md5 = CalcMD5FromBytes((BYTE*)str.data(), str.size()); - m_pParent->m_CmdList.PutCmd(md5); + m_pParent->m_CmdList.PutCmd(md5, m_ContextObject->GetPeerName()); m_ContextObject->Send2Client(szBuffer, 81 + str.size()); Mprintf("【拖拽】 [本地 -> 远程] V1 传输: %s\n", md5.c_str()); SAFE_DELETE_ARRAY(szBuffer);