| Monday July 28th 2014

Feedburner

Subscribe by email:

We promise not to spam/sell you.


Search Amazon deals:

Unblocking Adblock


If you’re using Firefox with the Adblock Plus extension installed what would you think of a site that implemented a site-wide script that unblocks ads which were blocked by Adblock Plus?firefox

See, a while ago, when the whole Firefox is blocked debacle was raging, I made some hand-waving statement about how easy it would be to circumvent ad-blocking software (though of course, I wouldn’t say how). Then a little while later (not quite sure when…  I was probably drunk) I lost a bet, and had to prove it. The stakes were high, and I felt a little dirty, but I’m a man of my word, so I had to do it. And as it happened, it turned into a fun and interesting challenge, with a relevant point to make…


To ad or not to ad
I’m no fan of ads myself, and I really don’t have an issue with people using ad-blocking software. I know there are some who think that blocking ads is tantamount to theft, but I think their arguments are entirely specious. Any part of the web is customizable for you — you can make sites have a high-contrast color scheme because you need that to read, or you can filter out all images because you don’t have the bandwidth to load them, or you can selectively remove particular types of content because you simply don’t want to see it. It’s all the same, and objecting to one is objecting to all of them (which is why the law would never be able to intervene — making the activity of ad-blocking illegal would make all user-modification illegal, including the use of assistive technologies, user CSS and user scripting, and other extensions).

In any case it isn’t really the ad-blocking that’s the issue, it’s business models that refuse to acknowledge human nature, and content providers who want to make money while pretending that’s not their motive. The hypocrisy of that is pretty insulting, in my view — if your content is free, make it free; if it’s not free then charge for it. But don’t make it superficially free and then try to make me feel guilty when it costs you something.

Anyway
That’s my opinion, but be that as it may, I do love to throw the odd cat among the pigeons! So I implemented the un-blocking script anyway just to throw some petrol on the debate, and remind anyone who’s forgotten of a simple truth: content control — and digital rights management in general — is an arms race, and nobody will ever win.

Here’s how it works
To begin with we need to establish that Adblock is actually running — we don’t want to run the script if it isn’t or we’ll end up duplicating all our ads. There used to be a Firefox bug that exposed information about installed extensions, but that’s long since fixed. So:

if(navigator.userAgent.toLowerCase().indexOf('firefox') == -1) { return; }
var self = this, tester = document.createElement('img');
document.getElementsByTagName('body').item(0).appendChild(tester);
tester.setAttribute('src', '/ads/cupcakes.gif');
window.setTimeout(function()
{
if(!tester || tester.style.display == 'none')
{
//adblock is running
}
}, 10);

Here we’re assuming that Adblock’s filters will match a URI containing /ads, because if it does then we’ll never be able to load that image, and that’s how we know Adblock is running. One of the clever things about Adblock is the fact that it doesn’t actually remove things from the document at all — it never lets them get to the document in the first place. Adblock hooks into Firefox’s content policy API, which controls allowable content at the level of HTTP requests, which means that blocked content is never even requested. (The empty source element is also undisplayed with a dynamically-generated user stylesheet, which can’t be overridden by any styling on the document, because user stylesheets always have precedence).

Of course if the test image isn’t blocked, then the rest of the script won’t run, but given the obviousness of the target URI, it’s pretty likely that it will.

Once we’ve established that Adblock is running we can begin to look for blockable content. The script defines a subset of URI filters (similar to Adblock itself, but limited to the ads we know we’re running), and a list of elements to check (scripts, images and iframes). Each instance of a target element is tested against the filters to see if it might be blocked, and if so we remember its address.

Now we have a list of addresses, we need to be able to request that data manually, which we can do by calling a PHP script via XMLHttpRequest — the server can request data from any domain and then pass it back up to the client. However it isn’t quite that straightforward, because even XHR requests are affected by Adblock. We need to prevent our requests from being blocked, so to do that we encode the addresses so that the filters don’t pick them up. It doesn’t need to be fancy, so Rot13 encoding is quite sufficient, and we’ll end up making requests like this:

request.open('GET', 'cupcakes.php?encuri=uggc://jjj.zlfvgr.pbz/nqf/cebzb.wf', true);

The PHP script decodes that URI and then grabs its contents using fopen() into a buffer (other approaches are possible, but this seemed like the most straightforward; note that the PHP script itself is validated to prevent injection attacks, and disallow requests that don’t come from specified hosts). Depending on what the ad code spits out, we might get JavaScript, HTML or image data in response.

If it’s HTML that would be because the original ad was an <iframe>, so in that case we simply change the iframe’s SRC to point to the PHP script, and we’re done:

<iframe src="cupcakes.php?encuri=uggc://jjj.zlfvgr.pbz/nqf/cebzb.wf">

If it’s image data we base64 encode it, so that on the client side we can use the data to create a new image with a data URI, which has no external resource and therefore doesn’t get blocked:

var img = document.createElement('img');
img.setAttribute('src', 'data:image/gif;base64,' + request.responseText);
element.parentNode.insertBefore(img, element);

The element referred to in that code is the original ad — by inserting the new image directly before it, we ensure that it appears in the same place in the document as the original.

Finally, if the response is JavaScript, we need to parse and evaluate that code. Each ad will be different, so we basically need a different case for each provider. For example, if the provider returned HTML surrounded in a document.write statement, we would need to extract the output and direct it to a new element using innerHTML, then append that new element directly before the original ad script (just as with images, so that it appears in the same place):

var code = eval(request.responseText.replace('document.write', ''));
var span = document.createElement('span');
span.innerHTML = code;
element.parentNode.insertBefore(span, element);

And that’s how we roll!
Adblock revolves around URI filtering, so if we can fool it into letting us request disallowed data, we can simply add that data directly to the page, and then there’s nothing for Adblock to work with anymore. We’re not even distorting our ad statistics, because the original ad was never requested in the first place — ours is the only request that gets through!

The only thing we can’t handle is a script which generates an <iframe> which itself contains another blockable element. Theoretically we could drill down further and recursively unblock the contents of the iframe page … but that’s getting a bit ridiculous.

Blocking the unblocker
Yeah well, I never wanted to be evil (once you begin down the dark path, forever will it dominate your destiny). If you want to stop all of this from working you just have to block the unblocking script — simply add cupcakes to your list of filters. And anyway, I’m going to remove it next week, it was just a bit of fun ;)

Unless I made that script unblockable too…

Related Posts: On this day...

Reader Feedback

One Response to “Unblocking Adblock”

  1. [...] of the unknown soldier tomb of the unknown soldier reo reo chilis snow white and the huntsman snow white and the huntsman Share [...]

Leave a Reply

You must be logged in to post a comment.