从 DFG JIT 一个字节的类型错误到 iPhone 任意内存读写

本文记录了 WebKit JavaScriptCore DFG 编译器中一个类型声明错误的完整利用过程——从 DFGNodeType.h 宏表中的 NodeResultInt32(应为 NodeResultJS),经 GC 写屏障绕过触发 Use-After-Free,一步步升级到在 iPhone 真机(iOS 26.1,非越狱原厂)上稳定实现任意内存地址读写(AAR/AAW)。端到端成功率约 80%。 字段 值 漏洞位置 Source/JavaScriptCore/dfg/DFGNodeType.h — MapIterationEntryKey 节点 Bugzilla 304950 rdar 167200795 修复提交 3f6f7836068(cherry-pick 47b55468bf82) 受影响版本 Safari ≤ 26.2 (WebKit 7623.1.14.11.9) 修复版本 Safari 26.3 (20623.2.7) — 安全公告 目标设备 iPhone / vphone (iOS 26.1) 及 macOS 26.2 Exploit 成功率 ~80%(失败时通常表现为页面重载,罕见无响应崩溃) 漏洞概述 JavaScriptCore (JSC) 将 JavaScript 编译为高效机器码的过程中,有一个名为 DFG(Data Flow Graph)的中间层编译器。DFG 对每个中间表示(IR)节点附加了一个 输出类型声明(NodeResult),告诉后续优化 pass 这个节点产出什么类型的值。 问题出在一张巨大的宏定义表中,一个单词写错了: // Source/JavaScriptCore/dfg/DFGNodeType.h, line 592 macro(MapIterationEntry, NodeResultJS) \ macro(MapIterationEntryKey, NodeResultInt32) \ // ← BUG: 应为 NodeResultJS macro(MapIterationEntryValue, NodeResultJS) \ // ← 正确 MapIterationEntryKey — Map.forEach() 回调中获取 key 的节点 — 被声明为只产出 32 位整数。但看一下运行时的实际实现: ...

2026年3月23日 · 13 分钟 · Xin

如何用 Frida 动态获取 ObjC Block 的参数类型

你是否想过窥探 iPhone 或 iPad 上的应用,观察它到底在处理什么数据?今天介绍一种简单而强大的方法——使用 Frida 动态获取 iOS 应用中 Block 的参数类型。 什么是 Block? 在 iOS 编程中,Block 是一小段可以被传递、稍后执行的自包含代码。可以把它想象成你把电话号码(Block)给朋友,朋友在有重要事情时给你回电话。 有时你想精确观察这些 Block 接收了什么输入、产出了什么结果。Frida 可以在不修改应用原始代码的情况下帮你做到这一点。 Block 的内存结构 理解 Block 的内部结构有助于你准确地获取它的签名和参数信息。以下是 Block 的内存布局: struct Block_literal_1 { void *isa; // 指向 &_NSConcreteStackBlock 或 &_NSConcreteGlobalBlock int flags; // 标志位,指示 block 类型和属性 int reserved; // 保留字段 void (*invoke)(struct Block_literal_1 *, ...); // 指向 block 实现的函数指针 struct Block_descriptor_1 { unsigned long reserved; // 保留,通常为 NULL unsigned long size; // Block_literal_1 结构体的大小 // 可选的辅助函数 void (*copy_helper)(void *dst, void *src); // flag (1<<25) 置位时存在 void (*dispose_helper)(void *src); // flag (1<<25) 置位时存在 // 包含签名的 block 必须有此字段 const char *signature; // flag (1<<30) 置位时存在 } *descriptor; // 捕获的变量(如果有) }; Block 内部的 signature 提供了返回类型和参数类型等关键信息,是动态分析的基础。 ...

2025年5月11日 · 2 分钟 · Xin