Research by: Jiri Vinopal
Over the past few months, we have been monitoring the increasing abuse of BoxedApp products in the wild. BoxedApp products are commercial packers that provide advanced features such as Virtual Storage (Virtual File System, Virtual Registry), Virtual Processes, and a universal instrumentation system (WIN/NT API hooking).
Even though BoxedApp has been commercially available for a while, in the past year we detected a significant increase in its abuse to deploy numerous known malware families, primarily related to RATs and stealers. The majority of the attributed malicious samples targeted financial institutions and government industries.
Our investigation shows that the main abused BoxedApp products are BoxedApp Packer and BxILMerge, which are built on top of the BoxedApp SDK. While both products provide threat actors with access to the most exciting features of the SDK, with the BoxedApp SDK itself they can create a custom, unique packer that leverages the most advanced features and is diverse enough to avoid static detection.
Packing the malware to lower its detection or to harden analysis is a known technique commonly applied to the malware´s payload. While using a known commercial packer has some disadvantages, the benefits of using advanced, unique features easily outweigh them. Among the most interesting features and capabilities of BoxedApp SDK are:
In this report, we provide a general overview of BoxedApp products and their abuse for malicious purposes, as well as an in-depth analysis of the resulting packed binary structures with Yara signatures that can be used to statically detect the packer in use while distinguishing the product itself.
Although BoxedApp products have been available for several years, in the past year there has been a significant increase in their abuse to deploy several different malware families without any public mention of their connection to BoxedApp.
The BoxedApp products are well-known commercial packers, so there are both pros and cons to abusing them to hide malicious payloads:
PROS:
CONS:
As you may expect, the abuse of BoxedApp to deploy malicious payloads and stay under the radar could result in discrepancies caused by its high-rate of FP detection even in non-malicious applications. The built-in Windows Defender and other top-notch AVs are usually not affected, but even a simple “Hello World” application packed by BoxedApp is initially detected by several AV engines.
Product | Original PE Format | Initial VT Detection | Link |
---|---|---|---|
BoxedApp Packer | Native PE (C/C++) | 14/71 | VirusTotal |
BoxedApp Packer | .NET PE (C#, .NET Framework) | 20/71 | VirusTotal |
BoxedApp BxILMerge | .NET PE (C#, .NET Framework) | 2/70 | VirusTotal |
As a side note, the number of FP detections could be significantly lowered by signing the resulted packed binary (regardless of what signature is used) or by using a custom packer built on top of the BoxedApp SDK.
Because of the high rate of AV FP in static detection, which detonates right at the moment of processing BoxedApp SDK, we decided to use the most suitable method (based on the sample´s behavior) to separate the FP from the actual malicious samples.
Among approximately 1200 tested samples (packed by BoxedApp) submitted to VT (VirusTotal) in the last 3 years and successfully processed by VT sandboxes, 25% were detected as “malicious” based on their behavior. The VT submission timeline of those malicious samples shows the increasing trend of BoxedApp abuse for malware deployment.
The table below shows the most deployed, attributed malware families. While a significant portion of the malicious samples are either RATs or stealers, we also detected several instances of ransomware, some of which belong to the notorious LockBit strain.
QuasarRAT | NanoCore | NjRAT | Neshta | AsyncRAT | XWorm | LodaRAT |
RevengeRAT | AgentTesla | LockBit | RedLine | Remcos | ZXShell | Ramnit |
To illustrate the more generic malware classification (based on the VT sandbox results), we separated and sorted the malicious BoxedApp samples based on their Behavior Verdicts.
Half of the malicious samples submitted to VT were primarily from Turkey, the United States, and Germany, but the increasing trend of BoxedApp abuse is apparently worldwide.
Most of the attributed malicious samples were used in attacks against financial institutions and government industries. Using BoxedApp products to pack the malicious payloads enabled the attackers to lower the detection rate, harden their analysis, and use the advanced capabilities of BoxedApp SDK (e.g., Virtual Storage) that would normally take a long time to develop from scratch.
When an application is packed by BoxedApp, the resulting format is a single self-contained PE binary, where the original PE’s imports are destroyed and resolved only during runtime via a stubbed TLS Callback. The TLS Callback is responsible not only for the runtime API resolving but also for initializing Virtual Storage and possibly decompressing its content.
All required dependencies of the original application may be part of the proprietary system of Virtual Storage, which consists of a Virtual File System and Virtual Registry. BoxedApp interceptions of I/O (inline hooking of certain WIN/NT API) handle such virtual files and registry in memory, resulting in the creation of a fake (Virtual) Registry and no files dropping to disk.
When the packed application performs I/O on files or registries that are a part of the Virtual Storage, the BoxedApp internals intercept these I/O operations and direct them to the Virtual Storage (the application does not recognize that it is not interacting with the real registry and files). On the other hand, when the packed application tries to interact with files and registry that are not a part of the Virtual Storage, the internal logic of BoxedApp directs the I/O to the real registry and files on the disk. The Virtual Storage can also be used to fake and mark certain files or registries as non-existing for the packed application despite the fact they exist on a real system. A simplified logic of BoxedApp internals is shown below.
By default, the content of files embedded in the Virtual Storage is clearly readable on disk (and also the main binary), but other compression options can be set. This results in all the embedded virtual files being compressed with the Zlib – DEFLATE algorithm, which makes all virtual files unreadable on disk. The decompression is processed only in memory during the runtime.
One of the other capabilities of BoxedApp is the creation of Virtual Processes that occur after the process is created from an executable file (PE) that is recognized as a virtual file (part of the Virtual Storage). A certain suitable PE binary from the System32/SysWOW64 (depending on the architecture) directory is selected and started as a suspended process. The original PE “Virtual File” is injected into the memory of the remote process (PE Injection is similar to PE Hollowing without unmapping the original main module) with a combination of WIN APIs (VirtualAllocEx
, VirtualProtectEx
, WriteProcessMemory
, CreateRemoteThreadEx
), and no file is dropped to disk.
Among the main BoxedApp products built on top of the BoxedApp SDK are BoxedApp Packer and BxILMerge. While the BoxedApp Packer can pack both native and .NET PEs, the latter is purely tailored to .NET applications.
BoxedApp Packer is a utility that packs the application into a self-contained PE binary (both native and .NET applications are supported). The self-contained PE binary is a single executable binary with all the files that the targeted original application depends on, such as ActiveX controls, dynamic libraries, “squeezed” into that single file. In other words, the packer creates an individual (virtual) work environment for the application.
The other files and registry that the targeted application depends on can be embedded into the Virtual Storage (creating virtual files and registry). BoxedApp interceptions of I/O (inline hooking of certain WIN/NT API) handle virtual files and registry in memory, resulting in no files dropped to disk and creating a fake Registry system (Virtual).
If a compression is selected, the content of files embedded in the Virtual Storage is compressed with the Zlib – DEFLATE algorithm. When packing a native PE binary with the compress option, the original packed binary is still readable on disk. It is not compressed; only the Virtual Storage is compressed.
However, in the case of packing a .NET PE binary with the compress option, the packed stub native PE binary DotNetAppStub is still readable on disk and not compressed; the original .NET PE Binary and the Virtual Storage are compressed.
The structure of the Original Packed Native PE Binary:
.bxpck
PE section – affected by the compression option.bxsdk32.dll/bxsdk64.dll
– 32/64-bit version of native DLL, depending on the architecture (the main part of BoxedAppSDK), in the .main
PE section – not affected by the compression option:
BoxedAppSDK_AppDomainManager.dll
– 32/64-bit version of .NET DLL.BoxedAppSDKThunk.dll
– 32/64-bit version of native DLL.TLSSupport.dll
– 32/64-bit version of native DLL (BoxedApp helper library setting the DllMain Callback).The structure of the Original Packed .NET PE Binary:
When the BoxedApp Packer is used to pack a .NET application, a special stub native PE DotNetAppStub
is created that wraps the original .NET PE into the .bxpck
section right above the Virtual Storage. The Packed Stub Native PE Binary is responsible for the initialization of BoxedApp internals where the in-memory execution of the original .NET PE follows.
DotNetAppStub
– 32/64-bit version, depending on the architecture of the original .NET PE binary (initialization via TLS Callback) – not affected by the compression option:
.bxpck
PE section – affected by the compression option..bxpck
PE section – affected by the compression option.bxsdk32.dll/bxsdk64.dll
– 32/64-bit version of native DLL, depending on the architecture (the main part of BoxedAppSDK), in the .main
PE section – not affected by the compression option:
BoxedAppSDK_AppDomainManager.dll
– 32/64-bit version of .NET DLL.BoxedAppSDKThunk.dll
– 32/64-bit version of native DLL.TLSSupport.dll
– 32/64-bit version of native DLL (BoxedApp helper library setting the DllMain Callback).BxILMerge is similar to ILMerge, a utility that merges multiple .NET assemblies into a single assembly. However, it can also bundle unmanaged DLLs/PEs and any other files such as data files, images, videos, and databases. BxILMerge provides support for packing managed assemblies, their unmanaged dependencies, and other files into a single-file .NET assembly that uses the internal logic of BoxedApp to handle any interactions with them.
The additional merged files (.NET assemblies, unmanaged DLLs, and other files) are embedded into the resulting packed .NET assembly resources. A created module constructor (a part of the packed .NET assembly) is responsible for the initialization of a custom assembly resolver and Virtual Storage where all merged files that are a part of the packed .NET assembly resources become a part of this Virtual Storage as virtual files. All I/O operations that interact with these virtual files (e.g., dependency loading of referenced .NET Assemblies, unmanaged DLLs) are handled via BoxedApp interceptions in a similar way as in the case of BoxedApp Packer (inline hooking of certain WIN/NT API). No file is dropped to disk.
The structure of the Original Packed .NET PE Binary:
BoxedAppSDK.Managed.dll
– AnyCPU version (managed wrapper for BoxedAppSDK), embedded in .NET Resources of the packed .NET PE binary:
bxsdk32.dll
and bxsdk64.dll
– 32/64-bit versions of native DLLs (the main part of BoxedAppSDK), embedded in .NET resources of the BoxedAppSDK.Managed.dll
:
BoxedAppSDK_AppDomainManager.dll
– 32/64-bit version of .NET DLL.BoxedAppSDKThunk.dll
– 32/64-bit version of native DLL.TLSSupport.dll
– 32/64-bit version of native DLL (BoxedApp helper library setting the DllMain Callback).BxIlMerge.Api.dll
– AnyCPU version (managed assembly interacting with BoxedAppSDK.Managed.dll
, initialization of BoxedApp internals, Virtual Storage, creation of virtual files), embedded in .NET resources of the packed .NET PE binary.With our understanding of the binary structures packed by different BoxedApp products, unpacking the original PE binary and all the virtual files is a relatively straightforward task. While the static approach can be used to extract files that are a part of the Virtual Storage (e.g., DIE – Extractor feature, HEX editor), we recommend the dynamic approach to dump the packed PE from the memory and reconstruct the runtime-resolved IAT that was destroyed by the packing algorithm (e.g., combination of x64dbg and Scylla). Unfortunately, existing tools for static unpacking (e.g., unboxed) are not as good or reliable.
We monitored the increasing abuse of BoxedApp products for a few months and discovered how these products are used to deploy numerous known malware families, primarily related to RATs and stealers. The majority of the attributed malicious samples were used in attacks against financial institutions and government industries. Packing the malicious payloads enabled the attackers to lower the detection of known threats, harden their analysis, and use the advanced capabilities of BoxedApp SDK (e.g., Virtual Storage) without needing to develop them from scratch.
Even though BoxedApp has been available for several years, the past year saw a significant increase in its abuse. Among the main abused BoxedApp products are BoxedApp Packer and BxILMerge, which are both built on top of the BoxedApp SDK. Both products give the attackers a direct opportunity to leverage the most exciting features of the SDK, but the BoxedApp SDK itself opens a space to create a custom, unique packer that leverages the most advanced features and is diverse enough to avoid static detection.
By conducting an in-depth analysis of the BoxedApp internals, with the main focus on the resulting binary structures packed by different products, we gained and shared enough knowledge that can help with the unpacking of the Virtual Storage and reconstruction of the main malicious binaries. The provided Yara signatures can be used to statically detect the packer in use while distinguishing the product itself.
import "pe" rule Packer_BoxedApp { meta: description = "Detects .NET/Native PE binary packed by BoxedApp Packer/BxILMerge" author = "Jiri Vinopal @ Check Point Research" date = "2024-04-29" modified = "2024-04-29" reference = "https://www.boxedapp.com/" hash = "77c30d1e3f12151b4e3d3090355c8ce06582f4d0dd3cdb395caa836bd80a97f6" // Native PE binary packed by BoxedApp Packer hash = "c76d2e396d654f6f92ea7cd58d43e739b9f406529369709adece23638436cd25" // .NET PE binary packed by BoxedApp Packer hash = "aefaf8401437262004d384c8f92968cfee9f5563d13c35b347c9f9eefccab7fc" // .NET PE binary packed by BoxedApp BxILMerge tags = "BoxedApp" tool = "BoxedApp" strings: $boxedapp_s1 = "bxsdk" ascii wide $boxedapp_s2 = "BoxedAppSDK_Init" ascii $boxedapp_dotnet1 = "BoxedAppSDK.Managed" ascii wide $boxedapp_dotnet2 = "BxIlMerge.Api" ascii wide condition: uint16(0) == 0x5a4d and uint16(uint32(0x3c)) == 0x4550 and ( (all of ($boxedapp_s*) and for any section in pe.sections : ( section.name == ".bxpck" )) or (all of them and pe.data_directories[pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].virtual_address != 0)) } import "pe" rule Packer_BoxedAppPacker_Native { meta: description = "Detects Native PE binary packed by BoxedApp Packer (result is Native PE binary)" author = "Jiri Vinopal @ Check Point Research" date = "2024-04-29" modified = "2024-04-29" reference = "https://www.boxedapp.com/boxedapppacker/" hash = "77c30d1e3f12151b4e3d3090355c8ce06582f4d0dd3cdb395caa836bd80a97f6" tags = "BoxedApp" tool = "BoxedApp" strings: $boxedapp_s1 = "bxsdk" ascii wide $boxedapp_s2 = "BoxedAppSDK_Init" ascii $boxedapp_dotnet1 = "DotNetAppStub" ascii condition: uint16(0) == 0x5a4d and uint16(uint32(0x3c)) == 0x4550 and all of ($boxedapp_s*) and not $boxedapp_dotnet1 and for any section in pe.sections : ( section.name == ".bxpck" ) } import "pe" rule Packer_BoxedAppPacker_Dotnet { meta: description = "Detects .NET PE binary packed by BoxedApp Packer (result is Native PE binary)" author = "Jiri Vinopal @ Check Point Research" date = "2024-04-29" modified = "2024-04-29" reference = "https://www.boxedapp.com/boxedapppacker/" hash = "c76d2e396d654f6f92ea7cd58d43e739b9f406529369709adece23638436cd25" tags = "BoxedApp" tool = "BoxedApp" strings: $boxedapp_s1 = "bxsdk" ascii wide $boxedapp_s2 = "BoxedAppSDK_Init" ascii $boxedapp_dotnet1 = "DotNetAppStub" ascii condition: uint16(0) == 0x5a4d and uint16(uint32(0x3c)) == 0x4550 and all of ($boxedapp_*) and for any section in pe.sections : ( section.name == ".bxpck" ) } import "pe" rule Packer_BoxedAppBxILMerge_Dotnet { meta: description = "Detects .NET PE binary packed by BoxedApp BxILMerge (result is .NET PE binary)" author = "Jiri Vinopal @ Check Point Research" date = "2024-04-29" modified = "2024-04-29" reference = "https://github.com/boxedapp/bxilmerge" hash = "aefaf8401437262004d384c8f92968cfee9f5563d13c35b347c9f9eefccab7fc" tags = "BoxedApp" tool = "BoxedApp" strings: $boxedapp_s1 = "bxsdk" ascii wide $boxedapp_s2 = "BoxedAppSDK_Init" ascii $boxedapp_dotnet1 = "BoxedAppSDK.Managed" ascii wide $boxedapp_dotnet2 = "BxIlMerge.Api" ascii wide condition: uint16(0) == 0x5a4d and uint16(uint32(0x3c)) == 0x4550 and all of ($boxedapp_*) and pe.data_directories[pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].virtual_address != 0 }
BoxedApp: https://www.boxedapp.com/
BoxedApp Packer: https://www.boxedapp.com/boxedapppacker/
BxILMerge: https://www.boxedapp.com/boxedappsdk/usecases/ilmerge_unmanaged_dll.html
BxILMerge: https://github.com/boxedapp/bxilmerge
ILMerge: https://github.com/dotnet/ILMerge
BoxedApp SDK: https://www.boxedapp.com/boxedappsdk/