Qilin Ransomware (Rust Variant) Analysis
Disclaimer: This analysis is conducted for educational and defensive security research purposes only. The malware sample was analyzed in an isolated environment. All IOCs are provided to help defenders protect their networks.
Table of Contents
- 1. Executive Summary
- 2. Sample Overview
- 3. Execution Flow Overview
- 4. Configuration Parsing
- 5. Command-Line Flags
- 6. Pre-Encryption Pipeline
- 7. Lateral Movement
- 8. Encryption Engine
- 9. Post-Encryption Cleanup
- 10. Decryption Feasibility Assessment
- 11. Build Environment & Rust Crate Analysis
- 12. MITRE ATT&CK Mapping
- 13. Indicators of Compromise (IOCs)
- 14. YARA Rule
- 15. Conclusion
1. Executive Summary
Qilin (formerly Agenda) is a Ransomware-as-a-Service (RaaS) operation active since mid-2022 that has steadily evolved its toolset. This analysis covers a Rust-compiled 32-bit Windows DLL variant that represents Qilin’s mature Windows encryptor.
Key findings:
- Hybrid encryption: AES-256-CTR (via hardware AESNI) or ChaCha20 fallback, with RSA-4096 OAEP key wrapping
- Runtime cipher selection: Uses
CPUIDto detect AESNI support at runtime, selecting the optimal symmetric cipher - Separate SSD/HDD encryption thread pools: Optimizes I/O patterns for different storage media
- Comprehensive lateral movement: Embedded PsExec binary, vCenter/ESXi spreading via PowerShell, domain computer enumeration via Active Directory
- Anti-forensics: VSS shadow deletion, event log purging,
cipher /w:disk overwriting, self-deletion - Double extortion: Data exfiltration threats combined with file encryption
- Decryption without private key: NOT feasible — RSA-4096 wraps per-file symmetric keys
2. Sample Overview
| Property | Value |
|---|---|
| SHA-256 | ee24110ddb4121b31561f86692650b63215a93fb2357b2bd3301fabc419290a3 |
| File Type | PE32 DLL (i386) |
| Compiler | Rust 1.73.0 (rustc d5c2e9c342b) cross-compiled via MinGW/GCC |
| Architecture | 32-bit (x86) |
| Export | DllMain (ordinal 1) |
| Sections | .text (1.8MB), .rdata (1.5MB), .eh_frame (204KB), .data, .bss, .idata, .CRT, .tls |
The .eh_frame section is a telltale indicator of Rust/GCC compilation - it contains exception handling frame data that is not present in MSVC-compiled binaries. The large .rdata section contains embedded strings, the RSA public key, ransom note template, PsExec binary, and the vCenter spreading PowerShell script.
3. Execution Flow Overview
The DLL’s entry point is trivial - mw_DllMain at 0x10053E00 simply calls the main orchestrator:

// mw_DllMain @ 0x10053E00
BOOL __stdcall __noreturn DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
mw_main_orchestrator(); // 0x10047280 - never returns
}
The mw_main_orchestrator (0x10047280) is a massive function (~62K chars of pseudocode, 204 local variables) that orchestrates the entire ransomware lifecycle.

Before any encryption begins, the orchestrator performs several critical gatekeeping checks:
- Password enforcement: If no
--passwordCLI argument is provided, the ransomware immediately exits with[FATAL] provide password with --password before start! - Singleton mutex: The ransomware checks if another instance is already running and exits with
[FATAL] Cannot run application: already runningto prevent double-encryption - Timer/background mode: If the
timerflag is set, the ransomware logs[INFO] Timer is set. Waiting for <N>and sleeps before proceeding. In non-debug mode, it detaches from the console:[INFO] Gone into background. You can close this console window now. - Panic handler: A custom Rust panic hook is installed that logs
[FATAL|PANIC] Unexpected crash occurred: <message>before terminating, preventing unhandled panics from leaving the process in an undefined state
The full execution flow:

4. Configuration Parsing
The ransomware embeds a 5,710-byte JSON configuration at 0x101E1390 that is parsed by mw_parse_embedded_config (0x10053E10). The configuration defines the complete EncryptorEmbeddedConfiguration structure with 15 fields:

{
"public_rsa_pem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkq...(RSA-4096)...\n-----END PUBLIC KEY-----\n",
"private_rsa_pem": "",
"directory_black_list": [...],
"file_black_list": [...],
"file_pattern_black_list": [...],
"process_black_list": [...],
"win_services_black_list": [...],
"company_id": "QTduEqZI6Q",
"n": 0,
"p": 1,
"fast": 0,
"skip": 0,
"step": 0,
"accounts": [],
"note": "-- Qilin \r\r\nYour network/system was encrypted...",
"password_hash": "26fb7c426e5d7c6288fa5936d776afee0d8550d016403769a79a67c9874981c0"
}
Key configuration fields:
| Field | Purpose |
|---|---|
public_rsa_pem |
RSA-4096 public key for wrapping per-file symmetric keys |
private_rsa_pem |
Empty - private key held by operators (decryption requires it) |
company_id |
Victim identifier (QTduEqZI6Q) - also used as encrypted file extension |
password_hash |
SHA-256 hash for CLI password validation |
n, p, fast, skip, step |
Encryption tuning parameters (partial encryption, skip intervals) |
directory_black_list |
30 directories to skip (Windows, Program Files, ProgramData, etc.) |
file_black_list |
31 files to skip (ntldr, bootmgr, desktop.ini, ntuser.dat, etc.) |
file_pattern_black_list |
49 extensions to skip (exe, dll, sys, bat, lnk, etc.) + company_id |
process_black_list |
71 processes to kill (vmms, sql, outlook, excel, oracle, veeam, etc.) |
win_services_black_list |
52 services to stop (vss, sql, msexchange, sophos, backup, sap, etc.) |
The company_id value QTduEqZI6Q serves dual purpose: it is appended as the file extension for encrypted files (e.g., document.docx.QTduEqZI6Q) and is also added to the file_pattern_black_list to prevent double-encryption of already-encrypted files.
Configuration Parsing Internals
mw_parse_embedded_config (0x10053E10) is a 6,502-byte function that implements a custom serde JSON deserializer optimized for this specific struct layout. Key implementation details:
SSE2 SIMD Key Matching: The deserializer uses SSE2 vectorized string comparison for JSON key matching. At 0x10055D50, the function loads 16-byte chunks of the JSON key name into XMM registers and performs _mm_cmpeq_epi8 followed by _mm_movemask_epi8 to compare against expected field names in a single instruction cycle. This is significantly faster than byte-by-byte comparison for the 15 field names, especially since keys like file_pattern_black_list (22 chars) and win_services_black_list (22 chars) would require many iterations with scalar code.
Packed Suffix String Optimization: For field names that share common prefixes (e.g., directory_black_list, file_black_list, file_pattern_black_list), the parser first matches the unique suffix after the shared prefix _black_list, reducing the number of full string comparisons.
15-Field Struct Layout: The deserialized EncryptorEmbeddedConfiguration struct is laid out as:
Offset Size Field
0x00 ptr public_rsa_pem (String: ptr+len+cap)
0x0C ptr private_rsa_pem
0x18 ptr directory_black_list (Vec<String>)
0x24 ptr file_black_list (Vec<String>)
0x30 ptr file_pattern_black_list (Vec<String>)
0x3C ptr process_black_list (Vec<String>)
0x48 ptr win_services_black_list (Vec<String>)
0x54 ptr company_id (String)
0x60 u32 n (multipass count)
0x64 u32 p (percent)
0x68 u32 fast
0x6C u32 skip
0x70 u32 step
0x74 ptr accounts (Vec<AccountEntry>)
0x80 ptr note (String)
0x8C ptr password_hash (String)
Serde JSON 1.0.105 Deserializer: The function is compiler-generated from #[derive(Deserialize)] on the Rust struct, compiled through serde_json-1.0.105 (path leaked: /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/serde_json-1.0.105/src/de.rs). It implements two complete deserialization paths: Path A (object {}) uses a MapAccess visitor with a switch on key length, and Path B (array []) uses a SeqAccess visitor with positional field parsing. All 15 fields are required — any missing field triggers a fatal error.
Packed Suffix String Optimization: For the 5 integer field error messages, the Rust compiler packs them into a single concatenated string "accountsstepskipfastnp" at 0x10243078. Missing step passes "stepskipfastnp" (ptr + len 4), missing skip passes "skipfastnp" (ptr + len 4), etc. — a compiler optimization for overlapping suffix deduplication.
9-Stage Error Cleanup Cascade: If deserialization fails at any stage, a cleanup cascade at 0x10057A80 walks backward through the partially constructed struct, calling drop on each successfully allocated Vec<String> (walking elements at stride 12 bytes, freeing each non-null string via HeapFree, then freeing the Vec buffer). The cascade terminates with the fatal error log and ExitProcess(1).
If the configuration fails to parse, the ransomware logs a fatal error and exits:
[FATAL|CONFIG] Embedded configuration is in wrong format
5. Command-Line Flags
The orchestrator parses 30+ CLI flags via repeated calls to the flag parser (sub_10059790). These flags provide fine-grained control over the ransomware’s behavior, enabling operators to customize execution per-target:
| Flag | Type | Purpose |
|---|---|---|
password |
string | Authentication password (hashed and compared to password_hash) |
paths |
string | Specific paths to encrypt |
ips |
string | Target IP addresses |
exclude |
string | Paths to exclude from encryption |
timer |
integer | Delay timer before execution |
no-sandbox |
bool | Disable sandbox detection |
no-escalate |
bool | Skip privilege escalation |
impersonate |
bool | Enable token impersonation |
safe |
bool | Enable safe mode boot |
no-local |
bool | Skip local drive encryption |
no-domain |
bool | Skip domain enumeration |
no-network |
bool | Skip network share encryption |
no-ef |
bool | Skip extension filtering |
no-ff |
bool | Skip file filtering |
no-df |
bool | Skip directory filtering |
no-proc |
bool | Skip process termination |
no-services |
bool | Skip service termination |
no-vm |
bool | Skip VM detection/handling |
no-cluster |
bool | Skip cluster service handling |
no-extension |
bool | Don’t append extension to encrypted files |
no-wallpaper |
bool | Skip wallpaper change |
no-note |
bool | Skip ransom note deployment |
no-delete |
bool | Skip self-deletion |
no-destruct |
bool | Skip destructive operations |
no-zero |
bool | Skip disk space overwriting |
print-image |
bool | Print ransom note as image |
print-delay |
integer | Delay before printing |
debug |
bool | Enable debug logging |
spread |
bool | Enable lateral spreading |
spread-vcenter |
bool | Enable vCenter spreading |
escalated |
bool | Already running with elevated privileges |
parent-sid |
string | Parent session SID for spread tracking |
spread-process |
string | Spread process configuration |
This extensive flag system is characteristic of mature RaaS operations where affiliates need operational flexibility across diverse victim environments.
6. Pre-Encryption Pipeline
6.1Privilege Escalation
mw_escalate_privileges (0x101A8430) is the first step in the pre-encryption pipeline. It spawns a dedicated thread for privilege escalation and performs the following:

- Token privilege adjustment: Acquires
SeDebugPrivilege,SeImpersonatePrivilege, andSeShutdownPrivilegeviaAdjustTokenPrivileges - Process token stealing: Enumerates running processes looking for high-privilege targets — specifically
winlogon.exe,wininit.exe, andlsass.exe(embedded at0x10351C44). For each target:- Opens the process handle via
OpenProcess - Extracts the process token:
[DEBUG] Successfully extracted process <pid> - Duplicates and converts it to an impersonation token via
DuplicateTokenExwithSecurityImpersonationlevel:[DEBUG] Successfully converted primary token to impersonation token - Sets the thread token via
SetThreadToken
- Opens the process handle via
- Account impersonation: If the
impersonateflag is set andaccountsconfig array is populated, iterates through each account entry callingLogonUserExExWto authenticate andCreateProcessAsUserWto spawn processes under the impersonated context:[DEBUG|PRIV] Executing program: <path> - WOW64 handling: On 64-bit systems, the ransomware disables WOW64 filesystem redirection (
[DEBUG] WOW64 redirection disabled) before executing system commands and re-enables it afterward ([DEBUG] WOW64 redirection reverted), ensuring commands likevssadmin.exeexecute the native 64-bit versions rather than their 32-bit SysWOW64 counterparts
6.2VSS Shadow Copy Deletion
Shadow copies are destroyed in two phases:
Phase 1 - Symlink Evaluation (mw_setup_vss_symlinks at 0x101A73F0):
Enables Windows symlink evaluation for both Remote-to-Remote (R2R) and Remote-to-Local (R2L) paths using fsutil, ensuring the ransomware can follow symlinks during file enumeration:
/C fsutil behavior set SymlinkEvaluation R2R:1
/C fsutil behavior set SymlinkEvaluation R2L:1
Log messages: [INFO] R2R symlinks evaluated / [WARNING] Cannot evaluate R2R symlinks:
Phase 2 - Shadow Deletion (mw_delete_shadow_copies at 0x101A7830):
First enables the VSS service (in case it was stopped), performs the deletion, then disables it again afterward.
Executes via cmd.exe:
/C vssadmin.exe delete shadows /all /quiet
This command string is embedded at 0x10350EF0 and executed through mw_execute_cmd (0x1015C4B0).

6.3Network Configuration
mw_configure_network_settings (0x101A6F30) modifies Windows registry to maximize network share accessibility:
- EnableLinkedConnections: Allows access to network drives mapped by other user sessions (
[WARNING] Cannot enable linked connections:on failure) - MaxMpxCt: Increases the maximum number of outstanding SMB requests, enabling faster network share enumeration and encryption (
[WARNING] Cannot increase network requests:on failure) - Net use: Establishes connections to network shares using credentials from the config via
mw_execute_net_use(0x101A7290) —[WARNING] Cannot call net use:on failure
6.4Safe Mode Boot Configuration
mw_safe_mode_boot_config (0x101A7EA0) configures the system to boot into Safe Mode via bcdedit commands. The full safe mode configuration module (shared/windows/src/safemode/mod.rs) performs these steps:
- Set safeboot:
BCDEdit.exe /set {current} safeboot network - Configure auto-logon: Sets registry values under
SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon:DefaultUserName,DefaultPassword,AutoAdminLogon=1
- Change user password: Calls
NetUserSetInfoto set the target user’s password for auto-logon - Persistence: Writes a
RunOncekey atSOFTWARE\Microsoft\Windows\CurrentVersion\RunOnceto re-execute after reboot - Acquire SeShutdownPrivilege and trigger system reboot
Upon restart in Safe Mode with Networking, the ransomware auto-logs in and executes with minimal security software interference.
6.5Process and Service Termination
mw_terminate_processes_services (0x1004CC90) is a large function (1,426 lines) that spawns a dedicated thread to terminate blacklisted processes and services. It uses the following approach:
Process Termination - iterates the process_black_list (71 entries):
vmms, vmwp, vmcompute, dfssvc, dfsrs, vds, agntsvc, dbeng50, dbsnmp,
encsvc, excel, firefox, infopath, isqlplussvc, sql, msaccess, mspub,
notepad, ocomm, ocssd, onenote, oracle, outlook, powerpnt, steam,
thunderbird, visio, winword, wordpad, veeamnfssvc, veeamtransportsvc,
veeamdeploymentsvc, teamviewer, sap, ...
Service Termination - iterates the win_services_black_list (52 entries):
/C wmic service where name='<service>' call stopservice
/C net stop <service> /y
Notable service targets: vss, sql, msexchange, sophos, backup, veeam, sap, acronisagent, and regex pattern (.*?)sql(.*?) for catching all SQL-related services.
6.6Hyper-V Service Exclusion
mw_exclude_hyperv_services (0x101B3250) specifically handles Hyper-V environments. It identifies and excludes the following Hyper-V integration services from termination to avoid disrupting the hypervisor:
Vmickvpexchange, vmicvmsession, Vmicguestinterface, Vmicshutdown,
Vmicheartbeat, Vmictimesync, Vmicrdv, Vmicvss, Vmicvbsnap, Vmicic,
TScBroker, TSGateway, TermServLicensing, TermService, RDSessMgr, VDS
This selective exclusion shows operational awareness - crashing the hypervisor would prevent encryption of guest VMs.
7. Lateral Movement
7.1PsExec Deployment
The ransomware contains an embedded PsExec binary (Microsoft Sysinternals tool) in the .rdata section — confirmed by the presence of the full Sysinternals EULA text (1,842 bytes, appearing twice in both ASCII and Unicode encoding) and the "PsInfo Service" service name string. mw_deploy_psexec (0x101B82D0) extracts and deploys it:
- Logs
[INFO|SPREAD] Starting spreading to domain computers via PsExec - Drops PsExec to a temporary path:
[INFO|PSEXEC] Dropping PsExec into: <path> - Enumerates domain computers via Active Directory (
mw_install_ad_powershellat0x101A9DD0installs the AD PowerShell module if needed,mw_enumerate_domain_computersat0x101AB5C0queries AD) - Excludes the local machine from the target list:
[DEBUG|SPREAD] Excluding local machine from spreading: <hostname> - For each discovered domain computer, the
mw_psexec_dropper(0x10128B90) function constructs the PsExec command line with thespread-process,parent-sid, andescalatedflags, then callsWaitForSingleObjectto wait for completion - Logs success/failure per host:
[INFO|SPREAD] Successfully spread to <host>or[WARNING|PSEXEC] Error spreading to <host> - Final status:
[INFO|SPREAD] Spreading complete!
Fatal errors during spreading: [FATAL|SPREAD] Fundamental spreading error: or [FATAL|SPREAD] Cannot get host list for spreading:
7.2vCenter Spreading
mw_spread_vcenter (0x101B8870) generates and executes a 7,554-byte PowerShell script (embedded at 0x10353C30) that targets VMware vCenter environments:

Before spreading, mw_spread_vcenter performs hypervisor detection via CPUID:
CPUIDwithEAX=1: checks bit 31 of ECX (hypervisor present flag)CPUIDwithEAX=0x40000000: reads hypervisor vendor string and identifies:"Microsoft Hv"→ Hyper-V (type 0)"VMwareVMware"→ VMware (type 1)"VBoxVBoxVBox"→ VirtualBox (type 2)"KVMKVMKVM"→ KVM (type 3)"XenVMMXenVMM"→ Xen (type 4)"prl hyperv "→ Parallels (type 5)
The PowerShell script has placeholders replaced at runtime:
<vCenterHost>,<vCenterUsername>,<vCenterPassword><newESXiPassword>,<localFolderPath>,<localFileName><payloadFlags>,<hostList>
The script performs these operations:
- Installs
VMware.PowerCLIandPosh-SSHPowerShell modules - Connects to vCenter via
Connect-VIServer - Disables HA and DRS on all clusters:
Set-Cluster -HAEnabled:$false -DrsEnabled:$false - Enumerates all ESXi hosts via
Get-VMHost - Changes the root password on every ESXi host via
esxcli.system.account.set - For each host: creates SSH session, uploads payload via
Set-SCPItem, disables execution restrictions (esxcli system settings advanced set -o /User/execInstalledOnly -i 0), and executes the payload
7.3Network Share Enumeration
mw_spread_to_network (0x101B1EA0) uses the mpr (Multiple Provider Router) and netapi32 Windows APIs to:
- Enumerate all network shares via
WNetEnumResourceW - Discover hidden admin shares (
ADMIN$,IPC$,C$, etc.) - Queue discovered paths for encryption
- Connect to shares using credentials from the
accountsconfig viamw_execute_net_use(0x101A7290)
The iphlpapi import module provides GetAdaptersInfo for network interface enumeration, while ws2_32 provides socket functionality for network scanning. A safety check prevents excessive enumeration on machines with many network interfaces: [WARNING] Unusually high number of local addresses. Skipping raw network enumeration: <count>.
Errors during share enumeration are logged but non-fatal: [WARNING] Cannot enumerate resources at '<path>' / [WARNING] Cannot enumerate domain computers: <error>.
8. Encryption Engine
8.1Cipher Selection via CPUID
mw_detect_aesni_cpuid (0x1005C2F0) uses the CPUID instruction to detect hardware AES-NI support at runtime:


This function returns 1 for AES-CTR mode (hardware-accelerated) or 0 for ChaCha20 mode (software fallback). The result is passed directly to mw_parse_rsa_key_and_init_crypto. The detection result is logged:
- AES path:
[INFO] AESNI support detected! Using AES-CTR mode - ChaCha20 path:
[WARNING] CPU doesn't support AESNI instructions. Using ChaCha20 mode. - CPUID failure:
[ERROR] Failed to query CPU info. Using ChaCha20 mode.
AES-256 Key Expansion (mw_aes256_key_expansion at 0x10080BE0, 568 lines): Implements the AES-256 key schedule using the fixslice32 software implementation from the aes-0.8.3 crate. The function takes a 32-byte key and expands it into the full set of round keys stored in a 4,132-byte structure (Src[1033] DWORDs). This is the software fallback — when AESNI is available, hardware AESENC/AESDEC instructions are used directly.
ChaCha20 Setup (mw_chacha20_setup at 0x1007FFA0, 433 lines): Implements the HC-128 stream cipher variant used by the rand_chacha-0.3.1 crate. The function initializes a 4,096-byte state table and performs the quarter-round operations with XOR-rotate-add sequences characteristic of the ChaCha family. The state is maintained at offset a1+4096 with a 512-element working table (0x1FF mask) for the key stream generation.

AES-NI Instruction Map
When AES-NI is detected, the ransomware uses four dedicated x86 AES instructions across three functional layers. A total of ~150 AES-NI instructions are present in the binary:
Layer 1 — Key Expansion (sub_10003F80): Calls 8 tiny helper functions, each wrapping a single AESKEYGENASSIST with a different round constant (RCON). These generate the 15 round keys for AES-256:

| Address | Instruction | RCON | Purpose |
|---|---|---|---|
0x10001B93 |
aeskeygenassist xmm0, [edx], 0x01 |
0x01 | Round 1 key derivation |
0x10001BD3 |
aeskeygenassist xmm0, [edx], 0x02 |
0x02 | Round 2 |
0x10001BB3 |
aeskeygenassist xmm0, [edx], 0x04 |
0x04 | Round 3 |
0x10001BC3 |
aeskeygenassist xmm0, [edx], 0x08 |
0x08 | Round 4 |
0x10001C03 |
aeskeygenassist xmm0, [edx], 0x10 |
0x10 | Round 5 |
0x10001BA3 |
aeskeygenassist xmm0, [edx], 0x20 |
0x20 | Round 6 |
0x10001BF3 |
aeskeygenassist xmm0, [edx], 0x40 |
0x40 | Round 7 |
0x10001BE3 |
aeskeygenassist xmm0, [edx], 0x00 |
0x00 | Even-round intermediate |
Between each AESKEYGENASSIST call, the key expansion function at sub_10003F80 uses pslldq (byte-shift left) and pxor chains to mix the previous round key with the assist output — the standard AES-256 key schedule algorithm implemented in SSE registers.
Layer 2 — Inverse Key Schedule (sub_100307E0): Applies AESIMC (AES Inverse MixColumns) to round keys 1–13, producing the decryption schedule. Round keys 0 and 14 are copied unchanged:
0x100307E6 aesimc xmm2, [edx+0x10] ; round key 1
0x100307EC aesimc xmm1, [edx+0x20] ; round key 2
0x100307FF aesimc xmm2, [edx+0x30] ; round key 3
... ; rounds 4-12
0x1003088B aesimc xmm2, [edx+0xD0] ; round key 13
Layer 3 — CTR Mode Encryption: Two functions handle the actual file data encryption:
sub_10031550 — Single-block AES-256 (14 rounds): A straightforward loop applying 13 aesenc instructions followed by 1 aesenclast:
0x1003158D aesenc xmm0, [ecx+0x10] ; round 1
0x10031593 aesenc xmm0, [ecx+0x20] ; round 2
... ; rounds 3-13
0x100315E4 aesenc xmm0, [ecx+0xD0] ; round 13
0x100315ED aesenclast xmm0, [ecx+0xE0] ; round 14 (final)
sub_10031600 — 8-block pipelined AES-256 CTR (~120 aesenc + 8 aesenclast): The high-throughput encryption core. It encrypts 8 counter blocks (128 bytes) simultaneously by interleaving AES rounds across all 8 XMM registers (xmm0–xmm7). This saturates the CPU’s AES-NI execution pipeline:

; 8 blocks processed in parallel through 14 rounds each
; Each aesenclast marks one block completing its final round:
0x10031968 aesenclast xmm2, xmm4 ; block 1 done
0x100319A6 aesenclast xmm3, xmm4 ; block 2 done
0x10031A24 aesenclast xmm1, [esp+var_EC] ; block 3 done
0x10031AD0 aesenclast xmm2, [esp+var_EC] ; block 4 done
0x10031B57 aesenclast xmm4, [esp+var_EC] ; block 5 done
0x10031BFC aesenclast xmm5, xmm7 ; block 6 done
0x10031C9D aesenclast xmm6, xmm7 ; block 7 done
0x10031D45 aesenclast xmm0, [esp+var_EC] ; block 8 done
The 8-block pipelining is critical for performance — modern CPUs can execute aesenc with 1-cycle throughput but 4-cycle latency. By interleaving 8 independent blocks, the ransomware keeps the AES execution unit fully utilized while each individual block waits for its round result. The encrypted counter blocks are then XORed with plaintext to produce ciphertext (CTR mode).
| Instruction | Count | Function | Purpose |
|---|---|---|---|
aeskeygenassist |
8 | sub_10001B90–sub_10001C00 |
Round key derivation |
aesimc |
13 | sub_100307E0 |
Inverse key schedule |
aesenc |
~120 | sub_10031550, sub_10031600 |
AES encryption rounds |
aesenclast |
9 | sub_10031550 (1), sub_10031600 (8) |
Final AES round |
| Total | ~150 |
8.2RSA-4096 Key Initialization
mw_parse_rsa_key_and_init_crypto (0x10034360) handles RSA public key parsing:
- Reads the PEM-encoded RSA-4096 public key from config
- Verifies the
"PUBLIC KEY"header viamemcmp - Decodes the Base64 content using
base64ct-1.6.0 - Parses the ASN.1 DER structure using
const-oid-0.9.5 - Initializes the RSA OAEP encryption context from
rsa-0.7.2
If the key is invalid, the ransomware terminates with:
[FATAL] Cannot parse public key
8.3Hybrid Encryption Scheme
The encryption uses a standard hybrid scheme combining symmetric and asymmetric cryptography:
┌─────────────── Per-File Encryption ───────────────┐
│ │
│ 1. Generate random symmetric key (32 bytes) │
│ └─► BCryptGenRandom(BCRYPT_USE_SYSTEM_PREFERRED│
│ _RNG) with SystemFunction036 fallback │
│ │
│ 2. Generate random IV/nonce │
│ └─► 16 bytes for AES-CTR │
│ └─► 12 bytes for ChaCha20 │
│ │
│ 3. Encrypt file contents │
│ ├─► AES-256-CTR (if AESNI supported) │
│ └─► ChaCha20 (software fallback) │
│ │
│ 4. Wrap symmetric key with RSA-4096 OAEP │
│ └─► Produces 512-byte ciphertext block │
│ │
│ 5. Append metadata to encrypted file │
│ ├─► RSA-encrypted symmetric key │
│ ├─► IV/nonce │
│ ├─► Original file size │
│ └─► "-----END CIPHERTEXT BLOCK-----" marker │
│ │
│ 6. Rename file with .QTduEqZI6Q extension │
│ │
└────────────────────────────────────────────────────┘

The core per-file encryption function mw_encrypt_file (0x10034CF0, 6,348 bytes, source: shared/cryptography/src/encryption.rs) takes 37 parameters — Rust struct fields flattened by the cdecl calling convention — including file handles, encryption config, RSA public key material, and buffer pointers. It is called through an error-handling wrapper at sub_100349E0, which opens the target file via NtCreateFile then dispatches to mw_encrypt_file.
Per-File Key Generation uses a two-tier CSPRNG approach:
- Primary:
BCryptGenRandom— generates 32 bytes (symmetric key) + 12-16 bytes (nonce/IV) - Fallback:
SystemFunction036(RtlGenRandomfromadvapi32.dll) — used if BCryptGenRandom fails on older Windows versions
I/O Pipeline: All file operations use low-level NT API calls for maximum performance:
- Allocates a 6,400-byte (
0x1900) I/O buffer viaGetProcessHeap+HeapAlloc - Reads a chunk from the file at the computed offset via
NtReadFilewrapper (0x10159320) - XOR-encrypts in-place with the AES-CTR or ChaCha20 keystream
- Writes the encrypted chunk back via
NtWriteFilewrapper (0x10159440) - Both NT wrappers use
WaitForSingleObject(handle, INFINITE)for synchronous completion whenSTATUS_PENDING(259) is returned - After all ranges are processed, appends the RSA-wrapped key + footer metadata
- Frees the buffer via
HeapFree
ChaCha20 SSE2 Vectorization: The ChaCha20 core function at 0x10032070 processes 4 blocks in parallel (256 bytes per invocation) using SSE2 intrinsics. It implements 10 double-rounds (20 rounds total) with the standard quarter-round operations (add, XOR, rotate by 16/12/8/7 bits via _mm_slli_epi32/_mm_srli_epi32) and contains the characteristic constant "expand 32-byte k".
Hardware AES Path: When AES-NI is present, the key expansion at 0x10003F80 uses AESKEYGENASSIST instructions producing 15 round keys, with inverse schedule via AESIMC at sub_100307E0 (14 rounds, round keys 1-13 inverted).
If RSA key wrapping fails, the error string "can't encrypt encryption meta" (0x101DFCB0) is emitted.
The zeroize-1.6.0 crate is used to securely wipe symmetric keys from memory after wrapping with RSA, preventing key recovery from memory dumps.
The ransomware supports configurable encryption depth through the n, p, fast, skip, and step config parameters. The mw_parse_encryption_ranges function (0x10037E40) calculates the byte ranges to encrypt based on these parameters, with a hard validation that p must be less than 100 (percent value):
| Mode | Behavior | Use Case |
|---|---|---|
| Fast | First 10MB only per file | Quick encryption, targets file headers |
| Percent | 30MB or 5% of file size | Balance of speed and destruction |
| Normal | Full file encryption | Maximum damage, slower |
| Multipass | Three passes: Fast → Percent → Normal | Progressive encryption |
The multipass mode is particularly sophisticated — when n > 0, the ransomware executes three sequential passes over all files:
[INFO] Multipass mode detected
[INFO] Starting first pass: Fast (10MB)
└─► Encrypts only the first 10MB of each file
[INFO] Starting second pass: Percent (30MB/5%)
└─► Encrypts 30MB or 5% of file size (whichever is larger)
[INFO] Starting third pass: Normal
└─► Full file encryption of remaining content
Each pass checks a per-file marker to skip files already processed in the current pass: [DEBUG] File last pass >= current pass: <path>. This prevents re-encrypting content during the same pass while allowing progressive encryption across passes. If invalid encryption parameters are provided, the ransomware falls back gracefully: [WARNING] Cannot create meaningful encryption encryption_config from these parameters: followed by [WARNING] Aborting to normal encryption mode.
8.4SSD/HDD Thread Pool Architecture
The ransomware creates separate thread pools for SSD and HDD encryption, optimizing I/O patterns for each storage type:
mw_encryption_worker_main (0x1004F4C0)
│
├─► Detect drive type (GetDriveTypeW)
│
├─► SSD Thread Pool (threadpool-1.8.1)
│ ├─► Higher thread count
│ ├─► Parallel random I/O optimized
│ └─► "[INFO] Assigned SSD encryption threads: N"
│
├─► HDD Thread Pool (threadpool-1.8.1)
│ ├─► Lower thread count (sequential I/O)
│ ├─► "[INFO] Assigned HDD encryption threads: N"
│ └─► Skipped in fast mode: "[INFO|ENC] Fast mode detected, ignoring HDD thread limit"
│
├─► File Traversal Thread (rayon)
│ ├─► "[DEBUG] Found drives: ..."
│ ├─► Walks directory tree
│ ├─► Applies blacklist filters
│ └─► Routes files to SSD or HDD queue
│
├─► Queue Management
│ ├─► " sent to SSD encryption queue. Queue size: N"
│ ├─► " sent to HDD encryption queue. Queue size: N"
│ ├─► "[DEBUG] SSD queue is full"
│ └─► "[DEBUG] HDD queue is full"
│
└─► Completion
├─► "[INFO] Traversal thread completed."
└─► "[INFO] Waiting for completion of all encryption threads."
The ransomware first detects the total CPU cores: [INFO] Detected Total Cores: <N>, then calculates thread allocation.
Fast mode (when fast config is set or flag is used) assigns 4x as many threads and ignores the HDD thread limit:
[INFO|ENC] Fast mode detected, assigning 4 times as many threads
[INFO|ENC] Fast mode detected, ignoring HDD thread limit
Crossbeam Ring Buffer Architecture
The SSD and HDD queues are implemented using crossbeam-channel-0.5.6 bounded MPMC channels, each backed by a ring buffer of 200 slots × 32 bytes per slot:
┌─────────────────────────────────────────────────┐
│ crossbeam bounded channel (200 slots) │
│ │
│ Producer (traversal) │
│ └─► try_send() → if full, spin + backoff │
│ │
│ Consumer (encryption worker) │
│ └─► Lock-free CAS recv() with exponential │
│ backoff: spin → yield → park │
│ │
│ Backpressure threshold: queue size ≥ 100 │
│ └─► "[DEBUG] SSD queue is full" │
│ └─► "[DEBUG] HDD queue is full" │
└─────────────────────────────────────────────────┘
The consumer side uses a lock-free CAS (Compare-And-Swap) pattern with exponential backoff: first spinning on the atomic head pointer, then yielding the thread, and finally parking until a producer signals new work. This avoids both busy-waiting overhead and context-switch latency.
An atomic completion tracker coordinates shutdown: the traversal thread sets a completion flag, and each worker thread checks this flag after finding an empty queue. Workers only exit when both the completion flag is set and the queue is drained, preventing premature shutdown that could leave files unencrypted.
Thread Count Formulas (from GetSystemInfo → dwNumberOfProcessors):
- Normal mode: SSD threads =
max(4, num_cores), HDD threads =1 - Fast mode: SSD threads =
max(4, num_cores) × 4, HDD threads = SSD threads (same value — fast mode removes HDD throttling)
Example on an 8-core system: Normal = 8 SSD / 1 HDD; Fast = 32 SSD / 32 HDD.
The thread pools use threadpool-1.8.1 for the encryption workers and rayon-core-1.10.1 (with RAYON_NUM_THREADS / RAYON_RS_NUM_CPUS environment variable support) for the work-stealing file traversal. Each file follows a pipeline through the worker:
[DEBUG] File received for encryption: <path>
└─► Encryption processing
[DEBUG] File encrypted: <path>
└─► Rename with extension
[DEBUG] Trimmed filename: <path> (or "[DEBUG] Nothing to trim")
└─► If rename fails: "[WARNING] Cannot rename file <path>"
└─► Retry: "[INFO] Trying to rename file again..."
└─► If still fails: "[WARNING] Unable to preserve original file name."
When a queue reaches capacity, the traversal thread blocks: [DEBUG] SSD queue is full / [DEBUG] HDD queue is full, providing natural backpressure to prevent memory exhaustion.
8.5File Filtering & Traversal
The ransomware uses aho-corasick-1.0.3 (Aho-Corasick multi-pattern matching algorithm) to build efficient blacklist matchers via mw_build_blacklist_matcher (0x10053AB0). Three matchers are created:
Directory Blacklist (30 entries - case-insensitive):
windows, system volume information, intel, admin$, ipc$, sysvol,
netlogon, $windows.~ws, application data, mozilla, program files (x86),
program files, $windows.~bt, msocache, tor browser, programdata, boot,
config.msi, google, perflogs, appdata, windows.old, $recycle.bin, .., .
File Blacklist (31 entries):
desktop.ini, autorun.ini, ntldr, bootsect.bak, thumbs.db, boot.ini,
ntuser.dat, iconcache.db, bootfont.bin, ntuser.ini, ntuser.dat.log,
autorun.inf, bootmgr, bootmgr.efi, bootmgfw.efi, #recycle
Extension Blacklist (49 entries):
themepack, nls, diapkg, msi, lnk, exe, scr, bat, drv, rtp, msp, prf,
msc, ico, key, ocx, diagcab, diagcfg, pdb, wpx, hlp, icns, rom, dll,
msstyles, mod, ps1, ics, hta, bin, cmd, ani, 386, lock, cur, idx, sys,
com, deskthemepack, shs, theme, mpa, nomedia, spl, cpl, adv, icl, msu,
QTduEqZI6Q ◄── company_id prevents double-encryption
The file traversal (mw_traversal_orchestrator at 0x101937D0, 1,460 lines) uses GetLogicalDrives and GetDriveTypeW to enumerate all drives ([DEBUG] Found drives: <list>), then recursively walks each directory using FindFirstFileW/FindNextFileW. The traversal supports two modes:
- Target mode: Encrypts only specified paths from the
pathsCLI flag:[INFO] Traversal mode: Target - Global mode: Enumerates and encrypts all local drives:
[INFO] Traversal mode: Global
During traversal, the mw_file_filter_and_note_deploy function (0x10185770, 1,678 lines) handles:
- Symlink detection:
[DEBUG] Symlink detected: <path>— follows symlinks for encryption coverage - Mountpoint detection:
[DEBUG|WALK] File detected as mountable— handles mounted volumes - Directory entry logging:
[DEBUG] Entering directory: <path> - Ransom note deployment: Drops
README-RECOVER-QTduEqZI6Q.txtinto each directory during traversal - Path classification: Distinguishes local vs. network resources:
[DEBUG] Path is a local resource/[DEBUG] Path is a shared resource - Error resilience:
[WARNING] Error advancing filesystem tree: <error>— continues traversal on errors rather than aborting
8.6Encrypted File Format
Each encrypted file has the following structure, appended after in-place XOR encryption of the file contents:
┌────────────────────────────────────────────────────┐
│ [Encrypted file data] │
│ In-place XOR with AES-256-CTR or ChaCha20 │
│ keystream. Full or partial depending on mode. │
│ │
├────────────────────────────────────────────────────┤
│ [RSA-4096 OAEP Encrypted Key Block] (512 bytes) │
│ Contains: 32-byte symmetric key + 12-16 byte │
│ nonce, wrapped with attacker's RSA-4096 public │
│ key. Produces exactly 512 bytes (4096/8). │
│ │
├────────────────────────────────────────────────────┤
│ [Metadata Footer] (variable length) │
│ - Encryption mode byte (0-4): │
│ 0 = Normal (full), 1 = Skip-based, │
│ 2 = Default, 3 = Percent-based, 4 = Step │
│ - Original file size (QWORD, 8 bytes) │
│ - Encryption ranges (start, length, step, skip) │
│ - Cipher identifier (AES-CTR vs ChaCha20) │
│ │
├────────────────────────────────────────────────────┤
│ "-----END CIPHERTEXT BLOCK-----" │
│ 30-byte ASCII marker (sentinel for parser) │
└────────────────────────────────────────────────────┘
The encryption range metadata in the footer allows the decryptor (held by the attacker) to know exactly which byte ranges were encrypted and with what parameters, enabling precise decryption even for partial encryption modes.
The file is renamed with the company_id extension: filename.ext → filename.ext.QTduEqZI6Q
8.7Restart Manager for File Unlocking
The ransomware dynamically resolves rstrtmgr.dll (Windows Restart Manager) APIs via LoadLibrary/GetProcAddress at runtime (0 static import xrefs, pure dynamic resolution):
| API | IAT Address | Purpose |
|---|---|---|
RmStartSession |
0x1038D93C |
Create a Restart Manager session |
RmRegisterResources |
0x1038D926 |
Register target file for lock analysis |
RmGetList |
0x1038D91A |
Query which processes hold file locks |
RmEndSession |
0x1038D90A |
Clean up the session |
The flow: create session → register the file being encrypted → query locking processes → terminate those processes → encrypt the now-unlocked file → end session.
This ensures maximum encryption coverage by unlocking files that would otherwise be inaccessible (e.g., database files, documents open in applications). When unlocking fails: [WARNING] Cannot unlock file: <path>. When a file cannot be opened at all: [WARNING] Cannot open file. Error: <code>.
The ransomware also checks if a file is held by a Windows service: [DEBUG] File handler identified as service — in which case it attempts to stop the service: [DEBUG] Trying to kill service with name: <service>.
9. Post-Encryption Cleanup
mw_post_encryption_cleanup (0x10053040, 422 lines) handles all post-encryption tasks. It begins with [INFO] Running post-completion tasks. and ends with [INFO] Post-completion tasks are done.
The function first handles impersonation cleanup — if token impersonation was used during encryption, it calls mw_impersonate_token (0x101AC890) to set the thread token for the cleanup phase, then calls RevertToSelf() to restore the original security context afterward. The impersonation function iterates through account entries, calling DuplicateTokenEx with SecurityImpersonation level and SetThreadToken to apply the token.
It then enumerates all disk drives via mw_enumerate_disk_drives (0x10184F20) to determine which drives need cleanup operations.
9.1Wallpaper Change
The ransomware changes the desktop wallpaper for all user profiles on the system:
- An embedded wallpaper image (
Wallpaper_3840x2160_simple- 3840x2160 resolution) is written to a temporary file - For each user profile, the registry key
\Control Panel\Desktop→Wallpaperis modified:
# Registry modification via PowerShell
Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name Wallpaper -Value '<temp_path>'
SystemParametersInfoWis called to apply the change immediately
Log messages:
[INFO|WALL] Set wallpaper for <user>
[ERROR|WALL] Error setting wallpaper for <user>
[INFO] Setting wallpapers for all users...
9.2Ransom Note Deployment & Printing
The ransom note is dropped into each encrypted directory with the filename pattern README-RECOVER-QTduEqZI6Q.txt. Deployment is controlled by the --no-note CLI flag.

Ransom Note Template (621 bytes at 0x10242674):
-- Qilin
Your network/system was encrypted.
Encrypted files have new extension.
-- Compromising and sensitive data
We have downloaded compromising and sensitive data from you system/network
If you refuse to communicate with us and we do not come to an agreement,
your data will be published.
Data includes:
- Employees personal data, CVs, DL, SSN.
- Complete network map including credentials for local and remote services.
- Financial information including clients data, bills, budgets, annual reports, bank statements.
- Complete datagrams/schemas/drawings for manufacturing in solidworks format
- And more...
-- Warning
1) If you modify files - our decrypt software won't able to recover data
2) If you use third party software - you can damage/modify files (see item 1)
3) You need cipher key / our decrypt software to restore you files.
4) The police or authorities will not be able to help you get the cipher key.
We encourage you to consider your decisions.
-- Recovery
1) Download tor browser: https://www.torproject.org/download/
2) Go to domain
3) Enter credentials
-- Credentials
Extension: QTduEqZI6Q
Domain: p3q5g2qsq4tglsbyhlghzutwr75uyz47ozasrserev7kann5h7qedxid.onion
login: BYxo9FGIiH58sNWWzh967d5fQexHPomf
password: [REDACTED]
Ransom Note Printing: The ransomware enumerates all installed printers via mw_enumerate_printers (0x1007E210) using PowerShell:
Get-Printer | Format-List Name,DriverName
The ransom note is then sent to every printer on the system, ensuring maximum visibility. A 621-byte copy of the note template is prepared for each printer.
9.3Event Log Clearing
mw_clear_event_logs (0x10196590) executes a PowerShell script (embedded at 0x10350FEC) to clear all Windows event logs:

$logs = Get-WinEvent -ListLog * |
Where-Object {$_.RecordCount} |
Select-Object -ExpandProperty LogName ;
ForEach ( $l in $logs | Sort | Get-Unique ) {
[System.Diagnostics.Eventing.Reader.EventLogSession]::GlobalSession.ClearLog($l)
}
This is a __noreturn function that runs in an infinite loop with a 10-second sleep interval (Sleep(0x2710)), continuously destroying forensic evidence even as encryption proceeds. A separate one-shot call is also made from mw_post_encryption_cleanup.
9.4Disk Space Overwriting
The ransomware uses the Windows cipher.exe utility to overwrite free disk space, making file recovery impossible:
/C cipher /w:"<drive_letter>:\"

This is embedded at 0x1035150C. The cipher /w: command overwrites all deallocated clusters on the volume with three passes (zeros, 0xFF, random data), complying with DoD 5220.22-M standard. Log messages:
[INFO] Started cleaning drive: C:\
[WARNING] Failed to start cleaning drive D:\
9.5Self-Deletion
mw_self_delete (0x101A9AE0) performs self-deletion after all operations complete:
- Retrieves the current executable path via
GetModuleFileNameW:[DEBUG] Current exe path: <path> - Logs the deletion intent:
[INFO] Self deleting... - Constructs a
cmd.exe /C delcommand to delete the DLL file from disk - Executes the deletion via
mw_shadow_spread_psexec(0x101A5050) — reusing the process spawning infrastructure - On success:
[INFO] Self deleting...completes silently - On failure:
[WARNING] Failed to self destruct: <error>
If the path cannot be determined: [FATAL] Failed to get current exe path. Try to execute program from another folder: <error>
10. Decryption Feasibility Assessment
Verdict: Decryption without the attacker’s private key is NOT feasible.
The encryption scheme is cryptographically sound:
| Component | Algorithm | Key Size | Assessment |
|---|---|---|---|
| Symmetric encryption | AES-256-CTR or ChaCha20 | 256-bit | Computationally infeasible to brute force |
| Key wrapping | RSA-4096 OAEP | 4096-bit | No known practical attacks |
| Key generation | BCryptGenRandom / rand_chacha | CSPRNG | Properly seeded, no PRNG weakness |
| Key zeroization | zeroize-1.6.0 | N/A | Keys wiped from memory after use |
The per-file symmetric key is generated using a CSPRNG, used to encrypt the file, then wrapped with the RSA-4096 public key and appended to the file. The private_rsa_pem field in the config is empty - the private key is held exclusively by the Qilin operators.
Potential (but impractical) recovery vectors:
- Memory forensics: If the system hasn’t been rebooted, symmetric keys might be recoverable from process memory before
zeroizeclears them. This is a race condition and unreliable. - Implementation bugs: No obvious flaws found in the cryptographic implementation. The use of well-audited Rust crates (
aes-0.8.3,rsa-0.7.2,cipher-0.4.4) reduces the likelihood of implementation errors. - Key reuse: Each file gets a unique random key, so compromising one key doesn’t help with others.
10.5. TLS Callbacks & Rust Runtime
The binary exports three TLS (Thread Local Storage) callbacks, which are standard Rust runtime infrastructure for thread-local variable cleanup:
TlsCallback_0 (0x101610A0, 456 bytes): The primary TLS destructor. When a thread detaches (DLL_THREAD_DETACH, reason=3) or the process detaches (reason=0), it walks a linked list of registered thread-local destructors at dword_1038A128. For each entry, it:
- Retrieves the thread-local value via
TlsGetValue(slot - 1) - Clears the slot via
TlsSetValue(slot, nullptr) - Calls the registered destructor function on the value
- Repeats up to 5 iterations to handle destructors that create new thread-locals during cleanup (a Rust runtime requirement)
TlsCallback_1 (0x101C4040, 153 bytes) and TlsCallback_2 (0x101C3FF0, 67 bytes): Additional initialization/cleanup callbacks for the Rust runtime’s thread-local storage subsystem.
This TLS infrastructure is critical for the zeroize-1.6.0 crate’s operation — when encryption threads complete, their thread-local cryptographic keys are securely wiped through these TLS destructors.
11. Build Environment & Rust Crate Analysis
The binary leaks significant build environment details through embedded Rust panic/debug strings:
Build Environment:
| Property | Value |
|---|---|
| Rust Compiler | rustc 1.73.0 (d5c2e9c342b358556da91d61ed4133f6f50fc0c3) |
| Build System | Cross-compilation: Linux Docker → Windows 32-bit |
| Source Path (Linux) | /usr/src/myapp/windows-rust/ |
| CI Runner (Windows) | C:\Users\runneradmin\.cargo\ |
| Cargo Registry | index.crates.io-6f17d22bba15001f (Linux), index.crates.io-1cd66030c949c28d (Windows) |
Rust Project Structure (from panic strings):
/usr/src/myapp/windows-rust/
├── shared/
│ ├── cryptography/src/
│ │ ├── encryption.rs
│ │ └── config.rs
│ ├── flags/src/flags/
│ │ ├── mod.rs
│ │ ├── flag_value.rs
│ │ └── unit_parsing.rs
│ ├── logging/src/lib.rs
│ ├── networking/src/lib.rs
│ ├── printing/src/
│ │ ├── windows.rs
│ │ └── printers.rs
│ └── windows/src/
│ ├── services/
│ │ ├── mod.rs
│ │ └── service_api.rs
│ └── wallpaper/mod.rs
└── encryptor/src/
├── config/
│ └── embedded_config.rs
└── pipelines/
├── encryption_pipeline.rs
└── pre_encryption_pipeline.rs
Key Rust Crates Used:
| Crate | Version | Purpose |
|---|---|---|
aes |
0.8.3 | AES-256-CTR encryption (with fixslice32 software fallback) |
rsa |
0.7.2 | RSA-4096 OAEP key wrapping |
cipher |
0.4.4 | Cipher trait abstractions |
rand_chacha |
0.3.1 | ChaCha20 stream cipher (AESNI fallback) |
rand_core |
0.6.4 | Random number generation core |
rand |
0.8.5 | Thread-local RNG |
base64ct |
1.6.0 | Constant-time Base64 encoding (PEM parsing) |
aho-corasick |
1.0.3 | Multi-pattern string matching (blacklists) |
hashbrown |
0.14.0 | Hash maps (Rust stdlib replacement) |
sha1_smol |
1.0.0 | SHA-1 hashing |
const-oid |
0.9.5 | ASN.1 OID parsing (RSA key format) |
chrono |
0.4.26 | Date/time handling |
zeroize |
1.6.0 | Secure memory wiping |
threadpool |
1.8.1 | Thread pool for encryption workers |
crossbeam-channel |
0.5.6 | Lock-free MPMC channels |
rayon |
(detected) | Work-stealing parallelism |
gimli |
0.28.0 | DWARF debug info parsing |
rustc-demangle |
0.1.23 | Rust symbol demangling |
object |
0.32.0 | Object file parsing |
The dual build environment (Linux Docker + Windows CI runner) suggests an automated build pipeline, likely using Docker for cross-compilation with x86_64-pc-windows-gnu or i686-pc-windows-gnu targets.
12. MITRE ATT&CK Mapping
| Technique ID | Technique Name | Evidence |
|---|---|---|
| T1486 | Data Encrypted for Impact | AES-256-CTR/ChaCha20 + RSA-4096 hybrid encryption |
| T1490 | Inhibit System Recovery | vssadmin.exe delete shadows /all /quiet |
| T1489 | Service Stop | wmic service ... call stopservice, net stop ... /y |
| T1057 | Process Discovery | Enumerate and kill blacklisted processes |
| T1562.002 | Disable Windows Event Logging | PowerShell ClearLog() on all event logs |
| T1070.001 | Clear Windows Event Logs | Same PowerShell-based event log clearing |
| T1021.002 | SMB/Windows Admin Shares | PsExec lateral movement via admin shares |
| T1570 | Lateral Tool Transfer | Embedded PsExec deployment to domain computers |
| T1087.002 | Domain Account Discovery | AD PowerShell module for computer enumeration |
| T1082 | System Information Discovery | CPUID for AESNI detection, GetDriveTypeW |
| T1135 | Network Share Discovery | WNetEnumResourceW for share enumeration |
| T1112 | Modify Registry | EnableLinkedConnections, MaxMpxCt, wallpaper registry keys |
| T1491.001 | Internal Defacement | Desktop wallpaper changed to ransom message |
| T1485 | Data Destruction | cipher /w: free space overwriting |
| T1070.004 | File Deletion | Self-deletion after execution |
| T1078 | Valid Accounts | Token impersonation via LogonUserExExW |
| T1134.001 | Token Impersonation/Theft | Impersonate accounts from config |
| T1059.001 | PowerShell | vCenter spreading, event log clearing, wallpaper, printers |
| T1059.003 | Windows Command Shell | cmd.exe for VSS deletion, service stops |
| T1569.002 | Service Execution | PsExec remote service creation |
| T1106 | Native API | Direct Win32 API calls throughout |
| T1047 | Windows Management Instrumentation | WMIC for service management |
| T1071 | Application Layer Protocol | Tor-based C2/payment portal |
| T1480 | Execution Guardrails | Password authentication required for execution |
| T1497 | Virtualization/Sandbox Evasion | no-sandbox flag, Hyper-V detection |
| T1134.002 | Create Process with Token | Token stealing from winlogon.exe, wininit.exe, lsass.exe |
| T1027.002 | Software Packing | Embedded PsExec binary in .rdata section |
| T1083 | File and Directory Discovery | FindFirstFileW/FindNextFileW recursive traversal |
| T1529 | System Shutdown/Reboot | Safe mode reboot via bcdedit + AcquireShutdownPrivilege |
13. Indicators of Compromise (IOCs)
File-Based Indicators
| Type | Value |
|---|---|
| Qilin DLL SHA-256 | ee24110ddb4121b31561f86692650b63215a93fb2357b2bd3301fabc419290a3 |
| Encrypted Extension | .QTduEqZI6Q |
| Ransom Note Marker | -----END CIPHERTEXT BLOCK----- |
| Company ID | QTduEqZI6Q |
Network Indicators
| Type | Value |
|---|---|
| Tor Payment Portal | p3q5g2qsq4tglsbyhlghzutwr75uyz47ozasrserev7kann5h7qedxid.onion |
| Login Credential | BYxo9FGIiH58sNWWzh967d5fQexHPomf |
Host-Based Indicators
| Indicator | Description |
|---|---|
vssadmin.exe delete shadows /all /quiet |
Shadow copy deletion |
cipher /w:"<drive>:\" |
Free space overwriting |
bcdedit /set {default} safeboot network |
Safe mode boot configuration |
PowerShell ClearLog() on all event logs |
Forensic evidence destruction |
Get-Printer \| Format-List Name,DriverName |
Printer enumeration for note printing |
Registry: EnableLinkedConnections = 1 |
Network drive access from elevated context |
Registry: MaxMpxCt modification |
SMB performance tuning |
Wallpaper modification in \Control Panel\Desktop |
Desktop defacement |
Embedded Log Strings (Complete)
FATAL level — execution-terminating errors:
[FATAL] provide password with `--password` before start!
[FATAL|CONFIG] Embedded configuration is in wrong format:
[FATAL] Cannot parse public key
[FATAL] Public key is not provided
[FATAL] Password is not correct!
[FATAL] Cannot run application: already running
[FATAL] Multipass mode should not be passed directly during encryption!
[FATAL|PANIC] Unexpected crash occurred:
[FATAL|SPREAD] Fundamental spreading error:
[FATAL|SPREAD] Cannot get host list for spreading:
[FATAL|SPREAD] ESXi binary is not found in path:
[FATAL|SPREAD] ESXi host list is not found in path:
[FATAL|CONFIG] 'Accounts' field from embedded config is in wrong format:
[FATAL] Failed to get current exe path. Try to execute program from another folder:
INFO level — operational status:
[INFO] Timer is set. Waiting for
[INFO] Gone into background. You can close this console window now.
[INFO] Debug mode activated. Not going into background.
[INFO] Checking password validity
[INFO] Password is correct.
[INFO] Current execution context:
[INFO] Waiting for startup tasks to complete (max 10 seconds)...
[INFO] Waiting for 15 seconds for services to be stopped and disabled
[INFO] Waiting for 5 seconds for processes to be killed
[INFO|SPREAD] Starting spreading to domain computers via PsExec
[INFO|SPREAD] Successfully spread to
[INFO|SPREAD] Spreading complete!
[INFO|PSEXEC] Dropping PsExec into:
[INFO] Traversal mode: Target
[INFO] Traversal mode: Global
[INFO] Multipass mode detected
[INFO] Starting first pass: Fast (10MB)
[INFO] Starting second pass: Percent (30MB/5%)
[INFO] Starting third pass: Normal
[INFO|ENC] Fast mode detected, assigning 4 times as many threads
[INFO|ENC] Fast mode detected, ignoring HDD thread limit
[INFO] Assigned SSD encryption threads:
[INFO] Assigned HDD encryption threads:
[INFO] Detected Total Cores:
[INFO] Traversal thread completed.
[INFO] Waiting for completion of all encryption threads.
[INFO] Trying to rename file again...
[INFO] AESNI support detected! Using AES-CTR mode
[INFO] Running post-completion tasks.
[INFO] Post-completion tasks are done.
[INFO] Overwriting all empty space on all disks in background.
[INFO] Removing event logs one last time...
[INFO] Final event logs cleared.
[INFO] Self deleting...
[INFO] Sheduling prints...
[INFO] Sending print job to printer [
[INFO] Setting wallpapers for all users...
[INFO|WALL] Set wallpaper for
[INFO] Target lock complete.
[INFO] Done!
WARNING level — non-fatal issues:
[WARNING] CPU doesn't support AESNI instructions. Using ChaCha20 mode.
[WARNING] Cannot rename file
[WARNING] Unable to preserve original file name.
[WARNING] Cannot create meaningful encryption encryption_config from these parameters:
[WARNING] Aborting to normal encryption mode
[WARNING] Cannot open file. Error:
[WARNING] Cannot unlock file:
[WARNING] Can't get partitions:
[WARNING] Cannot enumerate resources at '
[WARNING] Cannot enumerate domain computers:
[WARNING|NET] Error enumerating active hosts:
[WARNING|PSEXEC] Error spreading to
[WARNING|SPREAD] Couldnt spread to
[WARNING] Error advancing filesystem tree:
[WARNING] Cannot enable linked connections:
[WARNING] Cannot increase network requests:
[WARNING] Cannot evaluate R2R symlinks:
[WARNING] Cannot evaluate R2L symlinks:
[WARNING] Failed to clear event logs:
[WARNING] Failed to start cleaning drive
[WARNING] Failed to self destruct:
[WARNING] Some tasks did not complete in time, resuming operation.
[WARNING] Unusually high number of local addresses. Skipping raw network enumeration:
[WARNING] Error flushing buffer to disk for file
[WARNING|FLAG]
DEBUG level — verbose operational tracing:
[DEBUG] File received for encryption:
[DEBUG] File encrypted:
[DEBUG] File last pass >= current pass:
[DEBUG] Found drives:
[DEBUG] Entering directory:
[DEBUG] Symlink detected:
[DEBUG|WALK] File detected as mountable
[DEBUG] SSD queue is full
[DEBUG] HDD queue is full
[DEBUG] Trimmed filename:
[DEBUG] Nothing to trim
[DEBUG] Path is a local resource
[DEBUG] Path is a shared resource
[DEBUG] Current exe path:
[DEBUG] Current exe arguments are:
[DEBUG] WOW64 redirection disabled
[DEBUG] WOW64 redirection reverted
[DEBUG] Succesfully opened process
[DEBUG] Successfully extracted process
[DEBUG] Successfully converted primary token to impersonation token
[DEBUG|PRIV] Executing program:
[DEBUG] Successfully created new process with extracted token
[DEBUG] File handler identified as service
[DEBUG] Trying to kill service with name:
[DEBUG] Flushing buffer to disk
[DEBUG] Flushed buffer to disk
[DEBUG|SPREAD] Excluding local machine from spreading:
[DEBUG|SPREAD] Excluding local machine from analytics:
[DEBUG|SPREAD] Process exited with status:
[DEBUG|POWERSHELL]
[ERROR] Failed to query CPU info. Using ChaCha20 mode.
Password Hash
SHA-256: 26fb7c426e5d7c6288fa5936d776afee0d8550d016403769a79a67c9874981c0
14. YARA Rule
rule Qilin_Ransomware_Rust_DLL
{
meta:
description = "Detects Qilin ransomware Rust DLL variant"
author = "Malware Cake Factory"
date = "2026-03-07"
hash = "ee24110ddb4121b31561f86692650b63215a93fb2357b2bd3301fabc419290a3"
tlp = "white"
strings:
// Config parsing
$config1 = "public_rsa_pem" ascii
$config2 = "company_id" ascii
$config3 = "password_hash" ascii
$config4 = "directory_black_list" ascii
$config5 = "file_pattern_black_list" ascii
$config6 = "win_services_black_list" ascii
$config7 = "process_black_list" ascii
// Ransom note markers
$note1 = "-- Qilin" ascii
$note2 = "-----END CIPHERTEXT BLOCK-----" ascii
$note3 = "Your network/system was encrypted" ascii
// CLI flags (unique concatenated string)
$flags = "no-escalateimpersonatesafe" ascii
// Logging strings
$log1 = "[INFO] Assigned SSD encryption threads:" ascii
$log2 = "[INFO] Assigned HDD encryption threads:" ascii
$log3 = "[FATAL|CONFIG] Embedded configuration is in wrong format" ascii
$log4 = "[FATAL] Cannot parse public key" ascii
// Rust source paths
$rust1 = "/usr/src/myapp/windows-rust/" ascii
$rust2 = "shared/cryptography/src/encryption.rs" ascii
$rust3 = "encryptor/src/pipelines/encryption_pipeline.rs" ascii
// Build artifact
$build = "runneradmin" ascii
// Crate indicators
$crate1 = "threadpool-1.8.1" ascii
$crate2 = "aho-corasick-1.0.3" ascii
$crate3 = "zeroize-1.6.0" ascii
// Anti-forensics
$af1 = "cipher /w:\"" ascii
$af2 = "vssadmin.exe delete shadows /all /quiet" ascii
// New: FATAL error strings (unique to Qilin)
$fatal1 = "[FATAL|PANIC] Unexpected crash occurred:" ascii
$fatal2 = "[FATAL] provide password with `--password` before start!" ascii
$fatal3 = "[FATAL|SPREAD] ESXi binary is not found in path:" ascii
// New: Privilege escalation targets
$priv1 = "winlogon.exewininit.exelsass.exe" ascii
$priv2 = "SeImpersonatePrivilegeSeDebugPrivilege" ascii
// New: Multipass encryption
$enc1 = "Starting first pass: Fast (10MB)" ascii
$enc2 = "Starting second pass: Percent (30MB/5%)" ascii
condition:
uint16(0) == 0x5A4D and
(
(3 of ($config*) and 1 of ($note*)) or
(2 of ($log*) and $flags) or
(2 of ($rust*)) or
($note2 and 2 of ($config*) and 1 of ($af*)) or
(1 of ($fatal*) and 1 of ($enc*)) or
($priv1 and $priv2 and 1 of ($config*))
)
}
15. Conclusion
This Qilin ransomware variant represents a mature, well-engineered RaaS payload. The Rust implementation provides memory safety guarantees while the developers have built a modular architecture (shared/ libraries for cryptography, networking, printing, and Windows API abstractions) that enables rapid cross-platform development.
Key takeaways:
-
Operational sophistication: 30+ CLI flags give affiliates granular control over every aspect of execution, from encryption scope to anti-forensics measures.
-
Adaptive encryption: Runtime CPUID detection ensures optimal cipher selection - hardware AES-NI when available, ChaCha20 software fallback otherwise. The separate SSD/HDD thread pools show awareness of storage I/O characteristics.
-
Comprehensive lateral movement: The combination of embedded PsExec, AD-based computer enumeration, vCenter/ESXi spreading, and network share encryption maximizes the blast radius of a single initial compromise.
-
Anti-forensics depth: The layered approach (event log clearing → shadow deletion →
cipher /w:overwriting → self-deletion) makes incident response significantly harder. -
Unbreakable encryption: The RSA-4096 + AES-256-CTR/ChaCha20 hybrid scheme with CSPRNG key generation and zeroize-based key cleanup leaves no practical path to decryption without the operator’s private key.