| Original Text |
|---|
DodgeBox, a reflective DLL loader written in C, showcases similarities to StealthVector in terms of
concept but incorporates significant improvements in its implementation. It offers various
capabilities, including decrypting and loading embedded DLLs, conducting environment checks and
bindings, and executing cleanup procedures. What sets DodgeBox apart from other malware is its
unique algorithms and techniques.
During our threat hunting activities, we came across two DodgeBox samples that were designed to
be sideloaded by signed legitimate executables. One of these executables was developed by
Sandboxie (SandboxieWUAU.exe), while the other was developed by AhnLab. All exports within the
DLL point to a single function that primarily invokes the main function of the malware, as
illustrated below:
void SbieDll_Hook()
{
if ( dwExportCalled )
{
Sleep(0xFFFFFFFF);
}
else
{
hSbieDll_ = hSbieDll;
dwExportCalled = 1;
MalwareMain();
}
}
MalwareMain implements the main functionality of DodgeBox, and can be broken down into three
main phases:
1. Decryption of DodgeBox’s configuration
DodgeBox employs AES Cipher Feedback (AES-CFB) mode for encrypting its configuration. AES-
CFB transforms AES from a block cipher into a stream cipher, allowing for the encryption of data
with different lengths without requiring padding. The encrypted configuration is embedded within
the .data section of the binary. To ensure the integrity of the configuration, DodgeBox utilizes
hard-coded MD5 hashes to validate both the embedded AES keys and the encrypted configuration.
For reference, a sample of DodgeBox's decrypted configuration can be found in the Appendix
section of this blog. We will reference this sample configuration using the variable Config in the
following sections.
2. Execution guardrails and environment setup
After decrypting its configuration, DodgeBox performs several environment checks to ensure it is
DodgeBox | ThreatLabz
Yin Hong Chang, Sudeep Singh
Technical Analysis
Attack chain
APT41 employs DLL sideloading as a means of executing DodgeBox. They utilize a legitimate
executable (taskhost.exe), signed by Sandboxie, to sideload a malicious DLL (sbiedll.dll). This
malicious DLL, DodgeBox, serves as a loader and is responsible for decrypting a second stage
payload from an encrypted DAT file (sbiedll.dat). The decrypted payload, MoonWalk functions as a
backdoor that abuses Google Drive for command-and-control (C2) communication. The figure
below illustrates the attack chain at a high level.
Figure 1: Attack chain used to deploy the DodgeBox loader and MoonWalk backdoor.
DodgeBox analysis
running on its intended target.
Execution guardrail: Argument check
DodgeBox starts by verifying that the process was launched with the correct arguments. It scans
the argv parameter for a specific string defined in Config.szArgFlag. Next, it calculates the MD5
hash of the subsequent argument and compares it to the hash specified
in Config.rgbArgFlagValueMD5. In this case, DodgeBox expects the arguments to include --type
driver. If this verification check fails, the process is terminated.
Environment setup: API Resolution
Afterwards, DodgeBox proceeds to resolve multiple APIs that are utilized for additional
environment checks and setup. Notably, DodgeBox employs a salted FNV1a hash for DLL and
function names. This salted hash mechanism aids DodgeBox in evading static detections that
typically search for hashes of DLL or function names. Additionally, it enables different samples of
DodgeBox to use distinct values for the same DLL and function, all while preserving the integrity of
the core hashing algorithm.
The following code shows DodgeBox calling its ResolveImport function to resolve the address
of LdrLoadDll, and populating its import table.
// ResolveImport takes in (wszDllName, dwDllNameHash, dwFuncNameHash)
sImportTable->ntdll_LdrLoadDll =
ResolveImport(L"ntdll", 0xFE0B07B0, 0xCA7BB6AC);
Inside the ResolveImport function, DodgeBox utilizes the FNV1a hashing function in a two-step
process. First, it hashes the input string, which represents a DLL or function name. Then, it hashes a
salt value separately. This two-step hashing procedure is equivalent to hashing the concatenation of
the input string and salt. The following pseudo-code represents the implementation of the salted
hash:
dwHash = 0x811C9DC5; // Standard initial seed for FNV1a
pwszInputString_Char = pwszInputString;
cchInputString = -1LL;
do
++cchInputString;
while ( pwszInputString[cchInputString] );
pwszInputStringEnd = (pwszInputString + 2 * cchInputString);
if ( pwszInputString < pwszInputStringEnd )
{
do // Inlined FNV1a hash
{
chChar = pwszInputString_Char;
pwszInputString_Char = (pwszInputString_Char + 1);
dwHash = 0x1000193 * (dwHash ^ chChar);
}
while ( pwszInputString_Char < pwszInputStringEnd );
}
v17 = &g_HashSaltPostfix; // Salt value: CB 24 B4 BA
do // Inlined FNV1a hash, use previous hash as seed
{
v18 = v17;
v17 = (v17 + 1);
dwHash = 0x1000193 * (dwHash ^ v18);
}
while ( v17 < g_HashSaltPostfix_End );
A Python script to generate the salted hashes is included in the Appendix.
In addition to the salted hash implementation, DodgeBox incorporates another noteworthy feature
in its ResolveImport function. This function accepts both the DLL name as a string and its hash
value as arguments. This redundancy appears to be designed to provide flexibility, allowing
DodgeBox to handle scenarios where the target DLL has not yet been loaded. In such cases,
DodgeBox invokes the LoadLibraryW function with the provided string to load the DLL
dynamically.
Furthermore, DodgeBox effectively handles forwarded exports and exports by ordinals. It
utilizes ntdll!LdrLoadDll and ntdll!LdrGetProcedureAddressEx when necessary to resolve the
address of the exported function. This approach ensures that DodgeBox can successfully resolve and
utilize the desired functions, regardless of the export method used.
Environment setup: DLL unhooking
Once DodgeBox has resolved the necessary functions, it proceeds to scan and unhook DLLs that are
loaded from the System32 directory. This process involves iterating through the .pdata section of
each DLL, retrieving each function’s start and end addresses, and calculating an FNV1a hash for the
bytes of each function. DodgeBox then computes a corresponding hash for the same function's bytes
as stored on disk. If the two hashes differ, potential tampering can be detected, and DodgeBox will
replace the in-memory function with the original version from the disk.
For each DLL that has been successfully scanned, DodgeBox marks the
corresponding LDR_DATA_TABLE_ENTRY by clearing the ReservedFlags6 field and setting the upper
bit to 1. This marking allows DodgeBox to avoid scanning the same DLL twice.
Environment setup: Disabling CFG
Following that, DodgeBox checks if the operating system is Windows 8 or newer. If so, the code
verifies whether Control Flow Guard (CFG) is enabled by calling GetProcessMitigationPolicy
with the ProcessControlFlowGuardPolicy parameter. If CFG is active, the malware attempts to
disable it.
To achieve this, DodgeBox locates the LdrpHandleInvalidUserCallTarget function
within ntdll.dll by searching for a specific byte sequence. Once found, the malware patches this
function with a simple jmprax instruction:
ntdll!LdrpHandleInvalidUserCallTarget:
00007ffe |
| Original Text | ChatGPT 4o Paged |
|---|---|
DodgeBox, a reflective DLL loader written in C, showcases similarities to StealthVector in terms of concept but incorporates significant improvements in its implementation. It offers various capabilities, including decrypting and loading embedded DLLs, conducting environment checks and bindings, and executing cleanup procedures. What sets DodgeBox apart from other malware is its unique algorithms and techniques. During our threat hunting activities, we came across two DodgeBox samples that were designed to be sideloaded by signed legitimate executables. One of these executables was developed by Sandboxie (SandboxieWUAU.exe), while the other was developed by AhnLab. All exports within the DLL point to a single function that primarily invokes the main function of the malware, as illustrated below: void SbieDll_Hook() { if ( dwExportCalled ) { Sleep(0xFFFFFFFF); } else { hSbieDll_ = hSbieDll; dwExportCalled = 1; MalwareMain(); } } MalwareMain implements the main functionality of DodgeBox, and can be broken down into three main phases: 1. Decryption of DodgeBox’s configuration DodgeBox employs AES Cipher Feedback (AES-CFB) mode for encrypting its configuration. AES- CFB transforms AES from a block cipher into a stream cipher, allowing for the encryption of data with different lengths without requiring padding. The encrypted configuration is embedded within the .data section of the binary. To ensure the integrity of the configuration, DodgeBox utilizes hard-coded MD5 hashes to validate both the embedded AES keys and the encrypted configuration. For reference, a sample of DodgeBox's decrypted configuration can be found in the Appendix section of this blog. We will reference this sample configuration using the variable Config in the following sections. 2. Execution guardrails and environment setup After decrypting its configuration, DodgeBox performs several environment checks to ensure it is |
Phase: Initial Access
Phase: Execution
Phase: Persistence
Phase: Decryption Phase
Phase: Execution Guardrails
|
DodgeBox | ThreatLabz Yin Hong Chang, Sudeep Singh Technical Analysis Attack chain APT41 employs DLL sideloading as a means of executing DodgeBox. They utilize a legitimate executable (taskhost.exe), signed by Sandboxie, to sideload a malicious DLL (sbiedll.dll). This malicious DLL, DodgeBox, serves as a loader and is responsible for decrypting a second stage payload from an encrypted DAT file (sbiedll.dat). The decrypted payload, MoonWalk functions as a backdoor that abuses Google Drive for command-and-control (C2) communication. The figure below illustrates the attack chain at a high level. Figure 1: Attack chain used to deploy the DodgeBox loader and MoonWalk backdoor. DodgeBox analysis |
Phase: Execution
Phase: Payload Decryption
Phase: Command and Control
|
running on its intended target. Execution guardrail: Argument check DodgeBox starts by verifying that the process was launched with the correct arguments. It scans the argv parameter for a specific string defined in Config.szArgFlag. Next, it calculates the MD5 hash of the subsequent argument and compares it to the hash specified in Config.rgbArgFlagValueMD5. In this case, DodgeBox expects the arguments to include --type driver. If this verification check fails, the process is terminated. Environment setup: API Resolution Afterwards, DodgeBox proceeds to resolve multiple APIs that are utilized for additional environment checks and setup. Notably, DodgeBox employs a salted FNV1a hash for DLL and function names. This salted hash mechanism aids DodgeBox in evading static detections that typically search for hashes of DLL or function names. Additionally, it enables different samples of DodgeBox to use distinct values for the same DLL and function, all while preserving the integrity of the core hashing algorithm. The following code shows DodgeBox calling its ResolveImport function to resolve the address of LdrLoadDll, and populating its import table. // ResolveImport takes in (wszDllName, dwDllNameHash, dwFuncNameHash) sImportTable->ntdll_LdrLoadDll = ResolveImport(L"ntdll", 0xFE0B07B0, 0xCA7BB6AC); Inside the ResolveImport function, DodgeBox utilizes the FNV1a hashing function in a two-step process. First, it hashes the input string, which represents a DLL or function name. Then, it hashes a salt value separately. This two-step hashing procedure is equivalent to hashing the concatenation of the input string and salt. The following pseudo-code represents the implementation of the salted hash: dwHash = 0x811C9DC5; // Standard initial seed for FNV1a pwszInputString_Char = pwszInputString; cchInputString = -1LL; do ++cchInputString; while ( pwszInputString[cchInputString] ); pwszInputStringEnd = (pwszInputString + 2 * cchInputString); if ( pwszInputString < pwszInputStringEnd ) { do // Inlined FNV1a hash { chChar = *pwszInputString_Char; pwszInputString_Char = (pwszInputString_Char + 1); |
Phase: Execution Guardrail
Phase: Execution
Phase: Execution
This setup enables DodgeBox to perform its functions while evading detection through unique, salted hash values for API calls. |
dwHash = 0x1000193 * (dwHash ^ chChar); } while ( pwszInputString_Char < pwszInputStringEnd ); } v17 = &g_HashSaltPostfix; // Salt value: CB 24 B4 BA do // Inlined FNV1a hash, use previous hash as seed { v18 = *v17; v17 = (v17 + 1); dwHash = 0x1000193 * (dwHash ^ v18); } while ( v17 < g_HashSaltPostfix_End ); A Python script to generate the salted hashes is included in the Appendix. In addition to the salted hash implementation, DodgeBox incorporates another noteworthy feature in its ResolveImport function. This function accepts both the DLL name as a string and its hash value as arguments. This redundancy appears to be designed to provide flexibility, allowing DodgeBox to handle scenarios where the target DLL has not yet been loaded. In such cases, DodgeBox invokes the LoadLibraryW function with the provided string to load the DLL dynamically. Furthermore, DodgeBox effectively handles forwarded exports and exports by ordinals. It utilizes ntdll!LdrLoadDll and ntdll!LdrGetProcedureAddressEx when necessary to resolve the address of the exported function. This approach ensures that DodgeBox can successfully resolve and utilize the desired functions, regardless of the export method used. Environment setup: DLL unhooking Once DodgeBox has resolved the necessary functions, it proceeds to scan and unhook DLLs that are loaded from the System32 directory. This process involves iterating through the .pdata section of each DLL, retrieving each function’s start and end addresses, and calculating an FNV1a hash for the bytes of each function. DodgeBox then computes a corresponding hash for the same function's bytes as stored on disk. If the two hashes differ, potential tampering can be detected, and DodgeBox will replace the in-memory function with the original version from the disk. For each DLL that has been successfully scanned, DodgeBox marks the corresponding LDR_DATA_TABLE_ENTRY by clearing the ReservedFlags6 field and setting the upper bit to 1. This marking allows DodgeBox to avoid scanning the same DLL twice. Environment setup: Disabling CFG Following that, DodgeBox checks if the operating system is Windows 8 or newer. If so, the code verifies whether Control Flow Guard (CFG) is enabled by calling GetProcessMitigationPolicy with the ProcessControlFlowGuardPolicy parameter. If CFG is active, the malware attempts to |
Phase: Discovery
Phase: Defense Evasion
Phase: Defense Evasion
|
disable it.
To achieve this, DodgeBox locates the LdrpHandleInvalidUserCallTarget function
within ntdll.dll by searching for a specific byte sequence. Once found, the malware patches this
function with a simple jmprax instruction:
ntdll!LdrpHandleInvalidUserCallTarget:
00007ffe |
Phase: Defense Evasion
Phase: Execution Guardrail
Phase: Decryption
Phase: Environment Keying
|
machine's GUID as the AES IV. This approach guarantees that the decrypted DAT file cannot be
decrypted on any other machine, thus enhancing the payload's security.
Loading the payload using DLL hollowing
Next, DodgeBox reflectively loads the payload using a DLL hollowing technique. At a high level, the
process begins with the random selection of a host DLL from the System32 directory, ensuring it is
not on a blocklist (DLL blocklist available in the Appendix section) and has a sufficiently large .text
section. A copy of this DLL is then created at C:
\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Data.Trace\v4.0_4.0.0.0__ |
Phase: Defense Evasion
Phase: Defense Evasion
|
Figure 2: Normal example of stack trace from explorer.exe calling CreateFileW. In contrast, the next figure shows the call stack recorded by SysMon when DodgeBox uses stack spoofing to call the CreateFileW function. Notice that there is no indication of DodgeBox’s modules that triggered the API call. Instead, the modules involved all appear to be legitimate Windows modules. Figure 3: Stack trace of DodgeBox calling CreateFileW using the stack spoofing technique. There is an excellent writeup of this technique, so we will only highlight some implementation details specific to DodgeBox: When the CallFunction is invoked, DodgeBox uses a random jmp qword ptr [rbp+48h] gadget residing within the .text section of KernelBase. DodgeBox analyzes the unwind codes within the .pdata section to extract the unwind size for the function that includes the selected gadget. DodgeBox obtains the addresses of RtlUserThreadStart + 0x21 and BaseThreadInitThunk + 0x14, along with their respective unwind sizes. DodgeBox sets up the stack by inserting the addresses of RtlUserThreadStart + 0x21, BaseThreadInitThunk + 0x14, and the address of the gadget at the right positions, utilizing the unwind sizes retrieved. Following that, DodgeBox proceeds to insert the appropriate return address at [rbp+48h] and prepares the registers and stack with the necessary argument values to be passed to the API. This preparation ensures that the API is called correctly and with the intended parameters. Finally, DodgeBox executes a jmp instruction to redirect the control flow to the targeted API. Explore more Zscaler blogs |
Phase: Execution
|
(sImportTable->ntdll_RtlInitUnicodeString)(v25, v26); However, call stack spoofing is used when calling NtAllocateVirtualMemory, an API known to be abused by malware, as shown below: CallFunction( sImportTable->ntdll_NtAllocateVirtualMemory, // API to call 0, // Unused 6LL, // Number of parameters // Parameters to the API -1LL, &pAllocBase, 0LL, &dwSizeOfImage, 0x3000, PAGE_READWRITE) The technique mentioned above can be observed in the figures below. In the first figure, we can see a typical call stack when explorer.exe invokes the CreateFileW function. The system monitoring tool, SysMon, effectively walks the call stack, enabling us to understand the purpose behind this API call and examine the modules and functions involved in the process. |
Phase: Memory Manipulation
Example API Call:
|
| Gemini 2.0 |
|---|
Model: gemini-2.0-flashPhase: Initial Access
Phase: Execution
|
| Gemini 2.5 |
|---|
Model: gemini-2.5-flash-preview-05-20Here's a structured breakdown of the attack procedures and techniques, suitable for red team emulation, based on the provided CTI report: Phase: Initial Execution
Phase: Loader Execution & Defense Evasion (DodgeBox)
Phase: Payload Decryption & Loading
Phase: Defense Evasion (Post-Execution)
Phase: Command and Control
|