247b54b524dcdd1a4dbe76ac11473ba26ea003193ad86216fe411f9b80e8c7fbletsdiskusscom: 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 labelletsdiskuss-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] dll4Pathdead code still present. Same copy-paste error as sibling 2:const dll4Path = path.join(folder, "qZWpLKQXEGaa.bat")is declared but never passed towritePositionsToFile. The BAT is written viaautorunPathinstead. ^[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, andbatstrings 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.dllfor 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 identificationexiftool.json— metadata extractiontriage.json— triage pipeline classificationstrings.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_*.binfiles) - Manual PE verification via
rabin2 -Iandsha256sum
Tool versions: file 5.44, exiftool 12.76, radare2 5.9.0, capa 7.0.1, Node.js v22.22.3.