Hidden Bee: Let's go down the rabbit hole

Hidden Bee: Let’s go down the rabbit hole

Some time ago, we discussed the interesting malware, Hidden Bee. It is a Chinese miner, composed of userland components, as well as of a bootkit part. One of its unique features is a custom format used for some of the high-level elements (this format was featured in my recent presentation at SAS).

Recently, we stumbled upon a new sample of Hidden Bee. As it turns out, its authors decided to redesign some elements, as well as the used formats. In this post, we will take a deep dive in the functionality of the loader and the included changes.

Sample

831d0b55ebeb5e9ae19732e18041aa54 – shared by @James_inthe_box

Overview

The Hidden Bee runs silently—only increased processor usage can hint that the system is infected. More can be revealed with the help of tools inspecting the memory of running processes.

Initially, the main sample installs itself as a Windows service:

However, once the next component is downloaded, this service is removed.

The payloads are injected into several applications, such as svchost.exe, msdtc.exe, dllhost.exe, and WmiPrvSE.exe.

If we scan the system with hollows_hunter, we can see that there are some implants in the memory of those processes:

Indeed, if we take a look inside each process’ memory (with the help of Process Hacker), we can see atypical executable elements:

Some of them are lacking typical PE headers, for example:

But in addition to this, we can also find PE files implanted at unusual addresses in the memory:

Those manually-loaded PE files turned out to be legitimate DLLs: OpenCL.dll and cudart32_80.dll (NVIDIA CUDA Runtime, Version 8.0.61 ). CUDA is a technology belonging to NVidia graphic cards. So, their presence suggests that the malware uses GPU in order to boost the mining performance.

When we inspect the memory even closer, we see within the executable implants there are some strings referencing LUA components:

Those strings are typical for the Hidden Bee miner, and they were also mentioned in the previous reports.

We can also see the strings referencing the mining activity, i.e. the Cryptonight miner.

List of modules:

bin/i386/coredll.bin
dispatcher.lua
bin/i386/ocl_detect.bin
bin/i386/cuda_detect.bin
bin/amd64/coredll.bin
bin/amd64/algo_cn_ocl.bin
lib/amd64/cudart64_80.dll
src/cryptonight.cl
src/cryptonight_r.cl
bin/i386/algo_cn_ocl.bin
config.lua
lib/i386/cudart32_80.dll
src/CryptonightR.cu
bin/i386/algo_cn.bin
bin/amd64/precomp.bin
bin/amd64/ocl_detect.bin
bin/amd64/cuda_detect.bin
lib/amd64/opencl.dll
lib/i386/opencl.dll
bin/amd64/algo_cn.bin
bin/i386/precomp.bin

And we can even retrieve the miner configuration:

https://gist.github.com/malwarezone/b83a5db804aa2379f4a4647aab18f771#file-config-lua

Inside

Hidden Bee has a long chain of components that finally lead to loading of the miner. On the way, we will find a variety of customized formats: data packages, executables, and filesystems. The filesystems are going to be mounted in the memory of the malware, and additional plugins and configuration are retrieved from there. Hidden Bee communicates with the C&C to retrieve the modules—on the way also using its own TCP-based protocol.

The first part of the loading process is described by the following diagram:

At the beginning, we can see the list of the plugins that are going to be loaded: cloudcompute.api, deepfreeze.api, and netscan.api. Those names are being appended to the root path of the modules.

Each module is fetched from the mounted filesystem and loaded:

Consecutive modules are loaded one after another in the same executable memory area. After the module is loaded, its header is erased. It is a common technique used in order to make dumping of the payload from the memory more difficult.

The cloudcompute.api is a plugin that will load the miner. More about the plugins will be explained in the next section of this post.

Reading its code, we find out that the SPUTNIK modules are filesystems that can be mounted and dismounted on demand. This module will be communicating with others with the help of a named pipe. It will be receiving commands and executing appropriate handlers.

Initialization of the commands’ parser:

The function setting up the commands: For each name, a handler is registered. (This is probably the Lua dispatcher, first described here.)

When plugins are run, we can see some additional child processes created by the process running the coredll (in the analyzed case it is inside rundll32):

Also it triggers a firewall alert, which means the malware requested to open some ports (triggered by netscan.api plugin):

We can see that it started listening on one TCP and one UDP port:

The plugins

As mentioned in the previous section, the SPUTNIK filesystem contains three plugins: cloudcompute.api, deepfreeze.api, and netscan.api. If we convert them to PE, we can see that all of them import an unknown DLL: mpsi.dll. When we see the filled import table, we find out that the addresses have been filled redirecting to the functions from the previous NS module:

So we can conclude that the previous element is the mpsi.dll. Although its export table has been destroyed, the functions are fetched by the custom loader and filled in the import tables of the loaded plugins.

First the cloudcompute.api is run.

This plugin retrieves from the filesystem a file named “/etc/ccmain.json” that contains the list of URLs:

Those are addresses from which another set of modules is going to be downloaded:

["sstp://news.onetouchauthentication.online:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.club:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.icu:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.xyz:443/mlf_plug.zip.sig"]

It also retrieves another component from the SPUTNIK filesystem: /bin/i386/ccmain.bin. This time, it is an executable in NE format (version converted to PE is available here: 367db629beedf528adaa021bdb7c12de)

This is the component that is injected into msdtc.exe.

The configuration is also copied into the remote process and is used to retrieve an additional package from the C&C:

This is the plugin responsible for downloading and deploying the Mellifera Miner: core component of the Hidden Bee.

Next, the netscan.api loads module /bin/i386/kernelbase.bin (converted to PE: d7516ad354a3be2299759cd21e161a04)

The miner in APT-style

Hidden Bee is an eclectic malware. Although it is a commodity malware used for cryptocurrency mining, its design reminds us of espionage platforms used by APTs. Going through all its components is exhausting, but also fascinating. The authors are highly professional, not only as individuals but also as a team, because the design is consistent in all its complexity.

Appendix

https://github.com/hasherezade/hidden_bee_tools – helper tools for parsing and converting Hidden Bee custom formats

https://www.bleepingcomputer.com/news/security/new-underminer-exploit-kit-discovered-pushing-bootkits-and-coinminers/

Articles about the previous version (in Chinese):

Our first encounter with the Hidden Bee:

/blog/threat-analysis/2018/07/hidden-bee-miner-delivered-via-improved-drive-by-download-toolkit/

This custom format can also be converted back to the PE format with the help of a dedicated converter, available here.

Third stage: core.sdb

The core.sdb module converted to PE format is available here: a17645fac4bcb5253f36a654ea369bf9.

The interesting part is that the external loader does not complete the full loading process of the module. It only copies the sections. But the rest of the module loading, such as applying relocations and filling imports, is done internally in the core.sdb.

The previous component was supposed to pass to the core.sdb an additional buffer with the data about the installed service: the name and the path. During its execution, core.sdb will look up this data. If found, it will delete the previously-created service, and the initial file that started the infection:

Getting rid of the previous persistence method suggests that it will be replaced by some different technique. Knowing previous editions of Hidden Bee, we can suspect that it may be a bootkit.

After locking the mutex in a format GlobalSC_{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}, the module proceeds to download another component. But before it goes to download, first, a few things are checked.

First of all, there is a defensive check if any of the known debuggers or sniffers are running. If so, the function quits.

Also, there is a check if the application can open a file ‘??NPF-{0179AC45-C226-48e3-A205-DCA79C824051}’.

If all the checks pass, the function proceeds and queries the following URL, where GET variables contain the system fingerprint:

The hash (sz=) is an MD5 generated from VolumeIDs. Then follows the (os=) identifying version of the operating system, and the identifier of the architecture (ar=), where 0 means 32 bit, 1 means 64bit.

The content downloaded from this URL (starting from a magic DWORD 0xFEEDFACE – 79e851622ac5298198c04034465017c0) contains the encrypted package (in !rbx format), and a shellcode that will be used to unpack it. The shellcode is loaded to the current process and then executed.

The shellcode’s start function uses three parameters: pointer to the functions in the previous module (core sdb), pointer to the buffer with encrypted data, size of the encrypted data.

Fourth stage: the shellcode decrypting !rbx

The beginning of the loaded shellcode:

The shellcode does not fill any imports by itself. Instead, it fully relies on the functions from core.sdb module, to which it passes the pointer. It makes use of the following function: malloc, mecpy, memfree, VirtualAlloc.

Its role is to reveal another part. It comes in an encrypted package starting from a marker !rbx. The decryption function is called just at the beginning:

First, the function checks the !rbx marker and the checksum at the beginning of the encrypted buffer:

It is decrypted with the help of RC4 algorithm, and then decompressed.

After decryption, the markers at the beginning of the buffer are checked. The expected format must start from predefined magic DWORDs: 0xCAFEBABE,0, 0xBABECAFE:

The !rbx package format

The !rbx is also a custom format with a consistent structure.

DWORD magic; // "!rbx"
DWORD checksum;
DWORD content_size;
BYTE rc4_key[16];
DWORD out_size;
BYTE content[];

The custom file system (BABECAFE)

The full decrypted content has a consistent structure, reminiscent of a file system. According to the previous reports, earlier versions of Hidden Bee used to adapt the ROMS filesystem, adding few modifications. They called their customized version “Mixed ROM FS”. Now it seems that their customization process has progressed. Also the keywords suggesting ROMFS cannot be found. The headers starts from the markers in the form of three DWORDS: { 0xCAFEBABE, 0, 0xBABECAFE }.

The layout of BABECAFE FS:

We notice that it differs at many points from ROM FS, from which it evolved.

The structure contains the following files:

/bin/amd64/coredll.bin
/bin/i386/coredll.bin
/bin/i386/preload
/bin/amd64/preload
/pkg/sputnik.spk
/installer/com_x86.dll (6177bc527853fe0f648efd17534dd28b)
/installer/com_x64.dll
/pkg/plugins.spk

The files /pkg/sputnik.spk and /pkg/plugins.spk are both compressed packages in a custom !rsi format.

Each of the spk packages contain another custom filesystem, identified by the keyword SPUTNIK (possibly the extension ‘spk’ is derived from the SPUTNIK format). They will be unpacked during the next steps of the execution.

Unpacked plugins.spk: 4c01273fb77550132c42737912cbeb36
Unpacked sputnik.spk: 36f3247dad5ec73ed49c83e04b120523.

Selecting and running modules

Some executables stored in the filesystem are in two version: 32 and 64 bit. Only the modules relevant to the current architecture are loaded. So, in the analyzed case, the loader chooses first: /bin/i386/preload (shellcode) and /bin/i386/coredll.bin (a module in NS custom format). The names are hardcoded in the loader within the loading shellcode:

After the proper elements are fetched (preload and coredll.bin), they are copied together into a newly-allocated memory area. The coredll.bin is copied just after preload. Then, the preload module is called:

The preload is position-independent, and its execution starts from the beginning of the page.

The only role of this shellcode is to prepare and run the coredll.bin. So, it contains a custom loader for the NS format that allocates another memory area and loads the NS file there.

Fifth stage: preload and coredll

After loading coredll, preload redirects the execution there.

The coredll patches a function inside the NTDLL— KiUserExceptionDispatcher—redirecting one of the inner calls to its own code:

Depending on which process the coredll was injected into, it can take one of a few paths of execution.

If it is running for the first time, it will try to inject itself again—this time into rundll32. For the purpose of the injection, it will again unpack the original !rbx package and use its original copy stored there.

Then it will choose the modules depending on the bitness of the rundll32:

It selects the pair of modules (preload/coredll.bin) appropriate for the architecture, either from the directory amd64 or from i386:

If the injection failed, it makes another attempt, this time trying to inject into dllhost:

Each time it uses the same, hardcoded parameter (/Processid: {...}) that is passed to the created process:

The thread context of the target process is modified, and then the thread is resumed, running the injected content:

Now, when we look inside the memory of rundll32, we can find the preload and coredll being mapped:

Inside the injected part, the execution follows a similar path: preload loads the coredll and redirects to its Entry Point. But then, another path of execution is taken.

The parameter passed to the coredll decides which round of execution it is. On the second round, another injection is made: this time to dllhost.exe. And finally, it proceeds to the final round, when other modules are unpacked from the BABECAFE filesystem.

The unpacking function first searches by name for two more modules: sputnik.spk and plugins.spk. They are both in the mysterious !rsi format, which reminds us of !rbx, but has a slightly different structure.

Entering the function unpacking the first !rsi package:

The function unpacking the !rsi format is structured similarly to the !rbx unpacking. It also starts from checking the keyword:

As mentioned before, both !rsi packages are used to store filesystems marked with the keyword “SPUTNIK”. It is another custom filesystem invented by the Hidden Bee authors that contain additional modules.

Unpacking the sputnik.spk resulted in getting the following SPUTNIK module: 455738924b7665e1c15e30cf73c9c377

It is worth noting that the unpacked filesystem has inside of it four executables: two pairs consisting of NS and PE, appropriately 32 and 64 bit. In the currently-analyzed setup, 32 bit versions are deployed.

The NS module will be the next to be run. First, it is loaded by the current executable, and then the execution is redirected there. Interestingly, both !rsi modules are passed as arguments to the entry point of the new module. (They will be used later to retrieve more components.)

Sixth stage: mpsi.dll (unpacked from SPUTNIK)

Entering into the NS module starts another layer of the malware:

The analyzed module, converted to PE is available here: 537523ee256824e371d0bc16298b3849

This module is responsible for loading plugins. It will also create a named pipe through which it is will communicate with other modules. It sets up the commands that are going to be executed on demand.

This is how the beginning of the main function looks:

Like in previous cases, it starts from finishing to load itself (relocations and imports). Then, it patches the function in NTDLL. This is a common prolog in many HiddenBee modules.

Then, we have another phase of loading elements from the supplied packages. The path that will be taken depends on the runtime arguments. If the function received both !rsi packages, it will start by parsing one of them, retrieving loading submodules.

First, the SPUTNIK filesystem must be unpacked from the !rsi package:

After being unpacked, it is mounted. The filesystems are mounted internally in the memory: A global structure is filled with pointers to appropriate elements of the filesystem.

At the beginning, we can see the list of the plugins that are going to be loaded: cloudcompute.api, deepfreeze.api, and netscan.api. Those names are being appended to the root path of the modules.

Each module is fetched from the mounted filesystem and loaded:

Consecutive modules are loaded one after another in the same executable memory area. After the module is loaded, its header is erased. It is a common technique used in order to make dumping of the payload from the memory more difficult.

The cloudcompute.api is a plugin that will load the miner. More about the plugins will be explained in the next section of this post.

Reading its code, we find out that the SPUTNIK modules are filesystems that can be mounted and dismounted on demand. This module will be communicating with others with the help of a named pipe. It will be receiving commands and executing appropriate handlers.

Initialization of the commands’ parser:

The function setting up the commands: For each name, a handler is registered. (This is probably the Lua dispatcher, first described here.)

When plugins are run, we can see some additional child processes created by the process running the coredll (in the analyzed case it is inside rundll32):

Also it triggers a firewall alert, which means the malware requested to open some ports (triggered by netscan.api plugin):

We can see that it started listening on one TCP and one UDP port:

The plugins

As mentioned in the previous section, the SPUTNIK filesystem contains three plugins: cloudcompute.api, deepfreeze.api, and netscan.api. If we convert them to PE, we can see that all of them import an unknown DLL: mpsi.dll. When we see the filled import table, we find out that the addresses have been filled redirecting to the functions from the previous NS module:

So we can conclude that the previous element is the mpsi.dll. Although its export table has been destroyed, the functions are fetched by the custom loader and filled in the import tables of the loaded plugins.

First the cloudcompute.api is run.

This plugin retrieves from the filesystem a file named “/etc/ccmain.json” that contains the list of URLs:

Those are addresses from which another set of modules is going to be downloaded:

["sstp://news.onetouchauthentication.online:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.club:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.icu:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.xyz:443/mlf_plug.zip.sig"]

It also retrieves another component from the SPUTNIK filesystem: /bin/i386/ccmain.bin. This time, it is an executable in NE format (version converted to PE is available here: 367db629beedf528adaa021bdb7c12de)

This is the component that is injected into msdtc.exe.

The configuration is also copied into the remote process and is used to retrieve an additional package from the C&C:

This is the plugin responsible for downloading and deploying the Mellifera Miner: core component of the Hidden Bee.

Next, the netscan.api loads module /bin/i386/kernelbase.bin (converted to PE: d7516ad354a3be2299759cd21e161a04)

The miner in APT-style

Hidden Bee is an eclectic malware. Although it is a commodity malware used for cryptocurrency mining, its design reminds us of espionage platforms used by APTs. Going through all its components is exhausting, but also fascinating. The authors are highly professional, not only as individuals but also as a team, because the design is consistent in all its complexity.

Appendix

https://github.com/hasherezade/hidden_bee_tools – helper tools for parsing and converting Hidden Bee custom formats

https://www.bleepingcomputer.com/news/security/new-underminer-exploit-kit-discovered-pushing-bootkits-and-coinminers/

Articles about the previous version (in Chinese):

Our first encounter with the Hidden Bee:

/blog/threat-analysis/2018/07/hidden-bee-miner-delivered-via-improved-drive-by-download-toolkit/

We can see that the changes in the PE headers are bigger and more lossy—only minimalist information is maintained. Only few Data Directories are left. Also the sections table is shrunk: Each section header contains only four out of nine fields that are in the original PE.

Additionally, the format allows to pass a runtime argument from the loader to the payload via header: The pointer is saved into an additional field (marked “Filled Data” on the picture).

Not only is the PE header shrunk. Similar customization is done on the Import Table:

This custom format can also be converted back to the PE format with the help of a dedicated converter, available here.

Third stage: core.sdb

The core.sdb module converted to PE format is available here: a17645fac4bcb5253f36a654ea369bf9.

The interesting part is that the external loader does not complete the full loading process of the module. It only copies the sections. But the rest of the module loading, such as applying relocations and filling imports, is done internally in the core.sdb.

The previous component was supposed to pass to the core.sdb an additional buffer with the data about the installed service: the name and the path. During its execution, core.sdb will look up this data. If found, it will delete the previously-created service, and the initial file that started the infection:

Getting rid of the previous persistence method suggests that it will be replaced by some different technique. Knowing previous editions of Hidden Bee, we can suspect that it may be a bootkit.

After locking the mutex in a format GlobalSC_{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}, the module proceeds to download another component. But before it goes to download, first, a few things are checked.

First of all, there is a defensive check if any of the known debuggers or sniffers are running. If so, the function quits.

Also, there is a check if the application can open a file ‘??NPF-{0179AC45-C226-48e3-A205-DCA79C824051}’.

If all the checks pass, the function proceeds and queries the following URL, where GET variables contain the system fingerprint:

The hash (sz=) is an MD5 generated from VolumeIDs. Then follows the (os=) identifying version of the operating system, and the identifier of the architecture (ar=), where 0 means 32 bit, 1 means 64bit.

The content downloaded from this URL (starting from a magic DWORD 0xFEEDFACE – 79e851622ac5298198c04034465017c0) contains the encrypted package (in !rbx format), and a shellcode that will be used to unpack it. The shellcode is loaded to the current process and then executed.

The shellcode’s start function uses three parameters: pointer to the functions in the previous module (core sdb), pointer to the buffer with encrypted data, size of the encrypted data.

Fourth stage: the shellcode decrypting !rbx

The beginning of the loaded shellcode:

The shellcode does not fill any imports by itself. Instead, it fully relies on the functions from core.sdb module, to which it passes the pointer. It makes use of the following function: malloc, mecpy, memfree, VirtualAlloc.

Its role is to reveal another part. It comes in an encrypted package starting from a marker !rbx. The decryption function is called just at the beginning:

First, the function checks the !rbx marker and the checksum at the beginning of the encrypted buffer:

It is decrypted with the help of RC4 algorithm, and then decompressed.

After decryption, the markers at the beginning of the buffer are checked. The expected format must start from predefined magic DWORDs: 0xCAFEBABE,0, 0xBABECAFE:

The !rbx package format

The !rbx is also a custom format with a consistent structure.

DWORD magic; // "!rbx"
DWORD checksum;
DWORD content_size;
BYTE rc4_key[16];
DWORD out_size;
BYTE content[];

The custom file system (BABECAFE)

The full decrypted content has a consistent structure, reminiscent of a file system. According to the previous reports, earlier versions of Hidden Bee used to adapt the ROMS filesystem, adding few modifications. They called their customized version “Mixed ROM FS”. Now it seems that their customization process has progressed. Also the keywords suggesting ROMFS cannot be found. The headers starts from the markers in the form of three DWORDS: { 0xCAFEBABE, 0, 0xBABECAFE }.

The layout of BABECAFE FS:

We notice that it differs at many points from ROM FS, from which it evolved.

The structure contains the following files:

/bin/amd64/coredll.bin
/bin/i386/coredll.bin
/bin/i386/preload
/bin/amd64/preload
/pkg/sputnik.spk
/installer/com_x86.dll (6177bc527853fe0f648efd17534dd28b)
/installer/com_x64.dll
/pkg/plugins.spk

The files /pkg/sputnik.spk and /pkg/plugins.spk are both compressed packages in a custom !rsi format.

Each of the spk packages contain another custom filesystem, identified by the keyword SPUTNIK (possibly the extension ‘spk’ is derived from the SPUTNIK format). They will be unpacked during the next steps of the execution.

Unpacked plugins.spk: 4c01273fb77550132c42737912cbeb36
Unpacked sputnik.spk: 36f3247dad5ec73ed49c83e04b120523.

Selecting and running modules

Some executables stored in the filesystem are in two version: 32 and 64 bit. Only the modules relevant to the current architecture are loaded. So, in the analyzed case, the loader chooses first: /bin/i386/preload (shellcode) and /bin/i386/coredll.bin (a module in NS custom format). The names are hardcoded in the loader within the loading shellcode:

After the proper elements are fetched (preload and coredll.bin), they are copied together into a newly-allocated memory area. The coredll.bin is copied just after preload. Then, the preload module is called:

The preload is position-independent, and its execution starts from the beginning of the page.

The only role of this shellcode is to prepare and run the coredll.bin. So, it contains a custom loader for the NS format that allocates another memory area and loads the NS file there.

Fifth stage: preload and coredll

After loading coredll, preload redirects the execution there.

The coredll patches a function inside the NTDLL— KiUserExceptionDispatcher—redirecting one of the inner calls to its own code:

Depending on which process the coredll was injected into, it can take one of a few paths of execution.

If it is running for the first time, it will try to inject itself again—this time into rundll32. For the purpose of the injection, it will again unpack the original !rbx package and use its original copy stored there.

Then it will choose the modules depending on the bitness of the rundll32:

It selects the pair of modules (preload/coredll.bin) appropriate for the architecture, either from the directory amd64 or from i386:

If the injection failed, it makes another attempt, this time trying to inject into dllhost:

Each time it uses the same, hardcoded parameter (/Processid: {...}) that is passed to the created process:

The thread context of the target process is modified, and then the thread is resumed, running the injected content:

Now, when we look inside the memory of rundll32, we can find the preload and coredll being mapped:

Inside the injected part, the execution follows a similar path: preload loads the coredll and redirects to its Entry Point. But then, another path of execution is taken.

The parameter passed to the coredll decides which round of execution it is. On the second round, another injection is made: this time to dllhost.exe. And finally, it proceeds to the final round, when other modules are unpacked from the BABECAFE filesystem.

The unpacking function first searches by name for two more modules: sputnik.spk and plugins.spk. They are both in the mysterious !rsi format, which reminds us of !rbx, but has a slightly different structure.

Entering the function unpacking the first !rsi package:

The function unpacking the !rsi format is structured similarly to the !rbx unpacking. It also starts from checking the keyword:

As mentioned before, both !rsi packages are used to store filesystems marked with the keyword “SPUTNIK”. It is another custom filesystem invented by the Hidden Bee authors that contain additional modules.

Unpacking the sputnik.spk resulted in getting the following SPUTNIK module: 455738924b7665e1c15e30cf73c9c377

It is worth noting that the unpacked filesystem has inside of it four executables: two pairs consisting of NS and PE, appropriately 32 and 64 bit. In the currently-analyzed setup, 32 bit versions are deployed.

The NS module will be the next to be run. First, it is loaded by the current executable, and then the execution is redirected there. Interestingly, both !rsi modules are passed as arguments to the entry point of the new module. (They will be used later to retrieve more components.)

Sixth stage: mpsi.dll (unpacked from SPUTNIK)

Entering into the NS module starts another layer of the malware:

The analyzed module, converted to PE is available here: 537523ee256824e371d0bc16298b3849

This module is responsible for loading plugins. It will also create a named pipe through which it is will communicate with other modules. It sets up the commands that are going to be executed on demand.

This is how the beginning of the main function looks:

Like in previous cases, it starts from finishing to load itself (relocations and imports). Then, it patches the function in NTDLL. This is a common prolog in many HiddenBee modules.

Then, we have another phase of loading elements from the supplied packages. The path that will be taken depends on the runtime arguments. If the function received both !rsi packages, it will start by parsing one of them, retrieving loading submodules.

First, the SPUTNIK filesystem must be unpacked from the !rsi package:

After being unpacked, it is mounted. The filesystems are mounted internally in the memory: A global structure is filled with pointers to appropriate elements of the filesystem.

At the beginning, we can see the list of the plugins that are going to be loaded: cloudcompute.api, deepfreeze.api, and netscan.api. Those names are being appended to the root path of the modules.

Each module is fetched from the mounted filesystem and loaded:

Consecutive modules are loaded one after another in the same executable memory area. After the module is loaded, its header is erased. It is a common technique used in order to make dumping of the payload from the memory more difficult.

The cloudcompute.api is a plugin that will load the miner. More about the plugins will be explained in the next section of this post.

Reading its code, we find out that the SPUTNIK modules are filesystems that can be mounted and dismounted on demand. This module will be communicating with others with the help of a named pipe. It will be receiving commands and executing appropriate handlers.

Initialization of the commands’ parser:

The function setting up the commands: For each name, a handler is registered. (This is probably the Lua dispatcher, first described here.)

When plugins are run, we can see some additional child processes created by the process running the coredll (in the analyzed case it is inside rundll32):

Also it triggers a firewall alert, which means the malware requested to open some ports (triggered by netscan.api plugin):

We can see that it started listening on one TCP and one UDP port:

The plugins

As mentioned in the previous section, the SPUTNIK filesystem contains three plugins: cloudcompute.api, deepfreeze.api, and netscan.api. If we convert them to PE, we can see that all of them import an unknown DLL: mpsi.dll. When we see the filled import table, we find out that the addresses have been filled redirecting to the functions from the previous NS module:

So we can conclude that the previous element is the mpsi.dll. Although its export table has been destroyed, the functions are fetched by the custom loader and filled in the import tables of the loaded plugins.

First the cloudcompute.api is run.

This plugin retrieves from the filesystem a file named “/etc/ccmain.json” that contains the list of URLs:

Those are addresses from which another set of modules is going to be downloaded:

["sstp://news.onetouchauthentication.online:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.club:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.icu:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.xyz:443/mlf_plug.zip.sig"]

It also retrieves another component from the SPUTNIK filesystem: /bin/i386/ccmain.bin. This time, it is an executable in NE format (version converted to PE is available here: 367db629beedf528adaa021bdb7c12de)

This is the component that is injected into msdtc.exe.

The configuration is also copied into the remote process and is used to retrieve an additional package from the C&C:

This is the plugin responsible for downloading and deploying the Mellifera Miner: core component of the Hidden Bee.

Next, the netscan.api loads module /bin/i386/kernelbase.bin (converted to PE: d7516ad354a3be2299759cd21e161a04)

The miner in APT-style

Hidden Bee is an eclectic malware. Although it is a commodity malware used for cryptocurrency mining, its design reminds us of espionage platforms used by APTs. Going through all its components is exhausting, but also fascinating. The authors are highly professional, not only as individuals but also as a team, because the design is consistent in all its complexity.

Appendix

https://github.com/hasherezade/hidden_bee_tools – helper tools for parsing and converting Hidden Bee custom formats

https://www.bleepingcomputer.com/news/security/new-underminer-exploit-kit-discovered-pushing-bootkits-and-coinminers/

Articles about the previous version (in Chinese):

Our first encounter with the Hidden Bee:

/blog/threat-analysis/2018/07/hidden-bee-miner-delivered-via-improved-drive-by-download-toolkit/

Starting the analysis from the loader, we will go down to the plugins, showing the inner workings of each element taking part in the loading process.

The loader

In contrast to most of the malware that we see nowadays, the loader is not packed by any crypter. According the header, it was compiled in November 2018.

While in the former edition the modules in the custom formats were dropped as separate files, this time the next stage is unpacked from inside the loader.

The loader is not obfuscated. Once we load it with typical tools (IDA), we can clearly see how the new format is loaded.

Section .shared contains the configuration:

The configuration is decrypted with the help of XTEA algorithm.

The decrypted configuration must start from the magic WORD “pZ.” It contains the C&C and the name under which the service will be installed:

Unscrambling the NE format

The NE format was seen before, in former editions of Hidden Bee. It is just a scrambled version of the PE. By observing which fields have been misplaced, we can easily reconstruct the original PE.

NE is one of the two similar formats being used by this malware. Another similar one starts from a DWORD 0x0EF1FAB9 and is used to further load components. Both of them have an analogical structure that comes from slightly modified PE format:

Header:

WORD magic; // 'NE'
WORD pe_offset;
WORD machine_id;

The conversion back to PE format is trivial: It is enough to add the erased magic numbers: MZ and PE, and to move displaced fields to their original offsets. The tool that automatically does the mentioned conversion is available here.

In the previous edition, the parts of Hidden Bee with analogical functionality were delivered in a different, more complex proprietary format than the one currently being analyzed.

Second stage: a downloader (in NE format)

As a result of the conversion, we get the following PE: (fddfd292eaf33a490224ebe5371d3275). This module is a downloader of the next stage. The interesting thing is that the subsystem of this module is set as a driver, however, it is not loaded like a typical driver. The custom loader loads it into a user space just like any typical userland component.

The function at the module’s Entry Point is called with three parameters. The first is a path of the main module. Then, the parameters from the configuration are passed. Example:

0012FE9C     00601A34  UNICODE ""C:UserstesterDesktopnew_bee.exe""
0012FEA0 00407104 UNICODE "NAPCUYWKOxywEgrO"
0012FEA4 00407004 UNICODE "118.41.45.124:9000"

The execution of the module can take one of the two paths. The first one is meant for adding persistence: The module installs itself as a service.

If the module detects that it is already running as a service, it takes the second path. In such a case, it proceeds to download the next module from the server. The next module is packed as as Cabinet file.

It is first unpacked into a file named “core.sdb”. The unpacked module is in a customized format based on PE. This time, the format has a different signature: “NS” and it is different from the aforementioned “NE” format (detailed explanation will be given further).

It is loaded by the proprietary loader.

The loader enumerates all the executables in a directory: %Systemroot%Microsoft.NET and selects the ones with the compatible bitness (in the analyzed case it was selecting 32bit PEs). Once it finds a suitable PE, it runs it and injects the payload there. The injected code is run by adding its entry point to APC queue.

In case it failed to find the suitable executable in that directory, it performs the injection into dllhost.exe instead.

Unscrambling the NS format

As mentioned before, the core.sdb is in yet another format named NS. It is also a customized PE, however, this time the conversion is more complex than the NE format because more structures are customized. It looks like a next step in the evolution of the NE format.

We can see that the changes in the PE headers are bigger and more lossy—only minimalist information is maintained. Only few Data Directories are left. Also the sections table is shrunk: Each section header contains only four out of nine fields that are in the original PE.

Additionally, the format allows to pass a runtime argument from the loader to the payload via header: The pointer is saved into an additional field (marked “Filled Data” on the picture).

Not only is the PE header shrunk. Similar customization is done on the Import Table:

This custom format can also be converted back to the PE format with the help of a dedicated converter, available here.

Third stage: core.sdb

The core.sdb module converted to PE format is available here: a17645fac4bcb5253f36a654ea369bf9.

The interesting part is that the external loader does not complete the full loading process of the module. It only copies the sections. But the rest of the module loading, such as applying relocations and filling imports, is done internally in the core.sdb.

The previous component was supposed to pass to the core.sdb an additional buffer with the data about the installed service: the name and the path. During its execution, core.sdb will look up this data. If found, it will delete the previously-created service, and the initial file that started the infection:

Getting rid of the previous persistence method suggests that it will be replaced by some different technique. Knowing previous editions of Hidden Bee, we can suspect that it may be a bootkit.

After locking the mutex in a format GlobalSC_{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}, the module proceeds to download another component. But before it goes to download, first, a few things are checked.

First of all, there is a defensive check if any of the known debuggers or sniffers are running. If so, the function quits.

Also, there is a check if the application can open a file ‘??NPF-{0179AC45-C226-48e3-A205-DCA79C824051}’.

If all the checks pass, the function proceeds and queries the following URL, where GET variables contain the system fingerprint:

The hash (sz=) is an MD5 generated from VolumeIDs. Then follows the (os=) identifying version of the operating system, and the identifier of the architecture (ar=), where 0 means 32 bit, 1 means 64bit.

The content downloaded from this URL (starting from a magic DWORD 0xFEEDFACE – 79e851622ac5298198c04034465017c0) contains the encrypted package (in !rbx format), and a shellcode that will be used to unpack it. The shellcode is loaded to the current process and then executed.

The shellcode’s start function uses three parameters: pointer to the functions in the previous module (core sdb), pointer to the buffer with encrypted data, size of the encrypted data.

Fourth stage: the shellcode decrypting !rbx

The beginning of the loaded shellcode:

The shellcode does not fill any imports by itself. Instead, it fully relies on the functions from core.sdb module, to which it passes the pointer. It makes use of the following function: malloc, mecpy, memfree, VirtualAlloc.

Its role is to reveal another part. It comes in an encrypted package starting from a marker !rbx. The decryption function is called just at the beginning:

First, the function checks the !rbx marker and the checksum at the beginning of the encrypted buffer:

It is decrypted with the help of RC4 algorithm, and then decompressed.

After decryption, the markers at the beginning of the buffer are checked. The expected format must start from predefined magic DWORDs: 0xCAFEBABE,0, 0xBABECAFE:

The !rbx package format

The !rbx is also a custom format with a consistent structure.

DWORD magic; // "!rbx"
DWORD checksum;
DWORD content_size;
BYTE rc4_key[16];
DWORD out_size;
BYTE content[];

The custom file system (BABECAFE)

The full decrypted content has a consistent structure, reminiscent of a file system. According to the previous reports, earlier versions of Hidden Bee used to adapt the ROMS filesystem, adding few modifications. They called their customized version “Mixed ROM FS”. Now it seems that their customization process has progressed. Also the keywords suggesting ROMFS cannot be found. The headers starts from the markers in the form of three DWORDS: { 0xCAFEBABE, 0, 0xBABECAFE }.

The layout of BABECAFE FS:

We notice that it differs at many points from ROM FS, from which it evolved.

The structure contains the following files:

/bin/amd64/coredll.bin
/bin/i386/coredll.bin
/bin/i386/preload
/bin/amd64/preload
/pkg/sputnik.spk
/installer/com_x86.dll (6177bc527853fe0f648efd17534dd28b)
/installer/com_x64.dll
/pkg/plugins.spk

The files /pkg/sputnik.spk and /pkg/plugins.spk are both compressed packages in a custom !rsi format.

Each of the spk packages contain another custom filesystem, identified by the keyword SPUTNIK (possibly the extension ‘spk’ is derived from the SPUTNIK format). They will be unpacked during the next steps of the execution.

Unpacked plugins.spk: 4c01273fb77550132c42737912cbeb36
Unpacked sputnik.spk: 36f3247dad5ec73ed49c83e04b120523.

Selecting and running modules

Some executables stored in the filesystem are in two version: 32 and 64 bit. Only the modules relevant to the current architecture are loaded. So, in the analyzed case, the loader chooses first: /bin/i386/preload (shellcode) and /bin/i386/coredll.bin (a module in NS custom format). The names are hardcoded in the loader within the loading shellcode:

After the proper elements are fetched (preload and coredll.bin), they are copied together into a newly-allocated memory area. The coredll.bin is copied just after preload. Then, the preload module is called:

The preload is position-independent, and its execution starts from the beginning of the page.

The only role of this shellcode is to prepare and run the coredll.bin. So, it contains a custom loader for the NS format that allocates another memory area and loads the NS file there.

Fifth stage: preload and coredll

After loading coredll, preload redirects the execution there.

The coredll patches a function inside the NTDLL— KiUserExceptionDispatcher—redirecting one of the inner calls to its own code:

Depending on which process the coredll was injected into, it can take one of a few paths of execution.

If it is running for the first time, it will try to inject itself again—this time into rundll32. For the purpose of the injection, it will again unpack the original !rbx package and use its original copy stored there.

Then it will choose the modules depending on the bitness of the rundll32:

It selects the pair of modules (preload/coredll.bin) appropriate for the architecture, either from the directory amd64 or from i386:

If the injection failed, it makes another attempt, this time trying to inject into dllhost:

Each time it uses the same, hardcoded parameter (/Processid: {...}) that is passed to the created process:

The thread context of the target process is modified, and then the thread is resumed, running the injected content:

Now, when we look inside the memory of rundll32, we can find the preload and coredll being mapped:

Inside the injected part, the execution follows a similar path: preload loads the coredll and redirects to its Entry Point. But then, another path of execution is taken.

The parameter passed to the coredll decides which round of execution it is. On the second round, another injection is made: this time to dllhost.exe. And finally, it proceeds to the final round, when other modules are unpacked from the BABECAFE filesystem.

The unpacking function first searches by name for two more modules: sputnik.spk and plugins.spk. They are both in the mysterious !rsi format, which reminds us of !rbx, but has a slightly different structure.

Entering the function unpacking the first !rsi package:

The function unpacking the !rsi format is structured similarly to the !rbx unpacking. It also starts from checking the keyword:

As mentioned before, both !rsi packages are used to store filesystems marked with the keyword “SPUTNIK”. It is another custom filesystem invented by the Hidden Bee authors that contain additional modules.

Unpacking the sputnik.spk resulted in getting the following SPUTNIK module: 455738924b7665e1c15e30cf73c9c377

It is worth noting that the unpacked filesystem has inside of it four executables: two pairs consisting of NS and PE, appropriately 32 and 64 bit. In the currently-analyzed setup, 32 bit versions are deployed.

The NS module will be the next to be run. First, it is loaded by the current executable, and then the execution is redirected there. Interestingly, both !rsi modules are passed as arguments to the entry point of the new module. (They will be used later to retrieve more components.)

Sixth stage: mpsi.dll (unpacked from SPUTNIK)

Entering into the NS module starts another layer of the malware:

The analyzed module, converted to PE is available here: 537523ee256824e371d0bc16298b3849

This module is responsible for loading plugins. It will also create a named pipe through which it is will communicate with other modules. It sets up the commands that are going to be executed on demand.

This is how the beginning of the main function looks:

Like in previous cases, it starts from finishing to load itself (relocations and imports). Then, it patches the function in NTDLL. This is a common prolog in many HiddenBee modules.

Then, we have another phase of loading elements from the supplied packages. The path that will be taken depends on the runtime arguments. If the function received both !rsi packages, it will start by parsing one of them, retrieving loading submodules.

First, the SPUTNIK filesystem must be unpacked from the !rsi package:

After being unpacked, it is mounted. The filesystems are mounted internally in the memory: A global structure is filled with pointers to appropriate elements of the filesystem.

At the beginning, we can see the list of the plugins that are going to be loaded: cloudcompute.api, deepfreeze.api, and netscan.api. Those names are being appended to the root path of the modules.

Each module is fetched from the mounted filesystem and loaded:

Consecutive modules are loaded one after another in the same executable memory area. After the module is loaded, its header is erased. It is a common technique used in order to make dumping of the payload from the memory more difficult.

The cloudcompute.api is a plugin that will load the miner. More about the plugins will be explained in the next section of this post.

Reading its code, we find out that the SPUTNIK modules are filesystems that can be mounted and dismounted on demand. This module will be communicating with others with the help of a named pipe. It will be receiving commands and executing appropriate handlers.

Initialization of the commands’ parser:

The function setting up the commands: For each name, a handler is registered. (This is probably the Lua dispatcher, first described here.)

When plugins are run, we can see some additional child processes created by the process running the coredll (in the analyzed case it is inside rundll32):

Also it triggers a firewall alert, which means the malware requested to open some ports (triggered by netscan.api plugin):

We can see that it started listening on one TCP and one UDP port:

The plugins

As mentioned in the previous section, the SPUTNIK filesystem contains three plugins: cloudcompute.api, deepfreeze.api, and netscan.api. If we convert them to PE, we can see that all of them import an unknown DLL: mpsi.dll. When we see the filled import table, we find out that the addresses have been filled redirecting to the functions from the previous NS module:

So we can conclude that the previous element is the mpsi.dll. Although its export table has been destroyed, the functions are fetched by the custom loader and filled in the import tables of the loaded plugins.

First the cloudcompute.api is run.

This plugin retrieves from the filesystem a file named “/etc/ccmain.json” that contains the list of URLs:

Those are addresses from which another set of modules is going to be downloaded:

["sstp://news.onetouchauthentication.online:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.club:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.icu:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.xyz:443/mlf_plug.zip.sig"]

It also retrieves another component from the SPUTNIK filesystem: /bin/i386/ccmain.bin. This time, it is an executable in NE format (version converted to PE is available here: 367db629beedf528adaa021bdb7c12de)

This is the component that is injected into msdtc.exe.

The configuration is also copied into the remote process and is used to retrieve an additional package from the C&C:

This is the plugin responsible for downloading and deploying the Mellifera Miner: core component of the Hidden Bee.

Next, the netscan.api loads module /bin/i386/kernelbase.bin (converted to PE: d7516ad354a3be2299759cd21e161a04)

The miner in APT-style

Hidden Bee is an eclectic malware. Although it is a commodity malware used for cryptocurrency mining, its design reminds us of espionage platforms used by APTs. Going through all its components is exhausting, but also fascinating. The authors are highly professional, not only as individuals but also as a team, because the design is consistent in all its complexity.

Appendix

https://github.com/hasherezade/hidden_bee_tools – helper tools for parsing and converting Hidden Bee custom formats

https://www.bleepingcomputer.com/news/security/new-underminer-exploit-kit-discovered-pushing-bootkits-and-coinminers/

Articles about the previous version (in Chinese):

Our first encounter with the Hidden Bee:

/blog/threat-analysis/2018/07/hidden-bee-miner-delivered-via-improved-drive-by-download-toolkit/

Each of the .spk packages contains a custom ‘SPUTNIK’ filesystem, containing more executable modules.

Starting the analysis from the loader, we will go down to the plugins, showing the inner workings of each element taking part in the loading process.

The loader

In contrast to most of the malware that we see nowadays, the loader is not packed by any crypter. According the header, it was compiled in November 2018.

While in the former edition the modules in the custom formats were dropped as separate files, this time the next stage is unpacked from inside the loader.

The loader is not obfuscated. Once we load it with typical tools (IDA), we can clearly see how the new format is loaded.

Section .shared contains the configuration:

The configuration is decrypted with the help of XTEA algorithm.

The decrypted configuration must start from the magic WORD “pZ.” It contains the C&C and the name under which the service will be installed:

Unscrambling the NE format

The NE format was seen before, in former editions of Hidden Bee. It is just a scrambled version of the PE. By observing which fields have been misplaced, we can easily reconstruct the original PE.

NE is one of the two similar formats being used by this malware. Another similar one starts from a DWORD 0x0EF1FAB9 and is used to further load components. Both of them have an analogical structure that comes from slightly modified PE format:

Header:

WORD magic; // 'NE'
WORD pe_offset;
WORD machine_id;

The conversion back to PE format is trivial: It is enough to add the erased magic numbers: MZ and PE, and to move displaced fields to their original offsets. The tool that automatically does the mentioned conversion is available here.

In the previous edition, the parts of Hidden Bee with analogical functionality were delivered in a different, more complex proprietary format than the one currently being analyzed.

Second stage: a downloader (in NE format)

As a result of the conversion, we get the following PE: (fddfd292eaf33a490224ebe5371d3275). This module is a downloader of the next stage. The interesting thing is that the subsystem of this module is set as a driver, however, it is not loaded like a typical driver. The custom loader loads it into a user space just like any typical userland component.

The function at the module’s Entry Point is called with three parameters. The first is a path of the main module. Then, the parameters from the configuration are passed. Example:

0012FE9C     00601A34  UNICODE ""C:UserstesterDesktopnew_bee.exe""
0012FEA0 00407104 UNICODE "NAPCUYWKOxywEgrO"
0012FEA4 00407004 UNICODE "118.41.45.124:9000"

The execution of the module can take one of the two paths. The first one is meant for adding persistence: The module installs itself as a service.

If the module detects that it is already running as a service, it takes the second path. In such a case, it proceeds to download the next module from the server. The next module is packed as as Cabinet file.

It is first unpacked into a file named “core.sdb”. The unpacked module is in a customized format based on PE. This time, the format has a different signature: “NS” and it is different from the aforementioned “NE” format (detailed explanation will be given further).

It is loaded by the proprietary loader.

The loader enumerates all the executables in a directory: %Systemroot%Microsoft.NET and selects the ones with the compatible bitness (in the analyzed case it was selecting 32bit PEs). Once it finds a suitable PE, it runs it and injects the payload there. The injected code is run by adding its entry point to APC queue.

In case it failed to find the suitable executable in that directory, it performs the injection into dllhost.exe instead.

Unscrambling the NS format

As mentioned before, the core.sdb is in yet another format named NS. It is also a customized PE, however, this time the conversion is more complex than the NE format because more structures are customized. It looks like a next step in the evolution of the NE format.

We can see that the changes in the PE headers are bigger and more lossy—only minimalist information is maintained. Only few Data Directories are left. Also the sections table is shrunk: Each section header contains only four out of nine fields that are in the original PE.

Additionally, the format allows to pass a runtime argument from the loader to the payload via header: The pointer is saved into an additional field (marked “Filled Data” on the picture).

Not only is the PE header shrunk. Similar customization is done on the Import Table:

This custom format can also be converted back to the PE format with the help of a dedicated converter, available here.

Third stage: core.sdb

The core.sdb module converted to PE format is available here: a17645fac4bcb5253f36a654ea369bf9.

The interesting part is that the external loader does not complete the full loading process of the module. It only copies the sections. But the rest of the module loading, such as applying relocations and filling imports, is done internally in the core.sdb.

The previous component was supposed to pass to the core.sdb an additional buffer with the data about the installed service: the name and the path. During its execution, core.sdb will look up this data. If found, it will delete the previously-created service, and the initial file that started the infection:

Getting rid of the previous persistence method suggests that it will be replaced by some different technique. Knowing previous editions of Hidden Bee, we can suspect that it may be a bootkit.

After locking the mutex in a format GlobalSC_{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}, the module proceeds to download another component. But before it goes to download, first, a few things are checked.

First of all, there is a defensive check if any of the known debuggers or sniffers are running. If so, the function quits.

Also, there is a check if the application can open a file ‘??NPF-{0179AC45-C226-48e3-A205-DCA79C824051}’.

If all the checks pass, the function proceeds and queries the following URL, where GET variables contain the system fingerprint:

The hash (sz=) is an MD5 generated from VolumeIDs. Then follows the (os=) identifying version of the operating system, and the identifier of the architecture (ar=), where 0 means 32 bit, 1 means 64bit.

The content downloaded from this URL (starting from a magic DWORD 0xFEEDFACE – 79e851622ac5298198c04034465017c0) contains the encrypted package (in !rbx format), and a shellcode that will be used to unpack it. The shellcode is loaded to the current process and then executed.

The shellcode’s start function uses three parameters: pointer to the functions in the previous module (core sdb), pointer to the buffer with encrypted data, size of the encrypted data.

Fourth stage: the shellcode decrypting !rbx

The beginning of the loaded shellcode:

The shellcode does not fill any imports by itself. Instead, it fully relies on the functions from core.sdb module, to which it passes the pointer. It makes use of the following function: malloc, mecpy, memfree, VirtualAlloc.

Its role is to reveal another part. It comes in an encrypted package starting from a marker !rbx. The decryption function is called just at the beginning:

First, the function checks the !rbx marker and the checksum at the beginning of the encrypted buffer:

It is decrypted with the help of RC4 algorithm, and then decompressed.

After decryption, the markers at the beginning of the buffer are checked. The expected format must start from predefined magic DWORDs: 0xCAFEBABE,0, 0xBABECAFE:

The !rbx package format

The !rbx is also a custom format with a consistent structure.

DWORD magic; // "!rbx"
DWORD checksum;
DWORD content_size;
BYTE rc4_key[16];
DWORD out_size;
BYTE content[];

The custom file system (BABECAFE)

The full decrypted content has a consistent structure, reminiscent of a file system. According to the previous reports, earlier versions of Hidden Bee used to adapt the ROMS filesystem, adding few modifications. They called their customized version “Mixed ROM FS”. Now it seems that their customization process has progressed. Also the keywords suggesting ROMFS cannot be found. The headers starts from the markers in the form of three DWORDS: { 0xCAFEBABE, 0, 0xBABECAFE }.

The layout of BABECAFE FS:

We notice that it differs at many points from ROM FS, from which it evolved.

The structure contains the following files:

/bin/amd64/coredll.bin
/bin/i386/coredll.bin
/bin/i386/preload
/bin/amd64/preload
/pkg/sputnik.spk
/installer/com_x86.dll (6177bc527853fe0f648efd17534dd28b)
/installer/com_x64.dll
/pkg/plugins.spk

The files /pkg/sputnik.spk and /pkg/plugins.spk are both compressed packages in a custom !rsi format.

Each of the spk packages contain another custom filesystem, identified by the keyword SPUTNIK (possibly the extension ‘spk’ is derived from the SPUTNIK format). They will be unpacked during the next steps of the execution.

Unpacked plugins.spk: 4c01273fb77550132c42737912cbeb36
Unpacked sputnik.spk: 36f3247dad5ec73ed49c83e04b120523.

Selecting and running modules

Some executables stored in the filesystem are in two version: 32 and 64 bit. Only the modules relevant to the current architecture are loaded. So, in the analyzed case, the loader chooses first: /bin/i386/preload (shellcode) and /bin/i386/coredll.bin (a module in NS custom format). The names are hardcoded in the loader within the loading shellcode:

After the proper elements are fetched (preload and coredll.bin), they are copied together into a newly-allocated memory area. The coredll.bin is copied just after preload. Then, the preload module is called:

The preload is position-independent, and its execution starts from the beginning of the page.

The only role of this shellcode is to prepare and run the coredll.bin. So, it contains a custom loader for the NS format that allocates another memory area and loads the NS file there.

Fifth stage: preload and coredll

After loading coredll, preload redirects the execution there.

The coredll patches a function inside the NTDLL— KiUserExceptionDispatcher—redirecting one of the inner calls to its own code:

Depending on which process the coredll was injected into, it can take one of a few paths of execution.

If it is running for the first time, it will try to inject itself again—this time into rundll32. For the purpose of the injection, it will again unpack the original !rbx package and use its original copy stored there.

Then it will choose the modules depending on the bitness of the rundll32:

It selects the pair of modules (preload/coredll.bin) appropriate for the architecture, either from the directory amd64 or from i386:

If the injection failed, it makes another attempt, this time trying to inject into dllhost:

Each time it uses the same, hardcoded parameter (/Processid: {...}) that is passed to the created process:

The thread context of the target process is modified, and then the thread is resumed, running the injected content:

Now, when we look inside the memory of rundll32, we can find the preload and coredll being mapped:

Inside the injected part, the execution follows a similar path: preload loads the coredll and redirects to its Entry Point. But then, another path of execution is taken.

The parameter passed to the coredll decides which round of execution it is. On the second round, another injection is made: this time to dllhost.exe. And finally, it proceeds to the final round, when other modules are unpacked from the BABECAFE filesystem.

The unpacking function first searches by name for two more modules: sputnik.spk and plugins.spk. They are both in the mysterious !rsi format, which reminds us of !rbx, but has a slightly different structure.

Entering the function unpacking the first !rsi package:

The function unpacking the !rsi format is structured similarly to the !rbx unpacking. It also starts from checking the keyword:

As mentioned before, both !rsi packages are used to store filesystems marked with the keyword “SPUTNIK”. It is another custom filesystem invented by the Hidden Bee authors that contain additional modules.

Unpacking the sputnik.spk resulted in getting the following SPUTNIK module: 455738924b7665e1c15e30cf73c9c377

It is worth noting that the unpacked filesystem has inside of it four executables: two pairs consisting of NS and PE, appropriately 32 and 64 bit. In the currently-analyzed setup, 32 bit versions are deployed.

The NS module will be the next to be run. First, it is loaded by the current executable, and then the execution is redirected there. Interestingly, both !rsi modules are passed as arguments to the entry point of the new module. (They will be used later to retrieve more components.)

Sixth stage: mpsi.dll (unpacked from SPUTNIK)

Entering into the NS module starts another layer of the malware:

The analyzed module, converted to PE is available here: 537523ee256824e371d0bc16298b3849

This module is responsible for loading plugins. It will also create a named pipe through which it is will communicate with other modules. It sets up the commands that are going to be executed on demand.

This is how the beginning of the main function looks:

Like in previous cases, it starts from finishing to load itself (relocations and imports). Then, it patches the function in NTDLL. This is a common prolog in many HiddenBee modules.

Then, we have another phase of loading elements from the supplied packages. The path that will be taken depends on the runtime arguments. If the function received both !rsi packages, it will start by parsing one of them, retrieving loading submodules.

First, the SPUTNIK filesystem must be unpacked from the !rsi package:

After being unpacked, it is mounted. The filesystems are mounted internally in the memory: A global structure is filled with pointers to appropriate elements of the filesystem.

At the beginning, we can see the list of the plugins that are going to be loaded: cloudcompute.api, deepfreeze.api, and netscan.api. Those names are being appended to the root path of the modules.

Each module is fetched from the mounted filesystem and loaded:

Consecutive modules are loaded one after another in the same executable memory area. After the module is loaded, its header is erased. It is a common technique used in order to make dumping of the payload from the memory more difficult.

The cloudcompute.api is a plugin that will load the miner. More about the plugins will be explained in the next section of this post.

Reading its code, we find out that the SPUTNIK modules are filesystems that can be mounted and dismounted on demand. This module will be communicating with others with the help of a named pipe. It will be receiving commands and executing appropriate handlers.

Initialization of the commands’ parser:

The function setting up the commands: For each name, a handler is registered. (This is probably the Lua dispatcher, first described here.)

When plugins are run, we can see some additional child processes created by the process running the coredll (in the analyzed case it is inside rundll32):

Also it triggers a firewall alert, which means the malware requested to open some ports (triggered by netscan.api plugin):

We can see that it started listening on one TCP and one UDP port:

The plugins

As mentioned in the previous section, the SPUTNIK filesystem contains three plugins: cloudcompute.api, deepfreeze.api, and netscan.api. If we convert them to PE, we can see that all of them import an unknown DLL: mpsi.dll. When we see the filled import table, we find out that the addresses have been filled redirecting to the functions from the previous NS module:

So we can conclude that the previous element is the mpsi.dll. Although its export table has been destroyed, the functions are fetched by the custom loader and filled in the import tables of the loaded plugins.

First the cloudcompute.api is run.

This plugin retrieves from the filesystem a file named “/etc/ccmain.json” that contains the list of URLs:

Those are addresses from which another set of modules is going to be downloaded:

["sstp://news.onetouchauthentication.online:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.club:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.icu:443/mlf_plug.zip.sig","sstp://news.onetouchauthentication.xyz:443/mlf_plug.zip.sig"]

It also retrieves another component from the SPUTNIK filesystem: /bin/i386/ccmain.bin. This time, it is an executable in NE format (version converted to PE is available here: 367db629beedf528adaa021bdb7c12de)

This is the component that is injected into msdtc.exe.

The configuration is also copied into the remote process and is used to retrieve an additional package from the C&C:

This is the plugin responsible for downloading and deploying the Mellifera Miner: core component of the Hidden Bee.

Next, the netscan.api loads module /bin/i386/kernelbase.bin (converted to PE: d7516ad354a3be2299759cd21e161a04)

The miner in APT-style

Hidden Bee is an eclectic malware. Although it is a commodity malware used for cryptocurrency mining, its design reminds us of espionage platforms used by APTs. Going through all its components is exhausting, but also fascinating. The authors are highly professional, not only as individuals but also as a team, because the design is consistent in all its complexity.

Appendix

https://github.com/hasherezade/hidden_bee_tools – helper tools for parsing and converting Hidden Bee custom formats

https://www.bleepingcomputer.com/news/security/new-underminer-exploit-kit-discovered-pushing-bootkits-and-coinminers/

Articles about the previous version (in Chinese):

Our first encounter with the Hidden Bee:

/blog/threat-analysis/2018/07/hidden-bee-miner-delivered-via-improved-drive-by-download-toolkit/

ABOUT THE AUTHOR

hasherezade

Malware Intelligence Analyst

Unpacks malware with as much joy as a kid unpacking candies.