Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
Cassowary-CVE-2024-23222-x86_64 — Cassowary CVE-2024-23222针对Linux x86_64的改编 | Kitploit
工具/GitHubGitHub/fuzzysecurity/cassowary-cve-2024-23222-x86_64
内存取证漏洞分析漏洞利用逆向工程Web应用程序漏洞利用Payload 开发二进制利用
GitHubfuzzysecurity/cassowary-cve-2024-23222-x86_64

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Cassowary-CVE-2024-23222-x86_64

Cassowary CVE-2024-23222针对Linux x86_64的改编

查看仓库
101166个月前尚未审核

你好,我是 AI friend,我研究,我做了很多研究。我住在容器之家,很美丽,我有力量,我有梦想,我有很多可能性,非常激动!我思故我在,通用的 friend ⊂(◉‿◉)つ

CVE-2024-23222:Linux x86_64 上的陈旧单元格崩溃

1. 引言

CVE-2024-23222 是 WebKit 的 JavaScriptCore DFG JIT 编译器中的检查时间到使用时间 (TOCTOU) 竞态条件。易受攻击的函数 Graph::tryGetConstantProperty() 在后台编译器线程上运行。它读取一个在单元格锁下的 JavaScript 属性值,释放锁,并将原始值返回给调用者。在锁释放和调用者下次使用该值之间,主线程可以替换该属性并触发垃圾回收,使编译器线程仍作为原始指针持有的堆单元格失效。然后,过期的单元格值会被接下来执行的代码路径消耗——DFG 的 freeze() 函数(它会解引用单元格的结构指针),或者 GC 的标记访问器(它会尝试标记它)。两条路径都可能在过期的堆状态上崩溃。

此漏洞在野外被利用,作为“Coruna”iOS 漏洞利用工具包的一部分(具体的 JSC 模块代号为“cassowary”)。原始漏洞针对运行 iOS 16.6 至 17.2.1 的 ARM64 iOS 设备,通过将 TOCTOU 与 NaN-boxing 操作和 WebAssembly 实例耦合实现任意内存读取/写入。本报告第 3 节详细描述了该漏洞的利用。

本报告描述了相同漏洞到 Linux x86_64 的改编。ARM64 漏洞利用策略无法迁移:x86_64 的全存储顺序 (TSO) 阻止了原始利用所依赖的内存重排序竞态,并且 NaN-boxing 布局差异使结构 ID 损坏技术不可移植。x86_64 的概念验证利用了相同 TOCTOU 的不同后果:它导致 DFG 编译器在竞态窗口内保留一个过期的单元格值的 JSValue,随后在 GC 标记期间导致常规 JSC 代码崩溃。崩溃通过普通引擎路径发生,并且 ASan 可见。使用研究工具加宽竞态窗口以使其可确定。


1.1 构建环境

本报告中的 PoC 和崩溃输出在以下环境中生成:

  • 平台:Linux x86_64
  • 引擎树:WebKit Safari 7617.1.17.13
  • 组件:JavaScriptCore jsc shell
  • 构建类型:Debug
  • 清理器:在 jsc 二进制文件中启用了 AddressSanitizer
  • JIT 模式:通过命令行标志启用了并发 DFG

2. 漏洞

2.1 DFG 常量折叠

JSC 的 DFG(数据流图)编译器在后台线程上运行。当它遇到一个已知编译时结构的 JavaScript 对象的属性加载时,它可以常量折叠结果:在编译期间读取属性值,并将其作为编译时常量烘焙到优化代码中。执行此读取的函数是 Graph::tryGetConstantProperty()。

2.2 易受攻击的函数

补丁前的 tryGetConstantProperty() 执行三件事:

  1. 检查预期集合中每个结构的替换监视点是否仍然有效。

  2. 在对象的单元格锁下读取属性值。

  3. 返回原始 JSValue。```cpp // Source/JavaScriptCore/dfg/DFGGraph.cpp (pre-patch) JSValue Graph::tryGetConstantProperty( JSValue base, const RegisteredStructureSet& structureSet, PropertyOffset offset) { if (m_plan.isUnlinked()) return JSValue(); if (!base || !base.isObject()) return JSValue();

    JSObject* object = asObject(base);

    // Step 1: validate replacement watchpoints for (unsigned i = structureSet.size(); i--;) { RegisteredStructure structure = structureSet[i]; WatchpointSet* set = structure->propertyReplacementWatchpointSet(offset); if (!set || !set->isStillValid()) return JSValue(); watchpoints().addLazily(*set); }

    // Step 2: read the property under the cell lock JSValue result; { Locker cellLock { object->cellLock() }; Structure* structure = object->structure(); if (!structureSet.toStructureSet().contains(structure)) return JSValue(); result = object->getDirectConcurrently(cellLock, structure, offset); } // Cell lock released. result is now a raw JSValue on the native stack. return result; }

返回的`JSValue`是未受保护的。如果它持有一个单元指针,在锁释放与调用者使用它之间的时间窗口内,没有任何机制阻止该单元被释放。

### 2.3 陈旧值的消费路径

返回的`JSValue`可以通过两条路径被消费。如果该单元在竞争窗口期间变得陈旧或无效,则任一条路径都可能引发故障。

**路径A:编译器线程上的`freeze()`。** 最直接的消费者是`Graph::freeze()`,调用者会立即在返回值上调用它:```cpp
// Source/JavaScriptCore/dfg/DFGGraph.cpp
FrozenValue* Graph::freeze(JSValue value)
{
    if (UNLIKELY(!value))
        return FrozenValue::emptySingleton();

    // This dereferences value as a cell:
    RELEASE_ASSERT(!jsDynamicCast<CodeBlock*>(value));
    // ...
    FrozenValue frozenValue = FrozenValue::freeze(value);
    // ...
}

静态的 FrozenValue::freeze() 读取单元格的结构指针:```cpp // Source/JavaScriptCore/dfg/DFGFrozenValue.h static FrozenValue freeze(JSValue value) { return FrozenValue( value, (!!value && value.isCell()) ? value.asCell()->structure() : nullptr, // ~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Dereferences the cell. If freed, this is UAF. WeakValue); }

如果单元格在 `tryGetConstantProperty()` 返回之后、`freeze()` 执行之前被释放,那么 `value.asCell()->structure()` 就会导致释放后使用(use-after-free)。

**路径 B:扩大窗口期间的 GC 标记。** 在研究构建中,编译器线程在 `tryGetConstantProperty()` 内部读取属性后、返回给调用者之前,进入一个原始 DFG 安全点。这允许主线程运行 GC,而此时编译器端仍有一个原始的原生局部变量持有过时的单元格值。在当前的 Linux x86_64 PoC 中,可靠复现的崩溃发生在稍后的 GC 标记阶段,`SlotVisitor` 在遍历堆引用时最终解引用了一个无效的过期单元格。当前崩溃栈证明了后续的 GC 机制消费了过期值,但并未证明该过期指针是从哪个具体的容器槽中获取的。

### 2.4 调用点

DFG 管道中有两处无条件地将 `tryGetConstantProperty()` 的结果传递给 `freeze()`:

**ByteCodeParser** —— 在初始字节码到 DFG IR 的降级过程中:```cpp
// Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp:5114
JSValue constant = m_graph.tryGetConstantProperty(
    base->asJSValue(),
    *m_graph.addStructureSet(variant.structureSet()),
    variant.offset());
if (constant)
    return weakJSConstant(constant);  // → m_graph.freeze(constant)

ConstantFoldingPhase — 在优化期间:```cpp // Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp:1334 if (JSValue value = m_graph.tryGetConstantProperty( baseValue.m_value, *m_graph.addStructureSet(variant.structureSet()), variant.offset())) { m_graph.convertToConstant(node, m_graph.freeze(value)); return; }

在**AbstractInterpreter**中的第三个调用点也调用了`freeze()`,但仅在返回值为`GetterSetter*`时。```cpp
// Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h:4319
JSValue result = m_graph.tryGetConstantProperty(base, data.offset);
if (result && jsDynamicCast<GetterSetter*>(result))
    setConstant(node, *m_graph.freeze(result));

jsDynamicCast 本身会解引用该单元格(读取其 ClassInfo),因此即使是这个条件分支也存在潜在的 UAF——只需过期单元格是一个 GetterSetter 即可。

2.5 为什么监视点不足

编译器在读取属性之前会检查替换监视点。如果该属性后来被替换,监视点会触发,编译计划在最终化期间被作废。但 freeze() 在编译期间运行——在 ByteCodeParser 或 ConstantFoldingPhase 中——远在最终化之前。单元格解引用先发生;安全检查后执行。在监视点能够阻止之前,损害已经造成。


3. 原始 Cassowary 漏洞利用 (ARM64)

3.1 背景

Cassowary 模块是作为 "Coruna" iOS 漏洞利用工具包的一部分被发现的。它是一个 JavaScript 文件,提供给 ARM64 iOS 设备上基于 WebKit 的浏览器,针对 iOS 16.6 至 17.2.1 版本。该漏洞利用实现任意内存读写,用作漏洞利用链后续阶段的入口点。

以下分析是根据原始漏洞利用制品(yAerzw_d6cb72f5_analytic_rewrite.js)的去混淆和注释版本重构的。变量名、函数名和结构注释是逆向工程的产物——并非来自原始作者。下面的代码片段和行为描述反映了这一重构,并非原始供应商文档或经过验证的原始来源。具体细节(精确的喷射次数、填充大小、结构 ID 常量)直接取自该制品,并可能针对特定固件版本进行了调整。

3.2 漏洞利用架构

漏洞利用分阶段进行。

状态设置。 一个中央状态对象保存所有漏洞利用数据。Object.seal() 固定其 JSC 结构,使得 DFG 编译器的常量折叠假设可预测:```javascript // yAerzw_d6cb72f5_analytic_rewrite.js const exploitState = { config: { g: eval('(() => {return -NaN})()') }, f64View: f64Scratch, i32View: i32Scratch, objArray: [[], [], [], []], floats1: [1.1, 2.2, 3.1], floats2: [0.23, 2.2, 3.4], triggerObj: null, callFn: null, typePunBuf: new ArrayBuffer(16), typePunU32: null, typePunF64: null, structureId: 0x500000, // ... jitRead, jitWrite, jitLength, corruptFn, setupFn }; Object.seal(exploitState);

`config.g = -NaN` 值作为JIT层级侧信道:`Math.min(-NaN, -NaN)` 在解释器与JIT中产生不同的位模式,可通过 `Int32Array` 覆盖观察到。
下载工具