Threat Response Unit

STX RAT: A new RAT in 2026 with Infostealer Capabilities

eSentire Threat Response Unit (TRU)

April 8, 2026

35 MINS READ

What did we find?

In late February 2026, eSentire's Threat Response Unit (TRU) observed an attempted delivery of a previously undocumented malware family within a customer environment in the Finance industry. TRU is tracking this threat as STX RAT, named for its consistent use of the Start of Text (STX) magic byte "\x02" prefixed to C2 messages.

TRU observed attempted delivery of the malware via a browser-downloaded VBScript file; by early March 2026, Malwarebytes reported a separate initial access vector in their blog, "A fake FileZilla site hosts a malicious download", where the malware was distributed through trojanized FileZilla installers.

Key takeaways

Initial Access

Though we were unable to recover the original VBScript, we found a nearly identical sample in VirusTotal with hash: 799b29f409578c79639c37ea4c676475fd88f55251af28eb49f8199b904a51f3. The VBScript concatenates JScript contents, writes them to a JScript file on disk, and executes the JScript file via WScript with elevated privileges.

Figure 1 - VBScript that writes/launches JScript in elevated WScript (bottom half)
Figure 1 - VBScript that writes/launches JScript in elevated WScript (bottom half)

Note, the name of the JScript file varies between campaigns. In this incident, we observed the following command line:

"C:\Windows\System32\wscript.exe" "C:\Users\<Username>\AppData\Local\Temp\business-structure.xlsx.js" /elevated

The JScript file's contents are shown in the figure below, whose purpose is to download a TAR file, extract it to disk, and execute the next stage PowerShell loader.

The TAR file contains two files:

Figure 2 - VBScript that writes/launches JScript in elevated WScript (top half)
Figure 2 - VBScript that writes/launches JScript in elevated WScript (top half)

The PowerShell script acts as an in-memory loader: it takes the 1.bin payload string, reverses it and Base64-decodes it into bytes, allocates RWE memory using VirtualAlloc, copies both the payload and a parameter blob into those buffers via WriteProcessMemory, then computes the entrypoint address as base + offset and transfers execution to it by converting that address into a callable delegate with Marshal.GetDelegateForFunctionPointer and invoking it.

Figure 3 - PowerShell loader that deobfuscates payload and injects STX RAT into PowerShell address space
Figure 3 - PowerShell loader that deobfuscates payload and injects STX RAT into PowerShell address space

STX RAT Analysis

Packer Characteristics

The packer used by the malware is characterized by two exports named "init" and "run", and the use of XXTEA decryption / Zlib decompression on a byte array stored in the .DATA section. Following "tail jumps" is an effective technique in unpacking the core STX RAT payload.

Figure 4 - Pseudo-code of unpacking routine in STX RAT loader
Figure 4 - Pseudo-code of unpacking routine in STX RAT loader

Strings

As a means of slowing the malware analysis process, strings stored throughout the binary are rolling XOR-encoded and AES-128-CTR-encrypted.

The first set of strings is stored throughout the malware and referenced by memory address. In multiple basic blocks, the plaintext is recovered using a rolling XOR-decryption loop in which the key index is advanced per byte and wrapped using a predefined modulus (i.e., when the index reaches the limit, it returns to the starting position and repeats). 

For example, in many blocks the XOR key begins at 0x39 and increments for each ciphertext byte (so the next key byte would be 0x3A). When the key reaches a predefined upper bound (e.g., 0x6C), it wraps back to the initial value (e.g., 0x39) and the cycle repeats. The starting key differs between basic blocks. The figure below shows an example block that decrypts the C2 server address 95.216.51.236 (AS24940 - Hetzner Online GmbH).

Figure 5 - Rolling XOR basic block example
Figure 5 - Rolling XOR basic block example

The second set of strings are stored in a table and are decrypted on an as-needed basis via an offset into the table. The pseudo-code below displays the routine responsible for decrypting a string via offset. This routine eventually calls the CryptDecrypt API to decrypt a string via AES-128-CTR.

Figure 6 - AES-128-CTR decryption routine
Figure 6 - AES-128-CTR decryption routine

The table itself contains records matching the structure shown below.

struct AesEncryptedRecord {
    BYTE aesKey[16];                    // 128-bit key (16 bytes)
    BYTE nonce[5];                      // 5-byte Nonce
    DWORD dwCiphertextLength;           // Length of ciphertext
    BYTE ciphertext[dwCiphertextLength]; // Ciphertext
};

The annotated figure below further illustrates an example record from this table and highlights its layout: the AES key, nonce, ciphertext length, and finally the ciphertext itself.

Figure 7 - Annotated record in table storing key, nonce, ciphertext length, and ciphertext
Figure 7 - Annotated record in table storing key, nonce, ciphertext length, and ciphertext

API Hashing

The malware uses two lookup tables: one for resolving exported APIs and another for modules. The exports table contains records in the form [module_index, export_sha1, export_salt]. The module_index points to a record in the second table, which stores per-module SHA‑1/salt pairs.

To resolve an API, the malware enumerates modules already loaded in the process via the PEB loader data. It lowercases each DLL name and identifies the target module by computing:

If the module is not currently loaded, it searches the system directory for candidate DLLs, lowercases each filename, and manually loads any DLL whose hash matches the expected value.

Once the correct module is loaded, it enumerates that module's exported function names. For each export, it computes:

It then compares the result to the expected export_sha1 to locate the desired API. More details on this process are provided below.

  1. Call a lookup function (renamed here as mw_resolve_api, see Figure 10) and pass a single argument - an index into a table of "ExportSha1SaltBlock" structures. Through pointer math, the index enables retrieving the "wanted" exports associated SHA-1/Salt structure in the table. Each structure is 48 bytes long and follows the format below.

    struct ExportSha1SaltBlock {
        DWORD dwModuleIndex; // Used in calculating the virtual address of the export's associated module sha1/salt pair
        BYTE sha1Hash[20];   // 20 bytes representing the SHA-1 hash to check against the combined export name and salt
        BYTE salt[10];       // 10 bytes of salt to combine with the export name
        BYTE reserved1[14];
    };
  2. The first member dwModuleIndex from the prior structure is then used in another lookup to determine the export's associated module sha1/salt structure. The function (renamed here as mw_get_module_base_by_hash_and_salt in Figure 8) also takes a single argument - an index into an array of structures. Each structure is also 48 bytes long, but follows a different format:

    struct ModuleSha1SaltBlock {
        BYTE sha1Hash[20]; // 20 bytes representing the SHA-1 hash to check against the combined module name (lowercase) and salt
        BYTE salt[10];     // 10 bytes representing the salt to combine with the module name
        BYTE reserved2[18];
    };
  3. Next, the routine resolves the base address of the module associated with the export by querying the process loader data (PEB_LDR_DATA). It enumerates the InMemoryOrderModuleList and, for each entry, retrieves the module's base name as a wide-character string. It then iterates over the string, converting it to lowercase ASCII. The normalized name, along with the SHA-1/salt structure, is passed to another subroutine (shown here as mw_sha1_verify_with_salt), which returns a boolean indicating whether the string matches the salted hash, e.g. SHA-1(dll_name + salt). If a match is found, the routine breaks out of the loop and returns the module's base address.

    Figure 8 - Routine responsible for resolving module base by salted SHA-1 hash
    Figure 8 - Routine responsible for resolving module base by salted SHA-1 hash

    Here is a look at the raw data, which has been annotated with the formula for indexing into the table with the SHA-1 and salt for psapi.dll as an example.

    Figure 9 - Annotated record in module-specific SHA-1/Salt table
    Figure 9 - Annotated record in module-specific SHA-1/Salt table
  4. The resolved module base address is then used to parse the module's export directory. The routine iterates the module's exported function names and hashes each name as-is (no lowercase conversion like for module names), comparing the result to the SHA-1 hash from step 1. When a match is found, it resolves the corresponding function address (via export name/ordinal mapping) and returns it.

    Figure 10 - Routine responsible for resolving module base and export by salted SHA-1 hashes
    Figure 10 - Routine responsible for resolving module base and export by salted SHA-1 hashes

    Here is a look at the raw data, which has been annotated with the formula for indexing into the table with the SHA-1 and salt.

    Figure 11 - Annotated record in export-specific SHA-1/Salt table
    Figure 11 - Annotated record in export-specific SHA-1/Salt table

The table below lists modules and associated SHA1/SALT:

DLL SHA-1 SALT
psapi.dll de6a4b679e42b6b429bbe520c22b849224b2de26 4208deb22663bfc976e3
advapi32.dll d4a90f0a6efd9add984f8bbb51bc4565746874ad ef4453807d1f1fa6eb12
gdi32.dll 92bbc333b52c26ed10eaf38b9134d4ccfdf4389b 2c4c88c9cd454d5386a1
userenv.dll 2bad076733e220cde1dd42747ffd13e8eb382c2d fcc38246d4b212c09e83
shell32.dll 0207adb637fcb98598e74e6b90c817ad7079fc1b 1fc95c810e8b40d5c78c
mpr.dll e52557da86b7bde2d869074431ade27f6e26d0ae c6413ab9ee98bcdb558e
iphlpapi.dll 26647081133efeddf28e40be13c71355dc75dd53 a08da86af6b7bcaef571
crypt32.dll 6fee56f2e8d3fc710c2f98409fe20c2aa252fc7a f77530c3112e84739fca
ws2_32.dll ac708f17875fc75d328ecfe524e3614895cf042e e667dee10a8446e75a5c
ntdll.dll 5095ca6e522937910ae8367867d10a35c376d770 900d814448e86b5ceae1
user32.dll aa9d86d6096350faf04d0ea9e1fa0956766f7bc7 cbabf6a3fa051fe8d54e
kernel32.dll e2e3b4f81f27654d1c3579a3803ed0bfcaf833e3 8b29a4434d0192d729e5
wtsapi32.dll d90c15ba3566a54742312fbca2a022f586266813 0b65bf7b2fb9dda4192f
shlwapi.dll 2a9abfad84057d0d71e7bc4ecb23d29013c2e11e 50079b1f09075cfa8f48

The python snippet shown below can be used to emulate the aforementioned hashing behavior:

import hashlib
module_name = b'shlwapi.dll'
salt = bytes.fromhex('50079b1f09075cfa8f48')
result = hashlib.sha1(module_name + salt).hexdigest()
print(result)
# 2a9abfad84057d0d71e7bc4ecb23d29013c2e11e

Anti-VM

The tables below describe various Anti-VM detection artifacts, (i.e. files, registry keys/values, services) checked by the malware. If an artifact is found, the malware "jitter exits" (sleeps with a randomized delay and exits).

Registry Key Hypervisor
HKLM\HARDWARE\ACPI\DSDT\VBOX__ VirtualBox
HKLM\HARDWARE\ACPI\FADT\VBOX__ VirtualBox
HKLM\HARDWARE\ACPI\RSDT\VBOX__ VirtualBox
HKLM\SOFTWARE\Oracle\VirtualBox Guest Additions VirtualBox
HKLM\System\CurrentControlSet\Services\VBoxGuest VirtualBox
HKLM\System\CurrentControlSet\Services\VBoxMouse VirtualBox
HKLM\System\CurrentControlSet\Services\VBoxService VirtualBox
HKLM\System\CurrentControlSet\Services\VBoxSF VirtualBox
HKLM\SYSTEM\ControlSet001\Services\vmdebug VMware
HKLM\SOFTWARE\VMware, Inc.\VMware Tools VMware
HKLM\SYSTEM\ControlSet001\Services\VMMEMCTL VMware
HKLM\SYSTEM\ControlSet001\Services\VMTools VMware
HKLM\System\CurrentControlSet\Services\vioscsi QEMU
HKLM\System\CurrentControlSet\Services\viostor QEMU
HKLM\System\CurrentControlSet\Services\VirtIO-FS Service QEMU
HKLM\System\CurrentControlSet\Services\VirtioSerial QEMU
HKLM\System\CurrentControlSet\Services\BALLOON QEMU
HKLM\System\CurrentControlSet\Services\BalloonService QEMU
HKLM\System\CurrentControlSet\Services\netkvm QEMU
Registry Key\Value Name Substrings
HKLM\HARDWARE\DEVICEMAP\Scsi\Scsi Port X\Scsi Bus X\Target Id X\Logical Unit Id X\Identifier qemu, vbox, vmware
HKLM\HARDWARE\DESCRIPTION\System\SystemBiosVersion qemu, vbox
HKLM\HARDWARE\DESCRIPTION\System\VideoBiosVersion qemu, virtualbox
HKLM\HARDWARE\DESCRIPTION\System\SystemBiosDate 06/23/99
HKLM\System\CurrentControlSet\Control\SystemInformation\SystemManufacturer qemu, vmware
HKLM\System\CurrentControlSet\Control\SystemInformation\SystemProductName qemu, vmware
HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0\ProcessorNameString qemu
File Hypervisor
vdservice.exe QEMU
vdagent.exe QEMU
balloon.sys QEMU
netkvm.sys QEMU
pvpanic.sys QEMU
viogpudo.sys QEMU
vioinput.sys QEMU
viorng.sys QEMU
vioscsi.sys QEMU
vioser.sys QEMU
viostor.sys QEMU
vboxservice.exe VirtualBox
vboxtray.exe VirtualBox
VBoxMouse.sys VirtualBox
VBoxGuest.sys VirtualBox
VBoxSF.sys VirtualBox
VBoxVideo.sys VirtualBox
VGAuthService.exe VMware
vmnet.sys VMware
vmmouse.sys VMware
vmusb.sys VMware
vm3dmp.sys VMware
vmci.sys VMware
vmhgfs.sys VMware
vmx86.sys VMware
vmkdb.sys VMware
vmnetuserif.sys VMware
vmnetadapter.sys VMware

Anti-Analysis

After checking for virtual machine artifacts, anti-analysis checks are performed. Notably, the malware assumes it only runs in the context of PowerShell/MSBuild, otherwise it jitter exits. This is an effective technique to hinder malware analysis via debugger as malware is often dumped to disk in the analysis process and executed via rundll32 or a custom loader.

Figure 12 - BeingDebugged and process name check
Figure 12 - BeingDebugged and process name check

Hidden Window

The malware then hides its own window by calling EnumWindows to enumerate top-level windows, and passes a callback that retrieves each window's class name via GetClassNameA and compares the window's class name against the string, "CASCADIA_HOSTING_WINDOW_CLASS" (Windows Terminal class name). Upon match, it modifies the window's extended style to include WS_EX_TOOLWINDOW (0x80), causing the window to be hidden from Alt+Tab and the Taskbar.

Figure 13 - Routine responsible for hiding the malware's window
Figure 13 - Routine responsible for hiding the malware's window
Figure 14 - Callback routine that gets window class name and compares against Windows Terminal class name (truncated)
Figure 14 - Callback routine that gets window class name and compares against Windows Terminal class name (truncated)

AMSI Ghosting

Immediately after the malware hides its window, if configured to do so, it uses a known AMSI (Anti-Malware Scan Interface) -bypass technique called AMSI Ghosting, where it patches the Windows API rpcrt4!NdrClientCall3, effectively disabling a core RPC layer that AMSI depends on and preventing security solutions from being able to act on telemetry produced by AMSI.

Figure 15 - Routine responsible for AMSI Ghosting
Figure 15 - Routine responsible for AMSI Ghosting
Figure 16 - Before/after patching comparison in disassembly
Figure 16 - Before/after patching comparison in disassembly

Antivirus Identification

The table below describes the processes and drivers the malware searches for in an effort to identify security software present on the machine. Each identified product is appended to a list that is sent in the introduction message to the C2.

Process Name Anti-Virus Product
mcsysmon.exe McAfee SystemGuards
vsdatant.sys ZoneAlarm
GDBehave.sys Bitdefender
clamd.exe ClamAV
AntiSpyWare2.exe Trend Micro
AshAvScan.sys Avast
dwengine.exe Dr. Web
dwservice.exe Dr. Web
fortiscand.exe Fortinet FortiClient
fmon.exe Fortinet FortiClient
AvastSvc.exe Avast
aswSP.exe Avast
aswFsBlk.exe Avast
AVGIDSAgent.exe AVG
avgwdsvc.exe AVG
AVGIDSErHr.sys AVG
AVGIDSxx.sys AVG
avguard.exe Avira
avgntmgr.sys Avira
avgntdd.sys Avira
bdagent.exe Bitdefender
BDHV.sys Bitdefender
avc3.sys AVG
cmdagent.exe Comodo
cis.exe Comodo
inspect.sys Comodo
cmdhlp.sys Comodo
fsav32.exe F-Secure
FSSM32.exe F-Secure
fspex.exe F-Secure
fsdfw.sys F-Secure
WAHost.exe Windows Defender
pavproc.sys Panda Security
pavboot64.sys Panda Security
savmain.exe Sophos
wscclient.exe Sophos
savonaccess.sys Sophos
SMC.exe Symantec
nis.exe Symantec
SMCgui.exe Symantec

C2 Protocol

The malware communicates with its C2 server over TCP sockets via Winsock and protects C2 traffic using ECDH (Elliptic Curve Diffie–Hellman) key exchange. The figure below illustrates the key exchange process between the victim machine (e.g. Alice) and the C2 (e.g. Bob).

Figure 17 – Diffie-Hellman key exchange
Figure 17 – Diffie-Hellman key exchange

During the handshake, the C2 sends a ServerHello containing its X25519 public key and an Ed25519 signature of that key. The client verifies the signature using a hard-coded Ed25519PublicKey (stored as a rolling XOR–encoded Base64 string: 4DwvIfxy4thDpGXKYjew8MTI1jYwFEIs2oHuW35BtVM=), then completes the ECDH exchange. If the signature doesn't match, the malware assumes the C2 is not authentic and sends empty packets to the C2 in a loop.

The victim's X25519 private key is generated using the Windows API CryptGenRandom. By hooking this API and breaking just before it executes, you can detect the key-generation call by checking rdx (dwLen) for 0x20 (32 bytes), then save the output buffer pointer stored in r8 (pbBuffer), then step over the call and dump 32 bytes from that address.

Recovering this private key lets a researcher derive the shared secret and decrypt traffic exchanged between the victim and the C2. The routine that generates the victim's private key bytes can be seen in the figure below.

Figure 18 - Routine responsible for generating victim X25519 private key
Figure 18 - Routine responsible for generating victim X25519 private key

Another effective technique in dumping the private key involves hooking the X25519 clamping routine and dumping the private key bytes at rcx (first argument):

Figure 19 - X25519 private key clamping routine
Figure 19 - X25519 private key clamping routine

Simulating C2 communications requires patching the Ed25519 base64 string in the malware with a new Ed25519 public key and signing the returned X25519 key and returning that signature to the implant. We used the following code to convert our Ed25519 public key into hex bytes and patched the threat actor's Ed25519 public key with our own.

patch = b'dckcTs0XHVxirXbvz7g45gZTZ8eTBJ6hkXyHUqC9tYw=\x00'
patch_bytes = bytearray()
start = 0x34
for i in patch: # Rolling XOR over base64-encoded Ed25519 key
    patch_bytes.append(i ^ start)
    start += 1
print(patch_bytes.hex())
#50565d546c4a0a63746b4656321920353e7221737d2e101f16752b1b121b643b3f0d2f1f0d2819622804296260
Figure 20 - Bytes to be replaced in memory, enabling spoofing Ed25519 public key
Figure 20 - Bytes to be replaced in memory, enabling spoofing Ed25519 public key

The key exchange between the C2 and victim produces a shared secret, which the malware feeds into HKDF-SHA256 as the input keying material. It derives a 32‑byte key with no salt and an empty info/context field. That key is then used with ChaCha20‑Poly1305 to encrypt victim data and decrypt C2 data, with Poly1305 ensuring integrity via tag verification. Encrypted messages follow the format [chacha20_nonce₁₂ ∣ ciphertext ∣ poly1305_tag₁₆].

This communication protocol provides the threat actors with several operational benefits:

Framing

The malware implements its own message framing on top of TCP, where each frame/message is encoded as:

DWORD length
BYTE  payload[length]

Note: all traffic in both directions (victim ↔︎ C2) uses the same length-prefixed framing. Each side reads exactly 4 bytes to obtain the payload length, then continues reading from the TCP stream until length bytes of payload have been received.

Handshake / key exchange

1) C2 → Victim: ServerHello

The server sends two length‑prefixed values. The first is its X25519 public key, which the client (victim machine) uses to perform key exchange and compute the shared secret. The second is an Ed25519 signature over that X25519 key, which the client verifies using an embedded Ed25519PublicKey (decoded at run-time via pre-described rolling XOR block): 4DwvIfxy4thDpGXKYjew8MTI1jYwFEIs2oHuW35BtVM=.

DWORD len_pubkey                              // 0x20 (32)
BYTE  server_pubkey[len_pubkey]
DWORD len_ed25519_signature                   // 0x40 (64)
BYTE  ed25519_signature[len_ed25519_signature]

server_pubkey: the C2's 32‑byte X25519 public key used for key exchange

ed25519_signature: a 64‑byte Ed25519 signature used to verify the server_pubkey

2) Victim → C2: ClientHello

The client generates an ephemeral keypair and returns its public key:

DWORD len_victim_pubkey          // 0x20 (32)
BYTE  client_pubkey[len_victim_pubkey]

Encrypted Data Exchange and Message Format

The client computes a shared secret and derives the ChaCha20 key, generates a nonce, then encrypts and sends the message to the C2. The nonce is prepended to the message.

Key Agreement

Describes the process of generating the ChaCha20 key via pseudocode.

shared_secret = ECDH(client_privkey, server_pubkey)
key_material  = HKDF(shared_secret)

Encrypted Message

The data structure of encrypted messages is described in the pseudo-structure below.

BYTE chacha_nonce[12]
BYTE ciphertext[len_message - 12 - 16]
BYTE poly1305_tag[16]

Message Decryption Process

Describes the process of decrypting an Encrypted Message sent to and received from, the C2.

Decrypted Message Format Type A

The data structure of decrypted messages varies, however it generally follows the format shown below. The "data" key stores different data depending on the "type" key. The message shown below is the first message sent to the C2 by the malware and is essentially an introductory message to notify the operators of a new bot having joined the botnet.

If the version value sent in this JSON is considered out-of-date by the C2, the C2 sends the update command, otherwise it sends the get_creds command.

\x02 // Magic byte 
{ 
  "type": "intro", 
  "channel": 0, 
  "data": { 
    "user_id": "<SID-derived-hash>", 
    "machine_id": "<SID-derived-hash>", 
    "hostname": "DESKTOP-1UG9QX", // Victim hostname 
    "username": "john.smith", // Victim username 
    "os": "Windows 11 Pro", 
    "version": "2025.11.003", // Malware build version 
    "arch": "x64", // OS architecture 
    "is_admin": 1, // Is admin 
    "pid": 7953, // Malware process's PID 
    "process_path": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", 
    "server": "95.216.51.236", // C2 server 
    "utm_group": "" // (optional) Victim clipboard match regex: # Tag: ([^\s]*)\s 
    "utm_source": "", // (optional) Victim clipboard match regex: # Hostname: ([^\s]*)\s 
    "ram": 8191, // Installed RAM 
    "av": [] // AntiVirus names
  }
}

The snippet below shows the decrypted message JSON the C2 sends the malware in the event it considers the introductory JSON acceptable.

\x02 // Magic byte
{
  "type": "get_creds"
}

After receiving the get_creds command, and prior to sending harvested credentials, the malware creates a screenshot of the victim desktop via BitBlt/GDI functions, and transmits it to the C2 as a base64 encoded JPG file.

Figure 21 - Top-level routine responsible for getting a screenshot of the victim's current desktop
Figure 21 - Top-level routine responsible for getting a screenshot of the victim's current desktop
Figure 22 - Truncated JSON containing screenshot JPG that is sent to C2
Figure 22 - Truncated JSON containing screenshot JPG that is sent to C2

Decrypted Message Format Type B

The figure below displays the format of the payload returned when the implant is considered out-of-date by the C2 and the C2 returns the update command. This payload format is similar for the run command. Rather than JSON format, a custom binary format is returned by the C2. The header of the file is simple and follows a DWORD length pertaining to field names and the field names themselves. The remaining data includes a stub (beginning with FC 48 83...) and the update payload itself.

Figure 23 - Update payload with fields, stub/payload
Figure 23 - Update payload with fields, stub/payload

The malware parses the aforementioned structure, allocates PAGE_EXECUTE_READWRITE (RWX) memory via VirtualAlloc, writes the payload to it, and executes the stub, which in turn calculates the offset of the OEP in the payload and calls it. The update payload itself is packed by the same packer we previously mentioned, e.g. init/run exports and follows the same unpacking process. The use of a stub to calculate the OEP is a clever trick used by the malware to prevent analysis of payloads in sandboxes. The figure below displays the stub's disassembly.

Figure 24 - Debugger view of update payload stub and payload itself
Figure 24 - Debugger view of update payload stub and payload itself

Campaign Tracking and Callback URL

The introduction JSON previously described contains two optional keys, utm_group and utm_source. These keys are set after the malware gets the victim's clipboard via OpenClipboard/GetClipboardData and parses it via the regex patterns shown in the table below.

Notably, the cid value stores an AES-128-CTR encrypted callback domain for additional campaign tracking purposes. It is decrypted using a hard-coded key, "a91b5f85e4572d3030fc9f4e18fa7896" and nonce, "37927fee241db9697f94c0f3ca488946". After it is decrypted, it is checked to ensure it begins with the magic byte "\x02" to ensure authenticity of the decrypted callback domain.

The format of the callback URL is as follows:

https://<decrypted_domain>/api/v1?action=signal&ray_id=<ray_id>
Regex Pattern Description
# Tag: ([^\s]*)\s Regex match is set as utm_group in introductory message to the C2
# Hostname: ([^\s]*)\s Regex match is set as utm_source in introductory message to the C2
cid=([0-9a-f]+) AES-128-CTR encrypted callback domain (pre-fixed with STX byte \x02)
ray_id=([0-9a-f]+) Sent via callback URL via query parameter ray_id

The figure below displays pseudo-code of the start of the routine where APIs are resolved, the first regex pattern decoded via rolling XOR, and regex match against the victim's clipboard data.

Figure 25 - Clipboard parsing for campaign tracking and callback URL
Figure 25 - Clipboard parsing for campaign tracking and callback URL

C2 Commands

The table below describes commands identified in our analysis (non-exhaustive). The update and get_creds commands are listed first, as they are the primary commands delivered automatically when the malware checks into the C2 backend. The remainder of commands are sent by threat actor(s) via hands-on-keyboard, most likely through a web panel.

Command AKA "type" Description
update Sent by the C2 if the intro message's version value is considered out-of-date. Releases mutex, and executing the payload from the C2. Supported payload types include: exe, bin, dll, ps1, and shellcode. If the update is a success, it calls RtlExitUserProcess to exit, otherwise it regenerate mutex and continue running.
get_creds Typically sent automatically by the C2 Credential collection and transmission to C2 in a JSON formatted message
die Deletes persistence mechanisms, walks destructors, and calls RtlExitUserProcess, terminating itself
run Execute C2-supplied ps1, shellcode, dll, or exe in memory, similar to the update command, however it doesn't release its mutex or exit.
start_hvnc Hidden remote desktop allowing threat actors to control victim's machine
key_press Inject keystrokes into the hidden desktop, supporting the HVNC feature, uses SendInput API
mouse_input Simulate mouse movement, supporting the HVNC feature, uses SendInput API
mouse_wheel Threat actors can simulate mouse scroll wheel, supporting the HVNC feature
switch_desktop Switch between hidden desktops
paste Allows paste functionality, supporting the HVNC feature
start_tunnel Open socket on C2-specified host/port, connect_ok or connect_failed is sent back to the C2 to indicate success/failure.
tunnel_data Tunnel data through an opened socket. On failure tunnel_eof is sent to the C2.
connection_lost Clean up all desktop handles / close all HVNC channels
channel_closed Clean up desktop handle / close a specific HVNC channel

Loader Functionality

The malware supports various payload types delivered by the C2. Payloads are handled through numerous commands, e.g. update, run. Payload types supported by the malware are described below. The table below describes the id to string mapping of payload types and are internal identifiers used by the malware.

ID Payload Types Description
1 bin, exe VirtualAlloc (PAGE_EXECUTE_READWRITE) w/ stub that calls OEP
2 dll x64 DLL reflective injection
3 shellcode Calls CreateThread and passes it a routine that calls VirtualAlloc to allocate RWE buffer for the payload, then copies the payload to the buffer, flushes instruction cache, and calls the entrypoint.
4 ps1 PowerShell execution via STDIN

For the ps1 payload type specifically, the malware creates a pipe by calling CreatePipe and assigns it to a new PowerShell process's redirected STDIN handle. It then calls CreateProcessW to launch the process with its standard input bound to the pipe and calls WriteFile to write the C2-delivered PowerShell payload into the child process's STDIN. PowerShell reads the data from STDIN and executes it via Invoke-Expression, resulting in fileless, minimal command-line for executing PowerShell in memory. The minimal command line used in this process is shown in the code snippet below.

powershell.exe -Command "[Console]::In.ReadToEnd() | Invoke-Expression"
Figure 26 - Fileless PowerShell script execution via hijacked pipes
Figure 26 - Fileless PowerShell script execution via hijacked pipes
Figure 27 - Fileless PowerShell script execution via hijacked pipes (write pipe)
Figure 27 - Fileless PowerShell script execution via hijacked pipes (write pipe)

Credential Theft

The list below described credentials targeted by the malware (non-exhaustive):

Folder Name/Application Behavior
FileZilla

*.xml file contents are matched against:

  • (<Server>.*?</Server>|<LastServer>.*?</LastServer>)
WinSCP

Credentials stored in registry keys under the key shown below are harvested:

  • Software\Martin Prikryl\WinSCP 2\Sessions
Cyberduck/iterate_GmbH

Matches file contents against regex patterns:

  • (<setting[^>]*/>)
  • name="([^"]*)" value="([^"]*)"
  • ([a-z]+)://(.+)@(.+)
Figure 28 - Stolen credentials packaged into JSON
Figure 28 - Stolen credentials packaged into JSON

HVNC Functionality

This section covers two commands (key_press and mouse_input) that enable HVNC keyboard/mouse injection into hidden desktops. When the RAT receives the key_press command, the routine shown below is responsible for converting the C2-supplied string into keyboard input via the SendInput API, effectively allowing threat actors to send keyboard input in hidden desktop windows.

Figure 29 - Keystroke injection (key_press command)
Figure 29 - Keystroke injection (key_press command)

The next figure displays the handler for the mouse_input command, which allows threat actors to move the mouse cursor in hidden desktops using C2 supplied coordinates and the SendInput API.

Figure 30 - Mouse input injection (mouse_input command)
Figure 30 - Mouse input injection (mouse_input command)

Persistence

The malware uses several different techniques to persist on the infected host, some of which are described below.

Figure 31 - Persistence via autorun.ps1
Figure 31 - Persistence via autorun.ps1
Figure 32 - Persistence via C# project file and MSBuild
Figure 32 - Persistence via C# project file and MSBuild
Figure 33 - HKCU Run entries
Figure 33 - HKCU Run entries
Figure 34 - Persistence via hijacked COM object
Figure 34 - Persistence via hijacked COM object
Figure 35 - ActiveX.sct scriptlet file contents
Figure 35 - ActiveX.sct scriptlet file contents

eSentire Utilities

eSentire has created a python script for IDA Pro available here that decrypts rolling-XOR and AES-128-CTR strings in the unpacked payload and sets comments. The script will also resolve APIs by parsing the Module/Export SHA-1/Salt tables and match them against exports in the JSON file exports_map.json (or the System directory).

Figure 36 - Python script utility sets comments for resolved APIs
Figure 36 - Python script utility sets comments for resolved APIs
Figure 37 - Python script utility output for decrypted strings (+ sets comments)
Figure 37 - Python script utility output for decrypted strings (+ sets comments)

Yara Rules

rule STXRat 
{
    meta:
        author = "YungBinary"
        description = "Detection for unpacked STX RAT in memory"
    strings: 
        
        // Lowercasing
        $s1 = {
            8D 51 BF
            83 FA 19
            8D 41 20
            0F 47 C1
            C2 
        }
        // AMSI ghosting
        $s2 = {
            48 8D 05 ?? ?? ?? ??
            66 C7 45 ?? 48 B8 [0-6]
            48 89 45 ??
            48 8D 55 ??
            66 C7 45 ?? FF E0
        }
        // Debugger check
        $s3 = {
            65 48 8B 04 25 60 00 00 00
            80 78 02 01
        }
        // Crypto string
        $s4 = "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)" ascii
        
        // AES key/size/algo handling
        $s5 = {
            B9 10 66 00 00 [0-3]
            0F 44 C1
            B9 0F 66 00 00
            41 81 ?? C0 00 00 00
            0F 44 C1
            B9 0E 66 00 00
        }
        // module name copying
        $s6 = {
            48 83 FB 5A
            73 ??
            88 84 1C ?? ?? ?? ??
            48 FF C3
            48 FF C1
            8A 01
            84 C0
            75
        }
        // Sha1 initialization constants
        $s7 = {
            83 61 18 00
            83 61 14 00
            C7 01 01 23 45 67
            C7 41 04 89 AB CD EF
            C7 41 08 FE DC BA 98
            C7 41 0C 76 54 32 10
            C7 41 10 F0 E1 D2 C3
            C3
        }
        // X25519 clamping
        $s8 = {
            80 61 1F 3F
            80 49 1F 40
            80 21 F8
        }
    condition: 
        uint16(0) == 0x5a4d and (4 of ($s*))
}
import "pe"
rule STXRatLoader
{
    meta:
        author = "YungBinary"
        description = "Detection for STX RAT loader"
    strings: 
        
        // Kernel32 ROR-14
        $ror1 = { B9 4E 15 F5 1F E8 }
        // VirtualProtect ROR-14
        $ror2 = {
            BA 35 EC 33 57
            48 8B C8
            48 8B D8
            E8
        }
        // CreateThread ROR-14
        $ror3 = {
            BA 36 91 AC 32
        }
        // Ntdll ROR-14
        $ror4 = {
            BA 7E 91 90 5A
            48 8B C8
            E8
        }
        // XXTEA constant
        $s1 = {
            69 D0 47 86 C8 61
        }
        // Zlib
        $s2 = {
            B8 85 10 42 08
            41 F7 E2
        }
        // ROR
        $s3 = {
            41 C1 C8 0E
            0F BE C0
            44 03 C0
        }
    condition: 
        uint16(0) == 0x5a4d and ((pe.exports("init") and pe.exports("run")) and 1 of ($ror*) and 1 of ($s*))
}

What did we do?

What can you learn from this TRU Positive?

Recommendations from the Threat Response Unit (TRU)

Indicators of Compromise

Type Value Description
IPv4 147.45.178.61 Download IP seen in initial stage
File 799b29f409578c79639c37ea4c676475fd88f55251af28eb49f8199b904a51f3 VBScript that loads STX RAT
IPv4 95.216.51.236 STX RAT C2
URL yu7sbzk2tgm4vv56qgvsq44wnwgct6sven4akbb2n3onp46f42fcstid.onion STX RAT C2 (onion)
Command Line powershell.exe -Command "[Console]::In.ReadToEnd() | Invoke-Expression" Fileless PowerShell execution
Command Line "C:\Windows\System32\wscript.exe" "C:\Users\<Username>\AppData\Local\Temp\business-structure.xlsx.js" /elevated Elevated JScript execution
File a57683ae49dd24256dab0dd21ca83c4a08892fda92e83206447380a2b6c80221 STX RAT unpacked
File 0e9c8e5ce94641e0b07607647a55c162adb18048f9c1e1e3dbe859cd08b2a797 STX RAT unpacked
File 77eea991e5c11da46e10c208fb8920a08a9bbdd8ffd72d0d6548fd8e45aa4647 STX RAT unpacked
File 52862b538459c8faaf89cf2b5d79c2f0030f79f80a68f93d65ec91f046f05be6 STX RAT unpacked
File da65c30f4dee13d3c85c6a31386018d101d635e28eeb65ac73699787fecc20e0 STX RAT unpacked
File abd003ab1172cda83731dbe76d20a43c35a452d683d628a4e59eac8aadc68ffa STX RAT unpacked
File ca3bd6f8f4c8170c60896493b0bbfc4629bf94a3d0c5bd3f32397e869e98fb3d STX RAT unpacked
File b59ac3088a58ebafdcdf00a5597c0de156de667d498bb8eccdaad5c8ba380e99 STX RAT unpacked
File 2b0d8c8e86dd372b44b99f8be4e4a7cbbfe5ce78bc10b714fc0735c15b7ddb32 STX RAT unpacked
File 3511e2bb89f64555acdef3b486717fd517f500c8c630e02e9c6fa0ac5bed8950 STX RAT unpacked
File a1ac7046e99181fe46edd62c00ca53602e7cd4430365307d0b3a47ddd1e9e670 STX RAT unpacked
File 2d2073ee0404dba0de7e248dc50f60258ca85e493be9021657e325a9bbd7cb01 STX RAT unpacked
File f04b0c3a53e3af7699c30ab9adb4d60a71a7da6945cf0ae287a9f67675433a67 STX RAT unpacked
File d122d6c2ccc69594bbfbca82315aa0803b3b93972a6ab83699797812b35d9679 STX RAT unpacked
File f431ff7bc59df48c137ef63839a5a2af520e0d3b28429468398e3b291f30d1e6 STX RAT unpacked
File 3455ec49b8dc3743398a20c271194682eba40a67ee3b10549d3e6f837f7499ca STX RAT unpacked
File 64adf1715483f63fc47283393f89857f0545a45d9e7382417189b5084d19c37b STX RAT unpacked
File f74d052337110c6282f4d9738263b89056d0c89d131b329d5d4e3189b67206ae STX RAT unpacked
File 0a60ccf29f89019b1eebbbb8ad9bf0302dba399a26a62449078dda919bbd247b STX RAT Loader
File c4a5223bcf57b32e036c33c4d0e41aa44ff3eb4632c2fb4ed9c9bd593a04c3ee STX RAT Loader
File 17fb97a117cb684c82d522e65c0958c4c1267401317cda53c77035189546ebba STX RAT Loader
File f81e14ac7309019208529599a848c2287789f0ccbcd2f7609e9f239f52376763 STX RAT Loader
File 66a155f6672fbbb041cb754c143db91b30084f98e9102c280ba95ffda156123b STX RAT Loader
File 8c812fa14a4c5ac63dd1dce47232b45bea95f93dcc5cac40bb12fa6f1961e1bc STX RAT Loader
File 5168eae0ee183575b9a2d2c0c21a23400125502fb78f41b20db27a0bea58324d STX RAT Loader
File 3763b9e6eeb9a18875c45ba7d1a4f9fbfd6e80d1aea434e88ad99ee5b1bbd790 STX RAT Loader
File a2d703265d61b78837e86527aa2e31994a934c72b6c073db0c4d9c0c59a4e401 STX RAT Loader
File b3f21d0843fa7106b466c590c97b1b8b201a79ae82ed46b46d2422dd252d7836 STX RAT Loader
File 84c2f3b13f5251cf87d1a2c95ac7ca111238f61d56358b2c4228c84ef9ed1ae7 STX RAT Loader
File da65c30f4dee13d3c85c6a31386018d101d635e28eeb65ac73699787fecc20e0 STX RAT Loader
File ac97a49e17bf2a315205a30cf39a68c264b1dc4395b88e3997ec506c778159b0 STX RAT Loader
File 5c37b35929dac5c640d1d14e6dc74009c5072536d7fbe0c58822bf2387a8a22d STX RAT Loader
File ca3bd6f8f4c8170c60896493b0bbfc4629bf94a3d0c5bd3f32397e869e98fb3d STX RAT Loader
File aab1f1bdba7083a25d7c841cd2dc3588cc0f3e28e29260bea5c2fd5b033697fb STX RAT Loader
File af7a76820a42c4cadfc7ff5fd372c99e9c5fd96ee9d14e07bde0902fec1881ab STX RAT Loader
File 8b28c0568baa7da10200a012a70ff735ccec557678a40d1b3fb16f5c0a31f6b7 STX RAT Loader
File d32455fc430ffc13e8a89db9198f17184fd27001fc11a7e9531d6055932853db STX RAT Loader
File 9eeef204645391b9c9e3d5b54f3541b8e52440d2a288873749398741182ce7de STX RAT Loader
File b7d64d6a9c641855f400949c8c000e1b53b9355fbe3663e41f01e4b80f8eab60 STX RAT Loader
File 58460f8009df7ca5d2a9b2e9346d940388472cd4cd808ac6c797942824bde299 STX RAT Loader
File 3074a18a349d7e8022fbbdcfc059b7b729862488f1e23adcfe634bb94535fd61 STX RAT Loader

References

To learn how your organization can build cyber resilience and prevent business disruption with eSentire’s Next Level MDR, connect with an eSentire Security Specialist now.

GET STARTED

ABOUT ESENTIRE’S THREAT RESPONSE UNIT (TRU)

The eSentire Threat Response Unit (TRU) is an industry-leading threat research team committed to helping your organization become more resilient. TRU is an elite team of threat hunters and researchers that supports our 24/7 Security Operations Centers (SOCs), builds threat detection models across the eSentire XDR Cloud Platform, and works as an extension of your security team to continuously improve our Managed Detection and Response service. By providing complete visibility across your attack surface and performing global threat sweeps and proactive hypothesis-driven threat hunts augmented by original threat research, we are laser-focused on defending your organization against known and unknown threats.

Back to blog

Take Your Cybersecurity Program to the Next Level with eSentire MDR.

BUILD A QUOTE

Read Similar Blogs

EXPLORE MORE BLOGS