TL;DR#
A Dridex loader DLL with minimal static imports (OutputDebugStringA,
Sleep). It dynamically resolves all needed APIs at runtime using CRC32
hashing XOR-ed with 0x38BA5C7B, and calls them indirectly via int3/retn
with a registered vectored exception handler — neutralizing debugger
breakpoints in the process. Embedded strings are encrypted with RC4 using a
40-byte reversed key. After passing anti-VM and execution delay checks, it
connects to four hardcoded C2 servers to download additional modules via
InternetConnectW and InternetReadFile.
Initial Analysis#
| Field | Value |
|---|---|
| Type | DridexLoader Payload: 32-bit DLL |
| File Name | malware.dll |
| File Type | PE32 executable (DLL) (GUI) Intel 80386, for MS Windows |
| File Size | 249856 bytes |
| MD5 | df1b0f2d8e1c9ff27a9b0eb50d0967ef |
| SHA256 | f9495e968f9a1610c0cf9383053e5b5696ecc85ca3ca2a338c24c7204cc93881 |
The binary was identified by CAPA as DridexLoader Payload and matched
the YARA rule HeavensGate — indicating use of the Heaven’s Gate technique
to switch from 32-bit to 64-bit mode at runtime.

Imports#
Only two static imports were found:
1OutputDebugStringA
2SleepExports#
1.text:0x00009D70 .rdata:0x0003AB08 DllRegisterServerSections#
The .rdata section showed unusually high entropy of 7.761, suggesting
compressed or encrypted data. The .text section had entropy of 6.529.

CAPEv2 Sandbox#
The anti-VM functionality limited sandbox visibility, but CAPEv2 successfully extracted the malware configuration — revealing four C2 servers and the RC4 encryption key used for communication:
1C2: 192.46.210.220:443
2 143.244.140.214:808
3 45.77.0.96:6891
4 185.56.219.47:8116
5RC4: 9fRysqcdPgZffBlroqJaZHyCvLvD6BUV
Static Analysis#
Resolving API#
Function mw_API_resolve is called twice from the entry point function, both times with the same value for the first parameters. For the second call, the return value is called as a function, so we know that it must be dynamically resolving API through the hashes from its parameters. Since both calls share the same value for the first parameter but different values for the second one, we can assume that the first hash corresponds to a library name, and the second one corresponds to the name of the target API in that library.
1BOOL __stdcall DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
2{
3 v7[0] = v3;
4 v4 = hinstDLL;
5 sub_607980(v7, 0);
6 dword_62B1D4 = mw_API_resolve(-1590620315, 497732535);
7 if ( !byte_62B028 )
8 {
9 if ( hinstDLL != NtCurrentTeb()->ProcessEnvironmentBlock )
10 byte_62B265 = 1;
11 if ( !byte_62B265 )
12 {
13 mw_anti_vm(v7[0]);
14 dword_62B1D4(0);
15 }
16//...[snip]...
17 || (byte_62B004 = 0, (v6 = mw_API_resolve(-1590620315, -1462740277)) != 0)
18 && v6(0, 0, sub_5F5100, hinstDLL, 0, 0) )Investigation of cross-references (xrefs) to sub_6015C0 reveals that this function is called multiple times throughout the malware’s code, each time with different hash values as parameters. which confirms that our assumption about tecnhique dynamic API resolving
The subroutine first starts with passing the DLL hash to the functions sub_686C50 and sub_687564. The return value and the API hash are then passed into sub_6067C8 as parameters. From this, we can assume the first two functions retrieve the base of the DLL corresponding to the DLL hash, and this base address is passed to the last function with the API hash to resolve the API.
1int __stdcall mw_API_resolve(int maybe_DLL_hash, int maybe_API_hash)
2{
3//...[snip]...
4v6 = sub_607564(maybe_DLL_hash, maybe_DLL_hash);
5 if ( !v6 )
6 {
7 if ( sub_606C50(maybe_DLL_hash) )
8 v6 = sub_607564(maybe_DLL_hash, maybe_DLL_hash);
9 }
10 if ( !v6 )
11 return 0;
12 else
13 return sub_6067C8(v6, maybe_API_hash, v7, v8);
14}Hashing algorithm#
sub_607564 is the hashing algorithm. The target API hash is XOR-ed with 0x38BA5C7B before being compared to the hash of each API name
1void *__userpurge sub_607564@<eax>(int maybe_DLL_hash@<eax>, int maybe_API_hash)
2{
3//...[snip]...
4 if ( dll_hash == (sub_61D620(v43, v16) ^ 0x38BA5C7B) )
5//...[snip]...
Depend on constant values being loaded or used in the program can pick out the algorithm.
Among the three constants being used in this function, one stands out with the repetition of the value 0x0EDB8832, which is typically used in the CRC32 hashing algorithm. So, sub_69D620 is a function to generate a CRC32 hash from a given string, and the API hashing algorithm of DRIDEX boils down to XOR-ing the CRC32 hash of API/DLL names with 0x38BA5C7B.
1.rdata:0062A2F0 xmmword_62A2F0 xmmword 3000000020000000100000000h
2.rdata:0062A2F0 ; DATA XREF: sub_61D620+11↑r
3.rdata:0062A300 xmmword_62A300 xmmword 1000000010000000100000001h
4.rdata:0062A300 ; DATA XREF: sub_61D620+2D↑r
5.rdata:0062A300 ; sub_61D620+9A↑r ...
6.rdata:0062A310 xmmword_62A310 xmmword 0EDB88320EDB88320EDB88320EDB88320hI used hashdb to look up the hashes in the sample. The hash 0x1DAACBB7 corresponds correctly to the ExitProcess API, which confirms that our assumption about the hashing algorithm is correct.
C2 Communication#
To identify all resolved APIs without manually tracing each hash, I wrote
an IDAPython script that extracted all push arguments preceding calls to
mw_API_and_DLL_resolve and saved them to a file. Each hash was then resolved against the hashdb API using the XOR key
0x38BA5C7B identified earlier:
1import requests
2
3filename = r"C:\Users\f\Desktop\calls.txt"
4xor_key = 0x38BA5C7B
5
6unique_args = {}
7
8with open(filename, "r") as file:
9 for line in file:
10 parts = line.split()
11 if len(parts) >= 3:
12 addr, dll_h, api_h = parts[0], int(parts[1]), int(parts[2])
13 for h in [dll_h, api_h]:
14 if h not in unique_args:
15 unique_args[h] = set()
16 unique_args[h].add(addr)
17
18for hash_value in sorted(unique_args):
19 response = requests.get(
20 f"https://hashdb.openanalysis.net/hash/crc32/{str(hash_value ^ xor_key)}"
21 )
22 if response.status_code == 200:
23 data = response.json()
24 if data.get("hashes"):
25 name = data["hashes"][0]["string"]["string"]
26 addrs = ", ".join(sorted(unique_args[hash_value]))
27 print(f"Found: {name} @ {addrs}")The full WinINet call chain was recovered, confirming a complete HTTP-based C2 communication stack:
1Found: InternetOpenA @ 0x00623238
2Found: InternetConnectW @ 0x0062346C
3Found: HttpOpenRequestW @ 0x00623508
4Found: HttpSendRequestW @ 0x0062388E
5Found: InternetReadFile @ 0x00623AD6
6Found: HttpQueryInfoW @ 0x0062392F, 0x00623985, 0x006239DE
7Found: InternetSetOptionW @ 0x006235FF, 0x00623622, 0x0062367B
8Found: InternetQueryOptionW @ 0x0062364D
9Found: InternetCloseHandle @ 0x0062327D, 0x0062330D, ...
The function responsible for C2 connection is at 0x00623370
(InternetConnectW), and the module download function is at 0x00623820
(InternetReadFile).
Resolved API Analysis#
The full resolved API list revealed several additional capability clusters beyond the C2 stack.
Process injection — a complete injection toolkit using Nt* functions
directly from NTDLL to bypass higher-level API monitoring:
NtAllocateVirtualMemory, NtWriteVirtualMemory, NtReadVirtualMemory,
NtProtectVirtualMemory, NtMapViewOfSection, NtUnmapViewOfSection,
NtCreateSection, RtlCreateUserThread, NtQueueApcThread, NtResumeThread.
Registry persistence — RegCreateKeyExW, RegSetValueExA,
RegLoadKeyW, RegUnLoadKeyW, RegOpenKeyExW, RegQueryValueExW/A
indicate reading and writing of registry keys including hive load/unload
operations — NTUSER.DAT was also present in the decrypted strings.
Cryptography — CryptAcquireContextW, CryptGenRandom,
CryptCreateHash, CryptHashData, CryptGetHashParam, CryptDestroyHash
indicate use of the Windows CryptoAPI for key generation or data hashing
separate from the RC4 string encryption.
Process and memory enumeration — K32GetProcessImageFileNameW,
K32EnumProcessModulesEx, K32GetModuleBaseNameW,
NtQuerySystemInformation, NtQueryVirtualMemory,
CreateToolhelp32Snapshot, Thread32First, Thread32Next point to
thorough process and module scanning consistent with injection target
selection.
Inter-process atom communication — GlobalAddAtomW,
GlobalGetAtomNameA/W, GlobalDeleteAtom are used for stealthy
inter-process signaling without named pipes or sockets.
COM usage — CoCreateInstance, CoInitializeEx, CoUninitialize
suggest use of COM objects, possibly for IWebBrowser2-based form
grabbing consistent with Dridex’s known banking capabilities.
Exception Handler#
The sample does not use the call instruction to call APIs. Instead, the malware uses a combination of int3 and retn instructions to call its Windows APIs after dynamically resolving them.
The function sub_607980 dynamically resolves RtlAddVectoredExceptionHandler and calls it
to register sub_607D40 as a vectored exception handler. This means that when the program
encounters an int3 instruction, sub_607D40 is invoked by the kernel to handle the interrupt
and transfer control to the API stored in eax.
1LABEL_9:
2 v8 = mw_DLL_base(0x588AB3EA, 0x588AB3EA);
3 if ( !v8 && sub_606C50(NTDLL_DLL) )
4 v8 = mw_DLL_base(0x588AB3EA, 0x588AB3EA); // NTDLL.DLL
5 if ( v8 )
6 v9 = mw_API_resolve(v8, 0x82115E73, v10, v11);// RtlAddVectoredExceptionHandler
7 else
8 v9 = nullptr;
9LABEL_12:
10 n787139894 = sub_607A60(v9);
11 byte_62B26C = 0;
12 }sub_607D40 handles three exception codes:
| NTSTATUS Code | Symbolic Name | Description |
|---|---|---|
0xC0000005 | STATUS_ACCESS_VIOLATION | Invalid memory access |
0xC00000FD | STATUS_STACK_OVERFLOW | Stack exhaustion |
0xC0000374 | STATUS_HEAP_CORRUPTION | Heap metadata corruption |
1int __stdcall sub_607D40(int **a1)
2{
3 v1 = **a1;
4 if ( v1 == 0xC0000005 || v1 == 0xC00000FD || v1 == 0xC0000374 )
5 {
6 //...[snip]...
7 kernel32_base = mw_DLL_base(0xA1310F65, 0xA1310F65);
8 if ( !kernel32_base )
9 {
10 if ( sub_606C50(0xA1310F65) )
11 kernel32_base = mw_DLL_base(0xA1310F65, 0xA1310F65);// KERNEL32.DLL
12 }
13 if ( kernel32_base )
14 {
15 TreminateProcess = mw_API_resolve(kernel32_base, 0x93FAE3F6, v7, v8);// TreminateProcess
16//...[snip]...
17 __debugbreak();
18 return TreminateProcess;For these exceptions, the handler dynamically resolves an API using module hash 0xA1310F65(KERNEL32.DLL)
and function hash 0x93FAE3F6(TerminateProcess)
For STATUS_BREAKPOINT (0x80000003), the handler manually patches the exception context
record, advancing EIP past the breakpoint and adjusting the stack before returning
EXCEPTION_CONTINUE_EXECUTION (-1):
1//...[snip]...
2 else if ( v1 = 0x80000003 )
3 {
4 ++a1[1][46];
5 a1[1][49] -= 4;
6 *a1[1][49] = a1[1][46] + 1;
7 a1[1][49] -= 4;
8 *a1[1][49] = a1[1][44];
9 return -1;
10 }This is a known anti-debugging technique: by silently swallowing STATUS_BREAKPOINT,
DRIDEX neutralizes software breakpoints set by a debugger, allowing execution to continue
transparently from the next instruction.
Anti-VM#
The callback function sub_5F5100 called mw_anti_vm(), resolved two
APIs via hash, then performed an unconditional jmp to a resolved address —
consistent with a stager or loader pattern that transfers execution to
unpacked code:
1void __cdecl sub_5F5100(int a1)
2{
3 int v1; // [esp+0h] [ebp-8h]
4
5 mw_anti_vm();
6 v1 = mw_API_resolve(-1590620315, -169236058);
7 mw_API_resolve(-1590620315, -1206567270);
8 __asm { jmp [ebp+var_8] }
9}mw_anti_vm() implemented an execution delay loop that ran up to
199,999,100 iterations, calling OutputDebugStringA and Sleep(0xA)
repeatedly — a common sandbox evasion technique designed to exhaust
sandbox time limits:
1while ( 1 )
2{
3 sub_615CB0(20, 80);
4 OutputDebugStringA(lpOutputString[0]);
5 Sleep(0xAu);
6 sub_610B10(lpOutputString);
7 if ( ++v1 >= 199999100 )
8 break;
9 while ( v1 >= 4987 )
10 {
11 if ( ++v1 >= 199999100 )
12 {
13 OutputDebugStringA(v75);
14 goto LABEL_9;
15 }
16 }Strings Encryption#
capa identified RC4 encryption capabilities in the binary:
1encrypt data using RC4 KSA
2namespace data-manipulation/encryption/rc4
3scope function
4matches 0x61E5D0
5
6encrypt data using RC4 PRGA
7namespace data-manipulation/encryption/rc4
8scope function
9matches 0x61E5D0I renamed 0x61E5D0 to mw_RC4. The signed int a2 parameter may indicates
the key length:
1void __fastcall mw_RC4(int a1, signed int a2, int a3, int a4, int a5, int (__stdcall *a6)(int, int), int a7)
Tracing the callers of mw_RC4 confirmed the key length is 40 bytes
— passed as the second parameter. Before the key is applied, sub_61E780
performs a byte-reversal on the key bytes:

Following the call chain to identify the encrypted data source, I found
that sub_607B30 is called with &unk_629BC0 as the data parameter:
1_WORD **__fastcall sub_5FAC00(_WORD **a1, int a2)
2{
3 sub_607B30(a1, &unk_629BC0, a2);
4 return a1;
5}
The RC4 key (before reversal) is:
1D5BBC53E129470925A59E6EA6AA9E6C48BC48D5093D51CD433884126BAE4A81560E7B19148933CDBAfter accounting for the byte-reversal and decrypting,
the plaintext strings from 0x629BC0 were recovered:
1Program Manager
2Progman
3AdvApi32~PsApi~shlwapi~shell32~WinInet
4/run /tn "%ws"
5"%ws" /grant:r "%ws":F
6\NTUSER.DAT
7winsxs
8x86_*
9amd64_*
10*.exe
11\Sessions\%d\BaseNamedObjects\These strings reveal the malware’s targets and internal logic:
AdvApi32~PsApi~shlwapi~shell32~WinInet is a tilde-delimited list of
DLLs the malware dynamically resolves, Program Manager / Progman
indicate process or window targeting, and the schtasks /run /tn "%ws"
template suggests scheduled task abuse for execution or persistence.
IOCs#
Files
- malware.dll
- MD5: df1b0f2d8e1c9ff27a9b0eb50d0967ef
- SHA256: f9495e968f9a1610c0cf9383053e5b5696ecc85ca3ca2a338c24c7204cc93881
Network
- C2: 192.46.210.220:443
- C2: 143.244.140.214:808
- C2: 45.77.0.96:6891
- C2: 185.56.219.47:8116
Encryption
- Algorithm: RC4
- Key (pre-reversal): D5BBC53E129470925A59E6EA6AA9E6C48BC48D5093D51CD433884126BAE4A81560E7B19148933CDB
- CAPEv2 key: 9fRysqcdPgZffBlroqJaZHyCvLvD6BUV
Attack Flow#
%%{init: {'theme': 'base', 'themeVariables': { 'background': '#ffffff', 'mainBkg': '#ffffff', 'primaryTextColor': '#000000', 'lineColor': '#333333', 'clusterBkg': '#ffffff', 'clusterBorder': '#333333'}}}%%
graph TD
classDef default fill:#f9f9f9,stroke:#333,stroke-width:1px,color:#000;
classDef input fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,color:#000;
classDef check fill:#fff9c4,stroke:#fbc02d,stroke-width:2px,stroke-dasharray: 5 5,color:#000;
classDef exec fill:#ffebee,stroke:#c62828,stroke-width:2px,color:#000;
classDef term fill:#e0e0e0,stroke:#333,stroke-width:2px,color:#000;
Load([malware.dll Loaded
DllEntryPoint]):::input --> APIResolve[Dynamic API Resolution
CRC32 XOR 0x38BA5C7B]:::exec
subgraph Evasion [Evasion]
APIResolve --> VEH[Register VEH
RtlAddVectoredExceptionHandler]:::exec
VEH --> Int3[int3 + retn
Indirect API Calls]:::exec
Int3 --> AntiVM{Anti-VM
Execution Delay Loop
199,999,100 iterations}:::check
AntiVM -.->|Timeout / VM| Exit[Exit]:::term
AntiVM -- Pass --> HeavensGate[Heaven's Gate
32-bit → 64-bit switch]:::exec
end
subgraph Init [Initialization]
HeavensGate --> RC4[RC4 String Decryption
40-byte reversed key]:::exec
RC4 --> Strings["Program Manager, Progman
AdvApi32~PsApi~shlwapi~shell32~WinInet
NTUSER.DAT, schtasks /run /tn"]:::exec
Strings --> Loader[sub_5F5100
Resolve APIs + jmp to unpacked code]:::exec
end
subgraph Persistence [Persistence]
Loader --> Reg[Registry R/W
RegCreateKeyExW / RegSetValueExA]:::exec
Loader --> Hive[Offline Hive Manipulation
RegLoadKeyW / NTUSER.DAT]:::exec
Loader --> Task[Scheduled Task
schtasks /run /tn]:::exec
end
subgraph Injection [Process Injection]
Loader --> Enum[Process Enumeration
Toolhelp32 / NtQuerySystemInformation]:::exec
Enum --> Target[Select Injection Target]:::exec
Target --> Alloc[NtAllocateVirtualMemory
NtWriteVirtualMemory]:::exec
Alloc --> Protect[NtProtectVirtualMemory
NtMapViewOfSection]:::exec
Protect --> Thread[RtlCreateUserThread
NtQueueApcThread / NtResumeThread]:::exec
end
subgraph C2 [C2 Communication]
Thread --> IOpen[InternetOpenA]:::exec
IOpen --> IConnect[InternetConnectW
0x00623370]:::exec
IConnect --> IRequest[HttpOpenRequestW
HttpSendRequestW]:::exec
IRequest --> IRead[InternetReadFile
0x00623820
Download modules]:::exec
IRead --> IP1((192.46.210.220:443)):::exec
IRead --> IP2((143.244.140.214:808)):::exec
IRead --> IP3((45.77.0.96:6891)):::exec
IRead --> IP4((185.56.219.47:8116)):::exec
end
subgraph Banking [Banking Capabilities]
Thread --> COM[CoCreateInstance
IWebBrowser2 Form Grabbing]:::exec
Thread --> Atoms[GlobalAddAtomW
Inter-process Signaling]:::exec
Thread --> Crypt[CryptoAPI
CryptGenRandom / CryptHashData]:::exec
end