From Business Emails to XLoader: An Order Lure That Led to In-Memory Injectionย
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
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/ |
Stay up-to-date
Get the latest resources, news, and threat research delivered to your inbox.