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 CodeLet's start with a study 20151007203811.js
published 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;
window['' + randomID + ''] = ...
The author provided for thorough randomization to circumvent static blocking.Then returns an object with three functions: bab
, check
and 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_elementid
not used in any version of the code. setTimeout
passed 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',
__u1 = 1,
overlayColor = '#EEEEEE',
textColor = '#777777',
buttonBackgroundColor = '#adb8ff',
buttonColor = '#FFFFFF',
__u2 = '',
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!',
adblockDetected = 0,
nagMode = 0,
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) {
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_eid
intended 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 check
will work if it Predicate
returns 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 check
triggered several times, as shown above, it is adblockDetected
set 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 localStorage
on 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
} else {
sessionStorage.setItem('babn', (Math.random() + 1) * 1000)
}
};
...
Unfortunately, it is nagMode
installed 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_ad
and 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 CodeDifference v1 / v2Bait 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 setInterval
instead 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 setInterval
and 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 CodeDifference v2 / v3Bait 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 eval
that 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 setTimeout
passed 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) {
};
Signature List: Defining Code Templates. The function check
checks the string after eval
for compliance with these patterns.Next, proxy the functions eval
and 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);
}
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];
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 CodeDifference v3 / v4The 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) {
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',
adsbygoogleQuery = document.querySelector(q);
if ((adsbygoogleQuery) && (adblockDetected == 0)) {
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) {
consolelog('case2: Ads are blocked.');
window['' + randomID + ''].arm()
}
}
};
adblockDetected = 1
}
...
scriptExists
checks 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)) {
} else {
var q = 'ins.adsbygoogle',
adsbygoogleQuery = document.querySelector(q);
if ((adsbygoogleQuery) && (adblockDetected == 0)) {
if (aDefOne == 'yes') {
};
} else {
if (adblockDetected == 0) {
if (aDefTwo == 'yes') {
}
}
}
}
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') {
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 onerror
as 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.js
it skips it, but loads fake Google Ads 0 bytes in size . This tricky trick fools BlockAdBlock, because it onerror
only works if the network request fails.Version 5 (March 2016 - November 2016)
Source CodeDifference v4 / v5Advanced 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);
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 baitImages
can be called frequently, with a random number of images, to bypass static blockers.Version 6 (April 2016 - November 2016): Brave lock
Source CodeDifference v5 / v6BlockAdBlock 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);
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 eval
to console.log
in 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]