Deep dive analysis of browser extension malware live on Firefox extension store

yourdev.net · ernos · 8 days ago · view on HN · research
quality 7/10 · good
0 net
Browser extension malware analysis - using browser-xpi-malware-scanner.py to find malware in the wild | YourDev.net Blog πŸͺ We Value Your Privacy We use cookies to enhance your browsing experience, analyze site traffic, and personalize content. By clicking "Accept All", you consent to our use of cookies. Privacy Policy Accept All Cookie Settings Reject All Cookie Preferences Γ— Manage your cookie preferences below. You can enable or disable different types of cookies based on your preferences. Necessary Cookies Always Active These cookies are essential for the website to function properly. They enable basic features like page navigation, form submission, and secure areas access. The website cannot function properly without these cookies. Analytics Cookies These cookies help us understand how visitors interact with our website by collecting and reporting information anonymously. This helps us improve our website and provide better content. We use Google Analytics. Marketing Cookies These cookies are used to track visitors across websites. The intention is to display ads that are relevant and engaging for individual users. We currently do not use marketing cookies but may in the future. Preference Cookies These cookies enable the website to remember choices you make (such as your language preference or dark mode setting) and provide enhanced, more personalized features. Save My Preferences Reject All Home / Blog / Cryptography & Steganography 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: [email protected] 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 Finding 1 β€” Steganographic Payload in PNG Icon (CRITICAL) Finding 2 β€” Unicode Low-Byte Encoding Trick Finding 3 β€” Decoded Payload: The C2 String Table Finding 4 β€” 72-Hour Sleeper with Random Sampling Finding 5 β€” C2 Beacon via Another PNG File Finding 6 β€” Dynamic declarativeNetRequest Rule Injection Finding 7 β€” Affiliate Commission Hijacking Finding 8 β€” Content Script Privilege Escalation Bridge Finding 9 β€” Arbitrary URL Redirect on Any Domain Finding 10 β€” CSP Erasure Complete Attack Chain 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: '' β€” Access to ALL website content β€” can read/exfiltrate any page data PERMISSION: permissions: ['tabs', 'storage', 'declarativeNetRequest', 'downloads', ''] [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="