NWHStealer Spread Through Bun JavaScript Runtime
Detection stack
- AIDR
- Alert
- ETL
- Query
Summary
The article explains how threat actors are using the Bun JavaScript runtime to deliver the Rust-based infostealer NWHStealer. Malicious ZIP archives contain a primary installer that launches a Bun-packaged JavaScript loader along with a secondary loader named dw.exe. The JavaScript component performs anti-virtual machine checks, reaches out to command-and-control infrastructure, decrypts a follow-on payload, and abuses native Windows APIs to inject the stealer into memory. To reduce suspicion, the campaign hides malicious files behind legitimate hosting platforms such as GitHub, MediaFire, and SourceForge.
Investigation
Researchers found that Bun was used to package malicious JavaScript inside the .bun section of the loader. Two scripts, sysreq.js and memload.js, handled environment checks and communication with the attacker infrastructure. The loader retrieved configuration data and encrypted payloads from domains such as silent-harvester.cc, decrypted them with AES-256-CBC, and injected them through Win32 API calls. The final payload was NWHStealer, which steals credentials, browser data, and cryptocurrency wallet information, and can also launch additional mining activity.
Mitigation
Users should avoid downloading executables from untrusted websites and should verify installer signatures before execution. Security teams should monitor for unusual Bun runtime activity, PowerShell CIM queries used for virtualization checks, and suspicious API calls such as VirtualAlloc and LoadLibraryA. Organizations should also block the known malicious domains and enforce least-privilege execution policies to limit abuse.
Response
If this activity is detected, isolate the affected endpoint, collect memory and disk images, and hunt for Installer.exe, dw.exe, and suspicious .bun sections. Block the identified command-and-control domains and URLs at the network perimeter. Deploy endpoint detections for the observed PowerShell commands and Win32 API patterns. Rotate exposed credentials and monitor affected systems for signs of unauthorized cryptocurrency mining.
"graph TB %% Class definitions classDef technique fill:#ffcc99 classDef action fill:#99ccff %% Nodes node_initial_access["<b>Initial Access</b><br/><b>T1036.008 Masquerading: File Type</b>: Malicious ZIP disguised as a benign file type.<br/><b>T1204.002 User Execution: Malicious File</b>: Victim executes the dropped file."] class node_initial_access technique node_obfuscated_loader["<b>Obfuscated Loader</b><br/><b>T1027.016 Junk Code Insertion</b>: Inserts irrelevant JavaScript to hide malicious logic.<br/><b>T1027.008 Stripped Payloads</b>: Removes nonu2011essential code to reduce size and evade analysis."] class node_obfuscated_loader technique node_discovery["<b>Discovery</b><br/><b>T1082 System Information Discovery</b>: Collects operating system, hardware, and software details.<br/><b>T1016 System Network Configuration Discovery</b>: Enumerates network adapters, IP addresses, and routing information."] class node_discovery technique node_credential_access["<b>Credential Access</b><br/><b>T1555.003 Credentials from Web Browsers</b>: Extracts stored passwords and autofill data from browsers.<br/><b>T1550.004 Use Alternate Authentication Material</b>: Harvests web session cookies for authenticated access."] class node_credential_access technique node_c2["<b>Command & Control</b>: Retrieves AES decryption seed and encrypted payload from remote server."] class node_c2 action node_execution["<b>Execution</b><br/><b>T1620 Reflective Code Loading</b>: Loads encrypted payload directly into memory without writing to disk.<br/><b>T1055.009 Process Injection: Proc Memory</b>: Injects the reflected code into a running process."] class node_execution technique node_persistence["<b>Persistence</b><br/><b>T1053 Scheduled Task</b>: Creates a scheduled task to execute the malicious payload on a recurring basis."] class node_persistence technique %% Connections node_initial_access –>|leads_to| node_obfuscated_loader node_obfuscated_loader –>|leads_to| node_discovery node_discovery –>|leads_to| node_credential_access node_credential_access –>|leads_to| node_c2 node_c2 –>|leads_to| node_execution node_execution –>|leads_to| node_persistence "
Attack Flow
Detections
Suspicious Command and Control by Unusual Top Level Domain (TLD) DNS Request (via dns)
View
Possible Evasion Checks (via powershell)
View
Possible IP Lookup Domain Communications Attempted (via dns)
View
IOCs (HashSha256) to detect: Attackers adopt JavaScript runtime Bun to spread NWHStealer
View
Detect NWHStealer JavaScript Loader Self-Injection [Windows Process Creation]
View
Detection of NWHStealer Anti-Virtualization PowerShell Commands [Windows Powershell]
View
Simulation Execution
Prerequisite: The Telemetry & Baseline Pre‑flight Check must have passed.
Rationale: This section details the precise execution of the adversary technique (TTP) designed to trigger the detection rule. The commands and narrative MUST directly reflect the TTPs identified and aim to generate the exact telemetry expected by the detection logic. Abstract or unrelated examples will lead to misdiagnosis.
-
Attack Narrative & Commands:
- Initial Access: The attacker delivers a malicious JavaScript file (
loader.js) on the victim system and executes it using the bun runtime (bun loader.js). - Self‑Injection Routine: The JavaScript code calls into native Win32 APIs via a PowerShell helper that uses Add‑Type to define P/Invoke signatures for
VirtualAlloc,VirtualProtect, andLoadLibraryA. - Memory Allocation:
VirtualAllocreserves a RWX region sized for the embedded shellcode. - Shellcode Injection: The PowerShell script copies the base64‑decoded shellcode into the allocated memory using
Marshal.Copy. - Permission Change:
VirtualProtectflips the page protection to PAGE_EXECUTE_READ. - Payload Execution:
LoadLibraryAis invoked on a malicious DLL that resides only in memory (the DLL header is written to the allocated region, then the address is passed toLoadLibraryA). - No New Thread: The attacker deliberately avoids calling
CreateThread; the DLL’s DllMain runs in the context of the current process, satisfying the rule’s exclusion clause.
- Initial Access: The attacker delivers a malicious JavaScript file (
-
Regression Test Script:
# ------------------------------------------------- # NWHStealer JavaScript Loader – Self‑Injection Demo # ------------------------------------------------- # This script mimics the behavior of the real NWHStealer loader. # It deliberately calls VirtualAlloc, VirtualProtect, and LoadLibraryA # without invoking CreateThread. # 1. Define Win32 API signatures $sig = @" using System; using System.Runtime.InteropServices; public class Win32 { [DllImport("kernel32.dll", SetLastError=true)] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll", SetLastError=true)] public static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect); [DllImport("kernel32.dll", CharSet=CharSet.Ansi, SetLastError=true)] public static extern IntPtr LoadLibraryA(string lpFileName); } "@ Add-Type $sig # 2. Allocate executable memory (RWX) $size = 0x1000 $MEM_COMMIT = 0x1000 $PAGE_EXECUTE_READWRITE = 0x40 $mem = [Win32]::VirtualAlloc([IntPtr]::Zero, $size, $MEM_COMMIT, $PAGE_EXECUTE_READWRITE) if ($mem -eq [IntPtr]::Zero) { throw "VirtualAlloc failed" } # 3. Example shellcode (MessageBox) – base64 encoded for readability $b64 = "AQAAANAAAABAAEAAAABAAAAAgAAAAEAAABWAAAAAQAAAAIAAAD/////AQAAAAAAAAA=" $shellcode = [Convert]::FromBase64String($b64) # 4. Copy shellcode into allocated region [System.Runtime.InteropServices.Marshal]::Copy($shellcode, 0, $mem, $shellcode.Length) # 5. Change protection to EXECUTE_READ $oldProtect = 0 $PAGE_EXECUTE_READ = 0x20 $ok = [Win32]::VirtualProtect($mem, $size, $PAGE_EXECUTE_READ, [ref]$oldProtect) if (-not $ok) { throw "VirtualProtect failed" } # 6. Load the in‑memory "DLL" (here we simply call LoadLibraryA on kernel32 as a placeholder) # In the real loader this would be a custom DLL written into $mem. $hLib = [Win32]::LoadLibraryA("kernel32.dll") if ($hLib -eq [IntPtr]::Zero) { throw "LoadLibraryA failed" } Write-Host "Self‑injection steps completed – process should now be flagged by the detection rule." # ------------------------------------------------- -
Cleanup Commands:
# Terminate the PowerShell instance that performed the injection Stop-Process -Id $PID -Force # (Optional) If a custom DLL was written to disk for testing, remove it Remove-Item -Path "$env:TEMPmalicious.dll" -ErrorAction SilentlyContinue
Post‑Execution Validation
-
Confirm Alert Generation: Run the following KQL query to verify the rule fired:
// Detection rule: VirtualAlloc/VirtualProtect/LoadLibraryA without CreateThread Sysmon | where EventID == 1 | where CallTrace contains "VirtualAlloc" or CallTrace contains "VirtualProtect" or CallTrace contains "LoadLibraryA" | where not(CallTrace contains "CreateThread") | project TimeGenerated, Process, CallTrace, EventID -
Verify No False Positive: Re‑run the benign Notepad command and ensure the query returns zero results for that run.
End of Report