Signed PuTTY Installer Hides a Node.js Malware Loader

Summary
Analysis found a signed PuTTY.msi that installs genuine PuTTY alongside a re-signed Node.js loader. After a five-minute delay, it fetches an encrypted in-memory stage from Cloudflare Workers; the stage's purpose remains unknown.
Key points
- The MSI installs genuine PuTTY as a decoy and launches a re-signed Node.js 26.7.0 executable carrying a hidden script and the koffi library.
- The loader waits 300 seconds, checks for security-product DLLs, and chooses between network-communication paths based on what it finds.
- On one path, it patches ETW and AMSI; it also uses encrypted key exchange and avoids writing the downloaded stage to disk.
- In a sandbox, the loader contacted machinegirl[.]carvelpolman[.]workers[.]dev, then downloaded an approximately 240 KB module that contacted subdomains of normanitavo[.]workers[.]dev.
- The second-stage module remains unanalyzed. Loaded libraries could be relevant to credential theft, but the article says their presence alone does not establish that activity.
- The installer does not create a Run key, shortcut, or scheduled task, but its Node.js files remain after uninstall; the article recommends checking C:\ProgramData\NodeJS\node.exe and hunting for related activity.
Article Details
- Attack Vectors
- A PuTTY.msi installer installs the genuine PuTTY client while launching a re-signed Node.js executable from C:\ProgramData\NodeJS\ in the background.
- The embedded JavaScript waits 300 seconds, checks for security-product DLLs, and downloads a second stage from a Cloudflare Workers address.
- The loader decrypts and executes the returned module in memory; the observed second-stage module established a separate command channel through additional Workers hostnames.
- Defensive Notes
- Download PuTTY only from its official page or through winget, and stop if the installer is signed by someone other than Simon Tatham.
- If this installer was run, check for C:\ProgramData\NodeJS\node.exe; uninstalling PuTTY does not remove the Node.js files.
- Hunt for node.exe outside normal installation paths, particularly when it is signed by someone other than the OpenJS Foundation, and for HKCU\Software\NodeJS\Agent.
- Flag Node processes that load koffi.node and then create private executable memory or thread-pool work.
- Block or alert on *.carvelpolman[.]workers[.]dev and *.normanitavo[.]workers[.]dev; look for direct DNS to public resolvers or DoH from non-browser processes.
- Watch for MSI custom actions launching node.exe from ProgramData.
Indicators of compromise
| Type | Indicator | Context |
|---|---|---|
| HOSTNAME | bluepeak[.]normanitavo[.]workers[.]dev | Observed second-stage command-channel hostname. |
| HOSTNAME | cedar-stone[.]normanitavo[.]workers[.]dev | Observed second-stage command-channel hostname. |
| SHA256 | 2214430d1611a20f60cd656025b031d9476c5f2d9bdb421ec5d4e87551167453 | SHA-256 of decoy_launch.exe, the helper used to open the genuine PuTTY client. |
| SHA256 | 73ab77da60d7196bce61405c615f8cd03a3157abffaf4a2447bd79fb8082da9f | SHA-256 of the malicious PuTTY.msi installer. |
| SHA256 | 7409a74bb808d35d03e4aac7e05991b3b2263a30bc225144d6f520ad264be636 | SHA-256 of the in-memory httpc module used by the loader. |
| SHA256 | 823d9e0d1086a5894e126dbff272259731208bd17dc47a0753849ee98818339e | SHA-256 of the Node.js single-executable loader carrying the hidden script. |
| SHA256 | f384e2419f200e4eb8b49657be41a0e4327b044e7d571303b68463d417a2d544 | SHA-256 of the re-signed koffi.node component bundled with the loader. |
| URL | hxxps[:]//machinegirl[.]carvelpolman[.]workers[.]dev/ | Loader C2 URL that supplied the get_module_luna stage in the observed sandbox run. |
MITRE ATT&CK
T1027 · Obfuscated Files or InformationThe loader stores its configuration and a second module encrypted, and the returned stage is delivered inside an encrypted exchange.T1059.007 · JavaScriptThe Node.js executable runs an embedded JavaScript loader script.T1071.001 · Web ProtocolsThe loader communicates with its C2 over HTTPS, including requests to Cloudflare Workers hostnames.T1105 · Ingress Tool TransferThe loader downloads the get_module_luna stage from its C2.T1106 · Native APIThe loader builds syscall stubs to make direct system calls for allocating and protecting memory.T1497.003 · Time Based ChecksThe loader sleeps for 300 seconds before contacting its C2, a delay the article says is aimed at sandboxes.T1518.001 · Security Software DiscoveryThe loader checks its process's loaded DLLs for SentinelOne, Bitdefender, or Sophos before choosing its execution path.T1562.001 · Disable or Modify ToolsThe in-memory module patches ETW and AMSI to interfere with monitoring and script scanning.T1573.001 · Symmetric CryptographyThe loader encrypts its request with AES-GCM.T1573.002 · Asymmetric CryptographyThe client and server use an ECDH key exchange to agree on a shared secret for the encrypted exchange.T1620 · Reflective Code LoadingThe loader maps a returned PE file into memory or executes returned shellcode without writing the stage to disk.
People
Vendors
Products
BitdefenderIf it spots SentinelOne, Bitdefender or Sophos, it plays it safe: it uses Node’s normal HTTPS code and does its own DNS lookups against public resolvers (Cloudflare, Google, Quad9, OpenDNS) or Cloudflare DNS-over-HTTPS,Node.jsCanadian company called "Code Blue Staffing Inc." Alongside PuTTY it drops a genuine, re-signed copy of Node.js with a hidden script baked into it.PuTTYA file called PuTTY.msi installs the real PuTTY SSH client, and it carries a valid Extended Validation (EV) signature from a Canadian company called "Code Blue Staffing Inc." Alongside PuTTY it drops a genuine,SentinelOneIf it spots SentinelOne, Bitdefender or Sophos, it plays it safe: it uses Node’s normal HTTPS code and does its own DNS lookups against public resolvers (Cloudflare, Google, Quad9, OpenDNS) or Cloudflare DNS-over-HTTPS,SophosIf it spots SentinelOne, Bitdefender or Sophos, it plays it safe: it uses Node’s normal HTTPS code and does its own DNS lookups against public resolvers (Cloudflare, Google, Quad9, OpenDNS) or Cloudflare DNS-over-HTTPS,
Tools
ANY.RUNTools used: Malcat, msitools, osslsigncode, Python (pefile, capstone, pycryptodome), js-beautify, FLOSS, capa, ANY.RUN and Deception Pro.capaTools used: Malcat, msitools, osslsigncode, Python (pefile, capstone, pycryptodome), js-beautify, FLOSS, capa, ANY.RUN and Deception Pro.Deception ProTools used: Malcat, msitools, osslsigncode, Python (pefile, capstone, pycryptodome), js-beautify, FLOSS, capa, ANY.RUN and Deception Pro.FLOSSTools used: Malcat, msitools, osslsigncode, Python (pefile, capstone, pycryptodome), js-beautify, FLOSS, capa, ANY.RUN and Deception Pro.MalcatTools used: Malcat, msitools, osslsigncode, Python (pefile, capstone, pycryptodome), js-beautify, FLOSS, capa, ANY.RUN and Deception Pro.msitoolsTools used: Malcat, msitools, osslsigncode, Python (pefile, capstone, pycryptodome), js-beautify, FLOSS, capa, ANY.RUN and Deception Pro.osslsigncodeTools used: Malcat, msitools, osslsigncode, Python (pefile, capstone, pycryptodome), js-beautify, FLOSS, capa, ANY.RUN and Deception Pro.