On June 11, 2025, Huntress received contact from a partner saying that an end user had downloaded, potentially, a malicious Zoom extension. The depth of the intrusion became immediately apparent upon installing the Huntress EDR agent, and after some analysis, it was discovered that the lure used to gain access was received by the victim several weeks prior.
This post aims to provide a detailed analysis from beginning to end of the intrusion, including a full breakdown of several new pieces of malware used by the threat actors.
We attribute with high confidence that this intrusion was conducted by the North Korean (DPRK) APT subgroup tracked as TA444 aka BlueNoroff, Sapphire Sleet, COPERNICIUM, STARDUST CHOLLIMA, or CageyChameleon—a state-sponsored threat actor known for targeting cryptocurrencies stemming back to at least 2017.
An employee at a cryptocurrency foundation received a message from an external contact on their Telegram. The message requested time to speak to the employee, and the attacker sent a Calendly link to set up meeting time. The Calendly link was for a Google Meet event, but when clicked, the URL redirects the end user to a fake Zoom domain controlled by the threat actor.
Several weeks later, when the employee joined what ended up being a group Zoom meeting, it contained several deepfakes of known senior leadership within their company, along with external contacts. During the meeting, the employee was unable to use their microphone, and the deepfakes told them that there was a Zoom extension they needed to download.
The link to this “Zoom extension” sent to them via Telegram was hxxps[://]support[.]us05web-zoom[.]biz/troubleshoot-issue-727318. The file downloaded in turn was an AppleScript (Apple’s built-in scripting language) named zoom_sdk_support.scpt.
This AppleScript first opens a legitimate webpage for Zoom SDKs, but after over 10,500 blank lines, it downloads a payload from a malicious website, https[://]support[.]us05web-zoom[.]biz, and after downloading completes, runs a script. While we weren’t able to recover this second stage from the intrusion, we were able to find a version on VirusTotal that provides good insight as to what happens next.
The script begins by disabling bash history logging and then checks if Rosetta 2, which allows Apple Silicon Macs to run x86_64 binaries, is installed. If it isn’t, it silently installs it to ensure x86_64 payloads can run. It then creates a file called .pwd, which is hidden from the user’s view due to the period prepending it and downloads the payload from the malicious, fake Zoom page to /tmp/icloud_helper.
Next, it performs another curl request using the cur1-request user agent. This has been observed in previous BlueNoroff intrusions like the one covered by Kasperksy in 2022. Unfortunately, this payload was also not live at the time of analysis.
Then attempt to get the user’s password and verify it using sudo. They will continue doing this until a valid password is supplied.
Lastly, it removes the shell history, so users are unaware of what ran. During our investigation, we noted that these history files had been modified at the time of the attack.
By the end of our investigation, we recovered 8 different malicious binaries from the victim host. We’ll cover the functionality of some of these binaries in this section. To quickly summarize what each one is:
Telegram 2: the persistent binary, written in Nim, responsible for starting the primary backdoor.
Root Troy V4 (remoted): fully featured backdoor, written in Go, and used to download the other payloads as well as run them.
InjectWithDyld (a): a binary loader written in C++ that is downloaded by Root Troy V4. It will decrypt two additional payloads.
Base App: A benign Swift application that is injected into.
Payload: A different implant written in Nim, with command execution capability.
XScreen (keyboardd): a keylogger written in Objective-C that has capability to monitor keystrokes, the clipboard, and the screen.
CryptoBot (airmond): an infostealer written in Go that is designed to collect cryptocurrency related files from the host.
NetChk: an almost empty binary that will generate random numbers forever.
Most of the implants, with the exception of the ones written in Nim, contained build artifacts showing the usernames of those who compiled the binaries. There were 4 personas responsible for different tooling:
The core implant responsible for running all the other components is called Telegram 2 and is written in Nim. It persists out of /Library/LaunchDaemons/com.telegram2.update.agent.plist, running a binary at /Library/Application Support/Frameworks/Telegram 2. The binary is adhoc signed with the identifier root_startup_loader_arm64.
Telegram 2 was used as the persistence mechanism and starting hourly, with the following plist:
Upon execution, this binary will create a config file in /private/var/tmp/cfg. Unfortunately, we weren’t able to recover this file from the victim machine.
This binary is very small and only has a few pieces of functionality:
poEchoCmd: run an echo command (testing)
poEvalCommand: run a command using /bin/bash
poInteractive: spawn an interactive shell
poDaemon: initialize persistence
This binary which was found running from /Library/WebServer/bin/remoted is a fully featured backdoor written in Go. Build artifacts show it’s actually called “Root Troy V4” or “RTV”.
The primary use we saw for this binary was to execute an AppleScript payload to download and execute another implant (covered in the next section). This command was run 6 times from when the customer was onboarded to when the host was isolated.
The binary stores associated information such as its configuration, payload versions, and startup commands in a directory located at /Library/Google/Cache/. The configuration file (.cfg) is encrypted with an RC4 key (3DD226D0B700F33974F409142DEFB62A8CD172AE5F2EB9BEB7F5750EB1702E2A) found in the binary. It contains the C2 information along with user IDs (redacted here).
There is also a version file (.version) encrypted with another RC4 key (C4DB903322D17C8CBF1D1DB55124854C0B070D6ECE54162B6A4D06DF24C572DF). It contains the version information for two of the payloads used later: {"cbot":"1.0.1","rt":"4.0.1"}.
The file .startup contains commands that should be run whenever a user logs in. It will start running the keylogging binary and one of the binaries contained in the .version file:
When main runs, it first creates the directory to store the configuration files:
Then it attempts to load the C2 information from inside the binary, which we covered in the last section. For whatever reason if that fails, they will kill the current process, delete the artifacts and exit.
After checking that configuration is all good, and there aren’t other instances running using the PID file, it will run two new threads: the execStartup function, which runs the script detailed earlier, and logoutMonitor which watches for the user logging out. If that happens it will trigger execStartup again.
Finally, it enters an infinite loop that collects the /Volumes from the system, as well as the running process list. These are sent to the C2 server periodically using the sendRequest function.
There are several different ways an operator can execute commands on the host using this malware:
execScript: run a remote AppleScript payload using osascript -e.
execShell: run a shell command using /bin/zsh -sc
execShellDetached: run a shell command using /bin/zsh -sc in the background
To avoid any instances where a user might observe code being executed, commands are wrapped by a checkSleep function which queries the state of the display. If the display is asleep, commands are executed, otherwise they are entered into a queue for later execution. To accomplish this they use system_profiler SPDisplaysDataType and look for the Online field.
Requests to the C2 are sent to http://$DOMAIN/update and contain the host information collected using the function host.PlatformInformationWithContext from the library github.com/shirou/gopsutil/. It also supplies the boot time, and the versions of the other implants currently running.
As mentioned before, the binary called a was downloaded by remoted using the AppleScript payload. The password found in that download cradle is required for this binary to work correctly as it’s used to derive the AES keys for payload decryption. To quickly review what the threat actor did with this binary:
There are two main things this binary does: in the first, it takes another binary and a password as arguments and will decrypt embedded payloads. In the second, it simply takes the argument --d and will overwrite all files in the current directory with zeros as an antiforensic measure. The actors first ran the payload with the parameters ./cloudkit and a password of gift123$%^.
To decrypt both the payload and the base app, the supplied password is used with Password-Based Key Derivation Function (PBKDF) to derive AES keys.
The salt is prepended to the actual payload, in the case of the baseApp it’s the base64 decoded GJM0bP36hbomz9Gw.
After the key has been derived, the sample then decrypts the two payloads using a modified version of AES-CFB. They pass the base64 decoded content, skipping the salt bytes, and the key to the AesEncrypt function.
They iterate through the decoded base64 and call AesTrans on each block, which encrypts the buffer using AES, this output is then XORed against the original resulting in the decrypted content.
This occurs for both base64 blobs which are later used in the process injection portion.
By far the most interesting part about this malware is how it deploys the malicious payload. Anyone who looks at Windows is extremely familiar with the technique of process injection, in which a process will write code into another process’ memory. But, historically process injection hasn’t been common on macOS because there’s a large number of prerequisites needed to bypass Apple’s memory protections.
This sample takes advantage of some edge cases in Apple’s security model to allow for injection! Binaries that want to do this need a debugging tool entitlement(s), which allows them to attach to other processes and more importantly get task ports. This binary, and several of the others used in this intrusion have this:
com.apple.security.cs.debugger
com.apple.security.get-task-allow
After decrypting the payload the malware will check the magic bytes of the resulting macho file. If they are 0xbebafeca it’s a FAT executable (meaning both an ARM and x86_64 binary glued together), so it has to iterate over the FAT header entries until it finds the x86_64 macho header. Otherwise, if the magic bytes are 0xfeedfacf it is just an x86_64 macho and that isn’t necessary, so it can just call the injection routine. The same process occurs for the ARM executable but it looks for a cputype of CPU_TYPE_ARM64.
Then the process of injection begins, by calling InjectAMD64, which is illustrated in the following figure:
To kick off the injection process, a new process is spawned with the attributes setup before. Then task_for_pid is called on the process, which will return the Mach port of the process. Having access to this port allows the malware to utilize the mach_vm APIs allowing for arbitrary memory manipulation and task management.
From there, they get a list of threads associated with the process using task_threads. If that is successful, they begin to parse the mach-o header of the decrypted payload. This is a very similar process to how they decide whether or not to call the inject routine for a FAT binary or not. They do this to find the total number of segments in the payload binary.
At this point, the malware begins to copy the segments from the payload binary into the sacrificial process and modifies the memory to allow for execution. The following decompiled code shows how the page permissions were modified to read and write, as seen in the _mac_vm_protect function, where the new_protection variable is set to 3 (VM_PROT_READ | VM_PROT_WRITE).
After making the aforementioned memory modifications, the sleeping process is then restored with the injected payload, as seen in the following figure:
After the payloads were deployed, the actor then ran the binary using the --d flag which calls the ZeroWrite function. This iterates over all files in the current directory, and will write null bytes over all contained functions.
As was mentioned, there are two binaries decrypted by the previous step.
The Nim implant is primarily used to interactively send commands to and from the infected host. The primary file is called trojan1.nim and allows the operator to issue commands and receive responses asynchronously. To communicate with the C2 it uses websockets wss[:]//firstfromsep[.]online/client.
Analysis is still in progress on this binary and the post will be updated when complete.
The base application is a relatively bare-bones binary written in Swift by the author dominic.
The main method just runs a simple task on a loop (every 3.37 seconds).
The task simply prints the string Current: YYYY-MM-DD HH:MM:SS to /dev/null. This is probably just to keep the binary alive so it can be injected at some point in the future if needed.
This binary is used for keylogging, screen recording, and clipboard retrieval. It is written in Objective-C and was compiled by a user named pooh.
To start execution, it will first check if the file /Users/Shared/._cfg exists, which contains the C2 URL. It defaults to using the server https[:]//metamask[.]awaitingfor[.]site/update but in this case it was the same as the URL found in the recovered ._cfg file.
It accepts 3 potential command line arguments:
-u: use a custom C2 domain
-c: how long to sleep between screen captures
-p: if clipboard should be monitored
In the case of this intrusion it was called repeatedly by the remoted binary with the -p argument.
The overall way this binary works is by starting 3 asynchronous loops, one for each type of collection. The first loop will send the content of the keylog buffer to the C2 server:
The second calls the MonitorClipboared (sic) function in a loop:
The last is responsible for the screen collection functionality discussed in the next section.
The actual keylogging functionality is implemented using the Core Graphics library with the EventTapCreate API. This API takes a callback function that will execute every time a keypress event is registered.
The first thing the callback function will do is keep track of what application was being interacted with for each keypress. They do this by querying frontmostApplication and grab that app name’s bundle identifier. If it is different from the last call, they will log the application name and time to the keylog buffer:
After that happens, they will check if the keycode is a printable character. If it’s a special one (control, command, etc.) they will convert it to a text representation and then append it to the keylog buffer:
To capture the screens, the malware enters an infinite loop that checks the number of active displays using CGGetActiveDsiplayList. If there is at least one active display it will start capturing data, and if there are more than one it will iterate over all available screens to capture each one.
The function CaptureAndSend is responsible for actually gathering the data. It takes an image of the display using the CGDisplayCreateImage API, and then saves that content to a file located at /private/tmp/google_cache.db. If that is successful, it will convert the image to base64 and append the letter “I” so that the C2 can delineate what data is an image. Finally, it uses the same SendData function to send everything off to the C2 server.
During our investigation, we did not find any data stored at the file save location.
To monitor the clipboard, they simply grab the system pasteboard, and then extract the text content from that object. The infinite loop will monitor if there has been a change to the clipboard content and if so it will write the content to the shared keylog buffer.
To send the data to the C2 server, they create a string that contains a UUID, the uid of the victim, the data, the username, and a token embedded in the binary:
The airmond binary is a full-featured infostealer with a focus on cryptocurrency theft. It is written in Go and has a large number of build artifacts showing it’s a project called CryptoBot compiled by a user chris.
Much like the other malware in this incident, CryptoBot makes use of several files in its current directory
/Library/AirPlay/.pid: A PID file for preventing multiple instances.
/Library/AirPlay/.cache: A cache to store collected crypto data.
/Library/AirPlay/.CFUserTextEncoding: User and a key (user|key)
/Library/Google/Cache/.cfg: Shared config with the “Root Troy V4” binary.
/Library/Google/Cache/.version: Shared version info with “Root Troy V4” binary.
The config files in the AirPlay directory are encrypted using AES-CFB with an IV of 0. The key is static and is embedded in the binary f6102a492570dee84bbc9ebd8bd7bfab4e442eae3b416b1a. Several initialization functions are used to create the previously mentioned files:
main.initializeCryptoCache
main.initializeUserInfo
main.initializeVersion
main.writePid
And there are another set of functions to load those configuration files while running:
main.loadUserInfo
main.loadVersions
main.writeCryptoCache
main.readCryptoCache
The main purpose of this binary is to index cryptocurrency-related information from the host. As is typical with stealers to do this, they iterate over installed browser extensions looking for wallet plugins. If those are found, it then calls a number of helper functions designed to extract the sensitive information from those extensions. They are all contained in the crypto-bot module:
crypto-bot/wallet.ExtractAddressInfosFromBinance
crypto-bot/wallet.ExtractAddressInfosFromBitget
crypto-bot/wallet.ExtractAddressInfosFromCoin
crypto-bot/wallet.ExtractAddressInfosFromKeplr
crypto-bot/wallet.ExtractAddressInfosFromLeather
crypto-bot/wallet.ExtractAddressInfosFromMetamask
crypto-bot/wallet.ExtractAddressInfosFromNabox
crypto-bot/wallet.ExtractAddressInfosFromOKX
crypto-bot/wallet.ExtractAddressInfosFromPhantom
crypto-bot/wallet.ExtractAddressInfosFromPhantom.Println.func1
crypto-bot/wallet.ExtractAddressInfosFromRabby
crypto-bot/wallet.ExtractAddressInfosFromRainbow
crypto-bot/wallet.ExtractAddressInfosFromRonin
crypto-bot/wallet.ExtractAddressInfosFromSafepal
crypto-bot/wallet.ExtractAddressInfosFromSender
crypto-bot/wallet.ExtractAddressInfosFromStation
crypto-bot/wallet.ExtractAddressInfosFromSubwallet
crypto-bot/wallet.ExtractAddressInfosFromSui
crypto-bot/wallet.ExtractAddressInfosFromTon
crypto-bot/wallet.ExtractAddressInfosFromTron
crypto-bot/wallet.ExtractAddressInfosFromTrust
crypto-bot/wallet.ExtractAddressInfosFromUnisat
crypto-bot/wallet.ExtractAddressInfosFromXverse
The binary interacts with a C2 at productnews[.]online using HTTP. Requests are encrypted using the same key and algorithm used to encrypt the configuration files. There is also an option to send unencrypted packets if necessary:
main.postEncryptedData
main.postToServer
Remote workers, especially in high-risk areas of work are often the ideal targets for groups like TA444. It is important to train employees to identify common attacks that start off with social engineering related to remote meeting software:
Be wary of Calendar invites that are marked with urgency from individuals you haven’t communicated with in some time, or groups of individuals that are not normally in meetings together.
Users should be immediately wary of sudden, unnatural changes such as switching meeting platforms at the last minute, a request to install an “Extension” or “Plugin”, unpopular TLD names such as .biz, .xyz, .site, .online, or .click, and requests to enable remote access or similar controls.
Advise employees in the event any of these indicators, or even uncertainty, to disconnect the Meeting software immediately and report this to your security teams, HR, and other teams.
Historically, macOS has been viewed as a smaller target compared to its Windows counterpart. Spoken alongside the “Macs don’t get viruses” adage that has permeated the space over the last two decades, they are often seen as “not requiring protection.” Due to these sentiments, it understandably dovetails into more targeted attacks. Over the last few years, we have seen macOS become a larger target for threat actors, especially with regard to highly sophisticated, state-sponsored attackers.
In this instance, we saw BlueNoroff utilizing Mac-specific techniques in a very targeted attack. They leveraged AppleScript, which is unique to macOS, multiple implants, keyloggers, and screencaptures. Additionally, they would capture contents of the clipboard, clean up their session history, and also look for a very extensive array of cryptowallets, showcasing their focus on macOS.
As these attacks and the frequency in which they occur continue to rise, it will be evermore important to protect your Macs. As we saw here, the attackers didn’t just use common, cross-platform attack techniques, but instead leveraged Mac-specific binaries, APIs, and functionality.
Name |
SHA256 |
Notes |
a |
4cd5df82e1d4f93361e71624730fbd1dd2f8ccaec7fc7cbdfa87497fb5cb438c |
C++ Dropper |
remoted |
ad01beb19f5b8c7155ee5415781761d4c7d85a31bb90b618c3f5d9f737f2d320 |
Go Backdoor |
airmond |
ad21af758af28b7675c55e64bf5a9b3318f286e4963ff72470a311c2e18f42ff |
Go Infostealer |
keyboardd |
432c720a9ada40785d77cd7e5798de8d43793f6da31c5e7b3b22ee0a451bb249 |
Obj-C keylogger / screenrecorder |
zoom_sdk_support.scpt |
1ddef717bf82e61bf79b24570ab68bf899f420a62ebd4715c2ae0c036da5ce05 |
Initial access AppleScript payload |
Telegram 2 |
14e9bb6df4906691fc7754cf7906c3470a54475c663bd2514446afad41fa1527 |
Persistent Nim implant |
cloudkit |
2e30c9e3f0324011eb983eef31d82a1ca2d487bbd13a6d32d9e11cb89392af23d |
Sacrificial binary used for process injection |
netchk |
469fd8a280e89a6edd0d704d0be4c7e0e0d8d753e314e9ce205d7006b573865f |
C Injection candidate |
payload |
080a52b99d997e1ac60bd096a626b4d7c9253f0c7b7c4fc8523c9d47a71122af |
Nim Implant |
baseApp |
2e30c9e3f0324011eb983eef31d82a1ca2d47bbd13a6d32d9e11cb89392af23d |
Swift Injection Candidate |
IP |
Notes |
hxxps[://]safeupload[.]online | |
hxxps[://]metamask[.]awaitingfor[.]site/update |
C2 server for keylogger |
hxxps[://]support[.]us05web-zoom[.]biz/842799/check |
Initial url sent to victim via Telegram, resulting in download of zoom_sdk_support.scpt |
productnews[.]online |
C2 for CryptoBot |
firstfromsep[.]online |
C2 for a’s Nim Payload |
safefor[.]xyz |
C2 for RTV4 |
readysafe[.]xyz |
C2 for RTV4 |
Get insider access to Huntress tradecraft, killer events, and the freshest blog updates.