All posts
6 min readglizzykingdreko

Breaking Down a Unique Anti-Bot Measure

While working on automating car searches for DiscoverCars, I stumbled upon a fascinating — and somewhat unusual — security measure designed to verify…

Breaking Down a Unique Anti-Bot Measure

While working on automating car searches for DiscoverCars, I stumbled upon a fascinating — and somewhat unusual — security measure designed to verify legitimate users. This post details my debugging process, how I identified the key mechanism at play, and why this method is both creative and flawed.

The Initial Challenge

To retrieve car listings, the system required an initialization session flow before making a key request:

curl 'https://www.discovercars.com/en/search/get-result-list/{session_id}?gToken={recaptcha_token}' \
  -H 'accept: application/json, text/javascript, */*; q=0.01' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36' \
  -H 'x-requested-with: XMLHttpRequest'

After multiple attempts, I encountered HTTP 400 errors. Oddly enough, reCAPTCHA v3 tokens weren’t even validated. This made me suspect that the real check was happening elsewhere.

Finding the Real Gatekeeper

I started analyzing cookies one by one and finally identified the critical piece: gtm_ga_ggbit. Unlike the other cookies, this one was actively validated. However, online searches turned up almost nothing on it—except for a one-year-old Freelancer post from a Chinese user struggling with the same issue.

With no documentation available, I shifted to a debug-first approach to understand how this cookie was set.

Tampermonkey Debugging to the Rescue

Since I suspected that JavaScript was responsible for generating gtm_ga_ggbit, I wrote a Tampermonkey script to hook into document.cookie and break execution when this specific cookie was set.

// ==UserScript==
// @name         Cookie gtm_ga_ggbit Debugger
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Break into debugger when gtm_ga_ggbit cookie is set by any method.
// @author       glizzykingdreko
// @match        *://*/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const TARGET_NAME = 'gtm_ga_ggbit';
    let lastCookieValue = document.cookie;  // store initial cookies

    // Helper: trigger debugger if target cookie present in document.cookie string
    function triggerDebuggerIfCookieFound(source) {
        // Check for the target cookie name in the cookie string
        const cookieStr = document.cookie;
        if (cookieStr && cookieStr.indexOf(TARGET_NAME + '=') !== -1) {
            console.log(`[Tampermonkey] Cookie "${TARGET_NAME}" detected via ${source}.`);
            alert(`Cookie ${TARGET_NAME} was set via ${source}!`);
            debugger;  // pause execution
        }
    }

    // Periodic check (polling) as fallback for any missed events (e.g., Set-Cookie headers)
    function startCookiePolling() {
        setInterval(() => {
            const current = document.cookie;
            if (current !== lastCookieValue) {  // cookies changed
                if (current.indexOf(TARGET_NAME + '=') !== -1 && lastCookieValue.indexOf(TARGET_NAME + '=') === -1) {
                    // target cookie appeared (wasn't in last value)
                    triggerDebuggerIfCookieFound('polling');
                }
                lastCookieValue = current;
            }
        }, 500);  // check every 500ms (adjust as needed)
    }

    // Hook document.cookie setter for JavaScript writes
    const docCookieDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
    if (docCookieDesc && docCookieDesc.configurable) {
        const nativeSetter = docCookieDesc.set;
        const nativeGetter = docCookieDesc.get;
        Object.defineProperty(Document.prototype, 'cookie', {
            configurable: true,
            enumerable: true,
            get() {
                return nativeGetter.call(this);
            },
            set(value) {
                if (typeof value === 'string' && value.indexOf(TARGET_NAME + '=') === 0) {
                    console.log(`[Tampermonkey] Cookie "${TARGET_NAME}" being set via document.cookie:`, value);
                    alert(`Cookie ${TARGET_NAME} is being set via script!`);
                    debugger;
                }
                // Actually set the cookie using native setter
                nativeSetter.call(this, value);
            }
        });
    }

    // Hook Fetch API
    if (window.fetch) {
        const origFetch = window.fetch;
        window.fetch = function(...args) {
            return origFetch.apply(this, args).then(response => {
                // After response, check cookies
                triggerDebuggerIfCookieFound('fetch/XHR response');
                return response;
            });
        };
    }

    // Hook XMLHttpRequest
    if (window.XMLHttpRequest) {
        const origOpen = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function(...args) {
            // (Optional: store URL or method if needed for debugging)
            this._debugUrl = args[1];
            return origOpen.apply(this, args);
        };
        const origSend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.send = function(...args) {
            this.addEventListener('loadend', () => {
                triggerDebuggerIfCookieFound('XHR response');
            });
            return origSend.apply(this, args);
        };
    }

    // Hook WebSocket
    if (window.WebSocket) {
        const NativeWebSocket = WebSocket;
        // Override WebSocket constructor
        window.WebSocket = function(...args) {
            const ws = new NativeWebSocket(...args);
            ws.addEventListener('open', () => {
                triggerDebuggerIfCookieFound('WebSocket handshake');
            });
            return ws;
        };
        // Copy prototype to maintain instanceof checks
        window.WebSocket.prototype = NativeWebSocket.prototype;
        window.WebSocket.CONNECTING = NativeWebSocket.CONNECTING;
        window.WebSocket.OPEN = NativeWebSocket.OPEN;
        window.WebSocket.CLOSING = NativeWebSocket.CLOSING;
        window.WebSocket.CLOSED = NativeWebSocket.CLOSED;
    }

    // Use CookieStore API events if available (for modern browsers)
    if (window.cookieStore && window.cookieStore.addEventListener) {
        try {
            window.cookieStore.addEventListener('change', event => {
                // Check if our target cookie was added/changed
                for (const change of event.changed) {
                    if (change.name === TARGET_NAME) {
                        console.log(`[Tampermonkey] Cookie "${TARGET_NAME}" change detected via CookieStore API.`);
                        alert(`Cookie ${TARGET_NAME} was set (CookieStore API)!`);
                        debugger;
                    }
                }
            });
        } catch(e) {
            console.warn('CookieStore API event registration failed:', e);
        }
    } else {
        // Fallback to polling if CookieStore events not supported
        startCookiePolling();
    }
})();

With this script, I could pinpoint exactly where gtm_ga_ggbit was being set.

Uncovering the SVG-Based Obfuscation

The key revelation came from observing a strange JavaScript function that generated gtm_ga_ggbit using color attributes from SVG elements embedded in the frontend.

JavaScript Logic

var svgConvertor = [
    [109, 97, 105, 110, 45, 119, 114, 97, 112, 112, 101, 114, 45, 98, 111, 100, 121],
    [102, 97, 118, 105, 99, 111, 110, 95, 49, 54, 120, 49, 54],
    [102, 97, 118, 105, 99, 111, 110, 95, 51, 50, 120, 51, 50],
    [97, 112, 112, 108, 101, 95, 116, 111, 117, 99, 104, 95, 105, 99, 111, 110, 95, 49, 56, 48, 120, 49, 56, 48]
];

var svgContent = '';
for (let i in svgConvertor) {
    let s = '';
    for (let j in svgConvertor[i]) {
        s += String.fromCharCode(svgConvertor[i][j]);
    }
    s = $('#' + s).attr('colour');
    svgContent += s.split('').reverse().join('');
}

if (typeof myCookie !== 'undefined') {
    myCookie.setCookie('gtm_ga_ggbit', svgContent);
}

Breaking It Down

  • The svgConvertor array deciphers into the IDs of four frontend elements: main-wrapper-body, favicon_16x16, favicon_32x32, apple_touch_icon_180x180
  • Each of these elements has a colour attribute containing a seemingly random hex string.
  • The script **extracts these values, reverses them, concatenates them, and assigns them to **gtm_ga_ggbit.

Example:

<div class="main-wrapper" id="main-wrapper-body" colour="#d61805d3"></div>
<link id="favicon_16x16" colour="#8585e694">
<link id="favicon_32x32" colour="#7b589a35">
<link id="apple_touch_icon_180x180" colour="#5dae22af">

This approach is unique because it dynamically obfuscates the cookie value based on frontend-rendered elements, likely making it harder for automated tools to predict.

Bypassing It in Python

Despite its creativity, this method is easy to reverse-engineer. We can simply parse the frontend, extract the necessary values, and reconstruct the cookie.

soup = BeautifulSoup(frontend_response.text, 'html.parser')
svg_content = ''
for element in [
    "main-wrapper-body",
    "favicon_16x16",
    "favicon_32x32",
    "apple_touch_icon_180x180"
]:
    data = soup.find(id=element).get('colour')
    svg_content += ''.join(reversed(data))
svg_content = svg_content.replace('#', '')
session.cookies.set("gtm_ga_ggbit", svg_content)

Final Thoughts

This was one of the most creative anti-bot techniques I’ve encountered. While it’s a clever way to obfuscate authentication, it’s ultimately ineffective because:

  1. The values are client-side and can be extracted effortlessly.
  2. The logic is predictable once analyzed.
  3. It adds complexity without significantly improving security.

Nonetheless, this approach could trick less experienced devs into wasting time debugging other factors, making it an amusing case study in security-through-obscurity.

Would love to hear if anyone else has encountered similar techniques!

Follow & Contact Me

If you enjoyed this breakdown, feel free to follow me on:

Or reach out directly:

anti-botweb-scrapingreverse-engineeringweb-automationjavascript

Skip the reverse-engineering.

Takion returns fresh cookies, headers, and tokens for every major antibot wall. One POST, no browser, first call within the hour.