首页
社区
课程
招聘
[原创]从黑盒到可交互画面:极域 V6.0 连续投屏与远程操控协议逆向
发表于: 2026-10-5 19:32 329

[原创]从黑盒到可交互画面:极域 V6.0 连续投屏与远程操控协议逆向

2026-10-5 19:32
329

本文记录 2026.07.31 研究预览版中连续屏幕观看与远程操控链路的逆向过程。结论来自 IDA Pro 静态分析、官方教师端/学生端真实流量对照、逐字节重放和运行日志验证。本文只讨论隔离实验环境中的协议研究,不应被用于未经授权的监控或控制。

极域 V6.0 的“观看学生屏幕”和“远程控制学生”不是一个简单的 UDP 命令,也不是把截图循环发送回来。完整链路至少跨越三类通道:

截至当前版本,连续观看、H.264/JPEG 解封装、色度修正、SPUC 光标解析以及远程鼠标移动/点击已经在测试环境验证。键盘只验证到教师端捕获和 kind=16 发包,学生端是否最终完成 SendInput 仍不稳定,因此仍是实验性功能。

最初最容易犯的错误,是把“投屏”理解成缩略图协议 LANT/TNAL 的高频版本。抓包很快就能否定这个假设:点击官方教师端的“监看”后,学生端除了继续保活,还会开放或使用 4806/TCP;真正的连续画面主要出现在这条 TCP 流中。

远程控制同样不是直接向 4705/UDP 塞一个 WM_MOUSEMOVE。官方流程会先复用已经建立的桌面观看会话,然后通过通用命令事务启动一个独立的模拟输入通道。

这个模型解释了几个看似无关的现象:

这类老式 C/C++ 网络库常把 FourCC 写成 32 位立即数。文件里的字节顺序是人眼可读的 SHCO,反汇编中却可能出现:

原因是 x86 使用小端序。仅在 Strings 窗口里搜索 SHCO 很可能什么也找不到,应该同时搜索对应的立即数,然后查看交叉引用。

本次逆向中最有效的一组锚点是:

打开 LibBaseTrans.dll 后,先建立导入函数关系:connect、send、recv、WSARecv、线程创建、临界区和缓冲区复制。不要急着给每个函数命名,先回答三个问题:

从 0x43504B54 的交叉引用向上回溯,可以看到一个典型的“版本 + magic + payload length”检查;继续向下跟踪,payload 的首个 u32 被当作 channel。channel 10 随后进入桌面数据处理对象,而其他 channel 被分派给不同业务。

在 IDA 中可以先做以下重命名,之后伪代码会清晰很多:

名字不必一开始就完全正确。关键是让“传输层、分片层、记录层、编解码层”不再混在同一张调用图里。

在学生端的 MESS 处理路径中,feature id 8 会进入远程观看分支。这个分支不是只读一个布尔值,而是复制一个较大的参数对象。把官方教师端报文和伪代码中的复制长度对照后,可以确定:

不要把参数块里的端口直接当成学生端 4806/TCP。其中两个端口属于教师会话/桌面监控端点;学生端的 TCP 服务端口是另一层连接目标。这两个概念混用,会构造出长度正确但状态错误的请求。

MCMD 位于通用 COMD 事务内部。沿 MCMD 分支继续跟踪,可以看到 mode 分派以及模拟输入初始化路径。当前观察到的行为是:

再从输入接收线程向下跟踪,固定 magic 0x2321347C 是很强的定位点。分发器读取紧随其后的 kind,然后按鼠标或键盘结构解释剩余 20 字节。

逆向时必须继续跟到系统调用边界。看到 UDP recvfrom 并不表示控制已经生效,真正需要确认的是后续是否构造 INPUT、是否调用 SendInput、调用发生在哪个 Windows Session/desktop,以及本地 Hook 是否会再次拦截注入事件。

IDA 告诉我们代码可能怎样解释内存,抓包则告诉我们线上实际发送了什么。两者必须互相约束。

建议只在隔离测试网中采集,并把一次操作拆成单变量实验:

Wireshark 初始过滤器:

找到对应 TCP stream 后,再单独查看:

抓包分析时不要依赖 TCP segment 边界。一次 recv 可能拿到半个 UMSP 包,也可能一次拿到多个包。正确做法是维护流缓冲区,只有当缓冲区至少包含 12 字节头,并且达到 12 + payload_len 后才取出一包。

对 MCMD 和输入包尤其适合做差分:固定所有条件,只改变一个操作。例如把鼠标从画面左上移动到右下,比较两包中变化的 4 字节字段;连续点击左键,观察 0x0201 与 0x0202 的成对出现;滚轮则会让 data 字段变化。

字段命名按证据逐步升级:

不要因为某次值恰好等于屏幕像素,就立即命名为 pixel_x。本协议鼠标坐标实际使用 0..65535 归一化范围,窗口尺寸和远端编码尺寸只是映射输入。

当前实现构造的 feature payload 如下。所有多字节整数均为小端;IP 地址保持网络序原始字节。

604 字节参数块中,已经通过官方流量和代码路径确认或稳定观察到的字段:

最后一组三元组虽然已经稳定复现,但在没有进一步交叉引用证据前不应强行解释其业务名称。保留“已知偏移 + 已知值 + 未知语义”比写一个听起来合理的名字更有研究价值。

停止观看使用另一种短结构:

当前稳定时序是:

探测步骤只检查 TCP 是否监听,不启动编码器,也不发送 SHCO。正式启动后不能在失败时高频重试:学生端编码器和内部线程存在状态,重复 feature 请求或并发 TCP 会话可能导致学生端异常退出。当前实现限制每个 IP 同时只有一个观看会话,并在失败后冷却 60 秒。

连接 4806/TCP 后,教师端发送 20 字节选择包:

这里的 channel 10 是业务复用编号,不是 TCP/UDP 端口。

学生端返回的 UMSP 包使用 12 字节公共头:

代码或符号中可能把它写作 TCPK,但线上字节是 TKPC。逆向协议时应始终区分“源码/反编译变量名”和“wire bytes”,否则 Wireshark 搜索与代码常量会互相对不上。

channel 10 的 payload 不是直接的 H.264 NAL,而是先经过一层 12 字节桌面分片:

重组器按 frame_seq 保存状态,以 record_total_size 分配缓冲区,然后把每个 fragment 写入指定 offset。实现时至少要处理:

当前实现给每帧维护一份 seen 位图,只把第一次出现的字节计入 received,避免重复分片使计数提前达到 total。最多同时保留四个未完成帧,并将桌面记录大小限制在 0x241800。

一个重要踩坑是:并非所有完整记录都大于 64 字节。SPUC 光标记录只有 16 字节。如果在分片层直接用“total < 64”判定非法,就会把光标状态误报为坏帧。正确做法是先完成通用重组,再在记录层按 magic 和长度分流。

视频类记录包含一个 64 字节头:

magic 对应关系:

日志中的一个真实样本为:

这说明编码尺寸和实际可见尺寸并不总相同。直接按 encoded rect 展示会在边缘出现填充区域,所以解码后还需要按 visible rect 裁剪。

H.264 是有状态码流。解码器通常需要先看到 SPS、PPS 和 IDR,才能解释后续 P/B 帧。下面几种做法都会造成只有数据、没有画面:

当前实现从第一帧开始固定使用 bundled FFmpeg,view -> control 只改变窗口交互状态,不重启解码进程。可选裸流也按会话时间戳独立保存,避免把上一次的不完整码流接到新会话前面。

拿到画面后,最明显的问题曾是整体色相不对:本应为蓝色的区域显示成另一种颜色。几何内容正确、亮度基本正确而色相系统性错误,通常不是 RGB/BGR 顺序问题,而是 YUV 色度平面顺序问题。

对同一帧分别尝试常见转换后,可以确认极域这条 H.264 链路的 U/V 平面需要交换。FFmpeg 过滤器为:

然后再按可见尺寸裁剪:

这里选择在显示管线修正,而不改写保存的 H.264 裸流。这样原始证据仍可用于后续协议分析,同时实时窗口显示正确颜色。

光标状态使用独立的 16 字节记录:

位置打包方式:

实际控制窗口中有三个“鼠标位置”:

如果三者同时显示,用户会看到两个指针互相追赶。因此投屏区域隐藏本机系统指针;发送鼠标事件后的短时间内显示本地预测位置,约 180 ms 后切回 SPUC 确认位置。这个策略不能消除网络和编码延迟,但能减少操作感上的跳变。

画面显示可能经过缩放,坐标必须基于实际图像区域,而不是整个窗口或屏幕:

预测光标绘制回源画面时,再从 0..65535 映射到 encoded width/height。忽略这两次映射,会在窗口缩放后产生越来越明显的位置偏差。

MCMD 不能作为裸 UDP 报文发送,它是通用 COMD transaction 的 payload。COMD 外层负责版本、事务 GUID、超时、发送者和 payload 长度;学生端先进入事务分发器,再把 MCMD 交给监看/控制模块。

当前 MCMD 内层固定为 53 字节:

项目默认从 49152..65535 随机选择一个高位端口。使用固定端口虽然方便抓包,但学生端异常退出后旧线程或状态可能仍占用它;随机端口更适合反复调试。

MCMD 发出后不能立即发送第一条输入。学生端需要创建接收线程并完成 BeginSimulate 初始化。当前实现保守等待约 350 ms;这不是协议字段,而是从实际时序中得到的工程性保护。

为了避免学生端进入“有控制状态、没有画面会话”的不完整状态,control 只在 4806/TCP 已经连接后发送 MCMD。停止时发送 mode=1 并关闭本地 UDP sender。

所有输入事件使用相同外壳:

线上 magic 的小端字节是:

固定总长非常重要。多一个字节或少一个字节都可能让学生端接收线程丢包,或者让后续字段错位。

鼠标 payload 是五个 u32:

已验证的 message:

鼠标移动和点击在当前学生端环境已经观察到实际执行结果,因此证据链不仅停留在“教师端 sendto 成功”。

当前根据反汇编字段访问和教师端行为构造的 20 字节 payload:

当前 flags:

普通按下为 flags=0,释放为 flags=0x80;扩展键按下/释放再或上 0x01。

Tk 的键盘事件会受输入法和焦点影响,尤其在中文输入法状态下,部分按键不会按预期到达 Tk。因此控制窗口额外使用 WH_KEYBOARD_LL 获取 Windows 提供的 VK、扫描码、扩展键标志以及 key down/up,并拦截 Alt、Windows 键,防止这些键先作用于教师机本地桌面。

但是,“Hook 日志出现”和“学生端收到有效键盘输入”之间仍有多层边界。当前只能确认:

可能影响学生端最终执行的因素包括:学生端所在 Windows Session、当前 input desktop、UAC 安全桌面、接收线程权限、学生端本地 Hook、扫描码/VK 组合、扩展键标志以及注入事件过滤。没有接收端 ACK 时,教师端日志不能跨越这些边界作结论。

逆向远控最危险的误区是把 sendto() 返回成功当成远端执行成功。建议把证据拆成六级:

如果要继续研究键盘,下一步不应继续盲改教师端字段,而应在授权测试机上对学生端接收分支做动态观测:

只有走完这条链,才能判断问题究竟是协议字段错误,还是 Windows 输入隔离与 Hook 策略导致。

feature 参数里的教师端点不等于学生端 TCP 监听端口。包长和 IP 都正确时,这种错误特别难发现,因为学生端可能进入部分状态,却不产生正确桌面流。

本地网络里一次 recv 经常“刚好”是一包,导致错误解析器看似可用。一旦包被拆分或合并,就出现随机 magic、超大 length 和画面断流。TCP 必须按字节流处理。

早期解析器假设桌面记录至少 64 字节,日志中不断出现 total=16 的“非法帧”。保留这批原始数据并查看 magic 后,才发现它是独立光标记录。

view 能显示、control 一切换就卡在“等待画面”,根因不是 MCMD,而是切换时重启了解码器。H.264 参数集已经在流开头发过,新解码器只能等待下一次可恢复点。

如果交换 R/B 后肤色和亮度仍不合理,应检查 YUV 平面。当前链路需要交换 U/V,而不是在最终 RGB 图像上交换红蓝通道。

鼠标“不同步”不一定是包没生效。若本机系统指针可见,同时又绘制预测位置和 SPUC 位置,视觉上必然出现错位。隐藏本地指针并定义明确的预测回退窗口后,问题才可测量。

MCMD 是通用事务的 payload,不是 COMD 应用命令里的任意 cmdId。外层长度、GUID、事务字段或 payload offset 错一处,学生端通用分发器就可能走入完全不同的命令分支。调试时若出现与鼠标无关的窗口或系统提示,应先停止发送,重新核对事务边界,而不是继续猜键码。

协议能跑通不等于实现足够稳。当前版本加入了以下约束:

建议保留的关键日志字段:

日志必须限频。鼠标 move 可能达到每秒上百次,全量 INFO 不仅难读,还会反过来增加延迟;当前实现只详细记录前几包、关键按键和周期性样本。

连续投屏链路已经形成闭环:

远程鼠标也形成了从 MCMD 建立接收器到 28 字节输入包、学生端实际动作、SPUC 位置反馈的闭环。

键盘尚未闭环。当前研究最重要的结论不是“再换一个键码试试”,而是已经明确缺失的证据位于学生端接收分支到 Windows 输入桌面之间。下一阶段应优先补动态观测和返回值证据,再决定是否修改 kind=16 字段。

逆向协议真正有价值的部分,不是记住几个 magic 和 offset,而是把每个结论绑定到可重复的证据:反汇编交叉引用说明代码如何读,抓包说明线上如何写,日志说明我们的实现走到了哪一层,远端可见行为才说明功能最终生效。

这也是本项目在 README 中坚持区分“已验证”“可用”“部分验证”和“实验性”的原因。

请先执行 view_probe,并只在自己拥有管理权限或已获得明确授权的设备上测试。

sequenceDiagram
    participant T as 第三方教师端
    participant S as 极域学生端

    T->>S: TCP connect probe :4806(只探测)
    T->>S: MESS feature=8 / start(5512/UDP)
    T->>S: TCP connect :4806
    T->>S: SHCO / channel 10
    S-->>T: TKPC / channel 10 / 桌面分片
    S-->>T: HHRF(H.264) 或 HJRF(JPEG)
    T->>S: COMD transaction → MCMD mode=2
    Note over S: BeginSimulate,监听指定 UDP 端口
    T->>S: 28-byte mouse input
    S-->>T: SPUC cursor record
    T->>S: 28-byte kind=16 keyboard input(实验性)
wire bytes: 53 48 43 4F  -> "SHCO"
IDA imm32 : 0x4F434853

wire bytes: 54 4B 50 43  -> "TKPC"
IDA imm32 : 0x43504B54
sub_xxx_connect     -> umsp_connect
sub_xxx_read_loop   -> umsp_recv_loop
sub_xxx_dispatch    -> dispatch_umsp_channel
sub_xxx_channel10   -> handle_desktop_fragment
sub_xxx_record      -> handle_desktop_record
udp.port == 4705 || udp.port == 5512 || tcp.port == 4806
tcp.stream eq <n>

[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)

收藏
点赞 ・1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回