Inside the BlueNoroff Web3 macOS Intrusion Analysis | Huntress
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. . .
SummaryOn 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—astate-sponsored threat actor known for targeting cryptocurrencies stemming back to at least 2017. Figure 1: Visualization of attack chainInitial accessThe 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 MeetingSeveral 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 washxxps[://]support[. ]us05web-zoom[. ]biz/troubleshoot-issue-727318. The file downloaded in turn was an AppleScript (Apple’s built-in scripting language) namedzoom_sdk_support. scpt. Figure 3: Initial payload sent to the victim -zoom_sdk_support. scptThis 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 binaryNext, it performs another curl request using thecur1-requestuser agent. This has been observed in previous BlueNoroff intrusions like the one covered byKasperksy in 2022. Unfortunately, this payload was also not live at the time of analysis. Figure 5: Printing the “progress” extracting the download payloadThen attempt to get the user’s password and verify it usingsudo. They will continue doing this until a valid password is supplied. Figure 6: Attempting to verify the user's passwordLastly, 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 historyTechnical analysisBy 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 toolingPersistent implant: Telegram 2The core implant responsible for running all the other components is calledTelegram 2and 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 identifierroot_startup_loader_arm64. Telegram 2was used as the persistence mechanism and starting hourly, with the followingplist:Figure 9:com. telegram2. update. agentLaunchDaemonConfigurationUpon 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. FunctionalityThis binary is very small and only has a few pieces of functionality:poEchoCmd: run an echo command (testing)poEvalCommand: run a command using /bin/bashpoInteractive: spawn an interactive shellpoDaemon: initialize persistenceBackdoor: 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 structureThe 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 implantsConfigurationThe 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 fileThere 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. startupcontains 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. versionfile:Figure 13: Contents of the . startup script fileMain executionWhen main runs, it first creates the directory to store the configuration files:Figure 14: Creating the config directoryThen 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 failsAfter checking that configuration is all good, and there aren’t other instances running using the PID file, it will run two new threads: theexecStartupfunction, which runs the script detailed earlier, andlogoutMonitorwhich watches for the user logging out. If that happens it will triggerexecStartupagain. Figure 16: New threads to run script and monitor logoutFinally, it enters an infinite loop that collects the/Volumesfrom the system, as well as the running process list. These are sent to the C2 server periodically using thesendRequestfunction. Capability: Remote code executionThere are several different ways an operator can execute commands on the host using this malware:execScript: run a remote AppleScript payload usingosascript -e. execShell: run a shell command using /bin/zsh -scexecShellDetached: run a shell command using /bin/zsh -sc in the backgroundCapability: Execution during sleepTo avoid any instances where a user might observe code being executed, commands are wrapped by acheckSleepfunction 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 usesystem_profiler SPDisplaysDataTypeand look for theOnlinefield. Figure 17: Example output of queryingsystem_profiler SPDisplaysDataTypeC2 CommunicationsRequests to the C2 are sent tohttp://$DOMAIN/updateand contain the host information collected using the functionhost. PlatformInformationWithContextfrom the librarygithub. 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 calledawas downloaded byremotedusing 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 byremotedto install and runaThere 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. /cloudkitand a password ofgift123$%^. Key derivation functionTo 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 functionThe salt is prepended to the actual payload, in the case of the baseApp it’s the base64 decodedGJM0bP36hbomz9Gw. Figure 20: Salt prepended to the payload base64Payload decryptionAfter 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 theAesEncryptfunction. 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 implementationFigure 23: Decompilation of decryption routineThis occurs for both base64 blobs which are later used in the process injection portion. Process injectionBy 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 adebugging 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. debuggercom. apple. security. get-task-allowAfter decrypting the payload the malware will check the magic bytes of the resulting macho file. If they are0xbebafecait’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 are0xfeedfacfit 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 acputypeofCPU_TYPE_ARM64. Figure 24: Setup to calling process injection codeThen the process of injection begins, by callingInjectAMD64, which is illustrated in the following figure:Figure 25: Decompilation ofInjectAMD64function withposix_spawnattrsTo 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 themach_vmAPIs allowing for arbitrary memory manipulation and task management. Figure 26: Decompilation of getting a Mach port on the sacrificial processFrom there, they get a list of threads associated with the process usingtask_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 copyingAt 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_protectfunction, where thenew_protectionvariable is set to 3 (VM_PROT_READ | VM_PROT_WRITE). Figure 28: Decompilation of the memory protection modifications per segmentAfter 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 payloadPayload cleanupAfter the payloads were deployed, the actor then ran the binary using the--dflag which calls theZeroWritefunction. 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 AppAs 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 calledtrojan1. nimand allows the operator to issue commands and receive responses asynchronously. To communicate with the C2 it uses websocketswss[:]//firstfromsep[. ]online/client. Analysis is still in progress on this binary and the post will be updated when complete. Base appThe base application is a relatively bare-bones binary written in Swift by the authordominic. Figure 30: Build artifacts from base executableThe main method just runs a simple task on a loop (every 3. 37 seconds). Figure 31: Main method from the base appThe task simply prints the stringCurrent: YYYY-MM-DD HH:MM:SSto /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 methodKeylogger: 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 namedpooh. Figure 33: Build artifacts fromkeyboarddbinaryTo start execution, it will first check if the file/Users/Shared/. _cfgexists, which contains the C2 URL. It defaults to using the serverhttps[:]//metamask[. ]awaitingfor[. ]site/updatebut in this case it was the same as the URL found in the recovered. _cfgfile. Figure 34: Decompilation of C2 resolutionIt 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 monitoredIn the case of this intrusion it was called repeatedly by theremotedbinary 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 1The second calls theMonitorClipboared(sic) function in a loop:Figure 36:MonitorClipboaredcallbackThe last is responsible for the screen collection functionality discussed in the next section. Keylogging functionalityThe actual keylogging functionality is implemented using the Core Graphics library with theEventTapCreateAPI. This API takes a callback function that will execute every time a keypress event is registered. Figure 37: Keylogging function loopThe first thing the callback function will do is keep track of what application was being interacted with for each keypress. They do this by queryingfrontmostApplicationand 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 usedAfter 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 representationFigure 40: Conversion outputsScreencapture functionalityTo capture the screens, the malware enters an infinite loop that checks the number of active displays usingCGGetActiveDsiplayList. 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 functionThe functionCaptureAndSendis responsible for actually gathering the data. It takes an image of the display using theCGDisplayCreateImageAPI, 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 ofCaptureAndSendfunctionClipboard functionalityTo 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 codeSend dataTo 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 serverInfostealer: CryptoBot (airmond)Theairmondbinary 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 calledCryptoBotcompiled by a userchris. Figure 45: Compilation artifacts fromairmondbinaryConfigurationMuch 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 binaryf6102a492570dee84bbc9ebd8bd7bfab4e442eae3b416b1a. Several initialization functions are used to create the previously mentioned files:main. initializeCryptoCachemain. initializeUserInfomain. initializeVersionmain. writePidAnd there are another set of functions to load those configuration files while running:main. loadUserInfomain. loadVersionsmain. writeCryptoCachemain. readCryptoCacheCrypto StealerThe 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 thecrypto-botmodule:crypto-bot/wallet. ExtractAddressInfosFromBinancecrypto-bot/wallet. ExtractAddressInfosFromBitgetcrypto-bot/wallet. ExtractAddressInfosFromCoincrypto-bot/wallet. ExtractAddressInfosFromKeplrcrypto-bot/wallet. ExtractAddressInfosFromLeathercrypto-bot/wallet. ExtractAddressInfosFromMetamaskcrypto-bot/wallet. ExtractAddressInfosFromNaboxcrypto-bot/wallet. ExtractAddressInfosFromOKXcrypto-bot/wallet. ExtractAddressInfosFromPhantomcrypto-bot/wallet. ExtractAddressInfosFromPhantom. Println. func1crypto-bot/wallet. ExtractAddressInfosFromRabbycrypto-bot/wallet. ExtractAddressInfosFromRainbowcrypto-bot/wallet. ExtractAddressInfosFromRonincrypto-bot/wallet. ExtractAddressInfosFromSafepalcrypto-bot/wallet. ExtractAddressInfosFromSendercrypto-bot/wallet. ExtractAddressInfosFromStationcrypto-bot/wallet. ExtractAddressInfosFromSubwalletcrypto-bot/wallet. ExtractAddressInfosFromSuicrypto-bot/wallet. ExtractAddressInfosFromToncrypto-bot/wallet. ExtractAddressInfosFromTroncrypto-bot/wallet. ExtractAddressInfosFromTrustcrypto-bot/wallet. ExtractAddressInfosFromUnisatcrypto-bot/wallet. ExtractAddressInfosFromXverseC2 interactionThe binary interacts with a C2 atproductnews[. ]onlineusing 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. postEncryptedDatamain. postToServerIdentifying and mitigating Meeting application social engineeringRemote 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. ConclusionHistorically, 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. IOCsFilesNameSHA256Notesa4cd5df82e1d4f93361e71624730fbd1dd2f8ccaec7fc7cbdfa87497fb5cb438cC++ Dropperremotedad01beb19f5b8c7155ee5415781761d4c7d85a31bb90b618c3f5d9f737f2d320Go Backdoorairmondad21af758af28b7675c55e64bf5a9b3318f286e4963ff72470a311c2e18f42ffGo Infostealerkeyboardd432c720a9ada40785d77cd7e5798de8d43793f6da31c5e7b3b22ee0a451bb249Obj-C keylogger / screenrecorderzoom_sdk_support. scpt1ddef717bf82e61bf79b24570ab68bf899f420a62ebd4715c2ae0c036da5ce05Initial access AppleScript payloadTelegram 214e9bb6df4906691fc7754cf7906c3470a54475c663bd2514446afad41fa1527Persistent Nim implantcloudkit2e30c9e3f0324011eb983eef31d82a1ca2d47bbd13a6d32d9e11cb89392af23dSacrificial binary used for process injectionnetchk469fd8a280e89a6edd0d704d0be4c7e0e0d8d753e314e9ce205d7006b573865fC Injection candidatepayload080a52b99d997e1ac60bd096a626b4d7c9253f0c7b7c4fc8523c9d47a71122afNim ImplantbaseApp2e30c9e3f0324011eb983eef31d82a1ca2d47bbd13a6d32d9e11cb89392af23dSwift Injection CandidateInfrastructureIPNoteshxxps[://]safeupload[. ]onlinehxxps[://]metamask[. ]awaitingfor[. ]site/updateC2 server for keyloggerhxxps[://]support[. ]us05web-zoom[. ]biz/842799/checkInitial url sent to victim via Telegram, resulting in download ofzoom_sdk_support. scptproductnews[. ]onlineC2 for CryptoBotfirstfromsep[. ]onlineC2 for a’s Nim Payloadsafefor[. ]xyzC2 for RTV4readysafe[. ]xyzC2 for RTV4