Files
claudetools/scripts/syncro-kill-rogue-sc.ps1
Mike Swanson e34f51fe5d Session 2026-03-30: SOPS vault, SC-Syncro sync, Syncro scripts
- SOPS+age credential vault created (59 encrypted files, separate repo)
- Updated CLAUDE.md credential access to reference SOPS vault
- Updated memory for ACG-5070 (Windows 11, replaces CachyOS)
- SC-Syncro sync script: enriched 410 SC sessions with company/device data
- Syncro scripts: SC property updater, SC deployer, rogue SC killer
- Session log with full details

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 19:38:38 -07:00

440 lines
18 KiB
PowerShell

# Syncro Script: Find and Remove Rogue ScreenConnect Instances
#
# Detects ScreenConnect/ConnectWise Control services that do NOT connect
# to our instance (computerguru.screenconnect.com). Collects forensic
# evidence, creates a security ticket with attachments, then removes
# the unauthorized instance.
#
# Syncro Platform Variables (set in script editor):
# $OrgName - platform - {{customer_business_name_or_customer_name}}
#
# File Type: PowerShell | Run as: System | Max Run Time: 10 minutes
Import-Module $env:SyncroModule
$OurHost = "computerguru.screenconnect.com"
$Found = 0
$Killed = 0
$EvidenceDir = "$env:TEMP\RogueSC_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
$RogueInstances = @()
Write-Host "=== Rogue ScreenConnect Detection ==="
Write-Host "Legitimate host: $OurHost"
Write-Host ""
# ============================================================================
# Find all ScreenConnect services
# ============================================================================
$scServices = Get-Service -Name "ScreenConnect Client*" -ErrorAction SilentlyContinue
if (-not $scServices) {
Write-Host "No ScreenConnect services found."
exit 0
}
foreach ($svc in $scServices) {
$serviceName = $svc.Name
Write-Host "Found service: $serviceName (Status: $($svc.Status))"
# Get the ImagePath from registry to find the install location
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$serviceName"
$imagePath = (Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue).ImagePath
if (-not $imagePath) {
Write-Host " [WARNING] Could not read ImagePath - skipping"
continue
}
# Clean up the image path (remove quotes, get directory)
$exePath = ($imagePath -replace '"', '').Split(' ')[0]
$installDir = Split-Path -Parent $exePath
# Check the service launch parameters for the host
$connectedHost = "unknown"
# Method 1: Check registry ImagePath for host parameter
if ($imagePath -match 'h=([^&\s"]+)') {
$connectedHost = $Matches[1]
}
# Method 2: Check config/xml files in install directory
if ($connectedHost -eq "unknown" -and (Test-Path $installDir)) {
foreach ($pattern in @("*.xml", "*.config")) {
Get-ChildItem -Path $installDir -Filter $pattern -ErrorAction SilentlyContinue | ForEach-Object {
$content = Get-Content $_.FullName -Raw -ErrorAction SilentlyContinue
if ($content -match 'h=([^&\s<"]+)') {
$connectedHost = $Matches[1]
}
}
if ($connectedHost -ne "unknown") { break }
}
}
# Method 3: Check binary strings
if ($connectedHost -eq "unknown" -and (Test-Path $exePath)) {
try {
$content = [System.IO.File]::ReadAllText($exePath)
if ($content -match 'h=([a-zA-Z0-9\.\-]+\.screenconnect\.com)') {
$connectedHost = $Matches[1]
} elseif ($content -match 'h=([a-zA-Z0-9\.\-]+)') {
$connectedHost = $Matches[1]
}
} catch {}
}
Write-Host " Connected to: $connectedHost"
# Determine if this is ours
if ($connectedHost -like "*$OurHost*" -or $connectedHost -eq $OurHost) {
Write-Host " [OK] This is our instance - keeping."
continue
}
# ======================================================================
# ROGUE INSTANCE DETECTED - Collect forensic evidence before removal
# ======================================================================
$Found++
Write-Host " [ROGUE] Unauthorized ScreenConnect instance!"
Write-Host " Collecting forensic evidence..."
# Create evidence directory
if (-not (Test-Path $EvidenceDir)) {
New-Item -ItemType Directory -Path $EvidenceDir -Force | Out-Null
}
$instanceDir = Join-Path $EvidenceDir "instance_$Found"
New-Item -ItemType Directory -Path $instanceDir -Force | Out-Null
# --- Evidence Collection ---
$evidence = [ordered]@{
Timestamp = (Get-Date -Format "yyyy-MM-dd HH:mm:ss UTC" -AsUTC)
ComputerName = $env:COMPUTERNAME
ServiceName = $serviceName
ServiceStatus = $svc.Status.ToString()
ServiceStartType = $svc.StartType.ToString()
ConnectedHost = $connectedHost
ImagePath = $imagePath
InstallDirectory = $installDir
ExePath = $exePath
}
# Service account info
$svcWmi = Get-CimInstance Win32_Service -Filter "Name='$serviceName'" -ErrorAction SilentlyContinue
if ($svcWmi) {
$evidence["ServiceAccount"] = $svcWmi.StartName
$evidence["ServicePID"] = $svcWmi.ProcessId
$evidence["ServicePath"] = $svcWmi.PathName
}
# File details
if (Test-Path $exePath) {
$fileInfo = Get-Item $exePath -ErrorAction SilentlyContinue
$evidence["ExeSize"] = "$([math]::Round($fileInfo.Length/1KB, 2)) KB"
$evidence["ExeCreated"] = $fileInfo.CreationTimeUtc.ToString("yyyy-MM-dd HH:mm:ss UTC")
$evidence["ExeModified"] = $fileInfo.LastWriteTimeUtc.ToString("yyyy-MM-dd HH:mm:ss UTC")
# Digital signature
$sig = Get-AuthenticodeSignature $exePath -ErrorAction SilentlyContinue
if ($sig) {
$evidence["ExeSignatureStatus"] = $sig.Status.ToString()
$evidence["ExeSignerSubject"] = $sig.SignerCertificate.Subject
$evidence["ExeSignerIssuer"] = $sig.SignerCertificate.Issuer
$evidence["ExeSignerThumbprint"] = $sig.SignerCertificate.Thumbprint
}
# File hash
$hash = Get-FileHash $exePath -Algorithm SHA256 -ErrorAction SilentlyContinue
if ($hash) {
$evidence["ExeSHA256"] = $hash.Hash
}
}
# Install date from registry
$uninstallPaths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
)
foreach ($uPath in $uninstallPaths) {
if (-not (Test-Path $uPath)) { continue }
Get-ChildItem $uPath -ErrorAction SilentlyContinue | ForEach-Object {
$props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
if ($props.DisplayName -match "ScreenConnect|ConnectWise Control" -and
$props.UninstallString -notmatch [regex]::Escape($OurHost)) {
$evidence["InstallerDisplayName"] = $props.DisplayName
$evidence["InstallerVersion"] = $props.DisplayVersion
$evidence["InstallerPublisher"] = $props.Publisher
$evidence["InstallerInstallDate"] = $props.InstallDate
$evidence["InstallerProductCode"] = $_.PSChildName
$evidence["InstallerUninstallCmd"] = $props.UninstallString
}
}
}
# Save evidence summary
$evidenceTxt = ($evidence.GetEnumerator() | ForEach-Object { "$($_.Key): $($_.Value)" }) -join "`r`n"
$evidenceTxt | Out-File (Join-Path $instanceDir "evidence-summary.txt") -Encoding UTF8
# Copy install directory contents (configs, logs -- not the binary itself to save space)
if (Test-Path $installDir) {
$artifactDir = Join-Path $instanceDir "install_files"
New-Item -ItemType Directory -Path $artifactDir -Force | Out-Null
Get-ChildItem -Path $installDir -ErrorAction SilentlyContinue | ForEach-Object {
if ($_.Length -lt 5MB) {
Copy-Item $_.FullName -Destination $artifactDir -Force -ErrorAction SilentlyContinue
} else {
# For large files, just log metadata
"SKIPPED (too large): $($_.Name) - $([math]::Round($_.Length/1MB,2)) MB" |
Out-File (Join-Path $artifactDir "_skipped_files.txt") -Append -Encoding UTF8
}
}
}
# Export relevant Windows Event Logs
Write-Host " Collecting event logs..."
# Application log - SC install/service events
try {
$appEvents = Get-WinEvent -FilterHashtable @{
LogName = 'Application'
StartTime = (Get-Date).AddDays(-30)
} -ErrorAction SilentlyContinue | Where-Object {
$_.Message -match "ScreenConnect|ConnectWise Control" -or
$_.ProviderName -match "MsiInstaller"
} | Select-Object TimeCreated, Id, ProviderName, LevelDisplayName, Message -First 100
if ($appEvents) {
$appEvents | Export-Csv (Join-Path $instanceDir "eventlog-application.csv") -NoTypeInformation -Encoding UTF8
}
} catch {}
# System log - service start/stop events
try {
$sysEvents = Get-WinEvent -FilterHashtable @{
LogName = 'System'
Id = @(7034, 7035, 7036, 7040, 7045)
StartTime = (Get-Date).AddDays(-30)
} -ErrorAction SilentlyContinue | Where-Object {
$_.Message -match "ScreenConnect"
} | Select-Object TimeCreated, Id, ProviderName, LevelDisplayName, Message -First 100
if ($sysEvents) {
$sysEvents | Export-Csv (Join-Path $instanceDir "eventlog-system.csv") -NoTypeInformation -Encoding UTF8
}
} catch {}
# Security log - logon events around install time
if ($evidence["InstallerInstallDate"]) {
try {
$installDate = [DateTime]::ParseExact($evidence["InstallerInstallDate"], "yyyyMMdd", $null)
$secEvents = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = @(4624, 4625, 4648, 4672)
StartTime = $installDate.AddHours(-2)
EndTime = $installDate.AddHours(2)
} -ErrorAction SilentlyContinue |
Select-Object TimeCreated, Id, LevelDisplayName, Message -First 200
if ($secEvents) {
$secEvents | Export-Csv (Join-Path $instanceDir "eventlog-security-around-install.csv") -NoTypeInformation -Encoding UTF8
}
} catch {}
}
# Active network connections at time of detection
try {
$netConns = Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue |
Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, OwningProcess,
@{N='ProcessName';E={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}}
$netConns | Export-Csv (Join-Path $instanceDir "active-connections.csv") -NoTypeInformation -Encoding UTF8
} catch {}
# Running processes at time of detection
try {
Get-Process | Select-Object Id, ProcessName, Path, StartTime, Company |
Export-Csv (Join-Path $instanceDir "running-processes.csv") -NoTypeInformation -Encoding UTF8
} catch {}
# Recent user logins
try {
$loginEvents = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = @(4624)
StartTime = (Get-Date).AddDays(-7)
} -MaxEvents 50 -ErrorAction SilentlyContinue |
Select-Object TimeCreated, @{N='LogonType';E={$_.Properties[8].Value}},
@{N='User';E={"$($_.Properties[6].Value)\$($_.Properties[5].Value)"}},
@{N='SourceIP';E={$_.Properties[18].Value}}
$loginEvents | Export-Csv (Join-Path $instanceDir "recent-logins.csv") -NoTypeInformation -Encoding UTF8
} catch {}
# Scheduled tasks (attackers sometimes add persistence)
try {
Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object {
$_.Actions.Execute -match "ScreenConnect|ConnectWise" -or
$_.TaskPath -match "ScreenConnect|ConnectWise"
} | Select-Object TaskName, TaskPath, State,
@{N='Action';E={$_.Actions.Execute}},
@{N='Arguments';E={$_.Actions.Arguments}} |
Export-Csv (Join-Path $instanceDir "scheduled-tasks.csv") -NoTypeInformation -Encoding UTF8
} catch {}
# Collect the full service registry export
try {
reg export "HKLM\SYSTEM\CurrentControlSet\Services\$serviceName" (Join-Path $instanceDir "service-registry.reg") /y 2>&1 | Out-Null
} catch {}
# Add to rogue list for ticket
$RogueInstances += $evidence
# ======================================================================
# REMOVE the rogue instance
# ======================================================================
Write-Host " Stopping and removing rogue instance..."
# Stop the service
try {
Stop-Service -Name $serviceName -Force -ErrorAction Stop
} catch {
Get-Process -Name "ScreenConnect.ClientService" -ErrorAction SilentlyContinue |
Stop-Process -Force -ErrorAction SilentlyContinue
}
# Disable it
Set-Service -Name $serviceName -StartupType Disabled -ErrorAction SilentlyContinue
# Uninstall via MSI
$uninstalled = $false
foreach ($uPath in $uninstallPaths) {
if (-not (Test-Path $uPath)) { continue }
Get-ChildItem $uPath -ErrorAction SilentlyContinue | ForEach-Object {
$props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
if ($props.DisplayName -match "ScreenConnect|ConnectWise Control" -and
$props.UninstallString -and
$props.UninstallString -notmatch [regex]::Escape($OurHost)) {
if ($props.UninstallString -match "\{[0-9A-Fa-f\-]+\}") {
$productCode = $Matches[0]
Start-Process msiexec.exe -ArgumentList "/x $productCode /qn /norestart" -Wait | Out-Null
$uninstalled = $true
}
}
}
}
# Fallback: nuke install directory
if (-not $uninstalled -and (Test-Path $installDir)) {
Remove-Item -Path $installDir -Recurse -Force -ErrorAction SilentlyContinue
}
# Remove service registration
sc.exe delete $serviceName 2>&1 | Out-Null
$Killed++
Write-Host " [REMOVED] Rogue instance cleaned up."
Write-Host ""
}
# ============================================================================
# Package evidence and create ticket if rogue instances found
# ============================================================================
if ($Found -gt 0) {
Write-Host "=== Packaging Evidence ==="
# Build ticket body
$ticketBody = @"
## SECURITY INCIDENT: Rogue ScreenConnect Detected
**Computer:** $env:COMPUTERNAME
**Detection Time:** $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
**Customer:** $OrgName
**Instances Found:** $Found
**Instances Removed:** $Killed
### Rogue Instance Details
"@
foreach ($inst in $RogueInstances) {
$ticketBody += @"
---
**Service:** $($inst.ServiceName)
**Connected To:** $($inst.ConnectedHost)
**Install Directory:** $($inst.InstallDirectory)
**Exe Created:** $($inst.ExeCreated)
**Exe SHA256:** $($inst.ExeSHA256)
**Signature:** $($inst.ExeSignatureStatus) ($($inst.ExeSignerSubject))
**Installer Date:** $($inst.InstallerInstallDate)
**Installer Product:** $($inst.InstallerDisplayName) v$($inst.InstallerVersion)
**Service Account:** $($inst.ServiceAccount)
"@
}
$ticketBody += @"
### Actions Taken
- Forensic evidence collected (event logs, network connections, processes, install files)
- Service stopped and disabled
- Software uninstalled / install directory removed
- Service registration deleted
- Evidence package attached to this ticket
### Recommended Follow-Up
1. Review the attached evidence package for indicators of compromise
2. Check the security event logs around the install date for unauthorized access
3. Verify no other persistence mechanisms remain (scheduled tasks, startup items)
4. Consider password resets for accounts on this machine
5. Check other machines at this customer for similar rogue instances
6. Determine if this was an authorized install by another vendor or an actual breach
"@
# Create zip of evidence
$zipPath = "$EvidenceDir.zip"
try {
Compress-Archive -Path "$EvidenceDir\*" -DestinationPath $zipPath -Force
Write-Host "Evidence packaged: $zipPath"
} catch {
Write-Host "[WARNING] Could not create zip: $_"
}
# Create Syncro ticket
Write-Host "Creating security ticket..."
$ticketResult = Create-Syncro-Ticket -Subject "SECURITY: Rogue ScreenConnect on $env:COMPUTERNAME" -IssueType "Security" -Status "New" -Body $ticketBody
# Upload evidence zip to the ticket
if ($ticketResult -and (Test-Path $zipPath)) {
try {
Upload-File -FilePath $zipPath -TicketIdOrNumber $ticketResult.ticket.id
Write-Host "Evidence attached to ticket."
} catch {
Write-Host "[WARNING] Could not attach evidence to ticket: $_"
# Fallback: upload to asset files
try {
Upload-File -FilePath $zipPath
Write-Host "Evidence uploaded to asset files instead."
} catch {
Write-Host "[WARNING] Could not upload evidence at all. Local copy at: $zipPath"
}
}
}
# RMM Alert
Rmm-Alert -Category "Security" -Body "ROGUE SCREENCONNECT on $env:COMPUTERNAME - $Found instance(s) connecting to: $(($RogueInstances | ForEach-Object { $_.ConnectedHost }) -join ', '). Evidence collected. Ticket created. Instances removed."
# Activity log
Log-Activity -Message "SECURITY: Removed $Killed rogue ScreenConnect instance(s). Ticket created with forensic evidence." -EventName "Security"
Write-Host ""
Write-Host "[ALERT] Security ticket created with full evidence package."
# Cleanup local evidence (zip is uploaded, raw files no longer needed)
Remove-Item -Path $EvidenceDir -Recurse -Force -ErrorAction SilentlyContinue
} else {
Write-Host ""
Write-Host "=== Summary ==="
Write-Host "Total SC services found: $($scServices.Count)"
Write-Host "All clear - no rogue instances."
}