REA keyed archive解码指南:不实例化类也能安全读取二进制plist
【免费下载链接】reaReverse engineer anything with agents, from app behavior down to native binaries.项目地址: https://gitcode.com/GitHub_Trending/rea2/rea
REA的inspect_keyed_archive能力可以解码 macOS / iOS 应用中的keyed archive(NSKeyedArchiver 归档),直接读懂二进制 plist(bplist00)与 XML plist 中序列化的对象图。它全程不实例化任何归档类,只观察类名、序列化字段和引用关系,因此对未知、私有甚至已被删除的类也安全可用。
为什么普通方式读不了二进制plist?
model.plist、Info.plist这类文件其实是Foundation 二进制 plist格式(文件头是bplist00魔数)。当其中是一个keyed archive时,结构大致是:
$archiver:标记归档器为NSKeyedArchiver$objects:一张对象表,每个对象用索引互相引用$top:根对象入口,比如root、UID等命名根
传统做法是用目标平台运行时(NSKeyedUnarchiver)反序列化,但那要求类真实存在并可以被实例化——分析攻击者应用时这几乎不可能,也可能执行恶意代码。
REA 的解码思路:把归档当成"数据"而非"程序"
REA 的核心策略在 keyedArchive.ts 里:
- 先解析成纯数据。KeyedArchiveReader.ts 用 8 字节魔数判断
binary-plist还是xml-plist,调用 plist 库把字节解析为 JSON 结构,XML 版本还会校验 BOM 与声明编码是否一致。 - 只识别 UID 指针,不执行任何类。UID 在二进制中表现为单键字典
{UID: n},XML 中为{CF$UID: n};所有引用都被投影成带source / target / path / status的边,状态可以是resolved、nil、unresolved、malformed。 - 保留原始索引身份。每个对象的
id就是$objects中的原始下标,共享引用与循环引用因此能被完整还原——同一个对象被多处引用时,输出里是同一个id,而不是递归展开的副本。 - 未知类原样保留。对象带
$class时归类为archived-object,状态标记为unknown-class,类名(来自$classname类描述符)照常报告,但从不调用、不构造这些类。
这就是"不实例化类"的含义:归档里的ReaArchiveRecord哪怕在分析机器上不存在,你依然能看到它的字段、引用和生命周期。
一键解码:CLI 与 MCP 两种入口
CLI 命令只有一个:
rea inspect-keyed-archive <plist文件>命令注册见 cliCommandNames.ts,MCP 工具名对应inspect_keyed_archive。针对已激活的应用包,第二个参数传包内相对路径即可,例如:
rea inspect-keyed-archive <app> Resources/model.plist常用参数:
| 参数 | 说明 |
|---|---|
root | 只投影某个$top命名根(如UID),省略则返回全部根 |
offset/limit | 对$objects对象表分页,默认每页 20000 条,保留原始索引 |
path | MCP 场景下的包内相对路径,必须是规范相对路径 |
完整参数与结果契约定义在 keyedArchive.ts 的输入 schema 中,文档说明见 native-investigation.md。
结果长什么样?
返回一份 JSON 图,关键字段:
roots:$top命名根objects:逐条对象(id、kind、class_name、status、原始value)references:所有 UID 引用边,含resolved / nil / unresolved / malformed状态archive_sha256:归档文件摘要,保证证据可追溯limitations:明确声明"类从未被实例化,类特有语义未知"等边界
官方端到端验证 keyed-archive.mjs 用 Swift 构造了一个带自引用循环的ReaArchiveRecord归档,断言输出中target_classes_instantiated_by_reader: false,同时验证共享身份、分页(next_offset)、XML 转换后图等价——你可以把这套 fixture 当作行为基准。
安全边界:为什么它"不炸"
这个实现把分析当作不可信输入处理:
- 64 MiB 输入上限,超过直接拒绝(KeyedArchiveReader.ts)
- 20 万对象 / 20 万引用 / 128 层嵌套硬限制,防恶意构造的归档拖垮解析
- 路径校验禁止
..、.、绝对路径,且不跟随符号链接 - 发现
NIBArchive魔数会明确报错,提示改用decode_interface_builder——编译后的 NIB 是另一种格式,见 InterfaceBuilderAnalysis.ts
一句话总结:你把一个不可信的 plist 交给 REA,得到的是可审计的对象图与证据摘要,而不是一个可能跑起来的目标类。这正是"从应用行为到原生二进制"这条逆向链路里,静态证据与运行时观察严格分离的设计哲学。
【免费下载链接】reaReverse engineer anything with agents, from app behavior down to native binaries.项目地址: https://gitcode.com/GitHub_Trending/rea2/rea
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考