目标:在 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 副本、从载荷派生的批量产物。
8.4 KiB
用 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. 建议的下一步
- 装 Ghidra(免费):
GHIDRA_INSTALL_DIR指过去,REA 立刻能对libspine-editorarm64.dylib、launcher/2/libjvm.dylib、libgdxarm64.dylib出伪代码与 xrefs。 - 用 LaunchServices 启动 Spine,然后跑
rea capture-native-ui-scenario,把编辑器面板/工具栏的无障碍树和可点击路径抓出来(可直接验证「窗口内面板是否可见、能否点」)。 - 面板注入目标从 libjvm 改为
libspine-editorarm64.dylib:磁盘未加密、有完整 JNI 导出、可被 LLDB/Frida 附着。 - 若要 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 节问题) |