BlockAdBlock Advertising Anti-Blocking Reverse Engineering

If you use ad blockers, you might encounter BlockAdBlock . This script detects your blocker and does not allow it to the site until you disable it. But I wondered how it works. How does anti-blocker detect blockers? And how do blockers react to this and how do they block anti-blockers?

History of reverse engineering


The first thing I did was look at their site . BlockAdBlock offers a configurator with settings: the wait interval and how the warning will look, generating different versions of the script.

This made me think of versions. What if he could not see one version, and all at once? So I did. I went back in time using the Wayback Machine . After that, I downloaded all versions of BlockAdBlock and hashed them.

List of all versions of BlockAdBlock, with 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

Only six versions differ from each other, and the last of them refers to 2016, although some sites still use BlockAdBlock. This is great, because it will be enough for us to reverse engineer the only version, and then reverse the diffs. Spoiler: we will see the development of ideas and even the remnants of debugging code.

I posted all the versions on GitHub . If you want to look at diffs, then in this repository each commit is a new version.

Unpacking


The script code is not minified, but packed by a JS-packer from Dean Edwards with cosmetic changes and without any modification of the logic 1.

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,{}))

Fortunately, this is not a problem for us. The weakness of the packer is that all code during unpacking is passed to eval(). If we replace eval()with something like console.log(), then we suddenly get all the source code, and the packer is defeated 2.

Having done this for each version, we can examine each of them and see what functions were added there over time.

v1:? - November 2015: initial script


Source Code

Let's start with a study 20151007203811.jspublished around November 2015 3. Although this first version does not block blockers very well, it allows you to evaluate the BlockAdBlock architecture without the garbage that has accumulated over the years.

Architecture


In three sentences:

  • BlockAdBlock is a closure that returns an object with three functions:
    • bab() sets the bait and calls a check check
    • check() checks if the blocker has blocked the bait, causing arm
    • arm() overlay
  • The entry point bab()then starts after a specified amount of time.
  • Three functions are generated with arguments that are set in the BlockAdBlock configurator .

The code is built around a closure assigned to a global object with a random name.

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 + ''] = ...

The author provided for thorough randomization to circumvent static blocking.

Then returns an object with three functions: bab, checkand arm.

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

These are my own names. All variables are minified, and some are specially obfuscated.
The entry point is bab()called through setTimeout().

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

bab_elementidnot used in any version of the code. setTimeoutpassed as a string.

A closure has external variables. Two of them are used to save state in a script:

  • adblockDetected equal to 1 if an ad blocker is detected.
  • nagMode- This is a configuration option. If it is installed, then the script does not block access to the page, but only turns on you once.

Other variables for controlling appearance and behavior are set in the configurator .

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 set here in an attempt to obfuscate the BlockAdBlock domain.

bab: create a decoy banner


The main method of working with BlockAdBlock is to create “bait” or “bait” from advertising elements that look like real banners. He then checks to see if their blocker has blocked.

This creates a decoy: a fake div that pretends to be an ad but is hidden from view.

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);
    ...

Apparently, it is passed_eidintended to configure the identifier of the bait, but it is not used.

After that, it is checked whether the bait has been removed by the ad unit.

    ...
    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)
}

If the bait no longer exists, then the element is deleted (and we start the overlay).

The function checkwill work if it Predicatereturns a value true, and it starts arm.

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

Since the test is checktriggered several times, as shown above, it is adblockDetectedset to the first correct test in order to avoid repeated tripping arm.

Grunt mode


The script has a function called “nag mode”: in this mode, BlockAdBlock will only say once to disable the ad blocker, but will not block you every time you visit. This is done by setting the item localStorageon the first visit.

If we could install this element ourselves, could we disable the lock forever? Unfortunately, BlockAdBlock checks in advance whether the script was configured for nag mode, so this method will not work when it works in default mode, that is, with access lock.

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)
        }
    };
    ...

Unfortunately, it is nagModeinstalled in the configurator, and by default it is equal 0.

BlockAdBlock version 1


Ad blockers use so-called filters: lines of code that can block network requests and hide elements on the page. By creating bait elements, BlockAdBlock specifically launches these filters.

With this simple defense, BlockAdBlock is effective against all major ad blockers such as uBlock Origin, AdBlock Plus and Ghostery. To counter this, we must write our own filter, which is activated only on sites that run BlockAdBlock.

Writing filters for a blocker is a bit complicated. We need a content filter that blocks elements on the page generated after loading. Since the bait has an identifier banner_ad, we create an exception to hide elementsmarked #@#for all elements #with an identifier banner_adand put it in the list of user filters of our blocker.

The result is the following:

localhost#@# #banner_ad

I have localhost here for demonstration, you can replace it with your URL.

This successfully deactivates BlockAdBlock. The solution may seem simple, but it has long been working successfully in the Anti-AdBlock-Killer filter list .

Version 2 (November 2015 - January 2016): several improvements


Source Code
Difference v1 / v2

Bait Creation: Less Bugs


There is a subtle mistake in the first version of creating the bait: the created div has no content, so a div is generated with a height of 0 and a width of 0. Later, the code checks to see if the div is deleted by checking its height and width. But since it had zero height, BlockAdBlock always worked 4.

The error of an empty div is fixed.

bab: function(...) {
    bait = document.createElement('DIV');
    ...
    bait.appendChild(document.createTextNode('Â '));

A child div is created with some content.

Blocker Detection Through Fake Graphic Banners


In this method, we create a fake image with a random name on doubleclick.net. Ad blockers will block the image, thinking it is an ad banner. But this does not require any changes to our filter.

bab: function(...) {
    bait = document.createElement('DIV');
    bait.innerHTML = '<img src="http://doubleclick.net/' + randomStr() + '.jpg">';
    ...

randomStr()generates a string of arbitrary length.

Another notable difference was the use of a timer setIntervalinstead of a simple one-time check to see if a trigger is installed. He re-checks whether the graphic banner is displayed and if its attribute is not changed src, checking the contents of the bait.

New setIntervaland check for image availability:

    ...
    checkCallback = setInterval(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);
            try {
                check((document.getElementById('banner_ad').innerHTML.indexOf('click') == -1), delay)
            } catch (e) {}
        } else {
            check(true, delay)
        }
    }, 1000

Why indexof ('click')? Because the source of the image src="doubleclick.net/abcdefg.jpg"and we check whether the fragment of the string is preserved click.

Version 3 (November 2015 - March 2016): generalized bait


Source Code
Difference v2 / v3

Bait Creation: Randomized Identifiers


The only change in this version, although significant, is the appearance of randomized identifiers for the bait. The new identifier is taken from the list of identifiers when loading the page and is used for the bait, which is now placed in the middle of the page.

A long list of random identifiers demonstrates a good knowledge of the subject area.

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"
];

Random ID Generation


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

Since this works on every boot, the identifier will be different every time.

BlockAdBlock, third to last version


BlockAdBlock exploits the blind spot of ad blockers: if a filter skips all of the above identifiers, then it will also skip real ads. Thus, BlockAdBlock forces the ad blocker to render itself useless .

In blockers, we can execute arbitrary scripts before loading the page. You can try to remove the BlockAdBlock object in advance. But for this we need the name of the BlockAdBlock object, which is randomized each time it starts.

uBlock Origin took a different approach. Since the code is executed by a function eval, we can define our own function evalthat will block execution if we find BlockAdBlock. In JS, an object is capable of this Proxy: you can replace any property and method in any object.

This can be circumvented by not passing the initial BlockAdBlock payload through eval, but using it directly, so we also proxy the entry point: the call setTimeout. Since it is setTimeoutpassed by a string, not a function, we check this string.

Implementation in uBlock Origin ( source ):

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 
};

Signature List: Defining Code Templates. The function checkchecks the string after evalfor compliance with these patterns.

Next, proxy the functions evaland 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);
        }
    }
});

Since we are now using a scriptlet , a special custom code fragment executed by the blocker, the filter changes a bit:

localhost## +js(nobab)

It does not start by default on all sites for performance reasons, so you need to assign it to each site using a script.

Version 4 (January 2016 - April 2016): experimental functions


Source Code
Difference v3 / v4

The described method for blocking anti-blockers was developed in January 2016, according to the history of uBlock Origin commits , and has not changed conceptually since its inception. BlockAdBlock never tried to get around this filter by changing its architecture. Instead, he continued to develop new features. And when we go to the BlockAdBlock page, we see an interesting tab: “Do you need more anti-blocking power?”



Although these protective methods are available only through a special tab, they are included in all scripts and are executed through variables with the corresponding names. The fourth version implements two methods:

  • aDefOne, “Specific protection for adsense sites”
  • aDefTwo, “Special security feature”

Random debug comments


Before saying goodbye to you, I must mention one more thing. In the process of reverse engineering, one function caught my attention:

Debugging console.log()right in the code!

function consolelog(e) {
    // "Dev mode" check: developpers of BAB must set window.consolelog to 1.
    if (window.consolelog == 1) {
        console.log(e)
    }
};

This is only done if global consolelog, for example, is set window.consolelog = 1.

Debug comments are only available in this version. If I had not reversed all versions, I would never have noticed them. They provide valuable information on how the code works.

Advanced Security: AdSense


All of these special protection methods are encoded in check, not in arm, as the architecture suggests. Perhaps a new developer who is not familiar with the code base has begun working on the product.

If AdSense is active on the page, then we check for ads. If they disappeared due to a blocker, BlockAdBlock is activated.

function check() {
    ...
    var q = 'ins.adsbygoogle',
        // Selects all Google ads in the document.
        adsbygoogleQuery = document.querySelector(q);

    if ((adsbygoogleQuery) && (adblockDetected == 0)) {
        // Ads are not blocked, since the bait ad is still there,
        // and adblockDetected hasn't been set
        if (aDefOne == 'yes') {
            consolelog('case2: standard bait says ads are NOT blocked.');
            var adsbygoogle = '//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
            if (scriptExists(adsbygoogle)) {
                consolelog('case2: And Adsense pre-exists.');
                if (adsbygoogleQuery.innerHTML.replace(/\s/g, '').length == 0) {
                    // The ad's content was cleared, so...
                    consolelog('case2: Ads are blocked.');
                    window['' + randomID + ''].arm()
                }
            }
        };
        adblockDetected = 1
    }
    ...

scriptExistschecks the entire page for a script with a given URL. In this case, the AdSense 5 script.

The URL of the script is compared with all the scripts on the page. For some reason, the URL is truncated to 15 characters.

function scriptExists(href) {
    if (href) href = href.substr(href.length - 15);
    var scripts = document.getElementsByTagName('script');
    for (var i = scripts.length; i--;) {
        var src = String(scripts[i].src);
        if (src) src = src.substr(src.length - 15);
        if (src === href) return true
    };
    return false
};

Advanced Protection: Special Element


Unlike the first, this method is accompanied by a disclaimer: "Please test after installation to ensure compatibility with your site."

This special protection works only if a blocker is not found and there is no AdSense script on the page. Here is the relevant code snippet check:

check: function(checkPredicate, unused) {
    if ((checkPredicate) && (adblockDetected == 0)) {
        // Adblocker detected, arm
    } else {
        var q = 'ins.adsbygoogle',
            adsbygoogleQuery = document.querySelector(q);

        if ((adsbygoogleQuery) && (adblockDetected == 0)) {
            if (aDefOne == 'yes') {
                // Special defense one: AdSense defense (see above)
            };
        } else {
            if (adblockDetected == 0) {
                if (aDefTwo == 'yes') {
                    // Special defense two: Special element defense
                }
            }
        }
    }

The method assumes that site owners use only AdSense: if an AdSense script does not exist, then something is wrong.

Why was there a warning? This method attempts to enable the AdSense script. If it does not load, then most likely the blocker blocked the network request, therefore BlockAdBlock is triggered. But this can ruin some sites, hence the warning.

If AdSense has not loaded, the overlay starts.

if (aDefTwo == 'yes') {
    /* Add Google ad code to head.
        If it errors, the adblocker must have blocked the connection. */
    var googleAdCode = '//static.doubleclick.net/instream/ad_status.js';
    consolelog('case3: standard bait says ads are NOT blocked. Maybe ???\
      No Adsense is found. Attempting to add Google ad code to head...');
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('src', googleAdCode);
    script.onerror = function() {
        window['' + randomID + ''].arm()
    };
    adblockDetected = 1;
    if (!scriptExists(googleAdCode)) {
        document.getElementsByTagName('head')[0].appendChild(script)
    };
    adsbygoogleQuery = 0;
    window['' + randomID + ''].check = function() {
        return
    }
}

When a failure occurs at the network level, it works onerroras if an ad blocker were working.

Indeed, most ad blockers respond to this and block the request. But there is one blocker that I have not mentioned yet. Talk about the Brave browser .

Brave Browser Response


So far, we have been studying how anti-blocking is detected in uBlock Origin. And it works, only a specific filter is required for each site where BlockAdBlock is installed. The Brave browser is impressive in that it detects and bypasses BlockAdBlock of all versions without any necessary action on the part of the user. To do this, he fakes the request directly at the network layer 6.

Instead of blocking the request, ad_status.jsit skips it, but loads fake Google Ads 0 bytes in size . This tricky trick fools BlockAdBlock, because it onerroronly works if the network request fails.



Version 5 (March 2016 - November 2016)


Source Code
Difference v4 / v5

Advanced protection: spam favicons


The only change in this version is that the second extended protection was rewritten, although it still adheres to the same basic principle: checking network requests that will be blocked by the blocker. But now favicons are loading instead of AdSense.

Brave evades this attack in the same way. It does not block requests, but creates fake 1 × 1 images.

if (aDefTwo == 'yes') {
    if (! window['' + randomID + ''].ranAlready) {/
        var favicons = [
            "//www.google.com/adsense/start/images/favicon.ico",
            "//www.gstatic.com/adx/doubleclick.ico",
            "//advertising.yahoo.com/favicon.ico",
            "//ads.twitter.com/favicon.ico",
            "//www.doubleclickbygoogle.com/favicon.ico"
            ],
            len = favicons.length,
            img = favicons[Math.floor(Math.random() * len)],
        ...
        baitImages(Math.floor(Math.random() * 2) + 1); // creates bait images
        var m = new Image();
        m.onerror = function() {
            baitImages(Math.floor(Math.random() * 2) + 1);
            c.src = imgCopy;
            baitImages(Math.floor(Math.random() * 2) + 1)
        };
        c.onerror = function() {
            adblockDetected = 1;
            baitImages(Math.floor(Math.random() * 3) + 1);
            window['' + randomID + ''].arm()
        };
        m.src = img;
        baitImages(Math.floor(Math.random() * 3) + 1);
        window['' + randomID + ''].ranAlready = true
    };
}

The function baitImagescan be called frequently, with a random number of images, to bypass static blockers.

Version 6 (April 2016 - November 2016): Brave lock


Source Code
Difference v5 / v6

BlockAdBlock methods, although simple at first glance, gradually became more complex and more efficient. But the last undefeated enemy remained: the Brave browser.

Advanced Protection: Defining a Fake Favoricon


Why did BlockAdBlock switch from trying to load a script to load images (favicons)? The answer is in the code that is added to the protection through spam favicons, and which is activated against the protection of Brave.

Checking the response for a fake image:

if (aDefTwo == 'yes') {
    baitImages(Math.floor(Math.random() * 3) + 1);
    // earlier favicon code...
    var m = new Image();
    if ((aDefThree % 3) == 0) {
        m.onload = function() {
            if ((m.width < 8) && (m.width > 0)) {
                window['' + randomID + ''].arm()
            }
        }
    };
}

If the favicon size is less than 8 × 8, then this is probably a fake from the Brave browser.

With this trick, BlockAdBlock bypasses the disguise of Brave and other blockers who run this code (most, like uBlock Origin, block it first).

After this update, around the end of November 2016, BlockAdBlock disappeared from the Internet. Although their “advanced security methods” work, they have never been activated for most users. This was the last update. The last post on Twitter and on the site was published somewhere at the end of 2017.

However, the legacy of BlockAdBlock lives on. Although it’s trivial to block it these days, this script is still used by some modern sites.

Conclusion


Who will win the arms race between ad blockers and anti-blockers that block ad blockers? Only time will tell. As the arms race develops, anti-blockers will have to use more sophisticated methods and special code, as the evolution of BlockAdBlock shows.

On the other hand, blockers have the advantage of stable systems and powerful filtering tools through filter lists, as well as access to JavaScript. With such a system, it is enough for one person to understand how to defeat the enemy - and update the filter list with new sites.

By analyzing the evolution of BlockAdBlock, as well as the various responses of ad blockers, we were able to paint a picture of a small war between BlockAdBlock and blockers. In the process, we learned what methods are used in hostilities.

My reverse engineering code is published on GitHub . Thank you for reading.



1. If you want to get an idea of ​​how this works, try pasting the example below into the JS console, and then look at the code. If you are interested in his inner workings, here is the source code . [return]

2. Do not believe? Try changing evalto console.login the first line.
[to return]

3. The timestamp says 201510, so maybe October. But we do not know when the script changed. All we know:

  • 2015-10, one version was saved: 20151007203811.js
  • 2015-11 there is a new version: 20151113115955.js

As far as we know, the script could be changed the day before the second timestamp. Thus, I conservatively approach dating. [return]

4. During the correction of this error tests were developed for v1. [return]

5. Thanks to McStroyer on Reddit, who drew attention to this. [return]

6. The Brave ad blocker component is open, so we can look at the source code to get an idea of ​​how it works.


Thanks to François Marie for pointing out the source code for Brave. [return]

All Articles