Feeling Blue(Noroff): Inside a Sophisticated DPRK Web3 Intrusion

Glitch effectGlitch effectGlitch effect
Glitch banner

Summary

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.


Figure 1: Visualization of attack chain


Initial access

The setup:

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.


Figure 2: .ics meeting invitation file sent to the employee under the guise of a Google Meeting

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.

Figure 3: Initial payload sent to the victim - 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

Figure 4: Disable logging, install Rosetta 2, and download binary

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. 

Figure 5: Printing the “progress” extracting the download payload

Then attempt to get the user’s password and verify it using sudo. They will continue doing this until a valid password is supplied.


Figure 6: Attempting to verify the user's password

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.


Figure 7: bash script removing shell history


Technical analysis

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:


Figure 8: Usernames of attacker machines responsible for compiling certain tooling


Persistent implant: Telegram 2

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

Figure 9: com.telegram2.update.agent LaunchDaemon 

Configuration

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. 

Functionality

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


Backdoor: Root Troy V4 (remoted)

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”.


Figure 10: Build artifacts show user “dominic” and project structure

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.


Figure 11: remoted curling down additional implants

Configuration

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).


Figure 12: Contents of the configuration file

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:

Figure 13: Contents of the .startup script file


Main execution

When main runs, it first creates the directory to store the configuration files:


Figure 14: Creating the config directory

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.


Figure 15: Self deletion if config extraction fails

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.


Figure 16:  New threads to run script and monitor logout

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.

Capability: Remote code execution

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

Capability: Execution during sleep

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.



Figure 17: Example output of querying system_profiler SPDisplaysDataType

C2 Communications

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.


Loader: InjectWithDyld (a)

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:

Figure 18: Script run by remoted to install and run a

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$%^.

Key derivation function

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.



Figure 19: Decompilation of password supplied to key deviation function

The salt is prepended to the actual payload, in the case of the baseApp it’s the base64 decoded GJM0bP36hbomz9Gw


Figure 20: Salt prepended to the payload base64

Payload decryption

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.

Figure 21: Decompilation of base64 decoding the payload and skipping the first 16 bytes (salt)

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.

Figure 22: AES-CFB implementation


Figure 23: Decompilation of decryption routine

This occurs for both base64 blobs which are later used in the process injection portion.


Process injection

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.


Figure 24: Setup to calling process injection code

Then the process of injection begins, by calling InjectAMD64, which is illustrated in the following figure:


Figure 25: Decompilation of InjectAMD64 function with posix_spawnattrs

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. 


Figure 26: Decompilation of getting a Mach port on the sacrificial process

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.


Figure 27: Parsing payload mach-o in preparation for copying

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).

Figure 28: Decompilation of the memory protection modifications per segment

After making the aforementioned memory modifications, the sleeping process is then restored with the injected payload, as seen in the following figure: 


Figure 29: Decompilation of restoring the sleeping process to execute the injected payload

Payload cleanup

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.


Decrypted Payloads: Nim Implant (Trojan 1) & Base App 

As was mentioned, there are two binaries decrypted by the previous step. 

Nim Implant (Trojan 1)

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.

Base app

The base application is a relatively bare-bones binary written in Swift by the author dominic

Figure 30: Build artifacts from base executable

The main method just runs a simple task on a loop (every 3.37 seconds).


Figure 31: Main method from the base app

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.


Figure 32: Closure called by the main method


Keylogger: XScreen (keyboardd)

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.



Figure 33: Build artifacts from keyboardd binary

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.


Figure 34: Decompilation of C2 resolution

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:



Figure 35: Decompilation of async loop 1

The second calls the MonitorClipboared (sic) function in a loop:



Figure 36: MonitorClipboared callback

The last is responsible for the screen collection functionality discussed in the next section.

Keylogging functionality 

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. 



Figure 37: Keylogging function loop

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:

Figure 38: Callback function checking which active window is being used

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:


Figure 39: Converting non-printable characters to a text representation



Figure 40: Conversion outputs


Screencapture functionality

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.


Figure 41: Decompilation of the screen recording driver function

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.

Figure 42: Decompilation of CaptureAndSend function


Clipboard functionality

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.


Figure 43: Decompilation of clipboard monitoring code


Send data

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:



Figure 44: Sending data to the C2 server


Infostealer: CryptoBot (airmond)

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

Figure 45: Compilation artifacts from airmond binary

Configuration

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

Crypto Stealer

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

C2 interaction

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


Identifying and mitigating Meeting application social engineering

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.


Conclusion

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. 




IOCs

Files

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



Infrastructure

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






Categories
Share

Sign Up for Huntress Updates

Get insider access to Huntress tradecraft, killer events, and the freshest blog updates.

By submitting this form, you accept our Terms of Service & Privacy Policy
Oops! Something went wrong while submitting the form.
Huntress at work