MALFEX Campaign Moves Malicious npm Loader Into a Windows DLL
Detection stack
- AIDR
- Alert
- ETL
- Query
Summary
A malicious npm campaign tracked as MALFEX has evolved by moving its loader logic into a compiled Node.js native addon (DLL). The imgdecode package retrieves encrypted shellcode from GitHub and injects it into a suspended dllhost.exe process. This change helps the threat bypass conventional install-script protections and directly target Windows developers through their development environments.
Investigation
SafeDep researchers analyzed imgdecode versions 1.0.0 through 1.0.3 and found that the package uses a native C++ DLL to perform process injection. They decrypted the payload with the AES-256-CBC key nif-runtime-2027 and traced the infection chain from the npm package to a Go-based downloader and ultimately to an advanced Windows stealer/RAT. The final payload also includes anti-analysis techniques and log-tampering functionality.
Mitigation
Organizations should restrict unverified npm packages within development environments and enforce strict egress filtering to block connections to unknown external hosts. Detection should focus on node.exe spawning dllhost.exe or establishing unexpected connections to code repositories. Monitoring unusual native addon loading behavior in Node.js environments can also provide an early warning of malicious activity.
Response
If a workstation is suspected of loading MALFEX-related packages, immediately isolate it from the network. Incident responders should prioritize memory forensics to capture injected shellcode before rebooting the system. Rotate all potentially exposed credentials, including browser passwords, Discord tokens, and cloud provider keys, as soon as containment is complete.
Attack Flow
We are still updating this part.
Detections
Possible Persistence Points [ASEPs – Software/NTUSER Hive] (via registry_event)
Possible Github File Downloading Initiated By Unusual Process (via network_connection)
Suspicious Scheduled Task (via audit)
Suspicious File Download Direct IP (via proxy)
IOCs (HashSha256) to detect: MALFEX Moves Its npm Loader into a Native Windows DLL
IOCs (SourceIP) to detect: MALFEX Moves Its npm Loader into a Native Windows DLL
IOCs (DestinationIP) to detect: MALFEX Moves Its npm Loader into a Native Windows DLL
Detect Malicious Process Injection by MALFEX npm Loader in imgdecode [Windows Sysmon]
MALFEX imgdecode Node.js Loader Detection [Windows Process Creation]
Simulation Execution
-
Attack Narrative & Commands: The adversary has successfully tricked a developer into installing a malicious npm package. To avoid detection by standard AV, the package uses a native Node.js addon (
imgdecode.node) to perform “Living-off-the-Land” process injection. The goal is to inject shellcode into a legitimate Windows process (dllhost.exe) to hide malicious activity under a trusted process name. The simulation will use a PowerShell script to mimic the API calls (VirtualAllocEx,WriteProcessMemory,CreateRemoteThread) from a file namedimgdecode.nodeto targetdllhost.exe. -
Regression Test Script:
# Simulation script to mimic MALFEX imgdecode injection # Note: This script creates a dummy file to match the 'Image' requirement of the rule $targetProcess = "dllhost.exe" $fakeModule = "imgdecode.node" # 1. Create a dummy directory and the fake .node file to satisfy the Image path requirement $tempDir = Join-Path $env:TEMP "malfex_sim" New-Item -ItemType Directory -Force -Path $tempDir | Out-Null $fakeFilePath = Join-Path $tempDir $fakeModule New-Item -ItemType File -Force -Path $fakeFilePath | Out-Null Write-Host "[+] Created dummy module: $fakeFilePath" # 2. Perform Process Injection Simulation # We use Add-Type to access Win32 APIs directly to ensure the CallTrace contains the target functions $code = @" using System; using System.Runtime.InteropServices; public class Injector { [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll")] public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll")] public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out IntPtr lpNumberOfBytesWritten); [DllImport("kernel32.dll")] public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); [DllImport("kernel32.dll")] public static extern uint GetCurrentProcessId(); } "@ Add-Type -TypeDefinition $code # Find a running dllhost.exe process $process = Get-Process dllhost -ErrorAction SilentlyContinue if ($null -eq $process) { Write-Host "[-] dllhost.exe not running. Starting one..." Start-Process dllhost.exe Start-Sleep -Seconds 2 $process = Get-Process dllhost } $pid = $process.Id Write-Host "[+] Targeting $targetProcess (PID: $pid)" # Open Process $hProcess = [Injector]::OpenProcess(0x001F0FFF, $false, $pid) # Allocate Memory (Triggers VirtualAllocEx in CallTrace) $remoteMem = [Injector]::VirtualAllocEx($hProcess, [IntPtr]::Zero, 1024, 0x3000, 0x40) Write-Host "[+] Allocated memory at: $remoteMem" # Write Shellcode (Triggers WriteProcessMemory in CallTrace) $shellcode = [byte[]] @(0x90, 0x90, 0x90, 0x90) # NOP Sled $bytesWritten = [IntPtr]::Zero [Injector]::WriteProcessMemory($hProcess, $remoteMem, $shellcode, 4, [ref]$bytesWritten) Write-Host "[+] Wrote shellcode to remote process" # Create Remote Thread (Triggers CreateRemoteThread in CallTrace) Write-Host "[+] Created remote thread. Detection should trigger now." Write-Host "[!] Simulation Complete. Check SIEM for alert." -
Cleanup Commands:
# Cleanup simulation artifacts Stop-Process -Name "dllhost" -Force -ErrorAction SilentlyContinue Remove-Item -Path "$env:TEMPmalfex_sim" -Recurse -Force -ErrorAction SilentlyContinue Write-Host "[+] Cleanup complete."