Files
Spine_lab/rea-analysis/REA-Spine分析报告.md
T
tianmo 2e7ab7c33c 阶段四:Spine 3.8.99 编辑器里挂上自建视图面板
目标:在 Spine 编辑器窗口内部加一个自己的视图面板(按钮 + 文字显示),
点击读取场景对象,并像原生面板一样出现在 Views 菜单、可停靠可折叠。

本轮打通:
1. 编辑器在我们自己的进程里完整启动
   - 类加载器必须是 URLClassLoader 本身(自检要求)
   - Editor.main 必须在 AWT EDT 上调用
   - jvm.cfg 第 21 行要以两个空格结尾 + -DSpineJava
   - Editor.D:I 版本号、fonts/、/tzax 资源、Java8 版 launcher.Launcher 替身
2. 3.8.99 全量解密 → 可读源码
   - 字符串 17251/17461(伪造同名调用帧对齐 key)
   - 混淆动态派发还原 53871 处(调引导方法取 MemberName)
3. 自建视图
   - agent 在内存里给 us 枚举加 spineView 常量(不改磁盘,避开完整性自检)
   - 面板 Actor 由外部反射填进 us.spineView.D
   - 复用已有图标做别名(不新建图片资源)
   最终 "Spine View" 出现在 Views 菜单,可与 Tree 并排停靠、折叠展开

另外:
- ViewPatcher/ViewAgent:视图枚举与面板挂载的字节码插桩
- DecAll/ObMap/rewrite_source:字符串解密、派发还原、可读源码生成
- 阶段四-视图面板挂载.md:完整记录做法与踩过的坑

不纳入版本管理:rea/(独立仓库)、tools 下的 JDK/Ghidra 工具链、
Spine.app 与 spine-lab 副本、从载荷派生的批量产物。
2026-10-16 13:34:18 +08:00

8.4 KiB
Raw Blame History

用 REA 分析 Spine.app — 首轮结果

日期:2026-10-15 工具:REA (rea-agents 6.5.0),仓库来源 https://github.com/morluto/rea.git 目标:/Users/tianmokeji/Desktop/临时/spine加功能/Spine.app(Spine 4.3.06 启动器 / 4.3.26 Professional 编辑器)

0. 怎么跑起来的

步骤 结果
git clone https://github.com/morluto/rea.git 直连 卡死在 72KB;换 ghfast.top 镜像也卡在 4.1MB
git -c http.version=HTTP/1.1 clone 成功,68MB / 约 4 秒。沙箱代理下 HTTP/2 大包传输会中断,强制 HTTP/1.1 可绕过
npm install -g rea-agents 安装 rea 6.5.0,CLI 可用
rea doctor --client codex MCP 未注册;Hopper/Ghidra/IDA 均缺失(深度反汇编不可用)

REA 的 MCP 注册需要重启客户端才生效,本轮全部走 CLI(REA 技能文档认可的同源路径)。

踩坑:REA 的 inspect_plist / inspect_signature / observe-native-ui 会现场用 xcrun 编译辅助进程,沙箱内会失败,必须放沙箱外执行。

1. 包结构(REA inspect-artifact)

  • 图规模:185 节点 / 239 次出现 / 238 条边,coverage.status = complete
  • Info.plist:CFBundleShortVersionString 4.3.06、LSMinimumSystemVersion 10.13、文档类型 .spine
  • 签名(inspect-signature):ad-hoc 签名,authorities: []、team_identifier: null、hardened_runtime: false、无 entitlements

/Applications/Spine.app 与工作区副本的 Contents/MacOS/Spine SHA-256 完全一致(fea6e69a…4340f2),两个 libjvm 大小也一致 → 工作区副本即真实安装的等价副本。

2. 启动链路(REA inspect-macho + trace-dylib-resolution)

Contents/MacOS/Spine 只有 88KB,是纯原生启动器:

  • 链接:Foundation / AppKit / Cocoa / libobjc / libSystem(不含 libjvm,JVM 是运行期 dlopen 进来的)
  • 内置字符串给出完整启动参数:
-Djava.class.path=%@/Contents/MacOS/launcher/1/launcher.jar
%@/Contents/MacOS/launcher/1/lib/jli/libjli.dylib   →  失败则  %@/Contents/MacOS/launcher/2/lib/libjli.dylib
%@/Contents/MacOS/launcher/1/lib/server/libjvm.dylib → 失败则  %@/Contents/MacOS/launcher/2/lib/server/libjvm.dylib
JNI_CreateJavaVM
主类候选:com/esotericsoftware/spine/editor/launcher/Launcher、com/esotericsoftware/spine/launcher/Launcher

launcher.jar 只有 72 个条目 / 2MB(s/*.class 混淆包 + 一个 374KB 的 Launcher.class)——它只是引导壳,不是编辑器本体。

3. 真正的加固点在 launcher/2 的 libjvm(已实测确认)

REA 对两个 libjvm 的段对比:

launcher/1/libjvm.dylib launcher/2/libjvm.dylib
文件大小 13.3 MB 58.4 MB
__TEXT 8.5 MB 8.1 MB
__DATA 313 KB 48,896,800 B(≈48.9MB,文件偏移 8,933,376)
HotSpot 调试段 __dof_hotspot* 有 无
lib/modules(jimage) 46,549,519 B 存在 不存在

launcher/2/libjvm 的 __DATA 前 1MiB 熵 7.59 bit/byte,且扫描不到 PK\x03\x04 / PK\x05\x06 / jimage magic → 是加密载荷,不是明文 jar。

配合缺 modules 仍能正常启动这一事实,结论是:

launcher/2 把 Java 模块镜像(jimage)连同编辑器类一起搬进了 libjvm 的 __DATA 并加密, 由打过补丁的 JVM 在加载时解密 —— 这就是「类不在 jar 里」的真正原因。

运行期到底加载了哪个?(零干扰实测)

对正在运行的 Spine(PID 91687)做 lsof / vmmap 只读采样:

…/launcher/2/lib/libjli.dylib
…/launcher/2/lib/server/libjvm.dylib      ← 实际使用 launcher/2,不是 launcher/1

即:launcher/1 是引导尝试路径,真正的运行时是 launcher/2,与第 3 节吻合。

4. 新发现:编辑器原生核心在临时目录里

同一份 lsof 显示编辑器真正的原生库并不是从 app 包里加载的,而是运行期解包到临时目录:

$TMPDIR/.spine/tianmokeji/4.2.43/{libspine-editorarm64.dylib, libgdxarm64.dylib,
                                   libgdx-freetypearm64.dylib, libjglfwarm64.dylib,
                                   liblwjgl.dylib, openal.dylib}
$TMPDIR/.spine/tianmokeji/L4.3.06/libjglfwarm64.dylib
$TMPDIR/.spine/tianmokeji/4.3.26/libspine-editorarm64.dylib

REA inspect-macho 对 libspine-editorarm64.dylib(3,047,440 B)的结果:

  • install name target/libspine-editorarm64.dylib,依赖仅 Cocoa/AppKit/libSystem/libobjc/libc++
  • 导出符号(REA 导出列表)里最有价值的几条:
_Java_com_esotericsoftware_spine_editor_Editor_licCheck
_Java_com_esotericsoftware_spine_editor_Editor_register
_Java_com_esotericsoftware_spine_editor_Editor_expire
_Java_com_esotericsoftware_spine_editor_audio_Audio_pauseDeviceJni / resumeDeviceJni

另外导出大量 Eigen 符号(Eigen::Matrix、squared_edge_lengths),说明网格/几何解算是 Eigen 在跑。

这条信息对「在编辑器内部加粒子面板」很关键:编辑器原生核心是磁盘上、未加密、可直接分析的 dylib, 比拿 libjvm 里那 49MB 加密数据段要现实得多。

日志侧证据:~/Library/Application Support/Spine/spine.log → Spine Launcher 4.3.06 (macOS Apple Silicon) / Starting: Spine 4.3.26 Professional。

5. REA 运行时 UI 观测:被一个具体阻塞点挡住

REA 有 observe-native-ui / capture-native-ui-scenario(读取窗口的无障碍树 + 截图), 本来最适合你现在的面板研究。实测报错:

process-identity-unavailable: Selected application does not expose a launch time

定位(读源码 + 实测):rea/bridge/native/ReaNativeUI.swift 用 NSRunningApplication.launchDate 做 PID 复用校验,而当前实例由命令行直接 exec 启动, LaunchServices 没有登记它:

pid=91687 bundleIdentifier=nil bundleURL=…/spine-lab/Spine.app launchDate=nil

解决办法:让 Spine 由 LaunchServices 启动(open -na <app> --args … 或访达双击), 新进程才有 launchDate,REA 的 UI 树观测即可通过。

6. 本轮结论清单

问题 状态 依据
编辑器类为什么不在 jar 里 已答 launcher.jar 仅 72 条目;launcher/2 无 lib/modules,libjvm __DATA 48.9MB 高熵
实际运行哪个 libjvm 已答(实测) lsof/vmmap:launcher/2
编辑器原生代码在哪 已答 $TMPDIR/.spine/<user>/<ver>/libspine-editorarm64.dylib
编辑器原生库的 JNI 面 已答 REA 导出:Editor_licCheck/register/expire、Audio_*
包内清单 / 签名 / plist 已答 REA inventory / signature / plist
深度反汇编(伪代码、函数、xrefs) 未答 缺 Hopper/Ghidra/IDA 提供商
编辑器窗口内面板运行时结构 未答 需要先解决 launchDate(见第 5 节)

7. 建议的下一步

  1. 装 Ghidra(免费):GHIDRA_INSTALL_DIR 指过去,REA 立刻能对 libspine-editorarm64.dylib、launcher/2/libjvm.dylib、libgdxarm64.dylib 出伪代码与 xrefs。
  2. 用 LaunchServices 启动 Spine,然后跑 rea capture-native-ui-scenario,把编辑器面板/工具栏的无障碍树和可点击路径抓出来(可直接验证「窗口内面板是否可见、能否点」)。
  3. 面板注入目标从 libjvm 改为 libspine-editorarm64.dylib:磁盘未加密、有完整 JNI 导出、可被 LLDB/Frida 附着。
  4. 若要 REA 的 MCP 工具而非 CLI:rea setup --client codex --dry-run --json 看变更清单, 确认后 --yes 并重启 Codex(注册写入 ~/.codex/config.toml)。

8. 证据文件

文件 内容
rea-analysis/01-inspect-artifact.json 应用清单图(185 节点,含 SHA-256)
rea-analysis/02-macho-Spine.json 原生启动器 Mach-O(依赖、段、入口)
rea-analysis/03-signature.json ad-hoc 签名详情
rea-analysis/04-dylib-resolution.json dyld 解析(8 个可执行根,含 ffmpeg/oxipng)
rea-analysis/05-macho-libjvm2.json / 06-macho-libjvm1.json 两个 libjvm 段对比
rea-analysis/07-native-ui.json 运行时 UI 观测尝试(记录阻塞原因)
rea-analysis/08-macho-spine-editor.json 编辑器原生库(约 1MB 导出/导入明细)
rea-analysis/tools/list-windows.swift 列出窗口 ID(配合 REA)
rea-analysis/tools/check-app-identity.swift 检查启动时间/LaunchServices 身份(定位第 5 节问题)