Rhadamanthys v0.5.0 - a deep dive into the stealer’s components - Check Point Research By shlomoo@checkpoint.com Published: 2023-12-14 · Archived: 2026-04-02 12:42:39 UTC Research by: hasherezade Highlights The Rhadamanthys stealer is a multi-layer malware, sold on the black market, and frequently updated. Recently the author released a new major version, 0.5.0. In the new version, the malware expands its stealing capabilities and also introduces some general-purpose spying functions. A new plugin system makes the malware expandable for specific distributor needs. The custom executable formats, used for modules, are unchanged since our last publication (XS1 and XS2 formats are still in distribution). Check Point Research (CPR) provides a comprehensive review of the agent modules, presenting their capabilities and implementation, with a focus on how the stealer components are loaded and how they work. Introduction Rhadamanthys is an information stealer with a diverse set of modules and an interesting multilayered design. In our last article on Rhadamanthys [1], we focused on the custom executable formats used by this malware and their similarity to a different family, Hidden Bee, which is most likely its predecessor. In this article we do a deep dive into the functionality and cooperation between the modules. The first part of the article describes the loading chain that is used to retrieve the package with the stealer components. In the second part, we take a closer look at those components, their structure, abilities, and implementation. Changes in version 0.5.0 Since we published our previous reports [1][2], Rhadamanthys keeps evolving. At the beginning of October, the author announced version 0.5.0 which comes with many changes, and interesting features. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 1 of 55 Figure 1 – Telegram status of the author, announcing version 0.5.0. The full list of changes, as described by the author: Figure 2 – Fragment of the changelog published on the author’s Telegram channel (full version below). 02. Diversify the construction of stubs and provide x86 x32 native Exe Shellcode Dotnet4 Dotnet2 to better adapt to various usage scenarios and crypt service needs. 03. The client execution process is completely rewritten, and the BUG in the syscall unhook code that caused the crash in the old version is fixed. The execution success rate is very high, and the runtime status is better. 04. Fixed the wallet upgrade support for several wallets where the cracking algorithm fails. Currently supported ) Online real-time brute force cracking 05. Fixed Discord token acquisition, the correct encrypted token can now be decoded. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 2 of 55 06. Break through the browser data acquisition when the browser is protected by third-party programs, and add the login data decryption algorithm of 360 Secure Browser 07. The panel search condition settings have been upgraded. You can now select conditions in batches and select categories with one click. 08. Add a quick setting search filter menu to directly menu the search conditions you need to check frequently. 09. Modify some changes required by users in the Telegram notification module and add new templates for use 10. When building a page, the traffic source tag can directly set the previously used tag, and the URL address will be updated simultaneously. 11. If permissions permit, data collection under other user accounts used on the same machine is supported. 12. The file collection module adds browser extension collection settings. For the Chrome kernel browser, you only need to provide the extension directory name and whether to collect Local Storage data at the same time. Firefox kernel browser can provide extension ID 13. Fix the issue of using the browser to use the online password library after logging in to a Google account in Chrome, and obtaining the login password. 14. The task module has been greatly upgraded, and a new plug-in module has been introduced to support users in secondary development of their own plug-ins. Supports multiple task execution modes: In Memory LoadPE Execution DotNet Reflection Execution DotNet Extension Execution DotNet Extension with Zip Execution 15. Keylogger: supports recording all keyboard input, process details, file name, window title, supports setting process filtering, sending time, buffer size 16. Data spy plug-in: currently supports correct login access and IP username and password for remote RDP access. The correct certificate file and password imported by the user. 17. Plug-ins and loader modules support secondary development and provide SDK support. V0.5.0 Change List 01. Added observer mode 02. Diversify the construction of stubs and provide x86 x32 native Exe Shellcode Dotnet4 Dotnet2 to better adapt to various usage scenarios and crypt service needs. 03. The client execution process is completely rewritten, and the BUG in the syscall unhook code that caused the crash in the old version is fixed. The execution success rate is very high, and the runtime status is better. 04. Fixed the wallet upgrade support for several wallets where the cracking algorithm fails. Currently supported (UniSat Wallet Tronlink Trust Terra Station TokenPocket Phantom Metamask KardiaChain Exodus Desktop Exodus Web3 Binance ) Online real-time brute force cracking 05. Fixed Discord token acquisition, the correct encrypted token can now be decoded. 06. Break through the browser data acquisition when the browser is protected by third-party programs, and add the login data decryption algorithm of 360 Secure Browser 07. The panel search condition settings have been upgraded. You can now select conditions in batches and select categories with one click. 08. Add a quick setting search filter menu to directly menu the search conditions you need to check frequently. 09. Modify some changes required by users in the Telegram notification module and add new templates for use 10. When building a page, the traffic source tag can directly set the previously used tag, and the URL address will be https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 3 of 55 updated simultaneously. 11. If permissions permit, data collection under other user accounts used on the same machine is supported. 12. The file collection module adds browser extension collection settings. For the Chrome kernel browser, you only need to provide the extension directory name and whether to collect Local Storage data at the same time. Firefox kernel browser can provide extension ID 13. Fix the issue of using the browser to use the online password library after logging in to a Google account in Chrome, and obtaining the login password. 14. The task module has been greatly upgraded, and a new plug-in module has been introduced to support users in secondary development of their own plug-ins. Supports multiple task execution modes: Normal execution In Memory LoadPE Execution Powershell Execution DotNet Reflection Execution DotNet Extension Execution DotNet Extension with Zip Execution VbScript Execution JScript Execution X86 shellcode execution X64 shellcode execution Native Plugin Loader 15. Keylogger: supports recording all keyboard input, process details, file name, window title, supports setting process filtering, sending time, buffer size 16. Data spy plug-in: currently supports correct login access and IP username and password for remote RDP access. The correct certificate file and password imported by the user. 17. Plug-ins and loader modules support secondary development and provide SDK support. V0.5.0 Change List 01. Added observer mode 02. Diversify the construction of stubs and provide x86 x32 native Exe Shellcode Dotnet4 Dotnet2 to better ada 03. The client execution process is completely rewritten, and the BUG in the syscall unhook code that caused t 04. Fixed the wallet upgrade support for several wallets where the cracking algorithm fails. Currently support (UniSat Wallet Tronlink Trust Terra Station TokenPocket Phantom Metamask KardiaChain Exodus Desktop Exodus Web3 Binance ) Online real-time brute force cracking 05. Fixed Discord token acquisition, the correct encrypted token can now be decoded. 06. Break through the browser data acquisition when the browser is protected by third-party programs, and add 07. The panel search condition settings have been upgraded. You can now select conditions in batches and selec 08. Add a quick setting search filter menu to directly menu the search conditions you need to check frequently 09. Modify some changes required by users in the Telegram notification module and add new templates for use 10. When building a page, the traffic source tag can directly set the previously used tag, and the URL address 11. If permissions permit, data collection under other user accounts used on the same machine is supported. 12. The file collection module adds browser extension collection settings. For the Chrome kernel browser, you 13. Fix the issue of using the browser to use the online password library after logging in to a Google account 14. The task module has been greatly upgraded, and a new plug-in module has been introduced to support users i Supports multiple task execution modes: Normal execution In Memory LoadPE Execution Powershell Execution DotNet Reflection Execution DotNet Extension Execution DotNet Extension with Zip Execution VbScript Execution JScript Execution X86 shellcode execution X64 shellcode execution https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 4 of 55 Native Plugin Loader 15. Keylogger: supports recording all keyboard input, process details, file name, window title, supports setti 16. Data spy plug-in: currently supports correct login access and IP username and password for remote RDP acce 17. Plug-ins and loader modules support secondary development and provide SDK support. In this blog, we present a walkthrough a sample that belongs to this release. The initial loader The initial loader is a 32-bit Windows executable (PE). It was in large part rewritten, but still contains artifacts that make it similar to the previous edition (0.4.9). The author added a check of the executable’s name. When the samples are uploaded to sandboxes for automated analysis, they are often renamed as hashes. Therefore, if the name consists of characters from a hexadeciamal charset [0-9, a-f, A-F], and its length is 16, 32, 40, or 64, the malware assumes that it is being analyzed, and exits immediately. Similarly as we saw in the earlier releases, the initial executable contains the configuration (embedded by the builder) as well as the package with additional modules, that constitutes the next stage. As the execution progresses, these later components are unpacked, and the configuration is passed to them. Figure 3 – Overview of the relationship between the components used at this stage. The first change that we can observe during the initial triage is a new section:  .textbss . This section is empty in the raw binary (raw size = 0). However, will be filled at runtime with an unpacked content: a shellcode similar to the one used by the previous versions. The difference is that in the past versions the analogous code was loaded into a newly allocated memory region. Regardless of location, its role didn’t change: it is meant for unpacking and loading the first module from the package. Below is a fragment of the tracelog (created with TinyTracer) that illustrates the loading of the shellcode, and the calls executed from it (v.0.5.0): 1e91;kernel32.WaitForSingleObject 1e99;kernel32.CloseHandle 1eb7;[.text] -> [.textbss] ; <- redirection to the code that unpacks the XS module 27000;section: [.textbss]** 27311;kernel32.VirtualAlloc 270c1;kernel32.VirtualAlloc https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 5 of 55 2726d;kernel32.VirtualFree 27363;kernel32.VirtualAlloc 273bd;kernel32.VirtualProtect 273f0;kernel32.VirtualFree 27052;called: ?? [12081000+8a] ; <- redirection to the new module (XS) 1e91;kernel32.WaitForSingleObject 1e99;kernel32.CloseHandle 1eb7;[.text] -> [.textbss] ; <- redirection to the code that unpacks the XS module 27000;section: [.textbss]** 27311;kernel32.VirtualAlloc 270c1;kernel32.VirtualAlloc 2726d;kernel32.VirtualFree 27363;kernel32.VirtualAlloc 273bd;kernel32.VirtualProtect 273f0;kernel32.VirtualFree 27052;called: ?? [12081000+8a] ; <- redirection to the new module (XS) 1e91;kernel32.WaitForSingleObject 1e99;kernel32.CloseHandle 1eb7;[.text] -> [.textbss] ; <- redirection to the code that unpacks the XS module 27000;section: [.textbss]** 27311;kernel32.VirtualAlloc 270c1;kernel32.VirtualAlloc 2726d;kernel32.VirtualFree 27363;kernel32.VirtualAlloc 273bd;kernel32.VirtualProtect 273f0;kernel32.VirtualFree 27052;called: ?? [12081000+8a] ; <- redirection to the new module (XS) For comparison, this is the corresponding tracelog fragment from version 0.4.9: 18b43;kernel32.HeapDestroy 184dc;ntdll.ZwProtectVirtualMemory 184e6;called: ?? [a7a0000+0] <- redirection to the shellcode that unpacks the XS module > a7a0000+2fe;kernel32.LocalAlloc > a7a0000+ba;kernel32.LocalAlloc > a7a0000+260;kernel32.LocalFree > a7a0000+34c;kernel32.VirtualAlloc > a7a0000+3a4;kernel32.VirtualProtect > a7a0000+3bb;kernel32.LocalFree > a7a0000+52;called: ?? [10641000+88] <- redirection to the new module (XS) 18b3a;kernel32.HeapFree 18b43;kernel32.HeapDestroy 184dc;ntdll.ZwProtectVirtualMemory 184e6;called: ?? [a7a0000+0] <- redirection to the shellcode that unpacks the XS module > a7a0000+2fe;kernel32.LocalAlloc > a7a0000+ba;kernel32.LocalAlloc > a7a0000+260;kernel32.LocalFree > a7a0000+34c;kernel32.VirtualAlloc > a7a0000+3a4;kernel32.VirtualProtect > a7a0000+3bb;kernel32.LocalFree > a7a0000+52;called: ?? [10641000+88] <- redirection to the new module (XS) https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 6 of 55 18b3a;kernel32.HeapFree 18b43;kernel32.HeapDestroy 184dc;ntdll.ZwProtectVirtualMemory 184e6;called: ?? [a7a0000+0] <- redirection to the shellcode that unpacks the XS module > a7a0000+2fe;kernel32.LocalAlloc > a7a0000+ba;kernel32.LocalAlloc > a7a0000+260;kernel32.LocalFree > a7a0000+34c;kernel32.VirtualAlloc > a7a0000+3a4;kernel32.VirtualProtect > a7a0000+3bb;kernel32.LocalFree > a7a0000+52;called: ?? [10641000+88] <- redirection to the new module (XS) The next stage, as before, is in a custom executable format. The format itself hasn’t changed since the release of 0.4.9. Once more we are dealing with XS1 (as described in our article [1]) which can be converted into a PE with the help of our tools. The 2nd stage loader (XS1) The component revealed (in the XS1 format) is part of the second stage of the loading process. Looking at this module we can see many similarities to the one described in [1]. Yet, clearly some parts are enhanced and improved. One of the changes shows up in the initial triage, at the attempt to dump strings from the binary. In the past, we could obtain a lot of hints about the module functionality by dumping the strings i.e. with the help of the Flare FLOSS tool. This is no longer possible as the author decided to obfuscate them (more details in “String deobfuscation and the use of TLS”). After converting the module to PE, and opening it in IDA, we can follow up with a more detailed assessment. Looking at the outline of the start function, we see a slightly different, refined design. Figure 4 – The main_func is now passed as a callback to the function that initializes the XS module (loads imports, allocates storage, etc) As before, the module uses the configuration passed from the previous layer. The decoded buffer starts with the  !RHY  marker, and contains, among others, the URL of the C2 to be contacted. This is the reconstructed structure: struct rhy_config { DWORD magic; BYTE flags1; DWORD flags2; _BYTE iv[16]; _BYTE key[32]; char C2_URL[128]; }; struct rhy_config { DWORD magic; BYTE flags1; DWORD flags2; _BYTE iv[16]; _BYTE key[32]; char C2_URL[128]; }; https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 7 of 55 Just like in the previous version, the connection with the C2 is established by the  netclient  module which is loaded from package #1 (see Figure 3). In addition, the current component unpacks and uses multiple modules that are shipped in package #1. We already saw some of them in the previously described versions. The full list is below. Name Format Description New in 0.5.0? stage.x86 shellcode (32-bit) Shellcode used in the injection of the main module of the Stage 2 into another process; responsible for accessing the named mapping, copying its content, and reloading the main module in the context of the new process ✓ early.x86 XS Process injector (using raw syscalls) ✓ early.x64 XS Process injector (using raw syscalls) ✓ phexec.bin XS Injects from a 32-bit process into a 64-bit – prepare.bin shellcode (64-bit) – unhook.bin XS Checks DLLs against hooks, and does unhooking if needed – strategy.x86 XS Checks if any of the forbidden processes is running (evasion) ✓ process.x TXT List of forbidden processes (evasion) ✓ ua.txt TXT A list of user-agents (a random user-agent from the list will be selected and used for the internet connection) – dt.x86 XS Evasion checks based on Al-Khaser – proto.x86 shellcode Encrypts and decrypts netclient module with the help of RC4 and a random key – netclient.x86 XS Responsible for the connection with the C2 and downloading of further modules – A simplified flow: https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 8 of 55 Figure 5 – A high-level overview of the relationships between the components at this stage. More details about the execution flow, and possible diversions from the presented path, are explained later in this report. String deobfuscation and the use of TLS One of the new features in this release is the introduction of TLS (Thread Local Storage) for temporary buffers. They are used, among others, for decoding obfuscated strings. TLS is first allocated in the initialization function (denoted as  init_xs_module  in Figure 4). The value received from TlsAlloc is stored in a global variable. The malware then allocates a custom structure with a buffer and attaches it to the TLS. Later on, that saved buffer is retrieved and used multiple times, as a workspace for deobfuscating data, such as strings. Figure 6 – The string deobfuscation function is applied on a hardcoded value (pointing to the encrypted string). The string decryption function is passed as a callback to the function retrieving a buffer attached to the TLS storage. Figure 7 – The string decryption function is set as a callback to the function fetching the buffer from TLS. After the string is used, the buffer is cleared (see Figure 6). The use of TLS in the implementation of this functionality is quite atypical, and it isn’t clear what was the reason behind this design. The algorithms used for the string deobfuscation differ at different stages of the malware. In the case of the current module (XS1, Stage 2) the following algorithm is deployed: def mix_key_round(ctx, size, key3, key2, key_size): https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 9 of 55 result = (key2 + ((val >> 5) & 0xFF)) + ctx[(val % size)] + (i * (((val + key3) >> 3) & 0xFF)) + 1 ctx[i] = (ctx[i] + result) & 0xFF def decrypt_data(in_buf, in_size, key_buf, key_size, key2, key3): ctx = [key_buf[(i % key_size)] for i in range(in_size)] ctx = mix_key_round(ctx, in_size, key3, key2, key_size) out_buf[i] = (ctx[i] ^ in_buf[i]) & 0xFF def decrypt_string(in_buf, key_buf): return decrypt_data(in_buf, len(in_buf), key_buf, 16, 0x3779E9B, 0) #!/usr/bin/env python3 def mix_key_round(ctx, size, key3, key2, key_size): if not size: return for i in range(size): pos = key_size % size key_size += 87 val = ctx[pos] result = (key2 + ((val >> 5) & 0xFF)) + ctx[(val % size)] + (i * (((val + key3) >> 3) & 0xFF)) + 1 ctx[i] = (ctx[i] + result) & 0xFF return ctx def decrypt_data(in_buf, in_size, key_buf, key_size, key2, key3): out_buf = [0] * in_size ctx = [key_buf[(i % key_size)] for i in range(in_size)] for _ in range(4): ctx = mix_key_round(ctx, in_size, key3, key2, key_size) for i in range(in_size): out_buf[i] = (ctx[i] ^ in_buf[i]) & 0xFF return out_buf def decrypt_string(in_buf, key_buf): return decrypt_data(in_buf, len(in_buf), key_buf, 16, 0x3779E9B, 0) #!/usr/bin/env python3 def mix_key_round(ctx, size, key3, key2, key_size): if not size: return for i in range(size): pos = key_size % size key_size += 87 val = ctx[pos] result = (key2 + ((val >> 5) & 0xFF)) + ctx[(val % size)] + (i * (((val + key3) >> 3) & 0xFF)) + 1 ctx[i] = (ctx[i] + result) & 0xFF return ctx def decrypt_data(in_buf, in_size, key_buf, key_size, key2, key3): out_buf = [0] * in_size ctx = [key_buf[(i % key_size)] for i in range(in_size)] for _ in range(4): ctx = mix_key_round(ctx, in_size, key3, key2, key_size) for i in range(in_size): out_buf[i] = (ctx[i] ^ in_buf[i]) & 0xFF return out_buf def decrypt_string(in_buf, key_buf): return decrypt_data(in_buf, len(in_buf), key_buf, 16, 0x3779E9B, 0) The decryption key is stored at the beginning of the block and is always 16 bytes long. The data is terminated by 0. The IDA script for strings deobfuscation (assuming that the decrypting functions are named, appropriately:  dec_cstring  and  dec_wstring ): https://gist.github.com/hasherezade/c7701821784c436d40d1442c1a623614 The list of the deobfuscated strings for the Stage 2 loader: https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 10 of 55 https://gist.github.com/hasherezade/fb91598f6de62bdecf06edf9606a54fb Raw syscalls and Heaven’s Gate One of the techniques that we can observe across different Rhadamanthys modules is the use of raw syscalls for calling native API. This is a known way to evade function hooking and monitoring, and also helpful in obfuscating the names of the APIs used. This technique has a long history and occurs in multiple different variants (described, i.e. here: [3]). The method relies on the fact that each native system function (implemented kernel-mode) is represented by a syscall ID. These IDs may differ depending on the Windows version. For the programmer’s convenience, Windows allows access to them via API exported by system DLLs such as NTDLL and WIN32U. NTDLL is often hooked by antimalware products, in order to watch the called APIs and detect suspicious activity. Malware tries to bypass the installed hooks by copying the numbers of syscalls directly to its own stubs, so that it won’t have to use the NTDLL. However, doing so generates another event that some monitoring tools may find suspicious, as it’s not usual to execute a syscall from a non-system module. Looking at the implementation, we can see that the author was well aware of this problem and used a variant of the technique called indirect syscalls. The stub within the malware only prepares the syscall and its actual execution is done by returning to the NTDLL at the end of a function. In this way, the initial part of the function that contains the hook is bypassed, but the syscall is called from the NTDLL. Figure 8 – Implementation of indirect syscalls used by Rhadamanthys. The raw syscalls are used by Rhadamanthys in both 32 and 64-bit modules. As we know, a syscall can be performed only from a module that has the same bitness as the operating system. Doing a syscall from the WoW64 process (32-bit process on 64-bit Windows) requires a different approach, and using wrappers that may be different for different versions of Windows [4]. Underneath, each of those wrappers temporarily switch the process from 32-bit to 64-bit mode. The author of Rhadamanthys decided not to use existing wrappers, but instead implement his own, using the Heaven’s Gate [6] technique. Execution flow The role of the modules involved in the Stage 2 hasn’t changed since the last version. They are meant to prepare and obfuscate the downloading of the actual stealers that are shipped in package #2 supplied by the C2. As we know, the download operation is performed by the  netclient  module. The author used several other modules to scrupulously check the environment and make it harder for an analyst or various monitoring tools to track when the download occurs. Depending on the settings, it is possible to load next modules into the current process, or to add an extra round by injecting the current module into a new process, and deleting the original file. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 11 of 55 Figure 9 – Overview of the main function. Different execution paths can be deployed depending on the flags set in the configuration. If the restart flag is set, on the first run the module injects itself into a new process. In the case of the analyzed sample, the restart flag was selected. This caused the main loader module to run twice, with two different paths of execution. On the first run, the malware injects itself into a newly created process. It is implemented in the following steps: 1. Creates a named mapping where it places important data, such as encrypted config, the package containing later modules, the path to the original sample, checksums of functions to be used, etc. 2. Unpacks modules implementing the process injection, 32 or 64-bit, depending on the system. These modules export several helper functions that are implemented using raw syscalls (for more about the method used see “Raw syscalls and Heaven’s Gate”). In the currently analyzed sample, the modules were named  early.x86  and  early.x64  . They may be different depending on the build, as since 0.5.0 distributors can customize what injection method will be used. 3. The loader creates a new 32-bit process, and implants there the  stage.x86  with the help of functions exported from the injector component. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 12 of 55 4. The implanted shellcode is responsible for accessing the named mapping, copying its content, and reloading the Stage 2 module in the context of the new process. It then calls an alternative variant of the Stage 2 main function ( main_alt  in the XS1 structure [1]), to deploy the second execution path. The target for the injection is selected from a hardcoded list, and can be one of the following: L"%Systemroot%\\system32\\dialer.exe" L"%Systemroot%\\system32\\openwith.exe" L"%Systemroot%\\system32\\dllhost.exe" L"%Systemroot%\\system32\\rundll32.exe" L"%Systemroot%\\system32\\dialer.exe" L"%Systemroot%\\system32\\openwith.exe" L"%Systemroot%\\system32\\dllhost.exe" L"%Systemroot%\\system32\\rundll32.exe" L"%Systemroot%\\system32\\dialer.exe" L"%Systemroot%\\system32\\openwith.exe" L"%Systemroot%\\system32\\dllhost.exe" L"%Systemroot%\\system32\\rundll32.exe" The execution second path is deployed when the module runs inside the new process. It involves deleting the original malware file (if the relevant flag was selected in the configuration) and loading the other modules from package #1, including  netclient . Figure 10 – The alternative version of the main function (main_alt), called from the injected process. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 13 of 55 After performing the evasion checks, and making sure that the process is not monitored (with the help of additional modules), the malware connects to the C2 and downloads the next package (package #2 – Figure 5) with the stealer modules. Just like in the previous version, the package is shipped in a steganographic way, appended to the JPG or WAV file (for more details, refer to [1]). Retrieving Stage 3: stealer components Figure 11 – Execution flow of the modules Two modules are involved in downloading the payload:  netclient  (in XS1 format) and  proto  (shellcode). They are both loaded into the same memory region, and  proto  is copied after the  netclient . First  netclient  is called from the main module, with the appropriate parameters: the address of the C2 copied from the configuration, and the User Agent, selected from the  ua.txt  list (if the list fails to load, a hardcoded User Agent is used as a backup). Figure 12 – The Entry Point of the netclient module is called in the Event callback function The interesting element of the flow is that at the beginning of the  netclient  execution, the main XS1 component gets encrypted. It is decrypted later, just before the execution returns to it. The encryption and decryption are done with the help of the  proto  module, which is called twice by  netclient . This is yet another obfuscation step to minimize the memory artifacts. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 14 of 55 Figure 13 – If we try to view the code of the main module, i.e. go to the offset that called  netclient , we can see that it no longer makes sense – as it was encrypted. The  netclient  is responsible for connecting to the C2 and downloading the payload, then decrypting it. As before ([1]) the correctness of the resulting buffer is verified by comparing the hash calculated from the content with the expected hash that is stored in the header of the data block. In the currently analyzed case, the payload was shipped appended to a file in the WAV format. Figure 14 – The downloaded package after decryption After the payload is downloaded, and passes the verification step,  netclient  module calls  proto  for the second time to decrypt the previously encrypted main module (XS1), using the RC4 algorithm and the key that was generated and saved at the first run. The execution goes back to the (now decrypted) main module, which first destroys the memory region where  netclient  and  proto  were loaded. It then proceeds to load the next stage from the downloaded package #2. Depending on whether the system is 32 or 64-bit, further execution may proceed in the current process, or in a newly created, 64-bit process, where the required elements are injected. On a 64-bit system Most of the time we encounter a 64-bit version of Windows, which means there are several extra steps before the content from the downloaded package can be fetched and executed. The malware creates a new, 64-bit process, selecting one of the paths from the hardcoded list: L"%Systemroot%\\system32\\credwiz.exe" L"%Systemroot%\\system32\\OOBE-Maintenance.exe" L"%Systemroot%\\system32\\openwith.exe" L"%Systemroot%\\system32\\dllhost.exe" L"%Systemroot%\\system32\\rundll32.exe" https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 15 of 55 L"%Systemroot%\\system32\\credwiz.exe" L"%Systemroot%\\system32\\OOBE-Maintenance.exe" L"%Systemroot%\\system32\\openwith.exe" L"%Systemroot%\\system32\\dllhost.exe" L"%Systemroot%\\system32\\rundll32.exe" L"%Systemroot%\\system32\\credwiz.exe" L"%Systemroot%\\system32\\OOBE-Maintenance.exe" L"%Systemroot%\\system32\\openwith.exe" L"%Systemroot%\\system32\\dllhost.exe" L"%Systemroot%\\system32\\rundll32.exe" As writing into a 64-bit process from a 32-bit one is not officially supported by the Windows API, it uses Heaven’s Gate technique [6] to do so. This time the injection functions are encapsulated in an additional module from package #1:  phexec.bin . The malware now uses a 64-bit module  prepare.bin , which is to be injected and used to retrieve the downloaded package #2 from inside the new process. The module  prepare.bin  is a shellcode containing an empty data section to be filled. The unfilled version of the section starts with the marker  YHR!  which is replaced with  0xDEADBEEF  after it is filled. Figure 15 – Filled in data within the prepare.bin module. Data transmission using BaseNamedObject and the use of Exception Handlers The loader (32-bit executable) creates a named object that is used to share the information between the current process and the infected one (64-bit). The content downloaded from the C2 is passed to this object and then received in the other process, via  prepare.bin . During the injection, the  prepare.bin  is implanted into a fresh 64-bit process. The execution is redirected by patching the Entry Point of the main executable with a jump that leads to the shellcode. The shellcode ( prepare.bin ) contains a loop waiting for the BaseNamedObject to be filled. It sets a Vectored Exception Handler that is triggered after the data transfer from the other process is finished. The use of an exception handler is intended to be an additional obfuscation of the execution flow. During its execution, the shellcode installs some more patches in the original executable of the process, and returns back to the patched code after the exception handler was registered. The overwritten code changes the memory protection and waits for the first malware process to send data over the BaseNamedObject. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 16 of 55 Figure 16 – The code of the infected executable overwritten during shellcode execution, performing wait. When the wait is over, it triggers an exception executing the  int3  instruction. As a result the execution lands in the Vectored Exception Handler, that is installed by the shellcode. The exception handler is responsible for unpacking the retrieved package, fetching from it the next shellcode, and redirecting the execution. The further flow, starting from the retrieved shellcode from package #2, is analogous to the execution on a 32-bit system described below. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 17 of 55 Figure 17 – The exception handler responsible for unpacking the retrieved package, fetching from it a shellcode, and redirecting the execution. On 32-bit system The first element fetched from the new package is a shellcode. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 18 of 55 Figure 18 – Two alternative paths after the data is downloaded from the C2. If the system is 32-bit, the shellcode from the downloaded package is copied into the current process, and then run in its context. Otherwise, it is injected into a new, 64-bit process. The shellcode decrypts and decompresses the rest of the downloaded package. It then fetches the main stealer module, which is in the XS2 format [1]. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 19 of 55 Figure 19 – The module in XS2 format revealed in memory The unpacked module is loaded into a newly allocated memory region. However, the redirection contains some additional steps. Once again, the author took extra care to minimize the memory artifacts and made sure that the loading shellcode is released after use. A a small chunk of the shellcode is copied to the new memory region, just after the loaded XS2 module. After that block, other necessary data is copied, including the pointer to the package, and the Entry Point of the next module. After finishing, the loading function jumps to such a prepared stub. As the stub is in the new memory region, it can free the region containing the unpacking shellcode before the redirection to the next stage. Figure 20 – The copied stub frees the loading shellcode, and then redirects to the next module. Stage 3: coredll.bin (XS2) After passing the full loading chain, the execution finally reaches the main stealer module: coredll.bin (in XS2 format). The module coordinates all the work, collects the results, and reports them back to the C2. It is also responsible for retrieving other modules from package #2, and using them to perform various tasks. In the current release, the following modules are available in the package: https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 20 of 55 Module path Type Role Is new in 0.5.0? /bin/i386/coredll.bin /bin/amd64/coredll.bin XS Main stealer module (analogous to the one described in this chapter) – /bin/i386/stubmod.bin /bin/amd64/stubmod.bin XS Prepares a .NET environment inside the process, to load other .NET modules – /bin/i386/taskcore.bin /bin/amd64/taskcore.bin XS Manages additional modules for the tasks supplied by the C2 ✓ /bin/i386/stubexec.bin /bin/amd64/stubexec.bin XS Injects into regsvr32.exe, and remaps the module into a new process – /bin/KeePassHax.dll PE (.NET) Steals KeePass credentials – /bin/runtime.dll PE (.NET) Runs PowerShell scripts and plugins in the form of .NET assemblies – /bin/loader.dll PE (.NET) General purpose .NET assemblies runner ✓ As seen earlier, the malware supports up to 100 LUA extensions. They are loaded from the package, retrieved by paths in the format:  /extension/%08x.xs . The  coredll.bin  not only coordinates the work of other modules, but also has a lot of vital functionality hardcoded. It comes with a collection of built-in stealers that can be enabled or disabled depending on the configuration. String encryption In case of this module not all strings are obfuscated, so we can obtain some of them with the help of common applications such as FLOSS . Those are mainly strings from the statically linked libraries. The most important strings, relevant to the malware functionality, are encrypted and revealed just before use. Once again, TLS storage is used for the temporary buffer. This time the algorithm used for the decryption is RC4. The first 16 bytes of the input buffer is the key, followed by the encrypted data. IDA script for deobfuscation: https://gist.github.com/hasherezade/51cb827b101cd31ef3061543d001b190 Deobfuscated strings from the sample: https://gist.github.com/hasherezade/ac63c0cbe7855276780126be006f7304 Communication between the modules As there are multiple modules involved in the execution of various tasks, and some of them are injected into remote processes, they must communicate with the central module ( coredll.bin ), to coordinate the work and pass the collected results. The communication is implemented with the help of a pipe. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 21 of 55 Figure 21 – The function creating the pipe within the main module. The pipe is created by the main module, using the name  Core  and the current PID. This information, concatenated together, is hashed (SHA1), and the hash is used to generate the unique pipe name (following the pattern  \\.\pipe\{%08lx- %04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} ). Figure 22 – The function used to generate the pipe name. The PID is further passed to the submodules so they can regenerate the name, following the same algorithm, and then establish the connection. Execution flow After the module finishes initialization, which is typical for the XS2 format [1], execution is redirected to the main function of the module. The function takes 3 arguments: the pointer to the current module, a command to be executed, and a structure, including all the data passed from the previous layers. Depending on the command argument, the module can follow one of the 4 different execution paths. In each case, the most important functionality is to load additional components, establish the connection with the C2, and deploy the built-in stealers. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 22 of 55 Figure 23 – The overview of the main function within the Core module. In some cases, the function responsible for running all the modules can be executed at the exit of the application where it is injected. It is implemented by hooking the  ExitProcess  function. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 23 of 55 Disabling the Event Tracer (and other NTDLL patches) At the beginning of the main function (Figure 23), we can see a function patching NTDLL. Its role is to disable Event Tracering (ETW), which is a built-in Windows feature allowing to collect information about the application behavior. The bypass is implemented similarly to that described in the article [5]. In the case of the WoW64 process, both the 32-bit and the 64-bit version of the NTDLL are patched. By scanning the process with PE-sieve, we can quickly dump the modified versions of NTDLLs and receive the  .tag  file informing about the hooks. The found patches are listed below. In NTDLL 64-bit: The installed patch is simple, it forces the  EtwEventWrite function to always return 0. Figure 24 – The patch in the NTDLL (64-bit), disabling function EtwEventWrite. The patches (NTDLL 32-bit): Here we can see two functions patched:  EtwEventWrite  and  NtTraceEvent – they both are forced to return immediately at the start. Figure 25 – Multiple patches in the NTDLL (32-bit), including the ones disabling ETW. Additionally, we can see a hook inside the exception dispatcher, whose role was described in detail in our previous article on Rhadamanthys [1] (”Handling exceptions from a custom module”). In contrast to other hooks that are security bypasses, this hook is part of the XS format loading and allows to catch the exceptions thrown by the custom module. Running the stealers The main component of Stage 3 comes with a rich set of built-in stealers. They are stored as two distinct global lists. The first group involves stealers that rely on searching and parsing files of particular targets (i.e., profiles, local databases), which we refer to as “passive stealers”. The second set which we refer to as “active stealers” consists of more invasive components: they can interfere with running processes, inject into them, etc. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 24 of 55 Figure 26 – Global lists of the passive and active stealers. Each stealer has an initialization function and a run function. They also use a top-level structure from where they fetch the data and where they report back the results. By sharing this structure, they can pass data from one to another, working as elements of the pipeline. The overview of the function responsible for running the stealers: https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 25 of 55 Figure 27 – Fragment of the function responsible for the deployment of the stealers. First, the malware enumerates all the processes and stores their names, along with their PIDs, in a structure that will be used later. Then, all the built-in active stealers are initialized in a loop. At this point, they may reference the list of the processes to check if their targets are on the list. Some of them may also read additional information from the process, which will be https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 26 of 55 passed further into the pipeline. Next, the passive stealers are initialized. The User Profile Path is retrieved and walked recursively to check if it contains some targeted directories from which the malware can steal. After the initialization of both types of stealers is finished and information about the targets is collected, the functions performing the actual stealing activities run. First, all the initialized profile parsers are executed in a loop to collect the information from the files. Finally, the active stealers are executed. After all the hardcoded stealers were run, the malware initializes a built-in LUA interpreter and uses it to execute the additional set of stealers, implemented as LUA scripts (described in more detail in “The LUA scripts“). The results of all the executed stealers are collected in a global structure. They are posted into the queue and later uploaded to the C2. The passive stealers As mentioned earlier, passive stealers rely on reading and parsing files belonging to particular applications to retrieve saved credentials. The passive stealers are initialized by parsing recursively all the subdirectories in  %USERPROFILE%  and checking the presence of targeted paths. The inner function enumerates all the directories in the  %USERPROFILE% , and on each new directory found, it executes a passed callback. The callback then runs the full list of initialization functions ( is_target ) of the stealers. If the path is identified as a target, the relevant passive stealer is further initialized. Figure 28 – A loop initializing passive stealers with the directories found recursively in the %USERDATA% directory. The paths used for the target identifications (passive stealers): https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 27 of 55 Target Paths/extensions Is Optional Chrome “Web Data”, “Web Data Ts4” no Firefox “cookies.sqlite” no FileZilla “recentservers.xml” yes OpenVPN “OpenVPN Connect\profiles”, “.ovpn” yes Telegram “configs”, “\D877F783D5D3EF8C\configs” yes Discord “DiscordStorage”, “discordptbStorage”, “discordcanaryStorage” yes StickyNotes “Microsoft.MicrosoftStickyNotes”, “plum.sqlite” yes The malware comes with a statically linked SQLite library which allows it to load and query local SQLite databases, and fetch saved data such as cookies. The active stealers The set of active stealers is more interesting and diverse. Some of these stealers open running processes and inject additional components. One of such components is  KeePassHax.dll , injected into the KeePass process, to steal credentials from this open-source password manager (more details in: “Stealing from KeePass”). The malware may also inject its main component ( core.bin ) into other applications. The set of the stealers is too big to describe each of them in detail. In summary, the following applications are targeted: Chrome Vault WinSCP StickyNotes KeePass Mozilla products (Thunderbird, Firefox) and some of the Firefox-based browsers, such as K-Meleon.exe FoxMail Telegram Stream TeamViewer Internet Explorer OpenVPN Discord CoreFTP 360Browser Stealing from Chrome and Mozilla products In some cases, the active stealer’s initialization function retrieves from the running process information about the current profile and configuration files. It is done for the following targets: K-Meleon.exe (Firefox-based browser), Firefox Chrome The approach to finding the loaded profile is a bit different in each case. In the case of K-Meleon.exe it identifies the target files by paths retrieved from associated handles. First, the malware retrieves all object handles that are currently opened in the system, using  NtQuerySystemInformation (with the argument  SystemHandleInformation ). It walks through obtained handles and selects the ones owned by the target process. Then, it iterates over those handles to check which names they are associated with (using  NtQueryObject ), and compares each name with the hardcoded one ( \places.sqlite ) . If the check passed, the full path is retrieved and stored for further use. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 28 of 55 Figure 29 – Searching for the configuration file (“places.sqlite”) by walking through the retrieved object handles. In the case of Chrome, the current profile is provided in the command line used to run the application. The command line of a process is stored in a dedicated field of the PEB ( PEB  →  ProcessParameters  →  CommandLine ), which is where the malware retrieves it from. The path to the profile is given after the  —user-data-dir=  command line argument, so this hardcoded string is used for the search. As mentioned earlier, the main stealing actions are executed in the  run  function of each stealer. The stealers targeting Mozilla products may deploy additional processes and inject there the main module of the malware:  coredll.bin . Note that although it is the same module as the one currently described, its execution path is not identical. Looking at the XS2 header [1], we can see that this format lets you provide alternative entry points of the application (denoted as  entry_point  – primary, and  entry_point_alt  – secondary). The execution of the implanted  coredll.bin  starts https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 29 of 55 in  entry_point_alt . Looking at the injection routine, we can see the corresponding address being added to the APC queue of the target’s main thread. Figure 30 – Fragment of the injecting function, responsible for selecting the alternative Entry Point of the module, and setting it in the APC Queue of the targeted process, for execution. The alternative entry point leads to the execution of a minimalist main, which contain only Mozilla-specific stealers. Depending on the given settings, the stealing functions can be executed immediately, or when the application exits. The execution at exit may be necessary in case the current process blocks some files from which the module wants to read. Figure 31 – The overview of the Alternative Entry Point of the coredll.bin. We can see that the main, stealing function, can be executed either immediately, or at the exit of the process. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 30 of 55 There are three stealers implemented, and selected depending on the given configuration. Each of them sends the stolen content over the pipe back to the main element. Figure 32 – The function responsible for deploying a specific, targeted stealer attacking the Mozilla application data. Stealing from KeePass One of the modules shipped in the package is a .NET DLL named  KeePassHax.dll . As the name suggests, this module targets the KeePass password manager (which is a .NET application). The DLL is injected into the target along with the assisting native module, called  stubmod.bin . An interesting feature of the implementation is that it initializes the complete .NET environment of an appropriate version within the attacked KeePass process, just to deploy the stealing DLL. The function leading to the deployment of those components belongs to the active stealers list. As explained earlier, every stealer has two callback functions:  init  and  run . In this case, the first function is empty, as there is nothing to initialize. The activity starts in the  run  function, given below. First, it searches for the  keypass.exe  on the previously prepared list of running processes. If the name is found, it tries to inject the malicious modules into the corresponding process. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 31 of 55 Figure 33 – Fragment of the function responsible for doing an injection into the KeePass.exe. The injected payload, and the function to be executed from it, are passed as arguments. Two modules are fetched from the package:  stubmod.bin  (in a 32 or 64-bit version, matching the process) and  KeePassHax.dll . The  stubmod.bin  is an executable in the custom XS format [1], containing native code. This module is executed first, and prepares the stage for loading the DLL. The start function of the  stubmod.bin  initializes the whole .NET environment inside the process where it is injected, similar to what is described in the following writeup [6]. Figure 34 – Overview of the function responsible for custom initialization of the .NET environment within the process. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 32 of 55 It then runs the  KeePassHax.dll . As an argument, it passes a callback function which is responsible for sending the collected data to the main malware process, over the previously created pipe (more details in “Communication between the modules”). The way of passing the callback is a bit unusual, yet simple and effective: the pointer is first printed as a string and then given as an argument to the  DllMain  function of the  KeePassHax.dll . Figure 35 – The pointer to the callback function is printed as a string and then passed as an argument to the .NET module. Looking at the  DllMain  function, we can see the argument being parsed and used as two combined pointers. One of the pointers, stored in the variable  NativePtr , leads to the function that sends data over the pipe (that is a part of  stubmod.bin ). The other one stored in  NativeData  contains a PID of the process running the core module, which is needed to regenerate the pipe name. Figure 36 – The DllMain function of the KeePassHax.dll. The main actions of dumping the credentials are done by the function  KcpDump , which retrieves values of relevant fields from the main KeePass form. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 33 of 55 Figure 37 – The function KcpDump in the .NET module KeePassHax.dll. The collected data is serialized and passed to the previously retrieved native function. The first supplied argument is a  NativeData  containing the PID passed from the caller. Figure 38 – The KcpSendData function in the .NET module KeePassHax.dll. The function  Program.FnSendData  is implemented in the native module, which was responsible for executing the  KeePassHax.dll , and has the following prototype: int __cdecl to_read_write_to_pipe(int seed, DWORD numberOfBytesToWrite, _BYTE *data, int data_size) int __cdecl to_read_write_to_pipe(int seed, DWORD numberOfBytesToWrite, _BYTE *data, int data_size) int __cdecl to_read_write_to_pipe(int seed, DWORD numberOfBytesToWrite, _BYTE *data, int data_size) https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 34 of 55 The supplied seed argument is used in the generation of the pipe name, where the data is written to. Figure 39 – Generating the pipe name, analogous to the function described in “Communication between modules” This is how various components, native (in a custom format) as well as .NET modules, are blended together as one injected payload. The LUA scripts Although the list of stealers available by default is already very rich, the author decided to provide even more options by incorporating a LUA script runner. The main module can load up to 100 LUA scripts. In the analyzed cases, we found approximately 60 of them are used. They implement a variety of stealers that target cryptocurrency wallets, FTP applications, e-mail agents, SSH, and more. First, the malware loads all the available scripts from the package in a loop. They are stored in the internal structure, that is later passed to the interpreter. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 35 of 55 Figure 40 – A LUA script fetched from the package and revealed in memory. Scripts work as plugins to the built-in framework. Stealers are grouped by one-character identifiers: ID Type W wallets E e-mails F FTP N note-keeping apps M messengers V VPN 2 authentication related, password managers, etc. The stealer starts with a check if the group it belongs to is enabled in the framework. if not framework.flag_exist("W") then if not framework.flag_exist("W") then return if not framework.flag_exist("W") then return Some examples are given below. Stealer for Psi: if not framework.flag_exist("M") then local filename = framework.parse_path([[%AppData%\Psi+\profiles\default\accounts.xml]]) if filename ~= nil and framework.file_exist(filename) then framework.add_file("accounts.xml", filename) framework.set_commit("$[M]Psi+") if not framework.flag_exist("M") then return end local filename = framework.parse_path([[%AppData%\Psi+\profiles\default\accounts.xml]]) if filename ~= nil and framework.file_exist(filename) then framework.add_file("accounts.xml", filename) framework.set_commit("$[M]Psi+") end if not framework.flag_exist("M") then return end local filename = framework.parse_path([[%AppData%\Psi+\profiles\default\accounts.xml]]) if filename ~= nil and framework.file_exist(filename) then framework.add_file("accounts.xml", filename) https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 36 of 55 framework.set_commit("$[M]Psi+") end Stealer for a DashCore wallet: if not framework.flag_exist("W") then framework.parse_path([[%AppData%\DashCore\wallets\wallet.dat]]), framework.parse_path([[%LOCALAppData%\DashCore\wallets\wallet.dat]]) for _, filename in ipairs(filenames) do if filename ~= nil and framework.file_exist(filename) then framework.add_file("DashCore/wallet.dat", filename) file_count = file_count + 1 framework.set_commit("!CP:DashCore") local file_count = 0 if not framework.flag_exist("W") then return end local filenames = { framework.parse_path([[%AppData%\DashCore\wallets\wallet.dat]]), framework.parse_path([[%LOCALAppData%\DashCore\wallets\wallet.dat]]) } for _, filename in ipairs(filenames) do if filename ~= nil and framework.file_exist(filename) then if file_count > 0 then break end framework.add_file("DashCore/wallet.dat", filename) file_count = file_count + 1 end end if file_count > 0 then framework.set_commit("!CP:DashCore") end local file_count = 0 if not framework.flag_exist("W") then return end local filenames = { framework.parse_path([[%AppData%\DashCore\wallets\wallet.dat]]), framework.parse_path([[%LOCALAppData%\DashCore\wallets\wallet.dat]]) } for _, filename in ipairs(filenames) do if filename ~= nil and framework.file_exist(filename) then if file_count > 0 then break end framework.add_file("DashCore/wallet.dat", filename) file_count = file_count + 1 end end if file_count > 0 then framework.set_commit("!CP:DashCore") end Stealer for Notezilla: if not framework.flag_exist("N") then local filename = framework.parse_path([[%AppData%\Conceptworld\Notezilla\Notes9.db]]) https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 37 of 55 if filename ~= nil and framework.file_exist(filename) then framework.add_file("Notes9.db", filename) framework.set_commit("$[N]Notezilla") if not framework.flag_exist("N") then return end local filename = framework.parse_path([[%AppData%\Conceptworld\Notezilla\Notes9.db]]) if filename ~= nil and framework.file_exist(filename) then framework.add_file("Notes9.db", filename) framework.set_commit("$[N]Notezilla") end if not framework.flag_exist("N") then return end local filename = framework.parse_path([[%AppData%\Conceptworld\Notezilla\Notes9.db]]) if filename ~= nil and framework.file_exist(filename) then framework.add_file("Notes9.db", filename) framework.set_commit("$[N]Notezilla") end List of all the targeted applications: Armory AtomicDEX AtomicWallet Authy Desktop AzireVPN BinanceWallet BinanceWallet BitcoinCore CheckMail Clawsmail Clawsmail CuteFTP Cyberduck DashCore Defichain-Electrum Dogecoin Electron-Cash Electrum-SV Electrum EMClient Exodus Frame FtpNavigator FlashFXP FTPRush GmailNotifierPro Guarda Jaxx Litecoin-Qt Litecoin-Qt LitecoinCore Monero MyCrypto MyMonero NordVPN Notefly Notezilla SSH Outlook Pidgin PrivateVPN ProtonVPN Psi+ PuTTY Qtum-Electrum Qtum RoboForm Safepay SmartFTP Solar Wallet The Bat TokenPocket Total Commander Tox TrulyMail WinAuth WalletWasabi WindscribeVPN Zap Receiving commands from the C2 In addition to running the modules that were embedded in the package and sending back their results, the malware establishes a connection with the C2 to listen for additional instructions. On demand, it can drop and run additional executable files, or execute scripts via other modules. There are 11 different supported commands that are identified by numbers. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 38 of 55 Figure 41 – Switch-case parsing the commands from the C2 received by the coredll.bin module. The additional modules that can be run on demand, are  taskcore.bin ,  runtime.dll , and  loader.dll . The content received from the C2 is passed in the variable denoted above as  buf . Depending on the specifics of the particular command, it can be saved into a file, or into a named mapping, whose handle is passed to the other malicious module. For example, the module received from the C2 is passed to be executed via  taskcore.bin : https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 39 of 55 Figure 42 – Fragment of the function running the received module via taskcore.bin. The buffer received from the C2 (containing the module) is added into the mapping, whose handle is passed to the taskcore.bin module, that is run in a new process. Running received PowerShell scripts and .NET modules In addition to the ability to run LUA scripts, the malware allows the execution of PowerShell scripts, and since the version 0.5.0, also .NET assemblies. Unlike LUA, the scripts/assemblies to be executed are not shipped in the package but dynamically received from the C2. This part of the functionality is managed by additional modules from package #2:  runtme.dll  (runs PowerShell scripts, and plugins) and  loader.dll  (a general-purpose loader of .NET assemblies). The modules are run within a new process under the guise of  AppLaunch.exe  or  dllhost.exe . Evolution of PowerShell runner (runtime.exe/dll) In the previous versions, the PowerShell runner was implemented as  runtime.exe , which is a very small executable written in .NET. The runner consisted of the  Main  function presented below. It takes two string arguments, which are in fact pointers in hexadecimal form. The pointers lead to the functions in the native module that loaded the current one and are used to pass the input and output. The scripts are received by calling the function  sysNativeWrapper.GetScript() , then results are passed back by  sysNativeWrapper.SendDumpData(data) . https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 40 of 55 Figure 43 – The .NET module: “runtime.exe” (decompiled using dnSpy) The new version of the runner is a DLL, not an EXE ( runtime.dll ). It is fully rewritten, and much more complex. The runner exposes a new interface, to support the API of the newly introduced plugin system. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 41 of 55 Figure 44 – List of classes on the runtime.dll (decompiled using dnSpy). The plugins are in the form of .NET assemblies. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 42 of 55 Figure 45 – Fragment of the function executing the plugins (.NET assemblies following the defined API) – runtime.dll, decompiled using dnSpy. Simple PowerShell scripts, in the form of a string, are supported as well. Figure 46 – Fragment of the function executing PowerShell scripts – runtime.dll, decompiled using dnSpy. Loader of .NET assemblies (loader.dll) The current version has yet another module,  loader.dll , that is also responsible for running .NET assemblies, but in a much simpler way, and outside of the plugin system. It expects two arguments – a pointer to a byte array representing the assembly, and the array size. Then, the passed assembly is simply invoked. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 43 of 55 Figure 47 – Fragment of the function executing .NET assemblies – loader.dll, decompiled using dnSpy. The “TaskCore” module In the published changelog, the author advertises multiple changes in the task-running module: 14. The task module has been greatly upgraded, and a new plug-in module has been introduced to support users in secondary development of their own plug-ins. Supports multiple task execution modes: In Memory LoadPE Execution DotNet Reflection Execution DotNet Extension Execution DotNet Extension with Zip Execution 14. The task module has been greatly upgraded, and a new plug-in module has been introduced to support users in secondary development of their own plug-ins. Supports multiple task execution modes: Normal execution In Memory LoadPE Execution Powershell Execution DotNet Reflection Execution DotNet Extension Execution DotNet Extension with Zip Execution VbScript Execution JScript Execution X86 shellcode execution X64 shellcode execution Native Plugin Loader 14. The task module has been greatly upgraded, and a new plug-in module has been introduced to support users i Supports multiple task execution modes: Normal execution In Memory LoadPE Execution Powershell Execution DotNet Reflection Execution DotNet Extension Execution https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 44 of 55 DotNet Extension with Zip Execution VbScript Execution JScript Execution X86 shellcode execution X64 shellcode execution Native Plugin Loader Indeed, we can see the new module  taskcore.bin  which was added in this release. Depending on the specific command passed from the C2, it may be run under the guise of multiple applications, such as:  AppLaunch.exe ,  dllhost.exe ,  rundll32.exe ,  OpenWith.exe ,  notepad.exe ,  rkeywiz.exe ,  wmpntwrk.exe ,  wmpconfig.e Similar to  coredll.bin , it comes with RC4-encrypted strings. The list of decrypted strings (format: RVA, string) is given below: https://gist.github.com/hasherezade/47fa641d054d82de4059ff36c7e99918 As the module is in the XS2 format. The start function first finishes the initialization, then deploys the core functionality of the modules, i.e. command parsing. Each command is responsible for executing a module of a particular type. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 45 of 55 Figure 48 – Switch-case parsing the commands from the C2 received by the taskcore module. The passed structure contains parameters filled in by the  coredll.bin , and the mapping contains the buffer with a module to be executed that was received from the C2. While some of the other modules interact only with the  coredll.bin , this one is also capable of establishing its own connection with the C2 and reports there directly. As the author mentioned, the module is able to run a variety of content formats. The simplest of them is running shellcodes. Figure 49 – A function within taskcore.bin, executing simple shellcodes. Scripts, such as JScripts and VBScripts, are both executed by the same function: Figure 50 – Fragment of a function within taskcore.bin, executing JScripts and VBScripts. Underneath, it uses a COM interface and parses the given script with the help of a function  ParseScriptText . https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 46 of 55 Figure 51 – Parsing and executing scripts using the COM interface. Similar to the previously mentioned  stubmod.bin  (”Stealing from KeePass”), the .NET environment is manually initialized. It is then it is used to run PowerShell scripts as well as plugins in the custom format (that are delivered in the form of .NET assemblies). Figure 52 – Function leading to the execution of a plugin (.NET assembly). Before running the PowerShell scripts, the malware zeroes out the Environment Variable  _PSLockdownPolicy . This modification disables the PowerShell lockdown policy, which is a security feature restricting the execution of certain actions within the PowerShell environment. As a result, this allows more freedom in execution of scripts. Figure 53 – Function leading to the execution of a PowerShell script. The module also disables other security-related features: for example, it patches out AMSI checks and event tracing in NTDLL. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 47 of 55 Analyzing the strings The functionality of the modules is vast, but because we dumped and deobfuscated all the strings, we can get a good overview of different aspects of the modules by reviewing them. Selected artifacts described below. SQLite library There are multiple strings indicating that the module comes with a statically linked SQLite library. The presence of SQLite modules in malware strongly suggests that it steals cookies, as cookies are kept in local SQLite databases. The Rhadamanthys core comes with the version string, which leads to the particular commit of the SQLite: https://www3.sqlite.org/src/info/fe7d3b75fe1bde41 (updated since the previous version of the malware, which used an earlier commit: https://www2.sqlite.org/src/info/8180e320ee4090e4) 2016-04-08 15:09:49 fe7d3b75fe1bde41511b323925af8ae1b910bc4d SQLite format 3 2016-04-08 15:09:49 fe7d3b75fe1bde41511b323925af8ae1b910bc4d SQLite format 3 2016-04-08 15:09:49 fe7d3b75fe1bde41511b323925af8ae1b910bc4d In addition to the artifacts pointing to the library itself, we can see some queries among the decrypted strings, such as the following: SELECT title, url FROM (SELECT * FROM moz_bookmarks INNER JOIN moz_places ON moz_bookmarks.fk=moz_places.id) SELECT url FROM (SELECT * FROM moz_annos INNER JOIN moz_places ON moz_annos.place_id=moz_places.id) t GROUP BY place_id SELECT title, url FROM (SELECT * FROM moz_bookmarks INNER JOIN moz_places ON moz_bookmarks.fk=moz_places.id) SELECT url FROM (SELECT * FROM moz_annos INNER JOIN moz_places ON moz_annos.place_id=moz_places.id) t GROUP BY place_id SELECT title, url FROM (SELECT * FROM moz_bookmarks INNER JOIN moz_places ON moz_bookmarks.fk=moz_places.id) SELECT url FROM (SELECT * FROM moz_annos INNER JOIN moz_places ON moz_annos.place_id=moz_places.id) t GROUP BY Those queries are used for retrieving Mozilla browsing history and will be applied on the found database. They are part of the stealer described in “Stealing from Chrome and Mozilla products”. Mbed-Crypto We can find multiple strings suggesting that the malware makes use of a statically linked library with Elliptic curves. Some of the strings can help uniquely identify the library and its version. 8335DC163BB124B65129C96FDE933D8D723A70AADC873D6D54A7BB0D 14DEF9DEA2F79CD65812631A5CF5D3ED 8335DC163BB124B65129C96FDE933D8D723A70AADC873D6D54A7BB0D 14DEF9DEA2F79CD65812631A5CF5D3ED https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 48 of 55 8335DC163BB124B65129C96FDE933D8D723A70AADC873D6D54A7BB0D 14DEF9DEA2F79CD65812631A5CF5D3ED It turns out to be Mbed Crypto (source), an old version of the library now renamed Mbed TLS. The source file referencing the found strings can be seen on the project’s Github: [reference 1] [reference 2]. Note that the mentioned strings are not present in the new version of the library (under the new name Mbed-TLS) which suggests that the malware author used the old copy. After analyzing the application, we know that this library is used to establish the TLS connection with the C2. CJSON Library Some unencrypted strings suggest the use of JSON, implemented by the statically linked library cJSON: BUG: Unable to fetch CJSON configuration JSON parser does not support UTF-16 or UTF-32 cjson BUG: Unable to fetch CJSON configuration JSON parser does not support UTF-16 or UTF-32 '[' or '{' expected unexpected token '}' expected ':' expected string or '}' expected ']' expected cjson BUG: Unable to fetch CJSON configuration JSON parser does not support UTF-16 or UTF-32 '[' or '{' expected unexpected token '}' expected ':' expected string or '}' expected ']' expected Looking further at the context in which those strings are used, we can see that the JSON format was applied to create reports about the stolen content, that was retrieved by the LUA scripts. We can also find relevant artifacts among the decrypted strings: e7bcc,'{"root":' e7bb0,'root' e7d58,'profile' e3814,'username' e37f8,'password' e7d40,'openvpn' e7bcc,'{"root":' e7bb0,'root' e7d58,'profile' e3814,'username' e37f8,'password' e7d40,'openvpn' Artifacts from 360 Browser Password stealer Among unencrypted strings, we see the following strings that can be found in the open-source 360 Browser Password decoder. cf66fb58f5ca3485 (4B01F200ED01) https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 49 of 55 cf66fb58f5ca3485 (4B01F200ED01) The first of the strings is an AES key that is used for decrypting the password. The second is the prefix of the encrypted password that needs to be dropped. A snippet from a Chinese website One of the strings is quite unusual, and looks like along key or an obfuscated content: 0a{1b2c3d$4e5f6g7h_8@i9jAkBl`CmDnEo[FpGqHrI~s-JtKuLvMw%NxOyPz(Q9R8S7T6)U5V4]W3X2}Y1Z0 0a{1b2c3d$4e5f6g7h_8@i9jAkBl`CmDnEo[FpGqHrI~s-JtKuLvMw%NxOyPz(Q9R8S7T6)U5V4]W3X2}Y1Z0 0a{1b2c3d$4e5f6g7h_8@i9jAkBl`CmDnEo[FpGqHrI~s-JtKuLvMw%NxOyPz(Q9R8S7T6)U5V4]W3X2}Y1Z0 However, Googling for it gives few results only, leading to a Chinese website with a simple code snippet. The snippet illustrates how to generate a random file name, and the aforementioned string is just used as a charset. The implementation used in Rhadamanthys is not identical, but has analogous functionality: Figure 54 – Function generating a random file name. The string is referenced in the fragment of code responsible for generating names for the additional executables downloaded from the C2 that are dropped and executed. This piece of code is not particularly interesting, but the choice is unusual and may give a subtle hint that the author is a Chinese speaker. Hovewer, it is not enough to draw any conclusions. Additions in the version 0.5.1 During the writing of this article, the author already released version 0.5.1. The newest version contains additions such as a Clipper plugin, which watches a clipboard and replaces wallet addresses with attackers’ addresses. This version enables even https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 50 of 55 more customization – distributors can order private stubs, prepared especially for them. Figure 55 – Announcement about the version 0.5.1, published on author’s Telegram account. List of changes, as presented by the author: 2.Telegram notification, you can now choose whether to send wallet crack and seed records in the log ZIP 3.Google Account Cookie Recovery 4.Default build stub cleaning for Windows Defender, including cloud protection 1.Added Clippers plug-in 2.Telegram notification, you can now choose whether to send wallet crack and seed records in the log ZIP 3.Google Account Cookie Recovery 4.Default build stub cleaning for Windows Defender, including cloud protection 1.Added Clippers plug-in 2.Telegram notification, you can now choose whether to send wallet crack and seed records in the log ZIP 3.Google Account Cookie Recovery 4.Default build stub cleaning for Windows Defender, including cloud protection We can already see that the Clipper plugin supports a rich set of targets. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 51 of 55 Figure 56 – List of the applications attacked by the Clipper plugin (added in version 0.5.1). Conclusion Rhadamanthys is a well-designed, modular stealer. In this article, we presented some details of its implementation, showing the incorporated techniques and execution flow. Although the core component comes with a lot of interesting built-in features, the power of this malware lies in its extensibility. The currently analyzed version 0.5.0 supports multiple scripting languages, from LUA (whose interpreter is built-in to the main module) to PowerShell and other scripting languages, that are supported via an additional module. As we can see, the author keeps enriching the set of available features, trying to make it not only a stealer but a multipurpose bot, by enabling it to load multiple extensions created by a distributor. The added features, such as a keylogger, and collecting information about the system, are also a step towards making it a general-purpose spyware. It is evident that with the fast pace and ongoing development, Rhadamanthys is trying hard to become a big player on the malware market and is most likely here to stay. Check Point customers remain protected from the threats described in this research. https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 52 of 55 Check Point’s Threat Emulation provides comprehensive coverage of attack tactics, file types, and operating systems and has developed and deployed a signature to detect and protect customers against threats described in this research. Check Point’s Harmony Endpoint provides comprehensive endpoint protection at the highest security level, crucial to avoid security breaches and data compromise. Behavioral Guard protections were developed and deployed to protect customers against threats described in this research. BG: InfoStealer.Wins.Rhadamanthys.E/F BS: InfoStealer.Wins.Rhadamanthys.C/D/G TE/Harmony Endpoint protections: InfoStealer.Wins.Rhadamathys.C/D/G IOC/Analyzed samples ID Hash Module type Format #1.1 bb8bbcc948e8dca2e5a0270c41c062a29994a2d9b51e820ed74d9b6e2a01ddcf Stage 1 (version 0.5.0) PE #1.2.1 22a67f510dfb7ca822b5720b89cd81abfa5e63fefa1cdc7e266fbcbb0698db33 Stage 2: main module XS1 #1.2.2 6ed3ac428961b350d4c8094a10d7685578ce02c6cd41cc7f98d8eeb361f0ee38 dt.x86.bin XS1 #1.2.3 4fd469d08c051d6997f0471d91ccf96c173d27c8cff5bd70c3f2c5008faa786f early.x86.bin XS1 #1.2.4 633b0fe4f3d2bfb18d4ad648ff223fe6763397daa033e9c5d79f2cae89a6c3b2 early.x64.bin XS1 #1.2.5 50b1f29ccdf727805a793a9dac61371981334c4a99f8fae85613b3ee57b186d2 phexec.bin XS1 #1.2.6 01609701a3ea751dc2323bec8018e11742714dc1b1c2dcb39282f3c4a4537c7d netclient.x86.bin XS1 #1.2.7 a905226a2486ccc158d44cf4c1728e103472825fb189e05c17d998b9f5534d63 proto_x86.bin shellcode #1.2.8 ed713454c20844522304c49cfe25fe1490418c300e5ab0c9fca431ede1e91d7b strategy.x86.bin XS1 #1.2.9 f82ec2246dde81ca9edb69fb9c7ce3f7101f5ffcdc3bdb86fea2a5373fb026fb unhook.bin XS1 #1.3.1 ee4a487e78f23f5dffc35e73aeb9602514ebd885eb97460dd26635f67847bd16 Stage 3: main stealer component: “coredll.bin” (32- bit) XS2 #1.3.2 fcb00beaa88f7827999856ba12302086cadbc1252261d64379172f2927a6760e Stage 3 submodule: “KeePassHax.dll” PE https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 53 of 55 ID Hash Module type Format #1.3.3 a87032195e38892b351641e08c81b92a1ea888c3c74a0c7464160e86613c4476 Stage 3 submodule: “runtime.dll” PE #1.3.4 3d010e3fce1b2c9ab5b8cc125be812e63b661ddcbde40509a49118c2330ef9d0 Stage 3 submodule: “loader.dll” PE #1.3.5 ecab35dfa6b03fed96bb69ffcecd11a29113278f53c6a84adced1167b66abe62 Stage 3 submodule: “stubmod.bin” (32-bit) XS2 #1.3.6 5890b47df83b992e2bd8617d0ae4d492663ca870ed63ce47bb82f00fa3b82cf9 Stage 3 submodule: “taskcore.bin” (32-bit) XS2 #1.3.7 2b6faa98a7617db2bd9e70c0ce050588c8b856484d97d46b50ed3bb94bdd62f7 Stage 3 submodule: “stubexec.bin” (32-bit) XS2 #1.3.8 f1f33618bbb8551b183304ddb18e0a8b8200642ec52d5b72d3c75a00cdb99fd4 Stage 3: main stealer component: “coredll.bin” (64- bit) XS2 Appendix Our other writeups on Rhadamanthys: [1] “From Hidden Bee to Rhadamanthys – The Evolution of Custom Executable Formats”. [2] “Rhadamanthys: The”Everything Bagel” Infostealer” More about used techniques: [3] https://redops.at/en/blog/direct-syscalls-vs-indirect-syscalls [4] https://media.defcon.org/DEF CON 30/DEF CON 30 presentations/Tarek Abdelmotaleb Dr. Bramwell Brizendine – Weaponizing Windows Syscalls as Modern 32-bit Shellcode – paper.pdf [5] https://whiteknightlabs.com/2021/12/11/bypassing-etw-for-fun-and-profit/ [6] https://www.malwarebytes.com/blog/news/2018/01/a-coin-miner-with-a-heavens-gate [7] https://www.codeproject.com/Articles/11003/The-coding-gentleman-s-guide-to-detecting-the-NET [8] https://codingvision.net/calling-a-c-method-from-c-c-native-process https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 54 of 55 [9] https://communities.bentley.com/products/programming/microstation_programming/f/archived-microstation-v8i-programming-forum/64713/write-a-com-server-in-c—call-from-vba-c-or-c-or-vb-net Source: https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Page 55 of 55 https://research.checkpoint.com/2023/rhadamanthys-v0-5-0-a-deep-dive-into-the-stealers-components/ Figure 44-List of classes on the runtime.dll (decompiled using dnSpy). The plugins are in the form of .NET assemblies. Page 42 of 55