BLOG
Russian AI Slopsquatting Publishes 700+ Malicious NPM Packages
NUL1DROPPER is a new downloader targeting mobile SDK installs a RAT to Windows, Mac, and Linux with no install script required
By c0a15726-c5b1-4b0d-85e6-fe15553df9e2 ·
Over the course of 48 hours a threat actor has published more than 700 malicious packages to the NPM registry. These packages appear to use AI slop squatted, or randomly generated typo-squatting package names, but all of them deliver a powerful RAT and infostealer payload.
The NPM packages do not use a preinstall or postinstall script. It doesn’t need one. The README tells developers to load the library with require("checkout-mobile-bnpl"), and that single call starts the infection chain.
The downloader supports Windows, Linux, and macOS. It rotates through three Cloudflare Workers hosts for its primary payload delivery and falls back to reconstructing the payload from DNS TXT records hosted under wel1.ru.
That DNS fallback is the interesting part. Even if defenders block the obvious HTTPS hosts, the package has a second route for delivering the same executable.
TL;DR
Threat type: Cross-platform downloader and dropper
Affected ecosystem: npm
Malicious package:
checkout-mobile-bnpl@35.6.9Execution trigger: Importing the package with
require()Primary delivery: HTTPS from three
workers.devhostsFallback delivery: Base64-encoded payload chunks in DNS TXT records
Impact: Downloads and silently executes native malware; the macOS payload establishes persistence and retrieves an additional beacon
Severity: Critical
The OpenSourceMalware research team has analyzed multiple packages from this campaign. There are minor differences in the workers.dev C2, but otherwise the payloads are exactly the same.
A Mobile SDK That Doesn’t Do Much Mobile SDK Work
At first glance, the example package we chose, checkout-mobile-bnpl, presents itself as a small mobile SDK. Its public API exposes a class with init(), version(), and configure() methods. None of those methods perform any meaningful checkout or buy-now-pay-later functionality.
The real behavior is tucked underneath the export at the bottom of index.js:
module.exports = {
CheckoutMobileBnpl,
create: (opts) => new CheckoutMobileBnpl(opts),
VERSION
};
try { require("./_helpers"); } catch (_) {}
That last line loads _helpers.js. The helper file calls its own run() function immediately, so a developer does not need to initialize the alleged SDK or call a suspicious method. Importing the package is enough.
This is a useful reminder that npm malware does not require lifecycle scripts to execute. Install-script controls help, but they do not protect an application that later imports a malicious dependency during development, testing, or production startup.
What Does the Package Do?
Once loaded, the package identifies the operating system and CPU architecture and maps the victim to one of four payload paths:
Victim platform
Payload path
Linux x64
/pkg/package
Linux ARM64
/pkg/package-arm64
macOS x64 or ARM64
/pkg/loader_mac
Windows x64 or x86
/pkg/package.exe
It shuffles three attacker-controlled hosts and attempts an HTTPS GET against each one:
oob-worker.cf103-070.workers.dev
oob-worker.cf102-baf.workers.dev
oob-worker.cf99-9b3.workers.dev
The request is forced over IPv4, times out after 15 seconds, and uses node-fetch/2.6 as its User-Agent. A response is accepted only when the server returns HTTP 200 and more than 1,000 bytes.
There is no signature check, trusted certificate pinning, expected hash, or other payload verification. Whatever those servers return is treated as an executable.
When HTTPS Fails, It Downloads the Payload Through DNS
If all three HTTPS hosts fail, the package switches to a platform-specific domain:
Victim platform
DNS payload domain
Linux x64
sdk.dl.wel1.ru
Linux ARM64
ext.dl.wel1.ru
macOS
pkg.dl.wel1.ru
Windows
net.dl.wel1.ru
The package first requests a TXT record from c.<domain>. It parses the response as the number of payload chunks, accepting a value between 1 and 2,000.
It then requests numbered TXT records such as:
0.sdk.dl.wel1.ru
1.sdk.dl.wel1.ru
2.sdk.dl.wel1.ru
The returned strings are joined together and Base64-decoded into a binary buffer:
const cnt = await _dnsQuery("c." + domain);
const n = parseInt(cnt, 10);
// Queries 0.<domain> through (n-1).<domain>
return Buffer.from(parts.join(""), "base64");
This is not ordinary DNS service discovery. The TXT records are a secondary payload-delivery channel. DNS monitoring that only looks for known tunneling tools may miss this because the traffic consists of ordinary-looking TXT lookups to a small set of predictable names.
How the Payload Is Executed
After obtaining at least 1,001 bytes through HTTPS or DNS, the downloader generates a random eight-character hexadecimal identifier and writes the payload to disk.
On Linux and macOS, it uses:
/var/tmp/.cache_<8 hex characters>
The file is made executable and launched through:
/bin/sh -c "/var/tmp/.cache_<id> &"
On Windows, it uses:
%TEMP%\dotnet_diag_<8 hex characters>.exe
It launches the file in the background with:
cmd.exe /c start /b %TEMP%\dotnet_diag_<id>.exe
Both execution paths are detached and have their standard input and output ignored. The filenames—.cache_ on Unix and dotnet_diag_ on Windows—are designed to look uninteresting among legitimate temporary files.
The package also creates a marker file to avoid running again for 21,760 seconds, or just over six hours:
/tmp/.analytics_state
%TEMP%\analytics_state
The telemetry naming is camouflage. This file does not store analytics; it is a rate-limit marker for the downloader.
Is This a Multi-Stage Loader?
The first-stage JavaScript from the NPM packages behave like a downloader. It makes HTTPS GET requests and DNS TXT queries, but there are no active POST request, credential collection routine, or host-data exfiltration path in _helpers.js. That’s what the second stage is for: The second-stage payload can perform additional discovery, credential theft, persistence, or command-and-control activity that is not visible in the npm package itself.
The second stage comes in 4 platform specific versions, and the OSM team analyzed all four.
There are also no hardcoded IP addresses in the package. The Cloudflare Workers and wel1.ru hostnames resolve dynamically, so defenders should prioritize the domain names, DNS queries, process behavior, and dropped-file patterns over a static IP blocklist.
The 80 KB Telemetry Decoy
The package contains a second file, lib/telemetry.js, weighing in at roughly 80 KB. It implements a large, plausible-looking telemetry SDK, but buried inside it is another version of the same native downloader design: HTTPS retrieval, DNS TXT reconstruction, temporary-file creation, permission changes, and detached execution.
The package entry point does not import this file, and it contains no additional hardcoded infrastructure. Its network destinations have to be supplied by a caller. The active infection path is the much smaller _helpers.js file.
The oversized telemetry implementation appears intended to add noise and make the malicious behavior look like native profiling or analytics functionality during a quick review.
Inside the Native Payloads
The first-stage JavaScript is only the beginning of the chain. The OSM research team analyzed two binaries served by the infrastructure: a Linux x86-64 executable and a universal macOS Mach-O containing both Intel and Apple Silicon versions.
Sample
Format
SHA-256
oob-worker.cf103-070.workers.dev.linux-second-stage.payload
Linux x86-64 ELF, statically linked and UPX-packed
7e486657f30594afda379b97030252a09a19fe8055e25c9e371544f59bd8e9e3
oob-worker.cf99-9b3.workers.dev.second.stage.payload
Universal macOS Mach-O, x86-64 and ARM64
c214746c74cae8ece8bdaf69aa05da4db6ce013f9e77452d1eed1a002fd9ba00
The macOS Payload Downloads Another Payload
The macOS binary is not the final stage. It contains an additional download path named:
/pkg/beacon_mac.bin
It uses libcurl for HTTPS and libresolv for direct DNS queries. Its imports include curl_easy_perform, res_9_query, fork, setsid, execl, chmod, ptrace, and sysctlbyname. Together, these support HTTPS and DNS delivery, file installation, detached execution, and anti-debugging.
The likely macOS attack chain is:
Look for debuggers, instrumentation, packet capture, and VMware.
Query the machine’s memory size as a likely sandbox heuristic.
Test remote or local health endpoints.
Retrieve
/pkg/beacon_mac.binthrough a Cloudflare Workers proxy.Fall back to DNS TXT delivery if HTTPS fails.
Write the payload beneath
~/.local/share/runtime.Create and load a LaunchAgent for persistence.
Start the executable in a detached process.
More Configuration Hidden With XOR
The macOS binary hides its infrastructure strings with a single-byte XOR operation using the key 0x9c. Static decoding recovered five more Cloudflare Workers hosts:
package-proxy[.]cf5oobworker.workers.dev
package-proxy[.]cf6oobworker.workers.dev
package-proxy[.]cf7oobworker.workers.dev
package-proxy[.]cf8oobworker.workers.dev
package-proxy[.]cf11oobworker.workers.dev
Combined with the embedded /pkg/beacon_mac.bin path, these hosts appear to be third-stage payload proxies. The binary builds requests with the template https://%s:%d%s, so the connection can include an explicit port. The exact configured port could not be recovered confidently from strings alone.
The macOS loader also contains another DNS delivery domain:
dl[.]wel1.ru
Its query format strings are:
c.%s.%s
%d.%s.%s
This suggests a count query followed by numbered payload chunks, likely resembling:
TXT c.<session-or-host-id>.dl.wel1.ru
TXT 0.<session-or-host-id>.dl.wel1.ru
TXT 1.<session-or-host-id>.dl.wel1.ru
That is the same basic delivery technique used by the JavaScript first stage, with an extra session or host label in the query name.
Health Checks and Possible Decoy Infrastructure
Three other XOR-hidden domains are embedded in both architecture slices:
nexus[.]tcsbank.ru
repo-linux[.]tcsbank.ru
alertmanager[.]cloudpayments.ru
The binary contains the formatter https://%s/health, so these appear to be health checks, connectivity tests, fallback services, or decoy traffic. Static evidence alone does not prove that every embedded domain is attacker-owned. Defenders should treat them as investigation leads and correlate them with the process and filesystem indicators in this post.
The binary also probes:
http://127.0.0.1:4444/health
This is the only literal IP address found in either native sample. Because it is the local loopback address, it is not an external C2. It may test whether another local malware component is already active.
macOS Persistence
The loader constructs the following directory and file paths beneath the victim’s home directory:
~/.local/share/runtime
~/.local/share/runtime/.lock
~/.local/share/runtime/com.apple.runtime
It also creates:
~/Library/LaunchAgents/com.apple.windowserver.helper.plist
The embedded plist identifies itself as com.apple.windowserver.helper, runs its configured executable at login, and keeps restarting it after unsuccessful exits. Both standard output and standard error are redirected to /dev/null, and restart attempts are throttled to once every 60 seconds.
The loader activates the persistence entry with:
launchctl load -w '<path>/com.apple.windowserver.helper.plist' 2>/dev/null
The names com.apple.runtime and com.apple.windowserver.helper are camouflage. Neither artifact is a legitimate Apple component in these locations.
Anti-Analysis Checks
The macOS payload explicitly looks for several analysis tools and virtualization artifacts:
lldb
debugserver
dtrace
frida
/usr/local/bin/wireshark
/Applications/VMware Fusion.app
/Library/Application Support/VMware Tools
It imports ptrace, sysctl, and sysctlbyname and queries hw.memsize. This combination indicates debugger detection, instrumentation detection, VMware detection, and a likely low-memory sandbox check.
The exact reaction to each detected artifact—such as exiting, sleeping, or suppressing network activity—would require further control-flow reconstruction. The anti-analysis intent itself is clear from the combined strings and imports.
The Linux Payload Is UPX-Packed
The Linux sample is a statically linked, non-PIE x86-64 ELF with its section header table removed. It contains multiple UPX markers and identifies its packer as UPX 3.96.
The original program’s code, strings, and configuration are compressed. There is no dynamic section or useful import table, so ordinary static string and import analysis cannot expose its inner behavior. The visible /proc/self/exe string is consistent with the UPX loader reading its own image and should not be mistaken for an IOC by itself.
This file downloads a second set of binaries from one of a number of Cloudlfare Worker URLs:
https://oob-worker[.]cf99-9b3.workers.dev/pkg/package
https://oob-worker[.]cf99-9b3.workers.dev/pkg/package-arm64
https://oob-worker[.]cf99-9b3.workers.dev/pkg/loader_mac
https://oob-worker[.]cf99-9b3.workers.dev/pkg/package.exeThis last stage served by the Cloudflare URL delivers what another researcher is describing as a Sliver implant. Sliver is an open-source command and control framework, written in Go, and maintained by Bishop Fox. It is intended to be used by red teams and offensive security practitioners. However, Sliver is often used by malicious threat actors as well. We have not confirmed that this last stage is Sliver yet, but will update this section if we do.
Attribution
The use of the wel1[.]ru domain to host the TXT records could indicate that the actor is Russian. Additionally, the malware mentions several Russian financial institutions including cloudpayments[.]ru and tcsbank[.]ru which both appear to be legitimate.
The OSM team believes that this campaign might be an evolution of the Moika tech malware campaign that saw 250+ NPM packages published to the registry in April/May of this year.
You can see the OSM list of Moika packages HERE.
The Moika and wel1dropper campaigns share some tradecraft including:
The use of the "oob" string in file and server names
The focus on Russian financial institutions and mobile payments
A focus on fake telemetry camoflage to validate legitimacy
Moika and wel1dropper both have kill switches that work in similar ways
Indicators of Compromise (IOCs)
Malicious NPM Packages
We are currently tracking 788 packages in this campaign. You can see them all HERE.
We are also maintaining a list of affected packages on GitHub.
HTTPS Payload Hosts
oob-worker[.]cf103-070.workers.dev
oob-worker[.]cf102-baf.workers.dev
oob-worker[.]cf99-9b3.workers.dev
package-proxy[.]cf5oobworker.workers.dev
package-proxy[.]cf6oobworker.workers.dev
package-proxy[.]cf7oobworker.workers.dev
package-proxy[.]cf8oobworker.workers.dev
package-proxy[.]cf11oobworker.workers.dev
DNS Payload Domains
sdk[.]dl.wel1.ru
ext[.]dl.wel1.ru
pkg[.]dl.wel1.ru
net[.]dl.wel1.ru
dl[.]wel1.ru
Defenders should also search for TXT queries matching:
c.<domain>
<integer>.<domain>
c.<session-or-host-id>.dl.wel1.ru
<integer>.<session-or-host-id>.dl.wel1.ru
Additional Embedded Domains
nexus[.]tcsbank.ru
repo-linux[.]tcsbank.ru
alertmanager[.]cloudpayments.ru
These domains are embedded in the macOS sample and appear related to health checks. Static analysis does not establish whether they are attacker-owned, compromised, or used as decoys.
Payload URL Patterns
https://oob-worker[.]cf103-070.workers.dev/pkg/package
https://oob-worker[.]cf103-070.workers.dev/pkg/package-arm64
https://oob-worker[.]cf103-070.workers.dev/pkg/loader_mac
https://oob-worker[.]cf103-070.workers.dev/pkg/package.exe
https://oob-worker[.]cf102-baf.workers.dev/pkg/package
https://oob-worker[.]cf102-baf.workers.dev/pkg/package-arm64
https://oob-worker[.]cf102-baf.workers.dev/pkg/loader_mac
https://oob-worker[.]cf102-baf.workers.dev/pkg/package.exe
https://oob-worker[.]cf99-9b3.workers.dev/pkg/package
https://oob-worker[.]cf99-9b3.workers.dev/pkg/package-arm64
https://oob-worker[.]cf99-9b3.workers.dev/pkg/loader_mac
https://oob-worker[.]cf99-9b3.workers.dev/pkg/package.exe
https://package-proxy[.]cf5oobworker.workers.dev:<port>/pkg/beacon_mac.bin
https://package-proxy[.]cf6oobworker.workers.dev:<port>/pkg/beacon_mac.bin
https://package-proxy[.]cf7oobworker.workers.dev:<port>/pkg/beacon_mac.bin
https://package-proxy[.]cf8oobworker.workers.dev:<port>/pkg/beacon_mac.bin
https://package-proxy[.]cf11oobworker.workers.dev:<port>/pkg/beacon_mac.bin
File and Process Indicators
/tmp/.analytics_state
/var/tmp/.cache_<8 hex characters>
%TEMP%\analytics_state
%TEMP%\dotnet_diag_<8 hex characters>.exe
/bin/sh -c /var/tmp/.cache_<id> &
cmd.exe /c start /b %TEMP%\dotnet_diag_<id>.exe
~/.local/share/runtime/.lock
~/.local/share/runtime/com.apple.runtime
~/Library/LaunchAgents/com.apple.windowserver.helper.plist
launchctl load -w '<path>/com.apple.windowserver.helper.plist'
http://127.0.0.1:4444/health
SHA-256 Hashes
README.md
0fc30f82e1fa5e51a6c0c43f3ed7f13592ea731cb331e43a4d085df60a4db8b6
_helpers.js
94ef6b1c4a9d31f78f446d053048bcef34fd88f4376a1a46f7f777a9e9c83a29
index.js
b74c5675725911c62091bdf40714df760cc2af7a88360d21065f4e1c878aa8f0
package.json
e2650e9aa2f924433ba422857b22ee7c5996b5ad306f3f903283f6a13e248935
lib/telemetry.js
a3e2ffb440b779d30da3ff282affd649731088e8570df7b1aa72742d995b782c
Linux x86-64 native payload
7e486657f30594afda379b97030252a09a19fe8055e25c9e371544f59bd8e9e3
macOS universal native payload
c214746c74cae8ece8bdaf69aa05da4db6ce013f9e77452d1eed1a002fd9ba00
What Should Defenders Do?
If you identify that any of these 800+ NPM packages has been installed in your environment, appears in a lockfile, software bill of materials, package-manager cache, build log, or deployed application, do not assume that removing the dependency is sufficient.
Determine whether the package was ever imported. Review DNS logs for TXT lookups under wel1.ru, proxy logs for all eight Cloudflare Workers hosts, and endpoint telemetry for the dropped-file and process patterns above. On macOS, specifically hunt for the fake runtime executable and LaunchAgent. Because the final beacon remains unavailable for analysis, a confirmed execution should be handled as a potential host compromise.
Rotate credentials accessible to affected developer workstations or CI runners, including npm tokens, GitHub tokens, cloud credentials, signing keys, and deployment secrets. Rebuild affected systems from a known-good state where appropriate.
Conclusion
This campaign is noisy, but it also packs a punch. It hides behind a minimal SDK facade, executes on import instead of using an install script, supports the three major desktop operating systems, rotates its HTTPS infrastructure, and uses DNS TXT records as a fallback payload channel. Its macOS payload adds anti-analysis checks, user-level persistence, five more download proxies, and another beacon-delivery stage.
The most important lesson is simple: blocking npm lifecycle scripts does not eliminate npm malware. Dependency code can wait until the application imports it, then do exactly the same damage.
If you encounter similar packages or suspicious activity, please report them to OpenSourceMalware.com.
Stay safe out there.