
Auhor: Danyal Rana
SHA-256: 0e7b408f49d6eae2409b0de9a4a8881c8e9537acf1ef6b00f14c3da06ad1c49c
MD5: 4e33fc5727a687d4faddbfd50fd7e191
Malware Analysis | JavaScript → WMI → PowerShell → .NET → PE → XWorm
There is a particular kind of malware-analysis moment where you are expecting sophisticated obfuscation and instead discover that the author has apparently decided to flirt with you.
This sample was a malicious JavaScript loader weighing roughly 1.18 MB. Much of that file consisted of generated junk built around a bizarre Unicode delimiter:
The delimiter appeared repeatedly between characters of reconstructed strings, including otherwise ordinary .NET and PowerShell identifiers. The surrounding code was largely semantic noise designed to make static analysis unpleasant rather than to provide meaningful functionality.
Once the noise was stripped away, however, the sample turned out to be considerably more serious.
What follows is the complete chain uncovered during static analysis:
The initial JavaScript was heavily padded with repeated copies of the same Unicode sequence:
The obfuscator repeatedly concatenated, tested, joined and otherwise manipulated the delimiter in variables such as exageration. The important point is that most of this code did not contribute meaningful behavior; it served primarily to inflate and obscure the actual program.
The delimiter was inserted into reconstructed strings, meaning that something conceptually equivalent to:
could appear as:
This is a fairly simple obfuscation technique, but combined with thousands of lines of generated junk it makes ordinary text searches substantially less useful.
The funny part is obvious.
The analytical part is more interesting: the delimiter itself becomes an artifact for identifying and stripping the generated layer.
Buried beneath the padding was a straightforward Windows Script Host persistence mechanism.
The JavaScript creates:
retrieves WScript.ScriptFullName, obtains the user’s Startup folder through SpecialFolders("Startup"), and copies the current script there if it is not already present.
Conceptually:
This gives the initial JScript stage a persistence mechanism before the more complicated payload chain even begins.
The surviving code also exposed WMI-oriented execution logic and ultimately constructed a PowerShell command.
The PowerShell stage uses a System.Net.HttpWebRequest, retrieves a response, reads it through a StreamReader, searches the response for custom delimiters, reconstructs an encoded payload, decodes it and loads the resulting bytes into the current .NET AppDomain.
The important sequence is:
The loaded assembly is then queried for a specific obfuscated type and method before invocation.
That means the first major transition is:
JScript → PowerShell → in-memory .NET assembly
rather than a simple executable download.
The PowerShell stage contacted:
The URL suggested a PNG, but the downloaded object identified as a 1.8 MB JPEG image.
The important detail was that the image also contained an encoded payload between:
and:
The loader extracted that region, replaced * characters with A, reversed the result and Base64-decoded it.
The decoded bytes produced a 370 KB .NET assembly.
That assembly contained the reflection entry point:
The type and method are visible in the PE metadata alongside the embedded Task Scheduler implementation.
The entry point accepts 19 string parameters.
Before doing anything else, every parameter is passed through the same decryption routine using:
The assembly contains the key as ChaveSecretaGlobal, and the dispatcher applies the decryption function to all 19 values.
The decryption mechanism turned out to be:
The key construction was slightly unusual. The ASCII key occupies the beginning of the 32-byte buffer, while the remaining bytes are populated sequentially with 0x00 through 0x0f.
This is a good example of why reproducing malware crypto requires following the implementation exactly rather than assuming a conventional zero-padded key.
The 19 values were recovered offline.
The most important values were:
Several feature flags were empty rather than "1".
This matters because the dispatcher uses literal "1" comparisons to activate its optional functionality.
For this particular configuration:
The unconditional branch remained active.
That branch is the most important one.
The assembly’s _E030 routine is invoked regardless of those optional flags.
Its operation is another staged carrier decoder.
It:
WebClient.DownloadData().INICIO and FIM.### with A.This gave us a second carrier URL:
Again, the object was not a PNG.
It was a 2.1 MB JPEG, and the expected payload markers were present:
The enclosed region was 77,992 bytes.
After the malware’s own transformations:
the result was a 37,376-byte PE beginning with the expected:
signature.
The loader does not blindly execute the recovered bytes.
It reads the PE’s machine field and distinguishes between:
For PE32 samples it enters one loader path; for PE32+ samples it uses another.
The recovered payload in this investigation was:
with:
as its only imported native entry point.
That meant this specimen took the PE32-specific execution route.
Although the recovered payload was PE32, the assembly also contains a separate PE32+ loader.
That routine imports:
and creates the target process suspended before unmapping its original image, allocating memory, copying the replacement PE, modifying the thread context and resuming execution.
The implementation therefore corresponds to a classic process-hollowing style PE loader.
The presence of this routine is useful when documenting the assembly’s capabilities, but it should be kept distinct from the behavior actually exercised by the PE32 payload recovered in this sample.
The .NET assembly contains several optional persistence mechanisms even though the recovered configuration did not enable them.
VerificarMinutos() accepts six parameters:
It creates a scheduled task whose action is conhost.exe --headless launching hidden PowerShell.
The PowerShell command downloads a file with WebClient.DownloadFile() and then starts it.
The task also uses a repeating time trigger and an interactive-token logon configuration.
A second helper copies a local file and registers a recurring task running:
against the copied payload.
Another branch copies a payload and writes:
using the value:
to establish user-level persistence.
These should be treated as available capabilities rather than claiming that every one was enabled in this particular configuration.
The assembly also includes an anti-analysis branch.
One component gathers:
and compares the system against detection rules.
Another check enumerates enabled network interfaces and explicitly compares MAC addresses against:
A matching environment causes the process to exit.
Again, the decrypted configuration had this branch disabled in the analyzed specimen, but the capability is clearly present in the assembly.
The assembly also contains a separate elevation component.
A helper first checks:
It then contains an embedded PowerShell/C# routine that generates an INF file and invokes:
with /au.
The embedded default command attempts to modify:
and therefore disable UAC. The routine also hides the process, interacts with a window named CorpVPN, and removes the temporary INF afterward.
The important analytical distinction is:
the sample contains the elevation mechanism, but the recovered configuration did not enable the corresponding branch.
The final 37 KB PE is a PE32 .NET assembly.
Its static metadata showed:
It also contained the highly revealing string:
alongside strings such as:
That identifies the final payload as an XWorm client, rather than merely a generic .NET executable.
At this point, the chain is no longer speculative:
Stage 1 JPEG carrier
Stage 2 JPEG carrier
I would verify the second hash from your terminal before publishing it; the analysis log supplied here records the Stage 2 hash as:
and I don’t want to invent a character in an IOC.
Final XWorm PE
The PE was a 37 KB unsigned PE32 .NET executable.
This sample is a good illustration of why malware analysis rarely ends with the first suspicious primitive you find.
At first glance, the JavaScript looked like enormous Unicode garbage. Beneath that was WSH persistence and a PowerShell execution layer. The PowerShell stage then retrieved a legitimate-looking image carrier containing an encoded .NET assembly. That assembly decrypted its own configuration, selected functionality through feature flags, and made a second request for another image carrier. The second carrier ultimately yielded a PE32 .NET executable identified as an XWorm client.
The assembly also carried a much broader toolkit than the active configuration required: scheduled-task persistence, Run-key persistence, environment checks, virtualization detection, a CMSTP-based elevation component and architecture-specific PE loading code.
That distinction matters.
Malware often contains capabilities that are not active in every campaign or configuration. Treating every embedded capability as confirmed runtime behavior can turn an otherwise solid analysis into an inaccurate one.
In this specimen, the strongest confirmed execution path is:
And somewhere in that chain, the author decided that the best possible obfuscation delimiter was:
Cybersecurity remains a very serious field.
Apparently the malware authors did not get the memo. 💋
Further analysis of the final-stage payload identified it as an XWorm V7.1 client, substantially expanding the understanding of what the infection ultimately delivers. The recovered configuration contains an encrypted TCP C2 setup pointing to 194.116.236.54:2467, alongside a dedicated mutex (jkqRGXU87KMxJ5rD) and additional encrypted configuration values used by the client. The configuration and communications routines use Rijndael/AES-based encryption, with the client deriving its cryptographic material from embedded configuration values and the configured mutex.
The C2 client implements a broad command-dispatch system. Confirmed functionality includes remote shell and PowerShell execution, downloading and executing files, in-memory payload loading, plugin installation/removal, system shutdown/restart/logoff, hosts-file modification, and DDoS functionality. The plugin mechanism also allows additional payloads to be transferred and loaded dynamically, extending the capabilities of the base client after deployment.
The payload also provides several surveillance and collection capabilities. Static analysis confirmed full-screen capture, webcam functionality and low-level keyboard logging, with the keyboard logger using a Windows low-level keyboard hook and keyboard-layout APIs to reconstruct keystrokes.
Defense evasion is also present. During startup, the client launches PowerShell with -ExecutionPolicy Bypass to add Microsoft Defender exclusions for both its working directory and its own executable process. The client additionally creates a named mutex to prevent multiple instances from running simultaneously.
Taken together, the final payload is a fully featured RAT rather than a simple downloader, providing remote command execution, payload delivery, plugin-based extension, surveillance, host manipulation, system-control functionality and defense evasion. Combined with the earlier loader stages, this demonstrates an infection chain progressing from heavily obfuscated JScript through PowerShell and an in-memory .NET loader to a configurable XWorm client with direct operator-controlled capabilities.