6.8 C
София

Как работи и как да се преборим с анти-адблокера: реверсивно инженерство на BlockAdBlock

Оригиналът е на Hugo Elhaj-Lahsen

Най-четени

Даниел Десподовhttps://www.kaldata.com/
Ежедневен автор на новини. Увличам се от съвременни технологии, оръжие, информационна безопасност, спорт, наука и концепцията Internet of Things.

Ако използвате рекламни блокери от типа на uBlock, Adblock Plus, AdGuard или нещо друго, навярно сте се сблъсквали с BlockAdBlock. Този скрипт открива, че използвате рекламен блокер и не ви пуска на сайта, докато не изключите блокирането на рекламите. Но по какъв начин този анти-блокер открива блокерите? И как реагират на това чудо самите блокери и как успяват да блокират анти-блокера?

Историята на реверсивното инженерство на BlockAdBlock

Първата ми работа бе да погледна техния сайт. BlockAdBlock предлага конфигуратор с настройки: след избора следва кратка пауза, а след това се показва как ще изглежда предупреждението, че се използва блокиране на рекламите. Но най-интересното е, че всеки път се генерира различна версия на този скрипт.

Замислих се за тези версии. Какво ще стане, ако не се огранича само с една версия на BlockAdBlock, а разгледам всички версии? Точно така и направих. Върнах се назад във виртуалното време с помощта на Wayback Machine, изтеглих всички версии на BlockAdBlock и ги хеширах.

Списъкът с всички версии на BlockAdBlock с sha1sum контролен хеш:

6d5eafab2ca816ccd049ad8f796358c0a7a43cf3 20151007203811.js
065b4aa813b219abbce76ad20a3216b3481b11bb 20151113115955.js
d5dec97a775b2e563f3e4359e4f8f1c3645ba0e5 20160121132336.js
8add06cbb79bc25114bd7a2083067ceea9fbb354 20160318193101.js
8add06cbb79bc25114bd7a2083067ceea9fbb354 20160319042810.js
8add06cbb79bc25114bd7a2083067ceea9fbb354 20160331051645.js
8add06cbb79bc25114bd7a2083067ceea9fbb354 20160406061855.js
8add06cbb79bc25114bd7a2083067ceea9fbb354 20160408025028.js
555637904dc9e4bfc6f08bdcae92f0ba0f443ebf 20160415083215.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20161120215354.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20170525201720.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20170606090847.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20170703211338.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20170707211652.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20170813090718.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20170915094808.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20171005180631.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20171019162109.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20171109101135.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20171127113945.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20171211042454.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20171227031408.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20180202000800.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20180412213253.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20180419060636.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20180530223228.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20180815042610.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20181029233809.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20181122190948.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20181122205748.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20190324081812.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20190420155244.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20190424200651.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20190903121933.js
d8986247cad3bbc2dd92c3a2a06ac1540da6b286 20200112084838.js

Лесно може да се види, че само шест версии се различават една от друга, като последната е от 2016 година. Но редица сайтове и днес използват този BlockAdBlock скрипт, което е отлично, понеже ще ни е достатъчно да извършим обратно инженерство само на едната версия, а след това само да сравним разликите. В крайна сметка се оказа, че може да се види развитието на тази идея и дори остатъци от допълнителния код за дебъгера.

Всичките версии качих в GitHub. Ако искате да сравните разликите, ето в това хранилище всяка директория е нова версия.

Разкомпресирането

Кодът на скрипта не е минимизиран, а е пакетиран с помощта на JS-архиватора на Дийн Едуардс, в който са направени някои козметични промени, без каквато и да била модификация в логиката.

eval(function(p, a, c, k, e, d) {
    e = function(c) {
        return (c < a ? '' : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36))
    };
    if (!''.replace(/^/, String)) {
        while (c--) {
            d[e(c)] = k[c] || e(c)
        }
        k = [function(e) {
            return d[e]
        }];
        e = function() {
            return '\\w+'
        };
        c = 1
    };
    while (c--) {
        if (k[c]) {
            p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c])
        }
    }
    return p
}('0.1("2 3 4 5 6 7 8\'d. 9, h? a b c d e f g");i j=\'a\'+\'k\'+\'e\'+\'l\'+\'n\'+\'m\'+\'e\';',24,24,
'console|log|This|code|will|get|unpacked|then|eval|Cool||||||||huh|let|you|w|s||o'.split('|'),0,{}))

За щастие, за нас това не е никакъв проблем. Слабостта на този архиватор е в това, че след декомпресията целият код се подава на eval(). Тоест, ако заменим eval() с нещо, като например console.log(), то изведнъж получаваме целия сорс код и архиваторът е победен.

А като направим това с всяка версия, може да изучим развитието на тази идея и да видим какви функционалности са добавяни с течение на времето.

v1: ? – ноември 2015 година: първоначалния скрипт

Сорс кодът

Да започнем с разглеждането на 20151007203811.js, публикуван някъде през месец ноември 2015 година. Тази версия не може особено добре да спира рекламните блокери, но дава възможност добре да се види архитектурата на BlockAdBlock без боклука, който се е натрупал с годините.

Архитектурата

Три са основните моменти:

  • BlockAdBlock е затваряне (Closure), което връща обект с три функции:
    • bab() вмъква примамка и предизвиква проверката check
    • check() проверява дали блокаторът е „клъвнал“ на примамката и я е блокирал, след което е извикал arm
    • arm налага овърлей
  • Входната точка bab() се появява след известно време
  • Генерират се три функции с аргументи, които се задават в конфигуратора на BlockAdBlock.

Кодът се изгражда около затваряне към глобален обект със случайно име:

var randomID = '',
    e = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < 12; i++) randomID += 
    e.charAt(Math.floor(Math.random() * e.length));
var setTimeoutDelay = 7; // Delay after which to call BlockAdBlock
window['' + randomID + ''] = ...

Авторът е използвал възможно най-добра рандомизация, за да избегне възможно статично блокиране.

След това се връща обект с трите функции bab, check и arm.

window['' + randomID + ''] = (function() {
    var eid = ...
    return {
        bab: function(check, passed_eid) {},
        check: function(checkPredicate, unused) {},
        arm: function() {}
    }
})();

Тези имена съм ги дал аз. Всички променливи са минимизирани, а някои са специално обфускирани.

Входната точка bab() се използва и извиква чрез setTimeout().

setTimeout('window[\'\' + randomID + \'\'] \
.bab(window[\'\' + randomID + \'\'].check, \
     window[\'\' + randomID + \'\'].bab_elementid)', setTimeoutDelay * 1000)

bab_elementid не се използва е нито една версия на кода. setTimeout се подава във вид на стринг.

Затварянето има външни променливи. Две от тях се използват за запазване на междинната информация:

  • adblockDetected е равен на 1, ако е открит блокатор на реклами
  • nagMode е вариант на настройката. Ако този флаг е вдигнат, то скриптът не блокира достъпа до страницата, а само ще изведе съобщение, дали искате да продължите с включен адблокър. Съобщението може да е и друго.

Другите променливи за управлението на външния вид и поведение се задават в конфигуратора:

var eid = ' ad_box', // Name of the bait.
    __u1 = 1, // Unused.

    // Colors for the blockadblock prompt.
    overlayColor = '#EEEEEE',
    textColor = '#777777',
    buttonBackgroundColor = '#adb8ff',
    buttonColor = '#FFFFFF',

    __u2 = '', // Unused.

    // Text to display when the blockadblock prompt is shown.
    welcomeText = 'Sorry for the interruption...',
    primaryText = 'It looks like you\'re using an ad blocker. That\'s okay.  Who doesn\'t?',
    subtextText = 'But without advertising-income, we can\'t keep making this site awesome.',
    buttonText = 'I understand, I have disabled my ad blocker.  Let me in!',

    // If 1, adblock was detected.
    adblockDetected = 0,
    // If 1, BlockAdBlock will only nag the visitor once, rather than block access.
    nagMode = 0,

    // The blockadblock domain, reversed.
    bab_domain = 'moc.kcolbdakcolb';

Тук виждаме bab_domain, който се използва в опит за обфускация на домейна на BlockAdBlock.

bab: създаването на банера-примамка

Основният метод на работа на BlockAdBlock е в създаването на примамка или „стръв“ от рекламни елементи, които изглеждат като истински рекламни банери. След това се проверява дали адблокът не ги е блокирал.

По-конкретно, стръвта е фалшив div, който се преструва на реклама, но е скрит и не се вижда.

bab: function(check, passed_eid) {
    // Wait for the document to be ready.
    if (typeof document.body == 'undefined') {
        return
    };

    var delay = '0.1', 
        passed_eid = eid ? eid : 'banner_ad',
        bait = document.createElement('DIV');
        
    bait.id = passed_eid;
    bait.style.position = 'absolute';
    bait.style.left = '-999px';
    bait.appendChild(document.createTextNode(' '));
    document.body.appendChild(bait);
    ...

Вижда се, че има и passed_eid, който е предназначен за настройка на идентификатора на стръвта, но тук той не се използва.

След това се проверява дали стръвта в премахната от адблокъра:

  ...
    setTimeout(function() {
        if (bait) {
            check((bait.clientHeight == 0), delay);
            check((bait.clientWidth == 0), delay);
            check((bait.display == 'hidden'), delay);
            check((bait.visibility == 'none'), delay);
            check((bait.opacity == 0), delay);
            check((bait.left < 1000), delay);
            check((bait.top < 1000), delay)
        } else {
            check(true, delay)
        }
    }, 125)
}

Ако примамката вече не съществува, то елементът е премахнат от някой блокатор на реклами и трябва да се стартира подготвения овърлей.

Функцията check ще сработи, ако Predicate върне значение True, след което се стартира arm:

check: function(checkPredicate, unused) {
    if ((checkPredicate) && (adblockDetected == 0)) {
        adblockDetected = 1;
        window['' + randomID + ''].arm()
    } else {}
}

Проверката check се задейства няколко пъти. Ето защо adblockDetected е настроен на първата правилна проверка, за да се избегне многократното задействане на arm.

Режимът на натякване (nag mode)

В скрипта е реализирана функция за натякване (nag mode): в този режим BlockAdBlock само веднъж ще каже да си изключите адблокъра и няма да ви блокира при всяко посещение. Това е направено с помощта на елемента localStorage при първото посещение на уеб страницата.

Ако можехме сами да зададем значението на този елемент, то ние завинаги бихме изключили блокатора на адблокърите. За съжаление BlockAdBlock предварително проверява дали скриптът е настроен в nag mode и този метод няма да сработи в режима на блокиране на достъпа до страницата.

arm: function() {
    if (nagMode == 1) {
        var babNag = sessionStorage.getItem('babn');
        if (babNag > 0) {
            return true // Stop the script.
        } else {
            sessionStorage.setItem('babn', (Math.random() + 1) * 1000)
        }
    };
    ...

Наистина, nagMode се задава в конфигуратора и по подразбиране е равен на 0.

Блокирането на BlockAdBlock версия 1

Адблокърите (блокерите на реклами) използват филтри. Това са редове код, които могат да блокират мрежовите запитвания и да скриват елементи от уеб страниците. След като създава примамки, BlockAdBlock специално задейства тези филтри и проверява дали не се използва софтуер за блокиране на рекламите.

С помощта на тази доста опростена защита BlockAdBlock е ефективен срещу всичките основни адблокъри, включително uBlock Origin, AdBlock Plus и Ghostery. За да се противопоставим на това, трябва напишем собствен филтър, който се активизира само на сайтовете, в които работи BlockAdBlock.

Не е много лесно да се пишат филтри за адблокър. Необходим ни е филтър на съдържанието, който блокира елементите в страницата, генерирани след нейното зареждане. Понеже видяхме, че идентификатора на стръвта е banner_ad, то ние ще създадем изключение за скриването на елементите с маркер #@# за всички елементи # с идентификатор banner_ad и ще го поставим в списъка с потребителските филтри на използвания адблокър.

В този случай ще се получи следното:

localhost#@# #banner_ad

Разбира се, тук localhost е само за демонстрация и трябва да бъде заменен с вашия URL.

Това успешно деактивира BlockAdBlock. Решението изглежда простичко, но отдавна е включено в списъка с филтрите на Anti-AdBlock-Killer.

BlockAdBlock версия 2 (ноември 2015 – януари 2016)

Сорс кодът
Разликите

Използване на фалшиви графични банери

При този метод се създава фалшиво изображение в doubleclick.net. Адблокърите го премахват, понеже го считат за реклама.

Друга по-интересна разлика е използването на таймера setInterval вместо съвсем опростената еднократна проверка. Чрез него през определен период от време се определя дали този графичен банер е показан или е отрязан от някой адблокър.

Версия 3 на BlockAdBlock от (ноември 2015 до март 2016

Сорс кодът
Разликите

Използване на случайни идентификатори

Единствената промяна в тази версия, но за сметка на това значителна, е появата на случайни идентификатори за примамката. Новият идентификатор по случаен начин се взема от списък с идентификатори при зареждането на уеб страницата и по този начин се използва псевдослучайна стръв, която се поставя в средата на страницата.

Дългият списък от идентификатори показва много добри познания в тази област:

var baitIDs = [
  "ad-left",
  "adBannerWrap",
  "ad-frame",
  "ad-header",
  "ad-img",
  "ad-inner",
  "ad-label",
  "ad-lb",
  "ad-footer",
  "ad-container",
  "ad-container-1",
  "ad-container-2",
  "Ad300x145",
  "Ad300x250",
  "Ad728x90",
  "AdArea",
  "AdFrame1",
  "AdFrame2",
  "AdFrame3",
  "AdFrame4",
  "AdLayer1",
  "AdLayer2",
  "Ads_google_01",
  "Ads_google_02",
  "Ads_google_03",
  "Ads_google_04",
  "DivAd",
  "DivAd1",
  "DivAd2",
  "DivAd3",
  "DivAdA",
  "DivAdB",
  "DivAdC",
  "AdImage",
  "AdDiv",
  "AdBox160",
  "AdContainer",
  "glinkswrapper",
  "adTeaser",
  "banner_ad",
  "adBanner",
  "adbanner",
  "adAd",
  "bannerad",
  " ad_box",
  " ad_channel",
  " adserver",
  " bannerid",
  "adslot",
  "popupad",
  "adsense",
  "google_ad",
  "outbrain-paid",
  "sponsored_link"
];

Случайното генериране на идентификатор:

    randomBaitID = baitIDs[ Math.floor(Math.random() * baitIDs.length) ],
    ...
    var passed_eid = randomBaitID;
    bait = document.createElement('DIV');    
    bait.id = passed_eid;;

Това се задейства при всяко зареждане на страницата и по този начин от списъка се взема случаен идентификатор.

Блокирането на BlockAdBlock от третата до последната версия

BlockAdBlock използва сляпото петно на адблокърите: ако филтърът пропусне изброени по-горе идентификатори, то той ще пропусне и рекламата. По този начин BlockAdBlock прави безполезни програмите за блокиране на рекламите.

В адблокърите ние можем да изпълним произволни скриптове преди зареждането на страницата. Може да се опитаме предварително да изтрием обекта на BlockAdBlock, но за тази цел трябва да знаем неговото име, което е случайно при всяко нейно зареждане, а и тази таблица лесно се подменя.

uBlock Origin използва друг подход. Понеже кодът се изпълнява от функцията eval, то ние може да създадем собствена eval, която ще блокира изпълнението, ако бъде открит BlockAdBlock. В JavaScript това става с помощта на обекта Proxy: с който може да заменим всяко свойство и всеки метод в произволен обект.

Използваме Proxy за входната точка на BlockAdBlock – извикването setTimeout. И тъй като setTimeout се предава като стринг, а не като функция, то ние можем да проверим символите в този стринг:

Ето как е реализирано това в uBlock Origin (сорс кодът):

const signatures = [
    [ 'blockadblock' ],
    [ 'babasbm' ],
    [ /getItem\('babn'\)/ ],
    [
        'getElementById',
        'String.fromCharCode',
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
        'charAt',
        'DOMContentLoaded',
        'AdBlock',
        'addEventListener',
        'doScroll',
        'fromCharCode',
        '<<2|r>>4',
        'sessionStorage',
        'clientWidth',
        'localStorage',
        'Math',
        'random'
    ],
];
const check = function(s) {
    // check for signature 
};

След това поставяме прокси на функциите eval и setTimeout:

window.eval = new Proxy(window.eval, {
    apply: function(target, thisArg, args) {
        const a = args[0];
        if ( typeof a !== 'string' || !check(a) ) {
            return target.apply(thisArg, args);
        } 
        // BAB detected: clean up.
        if ( document.body ) {
            document.body.style.removeProperty('visibility');
        }
        let el = document.getElementById('babasbmsgx');
        if ( el ) {
            el.parentNode.removeChild(el);
        }
    }
});
window.setTimeout = new Proxy(window.setTimeout, {
    apply: function(target, thisArg, args) {
        const a = args[0];
        // Check that the passed string is not the BAB entrypoint.
        if (
            typeof a !== 'string' ||
            /\.bab_elementid.$/.test(a) === false
        ) {
            return target.apply(thisArg, args);
        }
    }
});

И понеже сега използваме инжектиране със скриптлет – специален потребителски фрагмент от код, филтърът малко се променя:

localhost## +js(nobab)

Но той не се стартира по подразбиране във всички сайтове, понеже това ще окаже влияние на производителността. Необходимо е да бъде задаван за всеки сайт с помощта на скрипт.

Следващи версии

Във версия 4 на BlockAdBlock се появи нов раздел, с интересното име „Искате ли още по-голяма мощ за блокиране на адблокърите?“.

Тук се използват още по-хитри методи за откриване и блокиране на програмите за блокиране на реклами. Разбира се, и срещу тях има контра действия. Борбата продължава и днес.

Но нека да да разгледаме още едно, много по-необичайно решение – браузърът Brave.

Отговорът на браузъра Brave

Досега разглеждахме как се откриват блокаторите на адблокъри в uBlock Origin. Това работи много добре, само че е необходим филтър за всеки конкретен сайт, в който се използва BlockAdBlock. Браузърът Brave е интересен и впечатлява с това, че открива всички до една версии на BlockAdBlock без каквито и да било действия от страна на потребителя. За да постигне това той подправя запитването директно на мрежово ниво.

Вместо да блокира запитването ad_status.js, той го пропуска, но зарежда фалшива Google Ads реклама с размер 0 байта. Този хитър трик прави на глупак BlockAdBlock, понеже onerror се задейства само в случай, че мрежовото запитване бъде неудачно.

 


Няма да се спираме на следващите версии на BlockAdBlock и на съответните отговори на адблокърите – това е дадено в оригинала. Но е интересно кой ще излезе победител във войната на адблокърите и техните блокатори от типа на BlockAdBlock. Това е нещо като надпревара във въоръжаването, в която се използват все по-мощни и хитри методи. Дотук разгледахме само някои от бойните похвати в реалния живот.

Абонирай се
Извести ме за
guest
25 Коментара
стари
нови
Отзиви
Всички коментари

Нови ревюта

Подобни новини