Немного пятничный пост. В последнее время заметил что есть реклама которая пробивает сразу 2(!) расширения блокирующие её. И обычно это очень низкокачественная и навязчивая реклама. Решил разобраться как так, и, возможно, даже попробовать побороть эту дрянь. Кому интересно — прошу под кат (осторожно, много картинок).

А чем мешает то?


Я — ярый сторонник блокировки рекламы. Особенно — низкокачественной. Если я не хочу смотреть рекламу на определённом сайте — то такая реклама это деньги на ветер для рекламодателя (бесполезный показ), неуважение ко мне, и нулевой профит вообще для кого либо. Оговорюсь сразу — есть сайты на которых я сознательно вырубаю адблоки (тот же хабр, например). Это сайты которые выбирают тематическую рекламу, а не «АЙФОНЫ СО СКЛАДА 70% СКИДКА!».

И тут на днях на одном дружественном мне сайте (новостной портал в моём городе) появляется ну ооочень низкопробная реклама в стиле «электрики долго скрывали — чтобы экономить электричество надо всего лишь...». Сначала я подумал что их взломали, но потом оказалось что админ этого сайта просто ввязался в какую-то партнёрку которая работает поверх адблока. И мало того что такая реклама это просто стыд, так она ещё и ломала местами вёрстку. Закрыть её стало делом принципа.

Попытка 1 — в тупую, или...


… «пфф, да ща напишу кастомный фильтр для адблока». Пишем фильтр, сохраняем, тестим… А страница то ломается, прям совсем. И косметические фильтры (блокировка по css) тоже не работают. Как так? Но, видимо, поэтому и не было такого фильтра «из коробки». Как окажется позже — наш «зловред» хитрый и заинлайнен, а хромообразные браузеры не позволяют расширениям блокировать инлайновые скрипты. Ну и ну…

Попытка 2 — меняем адблок


Слово «адблок» уже давно стало нарицательным, как джип или ксерокс. Так что на самом деле у меня стоит не adblock а ublock (надеюсь это не сочтут за рекламу?). Поэтому для начала тестим работу разных блокировщиков из топа выдачи, которые обещали скрываться от хитрозадых скриптов, и блокировать даже их. Выхлопа, на самом деле, это не дало, и часть рекламы продолжала пролезать (хотя и не вся, но всё же).

Попытка 3 — смотрим врагу в глаза


И нет, я не имею ввиду админа ресурса :) Приступим к самому интересному. Лезем смотреть исходники страницы, в надежде найти нашу гадость. И… Бинго!



Огромная обфусцированная кодина, которая явно тут не спроста. Смотрим похожие сайты (в плане рекламы) — да, это явно оно. И обфусцировано добротно, не банальным p.a.c.k.e.r-ом.

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



Переменная «а» у нас хранит весь набор слов которые нельзя было заменить на однобуквенные. А в первой части мы видим много фрагментов вида а[42], a[69], и т.д. Обфускация оказалась не такой уж и сложной. Делаем замену которая не гарантирует нам работающий код, но сильно облегчит чтение.



Уже видно более-менее читаемый код. Пропускаем через beautifier, и находим желаемый участок кода (я удалил неважный код чтобы поместиться в одном скрине).



Эта зараза делает запрос на определённый юрл, и в случае отвала (попадания в фильтр адблока, например) — вызывает window.stop(), чем ломает всю страницу. Довольно хитро, и встроенным функционалом адблока это, вроде, не решаемо. А заблокировать весь скрипт не позволяет хром, ибо инлайн.

Что же делать?

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

Работает оно следующим образом — перехватываем запрос на «проверочный» адрес, с помощью жёлудей и спичек элегантного костыля задерживаем его на 5 секунд, а в это время быстро-быстро отправляем сообщение на страницу. Страница принимает это сообщение, и тут же переопределяет window.stop на пустую функцию. Тем временем зловред таки получает отказ, ехидно улыбается, лупит по window.stop, и… ничего. Скрипт доволен (он уверен что всё сломал и пользователь таки отключит адблок), расширение довольно (потому что, как говорит мой коллега, на каждый хитрый зад найдётся хитрый мужской половой орган), пользователь (то есть — мы) доволен. Вероятно, в скором времени разработчики этой заразы что-то придумают и на это, но, пока-что, реклама успешно рубится.

Расширение заливать никуда не стал, ибо надо платный аккаунт, это proof-of-concept, сегодня пятница, и ещё 1000 и 1 малозначимая отмазка. Так что код на гитхаб.

UPD. В комментариях возникли вопросы, поэтому тезисно о главном:
Почему не блокировать все инлайн скрипты — потому что это сломает логику многих сайтов, не подходит
Почему не возвращать 200 (обманывать) — потому что хром так не умеет. Либо 500ую, либо пропускаем
Почему не переопределять window.stop сходу — потому что надо успеть во временной отрезок когда DOM-блок head уже существует, но аякс ещё не вернулся. Это ограничение extensions API.

Комментарии (56)


  1. vesper-bot
    20.10.2017 12:43

    Думаю, проще было бы переопределить window.stop() сразу, сохранив старую функцию в другом объекте, и как минимум логать к ней доступ по stackTrace или как оно в джаваскрипте устроено. Можно даже парсить его, определяя, насколько глубоко сидит вызвавший кролик, и насколько вызов стопа в этом месте валиден, и пропускать внутрь, если да. По идее, должно сломать всех любителей останавливать все скрипты в окне.


    1. vlreshet Автор
      20.10.2017 12:54

      Думаю, проще было бы переопределить window.stop() сразу

      А как? Chrome extension не может напрямую модифицировать windows объект (в документации так написано, да и тестил). Поэтому нужно только вставлять в тело html инлайн скрипт который это сделает. А вставить его надо до того как запустится наш зловред. А пока документ не создал блок head — нам некуда инлайнить. А если head уже отрисовали — то уже поздно, так как зловред в нём. Как-то так.


    1. esc
      21.10.2017 00:04

      В этом коде window.stop не используется. Хотя есть ребята, что сделали по образу и подобию через window.stop, но по факту способов убить страницу на столько много, что надоест переопределять.

      Есть способ лучше, переопределить XHR, как сделали в user script на форуме список для адблока. Конечно, это обходится, но, как минимум, код на сайтах нужно будет менять.


  1. Aingis
    20.10.2017 12:49

    Довольно хитро, и встроенным функционалом адблока это, вроде, не решаемо. А заблокировать весь скрипт не позволяет хром, ибо инлайн.
    Вообще-то uBlock умеет отключать инлайн-скрипты (надо включить галочку «Я опытный пользователь»):



    Или можно добавить фильтр CSP, который тоже будет блокировать инлайн-скрипты или даже хитро решать какие запускать с помощью параметра nonce, но для этого уже надо отдельное расширение.


    1. vlreshet Автор
      20.10.2017 12:55

      А если не надо вырубать вообще все инлайн скрипты, а только этот? Лично у меня не получилось составить такой фильтр. Если умеете — научите :)


      1. vlreshet Автор
        20.10.2017 13:05

        И в догоночку — сайт который меня и смутил — CMS на DLE. И там дофига логики в виде инлайн js. Поэтому блокировать всё через CSP — не выход


      1. VereVa
        20.10.2017 15:38

        body > script:nth-child(N), где N это его порядковый номер
        но это не спасет, если они сменят порядок скриптов


  1. PbIXTOP
    20.10.2017 12:49

    Смотря на исходники увидел URL сети — не проще его было заблокировать?
    Блокировка есть ведь разная бывает. при желании можно и 200 отдать.
    Посмотрел свои списки — этот домен оказался в рабочих proxy-anonimazer списках.


    1. vlreshet Автор
      20.10.2017 12:50

      Извиняюсь, но вы читали пост? Если

      не проще его было заблокировать?
      если скрипт видит что этот юрл заблокирован — он ломает всю страницу. Собственно в этом и вся проблема


    1. vlreshet Автор
      20.10.2017 13:02

      А, а про «200» отдать — может я что-то упустил в документации, но вроде нельзя возвращать произвольные хедеры в ответ скрипту. Или блокируем, или нет, третей опции не дано. Плюс если оно увидит что есть адблок, и при этом получило 200ую — начнёт совать рекламу, с чем мы, собственно, и боремся.


      1. datacompboy
        20.10.2017 14:40

        /etc/hosts
        127.0.0.1 evil.network

        /etc/nginx/sites-enabled/evil.network
        location ~ / {
        return 200 'bye';
        }


        1. vlreshet Автор
          20.10.2017 14:44

          С таким успехом можно сам сайт к себе перенаправить, вырезать из html ненужные скрипты, и отдавать обратно :)


          1. datacompboy
            20.10.2017 14:50

            разница большая — между «делать что-то с контентом» и «зараутить мусор в девнул».
            у меня много таких сетей как раз зароучено на 127.0.0.1.
            Те, кто не мешают жить при этом — просто довольствуются 404м. те, кому надо чтоб сервер спел и сплясал — получают 200тку.


            1. vlreshet Автор
              20.10.2017 14:59

              Ну это нормальное решение для девелопера, но не очень для рядового пользователя. Кстати, я не совсем понял из кода этой заразы, так как он специально запутан, но по-моему если оно получает 200, но респонс ему не подходит — то оно считает это провалом.


              1. esc
                21.10.2017 00:08

                Не считает. Но поправить это не проблема. Тогда вместо /dev/null прийдется делать эмулятор. Для каждого сайта.


          1. crower
            20.10.2017 19:05

            Пришёл к такому-же решению, когда не смог при помощи uBlock победить одну раздражающую текстовую рекламу. Раздражает, потому что красным цветом выбивается из сине-голубой гаммы сайта и шрифты заголовка рекламного блока значительно крупнее информативного сайта. uBlock, кстати, блок прячет, но только при первой блокировке, а потом эта зараза снова вылазит. Наверно потому что в скрипте рефрешер периодически обновляет спрятанный блок. А индентификторы блока на каждой странице свои.
            Апач всё-равно крутится на компе, страницей/сайтом пользуюсь с разных компов. Так почему бы ходить не на оригинальный сайт, а на локальную страничку, в которой вырезана вся дрянь?


            1. mihmig
              21.10.2017 00:20

              как-то давно баловался с proxomitron, а как Вы предлагаете вырезать из страниц рекламу?


              1. crower
                21.10.2017 06:31

                Сейчас у меня этим занимается скрипт на perl, поэтому реализовать можно любой достаточно сложный алгоритм поиска и удаления. Вот сегодня научил его подгружать новую версию скрипта, включенного в страницу, когда разработчики сайта, внеся изменения, меняют и его имя (типа с script.013.js на script.014.js).


        1. algotrader2013
          21.10.2017 10:22

          Какое-то время оно работать будет. Но чувствую, что следующим шагом упыри начнут не просто ждать 200 а ждать подписи переданного хеша секретным ключом сервера.


      1. nmaltsev
        21.10.2017 13:03

        chrome.webRequest.onBeforeRequest.addListener(function(e){
        	// some conditions
        	return {redirectUrl: 'data:text/html;charset=utf-8,<html style="background:#000;"><script>window.close();</script></html>'}; 
        });

        Есть еще возможность сделать redirect на страницы закодированные в data url (html страницу, картинку или скрипт)


        1. esc
          21.10.2017 13:49

          А с чего вы взяли, что ответ будет интерпретироваться исполняться как html со скриптами?


          1. nmaltsev
            21.10.2017 19:02

            Если редиректить страницу, открывающуюся в новой вкладке, или в iframe, то будет выведена вся закодированная страница со всеми скриптами.

            П. С. решение проверено на практике


  1. Dmitry_7
    20.10.2017 13:25

    Причина-то в администраторе сайта. Почему, используя методы социальной инженерии, не принудить отказаться?
    Например, запустить в этой сети баннер 'наш админ иванов и.и. — ....'?


    1. esc
      21.10.2017 00:09

      Кому вы предлагаете это запускать? Владельцу сайта, который сознательно установил подобный код, чтобы не лишать сайт дохода от пользователей с блокировщиками.


  1. inoyakaigor
    20.10.2017 13:38

    Во, может тут кто-нибудь мне подскажет как заблокировать Яндекс.директ который порой пролезает сквозь блокер? Например на тех же сайтах Яндекса


    1. vlreshet Автор
      20.10.2017 14:22

      А дайте пример такого сайта, ради интереса)


      1. juray
        20.10.2017 15:29

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

        Сейчас вот проверил — в яндексовой почте сетевой рекламы не вижу даже при отключенном адблокере (иногда всплывает внизу панель с рекомендацией мобильного приложения).

        А вот на мэйле реклама пролезает, и в коде страницы творится что-то, на мой взгляд довольно безнадежное — все идентификаторы обфусцированы примерно так:

        <div id="MkgdI63" class="MkgdI5P MkgdI26"></div>

        причём, при каждом заходе на страницу эти строки меняются. То есть косметические фильтры ublock тут не пригодны.


        1. vlreshet Автор
          20.10.2017 15:32

          Кстати да, с этим беда. Я на страницу mail.ru себе stylish делал, чтобы был только бокс с количеством новых писем, и черный фон (без рекламы, новостей, и.т.д.). Пару дней назад стиль сломался, снова всё вылезло. Ну ёпт…

          причём, при каждом заходе на страницу эти строки меняются.

          А меняются вооообще-воообще все названия, или есть статические? Тогда к ним можно было бы привязаться, и потом :child(n) делать


          1. esc
            21.10.2017 00:11

            А что, проблема генерировать случайное к-во пустых div перед нужным, чтобы :child(n) не работало?


          1. juray
            21.10.2017 13:59

            есть статические — относящиеся к шапке и подвалу. Всё что в середине — выглядит вот так.
            Уточнение: всё это веселье — если смотреть инспектором, или через интерфейс «заблокировать элемент» в uBlock, то есть в уже сформированном DOM. В исходном коде страницы во встречающихся фрагментах html идентификаторы не обфусцированы. Только попробуй, догадайся, какой див к какому элементу относится.


      1. esc
        21.10.2017 00:10

        www.liveinternet.ru/stat/ua например. Адблок под Хромоподобными браузерами не справляется. uBlock- да.


      1. inoyakaigor
        21.10.2017 01:50

        Я.Музыка например


        1. crower
          21.10.2017 08:56

          На Я.Музыке ghostery сказал, что заблокировал 6 элементов, среди которых и Yandex.Direct.
          В правом сайдбаре остались одна картинка-рекомендация, ведущая на какие-то тизеры.
          Там, кстати, исходного html практически нет — всё на скриптах, так что победить можно только редактируя уже сформированную страницу, правя на лету скрипты и перехватывая запросы.


      1. qw1
        21.10.2017 10:54

        А дайте пример такого сайта, ради интереса)

        Тут — https://habrahabr.ru/post/338580/#comment_10434492


      1. Xandrmoro
        21.10.2017 12:55

        drive2.ru юблок вообще отказывается грузить, если не добавить яндекс в исключения, например.


    1. sotnikdv
      21.10.2017 08:24

      У меня кончилось блоком всего от Яндекса. На DNS. И mail.ru тоже, глобально, все, что относилось к mail.ru. Слишком тесно сплели функционал и рекламу, которая для меня очень навязчива. Пришлось отказаться от функционала.


      1. qw1
        21.10.2017 10:58

        См. мой коммент чуть выше. Там пример, когда блока сетей Яндекса недостаточно, потому что сайт-партнёр устанавливает прокси на своём домене, и через этот прокси загружаются как все баннеры, как и остальной контент сайта.


  1. mihmig
    20.10.2017 14:23

    >>не adblock а ublock
    Как дети подрастут — буду им рассказывать сей хрестоматийный пример, как сложно заработать репутацию и легко её потерять.


    1. lifestyle
      20.10.2017 18:06

      «Ничего личного, это просто бизнес» — другой хрестоматийный пример, расскажите им позже, как подрастут еще.


  1. eugenebb
    20.10.2017 16:52

    Было бы интересно сделать возможность менять ID для рекламы на чьё-нибудь, кого не против поддержать (можно сделать через webRequest.onBeforeRequest)

    Т.е. ходишь по интернету и большинство просмотренной рекламы идёт на добрые дела.


    1. vlreshet Автор
      20.10.2017 17:09

      Интересно, не проверяют ли рекламные площадки хедер referrer.


    1. esc
      21.10.2017 00:21

      А как вы определите что там за id используется у конкретной рекламы?


  1. caudatecoder
    20.10.2017 19:06
    +3

    Встречал похожий пример борьбы с adblock'ом, только не на самой рекламе, а на модальнике с просьбой отключить adblock.
    Выпиливание модальника из DOM или скрывание с помощью css приводило к перезагрузке страницы. Аналогично нашел обфусцированный inline script, который с интервалом в 2 секунды проверял целостность модальника.
    Вот такой хак со stackoverflow для остановки всех интервалов позволил убрать назойливый модальник.
    Может и вам в вашем расширении однажды пригодиться :)


    1. sotnikdv
      21.10.2017 08:27

      Жму плечами и вкидываю сайт (как ни странно, на действительно нужных мне серьезных сайтах таким не страдают) в blacklist. При попытке захода на него предлагает или поискать нормальный источник или посмотреть котяток.

      Пока ни одного случая, когда в блеклист попадало что-то нужное, ибо страдают таким только откровенные помойки, из того что я сталкивался.

      Так что режем хвост по самую голову. У нас тут serious business, нам не до шуток ;)


  1. esc
    21.10.2017 00:01

    ublock этот код обходит (со своими стандартными списками). Там сейчас появилось что-то вроде встроенного user script. Но очень топорным способом сейчас это делается.

    adblock вообще очень инертен в этой борьбе. Там только вебсокет нормально блокировать научились в конце августа. Видимо, неудачи с монетизацией сильно подкосили их энтузиазм.


  1. esc
    21.10.2017 00:23

    var startTime = new Date().getTime();
            var randNumber;
    
            while ((new Date().getTime() - startTime) < 5000){
                randNumber = Math.random(); // because an error can happen if this block is empty, or needless
            }
    


    Без обид, но вы бы еще майнер встроили…


    1. vlreshet Автор
      21.10.2017 09:49

      Я знаю что код странный, но на то есть причины. While с пустым телом как-то нестабильно себя ведёт, очень нестабильно в плане времени. Пробовал делать просто какой-то мусор в теле — видимо компилятор палит то что он не используется далее, оптимизирует, и в итоге получается снова пустое тело. И только вот так получилось заставить нормально держать ровно 5 секунд ?\_(?)_/?


      1. esc
        21.10.2017 09:55

        Код не то. чтобы странный, он 5 секунд выедает 100% ядра в бесконечном цикле. После изменения кода на такой, что будет делать 3-4 попытки, пользователю прийдется 15-20 сек ждать пока ваш рандомайзер открутится. Не говоря уже о том, что могут пошутить и бесконечное число попыток сделать, при другом сценарии это мешать не будет, а вот с вашим решением в доме появится лишняя печка.


        1. vlreshet Автор
          21.10.2017 12:58

          На самом деле это одна из причин почему я не допиливал это расширения до продакшн-вида, чтобы можно было залить его в маркет, и т.д. Потому что хром не умеет по-человечески задерживать запрос, а мой костыль — ну очень уж не оптимальный. Но, как proof-of-concept — почему бы и нет?


          1. esc
            21.10.2017 13:51

            Не мучайте браузеры тех, кто решится ваше расширение поставить. Вот пример для этого прокси из ruadlist js fixes:

            // piguiqproxy.com circumvention prevention
                scriptLander(
                    function()
                    {
                        let _open = XMLHttpRequest.prototype.open;
                        let blacklist = /[/.@](piguiqproxy\.com|rcdn\.pro)[:/]/i;
                        XMLHttpRequest.prototype.open = function(method, url)
                        {
                            if (method === 'GET' && blacklist.test(url))
                            {
                                this.send = () => null;
                                this.setRequestHeader = () => null;
                                console.log('Blocked request: ', url);
                                return;
                            }
                            return _open.apply(this, arguments);
                        };
                    }
                );


            1. vlreshet Автор
              21.10.2017 18:32

              Я выложил не готовое расширение, а код на github. Человек который знает как его установить себе и запустить — наверняка прочитает исходники, и не будет мучать свой браузер не зная того) Да и опять таки — я нигде не говорил что у меня шикарное решение которое подойдёт всем и каждому.

              P.S. ruadlist js затестил, работает, блокирует. Спасибо за совет)


              1. Tallefer
                22.10.2017 02:30

                Прекрасно! Чем меньше параллельных костылей, тем легче браузеру. :)


  1. Ivan_83
    21.10.2017 04:53

    Раньше пользовался проксимитроном, он ловко кромсал страницы на лету.
    Вроде привокси умеет тоже самое.
    Сейчас влом этим заниматься из за https.
    Ещё где то в инете был плагин или ещё что то для запрета исполнения обфусцированных скриптов :)

    vlreshet
    Рядовой пользователь обречён страдать и/или платить, иного не дано.
    Не важно про что речь: ИТ, сантехника, электрика, авто, медицина…
    Или ты разбираешься сам или отстёгивай специалистам за свои хотелки.


  1. Londoner
    21.10.2017 11:24

    По-моему, это тот случай, когда не зазорно ещё и автоматически прокликивать эту рекламу в бэкграунде. Вот — vc.ru/21708-adnauseam


    1. esc
      21.10.2017 13:53

      Открою секрет, такая реклама в основном по cpa работает и «скликать» ее можно разве что сделав фейковый заказ.


  1. Tallefer
    21.10.2017 14:16

    Просьба проверить ту рекламу под RU AdList JS Fixes и RU AdList CSS Fixes,
    если это возможно (не уверен, что в хроме все будет работать).
    И зарепортить разрабу, пусть включит в новый выпуск.
    Все же лучше объединять усилия. :)