# ============================================================ # Remote Access Audit — nerd industries # ------------------------------------------------------------ # Run it (paste into Windows PowerShell — normal OR admin): # # irm audit.nerdyneighbor.net | iex # # (audit.nerdyneighbor.net is a Cloudflare proxy that returns this exact file, # latest commit, via the GitHub API. Direct fallback if the proxy is down: # irm -Headers @{Accept='application/vnd.github.raw'} https://api.github.com/repos/nerd-industries/Remote-Access-Audit/contents/RemoteAccessAudit.ps1 | iex ) # # - Pulls the latest committed version every time (GitHub API, # not the cached raw CDN) so you never run a stale copy. # - Self-elevates with a single UAC prompt, runs every scan, # opens the remediation window, and saves an HTML report. # ============================================================ # Use TLS 1.2 for web calls on older Windows builds try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch {} # Canonical source — used to relaunch elevated as the exact same latest copy $RAA_Source = 'https://api.github.com/repos/nerd-industries/Remote-Access-Audit/contents/RemoteAccessAudit.ps1' function Test-IsAdmin { try { $id = [Security.Principal.WindowsIdentity]::GetCurrent() return ([Security.Principal.WindowsPrincipal]$id).IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator) } catch { return $false } } # ── Self-elevation ────────────────────────────────────────────────────────── # When launched non-elevated via irm|iex there is no script file on disk, so we # relaunch by re-running the same one-liner inside an elevated PowerShell. The # elevated copy is fetched fresh from the API, so it is always the latest commit. if (-not (Test-IsAdmin)) { Write-Host "" Write-Host " Remote Access Audit needs Administrator rights." -ForegroundColor Yellow Write-Host " Click YES on the Windows UAC prompt..." -ForegroundColor Yellow $relaunch = "[Net.ServicePointManager]::SecurityProtocol=" + "[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12; " + "irm -Headers @{Accept='application/vnd.github.raw'} '$RAA_Source' | iex" $enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($relaunch)) try { Start-Process -FilePath 'powershell.exe' -Verb RunAs ` -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-NoExit','-EncodedCommand',$enc | Out-Null } catch { Write-Host " Elevation cancelled — the audit cannot run without admin rights." -ForegroundColor Red } return } # ── Run configuration ─────────────────────────────────────────────────────── $ErrorActionPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue' $timestamp = Get-Date -Format 'yyyy-MM-dd_HH-mm' $OutDir = Join-Path $env:USERPROFILE 'Desktop' $reportFile = Join-Path $OutDir "RemoteAccessAudit_$timestamp.html" Write-Host "" Write-Host " Remote Access Audit (running as Administrator)" -ForegroundColor Cyan Write-Host " Computer: $env:COMPUTERNAME User: $env:USERNAME" -ForegroundColor Gray Write-Host "" # ── Catalog of remote-access / remote-control tools ───────────────────────── # Matching is PRECISE — exact exe base name, service-name pattern, install-path # hint, or Authenticode signer — never loose substring matching. This is what # eliminates the false positives a keyword scanner produces. $RemoteTools = @( @{ Name='TeamViewer'; Exe=@('teamviewer','teamviewer_service','tv_w32','tv_x64'); Svc=@('teamviewer*'); Path=@('*\teamviewer*'); Signer=@('teamviewer'); Class='Commercial remote control' } @{ Name='AnyDesk'; Exe=@('anydesk'); Svc=@('anydesk*'); Path=@('*\anydesk*'); Signer=@('anydesk','philandro'); Class='Commercial remote control' } @{ Name='ScreenConnect / ConnectWise'; Exe=@('screenconnect.clientservice','screenconnect.windowsclient','connectwisecontrol.client'); Svc=@('screenconnect*','connectwisecontrol*'); Path=@('*\screenconnect*','*\connectwisecontrol*','*\connectwise control*'); Signer=@('connectwise','screenconnect','elsinore'); Class='RMM / remote support (heavily abused in scams)' } @{ Name='LogMeIn / GoTo'; Exe=@('logmein','lmiguardiansvc','logmeinsystray','ramaint'); Svc=@('logmein*'); Path=@('*\logmein*'); Signer=@('logmein','goto'); Class='Commercial remote control' } @{ Name='Splashtop'; Exe=@('sragent','srservice','strwinclt','splashtop'); Svc=@('splashtop*','sragent*'); Path=@('*\splashtop*'); Signer=@('splashtop'); Class='Commercial remote control' } @{ Name='RustDesk'; Exe=@('rustdesk'); Svc=@('rustdesk*'); Path=@('*\rustdesk*'); Signer=@('rustdesk','purslane'); Class='Open-source remote control (abused)' } @{ Name='Radmin'; Exe=@('radmin','rserver3','famitrfc'); Svc=@('rserver*','radmin*'); Path=@('*\radmin*'); Signer=@('famatech'); Class='Commercial remote control' } @{ Name='VNC (Real/Tight/Ultra/Tiger)'; Exe=@('winvnc','winvnc4','vncserver','vncviewer','tvnserver','uvnc_service'); Svc=@('*vnc*'); Path=@('*\realvnc*','*\tightvnc*','*\ultravnc*','*\uvnc*','*\tigervnc*'); Signer=@('realvnc','tightvnc','glavsoft'); Class='VNC remote control' } @{ Name='Ammyy Admin'; Exe=@('aa_v3','ammyy'); Svc=@(); Path=@('*\ammyy*'); Signer=@(); Class='RAT (commonly abused in scams)' } @{ Name='DameWare'; Exe=@('dwrcs','dwrcst','dameware'); Svc=@('dwmrcs*'); Path=@('*\dameware*'); Signer=@('solarwinds','dameware'); Class='Commercial remote control' } @{ Name='NetSupport Manager'; Exe=@('client32','pcicfgui'); Svc=@('client32*'); Path=@('*\netsupport*'); Signer=@('netsupport'); Class='Remote control (abused as a RAT)' } @{ Name='Atera Agent'; Exe=@('ateraagent'); Svc=@('ateraagent*'); Path=@('*\atera*'); Signer=@('atera'); Class='RMM agent' } @{ Name='Kaseya / VSA'; Exe=@('agentmon'); Svc=@('kaseya*'); Path=@('*\kaseya*'); Signer=@('kaseya'); Class='RMM agent' } @{ Name='NinjaOne / NinjaRMM'; Exe=@('ninjarmmagent','ninjarmmagentpatcher'); Svc=@('ninjarmm*'); Path=@('*\ninjarmm*','*\ninjaone*'); Signer=@('ninja'); Class='RMM agent' } @{ Name='Pulseway'; Exe=@('pcmonitorsrv','pulseway'); Svc=@('pcmonitor*','pulseway*'); Path=@('*\pulseway*','*\pc monitor*'); Signer=@('mmsoft','pulseway'); Class='RMM agent' } @{ Name='Supremo'; Exe=@('supremo','supremosystem'); Svc=@('supremo*'); Path=@('*\supremo*'); Signer=@('nanosystems'); Class='Commercial remote control' } @{ Name='UltraViewer'; Exe=@('ultraviewer','ultraviewer_desktop'); Svc=@('ultraviewer*'); Path=@('*\ultraviewer*'); Signer=@('ductho','ultraviewer'); Class='Remote control (abused in scams)' } @{ Name='RemotePC'; Exe=@('remotepc','rpcservice'); Svc=@('remotepc*'); Path=@('*\remotepc*'); Signer=@('remotepc','idrive'); Class='Commercial remote control' } @{ Name='Zoho Assist'; Exe=@('zaservice','za_access'); Svc=@('zaservice*'); Path=@('*\zoho*'); Signer=@('zoho'); Class='Remote support' } @{ Name='AnyViewer'; Exe=@('anyviewer'); Svc=@('anyviewer*'); Path=@('*\anyviewer*'); Signer=@('aomei'); Class='Remote control (abused in scams)' } @{ Name='Remote Utilities'; Exe=@('rutserv','rfusclient'); Svc=@('rmanservice*'); Path=@('*\remote utilities*'); Signer=@('remote utilities'); Class='Remote control (abused as a RAT)' } @{ Name='GoToAssist / GoToMyPC'; Exe=@('g2comm','g2svc','gotoassist','gotomypc'); Svc=@('gotoassist*','gotomypc*'); Path=@('*\gotoassist*','*\gotomypc*'); Signer=@('goto','logmein'); Class='Remote support' } @{ Name='BeyondTrust / Bomgar'; Exe=@('bomgar'); Svc=@('bomgar*','beyondtrust*'); Path=@('*\bomgar*','*\beyondtrust*'); Signer=@('bomgar','beyondtrust'); Class='Remote support' } @{ Name='ngrok (tunnel)'; Exe=@('ngrok'); Svc=@('ngrok*'); Path=@('*\ngrok*'); Signer=@('ngrok'); Class='Tunneling tool (used to expose remote access)' } @{ Name='Cloudflared (tunnel)'; Exe=@('cloudflared'); Svc=@('cloudflared*'); Path=@('*\cloudflared*'); Signer=@('cloudflare'); Class='Tunneling tool (used to expose remote access)' } @{ Name='Chisel (tunnel)'; Exe=@('chisel'); Svc=@(); Path=@('*\chisel*'); Signer=@(); Class='Tunneling tool (used to expose remote access)' } @{ Name='Netcat / Ncat'; Exe=@('ncat','netcat'); Svc=@(); Path=@(); Signer=@(); Class='Reverse-shell tool' } # ── Additional RMM / remote-support tools (commonly abused — see lolrmm.io, CISA AA23-025A) ── @{ Name='SimpleHelp'; Exe=@('simplehelpcustomer','simpleservice','simplegatewayservice','remote access'); Svc=@('simplehelp*','simpleservice*'); Path=@('*\simplehelp*','*\simple-help*'); Signer=@('simple-help','jwsoftware'); Class='Remote support (abused in 2025-26 ransomware intrusions)' } @{ Name='ITarian / Comodo RMM'; Exe=@('itsmagent','itsmservice','rviewer'); Svc=@('itsm*'); Path=@('*\itarian*','*\comodo\*rmm*'); Signer=@('itarian'); Class='RMM agent (abused)' } @{ Name='PDQ Connect / Deploy'; Exe=@('pdq-connect-agent','pdqconnectagent','pdqdeployrunner','pdqinventory'); Svc=@('pdq*'); Path=@('*\pdq*'); Signer=@('pdq'); Class='RMM / software deployment (abused)' } @{ Name='N-able Take Control / N-central'; Exe=@('basupsrvc','basupsrvcupdater','basuptshelper'); Svc=@('basupsrvc*','windows agent*','n-central*'); Path=@('*\n-able*','*\solarwinds*msp*','*\take control*','*\beanywhere*'); Signer=@('n-able','solarwinds'); Class='RMM / remote support (abused)' } @{ Name='Datto RMM (CentraStage)'; Exe=@('cagservice','aurora-agent','aurora-agent-v2'); Svc=@('cagservice*','datto*'); Path=@('*\centrastage*','*\datto*'); Signer=@('datto','centrastage','kaseya'); Class='RMM agent (abused)' } @{ Name='Syncro / Kabuto'; Exe=@('syncro.service','syncro','kabuto.app.service','kabuto'); Svc=@('syncro*','kabuto*'); Path=@('*\syncro*','*\repairtech*','*\kabuto*'); Signer=@('syncromsp','servably','repairtech'); Class='RMM agent (abused)' } @{ Name='Action1'; Exe=@('action1_agent','action1_remote','action1'); Svc=@('action1*'); Path=@('*\action1*'); Signer=@('action1'); Class='RMM agent (abused)' } @{ Name='Level.io'; Exe=@('level-windows-amd64','level-remote-control-ws','levelrmm'); Svc=@('level*'); Path=@('*\level.io*','*\level\agent*'); Signer=@('level'); Class='RMM agent (abused)' } @{ Name='Tactical RMM'; Exe=@('tacticalrmm','tacticalagent','trmm'); Svc=@('tacticalrmm*'); Path=@('*\tacticalagent*','*\tacticalrmm*'); Signer=@('amidaware'); Class='RMM agent (open-source, abused)' } @{ Name='MeshCentral / MeshAgent'; Exe=@('meshagent'); Svc=@('mesh agent*','meshagent*'); Path=@('*\meshagent*','*\meshcentral*'); Signer=@('meshcentral'); Class='Remote management (open-source, abused)' } @{ Name='ManageEngine Endpoint Central';Exe=@('dcagentservice','dcagenttrayicon','dcfilelogservice'); Svc=@('manageengine*','dcagent*'); Path=@('*\manageengine*','*\desktopcentral*','*\endpoint central*'); Signer=@('zoho'); Class='RMM / endpoint mgmt (abused)' } @{ Name='ImmyBot'; Exe=@('immyagent','immyupdater'); Svc=@('immy*'); Path=@('*\immybot*'); Signer=@('immense','immybot'); Class='RMM agent' } @{ Name='Goverlan Reach'; Exe=@('goverrmc','goverlanreach','grcagentservice'); Svc=@('grcagent*','goverlan*'); Path=@('*\goverlan*'); Signer=@('pj technologies','goverlan'); Class='Remote administration (abused)' } @{ Name='ISL Online / ISL Light'; Exe=@('isllight','isllightclient','islalwaysonmonitor','isllightservice'); Svc=@('isl*'); Path=@('*\isl online*','*\isllight*'); Signer=@('xlab','isl online'); Class='Remote support (abused)' } @{ Name='Parsec'; Exe=@('parsecd','parsec'); Svc=@('parsec*'); Path=@('*\parsec*'); Signer=@('parsec'); Class='Remote desktop (gaming; abused)' } @{ Name='Jump Desktop'; Exe=@('jumpdesktop','jumpconnect','jwm-prod'); Svc=@('jump*'); Path=@('*\jump desktop*','*\jumpdesktop*'); Signer=@('phase five','jump desktop'); Class='Remote desktop' } @{ Name='Getscreen'; Exe=@('getscreen'); Svc=@('getscreen*'); Path=@('*\getscreen*'); Signer=@('getscreen'); Class='Remote support (abused in scams)' } @{ Name='Iperius Remote'; Exe=@('iperiusremote'); Svc=@('iperius*'); Path=@('*\iperius*'); Signer=@('enter srl','iperius'); Class='Remote support' } @{ Name='DWService / DWAgent'; Exe=@('dwagent','dwagsvc','dwaglnc'); Svc=@('dwagent*'); Path=@('*\dwagent*','*\dwservice*'); Signer=@('dwservice'); Class='Remote support (open-source, abused)' } @{ Name='Distant Desktop'; Exe=@('distant_desktop'); Svc=@(); Path=@('*\distant desktop*'); Signer=@(); Class='Remote control (abused in scams)' } @{ Name='LiteManager'; Exe=@('romserver','romfusclient','romviewer'); Svc=@('romservice*','litemanager*'); Path=@('*\litemanager*'); Signer=@('litemanager'); Class='Remote control (abused as a RAT)' } @{ Name='ShowMyPC'; Exe=@('showmypc'); Svc=@('showmypc*'); Path=@('*\showmypc*'); Signer=@('showmypc'); Class='Remote support (abused)' } @{ Name='AeroAdmin'; Exe=@('aeroadmin'); Svc=@(); Path=@('*\aeroadmin*'); Signer=@('aeroadmin'); Class='Remote control (abused in scams)' } @{ Name='AweRay / AweSun'; Exe=@('aweray_remote','awesun','aweray'); Svc=@('aweray*'); Path=@('*\aweray*','*\awesun*'); Signer=@('aweray'); Class='Remote control (abused)' } @{ Name='HopToDesk'; Exe=@('hoptodesk'); Svc=@('hoptodesk*'); Path=@('*\hoptodesk*'); Signer=@('hoptodesk'); Class='Remote control (abused)' } @{ Name='Chrome Remote Desktop'; Exe=@('remoting_host','remote_assistance_host'); Svc=@('chrome remote desktop*'); Path=@('*\chrome remote desktop*'); Signer=@(); Class='Remote desktop' } @{ Name='FastViewer'; Exe=@('fastviewer','fastclient','fastmaster'); Svc=@('fastviewer*'); Path=@('*\fastviewer*'); Signer=@('fastviewer'); Class='Remote support (abused)' } @{ Name='SuperOps'; Exe=@('superops','superopsticket'); Svc=@('superops*'); Path=@('*\superops*'); Signer=@('superops'); Class='RMM agent' } @{ Name='Mikogo'; Exe=@('mikogo','mikogo-host','mikogo-service'); Svc=@('mikogo*'); Path=@('*\mikogo*'); Signer=@('mikogo','snapview'); Class='Remote support / screen sharing' } @{ Name='Atera Splashtop (Streamer)'; Exe=@('atera_agent'); Svc=@('ateraagent*'); Path=@('*\atera*'); Signer=@('atera'); Class='RMM agent (abused)' } # ── Mesh VPN / tunneling (used by attackers to reach a host or for C2) ─────── @{ Name='Tailscale'; Exe=@('tailscale','tailscaled','tailscale-ipn'); Svc=@('tailscale*'); Path=@('*\tailscale*'); Signer=@('tailscale'); Class='Mesh VPN (used by attackers for access)' } @{ Name='ZeroTier'; Exe=@('zerotier-one_x64','zerotier-one','zerotier'); Svc=@('zerotier*'); Path=@('*\zerotier*'); Signer=@('zerotier'); Class='Mesh VPN (used by attackers for access)' } @{ Name='NetBird'; Exe=@('netbird','netbird-ui'); Svc=@('netbird*'); Path=@('*\netbird*'); Signer=@('netbird','wiretrustee'); Class='Mesh VPN (used by attackers for access)' } @{ Name='frp (Fast Reverse Proxy)'; Exe=@('frpc','frps'); Svc=@(); Path=@('*\frp*'); Signer=@(); Class='Tunneling tool (used to expose remote access)' } @{ Name='LocalXpose'; Exe=@('loclx'); Svc=@(); Path=@('*\localxpose*'); Signer=@(); Class='Tunneling tool' } @{ Name='localtonet'; Exe=@('localtonet'); Svc=@('localtonet*'); Path=@('*\localtonet*'); Signer=@(); Class='Tunneling tool' } @{ Name='playit.gg'; Exe=@('playit'); Svc=@('playit*'); Path=@('*\playit*'); Signer=@(); Class='Tunneling tool' } @{ Name='plink (PuTTY link)'; Exe=@('plink'); Svc=@(); Path=@(); Signer=@(); Class='SSH tunneling tool (used for port-forward C2)' } # ── Known RAT / C2 families (default names; renamed variants are caught by the # unsigned + user-writable-path + deep-AppData heuristics in the scans) ──── @{ Name='AsyncRAT (malware)'; Exe=@('asyncrat','asyncclient'); Svc=@(); Path=@(); Signer=@(); Class='Remote Access Trojan' } @{ Name='Remcos (malware)'; Exe=@('remcos'); Svc=@(); Path=@('*\remcos*'); Signer=@(); Class='Remote Access Trojan' } @{ Name='njRAT / Bladabindi (malware)'; Exe=@('njrat'); Svc=@(); Path=@(); Signer=@(); Class='Remote Access Trojan' } @{ Name='Quasar RAT (malware)'; Exe=@('quasar','quasarrat'); Svc=@(); Path=@(); Signer=@(); Class='Remote Access Trojan' } @{ Name='XWorm (malware)'; Exe=@('xworm'); Svc=@(); Path=@(); Signer=@(); Class='Remote Access Trojan' } @{ Name='DCRat (malware)'; Exe=@('dcrat'); Svc=@(); Path=@(); Signer=@(); Class='Remote Access Trojan' } @{ Name='NanoCore (malware)'; Exe=@('nanocore'); Svc=@(); Path=@(); Signer=@(); Class='Remote Access Trojan' } @{ Name='VenomRAT (malware)'; Exe=@('venomrat'); Svc=@(); Path=@(); Signer=@(); Class='Remote Access Trojan' } @{ Name='Cobalt Strike (C2)'; Exe=@('cobaltstrike'); Svc=@(); Path=@(); Signer=@(); Class='Command-and-control framework' } ) # Keyword list for TEXT scans (registry values, scheduled-task command lines). # Only tokens long/specific enough to avoid collisions are included. $ratKeywords = @( 'teamviewer','anydesk','screenconnect','connectwise','logmein','splashtop', 'rustdesk','radmin','realvnc','tightvnc','ultravnc','tigervnc','winvnc', 'ammyy','dameware','netsupport','supremo','ultraviewer','remotepc','zohoassist', 'gotoassist','gotomypc','anyviewer','rutserv','bomgar','beyondtrust', 'ngrok','cloudflared','ateraagent','ninjarmm','kaseya','pulseway', # additional RMM / remote tools 'simplehelp','itarian','pdq-connect','pdqconnect','n-able','centrastage','aurora-agent', 'syncro','kabuto','action1','level-remote','tacticalrmm','tacticalagent','meshagent', 'meshcentral','desktopcentral','immybot','goverlan','isllight','parsec','jumpdesktop', 'getscreen','iperius','dwagent','dwservice','distant_desktop','litemanager','romserver', 'showmypc','aeroadmin','aweray','awesun','hoptodesk','remoting_host','fastviewer','superops', 'mikogo','tailscale','zerotier','netbird','localxpose','localtonet','playit', # tunneling / reverse-shell 'frpc','frps','loclx','plink', # known RAT / C2 family names 'remcos','njrat','quasar','asyncrat','venomrat','nanocore','darkcomet','netwire', 'xworm','dcrat','bitrat','warzonerat','orcus','limerat','cobaltstrike','bruteratel' ) # Benign comms / cloud-sync names that must never be flagged on name alone $benignNames = @( 'onedrive','dropbox','googledrive','googledrivesync','gdrive','icloud', 'zoom','webex','msteams','teams','slack','vonage','ringcentral','openphone', 'chrome','firefox','msedge','outlook','spotify','discord' ) function Get-WhitelistMatch([string]$text) { if (-not $text) { return $null } $t = $text.ToLower() return $benignNames | Where-Object { $t -like "*$_*" } | Select-Object -First 1 } # Ports associated with remote-access / C2 traffic $ratPorts = @{ 3389='RDP (Remote Desktop)'; 5900='VNC'; 5938='TeamViewer'; 7070='AnyDesk' 4444='Metasploit/RAT'; 4443='Reverse Shell'; 1337='RAT port' 5985='WinRM HTTP'; 5986='WinRM HTTPS'; 22='SSH'; 23='Telnet' 4899='Radmin'; 6568='Remote Utilities'; 6129='DameWare' 8040='ScreenConnect'; 55000='ScreenConnect'; 6667='IRC/Botnet C2' 4782='Quasar RAT C2'; 1604='DarkComet C2'; 1177='njRAT C2'; 5552='njRAT C2' 6606='AsyncRAT C2'; 7707='AsyncRAT C2'; 8808='AsyncRAT C2'; 2404='Remcos C2' 3460='Bifrost/RAT'; 9999='RAT C2'; 5050='RAT C2' } # Management ports that are commonly legitimate — flagged but at MEDIUM, not HIGH $mgmtPorts = @(22,3389,5985,5986) # Known install folders to check on disk $ratFolders = @( "$env:APPDATA\TeamViewer", "$env:APPDATA\AnyDesk", "$env:PROGRAMFILES\TeamViewer", "$env:PROGRAMFILES\AnyDesk", "$env:PROGRAMFILES\RealVNC", "$env:PROGRAMFILES\TightVNC", "$env:PROGRAMFILES\UltraVNC", "$env:PROGRAMFILES\Radmin", "$env:PROGRAMFILES\ScreenConnect", "$env:PROGRAMFILES\Supremo", "$env:PROGRAMFILES\UltraViewer", "$env:PROGRAMFILES\AnyViewer", "${env:ProgramFiles(x86)}\TeamViewer", "${env:ProgramFiles(x86)}\AnyDesk", "${env:ProgramFiles(x86)}\UltraViewer", "$env:LOCALAPPDATA\ngrok", "$env:PROGRAMFILES\SimpleHelp", "$env:PROGRAMDATA\SimpleHelpCustomer", "$env:PROGRAMFILES\RustDesk", "$env:APPDATA\RustDesk", "$env:PROGRAMFILES\Splashtop", "${env:ProgramFiles(x86)}\Splashtop", "$env:PROGRAMFILES\AeroAdmin", "$env:PROGRAMFILES\LiteManager Pro - Server", "$env:PROGRAMFILES\DWAgent", "$env:PROGRAMFILES\Getscreen.me", "$env:PROGRAMFILES\Mesh Agent", "$env:PROGRAMFILES\TacticalAgent", "$env:LOCALAPPDATA\Programs\distant-desktop", "$env:APPDATA\HopToDesk" ) # ── Trust / catalog helpers ───────────────────────────────────────────────── $script:TrustCache = @{} function Get-FileTrust { # Authenticode + version metadata for a file, cached per path. param([string]$Path) $info = [PSCustomObject]@{ Exists=$false; Signed=$false; SignerName=''; IsMicrosoft=$false; Company=''; Product='' } if (-not $Path) { return $info } $clean = ($Path -replace '"','').Trim() # strip any trailing service arguments (e.g. "...\svc.exe" /run) if ($clean -match '^(.*\.exe)\b') { $clean = $matches[1] } if ($script:TrustCache.ContainsKey($clean)) { return $script:TrustCache[$clean] } if (-not (Test-Path -LiteralPath $clean)) { $script:TrustCache[$clean] = $info; return $info } $info.Exists = $true try { $sig = Get-AuthenticodeSignature -LiteralPath $clean -ErrorAction Stop if ($sig.Status -eq 'Valid' -and $sig.SignerCertificate) { $info.Signed = $true $cn = (($sig.SignerCertificate.Subject -split ',')[0] -replace '^CN=','').Trim('" ') $info.SignerName = $cn if ($cn -match 'Microsoft (Corporation|Windows)') { $info.IsMicrosoft = $true } } } catch {} try { $vi = (Get-Item -LiteralPath $clean -ErrorAction Stop).VersionInfo $info.Company = $vi.CompanyName $info.Product = $vi.ProductName } catch {} $script:TrustCache[$clean] = $info return $info } function Find-RemoteTool { # Precise catalog match. Returns the catalog entry hashtable, or $null. # -Exe executable file name (with/without .exe) -> matched EXACTLY to catalog Exe base names # -ServiceName Windows service short name -> matched to catalog Svc patterns # -Display Windows service display name -> matched to catalog Svc patterns # -Path full path / command line -> matched to catalog Path patterns # -Signer Authenticode signer common name -> matched to catalog Signer list # Svc patterns are matched ONLY against service name/display (never against an # exe name) so a broad pattern like 'level*' can't flag an unrelated level.exe. param([string]$Exe, [string]$ServiceName, [string]$Display, [string]$Path, [string]$Signer) $base = '' if ($Exe) { $base = ($Exe.ToLower() -replace '\.exe$',''); if ($base -match '[\\/]') { $base = ($base -split '[\\/]')[-1] } } $svcN = if ($ServiceName) { $ServiceName.ToLower() } else { '' } $disp = if ($Display) { $Display.ToLower() } else { '' } $pth = if ($Path) { $Path.ToLower() } else { '' } $sgn = if ($Signer) { $Signer.ToLower() } else { '' } foreach ($t in $RemoteTools) { if ($base) { foreach ($e in $t.Exe) { if ($e -and $base -eq $e) { return $t } } } foreach ($s in $t.Svc) { if ($s -and (($svcN -and $svcN -like $s) -or ($disp -and $disp -like $s))) { return $t } } if ($pth) { foreach ($p in $t.Path) { if ($p -and $pth -like $p.ToLower()) { return $t } } } if ($sgn) { foreach ($g in $t.Signer) { if ($g -and $sgn -like "*$g*") { return $t } } } } return $null } function Test-BadPath([string]$Path) { # User-writable locations where legitimate background services rarely live. if (-not $Path) { return $false } return ($Path -like '*\AppData\*' -or $Path -like '*\Temp\*' -or $Path -like '*\Users\Public\*') } # Well-known vendor directories — used to suppress the weak "unsigned exe buried # deep in AppData" signal for mainstream apps (Teams, browsers, Electron apps, # etc. all drop unsigned helper exes deep in their own folders). Catalogued tools # and keyword matches are NOT affected by this — only the depth-only heuristic. $benignVendorDirs = @( '\microsoft\','\windowsapps\','\google\','\mozilla\','\adobe\','\discord\', '\slack\','\spotify\','\zoom\','\webex\','\steam\','\epic games\','\jetbrains\', '\github\','\githubdesktop\','\postman\','\1password\','\dropbox\','\box\', '\nvidia\','\intel\','\amd\','\logi','\citrix\','\python','\nodejs\','\valve\', '\obs-studio\','\zoomus\','\whatsapp\','\signal\','\telegram desktop\' ) function Test-TrustedVendorPath([string]$Path) { if (-not $Path) { return $false } $p = $Path.ToLower() foreach ($d in $benignVendorDirs) { if ($p -like "*$d*") { return $true } } return $false } # ── Helper functions ────────────────────────────────────────────────────────── function Esc([string]$s) { if (-not $s) { return '' } $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"') } function Badge([string]$sev) { $c = if ($sev -eq 'HIGH') { '#dc2626' } elseif ($sev -eq 'MEDIUM') { '#ea580c' } elseif ($sev -eq 'LOW') { '#2563eb' } else { '#d97706' } return "$sev" } function Section([string]$title, [string]$intro, $items, [string[]]$headers, [scriptblock]$rowFn) { $count = if ($items) { @($items).Count } else { 0 } $badge = if ($count -gt 0) { "$count found" } else { "Clean" } $out = "

$title $badge

$intro

" if ($count -gt 0) { $ths = ($headers | ForEach-Object { "$_" }) -join '' $out += "
$ths" foreach ($item in @($items)) { $out += (& $rowFn $item) } $out += "
" } else { $out += "

✔ None detected.

" } $out += "
" return $out } # ── Scan 1: Processes ───────────────────────────────────────────────────────── Write-Host " [1/10] Scanning running processes..." -ForegroundColor Yellow $allProcs = Get-Process | Select-Object Name, Id, Path, Company $suspProcs = New-Object 'System.Collections.Generic.List[object]' foreach ($p in $allProcs) { if (-not $p.Path) { continue } # no image path = protected/system; nothing to assess $trust = Get-FileTrust $p.Path $tool = Find-RemoteTool -Exe $p.Name -Path $p.Path -Signer $trust.SignerName $bad = Test-BadPath $p.Path # Microsoft-signed and benign signed comms apps are never flagged on name alone if (-not $tool -and $trust.IsMicrosoft) { continue } if (-not $tool -and -not $bad -and (Get-WhitelistMatch $p.Name)) { continue } if ($tool -or $bad) { if ($tool) { $sev = 'HIGH'; $reason = "Known remote access tool: $($tool.Name) — $($tool.Class)" } elseif (-not $trust.Signed) { $sev = 'HIGH'; $reason = 'Unsigned executable running from a user-writable location' } else { $sev = 'MEDIUM'; $reason = 'Runs from a user-writable location (AppData / Temp / Public)' } $signerNote = if ($trust.Signed) { "Signed by: $($trust.SignerName)" } else { 'Digital signature: UNSIGNED' } $suspProcs.Add([PSCustomObject]@{ Name = $p.Name PID = $p.Id Path = $p.Path Company = if ($trust.Company) { $trust.Company } elseif ($p.Company) { $p.Company } else { 'Unknown' } Sev = $sev Reason = "$reason. $signerNote." Fix = "Open Task Manager, find the process, right-click and choose End Task.`nOr run in Admin PowerShell: Stop-Process -Id $($p.Id) -Force" }) } } Write-Host " Found: $($suspProcs.Count)" -ForegroundColor Gray # ── Scan 2: Services ────────────────────────────────────────────────────────── Write-Host " [2/10] Scanning services..." -ForegroundColor Yellow $allSvcs = Get-WmiObject Win32_Service $suspSvcs = New-Object 'System.Collections.Generic.List[object]' foreach ($s in $allSvcs) { # Pull the bare executable path out of the service command line $binPath = '' if ($s.PathName) { $pn = $s.PathName.Trim() if ($pn -match '^\s*"([^"]+\.exe)"') { $binPath = $matches[1] } elseif ($pn -match '^\s*([^\s]+\.exe)') { $binPath = $matches[1] } else { $binPath = $pn } } $trust = Get-FileTrust $binPath $tool = Find-RemoteTool -Exe $binPath -ServiceName $s.Name -Display $s.DisplayName -Path $s.PathName -Signer $trust.SignerName $bad = Test-BadPath $s.PathName if (-not $tool -and $trust.IsMicrosoft) { continue } if (-not $tool -and -not $bad -and (Get-WhitelistMatch $s.Name)) { continue } if ($tool -or $bad) { if ($tool) { $sev = 'HIGH'; $reason = "Known remote access service: $($tool.Name) — $($tool.Class)" } elseif (-not $trust.Signed) { $sev = 'HIGH'; $reason = 'Unsigned service binary in a user-writable location' } else { $sev = 'MEDIUM'; $reason = 'Service binary runs from a user-writable location' } $signerNote = if ($trust.Signed) { "Signed by: $($trust.SignerName)" } else { 'UNSIGNED binary' } $suspSvcs.Add([PSCustomObject]@{ Name = $s.Name Display = $s.DisplayName State = $s.State Start = $s.StartMode Sev = $sev Reason = "$reason. $signerNote." Fix = "Run in Admin PowerShell:`nStop-Service '$($s.Name)' -Force`nSet-Service '$($s.Name)' -StartupType Disabled`nsc.exe delete '$($s.Name)'" }) } } Write-Host " Found: $($suspSvcs.Count)" -ForegroundColor Gray # ── Scan 3: Network connections ─────────────────────────────────────────────── Write-Host " [3/10] Scanning network connections..." -ForegroundColor Yellow $suspConns = New-Object 'System.Collections.Generic.List[object]' try { # Try Get-NetTCPConnection first (Win8.1+/PS4+), fall back to netstat for Win8 $netCmdAvail = $null -ne (Get-Command Get-NetTCPConnection -ErrorAction SilentlyContinue) if ($netCmdAvail) { $allConns = Get-NetTCPConnection -ErrorAction SilentlyContinue foreach ($c in $allConns) { $pNote = $ratPorts[$c.LocalPort] $rNote = $ratPorts[$c.RemotePort] $isExt = $c.RemoteAddress -and $c.RemoteAddress -ne '0.0.0.0' -and $c.RemoteAddress -ne '::' -and $c.RemoteAddress -ne '::1' -and $c.RemoteAddress -notlike '127.*' if (($pNote -or $rNote) -and $isExt) { $note = if ($pNote) { $pNote } else { $rNote } $isMgmt = ($mgmtPorts -contains $c.LocalPort) -or ($mgmtPorts -contains $c.RemotePort) $sev = if ($isMgmt) { 'MEDIUM' } elseif ($c.State -eq 'Established') { 'HIGH' } else { 'MEDIUM' } $pname = ($allProcs | Where-Object { $_.Id -eq $c.OwningProcess } | Select-Object -First 1).Name $suspConns.Add([PSCustomObject]@{ Process = if ($pname) { $pname } else { 'Unknown' } PID = $c.OwningProcess Local = "$($c.LocalAddress):$($c.LocalPort)" Remote = "$($c.RemoteAddress):$($c.RemotePort)" State = $c.State Note = $note Sev = $sev Fix = "Kill the process: Stop-Process -Id $($c.OwningProcess) -Force`nBlock the port in Windows Firewall > Advanced Settings > Outbound Rules." }) } } } else { # Windows 8 fallback: parse netstat -ano output $netstatOut = & netstat -ano 2>$null foreach ($line in $netstatOut) { if ($line -notmatch 'TCP\s+') { continue } $parts = $line.Trim() -split '\s+' if ($parts.Count -lt 5) { continue } $localFull = $parts[1] $remoteFull = $parts[2] $state = $parts[3] $procId = [int]$parts[4] $localPort = 0 $remotePort = 0 if ($localFull -match ':(\d+)$') { $localPort = [int]$matches[1] } if ($remoteFull -match ':(\d+)$') { $remotePort = [int]$matches[1] } $remoteIP = $remoteFull -replace ':\d+$','' $isExt = $remoteIP -and $remoteIP -ne '0.0.0.0' -and $remoteIP -ne '::' -and $remoteIP -ne '::1' -and $remoteIP -notlike '127.*' $pNote = $ratPorts[$localPort] $rNote = $ratPorts[$remotePort] if (($pNote -or $rNote) -and $isExt) { $note = if ($pNote) { $pNote } else { $rNote } $isMgmt = ($mgmtPorts -contains $localPort) -or ($mgmtPorts -contains $remotePort) $sev = if ($isMgmt) { 'MEDIUM' } elseif ($state -eq 'ESTABLISHED') { 'HIGH' } else { 'MEDIUM' } $pname = ($allProcs | Where-Object { $_.Id -eq $procId } | Select-Object -First 1).Name $suspConns.Add([PSCustomObject]@{ Process = if ($pname) { $pname } else { 'Unknown' } PID = $procId Local = $localFull Remote = $remoteFull State = $state Note = $note Sev = $sev Fix = "Kill the process: Stop-Process -Id $procId -Force`nBlock the port in Windows Firewall > Advanced Settings > Outbound Rules." }) } } } } catch { Write-Host " Network scan error: $_" -ForegroundColor Red } Write-Host " Found: $($suspConns.Count)" -ForegroundColor Gray # ── Scan 4: Scheduled tasks ─────────────────────────────────────────────────── Write-Host " [4/10] Scanning scheduled tasks..." -ForegroundColor Yellow $suspTasks = New-Object 'System.Collections.Generic.List[object]' try { $schedCmdAvail = $null -ne (Get-Command Get-ScheduledTask -ErrorAction SilentlyContinue) if ($schedCmdAvail) { $allTasks = Get-ScheduledTask | Where-Object { $_.State -ne 'Disabled' } foreach ($t in $allTasks) { $underMS = $t.TaskPath -like '\Microsoft\*' foreach ($a in $t.Actions) { if (-not $a.Execute) { continue } $exe = [Environment]::ExpandEnvironmentVariables($a.Execute) $trust = Get-FileTrust $exe $kw = $ratKeywords | Where-Object { $a.Execute -like "*$_*" -or $a.Arguments -like "*$_*" } | Select-Object -First 1 $bad = Test-BadPath $a.Execute # Random-name heuristic only for non-Microsoft, non-MS-signed tasks (pure hex) $rand = (-not $underMS) -and (-not $trust.IsMicrosoft) -and ($t.TaskName -match '^[a-f0-9]{16,}$') if (-not $kw -and $trust.IsMicrosoft) { continue } if ($kw -or $bad -or $rand) { $sev = if ($kw -or ($bad -and -not $trust.Signed)) { 'HIGH' } else { 'MEDIUM' } $suspTasks.Add([PSCustomObject]@{ Name = $t.TaskName Exe = $a.Execute Args = if ($a.Arguments) { $a.Arguments } else { '' } State = $t.State Sev = $sev Reason = if ($kw) { "References remote access tool: $kw" } elseif ($bad) { 'Runs from a user-writable path' } else { 'Obfuscated / random task name' } Fix = "Open Task Scheduler, find '$($t.TaskName)', right-click and choose Disable or Delete.`nOr run: Unregister-ScheduledTask -TaskName '$($t.TaskName)' -Confirm:`$false" }) } } } } else { # Windows 8 fallback: parse schtasks /query output $schtasksOut = & schtasks /query /fo CSV /v 2>$null | ConvertFrom-Csv -ErrorAction SilentlyContinue foreach ($t in $schtasksOut) { $taskName = $t.'TaskName' $exe = $t.'Task To Run' $status = $t.'Status' if (-not $taskName -or -not $exe) { continue } if ($status -eq 'Disabled') { continue } $underMS = $taskName -like '\Microsoft\*' $trust = Get-FileTrust $exe $kw = $ratKeywords | Where-Object { $exe -like "*$_*" } | Select-Object -First 1 $bad = Test-BadPath $exe $leaf = ($taskName -split '\\')[-1] $rand = (-not $underMS) -and (-not $trust.IsMicrosoft) -and ($leaf -match '^[a-f0-9]{16,}$') if (-not $kw -and $trust.IsMicrosoft) { continue } if ($kw -or $bad -or $rand) { $sev = if ($kw -or ($bad -and -not $trust.Signed)) { 'HIGH' } else { 'MEDIUM' } $suspTasks.Add([PSCustomObject]@{ Name = $taskName Exe = $exe Args = '' State = $status Sev = $sev Reason = if ($kw) { "References remote access tool: $kw" } elseif ($bad) { 'Runs from a user-writable path' } else { 'Obfuscated / random task name' } Fix = "Open Task Scheduler, find '$taskName', right-click and Disable or Delete.`nOr run: schtasks /delete /tn `"$taskName`" /f" }) } } } } catch { Write-Host " Task scan error: $_" -ForegroundColor Red } Write-Host " Found: $($suspTasks.Count)" -ForegroundColor Gray # ── Scan 5: Registry ────────────────────────────────────────────────────────── Write-Host " [5/10] Scanning registry startup keys..." -ForegroundColor Yellow $regPaths = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run', 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run', 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' ) $suspReg = New-Object 'System.Collections.Generic.List[object]' foreach ($rp in $regPaths) { if (-not (Test-Path $rp)) { continue } try { $props = Get-ItemProperty -Path $rp if (-not $props) { continue } foreach ($entry in ($props.PSObject.Properties | Where-Object { $_.Name -notlike 'PS*' })) { $val = [string]$entry.Value $hit = $ratKeywords | Where-Object { $val -like "*$_*" } | Select-Object -First 1 $badPath = $val -like '*\AppData\*' -or $val -like '*\Temp\*' -or $val -like '*\Users\Public\*' # Resolve the target exe so we can check its signature $exeTarget = '' if ($val -match '"([^"]+\.exe)"') { $exeTarget = $matches[1] } elseif ($val -match '([A-Za-z]:\\[^\s"]+\.exe)') { $exeTarget = $matches[1] } elseif ($val -match '(%[^%]+%\\[^\s"]+\.exe)') { $exeTarget = $matches[1] } $regTrust = Get-FileTrust ([Environment]::ExpandEnvironmentVariables($exeTarget)) # Unless it references a known remote tool, trust normal autostart entries: # Microsoft-signed, benign apps by name (Teams/OneDrive/Slack/...), or any # validly-signed publisher. Only an UNSIGNED program auto-starting from a # user-writable path is suspicious. if (-not $hit) { if ($regTrust.IsMicrosoft) { continue } if (Get-WhitelistMatch $val) { continue } if ($regTrust.Signed) { continue } if (-not $badPath) { continue } } if ($hit -or $badPath) { $sev = if ($hit) { 'HIGH' } else { 'MEDIUM' } $suspReg.Add([PSCustomObject]@{ RegPath = $rp Name = $entry.Name Value = $val Sev = $sev Reason = if ($hit) { "References known remote access tool: $hit" } else { 'Unsigned program auto-starting from a user-writable path (AppData/Temp/Public)' } Fix = "Open regedit.exe, navigate to:`n$rp`nFind the entry '$($entry.Name)' and delete it.`nOr run: Remove-ItemProperty -Path '$rp' -Name '$($entry.Name)'" }) } } } catch { } } Write-Host " Found: $($suspReg.Count)" -ForegroundColor Gray # ── Scan 6: Known folders ───────────────────────────────────────────────────── Write-Host " [6/10] Checking known RAT folders..." -ForegroundColor Yellow $suspFolders = New-Object 'System.Collections.Generic.List[object]' foreach ($f in $ratFolders) { if (Test-Path $f) { $suspFolders.Add([PSCustomObject]@{ Path = $f Sev = 'HIGH' Reason = 'Known remote access tool installation folder found on disk' Fix = "First uninstall via Settings > Apps if listed.`nThen delete leftovers: Remove-Item -Path '$f' -Recurse -Force" }) } } Write-Host " Found: $($suspFolders.Count)" -ForegroundColor Gray # ── Scan 7: RDP ─────────────────────────────────────────────────────────────── Write-Host " [7/10] Checking RDP configuration..." -ForegroundColor Yellow $rdpEnabled = $false $rdpPort = 3389 $nlaOn = $false try { $rdpReg = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' $rdpEnabled = ($rdpReg.fDenyTSConnections -eq 0) $rdpTcp = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' if ($rdpTcp.PortNumber) { $rdpPort = $rdpTcp.PortNumber } $nlaOn = ($rdpTcp.UserAuthentication -eq 1) } catch { } # ── Scan 8: ScreenConnect / ConnectWise Control detection & uninstall info ─── Write-Host " [8/11] Checking for ScreenConnect / ConnectWise Control installations..." -ForegroundColor Yellow $scFindings = New-Object 'System.Collections.Generic.List[object]' # Known ScreenConnect service name patterns (ConnectWise uses random-suffix names like # "ScreenConnect Client (abc123def456)" or just "ScreenConnect" for server installs) $scServicePatterns = @('*screenconnect*','*connectwisecontrol*','*connectwise control*') # Known ScreenConnect install/data paths $scKnownPaths = @( "$env:PROGRAMFILES\ScreenConnect", "$env:PROGRAMFILES\ConnectWise Control", "${env:ProgramFiles(x86)}\ScreenConnect", "${env:ProgramFiles(x86)}\ConnectWise Control", "$env:PROGRAMDATA\ScreenConnect", "$env:PROGRAMDATA\ConnectWiseControl", "$env:APPDATA\ScreenConnect", "$env:LOCALAPPDATA\ScreenConnect", "$env:LOCALAPPDATA\Apps", # ClickOnce installs sometimes land here "C:\Windows\Temp\ScreenConnect", "C:\Temp\ScreenConnect" ) # Registry uninstall keys to look for $uninstallHives = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall', 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' ) try { # ── Step A: Check services ──────────────────────────────────────────────── $allSvcsFull = Get-WmiObject Win32_Service foreach ($svc in $allSvcsFull) { $matchedPattern = $scServicePatterns | Where-Object { $svc.Name -like $_ -or $svc.DisplayName -like $_ } | Select-Object -First 1 if (-not $matchedPattern) { continue } # Try to find uninstall string from registry $uninstallCmd = '' $installPath = '' $version = '' foreach ($hive in $uninstallHives) { if (-not (Test-Path $hive)) { continue } Get-ChildItem $hive -ErrorAction SilentlyContinue | ForEach-Object { $props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue if ($props -and ($props.DisplayName -like '*screenconnect*' -or $props.DisplayName -like '*connectwise control*')) { if ($props.UninstallString) { $uninstallCmd = $props.UninstallString } if ($props.InstallLocation) { $installPath = $props.InstallLocation } if ($props.DisplayVersion) { $version = $props.DisplayVersion } } } } # Build the uninstall path from service binary if registry didn't have it if (-not $installPath -and $svc.PathName) { $installPath = Split-Path ($svc.PathName -replace '"','') -Parent } # ── Disk validation: skip ghost entries ─────────────────────────────── # If the service is registered but nothing exists on disk, it's a stale # registry remnant from a previous removal. Report it as a cleanup note # rather than an active threat. $binaryExists = $svc.PathName -and (Test-Path ($svc.PathName -replace '"','' -replace '\s+/.*$','')) $installExists = $installPath -and (Test-Path $installPath) $isGhost = (-not $binaryExists -and -not $installExists) if ($isGhost) { # Service key exists in registry but nothing is on disk — stale remnant $scFindings.Add([PSCustomObject]@{ ServiceName = $svc.Name DisplayName = "$($svc.DisplayName) [STALE — files already removed]" State = "$($svc.State) (binary missing from disk)" StartMode = $svc.StartMode BinaryPath = if ($svc.PathName) { $svc.PathName } else { 'N/A' } InstallPath = if ($installPath) { "$installPath (NOT FOUND ON DISK)" } else { 'Not found' } Version = if ($version) { $version } else { 'Unknown' } HasUninstaller = 'No — registry cleanup only needed' Sev = 'LOW' Removal = "# Files are already gone. Only the service registry key remains.`n# Run these to clean up the leftover entries:`n`n# Remove the dead service entry:`nsc.exe delete `"$($svc.Name)`"`n`n# Remove uninstall registry keys:`nforeach (`$hive in @('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall')) {`n Get-ChildItem `$hive -ErrorAction SilentlyContinue | ForEach-Object {`n `$p = Get-ItemProperty `$_.PSPath -ErrorAction SilentlyContinue`n if (`$p.DisplayName -like '*ScreenConnect*' -or `$p.DisplayName -like '*ConnectWise Control*') {`n Remove-Item `$_.PSPath -Recurse -Force`n Write-Host `"Removed: `$(`$_.PSPath)`"`n }`n }`n}`n`n# Remove any LSA auth package references:`n`$lsaKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa'`n`$sp = (Get-ItemProperty `$lsaKey -Name 'Security Packages' -ErrorAction SilentlyContinue).('Security Packages')`nif (`$sp) { Set-ItemProperty `$lsaKey -Name 'Security Packages' -Value (`$sp | Where-Object { `$_ -notlike '*ScreenConnect*' -and `$_ -notlike '*ConnectWise*' }) }`n`nWrite-Host 'Stale ScreenConnect registry entries removed.'" }) continue } $removalSteps = "# STEP 1 — Stop and disable the service:`nStop-Service '$($svc.Name)' -Force -ErrorAction SilentlyContinue`nSet-Service '$($svc.Name)' -StartupType Disabled`n`n" if ($uninstallCmd) { $removalSteps += "# STEP 2 — Run the official uninstaller (found in registry):`n$uninstallCmd`n`n" } else { $removalSteps += "# STEP 2 — No uninstaller found in registry. Delete the service manually:`nsc.exe stop `"$($svc.Name)`"`nsc.exe delete `"$($svc.Name)`"`n`n" } if ($installPath -and (Test-Path $installPath)) { $removalSteps += "# STEP 3 — Delete install folder:`nRemove-Item -Path '$installPath' -Recurse -Force`n`n" } $removalSteps += "# STEP 4 — Remove leftover AppData folders (run for each user):`nRemove-Item -Path `"`$env:APPDATA\ScreenConnect`" -Recurse -Force -ErrorAction SilentlyContinue`nRemove-Item -Path `"`$env:LOCALAPPDATA\ScreenConnect`" -Recurse -Force -ErrorAction SilentlyContinue`n`n" $removalSteps += "# STEP 5 — Remove registry keys:`nRemove-Item -Path 'HKLM:\SOFTWARE\ScreenConnect' -Recurse -Force -ErrorAction SilentlyContinue`nRemove-Item -Path 'HKLM:\SOFTWARE\WOW6432Node\ScreenConnect' -Recurse -Force -ErrorAction SilentlyContinue`nRemove-Item -Path 'HKCU:\SOFTWARE\ScreenConnect' -Recurse -Force -ErrorAction SilentlyContinue`n`n" $removalSteps += "# STEP 6 — Remove firewall rules added by ScreenConnect:`nGet-NetFirewallRule | Where-Object { `$_.DisplayName -like '*ScreenConnect*' -or `$_.DisplayName -like '*ConnectWise*' } | Remove-NetFirewallRule`n`n" $removalSteps += "# STEP 7 — IMPORTANT: Remove ScreenConnect Windows Authentication Package (SSP DLL).`n# ScreenConnect registers 'ScreenConnect.WindowsAuthenticationPackage.dll' as a`n# Security Support Provider (SSP), which causes lsass.exe to hold the DLL open.`n# This is why the folder cannot be deleted — the DLL is locked by lsass.`n# You MUST do this before rebooting, then reboot to release the lock.`n`n# 7a) Remove from LSA Security Packages registry list:`n`$lsaKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa'`n`$current = (Get-ItemProperty `$lsaKey -Name 'Security Packages').('Security Packages')`n`$cleaned = `$current | Where-Object { `$_ -notlike '*ScreenConnect*' -and `$_ -notlike '*ConnectWise*' }`nSet-ItemProperty `$lsaKey -Name 'Security Packages' -Value `$cleaned`n`n# 7b) Remove from LSA Authentication Packages list (second location):`n`$authPkgs = (Get-ItemProperty `$lsaKey -Name 'Authentication Packages' -ErrorAction SilentlyContinue).('Authentication Packages')`nif (`$authPkgs) {`n `$authCleaned = `$authPkgs | Where-Object { `$_ -notlike '*ScreenConnect*' -and `$_ -notlike '*ConnectWise*' }`n Set-ItemProperty `$lsaKey -Name 'Authentication Packages' -Value `$authCleaned`n}`n`n# 7c) Schedule the locked DLL for deletion on next reboot`n# (Windows will delete it before lsass loads it again):`n`$dllPaths = @(`n `$installPath + '\ScreenConnect.WindowsAuthenticationPackage.dll',`n `"`$env:SystemRoot\System32\ScreenConnect.WindowsAuthenticationPackage.dll`"`n)`nforeach (`$dll in `$dllPaths) {`n if (Test-Path `$dll) {`n # MoveFileEx with MOVEFILE_DELAY_UNTIL_REBOOT flag`n `$sig = '[DllImport(""kernel32.dll"",SetLastError=true)] public static extern bool MoveFileEx(string src, string dst, uint flags);'`n `$t = Add-Type -MemberDefinition `$sig -Name MFE -Namespace Win32 -PassThru`n `$t::MoveFileEx(`$dll, `$null, 4) | Out-Null # 4 = MOVEFILE_DELAY_UNTIL_REBOOT`n Write-Host "" Scheduled for delete on reboot: `$dll""`n }`n}`n`n# 7d) REBOOT THE PC after running all steps above.`n# After reboot, the DLL will be gone and you can delete the install folder:`nRemove-Item -Path '$installPath' -Recurse -Force`n`n" $removalSteps += "# STEP 8 — Remove registry keys:`nRemove-Item -Path 'HKLM:\SOFTWARE\ScreenConnect' -Recurse -Force -ErrorAction SilentlyContinue`nRemove-Item -Path 'HKLM:\SOFTWARE\WOW6432Node\ScreenConnect' -Recurse -Force -ErrorAction SilentlyContinue`nRemove-Item -Path 'HKCU:\SOFTWARE\ScreenConnect' -Recurse -Force -ErrorAction SilentlyContinue`n`n" $removalSteps += "# STEP 9 — Verify service is gone:`nGet-Service '$($svc.Name)' -ErrorAction SilentlyContinue" $scFindings.Add([PSCustomObject]@{ ServiceName = $svc.Name DisplayName = $svc.DisplayName State = $svc.State StartMode = $svc.StartMode BinaryPath = if ($svc.PathName) { $svc.PathName } else { 'N/A' } InstallPath = if ($installPath) { $installPath } else { 'Not found in registry' } Version = if ($version) { $version } else { 'Unknown' } HasUninstaller = if ($uninstallCmd) { 'Yes' } else { 'No - manual removal required' } Sev = if ($svc.State -eq 'Running') { 'HIGH' } else { 'MEDIUM' } Removal = $removalSteps }) } # ── Step C: Check if the SSP DLL is registered in LSA even if service is gone # This catches cases where SC was partially uninstalled but left its auth package behind try { $lsaKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' $secPkgs = (Get-ItemProperty $lsaKey -Name 'Security Packages' -ErrorAction SilentlyContinue).('Security Packages') $authPkgs = (Get-ItemProperty $lsaKey -Name 'Authentication Packages' -ErrorAction SilentlyContinue).('Authentication Packages') $allPkgs = @($secPkgs) + @($authPkgs) | Where-Object { $_ -like '*ScreenConnect*' -or $_ -like '*ConnectWise*' } foreach ($pkg in $allPkgs) { # Only add if not already represented by a service finding $alreadyCaptured = $scFindings | Where-Object { $_.ServiceName -notlike '*None*' } if ($alreadyCaptured) { continue } # Validate: check if the DLL actually exists anywhere on disk # If not, it's a stale LSA registry entry — still needs cleaning but not an active threat $dllSearchPaths = @( "$env:SystemRoot\System32\$pkg", "$env:SystemRoot\System32\${pkg}.dll", "$pkg" # in case it's a full path ) $dllExistsOnDisk = $dllSearchPaths | Where-Object { Test-Path $_ } | Select-Object -First 1 if (-not $dllExistsOnDisk) { # Stale LSA entry — DLL is gone but registry still references it $scFindings.Add([PSCustomObject]@{ ServiceName = 'None (stale LSA entry)' DisplayName = "Stale LSA Auth Package reference: $pkg" State = 'DLL not found on disk — registry reference only' StartMode = 'References removed auth package' BinaryPath = "LSA registry entry: $pkg (DLL NOT FOUND ON DISK)" InstallPath = 'N/A' Version = 'N/A' HasUninstaller = 'No — registry cleanup only' Sev = 'LOW' Removal = "# The ScreenConnect DLL is already gone from disk.`n# Only the LSA registry reference remains. Clean it up:`n`n`$lsaKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa'`n`n# Remove from Security Packages:`n`$sp = (Get-ItemProperty `$lsaKey -Name 'Security Packages' -ErrorAction SilentlyContinue).('Security Packages')`nif (`$sp) {`n Set-ItemProperty `$lsaKey -Name 'Security Packages' -Value (`$sp | Where-Object { `$_ -notlike '*ScreenConnect*' -and `$_ -notlike '*ConnectWise*' })`n}`n`n# Remove from Authentication Packages:`n`$ap = (Get-ItemProperty `$lsaKey -Name 'Authentication Packages' -ErrorAction SilentlyContinue).('Authentication Packages')`nif (`$ap) {`n Set-ItemProperty `$lsaKey -Name 'Authentication Packages' -Value (`$ap | Where-Object { `$_ -notlike '*ScreenConnect*' -and `$_ -notlike '*ConnectWise*' })`n}`n`nWrite-Host 'Stale LSA entries removed. No reboot required (DLL already gone).'" }) continue } $scFindings.Add([PSCustomObject]@{ ServiceName = 'None (SSP orphan)' DisplayName = "Orphaned LSA Auth Package: $pkg" State = 'No service — DLL registered in lsass only' StartMode = 'Loads at boot via LSA' BinaryPath = "LSA Security Packages registry: $pkg" InstallPath = 'N/A' Version = 'Unknown' HasUninstaller = 'No — registry edit + reboot required' Sev = 'HIGH' Removal = "# ScreenConnect left its Authentication Package registered in LSA.`n# lsass.exe holds the DLL open, which is why the folder cannot be deleted.`n`n# STEP 1 — Remove from LSA Security Packages:`n`$lsaKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa'`n`$current = (Get-ItemProperty `$lsaKey -Name 'Security Packages').('Security Packages')`n`$cleaned = `$current | Where-Object { `$_ -notlike '*ScreenConnect*' -and `$_ -notlike '*ConnectWise*' }`nSet-ItemProperty `$lsaKey -Name 'Security Packages' -Value `$cleaned`n`n# STEP 2 — Remove from Authentication Packages:`n`$authPkgs = (Get-ItemProperty `$lsaKey -Name 'Authentication Packages' -ErrorAction SilentlyContinue).('Authentication Packages')`nif (`$authPkgs) {`n `$authCleaned = `$authPkgs | Where-Object { `$_ -notlike '*ScreenConnect*' -and `$_ -notlike '*ConnectWise*' }`n Set-ItemProperty `$lsaKey -Name 'Authentication Packages' -Value `$authCleaned`n}`n`n# STEP 3 — Schedule the DLL for deletion on reboot:`n`$sig = '[DllImport(""kernel32.dll"",SetLastError=true)] public static extern bool MoveFileEx(string src, string dst, uint flags);'`n`$t = Add-Type -MemberDefinition `$sig -Name MFE2 -Namespace Win32 -PassThru -ErrorAction SilentlyContinue`nif (`$t) { `$t::MoveFileEx('$pkg', `$null, 4) | Out-Null }`n`n# STEP 4 — REBOOT, then delete any remaining ScreenConnect folders.`nWrite-Host 'Reboot required to release the DLL lock.'" }) } } catch { } # ── Step E: Check for portable (no-service) installs via known paths ───── foreach ($scPath in $scKnownPaths) { if (-not (Test-Path $scPath)) { continue } # Only add if not already captured via service scan $alreadyCaptured = $scFindings | Where-Object { $_.InstallPath -like "*$scPath*" } if ($alreadyCaptured) { continue } $exes = Get-ChildItem $scPath -Filter '*.exe' -Recurse -ErrorAction SilentlyContinue | Select-Object -First 3 $exeList = ($exes | ForEach-Object { $_.Name }) -join ', ' $scFindings.Add([PSCustomObject]@{ ServiceName = 'None (portable/no service)' DisplayName = 'ScreenConnect portable install' State = 'No service installed' StartMode = 'N/A' BinaryPath = $scPath InstallPath = $scPath Version = 'Unknown (no registry entry)' HasUninstaller = 'No - folder delete required' Sev = 'HIGH' Removal = "# Portable install — no uninstaller. Kill any running processes first:`nGet-Process | Where-Object { `$_.Path -like '*ScreenConnect*' -or `$_.Path -like '$scPath*' } | Stop-Process -Force`n`n# Then delete the entire folder:`nRemove-Item -Path '$scPath' -Recurse -Force`n`n# Remove leftover AppData too:`nRemove-Item -Path `"`$env:APPDATA\ScreenConnect`" -Recurse -Force -ErrorAction SilentlyContinue`nRemove-Item -Path `"`$env:LOCALAPPDATA\ScreenConnect`" -Recurse -Force -ErrorAction SilentlyContinue`n`n# Remove firewall rules:`nGet-NetFirewallRule | Where-Object { `$_.DisplayName -like '*ScreenConnect*' -or `$_.DisplayName -like '*ConnectWise*' } | Remove-NetFirewallRule" }) } } catch { Write-Host " ScreenConnect scan error: $_" -ForegroundColor Red } Write-Host " Found: $($scFindings.Count) ScreenConnect/ConnectWise installation(s)" -ForegroundColor Gray # ── Scan 9: AppData / user-profile executables ─────────────────────────────── # Scammers hide remote-access tools deep in AppData (ScreenConnect in particular # gets buried many sub-folders down, e.g. ClickOnce-style \Apps\2.0\\... ). # A generic "flag every exe that's deep" rule produces a false-positive storm # because legitimate apps (browsers, Electron apps) also bury signed exes. So we # flag, at ANY depth: # • catalogued remote tools (by exe name / install path / signer) -> HIGH # • UNSIGNED exes whose name matches a remote-access keyword -> MEDIUM # • UNSIGNED exes buried deep in AppData (legit deep exes are signed) -> LOW # Microsoft-signed and benign signed apps are always ignored. The scan recurses # the full tree, so a buried ScreenConnect is found no matter how deep it sits. Write-Host " [9/10] Deep-scanning user AppData folders (this is the slow one)..." -ForegroundColor Yellow $appDataFindings = New-Object 'System.Collections.Generic.List[object]' # Catalog exe base names, for a cheap pre-filter before the costly signature check $catalogExe = @{} foreach ($t in $RemoteTools) { foreach ($e in $t.Exe) { if ($e) { $catalogExe[$e] = $true } } } try { $userProfiles = New-Object 'System.Collections.Generic.List[string]' $usersRoot = Split-Path $env:USERPROFILE -Parent # e.g. C:\Users if (Test-Path $usersRoot) { Get-ChildItem $usersRoot -Directory -ErrorAction SilentlyContinue | ForEach-Object { foreach ($sub in @('AppData\Local','AppData\Roaming')) { $p = Join-Path $_.FullName $sub if (Test-Path $p) { $userProfiles.Add($p) } } } } foreach ($adPath in ($userProfiles | Sort-Object -Unique)) { $rootDepth = ($adPath -split '\\').Count Get-ChildItem -Path $adPath -Recurse -Filter '*.exe' -Force -ErrorAction SilentlyContinue | ForEach-Object { $exeName = $_.Name.ToLower() $base = $exeName -replace '\.exe$','' $exePath = $_.FullName # Cheap pre-filter: catalog exe names, remote keywords, or deeply buried exes $kw = $ratKeywords | Where-Object { $base -like "*$_*" } | Select-Object -First 1 $catalogN = $catalogExe.ContainsKey($base) $depth = ($exePath -split '\\').Count - $rootDepth $deep = $depth -ge 5 # buried many sub-folders below AppData\Local|Roaming if (-not $kw -and -not $catalogN -and -not $deep) { return } # Confirm with signature + catalog (expensive, runs only on candidates) $trust = Get-FileTrust $exePath if ($trust.IsMicrosoft) { return } if (Get-WhitelistMatch $exeName) { return } if (Get-WhitelistMatch $exePath) { return } # e.g. ...\Microsoft\Teams\..., ...\OneDrive\... $tool = Find-RemoteTool -Exe $exeName -Path $exePath -Signer $trust.SignerName if ($tool) { $sev = 'HIGH'; $reason = "Remote access tool in AppData: $($tool.Name) — $($tool.Class)" } elseif (-not $trust.Signed -and $kw) { $sev = 'MEDIUM'; $reason = "Unsigned executable in AppData whose name matches '$kw'" } elseif (-not $trust.Signed -and $deep -and -not (Test-TrustedVendorPath $exePath)) { $sev = 'LOW'; $reason = "Unsigned executable buried $depth folders deep in AppData — a common hiding spot for ScreenConnect and similar tools" } else { return # signed (non-Microsoft) without a catalog/keyword hit — too weak to flag } $signerNote = if ($trust.Signed) { "Signed by: $($trust.SignerName)" } else { 'UNSIGNED' } $appDataFindings.Add([PSCustomObject]@{ Path = $exePath Company = if ($trust.Company) { $trust.Company } else { 'Unknown' } Product = if ($trust.Product) { $trust.Product } else { 'Unknown' } Sev = $sev Reason = "$reason. $signerNote." Fix = "Terminate if running: Get-Process | Where-Object { `$_.Path -eq '$exePath' } | Stop-Process -Force`nThen delete: Remove-Item -Path '$exePath' -Force`nIf inside a named folder, delete the whole folder." }) } } } catch { Write-Host " AppData scan error: $_" -ForegroundColor Red } Write-Host " Found: $($appDataFindings.Count)" -ForegroundColor Gray # ── Scan 9b: Startup-folder persistence ─────────────────────────────────────── Write-Host " [9b] Checking Startup folders..." -ForegroundColor Yellow $suspStartup = New-Object 'System.Collections.Generic.List[object]' try { $startupDirs = New-Object 'System.Collections.Generic.List[string]' $startupDirs.Add("$env:ProgramData\Microsoft\Windows\Start Menu\Programs\Startup") $usersRoot2 = Split-Path $env:USERPROFILE -Parent if (Test-Path $usersRoot2) { Get-ChildItem $usersRoot2 -Directory -ErrorAction SilentlyContinue | ForEach-Object { $sp = Join-Path $_.FullName 'AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup' if (Test-Path $sp) { $startupDirs.Add($sp) } } } $wsh = $null; try { $wsh = New-Object -ComObject WScript.Shell } catch {} foreach ($sd in ($startupDirs | Sort-Object -Unique)) { Get-ChildItem -Path $sd -File -Force -ErrorAction SilentlyContinue | ForEach-Object { $item = $_.FullName; $name = $_.Name; $ext = $_.Extension.ToLower() if ($ext -eq '.ini') { return } # desktop.ini etc. $target = $item if ($ext -eq '.lnk' -and $wsh) { try { $t = $wsh.CreateShortcut($item).TargetPath; if ($t) { $target = $t } } catch {} } $kw = $ratKeywords | Where-Object { $target -like "*$_*" -or $name -like "*$_*" } | Select-Object -First 1 $trust = Get-FileTrust $target $tool = Find-RemoteTool -Exe (Split-Path $target -Leaf) -Path $target -Signer $trust.SignerName if (-not $tool -and -not $kw) { if ($trust.IsMicrosoft) { return } if (Get-WhitelistMatch $target) { return } if (Get-WhitelistMatch $name) { return } if ($trust.Signed) { return } # signed-publisher startup item is normal } $isScript = @('.bat','.cmd','.vbs','.js','.ps1','.hta','.scr') -contains $ext if ($tool -or $kw -or $isScript -or -not $trust.Signed) { $sev = if ($tool -or $kw) { 'HIGH' } else { 'MEDIUM' } $reason = if ($tool) { "Startup item launches a remote tool: $($tool.Name)" } elseif ($kw) { "Startup item references a remote tool: $kw" } elseif ($isScript) { "Startup runs a script ($ext) at every login — unusual for normal apps" } else { 'Unsigned program set to run at every login' } $suspStartup.Add([PSCustomObject]@{ Name=$name; Path=$item; Target=$target; Sev=$sev; Reason=$reason Fix="Remove the startup entry:`nRemove-Item -LiteralPath '$item' -Force`nIf the target is malicious, delete it too:`nRemove-Item -LiteralPath '$target' -Force" }) } } } } catch { Write-Host " Startup scan error: $_" -ForegroundColor Red } Write-Host " Found: $($suspStartup.Count)" -ForegroundColor Gray # ── Scan 9c: Microsoft Defender status & exclusions ─────────────────────────── # Attackers routinely disable real-time protection and add scan exclusions so # their remote-access tool is never inspected. Surfacing these is high-value. Write-Host " [9c] Checking Microsoft Defender status & exclusions..." -ForegroundColor Yellow $defenderFindings = New-Object 'System.Collections.Generic.List[object]' try { if (Get-Command Get-MpPreference -ErrorAction SilentlyContinue) { $mp = Get-MpPreference -ErrorAction SilentlyContinue $ms = $null; try { $ms = Get-MpComputerStatus -ErrorAction SilentlyContinue } catch {} if ($ms) { if ($ms.PSObject.Properties['RealTimeProtectionEnabled'] -and -not $ms.RealTimeProtectionEnabled) { $defenderFindings.Add([PSCustomObject]@{ Item='Real-time protection DISABLED'; Detail='Defender real-time protection is off — a common step right after a RAT is installed.'; Sev='HIGH'; Fix='Set-MpPreference -DisableRealtimeMonitoring $false' }) } if ($ms.PSObject.Properties['IsTamperProtected'] -and -not $ms.IsTamperProtected) { $defenderFindings.Add([PSCustomObject]@{ Item='Tamper Protection OFF'; Detail='Tamper Protection is off, letting malware change Defender settings.'; Sev='MEDIUM'; Fix='Turn on Tamper Protection: Windows Security > Virus & threat protection > Manage settings.' }) } } if ($mp) { foreach ($x in @($mp.ExclusionPath)) { if ($x) { $isBad=(Test-BadPath $x) -or ($x -match '^[A-Za-z]:\\?$') -or ($x -eq '*'); $defenderFindings.Add([PSCustomObject]@{ Item="Scan exclusion — path: $x"; Detail='A folder/file excluded from antivirus scanning. Operators add exclusions so their tool is never scanned.'; Sev=$(if($isBad){'HIGH'}else{'MEDIUM'}); Fix="Remove-MpPreference -ExclusionPath '$x'" }) } } foreach ($x in @($mp.ExclusionProcess)) { if ($x) { $defenderFindings.Add([PSCustomObject]@{ Item="Scan exclusion — process: $x"; Detail='A process excluded from antivirus scanning.'; Sev='MEDIUM'; Fix="Remove-MpPreference -ExclusionProcess '$x'" }) } } foreach ($x in @($mp.ExclusionExtension)) { if ($x) { $defenderFindings.Add([PSCustomObject]@{ Item="Scan exclusion — extension: $x"; Detail='A file extension excluded from antivirus scanning.'; Sev='MEDIUM'; Fix="Remove-MpPreference -ExclusionExtension '$x'" }) } } } } else { Write-Host " (Get-MpPreference unavailable; a third-party AV may be in use)" -ForegroundColor Gray } } catch { Write-Host " Defender scan error: $_" -ForegroundColor Red } Write-Host " Found: $($defenderFindings.Count)" -ForegroundColor Gray # ── Scan 9d: WMI event-subscription persistence ─────────────────────────────── # Fileless persistence: an __EventFilter triggers a CommandLine/ActiveScript # consumer at boot/logon. Used by RATs to survive reboots without a file on disk. Write-Host " [9d] Checking WMI persistence (event subscriptions)..." -ForegroundColor Yellow $wmiFindings = New-Object 'System.Collections.Generic.List[object]' try { $consumers = Get-WmiObject -Namespace 'root\subscription' -Class __EventConsumer -ErrorAction SilentlyContinue foreach ($c in $consumers) { $action = '' if ($c.CommandLineTemplate) { $action = [string]$c.CommandLineTemplate } elseif ($c.ExecutablePath) { $action = [string]$c.ExecutablePath } elseif ($c.ScriptText) { $st = [string]$c.ScriptText; $action = 'Script: ' + ($st.Substring(0,[Math]::Min(220,$st.Length)) -replace '\s+',' ') } else { continue } # log-only consumers don't execute anything if ($c.Name -match 'SCM Event|Microsoft|SCCM|CCM|BVTConsumer|TSEvent') { continue } # known defaults $kw = $ratKeywords | Where-Object { $action -like "*$_*" } | Select-Object -First 1 $bad = (Test-BadPath $action) -or ($action -match 'powershell|cmd\.exe|wscript|cscript|mshta|rundll32|regsvr32|certutil|bitsadmin|-enc |frombase64|downloadstring|iex') $sev = if ($kw -or $bad) { 'HIGH' } else { 'MEDIUM' } $wmiFindings.Add([PSCustomObject]@{ Name=$c.Name; Type=$c.__CLASS; Action=$action; Sev=$sev Fix="Inspect, then remove the subscription (run in Admin PowerShell):`nGet-WmiObject -Namespace root\subscription -Class __EventConsumer | ? Name -eq '$($c.Name)' | Remove-WmiObject`nGet-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding | ? { `$_.Consumer -match '$($c.Name)' } | Remove-WmiObject`n# Then remove the matching __EventFilter." }) } } catch { Write-Host " WMI persistence scan error: $_" -ForegroundColor Red } Write-Host " Found: $($wmiFindings.Count)" -ForegroundColor Gray # ── Scan 10: Suspicious outbound/inbound network traffic ───────────────────── Write-Host " [11/11] Scanning for suspicious network traffic patterns..." -ForegroundColor Yellow $suspTraffic = New-Object 'System.Collections.Generic.List[object]' # Known-safe process names for outbound connections (reduces noise) $trustedProcesses = @( 'svchost','lsass','wininit','services','spoolsv','explorer', 'searchindexer','onedrive','dropbox','teams','slack','zoom', 'chrome','firefox','msedge','opera','brave','iexplore', 'outlook','thunderbird','skype','discord','spotify', 'windows defender','mssense','msmpeng','antimalware' ) # IPs/ranges that are always safe to ignore $trustedCIDRs = @('10.','172.16.','172.17.','172.18.','172.19.', '172.20.','172.21.','172.22.','172.23.','172.24.', '172.25.','172.26.','172.27.','172.28.','172.29.', '172.30.','172.31.','192.168.','127.','169.254.') function Is-TrustedIP([string]$ip) { foreach ($prefix in $trustedCIDRs) { if ($ip.StartsWith($prefix)) { return $true } } return $false } try { $netCmdAvail = $null -ne (Get-Command Get-NetTCPConnection -ErrorAction SilentlyContinue) if ($netCmdAvail) { $allConns2 = Get-NetTCPConnection -ErrorAction SilentlyContinue | Where-Object { $_.State -eq 'Established' -and $_.RemoteAddress -and $_.RemoteAddress -ne '0.0.0.0' -and $_.RemoteAddress -ne '::' -and $_.RemoteAddress -ne '::1' -and $_.RemoteAddress -notlike '127.*' } # Group by owning process to detect potential beaconing (many connections from same process to different IPs) $procGroups = $allConns2 | Group-Object OwningProcess foreach ($grp in $procGroups) { $pid2 = $grp.Name $conns = $grp.Group $proc = ($allProcs | Where-Object { $_.Id -eq $pid2 } | Select-Object -First 1) $pname = if ($proc) { $proc.Name } else { 'Unknown' } $ppath = if ($proc) { $proc.Path } else { '' } # Fallback: resolve the owning process name/path via WMI when Get-Process missed it if (($pname -eq 'Unknown' -or -not $ppath) -and "$pid2" -match '^\d+$') { $wp = Get-CimInstance Win32_Process -Filter "ProcessId=$pid2" -ErrorAction SilentlyContinue if ($wp) { if ($pname -eq 'Unknown' -and $wp.Name) { $pname = ($wp.Name -replace '\.exe$','') } if (-not $ppath -and $wp.ExecutablePath) { $ppath = $wp.ExecutablePath } } } # Skip silently whitelisted processes $wl = Get-WhitelistMatch $pname if (-not $wl) { $wl = Get-WhitelistMatch $ppath } if ($wl) { continue } # Skip well-known trusted system/browser processes $isTrusted = $trustedProcesses | Where-Object { $pname -like "*$_*" } | Select-Object -First 1 if ($isTrusted) { continue } # Skip Microsoft-signed binaries — legitimate Windows components $ptrust = Get-FileTrust $ppath if ($ptrust.IsMicrosoft) { continue } # Filter to only external IPs $extConns = $conns | Where-Object { -not (Is-TrustedIP $_.RemoteAddress) } if ($extConns.Count -eq 0) { continue } # Flag 1: Process running from AppData/Temp with ANY external connection $suspPath = $ppath -and ( $ppath -like '*\AppData\*' -or $ppath -like '*\Temp\*' -or $ppath -like '*\Users\Public\*' ) # Flag 2: Process has unusually many simultaneous external connections (possible beaconing/exfil) $manyConns = $extConns.Count -ge 5 # Flag 3: Connecting to non-standard high ports that aren't in the known RAT port list # (exclude 80,443,8080,8443 which are normal web) $normalWebPorts = @(80,443,8080,8443,8888,8000) $weirdPortConns = $extConns | Where-Object { $_.RemotePort -notin $normalWebPorts -and -not $ratPorts.ContainsKey($_.RemotePort) -and $_.RemotePort -gt 1024 } $hasWeirdPorts = $weirdPortConns.Count -gt 0 # For validly signed (non-Microsoft) apps, only a bad image path is # noteworthy — high connection counts and odd ports are normal for # many legitimate signed apps and would otherwise be false positives. if ($ptrust.Signed) { $manyConns = $false; $hasWeirdPorts = $false } if ($suspPath -or $manyConns -or $hasWeirdPorts) { # Detailed, de-duplicated remote list, annotating any known remote/C2 ports $remoteList = $extConns | ForEach-Object { $rnote = $ratPorts[[int]$_.RemotePort] if ($rnote) { "$($_.RemoteAddress):$($_.RemotePort) [$rnote]" } else { "$($_.RemoteAddress):$($_.RemotePort)" } } | Select-Object -Unique $shown = @($remoteList | Select-Object -First 8) $remotes = $shown -join ', ' if ($remoteList.Count -gt 8) { $remotes += " (+$($remoteList.Count - 8) more)" } $reasons = @() if ($suspPath) { $reasons += "Process runs from a user-writable path (AppData/Temp/Public)" } if ($manyConns) { $reasons += "$($extConns.Count) simultaneous external connections (possible beaconing/exfil)" } if ($hasWeirdPorts){ $reasons += "Connecting to unusual high ports: $(($weirdPortConns | Select-Object -Unique -ExpandProperty RemotePort | Select-Object -First 5) -join ', ')" } $sev = if ($suspPath -and $manyConns) { 'HIGH' } elseif ($suspPath -or $manyConns) { 'MEDIUM' } else { 'LOW' } $signer = if ($ptrust.Signed) { $ptrust.SignerName } else { 'UNSIGNED' } $suspTraffic.Add([PSCustomObject]@{ Process = $pname PID = $pid2 Path = if ($ppath) { $ppath } else { 'N/A (could not resolve image path)' } Signer = $signer ExtConns = $extConns.Count Remotes = $remotes Sev = $sev Reason = $reasons -join '; ' Fix = "This traffic is coming from: $pname (PID $pid2)`nImage: $(if($ppath){$ppath}else{'unknown'})`nKill if suspicious: Stop-Process -Id $pid2 -Force`nSee live per-process connections: Get-NetTCPConnection -OwningProcess $pid2`nOr use TCPView (Sysinternals)." }) } } } else { Write-Host " (Get-NetTCPConnection unavailable; skipping advanced traffic analysis)" -ForegroundColor Gray } } catch { Write-Host " Traffic scan error: $_" -ForegroundColor Red } Write-Host " Found: $($suspTraffic.Count)" -ForegroundColor Gray # ── Tally findings ──────────────────────────────────────────────────────────── $allFindings = New-Object 'System.Collections.Generic.List[string]' foreach ($p in $suspProcs) { $allFindings.Add("PROCESS: $($p.Name) (PID $($p.PID)) - $($p.Sev) - $($p.Reason)") } foreach ($s in $suspSvcs) { $allFindings.Add("SERVICE: $($s.Display) [$($s.State)] - $($s.Sev)") } foreach ($c in $suspConns) { $allFindings.Add("NETWORK: $($c.Process) to $($c.Remote) [$($c.Note)] - $($c.Sev)") } foreach ($t in $suspTasks) { $allFindings.Add("TASK: $($t.Name) runs $($t.Exe) - $($t.Sev)") } foreach ($r in $suspReg) { $allFindings.Add("REGISTRY: $($r.Name) = $($r.Value) - $($r.Sev)") } foreach ($f in $suspFolders) { $allFindings.Add("FOLDER: $($f.Path) - HIGH") } foreach ($sc in $scFindings) { $scNote = if ($sc.ServiceName -like '*SSP orphan*') { 'SCREENCONNECT SSP DLL ORPHAN (locks folder, reboot needed)' } else { "SCREENCONNECT: $($sc.DisplayName) [$($sc.State)]" } $allFindings.Add("$scNote - $($sc.Sev)") } foreach ($a in $appDataFindings) { $allFindings.Add("APPDATA: $($a.Path) - $($a.Sev) - $($a.Reason)") } foreach ($su in $suspStartup) { $allFindings.Add("STARTUP: $($su.Name) -> $($su.Target) - $($su.Sev)") } foreach ($df in $defenderFindings){ $allFindings.Add("DEFENDER: $($df.Item) - $($df.Sev)") } foreach ($w in $wmiFindings) { $allFindings.Add("WMI PERSISTENCE: $($w.Name) - $($w.Sev)") } foreach ($tr in $suspTraffic) { $allFindings.Add("TRAFFIC: $($tr.Process) (PID $($tr.PID)) - $($tr.Sev) - $($tr.Reason)") } if ($rdpEnabled) { $allFindings.Add("RDP: Enabled on port $rdpPort, NLA=$(if($nlaOn){'YES - secure'}else{'NO - insecure'})") } $total = $allFindings.Count $risk = if ($total -eq 0) { 'CLEAN' } elseif ($total -le 3) { 'LOW' } elseif ($total -le 8) { 'MEDIUM' } else { 'HIGH' } $riskColor = switch ($risk) { 'CLEAN' {'#16a34a'} 'LOW' {'#d97706'} 'MEDIUM' {'#ea580c'} 'HIGH' {'#dc2626'} } $riskBg = switch ($risk) { 'CLEAN' {'#f0fdf4'} 'LOW' {'#fffbeb'} 'MEDIUM' {'#fff7ed'} 'HIGH' {'#fef2f2'} } $riskMsg = switch ($risk) { 'CLEAN' { 'No suspicious remote access activity detected on this machine.' } 'LOW' { 'A small number of items flagged. Review each one below.' } 'MEDIUM' { 'Several suspicious items found. Investigate and remediate promptly.' } 'HIGH' { 'HIGH RISK: Multiple remote access indicators found. Take immediate action.' } } Write-Host "" Write-Host " Total findings: $total | Risk: $risk" -ForegroundColor Cyan Write-Host " Building remediation window..." -ForegroundColor Yellow # ── Build WPF findings list ─────────────────────────────────────────────────── # Each entry: Category, Severity, Title, Detail, FixScript $guiFindings = New-Object 'System.Collections.Generic.List[object]' foreach ($p in $suspProcs) { $guiFindings.Add([PSCustomObject]@{ Category = 'Process' Sev = $p.Sev Title = "$($p.Name) (PID $($p.PID))" Detail = "$($p.Reason)`nPath: $($p.Path)`nCompany: $($p.Company)" FixScript = $p.Fix FixLabel = 'Kill Process' }) } foreach ($s in $suspSvcs) { $guiFindings.Add([PSCustomObject]@{ Category = 'Service' Sev = $s.Sev Title = "$($s.Display) [$($s.State)]" Detail = $s.Reason FixScript = $s.Fix FixLabel = 'Stop & Disable' }) } foreach ($c in $suspConns) { $cpath = ($allProcs | Where-Object { $_.Id -eq $c.PID } | Select-Object -First 1).Path $guiFindings.Add([PSCustomObject]@{ Category = 'Network' Sev = $c.Sev Title = "$($c.Process) (PID $($c.PID)) → $($c.Remote)" Detail = "From process: $($c.Process) (PID $($c.PID))`nImage path: $(if($cpath){$cpath}else{'unknown'})`nLocal: $($c.Local)`nRemote: $($c.Remote) [$($c.Note)]`nState: $($c.State)" FixScript = $c.Fix FixLabel = 'Kill & Block' }) } foreach ($t in $suspTasks) { $guiFindings.Add([PSCustomObject]@{ Category = 'Task' Sev = $t.Sev Title = $t.Name Detail = "$($t.Reason)`nRuns: $($t.Exe)" FixScript = $t.Fix FixLabel = 'Remove Task' }) } foreach ($r in $suspReg) { $guiFindings.Add([PSCustomObject]@{ Category = 'Registry' Sev = $r.Sev Title = $r.Name Detail = "$($r.Reason)`nValue: $($r.Value)" FixScript = $r.Fix FixLabel = 'Delete Key' }) } foreach ($f in $suspFolders) { $guiFindings.Add([PSCustomObject]@{ Category = 'Folder' Sev = $f.Sev Title = $f.Path Detail = $f.Reason FixScript = $f.Fix FixLabel = 'Delete Folder' }) } foreach ($sc in $scFindings) { $label = if ($sc.Sev -eq 'LOW') { 'Clean Registry' } else { 'Full Removal' } $guiFindings.Add([PSCustomObject]@{ Category = 'ScreenConnect' Sev = $sc.Sev Title = $sc.DisplayName Detail = "Service: $($sc.ServiceName) | State: $($sc.State)`nInstall path: $($sc.InstallPath)`nUninstaller: $($sc.HasUninstaller)" FixScript = $sc.Removal FixLabel = $label }) } foreach ($a in $appDataFindings) { $guiFindings.Add([PSCustomObject]@{ Category = 'AppData' Sev = $a.Sev Title = Split-Path $a.Path -Leaf Detail = "$($a.Reason)`nPath: $($a.Path)" FixScript = $a.Fix FixLabel = 'Delete File' }) } foreach ($su in $suspStartup) { $guiFindings.Add([PSCustomObject]@{ Category = 'Startup' Sev = $su.Sev Title = $su.Name Detail = "$($su.Reason)`nStartup entry: $($su.Path)`nLaunches: $($su.Target)" FixScript = $su.Fix FixLabel = 'Remove Startup' }) } foreach ($df in $defenderFindings) { $guiFindings.Add([PSCustomObject]@{ Category = 'Defender' Sev = $df.Sev Title = $df.Item Detail = $df.Detail FixScript = $df.Fix FixLabel = 'Fix Setting' }) } foreach ($w in $wmiFindings) { $guiFindings.Add([PSCustomObject]@{ Category = 'WMI' Sev = $w.Sev Title = "WMI subscription: $($w.Name)" Detail = "Type: $($w.Type)`nRuns: $($w.Action)" FixScript = $w.Fix FixLabel = 'Remove' }) } foreach ($tr in $suspTraffic) { $guiFindings.Add([PSCustomObject]@{ Category = 'Traffic' Sev = $tr.Sev Title = "$($tr.Process) (PID $($tr.PID)) — $($tr.ExtConns) external connection$(if($tr.ExtConns -ne 1){'s'})" Detail = "From process: $($tr.Process) (PID $($tr.PID))`nImage path: $($tr.Path)`nSigner: $($tr.Signer)`nWhy flagged: $($tr.Reason)`nConnecting to: $($tr.Remotes)" FixScript = $tr.Fix FixLabel = 'Kill Process' }) } if ($rdpEnabled) { $rdpFix = "# Disable RDP entirely:`nSet-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name fDenyTSConnections -Value 1`nDisable-NetFirewallRule -DisplayGroup 'Remote Desktop'`nWrite-Host 'RDP disabled.'" $guiFindings.Add([PSCustomObject]@{ Category = 'RDP' Sev = if (-not $nlaOn) { 'HIGH' } else { 'MEDIUM' } Title = "RDP Enabled on port $rdpPort $(if(-not $nlaOn){'— NLA OFF (insecure)'}else{'— NLA ON'})" Detail = "Network Level Authentication: $(if($nlaOn){'Enabled (secure)'}else{'DISABLED — anyone can attempt login without credentials first'})" FixScript = $rdpFix FixLabel = 'Disable RDP' }) } # ── Launch WPF GUI ──────────────────────────────────────────────────────────── Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName PresentationCore Add-Type -AssemblyName WindowsBase $sevColor = @{ 'HIGH' = '#dc2626' 'MEDIUM' = '#ea580c' 'LOW' = '#2563eb' 'CLEAN' = '#16a34a' } $catColor = @{ 'Process' = '#7c3aed' 'Service' = '#0369a1' 'Network' = '#b45309' 'Task' = '#0f766e' 'Registry' = '#be185d' 'Folder' = '#92400e' 'ScreenConnect' = '#dc2626' 'AppData' = '#b91c1c' 'Startup' = '#1d4ed8' 'Defender' = '#15803d' 'WMI' = '#6d28d9' 'Traffic' = '#c2410c' 'RDP' = '#9f1239' } $riskWindowColor = @{ 'CLEAN' = '#16a34a' 'LOW' = '#d97706' 'MEDIUM' = '#ea580c' 'HIGH' = '#dc2626' } # Helper: run a fix script in a new elevated powershell window function Invoke-Fix { param([string]$script, [string]$title) $confirm = [System.Windows.MessageBox]::Show( "Run fix: $title`n`nThis will execute PowerShell commands with admin rights.`nContinue?", "Confirm Fix", [System.Windows.MessageBoxButton]::YesNo, [System.Windows.MessageBoxImage]::Warning ) if ($confirm -ne 'Yes') { return } # Write script to temp file and run in a new visible PS window so user can see output $tmp = [System.IO.Path]::GetTempFileName() + '.ps1' $wrapped = @" Write-Host '=== Remote Access Audit — Fix: $title ===' -ForegroundColor Cyan Write-Host '' try { $script Write-Host '' Write-Host 'Fix completed.' -ForegroundColor Green } catch { Write-Host "Error: `$_" -ForegroundColor Red } Write-Host '' Write-Host 'Press Enter to close this window...' -ForegroundColor Gray Read-Host "@ [System.IO.File]::WriteAllText($tmp, $wrapped, [System.Text.Encoding]::UTF8) Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$tmp`"" -Verb RunAs } # ── Build the window XAML ───────────────────────────────────────────────────── [xml]$xaml = @"