Dridex - Technical Malware Analysis

image

Table of Contents

Overview

After receiving feedback on my first malware analysis (Qbot), I realized I had missed many important details in my article. So, I decided to start fresh with a new sample — Dridex.

I chose Dridex because it is one of the most well-known and widespread banking Trojans, dating back to late 2014. Additionally, I find its use of the VEH (Vectored Exception Handling) technique particularly interesting.

Capabilities

TacticTechnique
DEFENSE EVASIONObfuscated Files or Information [T1027]
DEFENSE EVASIONIndicator Removal [T1070]
EXECUTIONShared Modules [T1129]
DISCOVERYProcess Discovery [T1057]
DISCOVERYSystem Information Discovery [T1082]
DISCOVERYSoftware Discovery [T1518]
DISCOVERYQuery Registry [T1012]
COMMAND AND CONTROLApplication Layer Protocol: Web Protocols [T1071.001]
COMMAND AND CONTROLData Encoding [T1132]

Static Code Analysis

Anti-Analysis: Dynamic API Resolving

API hashing has been a common technique in malware development for many years, so its presence in Dridex is not surprising. Dridex relies on three core functions for API resolution:

  • mw_get_dllbase: Retrieves the base address of a specific DLL.
  • mw_load_dll: Loads a DLL if it is not already available.
  • mw_api_resolve: Resolves the address of a specific API function.

image

mw_get_dllbase

This function parses the InLoadOrderModuleList from the Process Environment Block (PEB) to enumerate all loaded modules, including system DLLs.

image

Then, it converts all DLL names to uppercase and hashes them using CRC32. The resulting hash is then XORed with the key 0x38ba5c7b and compared with dll_hash (arg1). If they match, the function returns Flink->DllBase.

image

mw_load_dll

If the current process has not loaded the required DLL, Dridex will call this function.

Basically, it first gathers system information using mw_get_system_info. This function retrieves details about the system Dridex is running on.

Dridex will determine whether to load the DLL from C:\Windows\System32 (for 64-bit DLLs) or C:\Windows\SysWOW64 (for 32-bit DLLs) based on whether it is running as a Wow64 process.

image

After determining the correct system folder path, it locates the appropriate DLL by comparing the XORed CRC32 hash, similar to mw_get_dllbase

Finally, Dridex uses LdrLoadDll (ntdll.dll) to load the identified DLL.

image

mw_api_resolve

This function requires two arguments: the base address of the DLL and the API hash.

Dridex parses the export directory, hashes and XORs each API name, and compares it with the provided hash argument. If they match, it simply returns the API address.

image

Solving

I tried handling this API hashing using hashdb from OALabs in Binary Ninja. Unfortunately, the version released by Cindy Xiao on the Plugin Manager hadn’t been updated yet, so it lacked the XOR key feature.

Feeling a bit lazy to install it from Git, I decided to create a hashes enum myself instead.

This approach was inspired by a stream where Herrcore (from OALabs) collaborated with c3rb3r3u5d3d53c to tackle API hashing using her peexports project.

Simply, I used peexports to extract all API names from each DLL in the system directory and save them to a JSON file. Then, I wrote a quick script to hash them and generate a header file, which I imported into Binary Ninja.

image

Anti-Analysis: Vectored Exception Handling

At the start of its routine, Dridex resolves RtlAddVectoredExceptionHandler (from ntdll.dll) to register an exception handler.

image

This is the exception handler Dridex uses to invoke APIs differently than the call instruction.

image

We will focus only on the STATUS_BREAKPOINT exception code. By using pairs of int3 and retn instructions, Dridex can stealthily call APIs while also making the code more difficult to analyze.

image

Before calling an API, Dridex stores the API’s address in eax and pushes all parameters onto the stack. Then, execution reaches an int3, triggering a STATUS_BREAKPOINT exception.

At this point, the exception handler updates eip to point to the next instruction, which is retn. It then pushes the address of the instruction following retn, along with the API address, onto the stack.

Here is a demo of the stack.

image

The function then return to the API address. As shown in the graph above, esp will point to the Address after retn, forming a normal stack frame for a function call.

Gathering: System Information

As mentioned earlier, Dridex uses the mw_get_system_info function to retrieve system information and store it in a structure.

Get the version information

Dridex resolves GetVersionExW to obtain version details, including the build number, minor version, and major version of the system.

image

Additionally, it calculates the version for later verification, as shown in the image below.

image

Get the architecture

As mentioned in mw_load_dll, Dridex calls IsWow64Process to check whether the current process is running as a Wow64 process. The result is then used to determine whether to load 64-bit or 32-bit system DLLs.

image

Check the current privilege

After determining the architecture, Dridex calls mw_check_Sid_administrators to check its privileges.

image

First, it uses GetTokenInformation with TokenGroups to retrieve information about the group security identifiers (SIDs) in the access token.

image

Then, it calls AllocateAndInitializeSid to create and initialize the Security Identifier S-1-5-32-544, which corresponds to the Administrators group in Windows.

image

Finally, it compares the Administrators SID with each group SID using EqualSid.

image

Get the Windows User Account Controls (UAC) level

Dridex stores the registry key SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System by XORing and CRC32 hashing each subkey. This key is then used to enumerate values from HKEY_LOCAL_MACHINE.

image

After that, it retrieves the values of EnableLUA, ConsentPromptBehaviorAdmin, and PromptOnSecureDesktop, determining a security level from 0 to 5 based on them.

image

Check elevated privileges

By querying TokenElevation with GetTokenInformation, Dridex checks whether the current process has elevated privileges.

image

Get the integrity level

Additionally, Dridex determines the integrity level of the current process.

First, it retrieves the mandatory integrity level by calling GetTokenInformation with TokenIntegrityLevel.

image

Then, Dridex determines the integrity level based on the result of GetSidSubAuthority.

image

Get the system information

Finally, it calls GetSystemInfo to retrieve system details, including the page size, number of processors, and more.

image

Final structure

Below is the final structure Dridex uses to store system information — some fields are still unknown to me.

struct system_info __packed
{
    int32_t Version;
    int32_t BuildNumber;
    char MajorVersion;
    char MinorVersion;
    char unk4;
    char Architecture;
    int16_t unk7;
    int16_t NumberOfProcessors;
    int32_t PageSize;
    int32_t MinimumApplicationAddress;
    int32_t MaximumApplicationAddress;
    int32_t AllocationGranularity;
    int32_t SessionId;
    int32_t UAC;
    char if_AdminstratorsSid;
    char if_ElevatedPrivileges;
    char unk15;
    char unk16;
    int32_t IntegrityLevel;
};

String Encryption

Dridex encrypts strings using RC4 and stores them alongside their corresponding keys in data variables.

image

Sometimes, it decrypts strings using the RC4 function directly, but most of the time, it relies on two wrapper functions. The key length for decrypting strings stored in the binary is 0x28 bytes.

image

In this post, I won’t go into detail about handling this decryption. Instead, here’s a small project where I built a Binary Ninja snippet to automatically find and decrypt all strings — whether they are called directly or through a wrapper.

You can check out the project on Github. Below is a demo.

C&C Communication

To communicate with the C&C server, Dridex encrypts the payload using RC4 before sending it. Here’s what it collects to craft the payload:

First, it retrieves account information, including the computer name, user name, and installation date. Then, it hashes these values using MD5 in the following format: MD5(computername + username + \x00\x00\x00 + installdate + \x00\x00)

image

Dridex also retrieves an MD5 hash that includes the Volume Serial Number and Installation Date. This can be represented as: MD5(volume_serial_number + installdate + \x28\x00\x00\x00)

image

Next, a value derived from the system information structure is appended to the payload. However, some fields, such as unk4 and unk7, are still unknown to me.

image

A list from the registry key HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\ is appended to the final payload, with each entry separated by a semicolon.

image

Finally, the payload is encrypted using a key stored in the binary.

image

The encrypted payload, along with the CRC32 hash, is stored in the variable final, which is then sent to the C2 server.

image

The IP addresses and ports of the C2 server are stored in plain text in the .data section.

image

IOCs

Hash
F9495E968F9A1610C0CF9383053E5B5696ECC85CA3CA2A338C24C7204CC93881
IP addressPort
192.46.210.220443
143.244.140.214808
45.77.0.966891
185.56.219.478116

Conclusion

Dridex is a great case study for understanding how malware gathers system information.

Additionally, its use of the VEH (Vectored Exception Handling) technique is particularly interesting to me, as this is the first time I’ve seen it implemented in malware.

References

Chuong Dong

0x0d4y