从黑盒到可交互画面:极域 V6.0 连续投屏与远程操控协议逆向
本文记录
2026.07.31 研究预览版中连续屏幕观看与远程操控链路的逆向过程。结论来自 IDA Pro 静态分析、官方教师端/学生端真实流量对照、逐字节重放和运行日志验证。本文只讨论隔离实验环境中的协议研究,不应被用于未经授权的监控或控制。
摘要
极域 V6.0 的“观看学生屏幕”和“远程控制学生”不是一个简单的 UDP 命令,也不是把截图循环发送回来。完整链路至少跨越三类通道:
- 教师端先经
5512/UDP的MESS feature=8通知学生端启动桌面发送功能; - 教师端连接学生端
4806/TCP,发送SHCO选择 UMSP channel 10,再从TKPC中重组HHRF/HJRF桌面记录; - 进入控制模式时,教师端再用
COMD事务承载MCMD,让学生端在指定 UDP 端口启动模拟输入接收器;鼠标和键盘事件通过固定 28 字节输入包发送。
截至当前版本,连续观看、H.264/JPEG 解封装、色度修正、SPUC 光标解析以及远程鼠标移动/点击已经在测试环境验证。键盘只验证到教师端捕获和 kind=16 发包,学生端是否最终完成 SendInput 仍不稳定,因此仍是实验性功能。
| 能力 | 当前证据 |
|---|---|
| 4806/TCP 建连与 SHCO 选路 | 已验证 |
| TKPC 分片重组 | 已验证 |
| HHRF/H.264、HJRF/JPEG 解析 | 已验证 |
| U/V 色度平面修正 | 已验证 |
| SPUC 远端光标解析 | 已验证 |
| MCMD mode=2 建立控制 | 已验证 |
| 28 字节鼠标包 | 移动与点击已验证 |
| kind=16 键盘包 | 捕获与发包已验证,学生端执行未稳定验证 |
一、先建立正确的系统模型
最初最容易犯的错误,是把“投屏”理解成缩略图协议 LANT/TNAL 的高频版本。抓包很快就能否定这个假设:点击官方教师端的“监看”后,学生端除了继续保活,还会开放或使用 4806/TCP;真正的连续画面主要出现在这条 TCP 流中。
远程控制同样不是直接向 4705/UDP 塞一个 WM_MOUSEMOVE。官方流程会先复用已经建立的桌面观看会话,然后通过通用命令事务启动一个独立的模拟输入通道。
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(实验性)这个模型解释了几个看似无关的现象:
- 只发送 feature 8,不连接 TCP,不会得到可显示的连续画面;
- 直接发输入包时,学生端没有对应 UDP 接收器,数据只会被操作系统丢弃;
- 从
view切换到control时重启 H.264 解码器,容易永久黑屏,因为新解码器没有收到此前的 SPS/PPS/IDR; - 本地鼠标与学生端鼠标短暂错位并非只有坐标算法问题,还包含编码、网络、显示和
SPUC回传延迟。
二、在 IDA 中如何找到入口
2.1 不要只搜索可见字符串
这类老式 C/C++ 网络库常把 FourCC 写成 32 位立即数。文件里的字节顺序是人眼可读的 SHCO,反汇编中却可能出现:
wire bytes: 53 48 43 4F -> "SHCO"
IDA imm32 : 0x4F434853
wire bytes: 54 4B 50 43 -> "TKPC"
IDA imm32 : 0x43504B54原因是 x86 使用小端序。仅在 Strings 窗口里搜索 SHCO 很可能什么也找不到,应该同时搜索对应的立即数,然后查看交叉引用。
本次逆向中最有效的一组锚点是:
| 线索 | 作用 |
|---|---|
0x4F434853 | 定位 UMSP channel 选择包 SHCO |
0x43504B54 | 定位 UMSP 数据包 TKPC |
0x46524848 | 定位 HHRF H.264 桌面记录 |
0x46524A48 | 定位 HJRF JPEG 桌面记录 |
MCMD | 定位监看/控制模式切换结构 |
SPUC | 定位光标状态记录 |
0x2321347C | 定位模拟输入包分发器 |
常量 4806、channel 10 | 从通用传输库缩小到桌面通道 |
2.2 从网络 API 反推对象边界
打开 LibBaseTrans.dll 后,先建立导入函数关系:connect、send、recv、WSARecv、线程创建、临界区和缓冲区复制。不要急着给每个函数命名,先回答三个问题:
- 哪个对象持有 socket?
- 哪个循环读取 12 字节头并按长度字段切包?
- 哪个分支在 payload 起始位置读取 channel id?
从 0x43504B54 的交叉引用向上回溯,可以看到一个典型的“版本 + magic + payload length”检查;继续向下跟踪,payload 的首个 u32 被当作 channel。channel 10 随后进入桌面数据处理对象,而其他 channel 被分派给不同业务。
在 IDA 中可以先做以下重命名,之后伪代码会清晰很多:
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名字不必一开始就完全正确。关键是让“传输层、分片层、记录层、编解码层”不再混在同一张调用图里。
2.3 从 feature 分发器找到 617 字节参数块
在学生端的 MESS 处理路径中,feature id 8 会进入远程观看分支。这个分支不是只读一个布尔值,而是复制一个较大的参数对象。把官方教师端报文和伪代码中的复制长度对照后,可以确定:
- 内层 feature payload 总长为 617 字节;
- 前 12 字节是
length / feature / flags; - 中间 604 字节是远程观看参数;
- 最后还有 1 字节尾部填充。
不要把参数块里的端口直接当成学生端 4806/TCP。其中两个端口属于教师会话/桌面监控端点;学生端的 TCP 服务端口是另一层连接目标。这两个概念混用,会构造出长度正确但状态错误的请求。
2.4 从 MCMD 跟到模拟输入
MCMD 位于通用 COMD 事务内部。沿 MCMD 分支继续跟踪,可以看到 mode 分派以及模拟输入初始化路径。当前观察到的行为是:
mode=0:Share,双方可操作;mode=2:Control Student,启动模拟输入并额外进入学生端本地输入锁定路径;mode=1:本项目用于停止当前模拟输入会话。
再从输入接收线程向下跟踪,固定 magic 0x2321347C 是很强的定位点。分发器读取紧随其后的 kind,然后按鼠标或键盘结构解释剩余 20 字节。
逆向时必须继续跟到系统调用边界。看到 UDP recvfrom 并不表示控制已经生效,真正需要确认的是后续是否构造 INPUT、是否调用 SendInput、调用发生在哪个 Windows Session/desktop,以及本地 Hook 是否会再次拦截注入事件。
三、用抓包给反汇编“定类型”
IDA 告诉我们代码可能怎样解释内存,抓包则告诉我们线上实际发送了什么。两者必须互相约束。
建议只在隔离测试网中采集,并把一次操作拆成单变量实验:
- 只打开“观看屏幕”,不移动鼠标;
- 只切换到“远程控制”,不输入;
- 只移动一次鼠标;
- 只按一次左键;
- 只按一个普通字母键;
- 分别停止控制和停止观看。
Wireshark 初始过滤器:
udp.port == 4705 || udp.port == 5512 || tcp.port == 4806找到对应 TCP stream 后,再单独查看:
tcp.stream eq <n>抓包分析时不要依赖 TCP segment 边界。一次 recv 可能拿到半个 UMSP 包,也可能一次拿到多个包。正确做法是维护流缓冲区,只有当缓冲区至少包含 12 字节头,并且达到 12 + payload_len 后才取出一包。
3.1 差分比对比猜字段更快
对 MCMD 和输入包尤其适合做差分:固定所有条件,只改变一个操作。例如把鼠标从画面左上移动到右下,比较两包中变化的 4 字节字段;连续点击左键,观察 0x0201 与 0x0202 的成对出现;滚轮则会让 data 字段变化。
字段命名按证据逐步升级:
unknown_08
-> changes_with_x
-> normalized_x
-> normalized_x_u16_range_in_u32不要因为某次值恰好等于屏幕像素,就立即命名为 pixel_x。本协议鼠标坐标实际使用 0..65535 归一化范围,窗口尺寸和远端编码尺寸只是映射输入。
四、观看请求:MESS feature 8
当前实现构造的 feature payload 如下。所有多字节整数均为小端;IP 地址保持网络序原始字节。
4.1 feature 8 内层结构
| 偏移 | 大小 | 当前解释 | 观察值 |
|---|---|---|---|
0x000 | 4 | payload length | 617 |
0x004 | 4 | feature id | 8 |
0x008 | 4 | start flags | 0x80000000 |
0x00C | 604 | remote-view params | 见下表 |
0x268 | 1 | tail padding | 0 |
604 字节参数块中,已经通过官方流量和代码路径确认或稳定观察到的字段:
| 参数内偏移 | 大小 | 含义 | 当前值 |
|---|---|---|---|
+0x00 | 4 | RemoteWithVoice | 0 |
+0x04 | 4 | 教师 IP | network order |
+0x08 | 2 | 教师会话端口 | 当前频道端口 |
+0x0A | 4 | 教师 IP(第二端点) | network order |
+0x0E | 2 | 桌面监控端口 | 当前频道端口 |
+0x24 | 4 | NetworkType | 1 |
+0x28 | 4 | ShowMonitorControlMessage | 1 |
+0x2C | 4 | MaxSendSpeed | 20480 |
+0x30 | 4 | RepairMode | 1 |
+0x34 | 4 | MaxPacketSize | 1440 |
+0x38 | 4 | FrameLimit | 25 |
+0x3C | 4 | CaptureQuality | 75 |
+0x40 | 4 | TcpCommMode | 1 |
+0x44 | 4 | 学生 IP | network order |
+0x48 | 12 | 未完全命名的三元组 | 5, 12, 16 |
最后一组三元组虽然已经稳定复现,但在没有进一步交叉引用证据前不应强行解释其业务名称。保留“已知偏移 + 已知值 + 未知语义”比写一个听起来合理的名字更有研究价值。
停止观看使用另一种短结构:
u32 length = 13
u32 feature = 0
u32 flags = 0
u8 tail = 04.2 正确启动时序
当前稳定时序是:
TCP connect probe 4806(立即关闭,不发 SHCO)
-> MESS feature 8 start
-> 单次连接 4806/TCP
-> 立即发送 SHCO channel 10
-> 从第一条 H.264 记录开始喂给同一个解码器探测步骤只检查 TCP 是否监听,不启动编码器,也不发送 SHCO。正式启动后不能在失败时高频重试:学生端编码器和内部线程存在状态,重复 feature 请求或并发 TCP 会话可能导致学生端异常退出。当前实现限制每个 IP 同时只有一个观看会话,并在失败后冷却 60 秒。
五、UMSP:SHCO、TKPC 与 channel 10
5.1 SHCO 选择包
连接 4806/TCP 后,教师端发送 20 字节选择包:
| 偏移 | 类型 | 值 |
|---|---|---|
0x00 | u32 | version 0x00010000 |
0x04 | u32 | 0x4F434853,线上为 SHCO |
0x08 | u32 | body length 8 |
0x0C | u32 | selector/op,观察值 1 |
0x10 | u32 | desktop channel 10 |
这里的 channel 10 是业务复用编号,不是 TCP/UDP 端口。
5.2 TKPC 数据包
学生端返回的 UMSP 包使用 12 字节公共头:
| 偏移 | 类型 | 说明 |
|---|---|---|
0x00 | u32 | version,预期 0x00010000 |
0x04 | u32 | 0x43504B54,线上为 TKPC |
0x08 | u32 | payload length |
0x0C | u32 | channel id,桌面为 10 |
0x10 | bytes | channel payload |
代码或符号中可能把它写作 TCPK,但线上字节是 TKPC。逆向协议时应始终区分“源码/反编译变量名”和“wire bytes”,否则 Wireshark 搜索与代码常量会互相对不上。
六、桌面分片重组
channel 10 的 payload 不是直接的 H.264 NAL,而是先经过一层 12 字节桌面分片:
+0x00 u32 frame_seq
+0x04 u32 fragment_offset
+0x08 u32 record_total_size
+0x0C bytes fragment_data重组器按 frame_seq 保存状态,以 record_total_size 分配缓冲区,然后把每个 fragment 写入指定 offset。实现时至少要处理:
- TCP 读取边界与 UMSP 包边界不一致;
- 同一分片重复到达;
- 分片乱序;
- 新 frame_seq 到来时旧帧仍未完成;
- offset 或 length 越界;
- 恶意或损坏的 total size 导致大内存分配。
当前实现给每帧维护一份 seen 位图,只把第一次出现的字节计入 received,避免重复分片使计数提前达到 total。最多同时保留四个未完成帧,并将桌面记录大小限制在 0x241800。
一个重要踩坑是:并非所有完整记录都大于 64 字节。SPUC 光标记录只有 16 字节。如果在分片层直接用“total < 64”判定非法,就会把光标状态误报为坏帧。正确做法是先完成通用重组,再在记录层按 magic 和长度分流。
七、HHRF/HJRF 桌面记录
视频类记录包含一个 64 字节头:
| 偏移 | 大小 | 说明 |
|---|---|---|
0x00 | 4 | magic:HHRF / HJRF / HMRF |
0x04 | 4 | declared record size |
0x08 | 4 | timestamp |
0x0C | 16 | encoded rect:left/top/right/bottom |
0x1C | 16 | visible rect:left/top/right/bottom |
0x2C | 4 | frame type |
0x30 | 16 | 尚未完全命名的字段 |
0x40 | variable | 编码 payload |
magic 对应关系:
0x46524848 -> wire "HHRF" -> H.264 Annex-B
0x46524A48 -> wire "HJRF" -> JPEG
0x46524D48 -> wire "HMRF" -> legacy/兼容记录日志中的一个真实样本为:
H264 frame=1 seq=11 record=623 declared=623
encoded=(0,0,1728,928) visible=(0,0,1718,918)这说明编码尺寸和实际可见尺寸并不总相同。直接按 encoded rect 展示会在边缘出现填充区域,所以解码后还需要按 visible rect 裁剪。
7.1 为什么请求真实 H.264 时容易“黑屏”
H.264 是有状态码流。解码器通常需要先看到 SPS、PPS 和 IDR,才能解释后续 P/B 帧。下面几种做法都会造成只有数据、没有画面:
- TCP 建连太晚,错过码流初始化;
- 先用一个播放器观看,切换 control 时杀掉播放器再启动另一个;
- 把多次观看留下的半段
.h264直接拼在一起; - 将 UMSP/桌面记录头误写进 H.264 payload;
- 按 TCP recv 块直接喂解码器,破坏了上层记录边界。
当前实现从第一帧开始固定使用 bundled FFmpeg,view -> control 只改变窗口交互状态,不重启解码进程。可选裸流也按会话时间戳独立保存,避免把上一次的不完整码流接到新会话前面。
7.2 为什么画面颜色会错
拿到画面后,最明显的问题曾是整体色相不对:本应为蓝色的区域显示成另一种颜色。几何内容正确、亮度基本正确而色相系统性错误,通常不是 RGB/BGR 顺序问题,而是 YUV 色度平面顺序问题。
对同一帧分别尝试常见转换后,可以确认极域这条 H.264 链路的 U/V 平面需要交换。FFmpeg 过滤器为:
shuffleplanes=0:2:1:3然后再按可见尺寸裁剪:
shuffleplanes=0:2:1:3,crop=<visible_w>:<visible_h>:0:0这里选择在显示管线修正,而不改写保存的 H.264 裸流。这样原始证据仍可用于后续协议分析,同时实时窗口显示正确颜色。
八、SPUC:远端光标不是画面的一部分
光标状态使用独立的 16 字节记录:
| 偏移 | 类型 | 说明 |
|---|---|---|
0x00 | 4 bytes | SPUC |
0x04 | u32 | record size,预期 16 |
0x08 | u32 | timestamp |
0x0C | u32 | packed position |
位置打包方式:
x = packed_position & 0xFFFF
y = packed_position >> 16实际控制窗口中有三个“鼠标位置”:
- 本机系统指针;
- 刚刚发送给学生端的预测位置;
- 学生端通过
SPUC回传的确认位置。
如果三者同时显示,用户会看到两个指针互相追赶。因此投屏区域隐藏本机系统指针;发送鼠标事件后的短时间内显示本地预测位置,约 180 ms 后切回 SPUC 确认位置。这个策略不能消除网络和编码延迟,但能减少操作感上的跳变。
画面显示可能经过缩放,坐标必须基于实际图像区域,而不是整个窗口或屏幕:
normalized_x = round(local_x * 65535 / (display_width - 1))
normalized_y = round(local_y * 65535 / (display_height - 1))预测光标绘制回源画面时,再从 0..65535 映射到 encoded width/height。忽略这两次映射,会在窗口缩放后产生越来越明显的位置偏差。
九、远控建立:COMD 事务中的 MCMD
MCMD 不能作为裸 UDP 报文发送,它是通用 COMD transaction 的 payload。COMD 外层负责版本、事务 GUID、超时、发送者和 payload 长度;学生端先进入事务分发器,再把 MCMD 交给监看/控制模块。
当前 MCMD 内层固定为 53 字节:
| 偏移 | 大小 | 说明 |
|---|---|---|
0x00 | 4 | inner length 53 |
0x04 | 4 | feature/category 8 |
0x08 | 4 | flags 0 |
0x0C | 4 | magic MCMD |
0x10 | 4 | mode:0 / 1 / 2 |
0x14 | 4 | 学生 IP,网络序字节 |
0x18 | 2 | 学生端输入监听 UDP 端口 |
0x1A | 27 | 保留/零填充 |
项目默认从 49152..65535 随机选择一个高位端口。使用固定端口虽然方便抓包,但学生端异常退出后旧线程或状态可能仍占用它;随机端口更适合反复调试。
MCMD 发出后不能立即发送第一条输入。学生端需要创建接收线程并完成 BeginSimulate 初始化。当前实现保守等待约 350 ms;这不是协议字段,而是从实际时序中得到的工程性保护。
为了避免学生端进入“有控制状态、没有画面会话”的不完整状态,control 只在 4806/TCP 已经连接后发送 MCMD。停止时发送 mode=1 并关闭本地 UDP sender。
十、28 字节模拟输入包
所有输入事件使用相同外壳:
+0x00 u32 magic = 0x2321347C
+0x04 u32 kind
+0x08 u8 payload[20]
total = 28 bytes线上 magic 的小端字节是:
7C 34 21 23固定总长非常重要。多一个字节或少一个字节都可能让学生端接收线程丢包,或者让后续字段错位。
10.1 鼠标:kind=1
鼠标 payload 是五个 u32:
| payload 偏移 | 字段 | 说明 |
|---|---|---|
+0x00 | message | Windows 鼠标消息 |
+0x04 | x | 0..65535 归一化坐标 |
+0x08 | y | 0..65535 归一化坐标 |
+0x0C | data | 滚轮等附加数据 |
+0x10 | swapped | 键位交换/保留状态 |
已验证的 message:
| 动作 | 值 |
|---|---|
| move | 0x0200 |
| left down / up | 0x0201 / 0x0202 |
| right down / up | 0x0204 / 0x0205 |
| middle down / up | 0x0207 / 0x0208 |
| wheel | 0x020A |
鼠标移动和点击在当前学生端环境已经观察到实际执行结果,因此证据链不仅停留在“教师端 sendto 成功”。
10.2 键盘:kind=16,仍是实验性
当前根据反汇编字段访问和教师端行为构造的 20 字节 payload:
| payload 偏移 | 类型 | 说明 |
|---|---|---|
+0x00 | u16 | scan code |
+0x02 | u16 | virtual key |
+0x04 | u32 | flags |
+0x08 | 12 bytes | zero/reserved |
当前 flags:
bit 0 = extended key
bit 7 = key up普通按下为 flags=0,释放为 flags=0x80;扩展键按下/释放再或上 0x01。
Tk 的键盘事件会受输入法和焦点影响,尤其在中文输入法状态下,部分按键不会按预期到达 Tk。因此控制窗口额外使用 WH_KEYBOARD_LL 获取 Windows 提供的 VK、扫描码、扩展键标志以及 key down/up,并拦截 Alt、Windows 键,防止这些键先作用于教师机本地桌面。
但是,“Hook 日志出现”和“学生端收到有效键盘输入”之间仍有多层边界。当前只能确认:
本地物理按键
-> WH_KEYBOARD_LL 捕获成功
-> kind=16 的 28 字节包构造成功
-> UDP sendto 成功
-x-> 学生端解析/SendInput/目标应用收到字符:尚未稳定证明可能影响学生端最终执行的因素包括:学生端所在 Windows Session、当前 input desktop、UAC 安全桌面、接收线程权限、学生端本地 Hook、扫描码/VK 组合、扩展键标志以及注入事件过滤。没有接收端 ACK 时,教师端日志不能跨越这些边界作结论。
十一、如何判断“真的成功”,而不是日志看起来成功
逆向远控最危险的误区是把 sendto() 返回成功当成远端执行成功。建议把证据拆成六级:
| 等级 | 证据 | 鼠标 | 键盘 |
|---|---|---|---|
| A | 教师端 UI/Hook 捕获事件 | 已验证 | 已验证 |
| B | 教师端构造并发送正确长度报文 | 已验证 | 已验证 |
| C | 抓包看到报文到达学生端 | 已验证 | 需持续复核 |
| D | 学生端接收器进入对应 kind 分支 | 已验证 | 尚缺稳定动态证据 |
| E | 学生端系统注入 API 成功 | 已验证实际效果 | 未稳定验证 |
| F | 目标桌面/应用收到输入 | 已验证 | 未稳定验证 |
如果要继续研究键盘,下一步不应继续盲改教师端字段,而应在授权测试机上对学生端接收分支做动态观测:
- 在
recvfrom返回后记录 28 字节缓冲区; - 确认 magic、kind 和 payload 分支;
- 在
SendInput前检查构造出的INPUT; - 记录
SendInput返回值和GetLastError; - 确认线程所属 Session 与当前 input desktop;
- 最后再观察目标窗口是否收到键盘消息。
只有走完这条链,才能判断问题究竟是协议字段错误,还是 Windows 输入隔离与 Hook 策略导致。
十二、几个真正耗时的坑
12.1 把 4806 与 feature 参数端口混为一谈
feature 参数里的教师端点不等于学生端 TCP 监听端口。包长和 IP 都正确时,这种错误特别难发现,因为学生端可能进入部分状态,却不产生正确桌面流。
12.2 按 recv 边界解析 TCP
本地网络里一次 recv 经常“刚好”是一包,导致错误解析器看似可用。一旦包被拆分或合并,就出现随机 magic、超大 length 和画面断流。TCP 必须按字节流处理。
12.3 把 16 字节 SPUC 当坏视频帧
早期解析器假设桌面记录至少 64 字节,日志中不断出现 total=16 的“非法帧”。保留这批原始数据并查看 magic 后,才发现它是独立光标记录。
12.4 在码流中途更换播放器
view 能显示、control 一切换就卡在“等待画面”,根因不是 MCMD,而是切换时重启了解码器。H.264 参数集已经在流开头发过,新解码器只能等待下一次可恢复点。
12.5 把颜色问题误判为 RGB/BGR
如果交换 R/B 后肤色和亮度仍不合理,应检查 YUV 平面。当前链路需要交换 U/V,而不是在最终 RGB 图像上交换红蓝通道。
12.6 本地指针、预测指针和远端指针混在一起
鼠标“不同步”不一定是包没生效。若本机系统指针可见,同时又绘制预测位置和 SPUC 位置,视觉上必然出现错位。隐藏本地指针并定义明确的预测回退窗口后,问题才可测量。
12.7 用错误的 COMD 边界调 MCMD
MCMD 是通用事务的 payload,不是 COMD 应用命令里的任意 cmdId。外层长度、GUID、事务字段或 payload offset 错一处,学生端通用分发器就可能走入完全不同的命令分支。调试时若出现与鼠标无关的窗口或系统提示,应先停止发送,重新核对事务边界,而不是继续猜键码。
十三、实现中的防崩溃与可观测性设计
协议能跑通不等于实现足够稳。当前版本加入了以下约束:
view_probe只做 TCP connect 探测,不发送 feature 8 或 SHCO;- 每个学生 IP 同时只允许一个 RemoteViewSession;
- 正式观看只进行一次 TCP 连接,不循环轰炸学生端;
- 连接/接收失败后发送停止 feature,并进入 60 秒冷却;
- UMSP payload 和桌面 record 都有长度上限与越界检查;
- 未完成分片数量受限,重复字节不重复计数;
- H.264 原始流按会话独立保存;
control必须等待 TCP 会话 connected event;- MCMD 后等待接收线程初始化,再发送首个输入包;
- 远控可用
TEACHER_ENABLE_REMOTE_CONTROL=0紧急禁用; - 日志区分“捕获”“发包”“远端 SPUC 反馈”,避免把不同证据混成一句成功。
建议保留的关键日志字段:
[RemoteView] feature start / stop
[RemoteView] TCP connected, SHCO channel
[RemoteView] frame_seq / offset / total
[RemoteView] record magic / declared size / rect / frame type
[RemoteControl] MCMD mode / UDP endpoint
[RemoteControl] input count / kind / exact 28-byte hex
[RemoteControl] SPUC x / y / timestamp日志必须限频。鼠标 move 可能达到每秒上百次,全量 INFO 不仅难读,还会反过来增加延迟;当前实现只详细记录前几包、关键按键和周期性样本。
十四、当前结论与下一步
连续投屏链路已经形成闭环:
IDA 定位 feature 8 和 UMSP
-> 抓包确认 SHCO/TKPC/channel 10
-> 重组桌面分片
-> 识别 HHRF/HJRF/SPUC
-> FFmpeg 解码、U/V 修正、visible rect 裁剪
-> Pillow/Tk 内嵌交互窗口远程鼠标也形成了从 MCMD 建立接收器到 28 字节输入包、学生端实际动作、SPUC 位置反馈的闭环。
键盘尚未闭环。当前研究最重要的结论不是“再换一个键码试试”,而是已经明确缺失的证据位于学生端接收分支到 Windows 输入桌面之间。下一阶段应优先补动态观测和返回值证据,再决定是否修改 kind=16 字段。
逆向协议真正有价值的部分,不是记住几个 magic 和 offset,而是把每个结论绑定到可重复的证据:反汇编交叉引用说明代码如何读,抓包说明线上如何写,日志说明我们的实现走到了哪一层,远端可见行为才说明功能最终生效。
这也是本项目在 README 中坚持区分“已验证”“可用”“部分验证”和“实验性”的原因。
附录 A:协议层级速查
MESS feature 8 (5512/UDP)
└── 617-byte remote-view payload
4806/TCP
└── UMSP
├── SHCO: select channel 10
└── TKPC
└── channel 10
└── desktop fragment: seq + offset + total
├── HHRF: H.264 Annex-B
├── HJRF: JPEG
├── HMRF: legacy record
└── SPUC: cursor position
COMD transaction (4705/UDP)
└── MCMD mode 0/1/2 + student input UDP port
└── 28-byte input packet
├── kind=1: mouse
└── kind=16: keyboard (experimental)附录 B:运行命令
view_probe <ip> # 只探测 4806/TCP
view <ip> # 启动连续观看
control <ip> # 在观看会话上启用远控
control_stop <ip> # 停止输入模拟
view_stop <ip> # 停止桌面流请先执行 view_probe,并只在自己拥有管理权限或已获得明确授权的设备上测试。
还没有评论,来说点什么吧。