When Malware Uses a Kissing Emoji: Dissecting a Multi-Stage XWorm Loader

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:

 
Malicious JScript
│
├── Startup-folder persistence
│
└── WMI → PowerShell
│
▼
JPEG carrier #1
│
encoded .NET assembly
│
▼
reflection
│
▼
configuration decode
│
▼
second JPEG carrier
│
encoded Windows PE
│
▼
XWorm
 

1. The first layer: 1.18 MB of Unicode nonsense

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:

System.Net.HttpWebRequest

could appear as:

S💋y💋s💋t💋e💋m💋.💋N💋e💋t💋.💋H💋t💋t💋p…

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.

2. Initial persistence: copying itself into Startup

Buried beneath the padding was a straightforward Windows Script Host persistence mechanism.

The JavaScript creates:

new ActiveXObject(“Scripting.FileSystemObject”)
new ActiveXObject(“WScript.Shell”)
 

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:

Current script
│
▼
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup
│
└── same filename
 

This gives the initial JScript stage a persistence mechanism before the more complicated payload chain even begins.

3. The JScript launches a PowerShell stage

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:

HTTP request
↓
GetResponse()
↓
GetResponseStream()
↓
StreamReader.ReadToEnd()
↓
extract encoded material
↓
Base64 → byte[]
↓
AppDomain.Load()
↓
reflection

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.

4. The first image carrier

The PowerShell stage contacted:

https://sunix-technology.com/images/MSI_PRO.png

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:

IN-
 

and:

 
-in1

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:

OtnmpxnddVnptbN.mpxnddVn
└── Otnmpxn(…)

The type and method are visible in the PE metadata alongside the embedded Task Scheduler implementation.

5. The .NET stage: encrypted configuration

The entry point accepts 19 string parameters.

Before doing anything else, every parameter is passed through the same decryption routine using:

tQaCQYKIo1FdSsme

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:

  • AES
  • CBC mode
  • PKCS#7 padding
  • a 32-byte key
  • the first 16 bytes of each encrypted value used as the IV
  • the remaining bytes used as ciphertext

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.

6. The decrypted configuration reveals the actual behavior

The 19 values were recovered offline.

The most important values were:

00 https://sunix-technology.com/images/litxwnimg_082224.png
02 C:\\Users\\Public\\Downloads\\
03 z0afiE1JUW
04 Caspol
06 Caspol
08 URL
09 C:\\Users\\Public\\Downloads\\
10 z0afiE1JUW
11 js
12 5
14 oFQ8HRZUVS
15 0

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:

Scheduled downloader branch disabled
Second scheduled-task branch disabled
Run-key branch disabled
Environment-check branch disabled
UAC branch disabled

The unconditional branch remained active.

That branch is the most important one.

7. The unconditional loader

The assembly’s _E030 routine is invoked regardless of those optional flags.

Its operation is another staged carrier decoder.

It:

  1. Downloads a remote object with WebClient.DownloadData().
  2. Decodes the response as UTF-8.
  3. Searches for INICIO and FIM.
  4. Extracts the material between them.
  5. Replaces ### with A.
  6. Reverses the string.
  7. Determines whether it represents hexadecimal or Base64.
  8. Converts it to raw PE bytes.
  9. Reads the PE architecture.
  10. Selects an architecture-specific execution routine.

This gave us a second carrier URL:

https://sunix-technology.com/images/litxwnimg_082224.png

Again, the object was not a PNG.

It was a 2.1 MB JPEG, and the expected payload markers were present:

INICIO @ 2074229
FIM @ 2152227

The enclosed region was 77,992 bytes.

After the malware’s own transformations:

### → A
reverse
Base64 decode

the result was a 37,376-byte PE beginning with the expected:

4D 5A

signature.

8. Architecture-aware execution

The loader does not blindly execute the recovered bytes.

It reads the PE’s machine field and distinguishes between:

267 = PE32
523 = PE32+

For PE32 samples it enters one loader path; for PE32+ samples it uses another.

The recovered payload in this investigation was:

PE32
i386
.NET / CIL
GUI subsystem
37,376 bytes
 

with:

mscoree.dll → _CorExeMain

as its only imported native entry point.

That meant this specimen took the PE32-specific execution route.

9. The alternate PE32+ path is process hollowing

Although the recovered payload was PE32, the assembly also contains a separate PE32+ loader.

That routine imports:

CreateProcess
VirtualAllocEx
WriteProcessMemory
ZwUnmapViewOfSection
GetThreadContext
SetThreadContext
ResumeThread
CloseHandle

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.

10. Additional persistence mechanisms

The .NET assembly contains several optional persistence mechanisms even though the recovered configuration did not enable them.

Scheduled task + PowerShell downloader

VerificarMinutos() accepts six parameters:

interval
URL
destination directory
filename
extension
task name

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.

Another scheduled-task mechanism

A second helper copies a local file and registers a recurring task running:

wscript.exe

against the copied payload.

Registry Run key

Another branch copies a payload and writes:

HKCU\Software\Microsoft\Windows\CurrentVersion\Run

using the value:

Path

to establish user-level persistence.

These should be treated as available capabilities rather than claiming that every one was enabled in this particular configuration.

11. Environment and virtualization checks

The assembly also includes an anti-analysis branch.

One component gathers:

Win32_ComputerSystem
Win32_BIOS
Win32_MotherboardDevice
Win32_PnPEntity
Win32_DiskDrive
Windows services
running processes

and compares the system against detection rules.

Another check enumerates enabled network interfaces and explicitly compares MAC addresses against:

52:54:00:4A:04:AF

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.

12. UAC/elevation functionality

The assembly also contains a separate elevation component.

A helper first checks:

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
EnableLUA
ConsentPromptBehaviorAdmin

It then contains an embedded PowerShell/C# routine that generates an INF file and invokes:

C:\Windows\System32\cmstp.exe

with /au.

The embedded default command attempts to modify:

EnableLUA = 0

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.

13. Final payload: XWorm

The final 37 KB PE is a PE32 .NET assembly.

Its static metadata showed:

arch cil
bintype pe
bits 32
machine i386
lang cil
subsys dotnet

It also contained the highly revealing string:

XWormClient DJ AUGUST.exe

alongside strings such as:

HttpWebRequest
HttpWebResponse
avicap32.dll
user32.dll
kernel32.dll
SHCore.dll
System.Web.Services.Protocols.SoapHttpClientProtocol

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:

Malicious JScript
↓
Startup persistence
↓
WMI / PowerShell
↓
JPEG carrier
↓
AES-protected configuration
↓
in-memory .NET assembly
↓
second JPEG carrier
↓
encoded PE
↓
XWorm client
 

14. Indicators of compromise

Network

https://sunix-technology.com/images/MSI_PRO.png
https://sunix-technology.com/images/litxwnimg_082224.png

Payload hashes

Stage 1 JPEG carrier

SHA-256:
d30f7774ae0229e04be50ac137ed9b03bb08b1c797694e711c4f7ef2033358c2

Stage 2 JPEG carrier

SHA-256:
1987c108f7ecbaecf700417f0f9d6915962df2fc?

I would verify the second hash from your terminal before publishing it; the analysis log supplied here records the Stage 2 hash as:

1987c108f7ecbaecf700417f0f9d6915962df2df?

and I don’t want to invent a character in an IOC.

Final XWorm PE

SHA-256:
d3fed1cab9b8906608095b2a0605b4b2a689d252e2f18139797995f3cd77d2e9
 

The PE was a 37 KB unsigned PE32 .NET executable.

15. Final assessment

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:

JScript
→ WMI / PowerShell
→ JPEG carrier
→ in-memory .NET assembly
→ AES configuration
→ second JPEG carrier
→ encoded PE32
→ XWorm

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. 💋

Updated Findings

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.