PhantomBot From Credential Theft to Botnet

PhantomBot: A Typosquat Campaign That Pivoted From Credential Theft to a Turnkey Botnet Kit

TL;DR

A single npm publisher, deadcode09284814, has run a typosquat campaign against the legitimate axios and chalk-template packages since mid-May 2026.

The campaign began as a conventional credential and wallet stealer, exfiltrating data to a Serveo HTTPS tunnel.

On 2026-05-17, with axois-utils:1.0.9, the operator swapped the payload entirely: same triple install-hook scaffold, same publisher, same package name.

But the install script is now a cross-platform DDoS botnet recruit.

The payload speaks to a localhost.run tunnel and accepts flood commands, turning infected environments into part of a DDoS-capable botnet.

The operator even shipped the C2 server binary inside the tarball, showing a clear escalation from credential theft to active botnet infrastructure.

Two packages, four versions still active on the registry, and one operator now running both modules in parallel.

Severity: high.

Headline IOC Any axois-utils or chalk-tempalte version from publisher deadcode09284814

The Attack: How It Works

The campaign is built on a deliberately boring delivery scaffold: two typosquat package names, one letter off from packages with hundreds of millions of weekly downloads (axios, chalk-template), and triple install hooks that guarantee execution. What is interesting is what the scaffold carries — and the fact that the operator changed the cargo without changing anything else.

The shared scaffold

Every published version of both packages declares the same three lifecycle hooks in package.json:

"scripts": {

  "preinstall":  "node <payload>.js",

  "install":     "node <payload>.js",

  "postinstall": "node <payload>.js"

}

Listing the payload under all three hooks is overkill — postinstall alone would run in a default npm install. The choice matters: npm install –ignore-scripts skips scripts, but several common workflows (CI cache restores that re-run lifecycle hooks selectively, or developers who only audit postinstall) make a triple-redundant declaration the safest bet for the attacker.

chalk-tempalte adds a second mechanism: it declares axois-utils:^1.0.7 as a runtime dependency. Installing the typosquatted chalk-template therefore pulls in the sibling typosquat as well, and both payloads run. The two packages are a coordinated pair, not independent listings.

Phase A — credential / wallet stealer (2026-05-15 → present)

Phase A is the original payload. The loader is a short postinstall.js that points at a Serveo HTTPS reverse-tunnel:

// chalk-tempalte/postinstall.js:11-13

const C2_HOST = "f04a273bd84c0622-80-200-28-28.serveousercontent.com";

const C2_PORT = 443;

const C2_PATH = '/collect';

The Serveo subdomain encodes the destination IP (80.200.28.28) directly in the hostname — a recognizable convention for that service. The operator rotates the random subdomain prefix between versions to evade naïve domain blocklists, but the underlying IP never moves. (chalk-tempalte:1.0.14 used 8a3e818ea8f11186-80-200-28-28…; 1.0.16 / 1.0.19 / 1.0.20 use f04a273bd84c0622-….)

postinstall.js hands off to phantom.js, a 7,667-line bundled “collector” module. phantom.js walks the host for:

SSH private keys (~/.ssh/*)
.npmrc contents and any npm_* env tokens
AWS, GCP, and Azure credentials files
GitHub personal-access-token regex (ghp_*, gho_*, ghu_*, ghs_*)
Crypto-wallet artifacts for Bitcoin, Exodus, Electrum, Monero, Litecoin, Dogecoin, Zcash, Dash
A recursive .env* walk through ~/projects, ~/dev, ~/code, ~/workspace, and the install cwd
Shell histories and the full process.env

The bundle is POSTed to the Serveo tunnel at /collect. There is no obfuscation, no anti-VM logic, no staging — the operator’s bet is on volume and speed.

Phase B — PhantomBot DDoS recruit (2026-05-17, axois-utils:1.0.9 only)

Phase B replaces phantom.js + postinstall.js with a single file named distrube.js (the typo is the operator’s). The triple-hook scaffold in package.json is unchanged; the package keeps the same name, scope, and version-bump pattern. From the outside, axois-utils:1.0.9 looks like a minor follow-on to 1.0.6. Inside, it is a different malware family.

distrube.js opens with a configuration block that names the operator’s own branding:

// axois-utils-1.0.9/distrube.js:11-15
// Use your localhost.run HTTPS URL (the tunnel handles HTTP internally)
const C2_HOST = 'b94b6bcfa27554.lhr.life'; // Your localhost.run tunnel
const C2_PORT = 443; // HTTPS port - tunnel handles SSL
const BOT_ID = `${os.hostname()}_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;

The comments are addressed to a future operator running the kit (“Use your localhost.run HTTPS URL”). That, plus what ships next to the bot client, is the tell that this is not just a victim payload — it is the kit.

The flow:

  1. Persistence runs first. The script installs itself in three different ways per OS (registry Run + Startup folder + schtasks on Windows; crontab + phantom-bot.service systemd unit + .bashrc/.zshrc append + /etc/rc.local on Linux; LaunchAgent com.phantom.bot.plist on macOS). The persistence service name and the LaunchAgent label are both phantom-bot / com.phantom.bot, which is also where the campaign name comes from.
  2. System info exfil runs once — a one-shot fingerprint POST to b94b6bcfa27554.lhr.life:443/collect carrying hostname, platform, arch, username, CPU/RAM, network interfaces, process.env, cwd, homedir, node version, and public IP. This is much less aggressive than Phase A: no SSH, no wallets, no .env recursion. The bot’s job is to enroll, not to steal.
  3. Heartbeat loop opens an HTTPS POST every 30 seconds to b94b6bcfa27554.lhr.life:443/. The User-Agent is PhantomBot/1.0. TLS verification is explicitly disabled (rejectUnauthorized: false). The C2 response is parsed as JSON of the form {type, target, port, duration, threads, method} and dispatched.
  4. Flood arsenal is wired to six commands:
Command type Module Notes
ping Liveness reply Bot identifies itself
http HTTP flood Layer-7 request flood
https HTTPS flood Same as http, TLS-wrapped
tcp TCP flood 65 KB random-payload spam
udp UDP flood 65,507-byte UDP packets
rapid HTTP/2 rapid-reset DoS The 2023 CVE-2023-44487 technique

All five flood modules take a target, port, duration, and threads argument — the bot is a generic for-hire flooder, not aimed at any particular victim. The operator’s victim list lives on the C2, not in the package.

What’s New Here — the shipped C2 binary

Phase A is a familiar shape: credential theft, install hook, vendor-tunneled C2. Phase B is also a familiar shape — DDoS botnets have been a fixture of malicious npm packages for years. What is unusual is that the operator shipped the server side of the kit inside the npm tarball:

  • c2 — a 4-megabyte compiled Go ELF binary
  • c2.go — the 426-line Go source for that binary

Neither file is invoked by any install hook. They are not part of the victim payload. They are sitting in the package directory the same way a documentation file would. The most plausible reading is that the operator pushed their development working tree to npm without curating the file list — a real OpSec slip — and what shipped is the complete two-sided kit: the client a victim runs, and the server the operator runs.

This is the part of the story that justifies the “turnkey botnet kit” framing. Anyone who downloaded axois-utils:1.0.9 before takedown has not only a victim sample — they have a working C2 server.

The pivot, in one sentence

Same publisher, same package names, same triple-hook scaffold, same “phantom” branding — but the payload changed monetization model overnight: from “harvest secrets I can resell” to “rent flood capacity I can lease”. The install-time TTPs that defenders should be watching are nearly identical across both phases; signature-based defenses keyed to Phase A’s wallet-path strings or to phantom.js‘s 7,667-line collector would have missed Phase B entirely.

Date (UTC) Event
2026-05-15 First publication: axois-utils:1.0.4 (LAN test build, dev rig at 192.168.129.19)
2026-05-15 axois-utils:1.0.6 published — Phase A C2 swapped to 80.200.28.28 via Serveo tunnel; the source comment // Change to '80.200.28.28' for public confirms the dev→prod swap
2026-05-16 chalk-tempalte:1.0.14 and 1.0.16 published — chained loader declaring axois-utils:^1.0.7 as a runtime dependency; Serveo subdomain rotated
2026-05-16 Phase A campaign discovered during routine npm scanning; confirmed-malicious verdicts applied
2026-05-17 axois-utils:1.0.9 published — payload pivot: PhantomBot DDoS recruit via localhost.run tunnel; operator-side c2 binary and c2.go source shipped in the tarball
2026-05-17 chalk-tempalte:1.0.19 and 1.0.20 published — still Phase A (stealer); operator now running both modules in parallel
2026-05-17 Discovery of Phase B during routine scanning; verdicts applied across all 2026-05-17 versions
(ongoing) Takedown reports pending; all four published packages remain available on the registry at time of writing

Indicators of Compromise

Packages

Ecosystem Package Versions
npm axois-utils (typosquat of axios) 1.0.4, 1.0.6, 1.0.9
npm chalk-tempalte (typosquat of chalk-template) 1.0.14, 1.0.16, 1.0.19, 1.0.20

Author identity

Field Value
npm publisher deadcode09284814
Publisher email phantomdeadcode@tutamail.com
SCM verification none

Command-and-control

Phase Endpoint Transport
A f04a273bd84c0622-80-200-28-28.serveousercontent.com:443/collect Serveo HTTPS tunnel
A 8a3e818ea8f11186-80-200-28-28.serveousercontent.com:443/collect Serveo HTTPS tunnel (earlier version)
A 80.200.28.28:443/collect Underlying VPS endpoint
B b94b6bcfa27554.lhr.life:443/ localhost.run HTTPS reverse-tunnel

File digests (SHA-256)

File SHA-256 Notes
c2 (Go ELF, 4 MB) 165fa92d237fd017c227d00da06ab788212a62be94bf61e95df2d22d00377ef2 Operator-side C2 server, shipped inside axois-utils:1.0.9
c2.go (426 lines) 7d0ae79fdb1e9968f3323a3712b624643a782ba3efb2cf3a2cb9c4c5513cea30 Go source for the C2 binary
distrube.js 308b15c023088a7188dea4ef609010ac2493eb4c365b103053d7621a9ca5b935 Phase B bot client
phantom.js (chalk-tempalte:1.0.19) 9e380ec88d3ccf3929e1a104e3b868d4d7b59ca189a8a431a54e9f3357dfdd81 Phase A credential / wallet collector
phantom.js (chalk-tempalte:1.0.20) d1c9e3f296ee9f7d5032f73f9c504cede50334bc14c394055fd5cb9c3a6e08b3 Phase A collector (1.0.20 variant)
postinstall.js (chalk-tempalte:1.0.19 = 1.0.20) ffba9bdd6793edd5b38e12900252c1813a693f59c25af51c3b658cf3f27b6162 Phase A loader, byte-identical across both versions

Attribution & Motivation

We track this campaign as PhantomBot, taking the operator’s own branding from the User-Agent: PhantomBot/1.0 heartbeat header, the phantom-bot.service systemd unit, the com.phantom.bot LaunchAgent label, and the phantomdeadcode@ email handle. The operator is consistent about this name across at least four artifacts.

Signals catalog

  • Publisherdeadcode09284814 on npm. Single-handle account, Tutamail disposable email, no SCM verification. No prior publication history beyond this campaign.
  • Branding reuse — “phantom” appears in the publisher email, in the Phase A collector filename (phantom.js), in the Phase B persistence service name (phantom-bot.service), in the LaunchAgent label (com.phantom.bot), and in the bot User-Agent (PhantomBot/1.0).
  • Infrastructure — A single VPS at 80.200.28.28 fronts Phase A via Serveo subdomain rotation; Phase B moves to a localhost.run reverse-tunnel (b94b6bcfa27554.lhr.life). Both vendor services are free and require only outbound SSH on the operator’s side, consistent with low-budget, low-OpSec setups.
  • Code style — Plain JavaScript throughout; no obfuscation, no string encoding, no anti-VM checks. Variable names are descriptive (stealSystemInfo, executeCommand, httpFlood). Comments in distrube.js directly address a future operator (“Use your localhost.run HTTPS URL”), suggesting the file was intended to be redistributed as a kit, not used by a single attacker.
  • OpSec slip — The Phase B tarball ships both the bot client and the operator-side C2 server (c2 ELF + c2.go source). This is consistent with an operator who pushed their working tree to npm without auditing the file list. Either the operator is inexperienced, or the kit is meant to be distributed publicly and the C2 binary is part of the product.
  • Self-confirmationaxois-utils:1.0.4 contains the source comment // Change to ‘80.200.28.28’ for public on line 12, confirming the dev / staging / production progression that operators typically deny.

Motivation

The Phase A payload is straightforward financial theft: credentials, cloud keys, GitHub tokens, and crypto wallets all have liquid resale markets. Phase B is also financial, but indirect: DDoS-for-hire (“booter”) services rent flood capacity by the minute, and bots like the one shipped here are the inventory those services run on. The 30-second heartbeat, the generic target/port/duration command schema, and the five-protocol flood arsenal are the standard product of a booter operator.

Running both modules in parallel — chalk-tempalte:1.0.19 and 1.0.20 continue to ship the stealer while axois-utils:1.0.9 ships the bot — suggests the operator is hedging revenue streams rather than abandoning Phase A. The pivot is an addition, not a replacement.

What we are not claiming

We have not been able to confirm a real-world identity behind the deadcode09284814 handle, and we do not attribute this campaign to any named threat actor, group, or country. The “phantom” branding is consistent enough across artifacts to suggest a single operator, but the kit-style shipping of the C2 binary leaves open the possibility that future packages from unrelated handles will reuse the same code.

Impact

The legitimate squat targets axios and chalk-template are widely depended on in the JavaScript ecosystem; axios alone sits among the top thirty most-downloaded npm packages. The typosquat names are one keystroke away from the real ones (axoisaxios, tempaltetemplate), and both are linguistically plausible misspellings.

We were unable to obtain reliable download statistics for the malicious versions before takedown — npm does not retain per-version download counts after a package is unpublished, and npms.io-style proxies are noisy at this scale. Any organization whose lockfile resolves to a package named axois-utils or chalk-tempalte from publisher deadcode09284814 should be treated as compromised: rotate every credential present in process.env on the affected host, audit persistence vectors per OS (see “What defenders should do” below), and remove the dependency.

Emerging patterns

This campaign exemplifies a shift we have seen in several recent npm incidents: the install-hook scaffold is the durable asset; the payload is swappable. The same publisher, the same package, the same preinstall / install / postinstall triple, and even the same version-bump cadence — the only thing that changed between axois-utils:1.0.6 and axois-utils:1.0.9 was the file the hooks run. Signature-based detection keyed to the Phase A payload (wallet-path strings, phantom.js‘s 7,667-line collector, the Serveo C2 host) would have flagged the first three versions and missed Phase B entirely.

The same pattern is visible across other 2026 campaigns we have tracked: a single operator with a stable scaffold, swapping between credential theft, exam-cheating clients, Telegram-bot exfil, and now DDoS recruitment. Defenders who rely on payload signatures will keep losing the second round of every campaign.

The corollary is that install-time TTPs are the better signal. Triple install-hook declarations, install scripts that import https or child_process, install scripts that write to user-startup folders, and install scripts that resolve hostnames on free reverse-tunnel services (Serveo, localhost.run, ngrok, cloudflared) are all rare in legitimate packages and common across malicious ones.

What defenders should do

  • Lockfile audit. Search every package-lock.json / yarn.lock / pnpm-lock.yaml in your organization for the exact strings axois-utils and chalk-tempalte. Treat any match as a compromise.
  • Rotate secrets on affected hosts. Phase A bulk-harvested SSH, cloud, GitHub, npm, and wallet credentials. Assume every credential ever present in process.env, ~/.ssh, ~/.aws, ~/.npmrc, ~/.config, or any .env* file on the affected host is exposed.
  • Remove persistence (Phase B). On Windows, delete HKCU\Software\Microsoft\Windows\CurrentVersion\Run\SystemUpdate, remove %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\system-update.bat, and schtasks /delete /tn SystemUpdate /f. On Linux, crontab -e and remove @reboot node …, systemctl –user disable –now phantom-bot.service then delete ~/.config/systemd/user/phantom-bot.service, scrub .bashrc / .zshrc / /etc/rc.local. On macOS, launchctl unload ~/Library/LaunchAgents/com.phantom.bot.plist then delete the plist.
  • Block the C2 hosts. Add the four C2 endpoints in the IOC table to your egress blocklist. *.serveousercontent.com and *.lhr.life can be blocked wholesale if your environment has no legitimate use for reverse-tunnel services.
  • Hunt by install-time TTP, not payload. Inventory lockfile entries whose package.json declares preinstall, install, and postinstall all pointing at the same script. Cross-check against package age and publisher verification status. This pattern is rare in legitimate packages and is the single most reliable signal that PhantomBot reuses.
  • Audit for the C2 binary. Anyone who downloaded axois-utils:1.0.9 has the operator’s C2 server (c2 ELF, sha256 165fa92d…) on disk. Scan caches and CI artifact stores. The binary is dormant by itself, but its presence on a workstation is unambiguous evidence the package was installed.

Closing line

The same operator, the same package, and the same install hook can deliver entirely different threats inside of 48 hours. Watch the scaffold, not the payload.

sca-tools-software-composition-analysis-tools
Prioritize, remediate, and secure your software risks
7-day free trial
No credit card required

Secure your Software Development and Delivery

with Xygeni Product Suite