2016-10-02。两个文件:

服务器.e   7,434 字节
客户端.e   6,749 字节

这两个是归档里最适合做全文精读的样本 —— 小到没有编进任何第三方模块,把 gbk_runs 跑一遍,全部产出是 73 条 + 55 条。方法在上一篇。

当一个工程的扫描产出不到 100 条时,你不需要任何筛选技巧,直接把全部串打印出来读就行。这是我这次归档里学到的最实用的一条:研究格式要找最小的样本,写文章也是。


把两个窗口的串并排

服务器.e —— 73 条里的非噪声部分

   480  _启动窗口
   842  LP 远程控制 ---byXXXXXXXXXX        ← 窗口标题(QQ 号已抹)
   901  按钮2
  1013  记录v
  1045  服务器1                            ← 监听控件
  1167  标签5
  1331  右边是图像
  1377  标签4
  1541  备注
  1581  标签3
  1745  代码
  1918  鼠标类型
  1966  按钮1
  2078  开始执行
  2247  类型 X坐标 Y坐标                   ← 一行的格式
  2467  写入 备注
  2512  备注编辑框
  2793  写入 代码
  2838  代码编辑框
  2955  图片框1
  3270  系统核心支持库
  3330  特殊功能支持库
  3939  \Data\Data代码
  4170  _图片框1_鼠标位置被移动
  4248  横向位置
  4269  纵向位置
  4290  功能键状态
  4399  鼠标位置
  4487  _按钮1_被单击
  4726  _按钮2_被单击
  4905  _服务器1_客户进入
  5075  有一个客户进入……
  5111  _服务器1_数据到达
  5321  __启动窗口_尺寸被改变
  5567  _服务器1_客户离开
  5645  _启动窗口.标题 = “对方已经关闭客户端。”      ← 源码原文
  5710  _图片框1_鼠标左键被按下
  5939  鼠标类型 1
  6029  _图片框1_鼠标右键被按下
  6258  鼠标类型 3
  6348  _标签5_鼠标左键被按下
  6856  服务器.exe

客户端.e —— 55 条里的非噪声部分

   794  请开始连接---远程客户端
   849  时钟1
  1073  断开连接l                           ← 末尾这个 l 不是我打的
  1109  客户1                             ← 连接控件
  1385  第一个是IP第二个是端口
  1555  开始连接
  1591  编辑框2
  1791  编辑框1
  2082  系统核心支持库
  2142  应用接口支持库                    ← SetCursorPos 在这里
  2160  EThread
  2205  多线程支持库
  2448  _按钮1_被单击
  2693  正在连接中……
  2751  _客户1_数据到达
  3178  鼠标位置
  3327  鼠标类型
  3392  X坐标
  3526  Y坐标
  3776  文本类型
  4118  您已经停止了远程遥控。
  4278  _客户1_连接断开
  4408  对方关闭了远程控制服务端。
  4673  10.240.211.233                    ← 编辑框1 的默认值(学校内网)
  4705  连接服务器
  4982  连接成功!等待对方执行指令
  5252  连接失败,请更改设置再试。
  5317  _时钟1_周期事件
  5426  定时拍照                           ← 关键的一条
  5871  鼠标移动
  5891  SetCursorPos
  6205  客户端.exe

关于串尾那些多余的拉丁字母

断开连接l、找回密码n、顺序播放o、重新连接至服务器(断网重连)l、发送0、检查更新l —— 我这一批工程里到处是这种"末尾多一个 ASCII 字符"的串。

不是原作者打错,是我的扫描器越界,而且原因可以算出来。看 断开连接l 附近的原始字节:

1073  ← 08 00 00 00 │ b6cf bfaa c1ac bdb3 │ 6c 00 00 00 25 00 01 ...
      长度前缀 8    └── 正文正好 8 字节 ──┘ └─ 下一条记录的头部 ─┘
      「断开连接」= B6CF BFAA C1AC BDB3

字段是"长度前缀 + 正文",没有终止符。前缀说 8 字节,正文就到 1081 为止;而我的 gbk_runs 不认识前缀,它一路走到遇到非法字节才停,于是把下一条记录的头字节 0x6c(就是那个 l)也收了进去。后面 00 00 00 立刻截断了它,所以伪影永远只有 1–3 个字符。

校验方法:对任何可疑串,回看它前面的 uint32 前缀,用前缀算出正文应有的结尾,超出部分一律砍掉。上面那条 检查更新l(聊天器客户端.e 偏移 1523,前缀同样是 08 00 00 00)用这个办法一算就对上了。

我一开始差点把这些当笔误写进文章。读二进制容器时,任何"看起来很奇怪但极有规律"的现象,先怀疑自己的解析器,再怀疑原作者。


协议就这么被反推出来了

方向:名字起反了

服务器.e 里有 服务器1(监听控件),并且处理 _服务器1_客户进入;客户端.e 里有 客户1(连接控件),主动 连接服务器,界面上让你填 第一个是IP第二个是端口。

所以:

  • “服务器” = 控制端(操作者,坐在自己机器前等连接)
  • “客户端” = 被控端(要在目标机器上运行,并填入操作者的 IP)

这和常见的 RAT 恰好是同一个方向、不同的命名。被控端主动连出,所以能穿过目标机的入站限制 —— 这个我 2016 年当然不知道,只是照着"我操作 = 服务器"的直觉起的名。

画面怎么来的

控制端有个 图片框1 和一个标签 右边是图像,__启动窗口_尺寸被改变 让它跟着窗口缩放。但控制端没有任何截图相关的串。

被控端有:

5317  _时钟1_周期事件
5426  定时拍照

时钟周期事件里做的事叫定时拍照。所以画面是被控端按固定周期截屏、发回去、贴到 图片框1 上。没有增量、没有编码、没有压缩(两边都扫不到任何压缩或位图处理的串)—— 所以帧率和延迟大概都很难看。

输入怎么下去

被控端 _客户1_数据到达 里解析这几个字段:

鼠标位置   鼠标类型   X坐标   Y坐标   文本类型

然后 鼠标移动 事件调 SetCursorPos(来自应用接口支持库,这是唯一一个直接调 Win32 的地方)。

控制端侧更直白 —— 它把自己在图片框上的鼠标动作打上类型码:

_图片框1_鼠标左键被按下   →  鼠标类型 1
_图片框1_鼠标右键被按下   →  鼠标类型 3
_图片框1_鼠标位置被移动   →  横向位置 / 纵向位置 / 功能键状态
_标签5_鼠标左键被按下     →  同样取 横向位置 / 纵向位置 / 功能键状态

功能键状态 是被一起上报的,说明 Ctrl/Shift 这类修饰键有传;文本类型 说明还有文本通道(键盘输入或剪贴板,从串里分不出来)。

整个协议就是明文坐标流:鼠标类型 X坐标 Y坐标 三个数一行,没有长度字段、没有校验、没有鉴权。任何能连上端口的人都能控制那台机器。2016 年的我完全没意识到这件事,因为它的使用场景是"宿舍里两台机器互玩"。

那个没做完的"代码"通道

控制端还有一组东西:

按钮1 = 开始执行
代码编辑框 / 备注编辑框
写入 代码 / 写入 备注
类型 X坐标 Y坐标
记录v
\Data\Data代码

我的读法是:这是一个轨迹录制 + 回放。理由 —— 类型 X坐标 Y坐标 正好是"一行记录"的格式,记录v 像是记录表,写入 代码/写入 备注 是把内容落盘到 \Data\Data代码,代码编辑框 载入它,开始执行 按里面的行重放。备注 是给每段轨迹起的名字。

但也存在另一种可能:代码编辑框 里放的是可执行文本,被控端收到后解释执行 —— 那它就是这套东西里唯一"像后门"的部分。

我没法在容器里定死是哪种(控制流读不出来,见旗舰篇那张 2 行/27 万串的表)。所以我按第一种写,并在仓库 README 里保留了第二种可能。这种"两种解释都成立、证据不足以裁决"的状态,在归档旧代码时非常常见,承认它比硬选一个更有用。


全 26 个工程里只活下来 2 行源码,这是其中一行

偏移 5645:

_启动窗口.标题 = “对方已经关闭客户端。”

易语言把表达式以源码文本形态存了下来,运算符还是全角的 =。这一行的特征是"我的控件名 _启动窗口 + 全角算符 + 我的文案",三者齐备才敢认。另一行在 LP(用户)/LP聊天器.e:如果 (客户1.连接 (#IP, 20930) = 假)。

其余 24 个工程,0 行。

从这行还能读出一个设计选择:被控端下线时,控制端不是弹窗、不是状态栏,而是直接改自己的窗口标题。_服务器1_客户离开 → 标题变成"对方已经关闭客户端。"。很土,但一眼就看得见 —— 我当时的 UI 水平大概就是这样。

被控端侧对应的是 您已经停止了远程遥控。(_按钮2_被单击,也就是主动断开)和 对方关闭了远程控制服务端。(_客户1_连接断开)。两边都知道怎么优雅地告诉对方"没了",这在 2016 年算做得不错的错误处理。


仓库

https://cnb.cool/t1mmoc/tspace/eyuyan/lp-remote-control

服务器.e + 客户端.e。标题里那个 QQ 号在容器里已被等长替换,仓库里读出来就是 LP 远程控制 ---byXXXXXXXXXX,这里也不再重复原号。10.240.211.233 这个内网地址按我自己的意思留着 —— 2016 年学校网的机器,早就不存在了。

同系列还有一篇 ARP 欺骗,那篇的重点是"捞到的语句到底算谁写的"。