Go back

From Business Emails to XLoader: An Order Lure That Led to In-Memory Injectionย 

Shmuel Uzan
Shmuel Uzan
01 Nov 2026
17 min read
Threat Research

Introduction 

In recent months, Morphisec has observed increased XLoader activity using a range of business-themed email lures.  This investigation focuses on a distinct execution chain: an archive containing a single obfuscated JScript that embeds an x86 LuaJIT interpreter, a Lua loader, and an encoded native payload. The order and payment messages shown here illustrate three of the email lures observed in this activity. 

Beyond the chain analyzed here, the campaign also delivered several other payloads, including Agent Tesla, Remcos, and XWorm. 

Tracing the embedded components shows where execution remains local and where it crosses into another process. The Lua loader starts a packed PE on a thread within LuaJIT; the packer then decrypts and calls the XLoader core on that same thread. The final handoff uses a shared section to map a clone of the XLoader stage into a selected x86 process, then redirects a threadโ€™s execution context to the mapped code. 

Technical Analysis 

The chain begins with a Greek-language email presented as a request to confirm order 001828. Its subject starts with RE:, suggesting an ongoing exchange, and the short message asks the recipient to review the attached order and reply as soon as possible. This familiar business request gives the recipient a reason to open the archive without a lengthy or elaborate pretext. The message is addressed to โ€œundisclosed-recipients,โ€ so the capture does not identify a specific intended organization. Its RAR attachment repeats the order number, but contains a single obfuscated JScript rather than an order document.ย 

Figure 1. Greek-language order confirmation email. The attached RAR is presented as an order but contains the JScript that starts the XLoader chain

Figure 2. Slovenian-language order email with a RAR attachment, requesting a price quote and invoice to proceed with payment.

Figure 3. Quotation-themed email with two attachments, including a RAR archive, requesting the recipientโ€™s best price and availability while emphasizing an urgent item.

When Windows Script Host runs the JScript, it extracts an x86 LuaJIT interpreter and an extensionless Lua program from embedded data, writes both to the userโ€™s Documents directory, and starts the interpreter with the Lua file as its input. The Lua program attempts process-local security interference, decodes a packed PE, and manually maps it into LuaJIT. Although the mapper calls CreateRemoteThread, it passes GetCurrentProcess(), so this handoff starts a thread inside LuaJIT. The packed PE then decrypts the XLoader core and calls it on the same thread. 

XLoader prepares a clone of its stage and selects an x86 target process. It maps a shared section into both processes, sets the controlled threadโ€™s context to enter the remote view, copies the clone through the local view, and resumes the thread. This is the chainโ€™s cross-process transfer. The archive, LuaJIT executable, and Lua source are disk components; the packed PE, decrypted core, and transferred clone are memory stages. 

 

The JScript Installs Its Own Runtime – (ฮ ฮฑฯฮฑฮณฮณฮตฮปฮฏ623768193.JS) 

The JScript is the first executable stage inside the delivered archive. Despite its size, its active path has a focused purpose: recover its operational strings, extract two embedded components, and launch the next stage. A rotated string table hides the COM objects, methods, and paths used by that sequence. Large encoded literals, unused filler, and anti-analysis checks make the installer harder to inspect without adding another execution stage. 

Figure 4. Two JScript obfuscation examples: decorative comments and filler text (left), and wrapped functions with obfuscated identifiers (right).

When Windows Script Host runs the file, the script first attempts to overwrite its own Zone.Identifier stream with a blank line. It then reverses and cleans two encoded literals and uses Microsoft.XMLDOM to decode their Base64 content. Binary ADODB.Stream writes place an x86 LuaJIT-compatible executable and an extensionless Lua program in the userโ€™s Documents directory. Both components are already present in the JScript; this stage does not retrieve them from a server. 

The script checks whether the Lua file exists before writing either component, but its final launch command sits outside that condition. It starts the LuaJIT executable hidden, passing the extensionless file as its input without waiting for the process to finish. LuaJIT is therefore the interpreter that runs the next stage, while the Lua program supplies the logic that decodes and enters the native payload.

Figure 5. The JScript attempts to clear Zone.Identifier, drops the embedded LuaJIT and Lua loader, and launches LuaJIT hidden.

The LuaJIT Executable and Its Lua Script 

The JScript writes two components to the userโ€™s Documents directory: EHFTUOMQDRXVTJSF.exe, an x86 LuaJIT-compatible interpreter, and FSJTVXRDQMOUTFHE, an extensionless Lua source file. The JScript launches the executable with the Lua file as its argument. This relationship matters because the executable provides the process in which the next stage runs; the Lua file contains the logic that advances the chain. 

The supplied executable exposes LuaJIT 2.1 and Lua 5.1 interfaces and imports functions used for loading libraries, allocating memory, changing page protections, and managing threads. Those properties are consistent with its interpreter role. They do not establish that the attacker supplied an untouched official LuaJIT build. The executableโ€™s role is to host the Lua program under an attacker-chosen filename in the userโ€™s Documents directory. 

The Lua source uses FFI to access Windows APIs from inside that process. Its active path hides the console, attempts to interfere with selected security-related functions, checks a conditional persistence branch, decodes an embedded PE, and maps that PE into LuaJITโ€™s memory. Much of the sourceโ€™s size comes from repeated, unreferenced blocks; following the active calls reveals the sequence that leads to native execution. 

Inside the Lua Loader (FSJTVXRDQMOUTFHE) 

The extensionless FSJTVXRDQMOUTFHE file is Lua source executed by the supplied LuaJIT interpreter. Although it contains large repeated blocks resembling additional logic, the active path does not call them. The loader uses LuaJITโ€™s FFI to access Windows APIs, modify memory in its own process, decode an embedded PE, and start that PE from memory.

Figure 6. Reconstructed execution sequence of the Lua loader inside the supplied LuaJIT process.

The loader first hides the console window, then attempts two forms of process-local interference. One routine scans loaded CLR memory for AmsiScanBuffer and AmsiInitialize and attempts to zero matching strings. Another resolves 12 exports from the processโ€™s mapped ntdll: EtwEventWrite, EtwEventWriteFull, EtwEventWriteTransfer, EtwEventWriteString, NtTraceEvent, EtwEventEnabled, NtOpenThread, NtSuspendThread, NtTerminateThread, NtTerminateProcess, NtSuspendProcess, and NtCreateSection. For each export it finds, the wrapper attempts to replace its first 16 bytes with a RET instruction followed by 15 NOP bytes. The routines skip missing modules and exports and do not require every modification to succeed. Their code establishes the loaderโ€™s intent; successful patching on an affected host was not observed. 

A separate persistence branch checks for a .mui marker beside the LuaJIT executable. When that marker exists, the loader can write a VBS launcher, check for a scheduled task named EHFTUOMQDRXVTJSF, and create one that invokes the launcher every ten minutes: 

schtasks /query /tn “EHFTUOMQDRXVTJSF” 

schtasks /create /sc minute /mo 10 /tn “EHFTUOMQDRXVTJSF” /tr “wscript.exe \”<VBS path>\”” /ru “<USERNAME>” /f /it 

The expected first-run JScript path does not create the marker. These commands therefore document a conditional persistence capability, not a task confirmed to have been created during the analyzed chain. 

The loader then decodes a PE embedded in its Lua source and copies its headers and sections into executable memory. Its allocation, write, and thread-creation calls all receive the current LuaJIT process handle. The resulting thread starts the packed PE inside LuaJIT; the decoded XLoader core performs the later transfer into another process. 

Recovering the PE Embedded in Lua 

The Lua source contains a 382,982-byte encoded literal beginning with the two-character prefix |K. The K selects row 20 of the decoderโ€™s substitution table (the 21st row when counted from one), which maps @#$%^&*()_ to ABCDEFabcd. The decoder removes the prefix, applies that substitution to the remaining 382,980 bytes, and reverses the result. A custom Base64 routine then decodes the text, ignoring characters outside its alphabet and padding.

Figure 7: The Lua source embeds the encoded PE as a long string beginning with |K. The K selects the substitution-table row used in the first decoding step.

After Base64 decoding, the loader applies a character rotation and removes a leading control byte, revealing a packed PE32 image. It then maps that image into the LuaJIT process.

The Importless Native Packer 

The PE recovered from Lua is a 32-bit image with one high-entropy .text section and no declared imports. Its entry point at RVA 0x26F0, named packed_loader_entry during analysis, drives loader_state_dispatcher at RVA 0x10A0. The dispatcher locates a shared stack frame using the sentinel 0x65B6B058. A call/pop sequence at 0x118F recovers the imageโ€™s current location, allowing the loader to work from the address where Lua mapped it.

The Importless Native Packer 

The PE recovered from Lua is a 32-bit image with one high-entropy .text section and no declared imports. Its entry point at RVA 0x26F0, named packed_loader_entry during analysis, drives loader_state_dispatcher at RVA 0x10A0. The dispatcher locates a shared stack frame using the sentinel 0x65B6B058. A call/pop sequence at 0x118F recovers the imageโ€™s current location, allowing the loader to work from the address where Lua mapped it.

Figure 8. The Lua-recovered PE contains a single high-entropy .text section. Its entry point is at RVA 0x26F0, and the section entropy is approximately 7.9937 bits per byte.

The packer constructs encrypted module and API names on the stack. It walks the current processโ€™s PEB to locate ntdll.dll, parses that moduleโ€™s export directory, and resolves NtAllocateVirtualMemory. This supplies the allocator without an import table or import resolution by Luaโ€™s manual mapper. The call reserves and commits 0x45031 bytes with executable, writable protection inside the LuaJIT process, after which the packer copies its source data into the allocation. 

The encrypted portion begins at allocation offset 0x1C31 and spans 0x43400 bytes. The packer applies one RC4 pass across that entire region. It then initializes RC4 separately for each of 15 consecutive 0x47BB-byte blocks using a second key. Those blocks cover all but the final 11 bytes, which retain the result of the first pass. Reinitializing the cipher for every block is essential to reproducing the recovered code. 

After decoding, the dispatcher makes a synchronous indirect call to allocation + 0x30181. That address is a short entry stub leading into the XLoader core. The packer performs this transfer on the thread Lua started inside LuaJIT; it does not select or inject a second process. 

Figure 9. The entry at 0x30181 before decoding (right) and after decoding (left), where it calls xloader_stage_main.

Inside the XLoader Core 

The decoded entry stub calls xloader_stage_main at allocation offset 0x2E781. This routine prepares the XLoader runtime context, sets its protected dispatch selector to 0xF2, and initializes the core. If it detects an existing instance, it exits. Otherwise, it passes the descriptor seed 0xC02DCD50 to decrypt_execute_reencrypt_primary_routine. The main routine therefore leads into the protected injector wrapper; the injection operations are implemented beyond this entry function. 

Figure 10. xloader_stage_main initializes the runtime, checks for an existing instance, and invokes the protected routine.ย 

The call to decrypt_execute_reencrypt_primary_routine exposes the coreโ€™s on-demand code protection. The wrapper combines the callerโ€™s descriptor seed with its own stored value to derive a 20-byte key, then locates the injector between six-byte markers at offsets 0x1844B and 0x18AA7. It decrypts the 0x656-byte body between those markers, invokes the protected function at 0x18441, and re-encrypts the region after the call. The injector is therefore available as plaintext only during this wrapperโ€™s execution.

Figure 11. The XLoader wrapper decrypts and runs the protected injector, then re-encrypts it before returning.

Shared-Section Injection 

The protected injector creates a 0x43400-byte clone of the XLoader stage and transforms its 0x1000-byte prefix before transferring it. It then selects an x86 target. One route searches for an eligible running process, making up to three attempts. The other tries to launch a SysWOW64 host with its primary thread suspended. Its 12 candidates are cleanmgr.exe, clip.exe, TSTheme.exe, getmac.exe, UserAccountControlSettings.exe, proquota.exe, SearchProtocolHost.exe, mobsync.exe, mshta.exe, ftp.exe, fsutil.exe, and find.exe. 

The injector maps a shared section into LuaJIT and the selected target. It retrieves the target threadโ€™s x86 context and sets CONTEXT.Eax to remote_view + 0xC9C1. In the existing-process route, it also suspends a selected thread and redirects its EIP through a bootstrap. The injector copies the transformed clone into its local view and resumes the thread. Because both views map the same section, the clone becomes available in the target without copying the entire stage through WriteProcessMemory or creating a remote thread. 

The bootstrap at remote_view + 0xC9C1 signals its arrival through the shared section, initializes the XLoader context in the target, and enters the protected core. XLoader then prepares its network worker and endpoint configuration, processes responses and commands, and can invoke collection routines. The recovered code includes an HTTP GET command path, Windows Vault enumeration, and cookie-record preparation. 

The targetโ€™s original image remains mapped. This mechanism is therefore shared-section injection with x86 thread-context redirection, rather than process hollowing. 

Figure 12. XLoaderโ€™s suspended-host injection sequence. The injector maps a shared section into LuaJIT and the target process, sets the target threadโ€™s EAX to the mapped bootstrap, copies the XLoader clone through the local view, and resumes the thread.

XLoader Network Configuration and Capabilities 

Zscaler ThreatLabz documented the obfuscation and network protocol used by recent XLoader builds. The stage recovered in this investigation closely matches that architecture: protected functions are decrypted when called, the code constructs the PKT2: marker and POST parameters including dat=, &un=, and &br=9, and it implements both WinINet and raw-socket transport paths. The matching implementation supports its classification as modern XLoader in the FormBook lineage. 

The sampleโ€™s encrypted configuration contains 64 hostname candidates with four-character path tags, plus one separately encrypted host. Its initializer builds 16 endpoint records using fixed selectors from the 64-entry pool, then replaces the host in the final record with www[.]990match[.]com. That record retains the selected ccp8 path tag. Unlike the random initial selection described by Zscaler, this sample starts with a fixed selector array and has separate logic to refresh five slots later. The configured hosts include decoys; the decrypted list alone does not identify which endpoint is the real C2. 

Slot, Initial configured endpoint 

1, hxxp://iqos-one[.]com/gf2n/ 

2, hxxp://offon33fv[.]club/6yav/ 

3, hxxp://eolienzaer[.]com/jrsi/ 

4, hxxp://tori[.]fi/8yfp/ 

5, hxxp://okbajegame[.]com/l3iy/ 

6, hxxp://komplek[.]id/gkws/ 

7, hxxp://sonhero[.]top/xc0y/ 

8, hxxp://grandseosite91[.]shop/1ud5/ 

9, hxxp://moreultras[.]store/2r3a/ 

10, hxxp://bagolone[.]it/qbqm/ 

11, hxxp://tomatox[.]com[.]tw/upw6/ 

12, hxxp://511mma[.]ru/dnir/ 

13, hxxp://hahashoe[.]com/r0zm/ 

14, hxxp://snvipvn[.]com/v0rc/ 

15, hxxp://revelintravel[.]com/zd56/ 

16, hxxp://www[.]990match[.]com/ccp8/ 

The recovered stage initializes a network worker and processes responses through a command dispatcher. Its command 0x34 recognizes RMTD and RMTU and reaches an HTTP GET implementation with a Winsock fallback. The code also contains Windows Vault enumeration and cookie-record preparation. These sample-specific findings complement Zscalerโ€™s broader description of XLoaderโ€™s command and information-stealing capabilities without repeating the full family analysis. 

How Morphisec can help 

This chain gives defenders several opportunities to stop XLoader before its network and collection code runs. The Lua loader manually maps a packed PE into the LuaJIT process; the packer decrypts and executes the XLoader core in memory; and the injector transfers a clone into another process through a shared executable section. Morphisec Automated Moving Target Defense (AMTD) is designed to prevent malicious in-memory execution, including shellcode and reflective injection techniques. In this case, investigation should also correlate the order-themed JScript with the dropped LuaJIT executable and Lua file, changes to ntdll exports, and a shared executable mapping followed by x86 thread-context redirection. 

Resource 

  1. https://www.zscaler.com/blogs/security-research/latest-xloader-obfuscation-methods-and-network-protocolย 

Consolidated Indicators of Compromise 

File hashes 

Indicator Type Context 
6d7aaf1e563032ba46d8e0f1c8eab2b91d383fb2e5d00a1d4fff891f78a3572b SHA-256 EML 
78f08bdefb127c96a64229b591de7b3f984a451796ad132cb26ff54c76d073d4 SHA-256 EML 
e834ea9cd3025f8070778cf7ef8219976568cc28085feaf974912b64e5b77bc8 SHA-256 RAR attached to both messages 
d447a06cba8d3dbe80e09d73dd64576836b258961ee0d3ba56cf26cea7962b49 SHA-256 Dropped LuaJIT executable 
685c989f4d89b2402c167075d5d6d6e1f4573c0450adc1de3d8c08c65ed8ee9f SHA-256 Dropped Lua loader 
d87299e4a6457caccb6960c351003cb109748be9ab378cf31611461fce30ac28 SHA-256  ฮ ฮฑฯฮฑฮณฮณฮตฮปฮฏ623768193.rar 
05ecf6621bc2f2303db6bab33cdc231cb1cd08895b4d089f7410b98b21c38c24 SHA-256   ฮ ฮฑฯฮฑฮณฮณฮตฮปฮฏ623768193.rar 
78cb203de34c4a4c1961fe0e3700e6c76ab9b8b71dae9e2de408197fbc82733e SHA-256   ฮ ฮฑฯฮฑฮณฮณฮตฮปฮฏฮฑ 00182`8.rar 
46e8d2bd454926641176ea67b2e357089ff411c89e524def0158a04b6103d7d4 SHA-256   Naroฤilo_180SW_2828-501.JS 
896a4ddd93ab1d6fda5d57e71c5b2cf5db32970093350a1111396b2e80dc6686 SHA-256   EML   
be40d790d265afba64626b4e10fa2ff06e80119eb0056974407cf7d8e834745e SHA-256     EML 
21eb09400f387066649bec4c7fa63e4d4fd2171fb21a2b2ba182941325d76205 SHA-256     ROQ_O 2828-501.zip 
38a498b126f8fc799a4f36f37b6d955fba6410d7eb02a9922eb21767897a8769 SHA-256 180SW_2828-501.rar 
33dbb7d0c78bfa2e124b78409ecc7152a80016bbcccf39e960a2790d1efb8a22 SHA-256 S0_283723919.JS 
08aadfcffb61f90b0d3b34758048e0f699f241216d164c82919a6934488a52e9   

Filenames, paths, and task 

Indicator Type Context 
ฮ ฮฑฯฮฑฮณฮณฮตฮปฮฏฮฑ 00182`8.rar Filename Attachment in both captured EMLs 
ฮ ฮฑฯฮฑฮณฮณฮตฮปฮฏฮฑ 00182`8.JS Filename Sole member of the RAR 
%MyDocuments%\EHFTUOMQDRXVTJSF.exe File path Dropped LuaJIT host 
%MyDocuments%\FSJTVXRDQMOUTFHE File path Dropped extensionless Lua loader 
%MyDocuments%\EHFTUOMQDRXVTJSF.mui File path Marker for conditional persistence 
%MyDocuments%\FSJTVXRDQMOUTFHE.vbs File path VBS launcher in the conditional persistence path 
EHFTUOMQDRXVTJSF Scheduled task Conditional persistence task 

Email delivery indicators 

Indicator Type Context 
RE: ฮ ฮฑฯฮฑฮณฮณฮตฮปฮฏฮฑ 001828 Subject Both captured messages 
[email protected] Sender identity Displayed sender; account ownership is unverified 
66.113.160.32 IP address Observed delivery relay; not identified as C2 
focused-albattani.66-113-160-32.plesk.page Hostname Delivery infrastructure; not identified as C2 
webmail.bekdachegroup.com Hostname Received-chain host; not identified as C2 

Initial configured XLoader endpoints 

Slot Defanged endpoint 
1 hxxp://iqos-one[.]com/gf2n/ 
2 hxxp://offon33fv[.]club/6yav/ 
3 hxxp://eolienzaer[.]com/jrsi/ 
4 hxxp://tori[.]fi/8yfp/ 
5 hxxp://okbajegame[.]com/l3iy/ 
6 hxxp://komplek[.]id/gkws/ 
7 hxxp://sonhero[.]top/xc0y/ 
8 hxxp://grandseosite91[.]shop/1ud5/ 
9 hxxp://moreultras[.]store/2r3a/ 
10 hxxp://bagolone[.]it/qbqm/ 
11 hxxp://tomatox[.]com[.]tw/upw6/ 
12 hxxp://511mma[.]ru/dnir/ 
13 hxxp://hahashoe[.]com/r0zm/ 
14 hxxp://snvipvn[.]com/v0rc/ 
15 hxxp://revelintravel[.]com/zd56/ 
16 hxxp://www[.]990match[.]com/ccp8/ 

About the author

Shmuel Uzan Headshot

Shmuel Uzan

Security Researcher

Shmuel Uzan is a skilled security researcher with nearly a decade of experience. Shmuel holds a Bachelor of Software Engineering from Shamoon College of Engineering.

Stay up-to-date

Get the latest resources, news, and threat research delivered to your inbox.

Gartnerยฎ Emerging Tech: Top Use Cases Driving Preemptive Cybersecurity Growth