[SHEHZADE//ABDULLAH ANSARI]
Red Team Consultant @ Google | MS in Cybersecurity @ NYU
● Notice //GitHub (@shehzade) is temporarily suspended pending an automated false-positive review. All tools are hosted on this Code Mirror and available on request (contact); GitHub links will return once reinstated.
PUBLIC MIRRORGo v1.0
Multi-protocol network egress assessment and DLP verification framework featuring automated L4 sweeps, corporate proxy traversal, and covert protocol smuggling across 20+ channels.
$
git clone https://shehzade.io/repos/contraband.git
Copy
Source ZIP (1.93 MB)
279 lines · 7.6 KB
| 1 | package client |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "fmt" |
| 6 | "os" |
| 7 | "strconv" |
| 8 | "strings" |
| 9 | "time" |
| 10 | |
| 11 | _ "github.com/shehzade/contraband/pkg/client/exfil/cloud" |
| 12 | _ "github.com/shehzade/contraband/pkg/client/exfil/covert" |
| 13 | _ "github.com/shehzade/contraband/pkg/client/exfil/dns" |
| 14 | _ "github.com/shehzade/contraband/pkg/client/exfil/file" |
| 15 | _ "github.com/shehzade/contraband/pkg/client/exfil/mail" |
| 16 | _ "github.com/shehzade/contraband/pkg/client/exfil/smb" |
| 17 | _ "github.com/shehzade/contraband/pkg/client/exfil/web" |
| 18 | "github.com/shehzade/contraband/pkg/common" |
| 19 | "github.com/shehzade/contraband/pkg/report" |
| 20 | ) |
| 21 | |
| 22 | // ClientConfig holds all CLI and programmatic configuration parameters for the client engine. |
| 23 | type ClientConfig struct { |
| 24 | Server string `json:"server"` |
| 25 | Ports string `json:"ports"` |
| 26 | Port int `json:"port,omitempty"` |
| 27 | Protocols string `json:"protocols"` |
| 28 | DLPPayloads string `json:"dlp_payloads"` |
| 29 | CarrierType string `json:"carrier_type"` |
| 30 | Size string `json:"size"` |
| 31 | ChunkSize string `json:"chunk_size"` |
| 32 | Threads int `json:"threads"` |
| 33 | Throughput string `json:"throughput"` |
| 34 | Cadence string `json:"cadence"` |
| 35 | IntervalMin string `json:"interval_min"` |
| 36 | IntervalMax string `json:"interval_max"` |
| 37 | Transforms string `json:"transforms"` |
| 38 | Passphrase string `json:"passphrase"` |
| 39 | Stego string `json:"stego"` |
| 40 | TLSFingerprint string `json:"tls_fingerprint"` |
| 41 | Proxy string `json:"proxy"` |
| 42 | ProxyAuth string `json:"proxy_auth"` |
| 43 | Concurrency int `json:"concurrency"` |
| 44 | RateLimit int `json:"rate_limit"` |
| 45 | OutputJSON string `json:"output_json"` |
| 46 | OutputHTML string `json:"output_html"` |
| 47 | OutputSARIF string `json:"output_sarif"` |
| 48 | SyncReport string `json:"sync_report"` |
| 49 | Zone string `json:"zone"` |
| 50 | NoColor bool `json:"no_color"` |
| 51 | Destroy bool `json:"destroy"` |
| 52 | SavePayload bool `json:"save_payload"` |
| 53 | EvidenceDir string `json:"evidence_dir"` |
| 54 | DryRun bool `json:"dry_run"` |
| 55 | Seed int64 `json:"seed"` |
| 56 | Verbose bool `json:"verbose"` |
| 57 | JSON bool `json:"json"` |
| 58 | } |
| 59 | |
| 60 | // DefaultClientConfig returns a ClientConfig populated with standard operational defaults. |
| 61 | func DefaultClientConfig() ClientConfig { |
| 62 | return ClientConfig{ |
| 63 | Server: "127.0.0.1", |
| 64 | Ports: "quick", |
| 65 | Port: 0, |
| 66 | Protocols: "all", |
| 67 | DLPPayloads: "all", |
| 68 | CarrierType: "csv", |
| 69 | Size: "1MB", |
| 70 | ChunkSize: "1MB", |
| 71 | Threads: 4, |
| 72 | Throughput: "unlimited", |
| 73 | Cadence: common.CadenceSteady, |
| 74 | IntervalMin: "500ms", |
| 75 | IntervalMax: "4s", |
| 76 | Transforms: "raw", |
| 77 | Stego: common.StegoNone, |
| 78 | TLSFingerprint: common.TLSFingerprintChrome, |
| 79 | Proxy: "auto", |
| 80 | ProxyAuth: "basic", |
| 81 | Concurrency: 100, |
| 82 | RateLimit: 500, |
| 83 | EvidenceDir: "evidence", |
| 84 | Zone: "corporate-default", |
| 85 | Seed: 0, |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | // DefaultPortForProtocol returns the standard port associated with a protocol identifier. |
| 90 | func DefaultPortForProtocol(proto string) int { |
| 91 | switch proto { |
| 92 | case common.ProtoHTTP1, "http1", "http", common.ProtoWebDAV: |
| 93 | return 80 |
| 94 | case common.ProtoHTTP2, "http2", "https", common.ProtoHTTP3, "http3", "quic", common.ProtoDoH, common.ProtoSMBQUIC, common.ProtoAWSS3, common.ProtoGCPGCS, common.ProtoAzureBlob: |
| 95 | return 443 |
| 96 | case common.ProtoWebSocket, "websocket", "ws": |
| 97 | return 80 |
| 98 | case common.ProtoGRPC, "grpc": |
| 99 | return 50051 |
| 100 | case common.ProtoDNSA, common.ProtoDNSTXT, "dns": |
| 101 | return 53 |
| 102 | case common.ProtoDoT, "dot": |
| 103 | return 853 |
| 104 | case common.ProtoFTP: |
| 105 | return 21 |
| 106 | case common.ProtoFTPS: |
| 107 | return 990 |
| 108 | case common.ProtoSFTP: |
| 109 | return 22 |
| 110 | case common.ProtoSMTP: |
| 111 | return 25 |
| 112 | case common.ProtoSMBFile, common.ProtoSMBHash, "smb": |
| 113 | return 445 |
| 114 | case common.ProtoICMPEcho, "icmp": |
| 115 | return 0 |
| 116 | case common.ProtoNTPTime, "ntp": |
| 117 | return 123 |
| 118 | case common.ProtoSyslog: |
| 119 | return 514 |
| 120 | case common.ProtoMQTT: |
| 121 | return 1883 |
| 122 | default: |
| 123 | return 80 |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | // ResolveProtocols expands a protocol string ("all", category, or list) into a slice of protocol names. |
| 128 | func ResolveProtocols(spec string) []string { |
| 129 | spec = strings.TrimSpace(strings.ToLower(spec)) |
| 130 | if spec == "" || spec == "all" { |
| 131 | var list []string |
| 132 | order := []string{ |
| 133 | common.CategoryWeb, |
| 134 | common.CategoryDNS, |
| 135 | common.CategoryFile, |
| 136 | common.CategoryMail, |
| 137 | common.CategorySMB, |
| 138 | common.CategoryCloud, |
| 139 | common.CategoryCovert, |
| 140 | } |
| 141 | for _, cat := range order { |
| 142 | list = append(list, common.CategoryMap[cat]...) |
| 143 | } |
| 144 | return list |
| 145 | } |
| 146 | |
| 147 | if protos, ok := common.CategoryMap[spec]; ok { |
| 148 | return protos |
| 149 | } |
| 150 | |
| 151 | parts := strings.Split(spec, ",") |
| 152 | var result []string |
| 153 | for _, p := range parts { |
| 154 | trimmed := strings.TrimSpace(p) |
| 155 | if trimmed != "" { |
| 156 | if protos, ok := common.CategoryMap[trimmed]; ok { |
| 157 | result = append(result, protos...) |
| 158 | } else { |
| 159 | result = append(result, trimmed) |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | return result |
| 164 | } |
| 165 | |
| 166 | // RunAssessment coordinates the full 7-stage egress assessment workflow. |
| 167 | func RunAssessment(ctx context.Context, cfg ClientConfig) (*report.AssessmentReport, error) { |
| 168 | logger := common.GetLogger() |
| 169 | startTime := time.Now().UTC() |
| 170 | |
| 171 | // Apply defaults |
| 172 | if cfg.Server == "" { |
| 173 | cfg.Server = "127.0.0.1" |
| 174 | } |
| 175 | if cfg.Port > 0 && (cfg.Ports == "" || cfg.Ports == "quick") { |
| 176 | cfg.Ports = strconv.Itoa(cfg.Port) |
| 177 | } |
| 178 | if cfg.Ports == "" { |
| 179 | cfg.Ports = "quick" |
| 180 | } |
| 181 | if cfg.Protocols == "" { |
| 182 | cfg.Protocols = "all" |
| 183 | } |
| 184 | if cfg.DLPPayloads == "" { |
| 185 | cfg.DLPPayloads = "all" |
| 186 | } |
| 187 | if cfg.CarrierType == "" { |
| 188 | cfg.CarrierType = "csv" |
| 189 | } |
| 190 | if cfg.Size == "" { |
| 191 | cfg.Size = "1MB" |
| 192 | } |
| 193 | if cfg.ChunkSize == "" { |
| 194 | cfg.ChunkSize = "1MB" |
| 195 | } |
| 196 | if cfg.Cadence == "" { |
| 197 | cfg.Cadence = common.CadenceSteady |
| 198 | } |
| 199 | if cfg.TLSFingerprint == "" { |
| 200 | cfg.TLSFingerprint = common.TLSFingerprintChrome |
| 201 | } |
| 202 | if cfg.Proxy == "" { |
| 203 | cfg.Proxy = "auto" |
| 204 | } |
| 205 | if cfg.EvidenceDir == "" { |
| 206 | cfg.EvidenceDir = "evidence" |
| 207 | } |
| 208 | if cfg.Zone == "" { |
| 209 | cfg.Zone = "corporate-default" |
| 210 | } |
| 211 | if cfg.Concurrency <= 0 { |
| 212 | cfg.Concurrency = 100 |
| 213 | } |
| 214 | if cfg.RateLimit < 0 { |
| 215 | cfg.RateLimit = 500 |
| 216 | } |
| 217 | |
| 218 | // If SavePayload is enabled, ensure evidence directory exists |
| 219 | if cfg.SavePayload { |
| 220 | if err := os.MkdirAll(cfg.EvidenceDir, 0755); err != nil { |
| 221 | return nil, fmt.Errorf("failed to create evidence directory %q: %w", cfg.EvidenceDir, err) |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | // Pre-flight dry-run validation |
| 226 | if cfg.DryRun { |
| 227 | logger.Info("[DRY-RUN] Pre-flight validation passed successfully. Exiting without transmission.") |
| 228 | return &report.AssessmentReport{ |
| 229 | TargetHost: cfg.Server, |
| 230 | GeneratedAt: startTime, |
| 231 | }, nil |
| 232 | } |
| 233 | |
| 234 | var allProbes []common.ProbeResult |
| 235 | |
| 236 | // STAGE 1: Layer 4 Egress Port Matrix Sweep |
| 237 | sweepProbes, err := runStage1Sweep(ctx, cfg, logger) |
| 238 | if err != nil { |
| 239 | return nil, fmt.Errorf("stage 1 sweep failed: %w", err) |
| 240 | } |
| 241 | allProbes = append(allProbes, sweepProbes...) |
| 242 | |
| 243 | // STAGE 2: Corporate Proxy Discovery & Traversal |
| 244 | fallbackEngine := runStage2Proxy(ctx, cfg, logger) |
| 245 | |
| 246 | // STAGE 3: Traffic Shaper & Cadence Pacing |
| 247 | shaper, cadenceEngine := runStage3Shaping(ctx, cfg, logger) |
| 248 | |
| 249 | // STAGE 4: Procedural Synthetic DLP Generation |
| 250 | s4, err := runStage4DLP(ctx, &cfg, logger) |
| 251 | if err != nil { |
| 252 | return nil, fmt.Errorf("stage 4 dlp synthesis failed: %w", err) |
| 253 | } |
| 254 | |
| 255 | // STAGE 5: Evasion Transforms & Steganography |
| 256 | pipeline, hasEvasion := runStage5Evasion(cfg, s4.EffectiveSeed) |
| 257 | |
| 258 | // STAGE 6: L7 Protocol Smuggling Matrix |
| 259 | exfilProbes, err := runStage6Exfil( |
| 260 | ctx, |
| 261 | cfg, |
| 262 | logger, |
| 263 | s4.TargetBytes, |
| 264 | s4.DLPConfig, |
| 265 | s4.ExpectedHash, |
| 266 | fallbackEngine, |
| 267 | shaper, |
| 268 | cadenceEngine, |
| 269 | pipeline, |
| 270 | hasEvasion, |
| 271 | ) |
| 272 | if err != nil { |
| 273 | return nil, fmt.Errorf("stage 6 exfiltration failed: %w", err) |
| 274 | } |
| 275 | allProbes = append(allProbes, exfilProbes...) |
| 276 | |
| 277 | // STAGE 7: Report Generation, Remediation, ANSI Table & Dashboard Sync |
| 278 | return runStage7Reporting(ctx, cfg, logger, allProbes, startTime) |
| 279 | } |