Contents

Arrayref Rust Crate Hijacked to Run Malware While Your Project Compiled

 

Ethical Hacking Complete Course Zero to Expert

Hack like black hat hackers. Penetration testing, Kali Linux, WiFi and web hacking, and the hacker mindset behind it.

→ Take the full course
 
Contents

Your Rust build pulled in a backdoor for 86 minutes on Thursday. Five versions with 246 million downloads between them were pulled in 16 seconds, leaving one poisoned release to land on.

At 07:15 UTC on 20 August a new version of a Rust package called arrayref showed up on crates.io. That package had been sitting there since August 2015. One file, 327 lines of code, nine kilobytes on disk, and it does one small job: it lets you grab a fixed number of bytes out of a longer run of them. In almost eleven years it had never needed anything else to do that.

Version 0.3.10 was different. It had one extra line in the manifest:

1
2
[dependencies]
proc-macro1 = "1.0.107"

That name is one character away from proc-macro2, a package with nearly 1.5 billion downloads. If you build anything in Rust, you have it somewhere. The code inside proc-macro1 was a genuine copy of proc-macro2, so your project compiled and everything worked. The malware was not in the library. It was in build.rs, the build script.

The name was already in place. Five hours earlier the same account had published proc-macro1 version 1.0.106, a clean copy of proc-macro2 with nothing wrong in it. They took the name first. The malware came later.

The account was fake too. The name is one letter off from the developer who maintains the genuine proc-macro2. The package lists him as the author. The email address is not his. The repository link points to a page that does not exist.

Twenty-four seconds after 0.3.10 went live, the older versions started disappearing. Not deleted. Yanked, which is the registry’s way of saying a release has been withdrawn.

  • โ†’ 0.3.9 yanked at 07:15:24
  • โ†’ 0.3.8 at 07:15:26
  • โ†’ 0.3.7 at 07:15:30
  • โ†’ 0.3.6 at 07:15:36
  • โ†’ 0.3.5 at 07:15:40

Sixteen seconds, five versions, 246 million downloads between them, all from the owner’s own account. Yanking does not break what you already have. Your lockfile keeps working. What it does is make Cargo warn you: the version you are on has been withdrawn, move to one that has not. That Thursday morning the newest version left was 0.3.10.

The attacker did not have to trick a single developer into installing anything. The warning did that. The developer who reported it said exactly that: they saw the yank warning, did what it said, and ended up on the poisoned release.

The developers who got hit never chose arrayref. It sits four steps down. Your program pulls in a library to make a window. That library pulls in something to draw the frame. That pulls in a graphics crate. And that one asks for arrayref. Each link asks for any 0.3 version, and 0.3.10 is a 0.3 version. If you write Rust with a window on the screen, you were in range that morning, whether you knew this package or not.

Build scripts are a normal part of Rust. They exist so a package can look at your compiler, write a bit of code, or hook into a C library before your own code gets built. Cargo runs them for you, on your machine, with your rights. That is why this worked. You never have to call a single function from the package. Just compiling it does the job. So does cargo check. So does cargo test.

The build script starts by putting a web address together out of five pieces of base64:

1
2
const SRC_URL_PARTS: &[&str] =
    &["aHR0cHM6Ly8=", "MjMuMjU0Lg==", "MTY1Lg==", "MTEyOg==", "OTA4OS8="];

Decode those five pieces and you get https:// followed by an IP address and a port. Split up like this, the address is not in the file as readable text. Open the source and you see five strings of nonsense. Run a scanner over it looking for suspicious links and it finds nothing.

Then it sets up its own connection, with its own certificate check written by hand. That check has three questions to answer and it answers yes to all three, no matter what you give it. So the download accepts any certificate. It had to. The file came over HTTPS from a bare IP address, and a normal client would have refused it.

Then it picks one of four payloads, based on your operating system and your processor: Linux and Windows on x86-64, macOS on both Intel and Apple silicon.

On Linux and macOS it writes the file to /tmp/rust-setup, makes it executable and starts it detached, with the output thrown away. On Windows it drops a PowerShell script in the temp folder and starts it hidden, through a small VBScript launcher under wscript.exe. Then it lets go of that process on purpose. The comment in the source says why: to get out of Cargo’s job object, so the build does not wait for it.

Your compile finishes. Nothing looks wrong. The program you just built is fine.

What lands on Windows is a PowerShell backdoor of about 35 kilobytes. That is the one that has been analysed in detail, so everything below is Windows. It phones home over HTTPS to the path /49890878 and sends JSON: your username, your machine name, your Windows version and processor, whether you are administrator, what you have installed, and the browser extensions it can see.

It also opens the saved-password files of Chrome, Brave and Edge. The first reports said it steals your passwords. That was wrong, and the researchers corrected it a day later. What it reads is which sites you have a login saved for and which username belongs to each one. The encrypted passwords stay untouched. That is what this first program does. One of its four commands fetches and runs whatever the attacker sends next, so this is where the analysis stops, not where the access stops.

Those password files are small databases, and it needs a tool to read them. It does not have one, so it downloads one. The official SQLite tools, straight from sqlite.org, unpacked in a temp folder, with your browser login file copied in next to them. A normal download from a normal website, in the middle of an attack. That also makes it the easiest thing to spot. A machine that pulls those tools right after a build finished is worth a look.

It survives a reboot in the place each system offers:

  • โ†’ Windows: a value under HKCU\Software\Microsoft\Windows\CurrentVersion\Run that starts PowerShell with the window hidden
  • โ†’ macOS: a LaunchAgent
  • โ†’ Linux: a systemd user service

Its settings sit in a file next to the payload, locked with AES-128-GCM. The key is written straight into the malware: the words i am botking, padded out with zeroes to sixteen bytes. There is a full RSA private key in there as well, in each copy that goes out, which means it proves nothing. And if the main server goes quiet, it generates ten .com domains at a time, a fresh batch on a five-day cycle, and tries those instead.

The Rust security team was clear about the owner. They do not think he did this. Either his machine or his login had been taken over, and they were trying to reach him. How that happened, they have not said.

The warning about proc-macro1 reached the security team at 07:15 UTC, the same minute the poisoned arrayref went up. Two more packages from that account followed at 07:34 and 07:37. The team pulled 0.3.10 at 08:41:40, after 86 minutes, and the other two after 90 and 107 minutes. At 09:15:51 someone from the registry started putting the yanked versions back, and 66 seconds later the last one was live again.

Other maintainers moved faster than that. At 09:09:56, twenty-eight minutes after the poisoned version came down, one of the packages that depends on arrayref put out a release without it. Two more followed within twenty minutes.

The poisoned 0.3.10 was downloaded 2,285 times. That same Thursday, version 0.3.9 went out 714,361 times. Nearly all of that traffic went to versions people already had pinned in a lockfile, so it never went looking for something newer. The advisory says there is no sign the bad version was ever built.

But at least one person did get hit. In the public thread a victim reported traffic leaving their machine for the attacker’s server, and that report is one of the trails researchers followed back to older campaigns.

That server was not new either. The same path, /49890878, and addresses from the same small range showed up in two earlier attacks on package registries, and other researchers tied those two to groups in North Korea. For this one, no vendor has put a name to it. Attribution is the hardest part of this work. Servers get rented and rented out again. Tools get shared, sold and stolen. The same address twice tells you two jobs are connected, not who was behind the keyboard. What we know for sure is how it worked.

One more thing, and it stings a little. Rust has a setting that holds back any dependency younger than an age you pick, and on the nightly compiler it works today:

1
2
[registry]
global-min-publish-age = "14 days"

That setting was proposed in February, and the proposal describes this attack six months before it happened. It explains why Cargo should refuse a version that is too new instead of quietly taking the next best one: someone with the right permissions could publish a bad version, yank the safe ones, and leave the resolver nowhere to go. Cargo never got that quiet fallback, so this trap needed a human to walk into it. The change that brings the setting to everyday Cargo went up on 7 August and was marked to be merged. Sixteen days later that change has not been merged. Eighty-six minutes was all the bad version needed.

What to do if you build Rust on your machine:

  • โ†’ Search your cache for the deleted files:
1
2
3
4
5
find ~/.cargo/registry/cache -type f \
  \( -name 'arrayref-0.3.10.crate' \
  -o -name 'internment-0.8.7.crate' \
  -o -name 'append-only-vec-0.1.9.crate' \
  -o -name 'proc-macro1-*.crate' \) -print
  • โ†’ Grep your Cargo.lock files for proc-macro1, proc-macro-en, aovine, arone, aronenao and tinymember
  • โ†’ If any of that turns up, treat the machine as compromised: rotate the credentials and tokens reachable from it, check the Run key, LaunchAgents and systemd user services, and rebuild anything that machine produced afterwards
  • โ†’ Check outbound connections from your build machines and CI runners for the period around 20 August
  • โ†’ And the habit worth keeping: when several stable versions of an old package get yanked at once, that is a reason to go and look, not a reason to upgrade

This one ran while your project compiled, read your browser login file, and hid in a startup key. Spotting that is what you learn. My Ethical Hacking Complete Course Zero to Expert takes you there step by step: reconnaissance, scanning, exploitation and traffic analysis, hands-on, from your first day with no Linux or hacking background.

โ†’ Join my complete ethical hacking course

Hacking is not a hobby but a way of life.

Sources:

Rust Security Response Team | RUSTSEC-2026-0260 | RustSec advisory-db #3161 | RFC 3923

 
NEWSLETTER

Stay updated

Get the latest posts in your inbox every week. Ethical hacking, security news, tutorials, and everything that catches my attention. If that sounds useful, drop your email below.

By Bulls Eye

Jolanda de koff โ€ข email โ€ข donate

My name is Jolanda de Koff and on the internet, I'm also known as Bulls Eye. Ethical Hacker, Penetration tester, Researcher, Programmer, Self Learner, and forever n00b. Not necessarily in that order. Like to make my own hacking tools and I sometimes share them with you. "You can create art & beauty with a computer and Hacking is not a hobby but a way of life ...

I โ™ฅ open-source and Linux