MALFEX Hid a Windows Loader in an npm Native Addon: What to Check

The malicious imgdecode npm package hid a Windows shellcode loader in a native Node.js addon. Here is how it evaded common checks and what defenders should inspect.
Close-up of a developer laptop displaying terminal commands and code
Developer terminal and source code. Photo: park ingyeom/Unsplash.

A malicious npm package named imgdecode moved a Windows malware loader out of readable JavaScript and into a precompiled Node.js native addon, allowing the code to run when a developer imported the package without relying on a conventional install script.

SafeDep disclosed the package on October 13 after tracing it to MALFEX, a longer-running software supply-chain campaign documented earlier this month by Checkmarx. The newest package was published in four versions from September 30 through October 11. SafeDep found version 1.0.3 still available when it completed its analysis, though availability may have changed since then.

The finding matters because many package defenses focus on suspicious preinstall or postinstall hooks. imgdecode instead bundles a Windows DLL as a .node file and invokes it during module initialization. A developer can therefore pass an install-time check and still execute the loader later when an application calls require('imgdecode').

How the imgdecode loader works

The visible JavaScript entry point is only a few lines long. It attempts to load prebuilds/win32-x64/imgdecode.node, then falls back to a build-directory copy. The package does not contain the image-decoding implementation its name and description imply.

Once Node.js loads the native addon, the DLL’s N-API initialization function begins the malicious routine before control returns to the calling JavaScript. SafeDep found that the code checks whether standard output is attached to an interactive Windows console by calling GetConsoleMode(). It exits when output is redirected to a pipe or file, a condition common in CI jobs and automated sandboxes.

On a qualifying system, the addon contacts a GitHub-hosted path for a file named banner.jpg, decrypts the response with AES-256-CBC and injects the resulting shellcode into a suspended dllhost.exe process. The loader uses the familiar Windows sequence of VirtualAllocEx, WriteProcessMemory, VirtualProtectEx and CreateRemoteThread. If process injection fails, it attempts to execute the bytes inside node.exe.

The researchers could not recover that exact banner.jpg payload, so they did not claim to know its final contents. They did recover a sibling carrier, banner.png, from the same GitHub repository. It led to a Go downloader and then to a 71.5 MB MALFEX information stealer and remote-access tool. Shared infrastructure, encryption material and a matching downloader hash connect imgdecode to the campaign with high confidence, but the final payload behind the DLL-era file remains an inference.

Why ordinary package checks can miss it

Native addons are normal in the Node.js ecosystem. Database drivers, cryptography packages and performance-sensitive libraries routinely ship compiled .node files. Blocking every prebuilt binary would break legitimate development, while the presence of a DLL alone is not proof of malware.

imgdecode exploits that ambiguity in three ways. It ships no native source or binding.gyp file, hides the functional code behind a harmless-looking JavaScript wrapper and waits for a developer’s interactive terminal before running. Version 1.0.3 also removed its dependency on another MALFEX package, making it self-contained.

The campaign’s timeline shows why registry removal alone is insufficient. After npm removed img-to-native and native-runner on September 28, the operator published replacement packages within roughly two days. runhelper briefly used JavaScript foreign-function calls to execute shellcode; after that package disappeared, imgdecode moved the same job into a compiled addon.

Download totals should be read carefully. SafeDep counted 929 downloads of imgdecode from September 30 through October 12, but registry mirrors, security scanners and researchers can all contribute to that number. It is not a victim count. In the broader MALFEX campaign, Checkmarx counted 40,767 downloads across eight malicious packages as of October 1, with function-flag accounting for most of them.

How developers can check for MALFEX packages

Start with dependency manifests and lockfiles, including historical branches and build artifacts. The package names documented across the latest and earlier MALFEX activity include:

  • imgdecode versions 1.0.0 through 1.0.3
  • imgbundle, runhelper, cdn-img-fetch, img-to-native and native-runner
  • function-flag, function-color, tlxbnhd, tldriver and mxdriver

From a project directory, npm users can inspect the resolved tree without installing anything new:

npm ls imgdecode imgbundle runhelper cdn-img-fetch img-to-native native-runner function-flag function-color --all

Also search package-lock.json, npm-shrinkwrap.json, pnpm-lock.yaml and yarn.lock. A clean current tree does not rule out an earlier execution on a developer workstation.

Endpoint and network telemetry offer stronger evidence. SafeDep recommends looking for node.exe spawning dllhost.exe without the usual /Processid: argument, a Node process connecting to raw.githubusercontent.com followed by outbound traffic from dllhost.exe, and a package containing a prebuilt addon with no corresponding native source. The researchers also published file hashes and a YARA rule for the three recovered imgdecode DLL builds.

What to do if a Windows machine loaded the package

Do not treat removal from node_modules as remediation. SafeDep advises handling a Windows workstation that loaded imgdecode or another MALFEX package as potentially compromised.

  1. Isolate the host from the network and preserve volatile evidence before rebooting, because the loader runs shellcode in memory.
  2. Collect process, network and endpoint logs, paying particular attention to the node.exe-to-dllhost.exe relationship and the indicators in the research report.
  3. Check for the documented Run-key and scheduled-task persistence entries, along with the copied MicrosoftEdgeUpdate.exe file under the user’s application-data directory.
  4. Rotate browser sessions, npm and GitHub tokens, cloud keys, SSH credentials and other secrets used on the machine. Perform rotations from a known-clean system.
  5. Review package allowlists and inspection rules so precompiled native addons receive the same scrutiny as lifecycle scripts.

The practical lesson is narrower than “native addons are dangerous.” Teams need controls that follow code into runtime, distinguish expected binaries from unexplained ones and keep package history long enough to investigate after a registry takedown.

Sources

Previous Post
CERT-In officials during the international cyber security exercise Synergy

CERT-In Says AI Agents Reached Live Systems: What Defenders Should Check

Next Post
Meta headquarters sign in Menlo Park, California

Meta Removed AI-Training Language From Its Terms. Crawling Permission Remains

Related Posts