DEV Community

Cover image for How I Beat a 16-Click Anti-Bot Ad Trap
Mayank Srivastava
Mayank Srivastava

Posted on

How I Beat a 16-Click Anti-Bot Ad Trap

๐ŸŽฏ I Got Tired of Closing Popups. So I Reverse-Engineered the Entire Ad Stack.

Weโ€™ve all been there.
You open a streaming site.

You click play.

๐Ÿ’ฅ A new tab appears.

You close it. Click again.

๐Ÿ’ฅ Another popup.

Some sites are so aggressive that watching a single episode feels like defusing a bomb through 16 consecutive popups while trying not to accidentally download malware from 2007.

Most people tolerate it.
I couldnโ€™t.

As a developer, the moment software starts fighting the user this aggressively, it stops being an inconvenience and becomes a challenge.
So I decided to break it.


๐Ÿงช Phase 1 โ€” The โ€œThis Should Workโ€ Delusion

My first thought was simple:
Override window.open.

window.open = () => null;
Enter fullscreen mode Exit fullscreen mode

Done. Right?
Nope.
The site completely ignored it.
Thatโ€™s when I realized this wasnโ€™t normal popup logic anymore.
This was engineered hostility.

๐Ÿ‘ป Phase 2 โ€” Chasing Invisible Click Traps

I opened DevTools and started tracing the DOM during every interaction.
Something strange kept happening.
Inside the video player container, the final

kept rapidly changing after every click.
Then it clicked.
The site was dynamically generating transparent overlay layers directly above the player.
The actual video button wasnโ€™t receiving my clicks.
The invisible overlay was.
Every physical mouse interaction got hijacked by a temporary fullscreen layer designed to trigger an ad redirect using a trusted human click.
The sequence looked something like this:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿ–ฑ๏ธ User Click                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
               โ”‚
               โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿ‘ป Invisible Overlay Capturesโ”‚
โ”‚    the Trusted Click Event   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
               โ”‚
               โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿ’ฅ Popup / Redirect Triggeredโ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
               โ”‚
               โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿงน Overlay Self-Deletes      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
               โ”‚
               โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ โ™ป๏ธ New Overlay Injected      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
               โ”‚
               โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€ loops โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ

Ridiculously clever.
Because technically I initiated the click, the browser trusted the action.
So I escalated.

โš”๏ธ Phase 3 โ€” Fighting Back With a Chrome Extension

At this point, I realized manual debugging alone wasn't enough anymore.

So I decided to escalate.

I created a custom Chrome extension specifically for this battle.

The first version was primitive โ€” a DOM-level interceptor designed to watch the page in realtime and surgically remove the invisible overlay traps before they could hijack clicks.

I used a MutationObserver to monitor the page for dynamically injected elements:

const observer = new MutationObserver(() => {
    document.querySelectorAll('.suspicious-overlay')
        .forEach(el => el.remove());
});

observer.observe(document.body, {
    childList: true,
    subtree: true
});

For a brief momentโ€ฆ
โ€ฆit worked.
Then the site hit back.

๐Ÿค– It was at this moment I realized I was fighting an Anti-Bot System

Buried deep inside the bundled scripts was an aggressive anti-fraud service called Adscore.

The behavior suddenly changed:

๐Ÿงน Console logs started disappearing
๐Ÿ”„ The player entered infinite click loops
๐Ÿ•ต๏ธ Hardware fingerprinting routines activated
๐ŸŽฎ WebGL context checks triggered
๐Ÿ“ Screen dimension math got evaluated
๐Ÿง  Browser behavior started getting profiled

The site wasnโ€™t just showing ads anymore.
It was actively detecting interference.
And then I realized my mistake:

I was still fighting inside the webpage sandbox.

That was their territory.

๐Ÿง  Phase 4 โ€” Stop Fighting the UI. Attack the Network.

If the frontend is weaponizedโ€ฆ
โ€ฆyou stop fighting visuals.

You cut the supply lines.

Instead of targeting the popup elements themselves, I started tracing the network behavior behind them.
During tiny windows before the console got wiped, I managed to capture the redirect domains responsible for the popup chains.
That changed everything.

Because once you identify the routing infrastructure the illusion collapses.

๐Ÿ”ฅ The Final Architecture

I rebuilt the extension entirely around Chromeโ€™s native browser APIs:

  1. declarativeNetRequest
  2. tabs
  3. background service workers

No more DOM fighting.
No more chasing invisible overlays.
No more playing inside their sandbox.
The browser itself would now intercept requests before the scripts could fully execute.

๐Ÿ“ฆ manifest.json

{
  "manifest_version": 3,
  "name": "Native Ad Network Guard",
  "version": "1.0",
  "description": "Drops ad-network connections and instantly auto-closes leaked popups.",
  "permissions": [
    "declarativeNetRequest",
    "tabs"
  ],
  "host_permissions": [
    "<all_urls>"
  ],
  "background": {
    "service_worker": "background.js"
  }
}

โšก background.js

// Domains responsible for popup routing,
// ad redirects, tracking, and anti-user behavior.
const BLOCKED_DOMAINS = [
    "xadsmart.com",
    "adsco.re"
];


// ---------------------------------------------------
// 1. Create browser-level blocking rules
// ---------------------------------------------------

// Chrome's declarativeNetRequest API works by defining
// static rule objects that the browser engine enforces
// BEFORE requests fully execute.

const rules = BLOCKED_DOMAINS.map((domain, index) => ({
    id: index + 1,
    priority: 1,

    // Action to perform when matched.
    // In this case: completely block the request.
    action: { type: 'block' },

    // Conditions that trigger the rule.
    condition: {
        // Match any request containing this domain.
        urlFilter: domain,
        // Types of browser resources to intercept.
        resourceTypes: [
            // Entire page navigations
            'main_frame',
            // Embedded iframes
            'sub_frame',
            // External JavaScript files
            'script',
            // AJAX/fetch/XHR requests
            'xmlhttprequest'
        ]
    }
}));


// ---------------------------------------------------
// 2. Register rules when extension installs
// ---------------------------------------------------

chrome.runtime.onInstalled.addListener(() => {

    chrome.declarativeNetRequest.updateDynamicRules({
        // Remove previously existing rules
        // to avoid duplicates during reinstalls.
        removeRuleIds: rules.map(r => r.id),

        // Inject the new blocking rules.
        addRules: rules
    });
});


// ---------------------------------------------------
// 3. Instant popup execution kill-switch
// ---------------------------------------------------

// Even if a popup somehow bypasses the network block,
// this listener acts as a secondary defense layer.
// It watches every tab URL update in realtime.

chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {

    // Ignore updates that don't contain a URL.
    if (changeInfo.url) {

        // Normalize URL for safer comparisons.
        const url = changeInfo.url.toLowerCase();

        // Check whether the tab matches
        // known popup patterns or redirect signatures.
        const shouldKill =

            // Known blocked domains
            BLOCKED_DOMAINS.some(domain =>
                url.includes(domain)
            )

            // Common ad-network query patterns
            || url.includes('zoneid=')

            // Aggressive redirect handler pattern
            || url.includes('afu.php');

        // If detected:
        // instantly terminate the tab.
        if (shouldKill) {
            chrome.tabs.remove(tabId);
        }
    }
});

๐Ÿš€ The Result

The transformation was surreal.
The site still attempts the same aggressive click-hijacking flow.
Invisible overlays still spawn.
Scripts still try redirect chains.
But now?

๐Ÿ’€ The tabs die instantly.

No chaos.
No cleanup.
No losing immersion every five seconds.
Just a silent war happening underneath the browser while the video plays normally.

๐Ÿงฉ What This Actually Taught Me

The interesting part wasnโ€™t blocking ads.

It was understanding how modern websites are willing to weaponize the browser itself:

  • Event hijacking
  • Trusted-click exploitation
  • Fingerprinting
  • Anti-debugging
  • Console wiping
  • Behavioral analysis
  • Redirect chaining
  • Dynamic overlay injection

๐Ÿ‘จโ€๐Ÿ’ป Final Thought

This entire project started because I got annoyed clicking โ€œClose Tabโ€ fifteen times.
It ended with me reverse-engineering popup infrastructure, anti-bot systems, and browser-level request interception pipelines.
And honestly?
That escalation path perfectly summarizes what I enjoy most about software engineering.

Tinkering.

And refusing to accept that a system is โ€œunbeatable.โ€

Top comments (0)