26155786a8e0ff9745f18589d31cce9be1f5af922ca0840532b0d1ab0e9db675letsdiskusscom: 26155786 — fourteenth confirmed poem-stego sibling, Update_16.js build counter
Executive Summary
Update_16.js is the fourteenth confirmed sibling in the letsdiskusscom Node.js dropper cluster. It encodes four PE payloads and a persistence BAT inside a 256-word English poem via numbered-suffix steganography, stages them to %ProgramData%\Microsoft Edge Updates Helper iRRSX1DkpmRS, and silently executes a signed Revo Uninstaller component. No C2 — the threat is self-contained local payload delivery with registry Run persistence. ^[strings.txt:1]
What It Is
| Field | Value |
|---|---|
| SHA-256 | 26155786a8e0ff9745f18589d31cce9be1f5af922ca0840532b0d1ab0e9db675 |
| Filename | Update_16.js |
| Size | 7,466,720 bytes (7.1 MB) |
| File type | JavaScript source, ASCII text, very long lines (63,365), CRLF terminators ^[file.txt:1] |
| Family | letsdiskusscom (high-confidence cluster sibling) |
| Source | OpenCTI / MalwareBazaar (js label) |
The sample is a Node.js script that uses fs, path, and child_process modules to drop and execute embedded Windows PE payloads. It does not require an external network connection to function — all payloads are encoded inside the script itself via a custom poem-word-list steganography scheme. ^[strings.txt:1]
How It Works
1. Poem-word-list steganography
The script defines a 256-word lookup table (wlist) — an English poem fragment — and five payload strings (exe, dll1, dll2, dll3, bat). Each payload word is looked up in wlist by index, and the index value (masked to 0xFF) is written to disk as a byte. ^[strings.txt:6]
Builder variant observed here: numbered suffixes on repeated vocabulary (gentle1, hush2, that3 ... fail164). This poisons frequency analysis without changing the lookup semantics. First seen in sibling 3465e6ee; this sample confirms the template is still active. ^[strings.txt:6] ^[techniques/poem-word-list-steganography.md]
2. Payload decode and staging
const folder = path.join(process.env.PROGRAMDATA, `Microsoft Edge Updates Helper iRRSX1DkpmRS`);
const exePath = path.join(folder, "Microsoft Edge Updates Helper.exe");
const autorunPath = path.join(folder, "iRRSX1DkpmRS.bat");
// ... plus three VC++ runtime DLLs
The script creates the staging directory recursively with mode 0o755, decodes all five payloads via writePositionsToFile, then spawns the BAT (which adds registry persistence) followed by the EXE. ^[strings.txt:12]
3. Registry Run persistence via BAT
The decoded BAT adds an HKCU\Software\Microsoft\Windows\CurrentVersion\Run entry named "Microsoft Edge Updates Helper" pointing to the staged EXE. ^[manual decode of bat.bin]
4. Payloads decoded
| Payload | SHA-256 | Size | Description |
|---|---|---|---|
| EXE | 8b94af60...7fc55f |
52,400 B | RevoSrp.exe — VS Revo Group, MSVC 14.44, Authenticode signed (DigiCert). Same hash as all 13 prior siblings. ^[exiftool: exe.bin] |
| DLL1 | 9b9faac1...edafa |
876,032 B | msvcp140.dll, Microsoft, MSVC 14.27.29016.0. Fourteenth distinct msvcp140 morph in cluster. Unsigned. ^[exiftool: dll1.bin] |
| DLL2 | ff43e813...4c833 |
101,672 B | vcruntime140.dll, Microsoft, MSVC 14.27. Signed. Same hash as all 13 prior siblings. ^[exiftool: dll2.bin] |
| DLL3 | 7b8f70dd...6dfc7 |
44,328 B | vcruntime140_1.dll, Microsoft, MSVC 14.27. Signed. Same hash as all 13 prior siblings. ^[exiftool: dll3.bin] |
| BAT | dff20059...06919 |
440 B | Registry Run persistence script. Same hash as all 13 prior siblings. ^[manual decode] |
Decoded Script Behavior
The entry point is the top-level IIFE-like block at the bottom of the script:
- Directory creation —
safeMakeDir(folder)withrecursive: trueand mode0o755. ^[strings.txt:27] - Payload reconstruction — Five calls to
writePositionsToFile(wlist, payload, destPath)decode the poem strings back to raw PE/BAT bytes. ^[strings.txt:18] - Execution —
launchExecutablespawns"autorunPath"withshell: trueand passesexePathas an argument, then spawnsexePathdirectly. The BAT adds the registry key and exits; the EXE runs regardless. ^[strings.txt:29] - Error handling — Wrapped in a bare
try/catchthat logs"Installation failed"to stderr and exits with code 1. ^[strings.txt:42]
C2 Infrastructure
None. This is a self-contained local installer. No network requests, no hardcoded URLs, no DNS, no C2 callbacks. The malicious act is the silent staging and execution of a masqueraded signed binary with persistence.
Interesting Tidbits
- Build counter in filename:
Update_16.jscontinues the internal numbering observed in prior siblings (Update_13.js,Update_22.js,Update_25.js,Update_3.js). This suggests a builder script that increments a version counter per build. ^[metadata.json:5] - Fourteenth distinct msvcp140.dll: The cluster now shows 14 unique msvcp140 morphs. The builder appears to rotate VC++ runtime redistributables between builds while keeping the Revo EXE and vcruntime DLLs constant. This may be an attempt to evade hash-based detection on the DLL alone.
- MSVC 14.27 timestamp on DLL1:
2020-06-16 03:11:14— an older runtime than the EXE's MSVC 14.44 (2025-06-02). The builder pulls runtimes from different VS redist packages. - RevoSrp.exe vs Revo Registry Cleaner: The EXE's PDB path is
D:\\Work_REVO\\VSRevo\\Windows\\Projects\\Registry Cleaner\\revo-registry-cleaner\\Revo Registry Cleaner\\x64\\Release\\RevoSrp.pdb, not the Uninstaller path observed in some prior reports. Same vendor, different product line. ^[strings: exe.bin] - BAT argument passing: The BAT accepts
"%~1"(the EXE path) as an argument, but the registry key hardcodes the EXE path inside the BAT's own string — the argument is redundant and may be builder template residue. - No
javascript-obfuscator: Unlike sibling9dc2cded, this sample has no self-defend IIFE, no string-array rotator, no dead-code injection — the obfuscation is purely the poem steganography.
How To Mess With It (Homelab Replication)
- Create a 256-word poem:
cat > words.txt <<'EOF' gentle hush that wraps the midnight air ... (256 words) EOF - Encode a payload:
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 (optional) to defeat naive frequency analysis:
suffix_counter = 1 def suffix(word): nonlocal suffix_counter if word in used_words: # only suffix repeats result = f"{word}{suffix_counter}" suffix_counter += 1 return result return word - Wrap in Node.js carrier using the
writePositionsToFilepattern from the sample. - Verify: Decode the script and compare SHA-256 to original payload.
Deployable Signatures
YARA rule — poem-word-list steganography Node.js dropper
rule letsdiskusscom_poem_stego_js
{
meta:
description = "Node.js dropper using 256-word poem steganography to encode PE payloads"
author = "PacketPursuit"
reference = "/intel/analyses/26155786a8e0ff9745f18589d31cce9be1f5af922ca0840532b0d1ab0e9db675.html"
date = "2026-08-22"
strings:
$require_fs = "const fs = require('fs');"
$require_path = "const path = require('path');"
$require_spawn = "const { spawn } = require('child_process');"
$wlist = "const wlist = \"gentle hush"
$exe = "const exe = \"unwearied"
$dll1 = "const dll1 = \"unwearied"
$func = "function writePositionsToFile(listA, listB, outPath)"
$progdata = "Microsoft Edge Updates Helper"
condition:
filesize > 1MB and filesize < 15MB
and all of ($require_*)
and $func
and $progdata
and any of ($wlist, $exe, $dll1)
}
Sigma rule — Node.js spawning signed EXE from fake ProgramData directory
title: Node.js spawning signed EXE from fake Edge Updates Helper directory
logsource:
product: windows
category: process_creation
detection:
selection_parent:
ParentImage|endswith: '\node.exe'
selection_child:
CommandLine|contains:
- 'Microsoft Edge Updates Helper'
- 'iRRSX1DkpmRS'
selection_registry:
CommandLine|contains:
- 'reg add'
- 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
condition: selection_parent and (selection_child or selection_registry)
falsepositives:
- Unknown
level: high
IOC list
| Indicator | Value | Type |
|---|---|---|
| SHA-256 (carrier) | 26155786a8e0ff9745f18589d31cce9be1f5af922ca0840532b0d1ab0e9db675 |
Hash |
| SHA-256 (embedded EXE) | 8b94af60bb58bc1629edb3b4f6a86ccff5769bb9b96d8826f06686af2d7fc55f |
Hash |
| SHA-256 (embedded DLL1) | 9b9faac11eb09e33713534b25db934e35aea1ddfd3b4b1b8cce5a347030edafa |
Hash |
| SHA-256 (embedded DLL2) | ff43e813785ee948a937b642b03050bb4b1c6a5e23049646b891a66f65d4c833 |
Hash |
| SHA-256 (embedded DLL3) | 7b8f70dd3bdae110e61823d1ca6fd8955a5617119f5405cdd6b14cad3656dfc7 |
Hash |
| SHA-256 (embedded BAT) | dff20059f161090c76f9f45ac2269f2965bdc96023c78c1072f8d1aa66b06919 |
Hash |
| Staging directory | %ProgramData%\Microsoft Edge Updates Helper iRRSX1DkpmRS |
Path |
| Registry key | HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Microsoft Edge Updates Helper |
Registry |
| Process tree | node.exe → cmd.exe /c "iRRSX1DkpmRS.bat" → Microsoft Edge Updates Helper.exe |
Behavior |
Behavioral fingerprint
This Node.js script drops five files to a fake %ProgramData%\Microsoft Edge Updates Helper <random_suffix> directory: one 52 KB signed x64 EXE (RevoSrp.exe), three Microsoft VC++ runtime DLLs (msvcp140.dll, vcruntime140.dll, vcruntime140_1.dll), and one 440-byte BAT script. The BAT adds an HKCU\Run persistence entry, then the EXE is launched via child_process.spawn with shell: true. No network activity. The script body contains extremely long lines (tens of thousands of characters) composed of English poem words with optional numeric suffixes.
Detection Signatures
- capa: N/A — JavaScript source file, not a supported binary class.
- MITRE ATT&CK:
- T1059.007 (Command and Scripting Interpreter: JavaScript)
- T1027.002 (Obfuscated Files or Information: Software Packing) — poem-word-list encoding
- T1036.005 (Masquerading: Match Legitimate Name or Location)
- T1547.001 (Boot or Logon Autostart Execution: Registry Run Keys)
- T1543.003 (Create or Modify System Process: Windows Service) — via
child_process.spawn
References
- letsdiskusscom — cluster entity page
- poem-word-list-steganography — technique page
- natural-language-payload-encoding — concept page
- registry-run-persistence — procedure page
- OpenCTI label:
letsdiskuss-com - MalwareBazaar:
26155786a8e0ff9745f18589d31cce9be1f5af922ca0840532b0d1ab0e9db675
Provenance
- Source file:
wiki/wiki/raw/analyses/26155786a8e0ff9745f18589d31cce9be1f5af922ca0840532b0d1ab0e9db675/ - Decoded payloads written to
/tmp/261557_decode/via manual Python script - File type:
file 5.44 - Metadata:
exiftool 12.76 - PE analysis:
pefile 2024.8.26 - capa: skipped (JavaScript source, unsupported file class)
- CAPE: skipped (JavaScript source, no Windows guest)
- No radare2 or Ghidra analysis required — behavior is fully recoverable from plaintext JavaScript decode.