Winlogon会话劫持远程控制原型解析

发布时间:2026/10/28 12:57:11
Winlogon会话劫持远程控制原型解析

简介:本资源是一套基于Visual C++开发的远程控制软件完整源码工程,面向C++中级开发者及Windows系统编程学习者,聚焦远程桌面控制、系统信息获取与输入事件模拟等核心功能实现。压缩包共30个文件,包含10个头文件(.h)定义类结构与接口,8个源文件(.cpp)实现Winlogon会话管理、GetInfo系统信息采集、屏幕捕获与网络通信等关键逻辑,辅以资源文件(.ico、.bmp、.rc)和项目配置文件(.dsp、.dsw),整体仅37KB,轻量但结构完整。已有687人学习下载,适合深入理解VC++结合Windows API开发远程控制工具的技术路径。读者可直接编译运行两个子模块:WINLOGON(负责登录会话控制与权限交互)和GetInfo(实现远程主机硬件与系统状态采集),掌握Socket网络通信、GDI图像捕获、键盘鼠标事件转发及基础安全机制等实战要点。

1. 这不是“远程桌面精简版”,而是一套基于 Winlogon 会话劫持与 GDI 屏幕捕获的 VC++ 原生远程控制骨架:它能绕过 UAC 提权、实时捕获登录界面、在无用户交互状态下接管桌面——但必须用 Visual Studio 2008 或更低版本编译,且默认不带加密、无服务自启、无反调试,是典型“教学级可运行原型”,适合逆向学习 Windows 会话模型与远程控制底层通信链路

你拿到的这个visual c++ vc编写远程控制软件源码.zip,表面看是老派 VC6/VS2003 风格的 MFC 工程,但内核远比“远程桌面简化版”更硬核:它没有依赖第三方库(如 Qt 或 libuv),全部用原生 Win32 API 实现;WINLOGON模块不是名字唬人——它真在 hook Winlogon.exe 的窗口消息循环,用于捕获锁屏/登录态下的桌面图像;GetInfo子工程也不是泛泛的信息采集器,它通过WMI+Registry+PSAPI三路并行读取远程机 CPU 温度(需管理员权限)、进程列表、已安装服务、共享文件夹列表,甚至尝试枚举当前登录用户的 RDP 会话 ID。这不是给小白练手的“发个字符串弹个 MessageBox”Demo,而是当年真实用于内网运维工具链的早期原型——它能跑,但跑得磕绊;它能控,但控得裸露;它没加壳、没混淆、没 TLS 加密,所有网络通信明文走 TCP,所有命令结构体裸奔在内存里。适合两类人:一是想搞懂 Windows 登录会话隔离机制(WinStation、WTS、Session 0 vs Session 1)的驱动/安全方向学习者;二是需要快速复现一个“最小可行远程控制通道”的嵌入式或工控场景开发者。如果你期待开箱即用的 TeamViewer 替代品,请立刻停止——它连密码框都不渲染,更不支持多显示器缩放。但如果你正卡在“为什么我的远程截图在锁屏时全黑”“为什么 SendInput 在远程会话里失效”这类问题上,这份源码就是你该拆的第一块砖。


2. 从工程结构到通信协议:解析 WINLOGON 与 GetInfo 双模块协同机制及 TCP 指令帧设计

2.1 工程组织逻辑:两个独立 MFC 应用构成 C/S 架构雏形

整个压缩包实际包含两个完整可编译的 MFC 工程:

  • WINLOGON:服务端(Server),目标平台为 Windows XP/7(NT 5.1–6.1),核心功能是:

    • 创建隐藏服务进程(非 svchost 托管,而是直接 CreateService 启动)
    • HookWinlogon.exe的DefWindowProc,监听WM_WTSSESSION_CHANGE和WM_HOTKEY消息
    • 使用BitBlt+CreateCompatibleBitmap在 Session 0 中捕获当前活动会话桌面(关键:调用WTSGetActiveConsoleSessionId()+WTSQueryUserToken()获取目标会话 token)
    • 开启 TCP 监听端口(默认6666),接收客户端指令并返回屏幕帧(RGB24 原始数据,未压缩)
  • GetInfo:客户端(Client),目标平台同上,核心功能是:

    • 连接WINLOGON服务端 IP:6666
    • 发送预定义指令码(如0x01获取系统信息、0x02请求屏幕帧、0x03模拟鼠标点击)
    • 解析二进制响应包(结构体对齐为#pragma pack(1),含DWORD cmd,DWORD len,BYTE data[])
    • 本地 UI 显示远程桌面(使用SetDIBitsToDevice渲染 RGB24 数据)

提示:两个工程均未使用 ATL 或 WTL,纯 MFC Dialog-based Application;资源文件(.rc,.ico,.bmp)全部内嵌,无外部 DLL 依赖(除系统user32.dll,gdi32.dll,wininet.dll外);StdAfx.h中仅包含<afxwin.h><winsock2.h><wtsapi32.h><psapi.h>—— 这是刻意为之的轻量化设计,也是后续编译失败的根源之一。

2.2 关键通信协议:TCP 指令帧格式与状态机实现

所有远程控制指令均通过 TCP socket 以二进制帧传输,帧结构定义在GetInfo.h和WINLOGON.h共用头文件中:

#pragma pack(1) struct RemoteCommand { DWORD cmd; // 指令码:0x01=GetSysInfo, 0x02=GetScreen, 0x03=SendMouse, 0x04=SendKey DWORD len; // data 长度(字节),0 表示无 payload BYTE data[1]; // 可变长负载,如鼠标坐标(x,y)或键盘虚拟键码(VK_CODE) }; #pragma pack()

服务端WINLOGON的消息循环中,OnReceive()函数处理如下:

// WINLOGONView.cpp 中片段 void CWINLOGONView::OnReceive(int nErrorCode) { char buffer[4096]; int nBytes = m_socket.Receive(buffer, sizeof(buffer)); if (nBytes < sizeof(DWORD)*2) return; // 至少 cmd+len RemoteCommand* pCmd = (RemoteCommand*)buffer; switch(pCmd->cmd) { case 0x01: SendSysInfo(); // 调用 EnumProcesses + RegOpenKeyEx + WMIExecQuery break; case 0x02: CaptureAndSendScreen(); // BitBlt → RGB24 → send break; case 0x03: SimulateMouse(pCmd->data); // 使用 mouse_event(),非 SendInput() break; default: SendError(0xFF); // 未知指令 } }

参数说明:CaptureAndSendScreen()中关键参数m_hDesktopDC必须通过GetDC(NULL)获取全局 DC,而非GetDC(m_hWnd)—— 否则锁屏时返回黑屏;SimulateMouse()接收data[0](X 坐标低字节)、data[1](X 高字节)、data[2](Y 低字节)、data[3](Y 高字节),按MAKELONG(x,y)组合后调用mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, x, y, 0, 0)。注意:此方式在 Windows 10+ 默认被禁用,需关闭Enhanced Protected Mode或启用UIAccess权限。

2.3 屏幕捕获实现细节:GDI 双缓冲与会话上下文切换

WINLOGON的屏幕捕获并非简单BitBlt,而是分三步确保跨会话有效性:

  1. 获取目标会话句柄

    DWORD dwSessionId = WTSGetActiveConsoleSessionId(); HANDLE hToken; if (WTSQueryUserToken(dwSessionId, &hToken)) { // 切换到目标会话上下文 ImpersonateLoggedOnUser(hToken); }
  2. 创建兼容设备上下文

    HDC hScreenDC = GetDC(NULL); // 全局屏幕 DC HDC hMemDC = CreateCompatibleDC(hScreenDC); HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height); SelectObject(hMemDC, hBitmap);
  3. 执行位块传输并编码

    BitBlt(hMemDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY); // 获取位图数据(RGB24) BITMAPINFO bmi = {0}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -height; // top-down DIB bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 24; bmi.bmiHeader.biCompression = BI_RGB; BYTE* pBits; GetDIBits(hMemDC, hBitmap, 0, height, NULL, &bmi, DIB_RGB_COLORS); GetDIBits(hMemDC, hBitmap, 0, height, pBits, &bmi, DIB_RGB_COLORS); // 发送 pBits(注意:width 必须是 4 的倍数,否则需补零)

逻辑说明:WTSQueryUserToken()是绕过 Session 0 隔离的关键,它允许服务进程获取当前交互式用户的 token;ImpersonateLoggedOnUser()切换线程上下文,使后续 GDI 调用在目标用户会话中执行;GetDIBits()返回的pBits是 BGR 顺序(Windows GDI 默认),客户端需在渲染前转换为 RGB 或直接用SetDIBitsToDevice指定BI_BGR格式。


3. 编译与运行环境:Visual Studio 版本锁定、运行库依赖及 Windows 会话权限配置

3.1 必须使用 Visual Studio 2008(VC9)或更早版本编译

该源码基于 MFC 7.1(VS2003)和 MFC 8.0(VS2005)构建,WINLOGON.dsp和GetInfo.dsp是经典.dsp工程文件(非.vcproj),其内部硬编码了以下路径与库:

  • ToolsVersion="7.0"(对应 VS2003)
  • IncludePath="C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\atlmfc\include"
  • 链接器依赖mfc71.lib,mfcs71.lib,msvcr71.dll

若强行用 VS2015+ 打开.dsw文件,将遭遇:

  • fatal error C1083: Cannot open include file: 'afxwin.h': No such file or directory(新版本 MFC 路径变更)
  • LNK2001: unresolved external symbol __imp__WTSQueryUserToken@8(wtsapi32.lib未自动链接)
  • C2664: 'CreateServiceA' : cannot convert parameter 6 from 'LPCTSTR' to 'LPCSTR'(Unicode/ANSI 混淆)

正确做法:

  1. 安装 Visual Studio 2008 Express Edition (免费存档版)
  2. 安装Microsoft Visual C++ 2008 SP1 Redistributable (x86)(运行时必备)
  3. 在 VS2008 中打开WINLOGON.dsw→ 右键WINLOGON工程 → Properties → Configuration Properties → General → Platform Toolset → 选择v90
  4. Linker → Input → Additional Dependencies 添加wtsapi32.lib psapi.lib wininet.lib

3.2 运行时强制依赖:Microsoft Visual C++ 2008 Redistributable

编译生成的WINLOGON.exe和GetInfo.exe依赖以下 DLL(必须存在于System32或同目录):

DLL 名称作用是否可静态链接
msvcr90.dllVC9 CRT 运行时❌ 不推荐(增大体积且违反微软 EULA)
mfc90.dllMFC 类库❌ 必须动态加载
wtsapi32.dllWindows Terminal Services API✅ 系统自带(XP SP3+)
psapi.dll进程信息查询✅ 系统自带

验证方法:用Dependency Walker(depends.exe)打开WINLOGON.exe,检查msvcr90.dll是否标红。若红,则目标机未安装 VC9 运行库 —— 此时双击程序会弹窗:“由于找不到 msvcr90.dll,无法执行代码”。

3.3 Windows 权限与服务配置:UAC、服务账户与会话访问策略

WINLOGON作为服务运行时,必须满足三项策略:

  1. 服务登录账户必须为 LocalSystem
    在WINLOGON.cpp中,CreateService()调用指定lpServiceStartName = NULL(即 LocalSystem),否则WTSQueryUserToken()返回ERROR_ACCESS_DENIED。

  2. 关闭 UAC 的“Admin Approval Mode”(仅限测试)
    组策略路径:计算机配置 → Windows 设置 → 安全设置 → 本地策略 → 安全选项 → 用户账户控制: 管理员批准模式→ 设为已禁用。否则服务进程无法获取交互式会话 token。

  3. 启用“允许服务与桌面交互”(危险!仅调试用)
    服务属性 → 登录 → 勾选“允许服务与桌面交互”→ 重启服务。此选项在 Windows 7+ 默认禁用,且启用后存在严重安全风险(服务可直接操作用户桌面)。

生产环境替代方案:改用CreateProcessAsUser()启动一个位于 Session 1 的辅助进程(如WINLOGON_Helper.exe),由该进程执行BitBlt,再通过命名管道将图像传回服务进程 —— 但源码中未实现此模式。


4. 避坑指南:五个血泪经验总结——从黑屏、断连到提权失败的真实翻车现场

4.1 现象:客户端连接成功,但0x02指令返回全黑图像(RGB 值全为 0)

原因:服务端未正确切换到目标会话上下文,BitBlt操作在 Session 0 执行,而 Session 0 无图形界面(Windows Vista+ 默认)。WTSGetActiveConsoleSessionId()返回0xFFFFFFFF(无效会话 ID),导致WTSQueryUserToken()失败,后续GetDC(NULL)获取的是空桌面 DC。
解决:在CaptureAndSendScreen()开头添加会话有效性校验:

DWORD dwSessionId = WTSGetActiveConsoleSessionId(); if (dwSessionId == 0xFFFFFFFF) { // 尝试枚举所有会话,找第一个 Active 的 DWORD count; WTS_SESSION_INFO* pSessions; if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessions, &count)) { for (DWORD i = 0; i < count; i++) { if (pSessions[i].State == WTSActive) { dwSessionId = pSessions[i].SessionId; break; } } WTSFreeMemory(pSessions); } }

4.2 现象:GetInfo.exe连接后立即断开,服务端日志显示WSARecv failed: 10054

原因:客户端发送指令帧时未严格按#pragma pack(1)对齐,结构体RemoteCommand在 VS2008 编译下因字节对齐差异导致len字段读取错位,服务端解析出超大len值,触发recv()超时或缓冲区溢出保护。
解决:在GetInfo.cpp发送前强制重置结构体内存,并显式赋值:

RemoteCommand cmd = {0}; cmd.cmd = 0x02; cmd.len = 0; send(m_socket.m_hSocket, (char*)&cmd, sizeof(cmd), 0);

切勿使用memset(&cmd, 0, sizeof(cmd))后只赋cmd.cmd——cmd.len可能残留垃圾值。

4.3 现象:鼠标模拟失效(远程光标不动),但键盘输入正常

原因:mouse_event()在 Windows 8.1+ 默认被UI Access限制,即使服务以 LocalSystem 运行,若可执行文件未签名或未声明uiAccess="true",API 调用静默失败。
解决:

  • 方案 A(推荐):改用SendInput(),需先调用AttachThreadInput()获取目标线程输入队列:
    DWORD dwTargetTid = GetWindowThreadProcessId(FindWindow(_T("Shell_TrayWnd"), NULL), NULL); AttachThreadInput(GetCurrentThreadId(), dwTargetTid, TRUE); INPUT input = {0}; input.type = INPUT_MOUSE; ... SendInput(1, &input, sizeof(INPUT)); AttachThreadInput(GetCurrentThreadId(), dwTargetTid, FALSE);
  • 方案 B(临时):在服务可执行文件 manifest 中添加<uiAccess>true</uiAccess>并用微软证书签名(不现实)。

4.4 现象:GetInfo获取系统信息时卡死,CPU 占用 100%

原因:WMIExecQuery()在GetInfo.cpp中使用同步IWbemServices::ExecQuery(),未设置WBEM_FLAG_RETURN_IMMEDIATELY,且未实现IWbemObjectSink异步回调,导致线程阻塞在 WMI 查询上。
解决:改用异步查询模板(需重写GetSysInfo()函数),或降级为注册表读取(快速但信息有限):

// 替代 WMI 获取 CPU 型号(注册表路径) HKEY hKey; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) { TCHAR szBuffer[256]; DWORD dwSize = sizeof(szBuffer); RegQueryValueEx(hKey, _T("ProcessorNameString"), NULL, NULL, (LPBYTE)szBuffer, &dwSize); // ... 处理 szBuffer RegCloseKey(hKey); }

4.5 现象:服务安装成功,但启动时报错Error 1053: The service did not respond to the start or control request in a timely fashion

原因:WINLOGON服务主函数ServiceMain()中,StartServiceCtrlDispatcher()注册的服务控制处理器未及时响应SERVICE_CONTROL_INTERROGATE,或ServiceControlHandler()内部耗时操作(如WTSQueryUserToken())阻塞主线程。
解决:

  • 将WTSQueryUserToken()等耗时操作移至工作线程(_beginthreadex)
  • ServiceControlHandler()中仅处理SERVICE_CONTROL_STOP和SERVICE_CONTROL_INTERROGATE,立即返回
  • ServiceMain()开头调用SetServiceStatus()将dwCurrentState设为SERVICE_START_PENDING,并在工作线程就绪后设为SERVICE_RUNNING

5. 安全加固与功能扩展:为原始骨架注入 AES 加密、服务自启与多会话支持

5.1 通信层加密:在 TCP 帧上叠加 AES-128-CBC(无需 OpenSSL,用 Windows CryptoAPI)

原始源码网络通信完全明文,极易被 Wireshark 抓包分析。我们可在RemoteCommand结构体外层封装加密载荷,利用 Windows 自带CryptAcquireContext实现:

服务端加密流程(WINLOGONView.cpp):

#include <wincrypt.h> #pragma comment(lib, "crypt32.lib") BOOL EncryptPayload(BYTE* pData, DWORD dwLen, BYTE* pEncrypted, DWORD* pdwEncryptedLen) { HCRYPTPROV hProv; HCRYPTKEY hKey; HCRYPTHASH hHash; if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) return FALSE; // 生成 128-bit 密钥(实际应从配置文件读取) BYTE keyData[16] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08, 0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10}; if (!CryptImportKey(hProv, keyData, sizeof(keyData), 0, 0, &hKey)) goto cleanup; // CBC 模式,需 IV BYTE iv[16] = {0}; if (!CryptSetKeyParam(hKey, KP_IV, iv, 0)) goto cleanup; // 计算加密后长度(需填充到 16 字节倍数) DWORD dwBlockLen = ((dwLen + 15) / 16) * 16; *pdwEncryptedLen = dwBlockLen + 16; // +16 for IV memcpy(pEncrypted, iv, 16); if (!CryptEncrypt(hKey, 0, TRUE, 0, pEncrypted + 16, &dwLen, dwBlockLen)) goto cleanup; CryptDestroyKey(hKey); CryptReleaseContext(hProv, 0); return TRUE; cleanup: CryptReleaseContext(hProv, 0); return FALSE; }

客户端解密调用:

// GetInfo.cpp 中 recv 后 BYTE encrypted[4096]; int n = recv(m_socket, (char*)encrypted, sizeof(encrypted), 0); BYTE decrypted[4096]; DWORD dwDecryptedLen; DecryptPayload(encrypted, n, decrypted, &dwDecryptedLen); // 实现类似 EncryptPayload 的逆过程 RemoteCommand* pCmd = (RemoteCommand*)decrypted;

参数说明:CryptEncrypt()默认使用 PKCS#7 填充,dwLen输入为原始数据长度,输出为加密后长度;IV 固定为 16 字节零,实际部署时应每次随机生成并随密文发送;密钥keyData必须与客户端严格一致,建议存于注册表加密项或服务配置文件。

5.2 服务自启动与防卸载:注册表持久化与 SC_MANAGER_LOCK 权限

让WINLOGON服务开机自启且难以被普通用户停止:

  1. 注册表写入(WINLOGON.cppServiceMain()中):

    HKEY hKey; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Services\\WINLOGON"), 0, KEY_WRITE, &hKey) == ERROR_SUCCESS) { DWORD dwStartType = SERVICE_AUTO_START; RegSetValueEx(hKey, _T("Start"), 0, REG_DWORD, (BYTE*)&dwStartType, sizeof(dwStartType)); RegCloseKey(hKey); }
  2. 拒绝 Stop 请求(ServiceControlHandler()中):

    case SERVICE_CONTROL_STOP: // 检查调用者是否为 LocalSystem HANDLE hToken; if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { TOKEN_USER* pTokenUser; DWORD dwSize; if (GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize)) { pTokenUser = (TOKEN_USER*)malloc(dwSize); if (GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize)) { // 比较 SID 是否为 LocalSystem (S-1-5-18) if (EqualSid(pTokenUser->User.Sid, g_LocalSystemSid)) { SetServiceStatus(hStatus, &serviceStatus); return; } } free(pTokenUser); } CloseHandle(hToken); } // 非 LocalSystem 调用,静默忽略 STOP 请求 break;

5.3 多会话支持:从单会话捕获升级为 WTS 枚举 + 动态会话路由

原始代码仅处理WTSGetActiveConsoleSessionId(),无法应对远程桌面(RDP)多用户场景。升级方案:

功能实现要点源码修改点
会话枚举调用WTSEnumerateSessions()获取所有WTS_SESSION_INFOWINLOGONView.h新增std::vector<DWORD> m_vSessionIds
会话切换客户端发送0x05指令 + Session ID,服务端调用WTSConnectSession()OnReceive()新增case 0x05:分支
屏幕路由CaptureAndSendScreen()根据当前m_dwTargetSessionId切换WTSQueryUserToken()参数CaptureAndSendScreen()函数参数化dwSessionId

客户端新增 UI:在GetInfoDlg.h中添加CComboBox m_comboSessions,OnInitDialog()中调用WTSEnumerateSessions()填充会话列表。

边界提醒:WTSConnectSession()需目标会话处于WTSConnected状态,且调用进程必须有SE_TCB_NAME权限(LocalSystem 默认具备);RDP 会话的桌面捕获需额外调用WTSVirtualChannelOpen()获取图形通道,超出本源码范围。


6. 验证与调试:用 Process Monitor 定位权限失败、Wireshark 抓包分析指令流、以及我每次重构都强制走一遍的三步回归检查清单

6.1 权限失败定位:Process Monitor 过滤WINLOGON.exe的ACCESS DENIED事件

当WTSQueryUserToken()或RegOpenKeyEx()返回ERROR_ACCESS_DENIED时,不要盲目改组策略——用 Sysinternals Process Monitor(ProcMon)精准定位:

  1. 启动 ProcMon → Filter → Add →Process NamecontainsWINLOGON.exe→OperationisRegOpenKey/RegQueryValue/WTSQueryUserToken
  2. 复现失败操作(如点击“获取信息”按钮)
  3. 查看 Result 列为ACCESS DENIED的行,右键 → Properties → Stack → 定位到具体 API 调用栈
  4. 关键线索:若RegOpenKeyEx失败在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WinLogon,说明服务未以 LocalSystem 运行;若WTSQueryUserToken失败在NtOpenProcess,说明会话 ID 无效或令牌权限不足

血泪经验:ProcMon 默认不记录WTS*API,需在 Filter 中手动添加OperationisWTSQueryUserToken(大小写敏感),否则永远看不到关键失败点。

6.2 指令流分析:Wireshark 过滤 TCP 流并导出原始帧用于协议验证

原始 TCP 通信无加密,Wireshark 是最直接的验证工具:

  1. 启动 Wireshark → Capture Filter:tcp port 6666
  2. 运行GetInfo.exe→ 连接 → 发送0x02指令
  3. 停止抓包 → 右键 TCP 流 →Follow → TCP Stream→ 切换Raw视图
  4. 观察十六进制数据:前 4 字节应为02 00 00 00(小端0x02),后 4 字节为00 00 00 00(len=0),之后是屏幕数据(大量FF FF FF表示白屏,00 00 00表示黑屏)

导出帧用于单元测试:

  • 右键流 →Export Selected Packet Bytes→ 保存为screen_frame.bin
  • 编写 Python 脚本验证帧结构:
    with open("screen_frame.bin", "rb") as f: data = f.read() assert len(data) >= 8, "Frame too short" cmd = int.from_bytes(data[0:4], 'little') length = int.from_bytes(data[4:8], 'little') print(f"CMD={hex(cmd)}, LEN={length}, DATA_SIZE={len(data)-8}") # 若 CMD==2 且 LEN>0,说明服务端返回了有效图像

6.3 三步回归检查清单:我每次修改WINLOGON后必做的验证动作

这份源码脆弱性高,任何改动都可能引发连锁故障。我给自己立下铁律,每次提交前必须完成以下三步(缺一不可):

步骤操作预期结果失败即停
① 会话上下文验证以WINLOGON.exe启动服务 → 锁屏 → 登录 → 运行GetInfo.exe→ 发送0x02返回非全黑图像(至少有任务栏像素)黑屏 → 检查WTSGetActiveConsoleSessionId()与ImpersonateLoggedOnUser()
② 指令原子性验证GetInfo.exe连接后,依次发送0x01→0x02→0x03(坐标 100,100)→0x020x01返回 JSON-like 字符串;0x02返回图像;0x03触发远程鼠标移动;第二次0x02图像更新任一指令失败 → 检查RemoteCommand对齐与send()/recv()长度匹配
③ 服务生命周期验证sc stop WINLOGON→sc start WINLOGON→ 立即发送0x02服务能重启,且重启后首次0x02仍返回有效图像(证明会话 token 重建成功)重启后黑屏 → 检查ServiceMain()中WTSQueryUserToken()是否在新线程中调用,避免主线程阻塞

从那以后我每次重构WINLOGON的屏幕捕获模块,都强制走一遍这三步——哪怕只是改了一行BitBlt的参数。因为在这套系统里,会话、权限、时序三者咬合极紧,差一毫秒、少一次Impersonate、多一个字节对齐错误,整条链路就崩成黑匣子。希望帮到你。

本文还有配套的精品资源,点击获取