# 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."
}
