{"id":577,"date":"2026-04-15T14:05:16","date_gmt":"2026-04-15T14:05:16","guid":{"rendered":"https:\/\/quantusintel.group\/osint\/blog\/2026\/04\/15\/malware-analysis-stx-rat\/"},"modified":"2026-04-15T14:05:16","modified_gmt":"2026-04-15T14:05:16","slug":"malware-analysis-stx-rat","status":"publish","type":"post","link":"https:\/\/quantusintel.group\/osint\/blog\/2026\/04\/15\/malware-analysis-stx-rat\/","title":{"rendered":"Malware Analysis: STX RAT"},"content":{"rendered":"<p>d32455fc430ffc13e8a89db9198f17184fd27001fc11a7e9531d6055932853db.bin<\/p>\n<h3>Sample is available on Malware\u00a0Bazaar:<\/h3>\n<figure><img data-opt-id=674464436  fetchpriority=\"high\" decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*3w5ofPdMdrO6CAeYsrP4eA.png\" \/><figcaption><a href=\"https:\/\/bazaar.abuse.ch\/sample\/d32455fc430ffc13e8a89db9198f17184fd27001fc11a7e9531d6055932853db\/\">https:\/\/bazaar.abuse.ch\/sample\/d32455fc430ffc13e8a89db9198f17184fd27001fc11a7e9531d6055932853db\/<\/a><\/figcaption><\/figure>\n<p><strong>References:<\/strong><\/p>\n<ul>\n<li><a href=\"https:\/\/www.esentire.com\/blog\/stx-rat-a-new-rat-in-2026-with-infostealer-capabilities\">STX RAT: A new RAT in 2026 with Infostealer Capabilities<\/a><\/li>\n<li><a href=\"https:\/\/www.cyderes.com\/howler-cell\/how-cpuids-hwmonitor-supply-chain-was-hijacked-to-deploy-stx-rat\">Monitoring the Monitor: How CPUID&#8217;s HWMonitor Supply Chain Was Hijacked to Deploy STX RAT<\/a><\/li>\n<\/ul>\n<h3>Initial stage<\/h3>\n<p>Quick inspection of downloaded file, we see that it is a\u00a0DLL:<\/p>\n<figure><img data-opt-id=674464436  fetchpriority=\"high\" decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*o3Y5NvKhCRvxF-ZWNH0cFA.png\" \/><\/figure>\n<p>DIE output confirms it is a DLL, and likely written in\u00a0C.<\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*cb28wRLO0unJxcU4_K9IuQ.png\" \/><\/figure>\n<p>We do not see anything significant or useful from FLOSS \/\u00a0strings.<\/p>\n<p>CAPA output shows a fair amount of information hinting at PEB walking, function hash resolution and contains an embedded PE\u00a0file.<\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*TnbbaIS_uxnEOZ-KUCbHEg.png\" \/><\/figure>\n<p>Quick check on VirusTotal using the hash, there has been plenty of detections already available.<\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*2QIpmSDkFQHGrVMD-z7coQ.png\" \/><\/figure>\n<p>Through Ghidra, we can see that there a limited number of functions defined:<\/p>\n<figure><img data-opt-id=1321924899  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/306\/1*WOBz395q0Q0Ls_SkCx6eLQ.png\" \/><\/figure>\n<p>Entry function is a DLL entry point. From here on, we will start renaming functions to try making sense and interpreting the decompiled logic.<\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*9RdrdICF5w1ZZ16GtcnXTg.png\" \/><\/figure>\n<p><strong>Logically speaking, we can immediately move on to inspect FUN_180001160 (renamed to decryptCompressExec) since it is called in our entry function:<\/strong><\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*Vd4QhhAddxIZwvn55zJXlQ.png\" \/><\/figure>\n<ul>\n<li>This is the main glue which encapsulates decryption, decompression and then launches the next stage\u00a0payload.<\/li>\n<li>Line 12 calls decryptPayloadXXTEA on the encryptedBlob (0x151a6 bytes) using the 128-bit key at 0x1800586a0. The decrypted result is written back in\u00a0place.<\/li>\n<li>Line 13 sets expectedDecompressionSize to 0x55200\u00a0bytes.<\/li>\n<li>Line 14 calls zlibDecompress, decompressing the encryptedBlob into payloadBuffer. The compressed input is 0x54695\u00a0bytes.<\/li>\n<li>Line 16 checks if decompression succeeded (decompressResult ==\u00a00).<\/li>\n<li>Lines 17-18 resolve kernel32.dll (hash 0x1ff5154e) via GetModuleHandleByHash, then resolve VirtualProtect (hash 0x5733ec35) via GetProcAddressByHash.<\/li>\n<li>Lines 19-20 resolve CreateThread (hash 0x758bd126) via GetProcAddressByHash and store it in _resolveCreateThread.<\/li>\n<li>Line 21 resolves WaitForSingleObject (hash 0x32ac9136) and stores it in resolveWaitForSingleObj.<\/li>\n<li>Lines 22-23 resolve ntdll.dll (hash 0xbda4b2ca) via GetModuleHandleByHash, then resolve FlushInstructionCache (hash 0x5a90917e) via GetProcAddressByHash.<\/li>\n<li>Line 24 calls pCreateThread with ExecDecompressedPayload as the thread entry point, passing param_1 (the original module\u00a0handle).<\/li>\n<li>Line 26 calls resolveWaitForSingleObj on the thread handle with INFINITE (0xffffffff) timeout, blocking until the payload finishes executing.<\/li>\n<\/ul>\n<p><strong>Based on OSINT of some found hashes on already available analysis online, we can verify our own analysis:<\/strong><\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*jg3qn58HD4At69D-le57AQ.png\" \/><figcaption><a href=\"https:\/\/gist.github.com\/N3mes1s\/b5b0b96782b9f832819d2db7c6684f84\">https:\/\/gist.github.com\/N3mes1s\/b5b0b96782b9f832819d2db7c6684f84<\/a><\/figcaption><\/figure>\n<p><strong>FUN_180001030 (renamed to GetProcAddressByHash for analysis)<\/strong><\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*Io6_knV598DjUAJv3hqv-A.png\" \/><\/figure>\n<ul>\n<li>Line 12 parses the PE header, reading e_lfanew at moduleBaseAddr + 0x3C, which gives the offset to the PE signature. From there, +136 (0x88) reaches the export directory RVA, which gets added to the base to give\u00a0lVar5<\/li>\n<\/ul>\n<figure><img data-opt-id=1319011685  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/708\/1*MfOmn0KKcEvh29hitIzr-g.png\" \/><\/figure>\n<ul>\n<li>Line 14 reads AddressOfNames (lVar5 + 32 = 0x20) and resolves it to a pointer. This is the array of RVAs to exported function name\u00a0strings.<\/li>\n<li>Line 16 checks NumberOfNames (lVar5 + 24 = 0x18). If zero, there are no exports to\u00a0search.<\/li>\n<li>Lines 18-25 loop through each name. For each entry it resolves the name RVA to a string pointer, then hashes it using ROR14 (rotate right 14, add char, repeat until\u00a0null).<\/li>\n<li>Line 26 compares the computed hash against the target. On match, lines 27\u201331 do the lookup: it reads the index from AddressOfNameOrdinals (lVar5 + 36 = 0x24) at position uVar2, then uses that ordinal to index into AddressOfFunctions (lVar5 + 28 = 0x1C) and adds moduleBaseAddr to get the final function\u00a0pointer.<\/li>\n<li>Returns 0 on line 38 if nothing\u00a0matched.<\/li>\n<\/ul>\n<p><strong>Function renamed to decryptPayloadXXTEA for\u00a0analysis<\/strong><\/p>\n<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/XXTEA\">XXTEA &#8211; Wikipedia<\/a><\/p>\n<ul>\n<li>This is the XXTEA block cipher decryption routine. It operates on an array of 32-bit words and decrypts them in\u00a0place.<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*AizhmOj2qYVIr7uxUkb6yw.png\" \/><\/figure>\n<ul>\n<li>Line 15 reads the last word of the buffer. Line 16 sets uVar5 to blobSize-1, the index of the last element. Line 17 computes the initial sum\u00a0value.<\/li>\n<li>Each iteration undoes one round of encryption. Line 21 computes e = (sum &gt;&gt; 2) &amp; 3, which selects which of the 4 key words to use for this\u00a0round.<\/li>\n<li>Walks backward through the buffer from the last word to the second. For each word, line 26 reads the previous neighbor (blobAddr[uVar1]), lines 27\u201329 compute the XXTEA mixing function and subtract it from the current word to reverse the encryption. Line 30 updates uVar6 to the newly decrypted word for the next iteration.<\/li>\n<\/ul>\n<p>We can see the reference implementation for XXTEA on Wikipedia, which this code shows close resemblance to:<\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*1F62oaKcTHnZnDzhgU19Hg.png\" \/><figcaption><a href=\"https:\/\/en.wikipedia.org\/wiki\/XXTEA\">https:\/\/en.wikipedia.org\/wiki\/XXTEA<\/a><\/figcaption><\/figure>\n<p><strong>FUN_180001d24 renamed to zlibDecompress<\/strong><\/p>\n<ul>\n<li>This function takes compressed data in zlib format and decompresses it.<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*_kkwr0kpwiOHed12YynC4A.png\" \/><\/figure>\n<ul>\n<li>Lines 13-16 appears to be validating the zlib header. It ensures first two bytes pass the mod-31 checksum, that the compression method is DEFLATE (lower nibble == 8), that the window size is within bounds, and that no preset dictionary is\u00a0used.<\/li>\n<li>Line 17 points to the last 4 bytes of the input stream. Lines 18-21 extract those 4 bytes which are the stored Adler-32 checksum in big-endian order.<\/li>\n<li>Line 22 calls FUN_180001a84 which is the actual raw inflate routine. It is passed the input starting at param_3 + 2 (skipping the 2-byte zlib header) with length param_4-6 (stripping header and 4-byte trailer).<\/li>\n<li>Lines 23-25 verify integrity. FUN_1800013e4 computes an Adler-32 checksum over the decompressed output and compares it against the stored checksum from the\u00a0trailer.<\/li>\n<li>Returns 0 on success, 0xFFFFFFFD on failure (bad header or checksum mismatch).<\/li>\n<\/ul>\n<p><strong>FUN_180001a84 (renamed to rawDeflateInflate for ease of analysis)<\/strong><\/p>\n<ul>\n<li>This appears to be the core decompression engine.<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*9IV3fwRUz3T-v-25lFee7g.png\" \/><\/figure>\n<p><strong>FUN_1800013e4 (renamed to adler32Checksum for analysis)<\/strong><\/p>\n<ul>\n<li>This computes the Adler-32 checksum used by zlib for integrity verification.<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*tuw-BUkQ-inHtI92pm8yag.png\" \/><\/figure>\n<p>Comparison with example online implementation for adler32 (Wikipedia):<\/p>\n<figure><img data-opt-id=771569372  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/1024\/1*Z6boZuBDQOdTdgiIP_CnaA.png\" \/><\/figure>\n<p><strong>FUN_1800010c0 (renamed to GetModuleHandleByHash for analysis)<\/strong><\/p>\n<ul>\n<li>This function resolves a loaded DLL base address using a hash instead of plaintext string to avoid detection.<\/li>\n<\/ul>\n<figure><img data-opt-id=771569372  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/1024\/1*ZMCgg4KJFljKy474xZOUhg.png\" \/><\/figure>\n<ul>\n<li>Lines 14-15 walk the PEB to reach the InMemoryOrderModuleList, then follow Flink to the first loaded\u00a0module.<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*Wc3yDsQ7pi57GXrwyYWyuw.png\" \/><\/figure>\n<figure><img data-opt-id=1112000296  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/866\/1*x6gqN8xUMsa5-xqfQWnwhg.png\" \/><\/figure>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*xPzNS8U17pvMJg8icj53TQ.png\" \/><\/figure>\n<ul>\n<li>The main loop (lines 16-42) goes through each module one by one. For each module it does 3\u00a0things.<\/li>\n<li>First, lines 20\u201331 read the module\u2019s BaseDllName which is stored in Unicode. It grabs only the first byte of each wide character (skipping every second byte) and converts uppercase letters to lowercase by adding 0x20. The result is stored in a local\u00a0buffer.<\/li>\n<li>Second, lines 32\u201339 compute a hash over that lowercase name. The algorithm rotates the accumulator right by 14 bits and adds the next character, repeating until the null terminator.<\/li>\n<li>Third, the function checks if the computed hash matches param_1. If it does the loop breaks. If not, follows the Flink pointer to the next module in the\u00a0list.<\/li>\n<li>If a match is found, the function returns puVar5[4] which is the DllBase of that module. If the loop walks all the way around back to the list head without finding a match (lines 17\u201318), it returns\u00a00.<\/li>\n<\/ul>\n<p><strong>FUN_180001264 (renamed to ExecDecompressedPayload for ease of analysis)<\/strong><\/p>\n<ul>\n<li>This is the thread entry point spawned by decryptCompressExec.<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*1rwpJd5uot29P7rVb4lzBw.png\" \/><\/figure>\n<ul>\n<li>Line 8 reads the entry point offset (0x11F0) from DAT_180058698. This is the offset within the decompressed PE where the reflective loader (init) function\u00a0begins.<\/li>\n<li>Line 9 calls VirtualProtect, marking the payloadBuffer (0x55200 bytes) as PAGE_EXECUTE_READWRITE (0x40). This makes the decompressed PE executable.<\/li>\n<li>Line 10 calls FlushInstructionCache with 0xffffffffffffffff as the process handle, which is the GetCurrentProcess() pseudo-handle (-1). This ensures the CPU does not execute stale cached instructions from before the buffer was\u00a0written.<\/li>\n<\/ul>\n<p><a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/api\/processthreadsapi\/nf-processthreadsapi-flushinstructioncache\">FlushInstructionCache function (processthreadsapi.h) &#8211; Win32 apps<\/a><\/p>\n<ul>\n<li>Line 11 computes the address of the reflective loader by adding the entry point offset to payloadBuffer, then calls it as a function, passing param_1 (the original module handle) and payloadBuffer (the raw PE\u00a0image).<\/li>\n<\/ul>\n<h3>Second Stage<\/h3>\n<p>We can now get the second stage given we\u00a0know:<\/p>\n<ul>\n<li>Stage 1 XXTEA\u00a0key<\/li>\n<li>Decryption routine<\/li>\n<li>Buffers for storing decrypted blobs<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*yeDMsxFtXY7Et-tpWl7RBg.png\" \/><figcaption>second stage\u00a0dll<\/figcaption><\/figure>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*4mjG7ylaRrTOV3b6T_wIjw.png\" \/><figcaption>capa output on second stage\u00a0dll<\/figcaption><\/figure>\n<p>We can see that second stage dll is also heavily flagged by\u00a0now.<\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*nvWn14UEj-IWCDFL2CjMxw.png\" \/><\/figure>\n<p>Hang on\u2026<\/p>\n<p>Opening in Ghidra, we observe that the logic looks extremely similar to our first\u00a0stage.<\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*wGfPgQxSNkYhPrpYnkifUQ.png\" \/><\/figure>\n<h3>Third Stage<\/h3>\n<p>Use a decryptor routine to decrypt using the key defined within each stage! Now we have the third stage\u00a0payload!<\/p>\n<figure><img data-opt-id=128552691  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/594\/1*Jmmzyo6_EAbz1H-H8LVkmg.png\" \/><figcaption>running our decryptor python script given an XXTEA key and a blob at a known location DAT_180004000<\/figcaption><\/figure>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*WshIEjY11VQUTNn0focheA.png\" \/><figcaption>third stage dll\u00a0payload<\/figcaption><\/figure>\n<p>The third stage dll is also heavily detected by\u00a0now.<\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*Xy0wq-Frau1p1oi6NvtRtA.png\" \/><\/figure>\n<p>And\u2026 it looks really similar once again. Something that stands out is that the decompressed size jumped from previous layer of 0x53e00 to 0x91e00. The next layer may be different from the 3 layers we have encountered thus\u00a0far\u2026<\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*0OtPsf4Ph-2toEG2jU3pyA.png\" \/><\/figure>\n<h3>Fourth Stage<\/h3>\n<p>Use the same decryptor script to obtain the fourth stage\u00a0payload.<\/p>\n<figure><img data-opt-id=641623877  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/564\/1*4OspdKsDI2R3iAie5SlH7w.png\" \/><figcaption>obtaining the fourth stage paylad using our decryptor script<\/figcaption><\/figure>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*HtRWTdbA9BaIqhzL2U6qzg.png\" \/><figcaption>fourth stage\u00a0DLL<\/figcaption><\/figure>\n<p>Quick run through FLOSS indicates that this is vastly different from the previous layers we have\u00a0seen.<\/p>\n<ul>\n<li>Indicators hinting at anti-analysis: Checks for QEMU, VirtualBox (vboxservice.exe), and inspects BIOS\/processor strings via registry to detect VMs and sandboxes. Also checks BeingDebugged in the\u00a0PEB.<\/li>\n<li>Indicators of persistence: Uses SoftwareMicrosoftWindowsCurrentVersionRun (nRun), scheduled tasks (Unregister-ScheduledTask), and MSBuild for LotL execution (CommonBuild.proj).<\/li>\n<li>Indicators of execution: PowerShell with -WindowStyle Hidden -ExecutionPolicy Bypass, and\u00a0.NET Framework execution via C:WindowsMicrosoft.NETFramework64v4.0.30319.<\/li>\n<li>Indicators for crypto: SHA1, SHA256, AES, HMAC, Base64, XOR. Used for encrypting C2 traffic and credential theft.<\/li>\n<li>Indicators of registry operations: TypeLib registration, persistence keys, and configuration storage.<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*sxP9yOaXV0yRxpE3i1JFtQ.png\" \/><\/figure>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*aYXPpKRMpuszHYoU73IELw.png\" \/><\/figure>\n<figure><img data-opt-id=1882512057  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/804\/1*0oLbl_gD_2pRyMQdEFsJYQ.png\" \/><\/figure>\n<p>Verifying in CAPA, we see that it does appears to contain logic for credential theft:<\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*RL0_VCtzeqvbeUPYa-JkUw.png\" \/><\/figure>\n<p>We also see in Capa that there are exhibits of same PE header parsing and PEB walking once\u00a0more.<\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*c1JLqsKH5BhDKnaBQp89sw.png\" \/><\/figure>\n<h3>Further analysis of Fourth Stage\u00a0payload:<\/h3>\n<p><strong>Entry point of the main payload in Ghidra relablled for ease of analysis:<\/strong><\/p>\n<ul>\n<li>This is the DllMain of the final payload, called by the reflective loader with param_2 = 1 (DLL_PROCESS_ATTACH).<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*S0dm72B2r4zW-8XgTxmeHg.png\" \/><\/figure>\n<ul>\n<li>Lines 12\u201314 save three globals: a pointer to the embedded config data, the module handle, and the PE base address from the\u00a0loader.<\/li>\n<li>Lines 15\u201317 run decryptorAndResolver once on first load. It decrypts the config, populates the API lookup table, and resolves DLL handles. The flag at DAT_180094b30 prevents it from running\u00a0again.<\/li>\n<li>Line 19 calls envAntiAnalysisCheck to determine if the environment is safe for the malware to execute in (not a VM or debugger).<\/li>\n<li>If safe (0x01): lines 21\u201322 resolve CreateThread (index 0x17) and spawn threadEntryWrapper, which leads to mainRATInitializationSequence. Line 26\u201327 resolve WaitForSingleObject (index 0x134) and wait on the thread. If thread creation fails, line 24 calls processExitHandler to terminate.<\/li>\n<\/ul>\n<p><strong>In the main initialization sequence:<\/strong><\/p>\n<ul>\n<li>This function encapsulates several core functions of the\u00a0RAT.<\/li>\n<\/ul>\n<figure><img data-opt-id=1198507698  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/738\/1*P3xZBMxDvMboHAELKvhBAw.png\" \/><\/figure>\n<ul>\n<li>Lines 5-7 handle environment setup. initAndAntiAnalysis ensures config is decrypted and APIs are resolved, terminating if a VM or debugger is detected. runCallbackTable executes pre-registered function pointers. initCryptoProvider acquires a crypto\u00a0handle.<\/li>\n<li>Lines 8-9 create synchronization primitives (critical section and event) used by C2\u00a0threads.<\/li>\n<li>Lines 10-11 set up security and config. initSecurityInterface loads the SSPI function table. loadC2Config reads C2 connection parameters from the encrypted config\u00a0block.<\/li>\n<li>Lines 12-13 initialize networking. initWinsock starts Winsock 2.2. initNetworkComponents sets up callbacks for managing connections.<\/li>\n<li>Lines 14-17 activate the C2 and handle shutdown. decrementRefCount adjusts a reference counter. initC2Thread launches the main C2 loop. When the C2 loop exits, shutdownAndCleanup runs registered cleanup handlers, then processExitHandler(0) terminates the\u00a0process.<\/li>\n<\/ul>\n<p><strong>Zooming in on loadC2Config function:<\/strong><\/p>\n<ul>\n<li>This function retrieves the C2 connection parameters from the RAT\u2019s embedded configuration.<\/li>\n<\/ul>\n<figure><img data-opt-id=95692054  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/902\/1*YOwe-dT2fafgCEsaq7O6tA.png\" \/><\/figure>\n<ul>\n<li>Line 9 calls getConfigOffset(0, 200) which walks the structured config blob starting at DAT_180094b28, searching for entry index\u00a0200.<\/li>\n<li>Line 10 adds the returned offset to the config base pointer and stores the result in DAT_180094b40. This now points directly to the C2 configuration data.<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*hd9ResR4GI0Rymm1UJ0MBw.png\" \/><\/figure>\n<p><strong>Now zooming in on initC2Thread:<\/strong><\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*WP-QdCpaxduVDu8AdnquuQ.png\" \/><\/figure>\n<ul>\n<li>Line 10 calls c2CommandHandler which handles C2 message parsing, anti-analysis checks, and command dispatch.<\/li>\n<li>Lines 11\u201317 allocate memory and spawn a reconnection handler thread via workerThreadSpawn.<\/li>\n<li>Line 27 registers c2ThreadCleanup via registerCleanupCallback so the thread gets torn down during shutdown.<\/li>\n<li>Line 28 calls terminateProcess(-1) to kill the process after the C2 session ends. The RAT does not leave itself running once communication is complete.<\/li>\n<\/ul>\n<p><strong>For the final part of the analysis, we will just briefly look into the c2CommandHandler:<\/strong><\/p>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*FVT8eh7oPr1udSpv0ukXPw.png\" \/><\/figure>\n<ul>\n<li>Dequeues a pending C2 message within dequeueC2Message function, stores it globally, then passes it to parseC2Response which implements a TLV (Type-Length-Value) protocol\u00a0parser.<\/li>\n<li>Six fields are extracted: primaryPayload (type 2), commandFlags (type 3), enableFlag (type 1), secondaryPayload (type 4), execOffset (type 5), and tertiaryPayload (type\u00a06).<\/li>\n<li>If the raw response starts with MZ it is treated as a direct PE payload. If flag 0x08 is set, execution mode switches to\u00a02.<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*DaUH80EDHz9UpSUrDEexkw.png\" \/><\/figure>\n<figure><img data-opt-id=316926153  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/642\/1*LfMwN2zKe9EcNEwR41Agkg.png\" \/><\/figure>\n<ul>\n<li>Calls detectAnalysisEnvironment which returns a bitmask (0x02 = VM, 0x04 = debugger, 0x40 = sandbox). Two XOR-obfuscated strings are decoded and checked against the environment.<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*SIUwXwazKSmGyTNSEqs3Rw.png\" \/><\/figure>\n<ul>\n<li>If both checks pass, sandbox bits are cleared from the mask. Three conditions are OR\u2019d together: detection mask non-zero, PEB.BeingDebugged set, or FUN_18003cc18 returns true. If any fires, the RAT terminates with a random exit code between 500 and 1500. Not too certain why this range\u00a0though.<\/li>\n<\/ul>\n<figure><img data-opt-id=304726474  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/876\/1*Q90twJnldDdrfoNoyWLnCg.png\" \/><\/figure>\n<ul>\n<li>If tertiaryPayload is non-empty (line 146), parseC2Config (line 148) parses it as structured data and extracts named fields into several\u00a0globals.<\/li>\n<li>If enableFlag is set (line 150) and flag 0x100 is clear (line 151), collectSystemInfo (line 152) queries system properties and stores results in\u00a0globals.<\/li>\n<li>Lines 153\u2013158 resolve and call three APIs (0x12F, 0xEC, 0x7E). Line 159 calls hideWindow which hides the RAT\u2019s window using SW_HIDE and WS_EX_TOOLWINDOW. Line 161 calls exfiltrateCollectedData which packages data from the populated globals with a decoded keyword and sends it to the C2\u00a0server.<\/li>\n<\/ul>\n<figure><img data-opt-id=674464436  decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/936\/1*t-sjP-7iBmw_jKPpZyaWVw.png\" \/><\/figure>\n<ul>\n<li>Flag 0x100 (line 163) sets execution mode to 3, constructs a command from tertiaryPayload and primaryPayload, and sends it via sendCmdAndWait with a 15 second\u00a0timeout.<\/li>\n<li>Flag 0x02 (line 174) hides the RAT\u2019s window from the\u00a0taskbar.<\/li>\n<li>Flag 0x04 (line 178) copies secondaryPayload into RWX memory and executes it as shellcode in a worker\u00a0thread.<\/li>\n<li>Flag 0x10 (line 190) spawns a thread running executeSecondaryPayload, which processes secondaryPayload through a different execution path than flag\u00a00x04.<\/li>\n<li>Flag 0x40 (line 198) loads a DLL from secondaryPayload, resolves a known export by trying three obfuscated names, and runs it in a worker\u00a0thread.<\/li>\n<\/ul>\n<p>At this point we have traced a fair bit of the full execution chain from the initial DLL through three layers of XXTEA + zlib nesting to the core STX RAT payload. We managed to uncover the reflective PE loader, API hashing techniques, encrypted configuration parsing, C2 communication sequence, anti-analysis checks and the command dispatch mechanism with its various execution capabilities.<\/p>\n<p>There remains significant depth to explore in the final payload, particularly the C2 session manager, the network protocol implementation (which eSentire documents as X25519 ECDH + ChaCha20-Poly1305), persistence installation routines, and the individual capability modules such as HVNC and credential theft.<\/p>\n<p>Thanks for\u00a0reading!<\/p>\n<p>If this helped you in anyway, do drop a follow and consider checking out my other write-ups!<\/p>\n<p><strong>Disclaimer<\/strong><\/p>\n<p>This analysis is published strictly for educational and research purposes. The goal is to help security professionals, students, and researchers understand malware behavior and improve defensive capabilities. Every effort has been made to ensure accuracy, but reverse engineering is an interpretive process and some of my conclusions may be incorrect or incomplete. If you spot errors, I welcome corrections.<\/p>\n<p>No part of this research is intended to facilitate, encourage, or enable malicious activity. All samples were obtained from public sources and analyzed in isolated environments. Do not attempt to execute, distribute, or weaponize any of the samples or techniques described here.<\/p>\n<ul>\n<li><a href=\"https:\/\/medium.com\/@cydtseng\/malware-analysis-payloadfinal-bin-agent-tesla-c38cb9bc8c88\">Malware Analysis: payloadfinal.bin (Agent Tesla)<\/a><\/li>\n<li><a href=\"https:\/\/osintteam.blog\/malware-analysis-lista-de-productos-august2025-js-redline-stealer-76f6291a6b35\">Malware Analysis: Lista de productos-AUGUST2025.js (Redline Stealer)<\/a><\/li>\n<li><a href=\"https:\/\/osintteam.blog\/malware-analysis-documento-js-4c8f94030e61\">Malware Analysis: Documento.js<\/a><\/li>\n<\/ul>\n<p><img data-opt-id=574357117  decoding=\"async\" src=\"https:\/\/medium.com\/_\/stat?event=post.clientViewed&amp;referrerSource=full_rss&amp;postId=65e95da06f2f\" width=\"1\" height=\"1\" alt=\"\" \/><\/p>\n<hr \/>\n<p><a href=\"https:\/\/osintteam.blog\/malware-analysis-stx-rat-65e95da06f2f\">Malware Analysis: STX RAT<\/a> was originally published in <a href=\"https:\/\/osintteam.blog\/\">OSINT Team<\/a> on Medium, where people are continuing the conversation by highlighting and responding to this story.<\/p>","protected":false},"excerpt":{"rendered":"<p>d32455fc430ffc13e8a89db9198f17184fd27001fc11a7e9531d6055932853db.bin Sample is available on Malware\u00a0Bazaar: https:\/\/bazaar.abuse.ch\/sample\/d32455fc430ffc13e8a89db9198f17184fd27001fc11a7e9531d6055932853db\/ References: STX RAT: A new RAT in 2026 with Infostealer Capabilities Monitoring the Monitor: How CPUID&#8217;s HWMonitor Supply Chain Was Hijacked to Deploy STX RAT Initial stage Quick inspection of downloaded file, we see that it is a\u00a0DLL: DIE output confirms it is a DLL, and likely written &#8230; <a title=\"Malware Analysis: STX RAT\" class=\"read-more\" href=\"https:\/\/quantusintel.group\/osint\/blog\/2026\/04\/15\/malware-analysis-stx-rat\/\" aria-label=\"Read more about Malware Analysis: STX RAT\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":578,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-577","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/quantusintel.group\/osint\/wp-json\/wp\/v2\/posts\/577","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/quantusintel.group\/osint\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/quantusintel.group\/osint\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/quantusintel.group\/osint\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/quantusintel.group\/osint\/wp-json\/wp\/v2\/comments?post=577"}],"version-history":[{"count":0,"href":"https:\/\/quantusintel.group\/osint\/wp-json\/wp\/v2\/posts\/577\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/quantusintel.group\/osint\/wp-json\/wp\/v2\/media\/578"}],"wp:attachment":[{"href":"https:\/\/quantusintel.group\/osint\/wp-json\/wp\/v2\/media?parent=577"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/quantusintel.group\/osint\/wp-json\/wp\/v2\/categories?post=577"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/quantusintel.group\/osint\/wp-json\/wp\/v2\/tags?post=577"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}