1fbaf8ab9f90dc7288b2dfa77d12c22452f162046134750036bdafc8d7500bbcletsdiskusscom: 1fbaf8ab9f90 — Update_25.js, thirteenth sibling, thirteenth distinct msvcp140.dll morph
Executive Summary
The thirteenth confirmed sibling in the letsdiskusscom Node.js dropper cluster. A 9.0 MB JavaScript carrier (Update_25.js) masquerading as a Microsoft Edge update helper. It decodes four PE files and a BAT script from a 256-word English poem plus 164 numbered-suffix vocabulary tokens (gentle1, hush2, etc.), stages them to %ProgramData%\Microsoft Edge Updates Helper 9lra5SvMi38U, writes an HKCU Run key via the BAT, and spawns the payload. The inner EXE is a signed Revo Uninstaller Pro component; the DLL2/DLL3/BAT hashes match all twelve prior siblings, but the msvcp140.dll is a thirteenth distinct morph. No C2 from the carrier. Static-only (CAPE skipped — not a binary class). ^[triage.json] ^[file.txt]
What It Is
| Field | Value |
|---|---|
| SHA-256 | 1fbaf8ab9f90dc7288b2dfa77d12c22452f162046134750036bdafc8d7500bbc |
| Filename | Update_25.js |
| File type | JavaScript source, ASCII text, very long lines (63,365 chars), CRLF terminators ^[file.txt] |
| Size | 9,015,938 bytes |
| Family | letsdiskusscom (high confidence, 13th sibling) |
| Build | Node.js self-contained installer, numbered-suffix poem steganography |
The carrier is a 44-line Node.js script. It defines a 256-word vocabulary (wlist) with 164 numbered-suffix duplicates, encodes four PE files and a BAT as space-separated word sequences, then maps each word back to its array index to recover raw bytes. ^[strings.txt:1]
How It Works
1. Poem steganography decode
The 256-word poem vocabulary maps bytes 0x00–0xFF to words. Repeated words beyond the first cycle get numbered suffixes (gentle1 through fail164) to poison frequency analysis. The decode routine splits wlist, looks up each payload word via a.indexOf(word), and writes index & 0xFF to disk: ^[strings.txt:18]
function writePositionsToFile(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);
}
2. Payload staging
Staged to %ProgramData%\Microsoft Edge Updates Helper 9lra5SvMi38U (suffix 9lra5SvMi38U randomized per build): ^[strings.txt:5]
| File | Role | SHA-256 | Notes |
|---|---|---|---|
Microsoft Edge Updates Helper.exe |
Main payload | 8b94af60bb58bc1629edb3b4f6a86ccff5769bb9b96d8826f06686af2d7fc55f |
RevoSrp.exe (Registry Cleaner), signed by VS REVO GROUP OOD via DigiCert Trusted G4 Code Signing CA |
msvcp140.dll |
VC++ runtime | ce73fdede936589ca432eefcafedd24eb8035e09247973287b217c3482153c09 |
Thirteenth distinct morph (1,089,536 B), MSVC 14.27.29016.0, timestamp 1592277074 (2020-06-16) |
vcruntime140.dll |
VC++ runtime | ff43e813785ee948a937b642b03050bb4b1c6a5e23049646b891a66f65d4c833 |
Same as all prior siblings |
vcruntime140_1.dll |
VC++ runtime | 7b8f70dd3bdae110e61823d1ca6fd8955a5617119f5405cdd6b14cad3656dfc7 |
Same as all prior siblings |
9lra5SvMi38U.bat |
Persistence launcher | dff20059f161090c76f9f45ac2269f2965bdc96023c78c1072f8d1aa66b06919 |
Same as all prior siblings |
3. Persistence and execution
The BAT adds an HKCU Run registry entry for the EXE path, then exits. The carrier spawns the BAT with the EXE path as an argument, then spawns the EXE directly: ^[strings.txt:40]
launchExecutable(`"${autorunPath}"`, [`"${exePath}"`]);
launchExecutable(`"${exePath}"`);
Both spawns use child_process.spawn(..., { shell: true, stdio: 'inherit' }). ^[strings.txt:30]
4. Inner EXE analysis
The decoded EXE (8b94af60...) is a 51,424-byte x64 PE with a valid Authenticode signature by VS REVO GROUP OOD (DigiCert Trusted G4 Code Signing RSA4096 SHA384 2021 CA1). PDB path: D:\Work_REVO\VSRevo\Windows\Projects\Registry Cleaner\revo-registry-cleaner\Revo Registry Cleaner\x64\Release\RevoSrp.pdb. Imports are benign: KERNEL32 (LoadLibraryW, GetProcAddress), ADVAPI32 (registry APIs), OLE32, MSVCP140, VCRUNTIME140. No network imports. No C2 surface. This is a legitimate signed tool repurposed as a masquerade payload.
Decompiled Behavior
Not applicable — the threat is the carrier script, not the inner EXE. The carrier's control flow is linear: decode → mkdir → writeFileSync × 5 → spawn × 2. No branching, no anti-analysis, no sandbox gates. The obfuscation is entirely lexical (poem steganography).
C2 Infrastructure
None from the carrier. The inner EXE has no observable C2 surface. The threat model is social-engineering delivery + silent local installation + persistence, not remote command-and-control.
Interesting Tidbits
- Filename counter:
Update_25.jscontinues the internal build counter; prior siblings includeUpdate_3.js,Update_13.js,Update_22.js, and nowUpdate_25.js— confirming active campaign iteration. ^[triage.json] - Thirteenth distinct msvcp140.dll: The builder rotates this DLL per campaign while keeping the EXE, two vcruntime DLLs, and BAT identical. This is supply-chain artifact collection or deliberate hash-diversity against IOC-based blocking. The DLL is a legitimate Microsoft VC++ runtime (MSVC 14.27, 2020-06-16), not attacker-compiled.
- dll4Path typo: The script declares
const dll4Path = path.join(folder, "9lra5SvMi38U.bat")— a copy-paste error (reuses the BAT filename asdll4Path), butdll4Pathis never referenced in the execution block. Dead code from a builder template. ^[strings.txt:17] - No base64, no hex: The steganography is purely word-index mapping. Static tools that hunt for base64 blobs or hex strings will miss this entirely. ^[strings.txt:1]
- Same spawn mode as c075aeba: Uses
{ shell: true, stdio: 'inherit' }rather than{ detached: true }. The cluster oscillates between spawn modes; not a reliable family discriminator.
How To Mess With It (Homelab Replication)
- Poem encoder (Python):
with open('words.txt') as f: words = f.read().split() assert len(words) == 256 with open('payload.exe', 'rb') as f: data = f.read() encoded = ' '.join(words[b] for b in data) # Add numbered suffixes for the variant - Carrier template (Node.js): Wrap the word list and encoded strings in the
writePositionsToFile+spawnpattern shown above. - Verification: Decode back to the original file byte-for-byte. The MZ header (
4d5a) should appear asgentle unwearied(indices 0, 78) in the encoded stream.
Deployable Signatures
YARA rule
rule letsdiskusscom_js_dropper {
meta:
description = "Node.js poem-steganography dropper (letsdiskusscom cluster)"
author = "PacketPursuit"
date = "2026-08-22"
hash = "1fbaf8ab9f90dc7288b2dfa77d12c22452f162046134750036bdafc8d7500bbc"
strings:
$s1 = "const fs = require('fs')" ascii
$s2 = "const { spawn } = require('child_process')" ascii
$s3 = "Microsoft Edge Updates Helper" ascii
$s4 = "writePositionsToFile" ascii
$s5 = "Buffer.from(positions.map(p => p & 0xFF))" ascii
$poem1 = "gentle hush that wraps the midnight air" ascii
$poem2 = "moonlight stitches silver on sea" ascii
$poem3 = "let mortal sorrow find a softer chair" ascii
condition:
filesize > 1MB and
3 of ($s*) and
2 of ($poem*)
}
Sigma rule
title: Node.js Poem Steganography Dropper Execution
status: experimental
description: Detects Node.js process writing PE files to ProgramData and spawning child processes
logsource:
product: windows
category: process_creation
detection:
selection:
CommandLine|contains: 'node'
ParentImage|endswith:
- '\node.exe'
- '\nodejs.exe'
child_spawn:
Image|endswith:
- '\Microsoft Edge Updates Helper.exe'
- '.bat'
condition: selection and child_spawn
falsepositives:
- Legitimate Node.js applications
level: high
IOC list
| Type | Value | Context |
|---|---|---|
| SHA-256 (carrier) | 1fbaf8ab9f90dc7288b2dfa77d12c22452f162046134750036bdafc8d7500bbc |
Update_25.js |
| SHA-256 (EXE) | 8b94af60bb58bc1629edb3b4f6a86ccff5769bb9b96d8826f06686af2d7fc55f |
RevoSrp.exe |
| SHA-256 (DLL1) | ce73fdede936589ca432eefcafedd24eb8035e09247973287b217c3482153c09 |
msvcp140.dll (13th morph) |
| SHA-256 (DLL2) | ff43e813785ee948a937b642b03050bb4b1c6a5e23049646b891a66f65d4c833 |
vcruntime140.dll |
| SHA-256 (DLL3) | 7b8f70dd3bdae110e61823d1ca6fd8955a5617119f5405cdd6b14cad3656dfc7 |
vcruntime140_1.dll |
| SHA-256 (BAT) | dff20059f161090c76f9f45ac2269f2965bdc96023c78c1072f8d1aa66b06919 |
autorun.bat |
| File path | %ProgramData%\Microsoft Edge Updates Helper 9lra5SvMi38U\* |
Staging directory |
| Registry | HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Microsoft Edge Updates Helper |
Persistence |
Behavioral fingerprint statement
This JavaScript carrier (Node.js) defines a 256-word English poem vocabulary and encodes PE payloads as space-separated word sequences. At runtime it decodes words to array indices, writes the indices as bytes to %ProgramData%\Microsoft Edge Updates Helper <random_suffix>, drops a BAT file that writes an HKCU Run registry key, and spawns both the BAT and the EXE via child_process.spawn with shell: true. No network C2. The inner EXE is a signed legitimate tool (Revo Uninstaller Pro component) with no malicious imports.
Detection Signatures
| ATT&CK ID | Technique | Evidence |
|---|---|---|
| T1059.007 | JavaScript execution | Node.js require('fs') + require('child_process') ^[strings.txt:1] |
| T1027.002 | Obfuscated Files or Info | poem-word-list-steganography — 256-word English poem with numbered suffixes ^[strings.txt:1] |
| T1036.005 | Masquerading | Microsoft Edge Updates Helper directory and filename ^[strings.txt:4] |
| T1547.001 | Registry Run Keys | BAT calls reg add on HKCU\Software\Microsoft\Windows\CurrentVersion\Run ^[strings.txt:1] |
| T1543.003 | Create/modify system process | child_process.spawn(..., {shell: true, stdio: 'inherit'}) ^[strings.txt:30] |
References
- Artifact ID:
179ad3d5-52e3-4db5-a14e-687f73d08748 - Source: MalwareBazaar (abuse.ch)
- Family entity: letsdiskusscom
- Technique page: poem-word-list-steganography
- Concept page: natural-language-payload-encoding
- Procedure page: registry-run-persistence
Provenance
Analysis derived from file.txt (file type), triage.json (metadata), strings.txt (JavaScript source extraction), and manual payload decoding via Python/Node.js reimplementation of the writePositionsToFile routine. Payload hashes verified against VirusTotal. No dynamic execution performed (CAPE skipped — JS source not a supported binary class). Sample binary at <sample 1fbaf8ab9f90.bin>.