//go:build windows package vulnprobe import ( "encoding/json" "runtime" "strings" ) const windowsProbeScript = ` $ErrorActionPreference = 'SilentlyContinue' $out = [ordered]@{} # Hotfix + QuickFixEngineering (read-only patch inventory) $kbs = @() try { $hf = Get-HotFix | Sort-Object InstalledOn -Descending if ($hf) { $latest = $hf | Select-Object -First 1 if ($latest.InstalledOn) { $d = [datetime]$latest.InstalledOn $out.last_patch = $d.ToString('yyyy-MM-dd') $out.last_patch_days = [int]((Get-Date) - $d).TotalDays } $kbs += @($hf | ForEach-Object { $_.HotFixID }) } } catch {} try { $qfe = Get-CimInstance Win32_QuickFixEngineering -ErrorAction SilentlyContinue if ($qfe) { $kbs += @($qfe | ForEach-Object { $_.HotFixID }) } } catch { try { $qfe = Get-WmiObject Win32_QuickFixEngineering -ErrorAction SilentlyContinue if ($qfe) { $kbs += @($qfe | ForEach-Object { $_.HotFixID }) } } catch {} } $out.installed_kbs = @($kbs | Where-Object { $_ } | Select-Object -Unique) # Service surface (Get-Service) $exSvc = @(Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Name -like 'MSExchange*' -or $_.DisplayName -like '*Exchange*' }) $out.exchange_installed = ($exSvc.Count -gt 0 -or (Test-Path 'HKLM:\SOFTWARE\Microsoft\ExchangeServer')) try { $dc = (Get-CimInstance Win32_ComputerSystem).DomainRole -in 4,5 } catch { $dc = $false } $out.is_domain_controller = $dc $pulse = @(Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -match 'Pulse|Ivanti|Juniper Pulse' -or $_.Name -match 'Pulse' }) $out.pulse_present = ($pulse.Count -gt 0) $citrix = @( (Test-Path 'C:\Program Files\Citrix'), (Test-Path 'C:\Program Files (x86)\Citrix') ) | Where-Object { $_ } $out.citrix_present = ($citrix.Count -gt 0) $f5 = @(Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'bigip|f5' }) $out.f5_process = ($f5.Count -gt 0) $conf = @(Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Path -match 'atlassian|confluence|tomcat' -or $_.ProcessName -match 'confluence|tomcat' }) $out.confluence_like = ($conf.Count -gt 0) $me = @( (Test-Path 'C:\Program Files\ManageEngine'), (Test-Path 'C:\ManageEngine') ) | Where-Object { $_ } $out.manageengine_present = ($me.Count -gt 0) $forti = @(Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'forti' }) $out.forticlient = ($forti.Count -gt 0) $vmw = @(Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'vpxd|VMware' }) $out.vmware_serverish = ($vmw.Count -gt 0) try { $sp = Get-Service Spooler $out.spooler_running = ($sp.Status -eq 'Running') } catch { $out.spooler_running = $false } try { $sshd = Get-Service -Name sshd -ErrorAction SilentlyContinue $out.ssh_listening = ($sshd.Status -eq 'Running') } catch { $out.ssh_listening = $false } $log4j = @() $roots = @($env:ProgramFiles, ${env:ProgramFiles(x86)}, 'C:\ProgramData') | Where-Object { $_ -and (Test-Path $_) } foreach ($root in $roots) { $log4j += Get-ChildItem -Path $root -Filter 'log4j-core*.jar' -Recurse -Depth 3 -ErrorAction SilentlyContinue | Select-Object -First 5 -ExpandProperty FullName } $out.log4j_jars = @($log4j | Select-Object -Unique) $out | ConvertTo-Json -Compress -Depth 4 ` type windowsProbeResult struct { LastPatch string `json:"last_patch"` LastPatchDays int `json:"last_patch_days"` InstalledKBs []string `json:"installed_kbs"` ExchangeInstalled bool `json:"exchange_installed"` IsDomainController bool `json:"is_domain_controller"` PulsePresent bool `json:"pulse_present"` CitrixPresent bool `json:"citrix_present"` F5Process bool `json:"f5_process"` ConfluenceLike bool `json:"confluence_like"` ManageEnginePresent bool `json:"manageengine_present"` FortiClient bool `json:"forticlient"` VMwareServerish bool `json:"vmware_serverish"` SpoolerRunning bool `json:"spooler_running"` SSHListening bool `json:"ssh_listening"` Log4jJars []string `json:"log4j_jars"` } // ProbeHost gathers Windows LOTL recon inputs (Get-HotFix, Get-Service, WMI QFE). func ProbeHost(listeningPorts map[int]bool, osVersion string) HostContext { ctx := HostContext{ Platform: runtime.GOOS, OSVersion: osVersion, LastPatchDays: -1, ListeningPorts: listeningPorts, } out, err := HiddenExec( "powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", windowsProbeScript, ) if err != nil { ctx.ProbeError = err.Error() return ctx } raw := strings.TrimSpace(string(out)) if idx := strings.LastIndex(raw, "{"); idx > 0 { raw = raw[idx:] } var p windowsProbeResult if err := json.Unmarshal([]byte(raw), &p); err != nil { ctx.ProbeError = err.Error() return ctx } ctx.LastPatch = p.LastPatch ctx.LastPatchDays = p.LastPatchDays ctx.InstalledKBs = p.InstalledKBs ctx.ExchangeInstalled = p.ExchangeInstalled ctx.IsDomainController = p.IsDomainController ctx.PulsePresent = p.PulsePresent ctx.CitrixPresent = p.CitrixPresent ctx.F5Process = p.F5Process ctx.ConfluenceLike = p.ConfluenceLike ctx.ManageEnginePresent = p.ManageEnginePresent ctx.FortiClient = p.FortiClient ctx.VMwareServerish = p.VMwareServerish ctx.SpoolerRunning = p.SpoolerRunning ctx.SSHListening = p.SSHListening ctx.Log4jJars = p.Log4jJars return ctx } func linuxPackageFindings(_ HostContext) []VulnFinding { return nil }