LausivLoader: PNG Steganography and Environment-Variable Stage Handoffs in a Multi-Stage Loader

·

LausivLoader is a multi-stage malware loader documented by SANS Internet Storm Center handler Jan Kopriva after a sample surfaced in a customer mail-gateway quarantine at the end of August 2026. The initial script carried a 28/55 detection rate on VirusTotal — good enough to be caught, thin enough to slip past weaker gateways.

What makes it worth a teardown is not the delivery — a boring fiber-optic price-quotation lure — but the plumbing. LausivLoader passes data between its JavaScript and PowerShell stages through process environment variables, patches AMSI in memory, and pulls its final .NET stage out of the iTXt chunk of a PNG downloaded from its C2. Every handoff is engineered to leave as little on disk and in the command line as possible.

This analysis walks the full chain stage by stage, lists every published indicator, and ships paired KQL and Splunk detections plus a hunting YARA rule. The C2 was already inactive when the sample was studied, so the final payload could not be retrieved — treat the loader as the deliverable and the stealer-class payload as unconfirmed.

◈ Table of Contents

01 Malware Profile 02 Delivery & Lure 03 Initial Access — Stage 1 04 Technical Deep Dive — Stages 2–4 05 PNG Steganography Mechanics 06 Persistence & Execution 07 Indicators of Compromise 08 Detection & Hunt Queries 09 MITRE ATT&CK Mapping 10 Mitigation & Hardening 11 Sources & References
🧬

01 · Malware Profile

PHASE 01

LausivLoader is a loader family — its job is to stage and execute a follow-on payload, not to be the payload itself. Several VirusTotal engines tagged the initial script with the LausivLoader family name, which is where the public name comes from. The chain below is what a single detonation actually produced.

AttributeDetail
FamilyLausivLoader (loader / staged downloader)
ClassificationMulti-stage in-memory loader Severity: HIGH
First public analysis2026-09-17 — SANS ISC diary #33348 (Jan Kopriva)
Sample surfacedEnd of August 2026, mail-gateway quarantine
Detection at capture28 / 55 on VirusTotal
DeliveryMalspam — fiber-optic price-quotation lure, impersonating a legitimate company's employee
Stages observed4 — WSH JavaScript → PowerShell → .NET loader → .NET downloader
Final payloadUnconfirmed — C2 inactive at analysis; loader delivers a stealer-class .NET assembly
AttributionNone published — no named actor or crew
Notable TTPsEnv-var stage handoff, in-memory AMSI patching, RC4 + AES-128-CBC layering, PNG iTXt steganography, process hollowing

A "loader" is worth documenting even when its payload is unknown. The staging techniques — env-var handoff, AMSI patch, stego pull — are reusable across whatever the operator drops next week. Detect the loader and you catch every payload it was ever going to carry.

📧

02 · Delivery & Lure

PHASE 02

The delivery is deliberately mundane. A spearphishing message impersonated an employee of a legitimate company and asked the recipient to review attached requirements and return a price quotation for a fiber-optic system. Business-to-business procurement lures like this land in the inboxes of sales, estimating and procurement staff who are paid to open attachments from strangers.

L1
The attachment container
T1566.001
The malicious script arrived inside an .r01 archive — a split-RAR volume extension. Non-standard archive extensions serve two purposes: they defeat naive gateway rules that only inspect .zip and .rar, and they force the victim to have an archiver like WinRAR or 7-Zip installed, quietly filtering for a business-desktop environment.
Observed delivery facts
  • 1
    Inner file: PO.4843293191 For Supply Chain - Imports HM..js — a purchase-order-themed name with a double extension psychology (the .js is the real one).
  • 2
    Script size roughly 613 KB — unusually large for a first-stage dropper, padded with roughly 450 lines of comment noise.
  • 3
    Double-clicking the .js hands execution to Windows Script Host (wscript.exe / cscript.exe), which runs JScript with no macro warning and no Mark-of-the-Web prompt once extracted.

A 600 KB "purchase order" that is actually a .js file is the entire attack in one sentence. If your mail gateway lets Windows Script Host files through inside nested archives, this chain never needs a zero-day.

🚪

03 · Initial Access — Stage 1 (WSH JavaScript)

PHASE 03

Stage 1 is the most novel part of the whole chain. Rather than build a PowerShell command line and pass the payload on it — where EDR and command-line logging would capture it — the JavaScript writes its payload to disk in fragments and passes only the file paths to the next stage through process environment variables. The payload never appears on a command line.

S1
Fragment, stash, and hand off
T1059.007
Under 450 comment lines of noise, the JScript builds a working directory and splits its embedded payload into two files, then sets two environment variables in the process it is about to launch. The child PowerShell reads those variables to find its own input — a clean, log-quiet handoff.
Stage 1 behaviour
  • 1
    Creates a temporary directory under %TEMP% whose name is derived from a random number plus a timestamp, encoded in base-36 — so the folder name is different on every host and every run.
  • 2
    Writes two payload fragments to files whose names end in a and b.
  • 3
    Sets two environment variables — Kv7408 and Kv562 — to the full paths of those two fragment files.
  • 4
    Launches PowerShell. Because environment variables are inherited by child processes, the PowerShell stage reads Kv7408 and Kv562 to locate its input without ever receiving it as an argument.
Observed Stage 1 handoff logic (reconstructed)
// working dir: random int + timestamp, base-36 encoded var d = fso.GetSpecialFolder(2) + "\\" + (rnd + ts).toString(36); fso.CreateFolder(d); // two fragments written; names end in 'a' and 'b' writeFile(d + "\\...a", fragmentA); writeFile(d + "\\...b", fragmentB); // hand the PATHS (not the payload) to the child via env vars env("Kv7408") = d + "\\...a"; env("Kv562") = d + "\\...b"; shell.Run("powershell ...", 0, false);

Why bother? Command-line arguments are logged by Sysmon EID 1, Windows 4688 and every EDR. Environment variables of a child process are far less commonly captured. The payload location travels through a blind spot.

🔬

04 · Technical Deep Dive — Stages 2–4

PHASE 04

The remaining three stages are a study in layered unpacking: PowerShell reassembles and decrypts the first .NET assembly, that assembly disables AMSI and RC4-decrypts a downloader, and the downloader pulls the final stage out of a PNG. Each layer uses a different algorithm so a single decryptor never reveals the whole chain.

S2
PowerShell — reassemble, decrypt, reflect
T1059.001
The PowerShell stage reads the two fragment paths from Kv7408 and Kv562, concatenates the files, Base64-decodes the result, then decrypts it with AES-128-CBC and PKCS#7 padding using a hardcoded key and IV. The plaintext is GZip-compressed; after GZipStream decompression it is a .NET assembly, loaded reflectively in-memory with no file written to disk.
Hardcoded AES-128-CBC parameters (as published)
# Key (16 bytes) Key = 0xC9,0xE0,0xBF,0x98,0xDC,0x0E,0xC6,0x9C,0x8D,0x66,0x15,0x17,0x45,0x0E,0xC6,0xC9 # IV (16 bytes) IV = 0x4A,0x58,0x21,0xE3,0x29,0x41,0xDF,0xE5,0x19,0x8F,0xCE,0x68,0xEC,0x8A,0x06,0x2C # pipeline: read Kv7408 + Kv562 -> concat -> FromBase64 -> AES-CBC decrypt -> GZip -> [Reflection.Assembly]::Load

The stage typically runs under conhost.exe-hosted PowerShell. A wscript.exe parent spawning powershell.exe that then never touches a script file on disk is the highest-signal behavioural tell in the whole chain.

S3
.NET Loader #1 — AMSI patch + every-fifth-byte + RC4
T1685 · T1140
The first .NET assembly (315,904 bytes) exists to blind runtime scanning and unpack the next stage. It patches the two Antimalware Scan Interface entry points in the current process, then reconstructs an embedded blob using an unusual byte-stride trick before RC4-decrypting it.
Loader #1 behaviour
  • 1
    AMSI patch: both AmsiScanBuffer and AmsiScanString are patched in the current process so subsequent script and .NET content is never submitted to the antimalware scan interface.
  • 2
    Byte-stride extraction: from an embedded array the loader keeps only bytes at offsets 0, 5, 10, 15… — every fifth byte — and discards the rest. The four junk bytes between each real byte defeat signatures and confuse casual carving.
  • 3
    RC4: the reconstructed buffer is RC4-decrypted to produce the next .NET stage.
Every-fifth-byte reconstruction (observed logic)
// keep offsets 0,5,10,... drop 4 junk bytes between each real byte var real = []; for (var i = 0; i < blob.length; i += 5) real.push(blob[i]); // then RC4-decrypt the reconstructed buffer -> .NET Downloader #2 payload = RC4(real, key);

Patching AmsiScanBuffer/AmsiScanString in-process means script-based AMSI telemetry goes dark for everything that runs after Loader #1. Do not rely on AMSI event volume alone to tell you a host is clean once this stage has executed.

S4
.NET Downloader #2 — pull the PNG, extract, execute
T1620 · T1055.012
The second .NET assembly (59,904 bytes) is the network stage. It requests a PNG image from the C2, locates the hidden payload inside a specific PNG chunk, decodes it, and executes the result either by reflective load or by process hollowing. The PNG mechanics get their own phase below.
Downloader #2 behaviour
  • 1
    HTTP GET a PNG from the C2 — observed URL hxxps://yapw[.]life/phpt/stego_zrgaixkku8.png (inactive at analysis).
  • 2
    Parse the PNG for an iTXt chunk and locate the embedded payload using the byte marker FF 89 AD 4A.
  • 3
    XOR-decrypt the extracted bytes, then DEFLATE-decompress them into the final assembly / executable.
  • 4
    Execute — for a .NET assembly, reflective load in-process (T1620); for a native PE, process hollowing into a spawned host process (T1055.012).

Because the final stage is pulled from the C2 at runtime, the operator can swap it at will. Two victims hit an hour apart may run entirely different payloads from the same loader chain — attribution by final payload is unreliable here.

LAUSIVLOADER KILL CHAIN — FOUR STAGES PLUS A LOGON-TRIGGERED PERSISTENCE LOOP
🖼️

05 · PNG Steganography Mechanics

PHASE 05

Hiding a payload in an image is not new, but LausivLoader does it cleanly: it abuses a fully legitimate, spec-compliant PNG chunk rather than tampering with pixel data. A PNG is a signature byte sequence followed by a series of typed chunks. The iTXt chunk exists to hold international (UTF-8) textual metadata — a natural place to smuggle a large Base64/binary blob without corrupting the image, so it still renders normally if a curious analyst opens it.

ElementValue / Behaviour
CarrierPNG image fetched from C2 (stego_zrgaixkku8.png)
Hiding locationiTXt chunk (legitimate PNG textual-metadata chunk)
Payload markerByte sequence FF 89 AD 4A marks the start of the embedded data
Layer 1 decodeXOR decryption of the extracted bytes
Layer 2 decodeDEFLATE decompression → final .NET assembly / PE
Image integrityRenders normally — pixel data untouched
S5
Carving the payload out of the image
T1027.003
The extraction is a four-step routine inside Downloader #2. It never touches pixel data, so the PNG opens cleanly in any viewer — the payload rides entirely in a metadata chunk that most tooling ignores.
Extraction routine
  • 1
    Walk the PNG chunk list and locate the iTXt (international textual data) chunk.
  • 2
    Scan for the start marker FF 89 AD 4A; the embedded payload begins immediately after it.
  • 3
    XOR-decrypt the extracted byte range with the embedded key.
  • 4
    DEFLATE-inflate the result into the final .NET assembly or native PE, then execute it.
PNG iTXt payload extraction (observed logic)
# 1. find the iTXt chunk inside the PNG chunk = find_png_chunk(png_bytes, "iTXt") # 2. seek the payload start marker start = index_of(chunk, bytes("FF 89 AD 4A")) blob = chunk[start + 4:] # 3. XOR then DEFLATE-inflate to the final stage stage = inflate( xor(blob, key) ) # 4. reflective-load (.NET) or hollow a host process (native PE)

Network defenders often whitelist image downloads. A PNG pulled from a freshly-registered domain, over TLS, by a short-lived .NET process with no browser involved is the anomaly — not the image itself. Focus on the requester and the domain age, not the file type.

🔍
SANS ISC — LausivLoader analysis (Jan Kopriva)
Original screenshots of the PNG chunk carving and stage decompilation
VIEW SOURCE →
⚓

06 · Persistence & Execution

PHASE 06

LausivLoader keeps its foothold with a script-on-disk plus a scheduled task, disguised to blend into the noise of a normal Windows update surface.

P1
Script drop + logon-triggered scheduled task
T1053.005
The loader plants a JavaScript file in a Microsoft-branded path and registers a scheduled task that re-launches it at every logon through Windows Script Host in silent mode.
MechanismValue
Persistence script%LOCALAPPDATA%\Microsoft\PhotoEngine\PhotoStudio.js
Scheduled task name\MicrosoftEdgeUpdateTaskCore
TriggerAt logon
Actionwscript.exe //B //Nologo <script path>

The task name MicrosoftEdgeUpdateTaskCore deliberately mimics the genuine Edge updater task. Genuine Edge update tasks live under \MicrosoftEdgeUpdateTaskMachine* and run MicrosoftEdgeUpdate.exe — never wscript.exe. The action, not the name, is the tell.

//B (batch mode, suppress errors) plus //Nologo means the persistence script runs completely silently at every logon. There is no window, no error dialog — only the scheduled-task registration and the .js file betray it.

🧾

07 · Indicators of Compromise

PHASE 07

All indicators below are published in the SANS ISC analysis. Hashes cover the three recovered stages; the C2 URL was inactive when the sample was examined but remains a valid retro-hunt indicator.

File hashes
StageTypeHash
Stage 1 — JSMD57acd5c5f1689332615c03357e143f51e
Stage 1 — JSSHA-256408b2df6e81824fa5bdf4f0fbd185a7e6db06e2be98fbeebce416f66954b9fa9
.NET Loader #1MD55d92d1fb5d5fbd79a588f22e994a4aff
.NET Loader #1SHA-256e4130bf8769a50106a963b6a43dfd4fe5b56c70eae76a0de971d25993159acfe
.NET Downloader #2MD5f351968c76eefc80d4e292a3f179b7b9
.NET Downloader #2SHA-256be73e8b06c4356b5b4644d69b4f426bb3b32b4bf9f14cc5743f17532799f760b
Network & host indicators
TypeIndicator
C2 URLhxxps://yapw[.]life/phpt/stego_zrgaixkku8.png
C2 domainyapw[.]life
Persistence file%LOCALAPPDATA%\Microsoft\PhotoEngine\PhotoStudio.js
Scheduled task\MicrosoftEdgeUpdateTaskCore
Env vars (handoff)Kv7408, Kv562
Delivery archive.r01 (split-RAR) containing a .js
Lure filenamePO.4843293191 For Supply Chain - Imports HM..js
Stego markerFF 89 AD 4A (inside PNG iTXt chunk)
YARA — LausivLoader Stage-1 / stego marker (hunting)
rule LausivLoader_Stage1_and_Stego { meta: description = "LausivLoader WSH stage-1 env-var handoff and PNG iTXt marker" reference = "SANS ISC diary 33348" date = "2026-09-21" strings: $env1 = "Kv7408" ascii wide $env2 = "Kv562" ascii wide $task = "MicrosoftEdgeUpdateTaskCore" ascii wide $path = "PhotoEngine\\PhotoStudio.js" ascii wide $mark = { FF 89 AD 4A } condition: 2 of ($env1,$env2,$task,$path) or $mark }

Treat these as starting points, not a complete signature set. The C2 domain, folder names and env-var names are trivially rotated between campaigns; the behavioural detections in the next phase age far better than any single IOC.

📡

08 · Detection & Hunt Queries

PHASE 08

Behavioural detections beat static IOCs for a loader that rotates its infrastructure. Every KQL block below is paired with a Splunk equivalent. Each query leads with what it finds.

DF
First-responder triage — what to pull, in order
DFIR
If a host trips any indicator, work these steps before wiping — the loader leaves durable artifacts even after the C2 goes dark, and the order below preserves the volatile evidence first.
Triage sequence
  • 1
    Capture running processes and command lines. Look for a wscript.exe→powershell.exe lineage and any short-lived .NET process holding a network socket.
  • 2
    Enumerate scheduled tasks; export the XML for \MicrosoftEdgeUpdateTaskCore and any task whose action is a script host.
  • 3
    Collect %LOCALAPPDATA%\Microsoft\PhotoEngine\PhotoStudio.js and any base-36-named %TEMP% folder containing paired files ending in a / b.
  • 4
    Pull proxy/DNS logs for yapw[.]life and for any non-browser process fetching a .png over TLS. Note the fetch time — that is your stego-pull timestamp.
  • 5
    Hash the recovered stages and pivot on the SHA-256 values in the IOC table across the fleet.
Evidence checklist
  • Process tree screenshot showing WSH → PowerShell handoff
  • Scheduled-task XML export (MicrosoftEdgeUpdateTaskCore)
  • PhotoStudio.js + %TEMP% fragment files (a / b)
  • Proxy/DNS records for the C2 domain and any .png pulls
DETECTS: Windows Script Host (wscript/cscript) spawning PowerShell — the core LausivLoader handoff.
KQL — Microsoft Defender / Sentinel
DeviceProcessEvents | where InitiatingProcessFileName in~ ("wscript.exe","cscript.exe") | where FileName in~ ("powershell.exe","pwsh.exe") | project Timestamp, DeviceName, InitiatingProcessCommandLine, ProcessCommandLine, AccountName | order by Timestamp desc
SPL — Splunk
index=winsec EventCode=1 parent_process_nameIN("wscript.exe","cscript.exe") process_nameIN("powershell.exe","pwsh.exe") | table _time, host, parent_process, process, user | sort - _time
DETECTS: Creation of the impersonating scheduled task or any task whose action is wscript.exe.
KQL — Microsoft Defender / Sentinel
DeviceProcessEvents | where FileName =~ "schtasks.exe" and ProcessCommandLine has "/create" | where ProcessCommandLine has_any ("MicrosoftEdgeUpdateTaskCore","wscript","//B","//Nologo") | project Timestamp, DeviceName, AccountName, ProcessCommandLine
SPL — Splunk
index=winsec (EventCode=4698 OR (EventCode=1 process_name=schtasks.exe)) | search (TaskName="*MicrosoftEdgeUpdateTaskCore*" OR Command="*wscript*//B*") | table _time, host, user, TaskName, Command
DETECTS: The persistence artifact — a .js file under the fake PhotoEngine path, or wscript launching from LocalAppData.
KQL — Microsoft Defender / Sentinel
DeviceFileEvents | where FolderPath has @"\Microsoft\PhotoEngine\" or FileName =~ "PhotoStudio.js" | union (DeviceProcessEvents | where FileName in~ ("wscript.exe","cscript.exe") | where ProcessCommandLine has "AppData\\Local" and ProcessCommandLine has ".js") | project Timestamp, DeviceName, FileName, FolderPath, ProcessCommandLine
SPL — Splunk
index=winsec (EventCode=11 file_path="*\\Microsoft\\PhotoEngine\\*") OR (EventCode=1 process_nameIN("wscript.exe","cscript.exe") CommandLine="*AppData\\Local*.js*") | table _time, host, file_path, process, CommandLine
DETECTS: A short-lived .NET / script process downloading an image directly — the stego PNG pull without a browser.
KQL — Microsoft Defender / Sentinel
DeviceNetworkEvents | where RemoteUrl endswith ".png" | where InitiatingProcessFileName !in~ ("msedge.exe","chrome.exe","firefox.exe","iexplore.exe") | where InitiatingProcessFileName has_any ("powershell","wscript","cscript","rundll32","dllhost") or InitiatingProcessFileName endswith ".tmp.exe" | project Timestamp, DeviceName, RemoteUrl, InitiatingProcessFileName, InitiatingProcessCommandLine
SPL — Splunk
index=proxy url="*.png" | search NOT processIN("msedge.exe","chrome.exe","firefox.exe") processIN("powershell.exe","wscript.exe","cscript.exe","rundll32.exe") | table _time, host, url, process, user
High-signal hunt combinations
  • wscript.exe (parent) → powershell.exe (child) that reads env vars and writes no .ps1 to disk
  • schtasks /create referencing wscript with //B //Nologo
  • PhotoStudio.js written under \Microsoft\PhotoEngine\ in LocalAppData
  • Non-browser process fetching a .png over TLS from a newly-registered domain
  • Two child files in a base-36-named %TEMP% folder ending in 'a' and 'b'
🎯

09 · MITRE ATT&CK Mapping

PHASE 09

The SANS ISC analysis maps 18 techniques across the chain. The core set below covers delivery through in-memory execution and persistence.

TacticTechniqueID
Initial AccessSpearphishing AttachmentT1566.001
ExecutionJavaScript (WSH)T1059.007
ExecutionPowerShellT1059.001
PersistenceScheduled TaskT1053.005
Defense EvasionEmbedded PayloadsT1027.009
Defense EvasionDeobfuscate / Decode Files or InformationT1140
Defense EvasionReflective Code LoadingT1620
Defense EvasionImpair Defenses: Disable or Modify Tools (AMSI)T1685
Defense EvasionProcess Injection: Process HollowingT1055.012

The evasion tactic dominates this chain — six of the nine core techniques are defense-evasion. That is the signature of a loader whose entire value proposition is getting the real payload past AV and EDR untouched.

🛡️

10 · Mitigation & Hardening

PHASE 10

This chain dies at the very first stage if Windows Script Host cannot execute. Everything after that is defense-in-depth for hosts where WSH is still live.

🚫

Neuter Windows Script Host

Disable WSH via GPO or set HKLM\Software\Microsoft\Windows Script Host\Settings\Enabled = 0. Break the default .js / .jse / .vbs association so double-clicking a script opens Notepad, not wscript.

📎

Filter the container, not just the extension

Block or quarantine nested archives (.r01, split-RAR, .iso, .img) and any script file (.js, .vbs, .wsf) delivered by email. Inspect inside archives at the gateway.

🧱

ASR & script controls

Enable the Defender ASR rules blocking JS/VBS from launching downloaded content and obfuscated-script execution. Deploy AppLocker / WDAC to constrain script hosts.

📜

Full PowerShell logging

Turn on Script Block Logging and Module Logging. In-memory loaders leave their clearest trace in decoded script blocks — precisely what AMSI patching tries to silence.

🗓️

Audit scheduled tasks

Alert on task creation (EID 4698) whose action is a script host. Baseline the genuine Edge/Chrome updater tasks so imposters like MicrosoftEdgeUpdateTaskCore stand out.

🌐

Egress & domain-age control

Proxy outbound HTTP(S), block newly-registered domains, and alert when non-browser processes fetch images. The stego pull needs an outbound connection — deny it and the chain stalls at Stage 4.

Do not treat AMSI or antivirus telemetry as ground truth after Stage 1 executes — Loader #1 patches AmsiScanBuffer/AmsiScanString in-process. Corroborate with process-lineage, scheduled-task and network telemetry that the malware cannot silence from user-land.

📚

11 · Sources & References

PHASE 11
SANS Internet Storm Center — "LausivLoader analysis, or how to pass data between malware stages" (Jan Kopriva, 2026-09-17) SANS ISC diary #33348 (RSS mirror) — full IOC list, AES key/IV, ATT&CK mapping MITRE ATT&CK — T1027.009 Embedded Payloads MITRE ATT&CK — T1055.012 Process Hollowing MITRE ATT&CK — T1620 Reflective Code Loading

Hunting for staged loaders in your environment?

Drop the LausivLoader hashes and the yapw[.]life domain into the CyberHawk IOC Scanner, then work the paired KQL/SPL detections above through your SIEM. For the full library of loader and infostealer playbooks, see the Threat Intel feed and our SOC SOPs.

◈ Stay Connected

Follow CyberHawk Threat Intel for threat intelligence, deployment guides and hands-on SOC tooling content.

🌐 Website ▶️ YouTube ▶️ YouTube (2) 𝕏 Twitter / X ♪ TikTok ✈️ Telegram
🔍 IOC Scanner 🛠️ Live Tools 📚 Courses 🚨 Threat Intel 📝 Blog 📋 SOPs

"They can't exploit you if you are the Exploit."