typeanalysisfamilyletsdiskusscomconfidencehighmalware-familyloaderscriptnodejsobfuscationevasionpersistencemitre-attck
SHA-256: 26155786a8e0ff9745f18589d31cce9be1f5af922ca0840532b0d1ab0e9db675

letsdiskusscom: 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:

  1. Directory creation — safeMakeDir(folder) with recursive: true and mode 0o755. ^[strings.txt:27]
  2. Payload reconstruction — Five calls to writePositionsToFile(wlist, payload, destPath) decode the poem strings back to raw PE/BAT bytes. ^[strings.txt:18]
  3. Execution — launchExecutable spawns "autorunPath" with shell: true and passes exePath as an argument, then spawns exePath directly. The BAT adds the registry key and exits; the EXE runs regardless. ^[strings.txt:29]
  4. Error handling — Wrapped in a bare try/catch that 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.js continues 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 sibling 9dc2cded, 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)

  1. Create a 256-word poem:
    cat > words.txt <<'EOF'
    gentle hush that wraps the midnight air ... (256 words)
    EOF
    
  2. 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)
    
  3. 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
    
  4. Wrap in Node.js carrier using the writePositionsToFile pattern from the sample.
  5. 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

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.