Xubuntu "Safe Downloader" Dropper - Technical Analysis
Executive Summary
Xubuntu is a community-maintained official flavour of Ubuntu that ships the
lightweight Xfce desktop. it provides ISO/torrent downloads and website resources for users who prefer a
smaller, Xfce-based Ubuntu experience.
Concise Timeline & Impact:
In mid-October 2025, the Xubuntu website's download section was compromised. Attackers replaced legitimate download links (torrents/zip) with a malicious archive containing a fraudulent "Xubuntu - Safe Downloader" package. This archive included a Windows executable (identified as TestCompany.SafeDownloader.exe / elzvcf.exe) and a fake Terms of Service file. Security researchers and community members quickly flagged the executable as malicious.
The distributed binary is a crypto-clipper. When executed on Windows, it installs itself to %APPDATA%, establishes persistence via HKCU Run, hooks clipboard activity, and replaces copied cryptocurrency addresses with attacker-controlled addresses, thereby stealing cryptocurrency through clipboard hijacking. This behavior has been confirmed by multiple vendors and analysts.
Summary of Attack Tactics:
Initial analysis and community discussions suggest the compromise was a website hijack (web server / CMS / upload compromise), rather than an intrusion into the project's code repository. This allowed attackers to modify downloadable artifacts on the site without altering the Xubuntu source code. The payload demonstrates typical opportunistic criminal tactics, including obfuscated addresses, persistence mechanisms, telemetry/ETW/AMSI tampering, and user-facing decoys, all designed to target casual Windows users who download and install the bundle.
Current Status & Practical Notes:
Site operators promptly disabled the compromised downloads, and alerts were raised by the community and mirror operators. Users and mirror administrators are advised to verify ISOs exclusively from official, trusted mirrors and to cross-reference checksums.
Xubuntu "Safe Downloader" Dropper - Technical Analysis
The Xubuntu-Safe-Downloader.exe is a Stage-1 dropper implemented as a .NET Windows Presentation Foundation (WPF) application.
|
Since its dotnet based we can see the code via dnspy :
In the app.main we got practically nothing,
Lets jump to the window code.
Program startup follows normal WPF flow:
App.Main() →
MainWindow.InitializeComponent() → UI creation. Superficial UI operations are present:
BtnGenerate_Click composes a decoy download URL and populates TxtLink, BtnOpen_Click calls Process.Start to
open the URL, and BtnCopy_Click interacts with the clipboard. The single anomalous call in that flow is
W.UnPRslEqVw(), an obfuscated static method invoked immediately before BtnGenerate_Click returns. Its opaque
name and placement inside a user-initiated handler strongly indicate intentional hiding of initialization or
secondary-stage behavior. Treat W.UnPRslEqVw() as the highest-priority artifact for extraction and dynamic
tracing. Additional high-value targets for monitoring include any static constructors for class W, module
initializers, and any manifest resources or embedded Base64 blobs that could contain secondary
payloads.
Malicious clipboard-clipper functionality
The application contains explicit clipboard manipulation tied to user copy actions. The
relevant logic is executed from the BtnCopy_Click path which reads clipboard contents and writes back
modified text. In practice the malware will: read user clipboard text (System.Windows.Clipboard.GetText or
equivalent), apply pattern matching to detect cryptocurrency addresses (Bitcoin, Ethereum, Monero patterns,
etc.), and overwrite the clipboard using System.Windows.Clipboard.SetText with an attacker-controlled wallet
address. Placement of this logic in the copy handler makes the theft deterministic and reliable - the code
runs exactly when a user copies an address and is unlikely to be exercised by automated sandbox crawlers
unless the clipboard is populated during analysis.
Recommended follow-up actions
- Immediate static extraction - Extract and decompile class W, locate W.UnPRslEqVw() implementation, and identify
any .cctor (static constructor) behavior. Dump all manifest resources and large Base64-encoded blobs for
PE headers or encrypted payloads.
- Dynamic tracing - Run the
sample in an isolated VM with breakpoints on W.UnPRslEqVw, Process.Start, Assembly.Load/LoadFrom,
File.WriteAllBytes/WriteAllText, and clipboard APIs. Capture process, file system, and network activity
(Procmon, Sysmon, Wireshark).
- IOC collection - Harvest
URLs, domains, dropped filenames, registry keys, embedded wallet addresses, and any C2 indicators found
inside W or dumped resources.
- Sandbox tests - Populate
the clipboard with sample crypto addresses and exercise BtnCopy_Click to confirm replacement behavior
and capture the attacker address. Also exercise BtnGenerate_Click to trigger W.UnPRslEqVw() under
tracing.
- Containment and detection - Add detections for the observed clipboard modification patterns, W.UnPRslEqVw
symbol if present in any samples, and telemetry for Process.Start calls that launch URLs from this
binary. Monitor for new installs of applications with similar obfuscation and UI patterns.
Conclusion
The sample uses UI-driven
deception to hide a multi-stage infection chain and a deterministic clipboard-clipper for cryptocurrency
theft. W.UnPRslEqVw() is the single most critical artifact and should be reverse engineered immediately.
Once W is unpacked and its network/file behaviors are known, expand IOCs and detection rules
accordingly.
The initial execution stage is encapsulated within the obfuscated routine W.UnPRslEqVw(). Its primary purpose is to prepare the environment, bypass security mechanisms, and launch the main payload.Stage
Anti-Analysis and Evasion
This routine first executes several checks to evade analysis and disable telemetry:
1. Anti-Analysis Gates:
- Debugger Check: Calls kernel32\\!IsDebuggerPresent. If a debugger is detected, the process terminates.
- Virtual Machine (VM) Detection: Uses WMI (Win32\_ComputerSystem) to query the manufacturer and model. The process exits if substrings like "microsoft corporation + virtual," "vmware," "virtualbox," "qemu," or "parallels" are found (case-insensitive).
2. AMSI and ETW Bypass:
- AMSI Bypass: Loads amsi.dll, gets the address of AmsiScanBuffer, uses kernel32\\!VirtualProtect to change the page permissions, and patches the function start with bytes 31 C0 C3 (xor eax,eax; ret). This forces AMSI to report success and skip content scanning.
- ETW Bypass: Loads ntdll, gets the address of EtwEventWrite, uses VirtualProtect, and patches the function start with the single byte C3 (immediate ret). This disables the emission of ETW events.
Dropper and Persistence
The routine then drops and configures the second-stage payload:
1. Dropper Configuration:
- Staging Directory: %APPDATA%\\osn10963
- Payload Filename: elzvcf.exe
- File Operation: The payload is initially written to %APPDATA%\\osn10963\\elzvcf.tmp and then renamed to elzvcf.exe.
- Payload Source: The executable is decoded from a large Base64 blob within the binary, followed by an XOR operation using the key 0xF7.
- File Attributes: Both the directory and the files are set to FILE\_ATTRIBUTE\_HIDDEN (value 2).
2. Persistence Mechanism:
- Registry Key: HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run
- Value: A randomly generated 6-character string containing an embedded "NullValue" marker (created via ntdll\\!NtSetValueKey).
- Type and Data: Type REG_SZ, Data is the full path to the dropped payload: %APPDATA%\\osn10963\\elzvcf.exe.
- APIs Used: advapi32\\!RegOpenKeyExW, advapi32\\!RegCloseKey, and the native API ntdll\\!NtSetValueKey.
3. Execution Preparation:
- Delay: A delay of 1 to 2 seconds is introduced using ntdll\\!NtDelayExecution.
- Single-Instance Check: The launch is aborted if a process with the name elzvcf is already running.
4. Payload Launch:
- The second-stage payload is executed using kernel32\\!CreateProcessW with the creation flag 0x08000000 (CREATE_NO_WINDOW), ensuring it runs without a visible window.
These constants were typically decoded via Base64 followed by XOR with key 0xF7.
Decoded Modules and Functions:
- Modules: kernel32, ntdll, advapi32, amsi.dll
- Functions: IsDebuggerPresent, CreateProcessW, CreateFileW, WriteFile, CloseHandle, CreateDirectoryW, MoveFileW, SetFileAttributesW, VirtualProtect, NtDelayExecution, EtwEventWrite, RegOpenKeyExW, RegCloseKey, NtSetValueKey
Paths and Names:
- Staging Directory: osn10963
- Payload Filename: elzvcf.exe
- Persistence Registry Path: Software\\Microsoft\\Windows\\CurrentVersion\\Run
Patch Bytes (Signatures of Security Bypass):
- AmsiScanBuffer Patch: 31 C0 C3
- EtwEventWrite Patch: C3
Conclusion: W.UnPRslEqVw() Analysis
W.UnPRslEqVw() is an archetypal .NET loader. Its functionality includes native API resolution and telemetry
evasion. It achieves stealth by immediately disabling
both AMSI and ETW, then silently dropping a hidden payload at %APPDATA%\\osn10963\\elzvcf.exe. The loader establishes persistence via the HKCU Run registry key
and executes the payload in a headless state.
It
will be safe to assume the blob we saw is an executable, which could only be elzvcf.exe.
Live run:
The file appeared as expected:
Checksums
|
|
IOC Type |
IOC |
Description |
|
File path (staging) |
%APPDATA%\osn10963\elzvcf.tmp → %APPDATA%\osn10963\elzvcf.exe |
Payload is written to .tmp then renamed to elzvcf.exe in %APPDATA%\osn10963. |
|
File path (final) |
%APPDATA%\osn10963\elzvcf.exe |
Second-stage native implant location. |
|
Registry persistence |
HKCU\Software\Microsoft\Windows\CurrentVersion\Run\{<6-char random name>} |
REG_SZ value (random 6-char name containing "NullValue" marker) pointing to %APPDATA%\osn10963\elzvcf.exe. |
|
AMSI patch bytes |
31 C0 C3 |
Loader patches AmsiScanBuffer to xor eax,eax; ret to force AMSI bypass. |
|
ETW patch bytes |
C3 |
EtwEventWrite patched to single ret (0xC3) to disable ETW for the process. |
|
XOR keys / encoding |
0xF7 (payload), 0x15 (string/data decoding) |
Embedded payload is Base64 then XOR 0xF7; several data blocks are XOR-decoded with 0x15 at runtime. |
|
Creation flag |
CREATE_NO_WINDOW = 0x08000000 |
Payload launched with CreateProcessW and CREATE_NO_WINDOW (headless execution). |
|
Clipboard APIs / behavior |
IsClipboardFormatAvailable, AddClipboardFormatListener, GetClipboardData, SetClipboardData, EmptyClipboard, OpenClipboard, CloseClipboard |
Native implant implements full clipboard I/O (listener + read/replace) through these APIs. |
|
Native APIs resolved |
NtQuerySystemInformation, NtProtectVirtualMemory, NtCreateMutant, NtDelayExecution, NtQuerySystemTime, NtClose, NtSetValueKey, EtwEventWrite |
Many ntdll exports are resolved at runtime and stored in globals for native-mode operations (registry, delays, mutexes). |
|
Anti-analysis checks |
IsDebuggerPresent, CPUID/timing loop, WMI checks for VM strings (microsoft corporation, virtual, vmware, virtualbox, qemu, parallels) |
Debugger + timing + WMI-based VM detection that exit the process if triggered. |
|
Single-instance mechanism |
NtCreateMutant / process-name check (elzvcf) |
Uses mutant and/or process-name check to prevent multiple instances; aborts launch if already running. |
|
Data-decode locations (addresses shown) |
.crypted / addresses: 0x1400032e0, 0x140006010, write target 0x140004100 |
XOR/SIMD decode loop reads from .crypted/data sections and writes decoded bytes into runtime data region (0x140004100+). |
|
Obfuscated format-strings |
tqqg$d,tasxy vpb}m%o%,m"x, QDogbc__AMWmTwL_l_oTVY_agL___xXf_Q, AB___LwleaGLfM, g_cDSCbGmF_eStcbT__psES_BtBWDml_e, RFgs_CVS_M... |
High-value XORed/encoded blobs containing attacker wallet addresses and helper strings, decoded with XOR key 0x15. |
|
Patch/write primitives used |
VirtualProtect + direct write to function pointer |
Used to modify AmsiScanBuffer/EtwEventWrite in-process. |
|
Evidence of no obvious network |
No clear network APIs shown in provided snippets |
The visible behavior is local (dropper + clipboard listener); network I/O not present in the pasted sections. |
|
DLL Dependencies |
KERNEL32.dll, ntdll.dll, USER32.dll, ADVAPI32.dll |
Standard Windows DLLs utilized for core functionality. |
|
Entry Point Noise |
"This program cannot be run in DOS mode." |
Standard PE header noise. |
Random blobs :
Decoded Obfuscated Strings and Their Interpretation
Here's a breakdown of the decoded strings(XOR key 0x15, same routine the binary uses), along with their likely purpose as determined by their format and the malware's known behavior:
|
Obfuscated String |
Decoded Plaintext |
Interpretation |
|
RyzwtyInQ#,SP$%T8W$#&8#-'"8#"W%8&T,'&-#S#&,&h |
Global\{D69FE10A-B163-6827-67B0-3A92386F6393} |
A GUID, likely used for single-instance control (mutant) or as a unique identifier for registry entries or other system handles. |
|
tabc"l"'qpt},{#lpaar,'cr-rf~e%!f'g'dgp{#ab |
atwv7y72deah9un6yettg92vg8gskp04s2r2qren6tw` |
Opaque token or wallet ID. The specific format suggests it's an attacker-controlled identifier for a cryptocurrency. |
|
%m$%T-W'p"',%-",SSVqP $!QqP#$
w!"&'& |
0x10A8B2e7290879FFCdE514DdE615b4732312252D |
Ethereum-like wallet address. The 0x prefix and hexadecimal characters are consistent with Ethereum and ERC-20 token addresses. |
|
RFgsCVS M$Ttt,b{wOXAFrzwaLBV[A& vmLdQw |
GSrfuVCF5X1Aaa9wnbZMTSgobtYWCNT35cxYqDb` |
Opaque token or wallet ID. This could be a replacement address for a less common cryptocurrency or an internal identifier. |
|
g,cDSCbGmF~eStcbT,]psES~BtBWDml!e@ |
r9vQFVwRxSkpFavwA9HefPFkWaWBQxy4pU |
Opaque token or wallet ID. Given the context, it's highly probable this is a Ripple (XRP) address, starting with 'r' as previously noted. |
|
YD!W!t_d@],'WraQfpBm|VG{! D-p]oA~] |
LQ4B4aJqUH92BgtDseWxiCRn45Q8eHzTkH |
Opaque token or wallet ID. This could be a Litecoin replacement address, as Litecoin addresses commonly start with 'L' or 'M'. |
|
QDogbc@_AMWmTwL|l{oTVY{agL!|,xXf"Q |
DQzrwvUJTXBxAbYiynzACLntrY4i9mMs7D |
Dogecoin (DOGE) replacement address. The D prefix aligns with Dogecoin address formats. |
|
"stoc!! o!mlg rad}#v,e!g#~{}ys& |
7fazv445z4xyr5gtqh6c9p4r6knhlf3 |
Opaque token. Its format doesn't immediately suggest a cryptocurrency address, but it could be an internal identifier or part of a larger scheme. |
|
tqqg$d,tasxy vpb!}m%o%,m\\"x |
addr1q9atfml5cew4hx0z09xu7m(Cardanoaddr1...) |
Cardano (ADA) replacement address. The addr1q prefix is characteristic of Cardano's bech32 addresses. |
|
wv$dgo}"q%ll-v&tgdmv'&ab~ |
bc1qrzh7d0yy8c3arqxc23twk |
Bitcoin Bech32 replacement address. The bc1q prefix is a clear indicator of a SegWit (bech32) Bitcoin address. |
|
$g~]BlC@eef't{^ |
1rkHWyVUpps2anK12hg |
Opaque token, likely a Bitcoin legacy address given it starts with '1'. |
|
mmtmvx%-d}#%c |
xxaxcm08uqh60v` |
Opaque token. |
|
AB,&]LwleaGLfM |
TW93HYbyptRYsX |
Opaque token, likely a TRON (TRX) replacement address, starting with 'T' as previously identified in the analysis. |
|
D$H+D$xH=@T |
Q1u]>Q1m](UA` |
Opaque token. |
Through our analysis, we've identified the following attacker addresses across various cryptocurrency branches:
- Bitcoin (Bech32): bc1qrzh7d0yy8c3arqxc23twkjujxxaxcm08uqh60v
- Litecoin: LQ4B4aJqUH92BgtDseWxiCRn45Q8eHzTkH
- Ethereum / BSC style (hex): 0x10A8B2e2790879FFCdE514DdE615b4732312252D
- Dogecoin: DQzrwvUJTXBxAbYiynzACLntrY4i9mMs7D
- Tron (TRX): TW93HYbyptRYsXj1rkHWyVUpps2anK12hg
- XRP (Ripple): r9vQFVwRxSkpFavwA9HefPFkWaWBQxy4pU
- Cardano: addr1q9atfml5cew4hx0z09xu7mj7fazv445z4xyr5gtqh6c9p4r6knhlf3jatwv7y72deah9un6yettg92vg8gskp04s2r2qren6tw
The code from cutter:
|
/* jsdec pseudo code output */ /* C:\Users\*****\AppData\Roaming\osn10963\elzvcf.exe @ 0x140001000 */ #include <stdint.h>
uint64_t entry0 (int64_t arg1) { PUNICODE_STRING DestinationString; int64_t var_70h; int64_t var_6ch; int64_t var_bp_68h; int64_t var_60h; int64_t var_58h; int64_t var_54h; int64_t var_50h; WNDCLASSEXA ** ARG_0; int64_t var_38h; int64_t var_30h; int64_t var_28h; int64_t var_bp_20h; int64_t var_bp_10h; int64_t var_bp_8h; int64_t X; int64_t Y; int64_t nWidth; int64_t nHeight; HWND hWndParent; HMENU hMenu; HINSTANCE hInstance; LPVOID lpParam; int64_t var_sp_60h; int64_t var_64h; int64_t var_68h; int64_t var_sp_6ch; int64_t var_sp_70h; int64_t var_78h; int64_t var_140h; int64_t var_8h; int64_t var_10h; int64_t var_18h; int64_t var_20h; rcx = arg1; /* [00] -r-x section size 8192 named .text */ var_8h = rbx; rbp = rsp - 0x40; rdi = rcx; rcx = "ntdll.dll"; rax = uint64_t (*GetModuleHandleA)(void, void, void, void) (rbp, rsi, rdi, r14); rbx = rax; if (rax == 0) { goto label_0; } rdx = "NtQuerySystemInformation"; rcx = rax; rax = GetProcAddress (); rdx = "NtProtectVirtualMemory"; rcx = rbx; *(0x1400040d8) = rax; rax = GetProcAddress (); rdx = "NtCreateMutant"; rcx = rbx; *(0x1400040f0) = rax; rax = GetProcAddress (); rdx = "NtDelayExecution"; rcx = rbx; *(0x1400040e0) = rax; rax = GetProcAddress (); rdx = "NtQuerySystemTime"; rcx = rbx; *(0x1400040d0) = rax; rax = GetProcAddress (); rdx = "NtClose"; rcx = rbx; *(0x1400040f8) = rax; rax = GetProcAddress (); *(0x1400040e8) = rax; if (*(0x1400040d8) == 0) { goto label_0; } if (*(0x1400040f0) == 0) { goto label_0; } if (*(0x1400040e0) == 0) { goto label_0; } if (*(0x1400040d0) == 0) { goto label_0; } if (*(0x1400040f8) == 0) { goto label_0; } if (rax == 0) { goto label_0; } __asm ("movdqa xmm1, xmmword [0x1400032e0]"); rbx = 0x140006020; __asm ("movdqa xmm0, xmm1"); r14 = 0x140004100; __asm ("xorps xmm0, xmmword [section..crypted]"); esi = 0; __asm ("xorps xmm1, xmmword [0x140006010]"); ecx = esi; __asm ("movdqu xmmword [0x140004100], xmm0"); edx = 0xd; __asm ("movdqu xmmword [0x140004110], xmm1"); do { eax = *((rcx + rbx)); rcx = rcx + 1; al ^= 0x15; *((rcx + r14 + 0x1f)) = al; rdx--; } while (rdx != 0); __asm ("xorps xmm0, xmm0"); *(0x14000412d) = sil; rdx = r14; var_68h = rsi; rcx = &DestinationString; var_6ch = esi; __asm ("movups xmmword [DestinationString], xmm0"); uint64_t (*RtlInitUnicodeString)(void) (esi); rax = &DestinationString; var_70h = 0x30; __asm ("xorps xmm0, xmm0"); var_60h = rax; r9b = 1; var_bp_68h = rsi; r8 = &var_70h; var_58h = 0x40; edx = 0x1f0001; rcx = &var_68h; __asm ("movdqu xmmword [var_50h], xmm0"); eax = uint64_t (*0x1400040e0)() (); if (eax == 0xc0000035) { ecx = 0; uint64_t (*ExitProcess)() (); } rcx = &var_78h; uint64_t (*0x1400040f8)(void) (0xffffffffff676980); rdx = &var_sp_70h; ecx = 0; uint64_t (*0x1400040d0)() (); rcx = &var_sp_60h; uint64_t (*0x1400040f8)() (); rax = var_sp_60h; rax -= var_78h; if (rax < 0x895440) { ecx = 0; uint64_t (*ExitProcess)() (); } fcn_140001580 (); eax = uint64_t (*IsDebuggerPresent)() (); if (eax == 0) { ecx = 0; eax = 1; __asm ("cpuid"); var_64h = ebx; var_68h = ecx; var_sp_6ch = edx; if (eax >= 0) { goto label_1; } } var_sp_60h = esi; edx = esi; do { ecx = var_sp_60h; eax = var_sp_60h; ecx += ecx; ecx ^= edx; edx++; eax += ecx; var_sp_60h = eax; } while (edx < 0x186a0); label_1: rax = imp.RegOpenKeyExA; rax = uint64_t (*GetCurrentProcess)(uint64_t*, void) (*(RegOpenKeyExA), 8); r9d = 4; r8 = &var_78h; rcx = rax; rdx = &var_68h; rax = &var_sp_60h; uint64_t (*0x1400040f0)(void) (rax); rcx = "ntdll.dll"; rax = uint64_t (*GetModuleHandleA)() (); rcx = rax; rdx = "EtwEventWrite"; rax = GetProcAddress (); rbx = rax; if (rax != 0) { rax = uint64_t (*GetCurrentProcess)(void, void) (rax, 4); r9d = 0x40; r8 = &var_sp_70h; rcx = rax; rdx = &var_68h; rax = &var_sp_60h; eax = uint64_t (*0x1400040f0)(void) (rax); if (eax < 0) { goto label_2; } *(rbx) = 0xc3; rax = uint64_t (*GetCurrentProcess)() (); r9d = var_sp_60h; r8 = &var_sp_70h; rcx = rax; rdx = &var_68h; rax = &var_sp_60h; uint64_t (*0x1400040f0)(void) (rax); } label_2: rax = 0x1400014a0; ARG_0 = 0x50; var_38h = rax; rcx = &ARG_0; eax = *(0x140006030); __asm ("xorps xmm0, xmm0"); al ^= 0x15; var_30h = rsi; *(0x140004100) = al; __asm ("xorps xmm1, xmm1"); eax = *(0x140006031); al ^= 0x15; __asm ("movdqu xmmword [var_bp_20h], xmm0"); *(0x140004101) = al; eax = *(0x140006032); al ^= 0x15; __asm ("movdqu xmmword [var_bp_10h], xmm1"); *(0x140004102) = al; eax = *(0x140006033); al ^= 0x15; var_bp_8h = rsi; *(0x140004103) = al; eax = *(0x140006034); al ^= 0x15; var_28h = rdi; *(0x140004104) = al; eax = *(0x140006035); al ^= 0x15; *(0x140004106) = sil; *(0x140004105) = al; *(rbp) = r14; ax = uint64_t (*RegisterClassExA)() (); if (ax == 0) { goto label_0; } rdx = *(rbp); r9d = 0; r8d = 0; ecx = 0; rax = uint64_t (*CreateWindowExA)(void, void, void, void, void, void, void, void) (rsi, rdi, rsi, 0xfffffffffffffffd, esi, esi, esi, esi); rcx = rax; uint64_t (*AddClipboardFormatListener)() (); r9d = 0; rcx = rbp + 0x10; r8d = 0; edx = 0; eax = uint64_t (*GetMessageA)() (); if (eax == 0) { goto label_3; } do { rcx = rbp + 0x10; uint64_t (*TranslateMessage)() (); rcx = rbp + 0x10; uint64_t (*DispatchMessageA)() (); GetModuleHandleA_140001db0 (); r9d = 0; rcx = rbp + 0x10; r8d = 0; edx = 0; eax = uint64_t (*GetMessageA)() (); } while (eax != 0); label_3: eax = 0; goto label_4; label_0: eax = 1; label_4: r11 = &var_140h; rbx = *((r11 + 0x10)); rsi = *((r11 + 0x18)); rdi = *((r11 + 0x20)); r14 = *((r11 + 0x28)); return rax; }
|
This native Windows implant is heavily obfuscated and utilizes low-level native API calls. It incorporates numerous anti-analysis checks and dynamically resolves and stores various Nt*/kernel APIs. The implant patches in-process telemetry APIs (ETW/AMSI-style), decodes/XORs embedded data, registers a hidden window and clipboard listener, and executes a standard message loop. The loader stage, as previously demonstrated, exhibits persistence and stager behaviors.
In short: Clipboard Clipper Core (elzvcf.exe)
The native implant elzvcf.exe acts as the core clipboard clipper, intercepting and replacing cryptocurrency addresses. Its operation, initiated when the hidden window receives clipboard update notifications, follows these steps:
- Clipboard Access and Data Retrieval:
- The routine first checks for the availability of text data on the clipboard using IsClipboardFormatAvailable (for CF_UNICODETEXT or CF_TEXT).
- Upon confirmation, it opens the clipboard, retrieves the handle to the clipboard data via GetClipboardData, and locks the global memory handle (GlobalLock) to read the user's copied string.
- Wallet Address Validation:
- The captured string undergoes validation to determine if it resembles a cryptocurrency wallet address:
- Length Check: It verifies if the string's length falls within a typical crypto-address range (approximately 25–80 characters; specifically, rdi - 0x19 <= 0x37 in the pseudocode).
- Character Set Validation: It ensures that all characters are alphanumeric or specific special characters such as ':' and '_' (identified through a loop utilizing isalnum and additional checks for these special characters).
- Currency Identification and Replacement Address Selection:
- The implant identifies the specific cryptocurrency based on the prefix or initial character(s) of the validated string and selects a corresponding attacker-controlled replacement address, which is embedded and obfuscated within the binary:
- Bitcoin (bech32/legacy): Addresses starting with "bc1" or '1'/'3' are replaced with a predefined, decoded constant.
- Litecoin: Addresses beginning with "ltc1", 'L', or 'M' are replaced with another decoded constant.
- Dogecoin: Addresses starting with 'D' trigger the decoding of an obfuscated blob labeled QDogbc__AMWmTwL_l_oTVY_agL___xXf_Q to retrieve the attacker's DOGE address.
- TRON: Addresses starting with 'T' trigger the decoding of the blob AB___LwleaGLfM.
- XRP (Ripple): Addresses starting with 'r' trigger the decoding of the blob g_cDSCbGmF_eStcbT__psES_BtBWDml_e.
- Cardano: Addresses with the prefix "addr1" invoke a small decoder on the obfuscated string tqqg$d,tasxy vpb!}m%o%,m\"x to obtain the ADA address.
- Additional Branch: An unreferenced branch (associated with the RFgs_CVS_M… blob) suggests coverage for another cryptocurrency family or address type.
- Decryption of Replacement Address:
- For the identified currency, a small SIMD/XOR routine (using the key 0x15) is executed to decrypt the embedded replacement address into a writable memory buffer, typically located around 0x140004100.
- Clipboard Overwrite:
- If a replacement address is successfully prepared:
- The existing clipboard content is cleared (EmptyClipboard).
- A new global text buffer is allocated.
- The attacker's decoded address is copied into this new buffer.
- SetClipboardData(CF_UNICODETEXT, …) is called to overwrite the user's clipboard with the attacker's address.
- Cleanup:
- Finally, the routine unlocks and cleans up any used handles and closes the clipboard.
In practice:
1. Initialization and API Resolution:
- The code immediately calls GetModuleHandleA("ntdll.dll") to retrieve function pointers for various Nt* functions, including NtQuerySystemInformation, NtProtectVirtualMemory, NtCreateMutant, NtDelayExecution, NtQuerySystemTime, and NtClose.
- These resolved pointers are stored in fixed global memory locations (e.g., 0x1400040d8, 0x1400040f0).
- A critical dependency exists on these pointers being non-zero; the program exits early (via goto label_0) if any fail to resolve, indicating a strong reliance on native exports.
2. Obfuscated Data Decoding:
- The program employs SIMD/XMM operations and a small XOR loop to decode embedded constants.
- It loads blocks of data from sections like .crypted or other designated addresses, XORs them with the key 0x15, and writes the decoded bytes to memory starting at 0x140004100.
- The use of movdqu/xorps and the XOR key 0x15 are tactics to conceal static strings and data within the binary.
- Notably, different XOR keys are observed across the malware family (e.g., 0xF7 in the loader), with decoded values subsequently used by the code.
3. Anti-Analysis and Sandbox Evasion:
- The code incorporates several checks to hinder analysis and detect virtualized or sandboxed environments.
- It calls RtlInitUnicodeString and NtDelayExecution, triggering ExitProcess if eax equals 0xc0000035 after these calls.
- IsDebuggerPresent() is used in conjunction with a CPUID and timing-based loop. This loop performs up to 0x186a0 iterations under debugging conditions, serving as a typical CPU-delay/timing fingerprint.
- A size-based heuristic (if (rax < 0x895440) ExitProcess(), where rax is derived from NtQuerySystemTime and some delta) causes the program to abort in small or virtualized environments.
Telemetry Neutralization
- Locates and patches ntdll!EtwEventWrite in memory by using GetProcAddress to find its entry point. It then uses VirtualProtect to modify the page, writes 0xC3 (a ret instruction) into the function, and restores the original page protection. This effectively bypasses ETW telemetry.
- Unlike earlier AMSI/other patches found in the loader stage (which involved VirtualProtect and Marshal.Copy), this decompilation explicitly shows the ETW patch directly writing a single ret byte.
Window & Clipboard Infrastructure
- The code constructs and registers a WNDCLASSEX (via RegisterClassExA), creates a window (CreateWindowExA), and calls AddClipboardFormatListener. It then immediately enters a GetMessage/TranslateMessage/DispatchMessage loop, which is a standard setup for message-driven clipboard handling. The specific logic for clipboard operations is expected to reside within the registered lpfnWndProc function.
- The use of AddClipboardFormatListener (over older clipboard viewer chains) indicates an expectation of WM_CLIPBOARDUPDATE notifications, and the GetMessage loop ensures these notifications are handled synchronously.
Concurrency & Single-Instance Control
- References to NtCreateMutant and the use of GetCurrentProcess suggest the implementation of singleton enforcement (using a mutant or semaphore) and/or other synchronization primitives to prevent multiple instances of the application.
Native Timing/Sleep Mechanisms
- The code utilizes NtDelayExecution (accessed via a resolved pointer) for sleep operations. Numeric values indicate that short, randomized delays are employed in other parts of the code. Furthermore, ExitProcess is called from a native context when certain gate failures occur.
Memory and Pointer Arithmetic
- The code frequently performs low-level pointer arithmetic, exemplified by operations like *((rcx + rbx)); al ^= 0x15; *((rcx + r14 + 0x1f)) = al;. These operations involve decoding bytes into a target memory region. Such operations are not strongly typed (as seen in C pseudocode depicting raw memory operations), which introduces fragility but is consistent with obfuscation techniques and manual binary patching.
Hardcoded Constants:
- The code utilizes several hardcoded constants, including XOR keys (0x15 for decoding), a loop limit (0x186a0), a threshold (0x895440), and various offsets and global addresses. These values are obscure and likely linked to the layout of encoded data or environmental checks.
Error Handling and Robustness:
Error handling is primarily binary, relying on pointer checks that lead to a label_0 goto or ExitProcess upon failure. There is no observed logging or graceful fallback. Many operations assume success; for instance, while VirtualProtect success is checked, subsequent writes depend on prior non-zero GetProcAddress checks. Resource cleanup is minimal, with no explicit release of windows, handles, or allocated memory in the standard execution flow; early exits invariably call ExitProcess.
Code Style and Maintainability (Review Perspective):
The code is highly obfuscated and low-level, lacking variable names (instead using global addresses) and employing extensive manual SIMD and XOR operations, making it inherently unmaintainable. It features heavy reliance on raw API pointers and byte patches, no modularization or comments, and numerous opaque "magic numbers," which intentionally complicate analysis for concealment. The pseudocode reveals register/stack manipulation and inline assembly-like constructs, suggesting compiler-level packing or manual modifications.
Potential Fragility:
The system's reliance on absolute addresses (e.g., 0x140004100, 0x140006010) and global slots could lead to instability with Address Space Layout Randomization (ASLR), relocation, or minor build alterations. However, these characteristics are common for position-dependent PE sections within a specific build. Direct writes into exported function entry points (like EtwEventWrite) assume specific page alignment and size. If protection fails or the function is relocated, these writes could result in host process crashes.
Separation of Concerns:
Initialization, anti-analysis measures, telemetry patching, and the GUI/message pump all execute sequentially within the same thread/context. This lack of isolation means a single point of failure can compromise the entire implant.
What we have so far:
Stage 1 - TestCompany.SafeDownloader (Managed Loader)
This component operates as a WPF application with a seemingly legitimate user interface, where buttons are the only active elements. Upon clicking "BtnGenerate_Click," it invokes "W.UnPRslEqVw()," which functions as the actual loader.
"W.UnPRslEqVw()" executes several critical actions:
- Anti-analysis measures: It conducts VM/WMI checks and debugger detection to evade analysis.
- Telemetry disabling: It patches AMSI/ETW in-process to disable scanning and logging functionalities.
- Payload handling: It decodes an embedded XORed payload, writes it to "%APPDATA%\osn10963\elzvcf.tmp," renames it to "elzvcf.exe," and sets hidden attributes.
- Persistence: It establishes persistence by adding a "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" REG_SZ entry with a random short name, pointing to the executable.
- Launch: Finally, it launches the executable using "CreateProcessW" with the "CREATE_NO_WINDOW" flag, ensuring a stealthy execution.
In essence, the managed application acts as a dropper/stager. Its primary function is to disable telemetry, then drop and establish persistence for a native second-stage payload, subsequently transferring control to that payload.
Stage 2 - elzvcf.exe: Native Implant / Clipboard-Clipper Scaffold
This stage of the attack involves elzvcf.exe, a native implant designed to act as a clipboard clipper. Its key functionalities and behaviors include:
- Native API Resolution: It resolves numerous ntdll native APIs at runtime, such as NtQuerySystemInformation, NtProtectVirtualMemory, NtDelayExecution, NtCreateMutant, NtClose, among others. This heavy reliance on native-mode behavior indicates sophisticated operation.
- Anti-Analysis Measures: The implant incorporates anti-analysis gates to evade detection. These include checks for IsDebuggerPresent, CPUID/timing loops, and other size/time checks. If suspicious activity is detected, the process will ExitProcess.
- Telemetry Patching: To further avoid detection and monitoring, elzvcf.exe patches telemetry. It locates ntdll\\!EtwEventWrite and writes a ret (0xC3) instruction into it via VirtualProtect + write, effectively neutralizing Event Tracing for Windows (ETW) for the process. (Note: The loader also patches AMSI elsewhere).
- Clipboard Monitoring Setup: The implant registers a window class and creates a hidden window. It then calls AddClipboardFormatListener and enters a standard message loop (GetMessage/DispatchMessage). This setup forms the clipboard-monitoring scaffold. The actual code responsible for reading and replacing clipboard content resides within the window procedure, which is registered via RegisterClassExA.
- Single-Instance Execution and Delays: Mutants and other synchronization mechanisms are utilized to ensure single-instance behavior, preventing multiple copies of the implant from running simultaneously. NtDelayExecution is employed for timed sleeps and delays.
- Local Clipboard Interception: Based on the provided snippets, there is no apparent network code. The primary and visible function of elzvcf.exe is local clipboard interception. The logic for replacing clipboard content, including regexes, attacker wallet addresses, or specific overwrite behaviors, is implemented either within the WndProc or in decoded XORed data blocks. These decoded blocks and the WndProc contain the concrete theft logic.
Concluding remarks
The code, though intentionally
“obfuscated” , low-level, and brittle by conventional measures, aligns with the characteristics of a
weaponized implant. Its features include dynamic native
API resolution, in-memory patching of telemetry entry points, runtime decoding of XORed/encoded data
regions, anti-debug and sandbox gating, and a message-loop-based clipboard listener for core hijacking
logic. Maintaining or statically analyzing the binary
is challenging without decoding the XORed sections and dumping the WndProc. Furthermore, error handling is minimal, and the code heavily relies
on precise memory layout and runtime-decoded data.
But overall it's enough to determine the
malwares purpose.
IOC
|
IOC Type |
IOC |
Description |
|
File path (staging) |
%APPDATA%\osn10963\elzvcf.tmp → %APPDATA%\osn10963\elzvcf.exe |
Payload is written to .tmp then renamed to elzvcf.exe in %APPDATA%\osn10963. |
|
File path (final) |
%APPDATA%\osn10963\elzvcf.exe |
Second-stage native implant location. |
|
Registry persistence |
HKCU\Software\Microsoft\Windows\CurrentVersion\Run\{<6-char random name>} |
REG_SZ value (random 6-char name containing "NullValue" marker) pointing to %APPDATA%\osn10963\elzvcf.exe. |
|
AMSI patch bytes |
31 C0 C3 |
Loader patches AmsiScanBuffer to xor eax,eax; ret to force AMSI bypass. |
|
ETW patch bytes |
C3 |
EtwEventWrite patched to single ret (0xC3) to disable ETW for the process. |
|
XOR keys / encoding |
0xF7 (payload), 0x15 (string/data decoding) |
Embedded payload is Base64 then XOR 0xF7; several data blocks are XOR-decoded with 0x15 at runtime. |
|
Creation flag |
CREATE_NO_WINDOW = 0x08000000 |
Payload launched with CreateProcessW and CREATE_NO_WINDOW (headless execution). |
|
Clipboard APIs / behavior |
AddClipboardFormatListener, RegisterClassExA, CreateWindowExA, GetMessage/DispatchMessage |
Native implant registers a hidden window, calls AddClipboardFormatListener, and runs a message loop - WndProc handles WM_CLIPBOARDUPDATE. |
|
Native APIs resolved |
NtQuerySystemInformation, NtProtectVirtualMemory, NtCreateMutant, NtDelayExecution, NtQuerySystemTime, NtClose, NtSetValueKey |
Many ntdll exports are resolved at runtime and stored in globals for native-mode operations (registry, delays, mutexes). |
|
Anti-analysis checks |
IsDebuggerPresent, CPUID/timing loop, WMI checks for VM strings (microsoft corporation, virtual, vmware, virtualbox, qemu, parallels) |
Debugger + timing + WMI-based VM detection that exit the process if triggered. |
|
Single-instance mechanism |
NtCreateMutant / process-name check (elzvcf) |
Uses mutant and/or process-name check to prevent multiple instances; aborts launch if already running. |
|
Data-decode locations (addresses shown) |
.crypted / addresses: 0x1400032e0, 0x140006010, write target 0x140004100 |
XOR/SIMD decode loop reads from .crypted/data sections and writes decoded bytes into runtime data region (0x140004100+). |
|
Patch/write primitives used |
VirtualProtect + direct write to function pointer |
Used to modify AmsiScanBuffer/EtwEventWrite in-process. |
|
Evidence of no obvious network |
No clear network APIs shown in provided snippets |
The visible behavior is local (dropper + clipboard listener); network I/O not present in the pasted sections. |
Final thoughts
We
thought about doing a super deep dive into this malware, but it turned out to be pretty simple. It's basically a clipboard hijacker that swaps out
crypto addresses. So, if you copy a crypto
address, it quietly replaces it with the attacker's, hoping you won't notice and send your funds to
them by mistake. If you're curious about
how this kind of malware works, or want a more technical explanation of similar attacks, check out this
external resource: https://blog.synapticsystems.de/analyzing-malware-distributed-by-xubuntu-org/.
It's got some great info on malware distribution and analysis techniques.









