Cracked software beats gold: new macOS backdoor stealing cryptowallets

A month ago, we discovered some cracked apps circulating on pirating websites and infected with a Trojan proxy. The malicious actors repackaged pre-cracked applications as PKG files with an embedded Trojan proxy and a post-install script initiating the infection. We recently caught sight of a new, hitherto unknown, macOS malware family that was piggybacking on cracked software. The threat proved far more potent than an unauthorized proxy server installation.

Stage 1.

The samples we found could be successfully run on macOS Ventura 13.6 and later, suggesting that the operators were targeting only users of the newer operating system versions on both Intel processors and Apple silicon machines. The compromised disk images contain a program named “Activator” and the application that the user is looking to install. Opening/mounting the image brings up a window with installation instructions.

Window with installation instructions

The instruction tells the user to copy the app to /Applications/ and then launch Activator. The latter looks fairly unsophisticated: just a PATCH button that displays a password prompt when clicked.

Activator window and password form

A look under the hood revealed an interesting fact right away: the application in the Resources folder somehow contained a Python 3.9.6 installer and an extra Mach-O file with the name tool. The main Fat Mach-O file, tellingly named GUI, essentially implemented the PATCH button, clicking which launched two events:

  • The Python installer was copied to the temporary file directory: /tmp/
  • The tool executable in the resources folder ran with administrator privileges. To enable this, Activator employed the now-obsolete AuthorizationExecuteWithPrivileges function, which brought up the window with the admin password prompt.

Once running, tool checked the system for an installed copy of Python 3, and if it did not find one, it installed that which it had previously copied to /tmp/. Next, it “patched” the downloaded app: tool compared the first 16 bytes of the modified executable with a sequence hardcoded inside Activator and removed them in the case of a match:

Checking the first 16 bytes of the executable

The app amusingly started working and appeared to have been cracked. The trick was that the malicious actors had taken pre-cracked application versions and added a few bytes to the beginning of the executable, thus disabling it to make the user launch Activator.

Stage 2. A downloader

A completed “patching” kicked off the main payload, with the sample reaching out to its C2 for an encrypted script. The program obtained the C2 URL by stringing together words from two hardcoded lists and adding a random sequence of five letters as a third-level domain name. With this URL, the sample made a request to a DNS server as an attempt to get a TXT record for the domain. This was a fairly interesting and unusual way of contacting a command-and-control server and hiding activity inside traffic, and it guaranteed downloading the payload, as the response message came from the DNS server. TXT records could contain miscellaneous domain details that the application might require, so a request like that looked perfectly normal per se.

We tried every possible combination of the hardcoded words, which were the same for every sample we studied, to find only one functional domain name: imohub[.]net. The exact third-level domain name was irrelevant as long as it was part of the request. The response from the DNS server contained three TXT records, which the program later processed to assemble a complete message. Each record was a Base64-encoded ciphertext fragment whose first byte contained a sequence number, which was removed during assembly. The ciphertext was AES-encrypted in CBC mode. The decrypted message contained the following Python script.

Decrypted script

Before running the script, tool went through the following steps:

  • Computing a script hash and checking that it has not been launched before. To do this, it searched environment variables for the lastExecutedScriptHash key, and if it was there, it compared the hash with the data stored under the key. If the hashes matched, it did not launch the script. If the hashes differed, tool ran the script and replaced the hash in the variable. If the environment variables did not contain the key, tool created one and stored the hash of the current script in it.
  • Replacing the “_g_” in the link inside the script with a random sequence of 64 characters that resemble a hash.
  • Writing a script to /var/root/Library/Caches/<uuid>.py that kills all NotificationCenter processes every ten seconds.

Killing NotificationCenter processes

  • Writing to /Library/LaunchAgents/launched.<uuid>.plist two agents that autostart the scripts on reboot.

Launch agent code

As you can see from the decrypted script, it reached out to apple-health[.]org every 30 seconds and tried to download and execute the following script. The script was given 14,400 seconds to run, after which the process was killed and a new version of the script was downloaded.

Stage 3. A backdoor

At first we thought the downloaded Python script was out of date, as apple-health[.]org C2 server was not responding to our requests. However, after some time, we managed to obtain a payload in the form of another Python script. That finally revealed the malware operators’ goals: the main function of the script was to execute arbitrary commands it received from the server. Judging by the command processing code, these came in the form of further Base64-encoded Python scripts.

Code that executes received commands

Besides executing commands, the script harvested and sent to the server the following information:

  • Operating system version
  • List of directories inside /Users/
  • The blank “av” field presumably would be populated with information about the presence of antimalware programs in subsequent versions.
  • List of installed applications
  • CPU type
  • External IP address
  • The blank “ver” field that presumably would be used for sending information about the payload version

At the time of our investigation, the server notably returned no commands and later stopped responding altogether. So, we downloaded the third-stage Python script again, only to find that the new version contained changes. In particular, the developers had changed the “metadata” stored at the beginning of the program and containing the C2 server IP address and domain name, and the program GUID and version. These were apparently updated automatically inside the script as soon as the server IP address changed, which happened approximately every 10–20 minutes. There were also updates to the functional code that had to be made by humans (see the images below).

Three versions of the script side by side (left to right: the first version 18c564a5cc4b7414df8345a8bdce7418, and the two subsequent versions f4282d7e32c7e8ab4e075c572ac43803 and 352f0d288e612e4f66c50aaf9214a81d)

This suggested the malware campaign was still a work-in-progress. The commands we had been trying to get from the server simply might not have been written yet.

Stage 4. Good old cryptostealer

In addition to the aforementioned features, the script contained two more notable functions: check_exodus_and_hash() and check_btccore_and_hash().

Code that downloads an infected Exodus wallet

Both featured the domain apple-analyser[.]com, which served as the host for further payloads. The two functions had a similar purpose: checking if the device contained a relevant cryptowallet application, and if so, replace it with one downloaded from apple-analyser[.]com. Besides the application, the server also stored a clean version of the Electron framework for launching a “new” version of Exodus, as well as Exodus.scpt, which you can see in the screenshot above and which is only needed for running the following Shell command when starts.

12do shell script “~/electron/ ~/exodus”quit

Were the malware operators indeed so kind as to help their victims with updating their wallets as compensation? Alas, it was naive to hope for the best: both wallets proved infected.

The malicious actors had infected Exodus by embedding their brainchild right at the beginning of the application: the file main/index.js, which was the first to start when the application was launched.

Snippet of main/index.js containing the malicious implant

The code in the screenshot told us that the application sent into the channel with the name 334b4425988b47a5b67c92518f9815c6 some data, which subsequently went to 22[.]imohub[.]workers[.]dev. Well, all we had to do now is find the exact piece of the code that sent the data, and this was… drumroll… wallet/index.js. As the code had no line breaks or indents, we gave it proper formatting and checked to see what was happening there.

Contents of wallet/index.js

The malicious actors had added to the wallet unlock handler a call to a function that simply sent an entered seed phrase to the C2 through the channel. There were no other new features.

Bitcoin-Qt was no JavaScript file, but rather a full-fledged Mach-O. As we searched the code for the C2 from Exodus, we soon realized that Bitcoin-Qt had a similar modus operandi: this application stole the wallet unlock password along with the wallet, its name, and the balance.

Code that sends the data to the C2

Thus, even in the absence of incoming commands from the C2, the program was still capable of inflicting significant damage on the user by stealing their cryptowallets.


The aforementioned cracked applications are one of the easiest ways for malicious actors to get to users’ computers. To elevate their privileges, they just need to ask for the password, which typically causes no suspicions with users during software installation. That said, some of the things that the authors of the malware campaign had come up with, like placing the Python script inside a domain TXT record on the DNS server, were seriously ingenious. The script was later added to startup agents to download and execute the next-stage payload in an infinite loop, which enabled the malware operators to deliver updates to the infected machine. The final payload was a backdoor that could run any scripts with administrator privileges, and replace Exodus and Bitcoin cryptowallet applications installed on the machine with infected versions that stole secret recovery phrases the moment the wallet was unlocked.

Indicators of compromise


c88c28149387ccf52ca3869442533fd9Fat Mach-OGUI
a5924fff42d60a732853da167a743182Fat Mach-OGUI
9c0e8d45cbf5cae428bef90b5824e5b1Fat Mach-OGUI
a9231044dd45a85a0bf45e01584bf213Fat Mach-OGUI
2ed32d3df8b4a2ef891b44a6397cf6eaFat Mach-OGUI
7fd9a401fd0d7901cf4494333d1896cbFat Mach-OGUI
e12566cd9d72a9b56d5e53f00b7d2d53Fat Mach-OGUI
4c2ec35d13c5f44000caf658e40e444cFat Mach-OGUI
4886a687ada61fc7f53b41f6020e76ccFat Mach-OGUI
c7178d08c13f3e49a6ebefe23d1fedffFat Mach-OGUI
cad3081fc6174ca4a4c18b8f73b3fe59Fat Mach-OGUI
3a89719527d51e7c60854704e9f49a32Fat Mach-Otool
5bab5ba8c509a9baa5db246d932a099fFat Mach-Otool
948c1bdc9edf3e57758b677a0a449f34Fat Mach-Otool
e64773b03ad1eae52180c2b58907f1f6Fat Mach-Otool
3f89644dfc394e888a741f6c09638d98Fat Mach-Otool
29a35e0e65bba727a97747acdf921c09Fat Mach-Otool
3b357b8d65537d40e87599c5329d2a3dFat Mach-Otool
adede572ad9599e331592103f9eea2a2Fat Mach-Otool
a386380e03097055c24b0f35263d5492Fat Mach-Otool
95c86de53ad9ca116f8c6eb2e6a152f5DMGAiseesoft Blu-ray Player 6.6.38.dmg
be7e6e625d15d30ff47e34ebb1ee4511DMGBike 1.18.0.dmg
2ebfe93a39ce3fcecca883b5f182029eDMGFinal Draft 12.0.10.dmg
e5f12e92b1fa956d02d35d6224abdbc8DMGKeep It 2.3.7.dmg
3af3d6ba3c80b7bf5d67deddb2971c61DMGOmniReader Pro 2.6.8.dmg
29b1ba90407a93400e062fb65dc9b667DMGSimpleMind Pro 2.3.0.dmg
a33b6c5905cefced329fa89f5eebb481DMGSyncBird Pro 4.0.8.dmg
71eefe83f836ebceadc9f68ff0e37d3bDMGxScope 4.7.0.dmg
d1177ed07dddb09415c175a205143eb6DMGSwinsian 3.0.dmg
b2d519d13125c29832b132e927fd141bDMGInfuse Pro 7.6.6.dmg
609596d15e684f4a8ea80b7ee4b8c6a8DMGFix My iPhone 2.4.9.dmg
bbe4c19f3b675705073ba3e8a560b768DMGOmni Toolbox 1.5.1.dmg
9124843fdbf27e7b31d2f883042021a9Python scriptStage two
ff608ab027db4d1e076c1d8098e8dc8aPython scriptStage two (tool to disable notifications)
f4282d7e32c7e8ab4e075c572ac43803Python scriptStage three
09ab22fcf21385cc5702ec52ac4eca02Python scriptStage three
352f0d288e612e4f66c50aaf9214a81dPython scriptStage three
948a90b43ade9dbc559fd27be404f9f0Python scriptStage three
18c564a5cc4b7414df8345a8bdce7418Python scriptStage three
38e4ef0d9221b25510cc50bcc8f4b4e8AppleScript scriptExodus.scpt

C2 addresses


Don’t Stop Here

More To Explore

favicon__1_ removebg-png


Stay informed with the latest insights in our Infostealers weekly report.

Receive immediate notification if your email is involved in an infostealer infection.

No Spam, We Promise

favicon__1_ removebg-png


Stay informed with the latest insights in our Infostealers weekly report.

Receive immediate notification if your email is involved in an infostealer infection.

No Spam, We Promise