XCSSET evolves again: Analyzing the latest updates to XCSSET’s inventory

This post has been republished via RSS; it originally appeared at: Microsoft Security Blog.

Microsoft Threat Intelligence has identified yet another XCSSET variant in the wild that introduces further updates and new modules beyond those detailed in our March 2025 blog post. The XCSSET malware is designed to infect Xcode projects, typically used by software developers, and run while an Xcode project is being built. We assess that this mode of infection and propagation banks on project files being shared among developers building Apple or macOS-related applications.

This new variant of XCSSET brings key changes related to browser targeting, clipboard hijacking, and persistence mechanisms. It employs sophisticated encryption and obfuscation techniques, uses run-only compiled AppleScripts for stealthy execution, and expands its data exfiltration capabilities to include Firefox browser data. It also adds another persistence mechanism through LaunchDaemon entries.

This variant features a submodule designed to monitor the clipboard and references a downloaded configuration file containing address regex patterns associated with various digital wallets. If a pattern match is detected, XCSSET is capable of substituting the clipboard content with its own predefined set of wallet addresses.

In this blog, we will discuss the new modules added to the XCSSET’s inventory and key changes to existing ones. While we’re only seeing this new XCSSET variant in limited attacks as of this writing, we’re publishing our comprehensive analysis to increase awareness of this evolving threat. We shared these findings with Apple and collaborated with GitHub to take down repositories affected by XCSSET. This work reflects our broader commitment to disrupting attacks and dismantling attacker operations. Alongside our findings, we are sharing actionable detections, recommendations, and best practices to help organizations defend against this threat with confidence.

Analysis

The latest XCSSET variant follows a four-stage infection chain. The initial three stages are consistent with those observed in previous variants, as described in our previous blog. This analysis begins with the fourth stage, which includes the boot() function and its associated calls to download and run submodules.

boot() function of the fourth-stage script

The new variant introduces modifications to the boot function. These include additional checks for Firefox browser and modified logic for Telegram existence check. This stage also has multiple new modules that it downloads and executes.

Older variant:

Screenshot of the boot function of the earlier XCSSET variant
Figure 1. boot() function of the earlier variant

New variant:

Screenshot of the boot function of the latest XCSSET version
Figure 2. boot() function of the latest version

In the following sections, we examine changes to existing submodules as well as additional modules in this variant.

vexyeqj [Older variant: seizecj] (Info-stealer)

In comparison to the previous variant, several commands in this script are commented out. Additionally, it downloads a module called bnk, which is executed using osascript, with the domain supplied as a parameter. It then waits for three seconds and deletes the downloaded file.

Screenshot of the main logic code of the Info-stealer module
Figure 3. Main logic of the Info-stealer submodule

The bnk file is a run-only compiled AppleScript. Direct decompilation of run-only compiled AppleScript is generally considered challenging or not feasible; however, the AppleScript disassembler project on Github can be used to disassemble the code for analysis.

The script defines several functions for purposes such as data validation, encryption, decryption, obtaining additional data from command and control (C2), and logging. The script is executed with the domain as its parameter.

Screenshot of the disassembled code of the dec() function
Figure 4. Disassembled code of the dec() function

Above is a code snippet of the dec() function, which is used to decrypt the data received from C2 server. Parsing the above leads to the command:

Screenshot of the parsed data from C2 server

In the referenced code, the encrypted data is stored in the variable in. The first 32 characters of this variable are extracted to serve as the initialization vector (IV). The remaining data is then Base64-decoded and provided to the AES decryption function. In this case, the decryption key is a predefined constant, 27860c1670a8d2f3de7bbc74cd754121, which was established and computed within the main function.

The decoded blob appears to be a configuration file. Presented below is a formatted and redacted sample of the decrypted response obtained from the C2 server:

Screenshot of the configuration data from C2
Figure 5. Configuration data received from the C2 server

The following section examines the core logic of the downloaded bnk payload, explaining how the previous information is interpreted and applied.

Firstly, it calls a defined function to obtain the configuration data from the C2 server; this data is decrypted and stored in a variable. Shell commands are executed to retrieve the SerialNumber and the current user.

The clipboard content is retrieved which was determined by checking the AEVT (Apple Event Code) codes. The process then identifies the frontmost application, which is checked against a blocklist defined by the “bad” property in the response from C2. Processing proceeds only if the current clipboard data differs from both the last clipboard entry and the last replaced clipboard data, the length of the clipboard data exceeds 25 characters, and the oD() function does not return true. The oD() function returns true when the first four characters are digits. After the above checks, it then has multiple gates and conditions. The first condition checks if the clipboard length is between 50 < len(clipboard) < 300. It then checks if the clipboard matches the pattern defined in the s record in the response. If it matches, the clipboard data is formatted in a record type string and is exfiltrated to the C2 server. The transmitted data is also AES-encrypted.

In the second condition, the script verifies whether the clipboard length is between 25 and 65, whether it was executed with a single argument, and whether cD(clipboard_data) function returns a value greater than 1, which refers to the count of digits in the data passed in argument. If these conditions are met, the script iterates through the sub collection in the C2 response, which includes individual entries for various wallets. Each sub collection entry contains:

  • a: Contains a list of addresses from which one is selected; the corresponding clipboard data is subsequently replaced.
  • t: Refers to the wallet identifier.
  • r: Specifies the regex pattern used for matching addresses associated with this wallet.
  • ir (optional): Represents a negative regex pattern; addresses matching this pattern should be excluded.
  • p: Appears to function as a counter or record index.

For each record, it matches pattern for r and ir. If the variable r is true and ir is false, then the program checks whether the clipboard content matches any of the attacker’s addresses. If it does not, it selects an address from the list and replaces the clipboard’s content accordingly. The system subsequently sends information—including the original clipboard data, the replaced data, the wallet name, frontmost application, and other relevant details—to the C2 server. Next, it assigns the value of the clipboard data to the xcP variable, which tracks the most recently replaced clipboard entry. Finally, it updates the xP variable to reflect the current clipboard text, waits for two seconds, and repeats the loop.

neq_cdyd_ilvcmwx (File-stealer)

This module retrieves an additional script from the C2 server, which is saved in the /tmp/ directory. The script is subsequently executed with the domain and moduleName provided as parameters. After execution, the downloaded file is deleted. The module operates as a compiled, run-only AppleScript. The script bears similarity to the txzx_vostfdi module, previously identified as a digital wallet data stealer targeting browsers. During analysis, the C2 server did not supply a folder list; however, it is capable of exfiltrating files back to the C2 server.

Screenshot of additional downloaded script
Figure 6. Additional script being downloaded and executed

xmyyeqjx (LaunchDaemon-based persistence)

This submodule sets up LaunchDaemon persistence for the ~/.root file, which is created in this module. Here’s a summary of the script:

The process begins by creating several paths and a ~/.root file in the user’s HOME directory, which will contain the payload. The payload performs the following actions:

  • Changes the directory to /Users/Shared
  • Checks the network connection
  • Retrieves the local signed-in user
  • Sleeps for 30 seconds
  • Executes the ~/.zshrc file in the context of the signed-in user (.zshrc file was appended with malicious payload in previous submodules)
  • Sleeps for 30 seconds
  • Modifies two configurations to execute system commands that disable macOS automatic configuration updates and Rapid Security Response mechanisms.
Screenshot of commands that modify Software Update preferences on macOS

These commands modify macOS Software Update preferences to disable various critical Apple Updates, including Rapid Security Responses (RSR), Security Configuration updates, and others.

It then calls the doMainFunc() function.

Screenshot of function that creates fake application and download of additional script
Figure 7. Creation of fake application and downloading of additional script from C2 server.

This function first checks the existence of a LaunchDaemon entry with the presence of .root file in its contents. If it’s not found, it downloads another script from the C2 server, which is again a run-only compiled AppleScript. It then creates a fake application named System Settings.app in the /tmp directory, which basically executes this downloaded AppleScript with two parameters. These parameters appear to be the Label/Plist Name and the file to be persisted (~/.root file).  After creating the fake app, it calls another function where it waits for the legitimate System Settings application to get started, upon which it executes the fake application. This behavior is done to masquerade itself as legitimate.

The downloaded script first gets the device’s serial number and the current username by executing shell commands. It then forms path to the LaunchDaemon plist file and constructs its content. It uses the echo command to paste this constructed content to the LaunchDaemon file. The file name is the name that was passed in the argument. Below is an example of the created plist file:

Screenshot of plist content
Figure 8. Plist content of the created LaunchDaemon entry

It masquerades with prefix com.google. in plist name and executes the ~/.root file using bash. The echo command is run using “do shell script …. with administrator privileges” which can be implied by the badm AEVT code. It then executes chown command to change owner to root:wheel and sets 644 permissions to the plist file. Lastly, it executes the launchctl load -w command with sudo to start the daemon.

jey (Git-based persistence)

The command in the older variant executes a direct concatenation of encrypted payload along with the repeated decryption command directly through the shell. In the new variant, the decryption logic is encapsulated within a shell function, which is defined inline and then used to decrypt the encrypted string before passing it to the shell for execution. This change primarily enhances the obfuscation method used by malware. 

Old logic: 

Screenshot of payload generation logic in older variant
Figure 9. Payload generation logic in older variant

New logic: 

Screenshot of payload generation logic in the latest XCSSET variant
Figure 10. Payload generation logic in the latest variant

iewmilh_cdyd (Info-stealer targeting Firefox)

This new variant has added an info-stealer module to exfiltrate data stored by Firefox. The runMe() function is invoked at first to download a Mach-O FAT binary, which is responsible for all info stealing operations, from the C2 server.

Figure 11. Downloading compiled binary of the HackBrowserData project

This downloaded binary appears to be a modified version of a GitHub project HackBrowserData, which is capable of decrypting and exporting browser data stored by browsers. Passwords, history, credit card information, and cookies are some of the key information it can extract from almost all popular browsers.

Upon downloading, the binary is given executable file permissions, is ad-hoc signed on the victim’s machine, and executed with b firefox -f json –dir ” & resDir & ” –zip as arguments:

  • -b: Browser name
  • -f: format of the output data
  • –dir: Export directory where the output is stored
  • –zip: This flag stores the output in compressed ZIP

Once all the data is retrieved, it uploads the compressed ZIP and log file to C2 server with its old method of exfiltrating data in chunks.

Mitigation and protection guidance

Defenders can take the following mitigation steps to defend against this threat:

  • Run the latest version of your operating systems and applications. Deploy the latest security updates as soon as they become available.
  • Always inspect and verify Xcode projects downloaded or cloned from repositories, as the malware usually spreads through infected projects.
  • Exercise caution when copying and pasting sensitive data from the clipboard. Always verify that the pasted content matches the intended source to avoid falling victim to clipboard hijacking or data tampering attacks.
  • Encourage users to use web browsers that support Microsoft Defender SmartScreen like Microsoft Edge—available on macOS and various platforms—which identifies and blocks malicious websites, including phishing sites, scam sites, and sites that contain exploits and host malware.
  • Use Microsoft Defender for Endpoint on Mac, which detects, stops, and quarantines the malware discussed in this blog

Microsoft Defender for Endpoint customers can also apply the following mitigations to reduce the environmental attack surface and mitigate the impact of this threat and its payloads:

  • Turn on cloud-delivered protection and automatic sample submission on Microsoft Defender Antivirus. These capabilities use artificial intelligence and machine learning to quickly identify and stop new and unknown threats.
  • Enable potentially unwanted application (PUA) protection in block mode to automatically quarantine PUAs like adware. PUA blocking takes effect on endpoint clients after the next signature update or computer restart. PUA blocking takes effect on endpoint clients after the next signature update or computer restart.
  • Turn on network protection to block connections to malicious domains and IP addresses.

Microsoft Defender XDR detections

Microsoft Defender XDR customers can refer to the list of applicable detections below. Microsoft Defender XDR coordinates detection, prevention, investigation, and response across endpoints, identities, email, apps to provide integrated protection against attacks like the threat discussed in this blog.

TacticObserved activityMicrosoft Defender coverage
Initial access– Malicious Xcode projectsMicrosoft Defender Antivirus
– Trojan:MacOS/XCSSET.PB

Microsoft Defender for Endpoint
– Possible XCSSET activity
Execution– Malicious command execution
– Malicious file execution
– Malicious osascript execution
Microsoft Defender Antivirus
– Behaviour:MacOS/SuspOsascriptExec.B
– Behaviour:MacOS/SuspOsascriptExec.C
– Trojan:MacOS/XCSSET.AB
– Trojan:MacOS/XCSSET.BA
– Trojan:MacOS/XCSSET.SE
– Behavior:MacOS/SuspXcssetBehavior.AT
– Trojan:MacOS/XCSSET.ST
– Trojan:MacOS/XCSSET.SB
– Trojan:MacOS/XCSSET.SC  

Microsoft Defender for Endpoint
– Suspicious file dropped and launched
– Suspicious script launched
– Network connection by osascript
– Suspicious process launched from a world-writable directory
Persistence– Hidden LaunchDaemon persistenceMicrosoft Defender Antivirus
– Behavior:MacOS/SuspHiddenPersistence.A1  

Microsoft Defender for Endpoint
– Suspicious Plist modifications – Suspicious launchctl tool activity
Defense evasion– Suspicious obfuscated commandMicrosoft Defender for Endpoint
Suspicious file or information obfuscation detected
Credential access– Use of modified HackBrowserData projectMicrosoft Defender Antivirus
– Trojan:MacOS/HackBrowserData.A
Impact– Xcode project infectionMicrosoft Defender Antivirus
– Behavior:MacOS/XCSSET.A

Note: For detections associated with older variants of XCSSET, refer to our March 2025 blog post.

Threat intelligence reports

Microsoft customers can use the following reports in Microsoft products to get the most up-to-date information about the threat actor, malicious activity, and techniques discussed in this blog. These reports provide the intelligence, protection information, and recommended actions to prevent, mitigate, or respond to associated threats found in customer environments.

Microsoft Defender XDR threat analytics

Hunting queries

Microsoft Defender XDR

Microsoft Defender XDR customers can run the following query to find related activity in their networks:

Suspicious commands while building an Xcode project

Search for suspicious commands related to this XCSSET when an Xcode project is being built.

DeviceProcessEvents 
| where ProcessCommandLine has_all("echo", "xxd -p -r", "| sh") or ProcessCommandLine has_all("echo", "base64 -d", "| sh")
| where InitiatingProcessFileName has_any ("sh", "bash", "zsh") 
| where InitiatingProcessCommandLine contains "/Developer/Xcode/DerivedData"

Suspicious commands executed by XCSSET info-stealer module

Search for suspicious commands related to decryption logic of data received from C2.

DeviceProcessEvents
| where ProcessCommandLine has_any ("base64 --decode", "base64 -d") and ProcessCommandLine has_all ("openssl enc -d", "cut -c1-32")

Suspicious application creation

Search for suspicious applications created in Temp folder by this XCSSET.

DeviceFileEvents
| where FolderPath matches regex @"/tmp/[a-zA-Z]\.app"

Microsoft Sentinel

Microsoft Sentinel customers can use the TI Mapping analytics (a series of analytics all prefixed with ‘TI map’) to automatically match the malicious domain indicators mentioned in this blog post with data in their workspace. If the TI Map analytics are not currently deployed, customers can install the Threat Intelligence solution from the Microsoft Sentinel Content Hub to have the analytics rule deployed in their Sentinel workspace.

Below are the queries using Sentinel Advanced Security Information Model (ASIM) functions to hunt threats across both Microsoft first-party and third-party data sources. ASIM also supports deploying parsers to specific workspaces from GitHub, using an ARM template or manually.

Detect network IP and domain indicators of compromise using ASIM

The following query checks IP addresses and domain IOCs across data sources supported by ASIM network session parser:

//IP list and domain list- _Im_NetworkSession
let lookback = 30d;
let ioc_ip_addr = dynamic([]);
let ioc_domains = dynamic(["cdntor.ru", "checkcdn.ru", "cdcache.ru", "applecdn.ru", "flowcdn.ru", "elasticdns.ru", "rublenet.ru", "figmastars.ru", "bulksec.ru", "dobetrix.ru", "figmacat.ru", "digichat.ru", "diggimax.ru", "cdnroute.ru", "sigmanow.ru", "fixmates.ru", "mdscache.ru", "trinitysol.ru", "verifysign.ru", "digitalcdn.ru", "windsecure.ru", "dobecdn.ru"]);
_Im_NetworkSession(starttime=todatetime(ago(lookback)), endtime=now())
| where DstIpAddr in (ioc_ip_addr) or DstDomain has_any (ioc_domains)
| summarize imNWS_mintime=min(TimeGenerated), imNWS_maxtime=max(TimeGenerated),
  EventCount=count() by SrcIpAddr, DstIpAddr, DstDomain, Dvc, EventProduct, EventVendor

Detect domain and URL indicators of compromise using ASIM

The following query checks domain and URL IOCs across data sources supported by ASIM web session parser.

// file hash list - imFileEvent
// Domain list - _Im_WebSession
let ioc_domains = dynamic(["cdntor.ru", "checkcdn.ru", "cdcache.ru", "applecdn.ru", "flowcdn.ru", "elasticdns.ru", "rublenet.ru", "figmastars.ru", "bulksec.ru", "dobetrix.ru", "figmacat.ru", "digichat.ru", "diggimax.ru", "cdnroute.ru", "sigmanow.ru", "fixmates.ru", "mdscache.ru", "trinitysol.ru", "verifysign.ru", "digitalcdn.ru", "windsecure.ru", "dobecdn.ru"]);
_Im_WebSession (url_has_any = ioc_domains)

Indicators of compromise

IndicatorTypeDescription
cdntor[.]ruDomainC2 server
checkcdn[.]ruDomainC2 server
cdcache[.]ruDomainC2 server
applecdn[.]ruDomainC2 server
flowcdn[.]ruDomainC2 server
elasticdns[.]ruDomainC2 server
rublenet[.]ruDomainC2 server
figmastars[.]ruDomainC2 server
bulksec[.]ruDomainC2 server
dobetrix[.]ruDomainC2 server
figmacat[.]ruDomainC2 server
digichat[.]ruDomainC2 server
diggimax[.]ruDomainC2 server
cdnroute[.]ruDomainC2 server
sigmanow[.]ruDomainC2 server
fixmates[.]ruDomainC2 server
mdscache[.]ruDomainC2 server
trinitysol[.]ruDomainC2 server
verifysign[.]ruDomainC2 server
digitalcdn[.]ruDomainC2 server
windsecure[.]ruDomainC2 server
dobecdn[.]ruDomainC2 server
12ea52c4089d100e679a2350f03e598b2f3feebfbbd2ed5631a2a7a20b07e826SHA-256/tmp/ancr (Modified version of HackBrowserData Github project)
5a212c5ce1e0f41e721ce0940afb381b694a2e32a6d19c1d2210f703636362dfSHA-256/tmp/b (fourth-stage payload)
0fbd0e1995472f308cf1ac8229a02c277035404426769fa50947a72c95ad7d31SHA-256jey (establishes persistence through Git commits)
f3bc158619b2aad17def966f0ac8dddc2107e4911a7c488d358d906f27ac2a2bSHA-256/tmp/xmyyeqjx (LaunchDaemon based persistence)

References:

Learn more

For the latest security research from the Microsoft Threat Intelligence community, check out the Microsoft Threat Intelligence Blog.

To get notified about new publications and to join discussions on social media, follow us on LinkedIn, X (formerly Twitter), and Bluesky.

To hear stories and insights from the Microsoft Threat Intelligence community about the ever-evolving threat landscape, listen to the Microsoft Threat Intelligence podcast.

The post XCSSET evolves again: Analyzing the latest updates to XCSSET’s inventory appeared first on Microsoft Security Blog.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.