rea-reverse

用 REA 查看 NSKeyedArchiver 的对象和引用

基于 REA 6.8.0发布者 rea-reverse更新于 约 3 分钟读完
本页目录

简短回答:运行 rea inspect-keyed-archive archive.bin --json,查看命名根节点、对象表和引用。UID 是对象表里的编号,本身不是对象。REA 读取序列化后的图结构,不会实例化归档中的类;单凭文件,也不能推断自定义类解码时会执行什么操作。

当 plist 里有 $archiver、$objects 和 $top,普通查看器却只显示一串 UID 时,这个命令能帮助你把引用关系接起来。macOS 应用分析教程介绍过它在整体流程中的位置;本文只围绕对象图、分页和边界展开。

下文在 macOS arm64 上用 REA 6.8.0、Node.js 26.12.1 实测。REA 6.8.0 要求 Node.js 版本满足 ^22.23.4 || ^24.22.1 || >=26.12.1。示例是手工构造的二进制 plist,用来展示序列化结构;它不是从真实应用提取的文件,也不声称 Foundation 一定能把其中的自引用解码成应用对象。

制作一个小样本

Python 3 的 plistlib 可以写入二进制 plist 的 UID。这里放入三个对象:nil 标记、一个字典和一个字符串。字典里的 peer 指回自己。

python3 - <<'PY'
import plistlib

archive = {
    '$archiver': 'NSKeyedArchiver',
    '$version': 100000,
    '$objects': [
        '$null',
        {'name': plistlib.UID(2), 'peer': plistlib.UID(1)},
        'demo',
    ],
    '$top': {'root': plistlib.UID(1)},
}
with open('demo.archive', 'wb') as f:
    plistlib.dump(archive, f, fmt=plistlib.FMT_BINARY, sort_keys=False)
PY

请使用自己的样本或有权检查的文件副本。安装 REA 可参考配置教程,运行前用 rea --version 确认版本。

查看根节点和对象表

rea inspect-keyed-archive demo.archive --json > archive.json

实测结果的 normalized_result 中有 archive_format: binary-plist、total_objects: 3 和 total_references: 3。下面只保留理解结构所需的字段:

{
  "roots": {"root": {"UID": 1}},
  "objects": [
    {"id": 0, "kind": "nil", "value": "$null"},
    {"id": 1, "kind": "dictionary", "value": {"name": {"UID": 2}, "peer": {"UID": 1}}},
    {"id": 2, "kind": "scalar", "value": "demo"}
  ]
}

从 roots.root 开始:UID 1 对应字典,字典的 name 指向编号 2 的字符串 demo,peer 则指回编号 1。另一个 references 列表记录这些边,包括自引用。自己遍历时要记录已访问的编号,否则遇到环会一直绕下去。

在 macOS 上,也可以用 plutil -p demo.archive 查看原始 plist。它打印的 UID 包装对象包含进程相关的内存地址;对照时看其中的 value 数字即可。REA 的证据还记录了归档 SHA-256,可以确认实际检查的是哪一份文件。

分页时保留原始编号

归档较大时,可以只取对象表的一部分:

rea inspect-keyed-archive demo.archive --offset 1 --limit 1 --json

本例返回对象 ID 1,不会把它重新编号为 0;结果显示 offset: 1、next_offset: 2 和 truncated: true。接着使用 --offset 2 --limit 1 读取对象 2。实测最后一页的 next_offset 是 null,但 truncated 仍为 true,因此是否还有下一页应看 next_offset,不要把 truncated 本身当成游标。引用列表与对象切片分别返回:被引用的目标不一定在当前页。

如果 $top 有多个命名入口,可以指定其中一个:

rea inspect-keyed-archive demo.archive --root root --json

这个样本只有 root。实测传入 --root absent 返回 invalid_request,并列出可用入口;--limit 0 会在参数校验阶段失败,应使用正数。

对象图能说明什么

解析器报告已序列化的字段、对象编号、引用,以及存在时的类名;它不实例化归档中的类,也不解释自定义类的业务含义。UID 0 可以表示 nil,但仅凭序列化值,无法区分普通 nil 与编码为 UID 0 的条件引用。缺失字段仍然是缺失,不能据此断言应用会采用哪种默认值。

保留原文件和摘要,检查有权使用的副本,并把对象图当作静态证据。如果需要知道应用运行时如何使用某个字段,还要单独查看相关解码代码;归档文件本身不会展示执行过程。

下一步