Detects ScreenConnect.ClientService.exe first seen in the last 7 days with a rare relay host (seen on 3 or fewer endpoints). It only alerts when a MOTW write of an exe, msi, bat, cmd or ps1 from a rare domain landed up to 1 hour before the first execution. A download-capable LOLBin (PowerShell, curl, msiexec, certutil, mshta) pulling a remote URL within ±1 hour of the first execution adds to the score. Hosts are risk-scored and labeled Suspicious or Malicious.
#event_simpleName=ProcessRollup2 event_platform=Win
| ImageFileName=/\\ScreenConnect\.ClientService\.exe$/i
| CommandLine=/[?&]h=(?<RelayHost>[^&"'\s]+)/i
| case { CommandLine=/[?&]p=(?<RelayPort>[^&"'\s]+)/i ; * | RelayPort := "-" ; }
| case { CommandLine=/[?&]y=(?<SessionType>[^&"'\s]+)/i ; * | SessionType := "-" ; }
| case { CommandLine=/[?&]e=(?<SessionMode>[^&"'\s]+)/i ; * | SessionMode := "-" ; }
| FilePath=/ScreenConnect Client \((?<InstanceID>[a-f0-9]+)\)/i
| RelayHost := lower(RelayHost)
// Add your known relay hosts:
// Example: | RelayHost!=/(^|\.)(maliciousdomain\.com|example\.org|vendor\.net)$/
// Dedupe: one row per host + instance + relay (each service restart is its own ProcessRollup2)
| groupBy([aid, InstanceID, RelayHost], function=[
max(@timestamp, as=@timestamp),
min(@timestamp, as=InstanceFirstSeen),
max(@timestamp, as=InstanceLastSeen),
count(as=TotalExecutions),
count(SHA256HashData, distinct=true, as=DistinctHashes),
selectLast([ComputerName, UserName, RelayPort, SessionType, SessionMode, ParentBaseFileName, GrandParentBaseFileName, SHA256HashData])
], limit=max)
| now(as=_now)
| test(InstanceFirstSeen >= _now - 604800000)
// Relay prevalence across the fleet
| join(
query={
#event_simpleName=ProcessRollup2 event_platform=Win
| ImageFileName=/\\ScreenConnect\.ClientService\.exe$/i
| CommandLine=/[?&]h=(?<RelayHost>[^&"'\s]+)/i
| RelayHost := lower(RelayHost)
| groupBy([RelayHost], function=count(aid, distinct=true, as=RelayHostCount), limit=max)
},
field=RelayHost,
include=[RelayHostCount]
)
| RelayHostCount <= 3
// Rare-domain MOTW download on the host, in the window around the host's first ScreenConnect execution
| join(
query={
#event_simpleName=MotwWritten event_platform=Win
| FileName=/\.(msi|exe|bat|cmd|ps1)$/i OR TargetFileName=/\.(msi|exe|bat|cmd|ps1)$/i
| HostUrl=/^https?:\/\//i
| HostUrl!=/[&?]e=Support/i
| join(
query={
#event_simpleName=ProcessRollup2 event_platform=Win
| ImageFileName=/\\ScreenConnect\.ClientService\.exe$/i
| groupBy([aid], function=min(@timestamp, as=ScFirstSeen), limit=max)
},
field=aid,
include=[ScFirstSeen]
)
// up to 1h before first execution, 5m clock-skew allowance after
| test(@timestamp >= ScFirstSeen - 3600000)
| test(@timestamp <= ScFirstSeen + 300000)
| regex("^https?://(?<HostUrlDomain>[^/:?#]+)", field=HostUrl, strict=true)
| HostUrlDomain := lower(HostUrlDomain)
// Add your known download domains:
// Example: | HostUrlDomain!=/(^|\.)(example\.org|vendor\.net)$/
| join(
query={
#event_simpleName=MotwWritten event_platform=Win
| regex("^https?://(?<HostUrlDomain>[^/:?#]+)", field=HostUrl, strict=true)
| HostUrlDomain := lower(HostUrlDomain)
| groupBy([HostUrlDomain], function=count(aid, distinct=true, as=DomainHostCount), limit=max)
},
field=HostUrlDomain,
include=[DomainHostCount]
)
| DomainHostCount <= 5
| groupBy([aid], function=[
selectLast([HostUrl, ReferrerUrl, FileName, DomainHostCount]),
max(@timestamp, as=LastMOTWTime),
count(as=TotalMOTWWrites)
], limit=max)
| rename(field=HostUrl, as=DownloadHostUrl)
| rename(field=ReferrerUrl, as=DownloadReferrerUrl)
| rename(field=FileName, as=DownloadFileName)
},
field=aid,
include=[DownloadHostUrl, DownloadReferrerUrl, DownloadFileName, DomainHostCount, TotalMOTWWrites, LastMOTWTime]
)
| HasMOTW := "true"
// Dropper activity on the host within ±1h of its first ScreenConnect execution
| join(
query={
#event_simpleName=ProcessRollup2 event_platform=Win
| ImageFileName=/\\(curl|powershell|pwsh|bitsadmin|certutil|wget|msiexec|wscript|cscript|mshta)\.exe$/i
| join(
query={
#event_simpleName=ProcessRollup2 event_platform=Win
| ImageFileName=/\\ScreenConnect\.ClientService\.exe$/i
| groupBy([aid], function=min(@timestamp, as=ScFirstSeen), limit=max)
},
field=aid,
include=[ScFirstSeen]
)
| test(@timestamp >= ScFirstSeen - 3600000)
| test(@timestamp <= ScFirstSeen + 3600000)
| case {
ImageFileName=/\\msiexec\.exe$/i
| CommandLine=/https?:\/\/.+(ScreenConnect|\.msi)/i
| DropperType := "MSIEXEC_REMOTE_URL" ;
ImageFileName=/\\(curl|wget)\.exe$/i
| CommandLine=/(ScreenConnect|\.msi)/i
| CommandLine=/https?:\/\//i
| DropperType := "CURL_WGET_DOWNLOAD" ;
ImageFileName=/\\(powershell|pwsh)\.exe$/i
| CommandLine=/(ScreenConnect|\.msi)/i
| CommandLine=/https?:\/\//i
| DropperType := "POWERSHELL_DOWNLOAD" ;
ImageFileName=/\\(bitsadmin|certutil)\.exe$/i
| CommandLine=/(ScreenConnect|\.msi)/i
| CommandLine=/https?:\/\//i
| DropperType := "LOLBIN_DOWNLOAD" ;
ImageFileName=/\\(wscript|cscript)\.exe$/i
| CommandLine=/\.(vbs|js|wsf)/i
| CommandLine=/https?:\/\//i
| DropperType := "SCRIPT_DROPPER" ;
ImageFileName=/\\mshta\.exe$/i
| CommandLine=/https?:\/\//i
| CommandLine!=/about:blank/i
| DropperType := "MSHTA_DROPPER" ;
}
| groupBy([aid], function=[
selectLast([CommandLine, ImageFileName, DropperType]),
max(@timestamp, as=LastDropperTime),
count(as=DropperExecutions)
], limit=max)
| rename(field=CommandLine, as=DropperCommandLine)
| rename(field=ImageFileName, as=DropperProcess)
},
field=aid,
include=[DropperCommandLine, DropperProcess, DropperType, DropperExecutions, LastDropperTime],
mode=left
)
| case { LastDropperTime=* | HasDropper := "true" ; * | HasDropper := "false" ; }
// Scoring
| case { RelayHostCount=1 | RarityScore := 20 ; * | RarityScore := 10 ; }
| MOTWScore := 25
| case { DownloadFileName=/\.(bat|cmd|ps1)$/i | ScriptScore := 20 ; * | ScriptScore := 0 ; }
| case {
DropperType="MSIEXEC_REMOTE_URL" | DropperScore := 40 ;
DropperType=/^(POWERSHELL_DOWNLOAD|LOLBIN_DOWNLOAD|SCRIPT_DROPPER|MSHTA_DROPPER)$/ | DropperScore := 35 ;
DropperType="CURL_WGET_DOWNLOAD" | DropperScore := 30 ;
* | DropperScore := 0 ;
}
| RiskScore := RarityScore + MOTWScore + ScriptScore + DropperScore
| case { RiskScore >= 60 | Verdict := "MALICIOUS" ; * | Verdict := "SUSPICIOUS" ; }
// Time formatting: Pacific and Eastern (IANA zones handle PST/PDT and EST/EDT automatically)
| InstanceFirstSeenPST := formatTime("%Y-%m-%d %H:%M:%S", field=InstanceFirstSeen, timezone="America/Los_Angeles")
| InstanceFirstSeenEST := formatTime("%Y-%m-%d %H:%M:%S", field=InstanceFirstSeen, timezone="America/New_York")
| InstanceLastSeenPST := formatTime("%Y-%m-%d %H:%M:%S", field=InstanceLastSeen, timezone="America/Los_Angeles")
| InstanceLastSeenEST := formatTime("%Y-%m-%d %H:%M:%S", field=InstanceLastSeen, timezone="America/New_York")
| LastMOTWTimePST := formatTime("%Y-%m-%d %H:%M:%S", field=LastMOTWTime, timezone="America/Los_Angeles")
| LastMOTWTimeEST := formatTime("%Y-%m-%d %H:%M:%S", field=LastMOTWTime, timezone="America/New_York")
| LastDropperTimePST := formatTime("%Y-%m-%d %H:%M:%S", field=LastDropperTime, timezone="America/Los_Angeles")
| LastDropperTimeEST := formatTime("%Y-%m-%d %H:%M:%S", field=LastDropperTime, timezone="America/New_York")
| default(value="-", field=[DownloadHostUrl, DownloadReferrerUrl, DownloadFileName, LastDropperTimePST, LastDropperTimeEST, DropperProcess, DropperCommandLine, DropperType, SessionType, SessionMode, RelayPort])
| default(value=0, field=[DropperExecutions])
| table([
@timestamp, aid, ComputerName, UserName, InstanceID, RelayHost, RelayHostCount, RelayPort, SessionType, SessionMode,
InstanceFirstSeenPST, InstanceFirstSeenEST, InstanceLastSeenPST, InstanceLastSeenEST, TotalExecutions, DistinctHashes,
DownloadFileName, DownloadHostUrl, DownloadReferrerUrl, DomainHostCount, TotalMOTWWrites, LastMOTWTimePST, LastMOTWTimeEST,
DropperProcess, DropperCommandLine, DropperType, DropperExecutions, LastDropperTimePST, LastDropperTimeEST,
HasMOTW, HasDropper, RiskScore, Verdict,
ParentBaseFileName, GrandParentBaseFileName, SHA256HashData
], limit=1000, sortby=RiskScore, order=desc)