[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)
209 lines · 6.0 KB
| 1 | package client |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "context" |
| 6 | "crypto/sha256" |
| 7 | "encoding/hex" |
| 8 | "fmt" |
| 9 | "io" |
| 10 | "os" |
| 11 | "path/filepath" |
| 12 | "strconv" |
| 13 | "time" |
| 14 | |
| 15 | "github.com/shehzade/contraband/pkg/client/dlp" |
| 16 | "github.com/shehzade/contraband/pkg/client/evasion" |
| 17 | "github.com/shehzade/contraband/pkg/client/exfil" |
| 18 | "github.com/shehzade/contraband/pkg/client/proxy" |
| 19 | "github.com/shehzade/contraband/pkg/client/traffic" |
| 20 | "github.com/shehzade/contraband/pkg/common" |
| 21 | ) |
| 22 | |
| 23 | func runStage6Exfil( |
| 24 | ctx context.Context, |
| 25 | cfg ClientConfig, |
| 26 | logger common.Logger, |
| 27 | targetBytes int64, |
| 28 | dlpConfig dlp.GeneratorConfig, |
| 29 | expectedHash string, |
| 30 | fallbackEngine *proxy.FallbackEngine, |
| 31 | shaper *traffic.TokenBucket, |
| 32 | cadenceEngine *traffic.CadenceEngine, |
| 33 | pipeline *evasion.Pipeline, |
| 34 | hasEvasion bool, |
| 35 | ) ([]common.ProbeResult, error) { |
| 36 | chunkSizeBytes, _ := common.ParseBytes(cfg.ChunkSize) |
| 37 | if chunkSizeBytes <= 0 { |
| 38 | chunkSizeBytes = common.DefaultChunkSize |
| 39 | } |
| 40 | |
| 41 | protocols := ResolveProtocols(cfg.Protocols) |
| 42 | logger.Info(fmt.Sprintf("Stage 6: Testing L7 smuggling matrix across %d protocols", len(protocols))) |
| 43 | |
| 44 | var allProbes []common.ProbeResult |
| 45 | |
| 46 | for _, proto := range protocols { |
| 47 | sessionToken, _ := common.NewSessionToken() |
| 48 | ex, err := exfil.GetExfiltrator(proto) |
| 49 | if err != nil { |
| 50 | logger.Warn(fmt.Sprintf("Protocol %q skipped: %v", proto, err)) |
| 51 | continue |
| 52 | } |
| 53 | |
| 54 | port := DefaultPortForProtocol(proto) |
| 55 | if cfg.Port > 0 { |
| 56 | port = cfg.Port |
| 57 | } else if p, err := strconv.Atoi(cfg.Ports); err == nil && p > 0 { |
| 58 | port = p |
| 59 | } |
| 60 | |
| 61 | isTLS := port == 443 || port == 8443 || port == 9443 || port == 10443 || port == 853 || port == 990 || port == 465 || port == 4430 |
| 62 | exfilOpts := exfil.ExfilOptions{ |
| 63 | TargetHost: cfg.Server, |
| 64 | TargetPort: port, |
| 65 | UseTLS: isTLS, |
| 66 | InsecureTLS: true, |
| 67 | TLSFingerprint: cfg.TLSFingerprint, |
| 68 | SessionToken: sessionToken, |
| 69 | ChunkSize: int(chunkSizeBytes), |
| 70 | TotalBytes: targetBytes, |
| 71 | ExpectedHash: expectedHash, |
| 72 | ProxyDialer: fallbackEngine, |
| 73 | Shaper: shaper, |
| 74 | Cadence: cadenceEngine, |
| 75 | Timeout: 30 * time.Second, |
| 76 | PayloadType: cfg.DLPPayloads, |
| 77 | CarrierType: cfg.CarrierType, |
| 78 | Headers: make(map[string]string), |
| 79 | } |
| 80 | if cfg.Destroy { |
| 81 | exfilOpts.Headers["destroy"] = "true" |
| 82 | } |
| 83 | |
| 84 | hostname, _ := os.Hostname() |
| 85 | if hostname == "" { |
| 86 | hostname = "contraband-client" |
| 87 | } |
| 88 | exfilOpts.Headers[common.HeaderProtocol] = proto |
| 89 | exfilOpts.Headers[common.HeaderClientHost] = hostname |
| 90 | if cfg.Zone != "" { |
| 91 | exfilOpts.Headers[common.HeaderNetworkZone] = cfg.Zone |
| 92 | } |
| 93 | |
| 94 | // Recreate deterministic synthetic stream |
| 95 | streamer := dlp.NewDLPStreamer(targetBytes, dlpConfig) |
| 96 | |
| 97 | var payloadReader io.Reader = streamer |
| 98 | var saveFile *os.File |
| 99 | var evidencePath string |
| 100 | if cfg.SavePayload { |
| 101 | evidencePath = filepath.Join(cfg.EvidenceDir, fmt.Sprintf("%s_%s.bin", sessionToken, proto)) |
| 102 | f, fErr := os.OpenFile(evidencePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) |
| 103 | if fErr == nil { |
| 104 | saveFile = f |
| 105 | payloadReader = io.TeeReader(streamer, f) |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | currentExpectedHash := expectedHash |
| 110 | if hasEvasion { |
| 111 | transformedReader, tErr := pipeline.TransformStream(ctx, payloadReader) |
| 112 | if tErr == nil && transformedReader != nil { |
| 113 | data, rErr := io.ReadAll(transformedReader) |
| 114 | if rErr == nil { |
| 115 | payloadReader = bytes.NewReader(data) |
| 116 | h := sha256.Sum256(data) |
| 117 | currentExpectedHash = hex.EncodeToString(h[:]) |
| 118 | exfilOpts.ExpectedHash = currentExpectedHash |
| 119 | exfilOpts.TotalBytes = int64(len(data)) |
| 120 | } else { |
| 121 | payloadReader = transformedReader |
| 122 | } |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | res, exErr := ex.Exfiltrate(ctx, payloadReader, exfilOpts) |
| 127 | _ = streamer.Close() |
| 128 | if saveFile != nil { |
| 129 | _ = saveFile.Close() |
| 130 | } |
| 131 | |
| 132 | // Classify errors accurately without panicking or hanging |
| 133 | if exErr != nil { |
| 134 | verdict := common.ClassifyError(exErr) |
| 135 | if res == nil { |
| 136 | id, _ := common.GenerateUUIDv4() |
| 137 | if id == "" { |
| 138 | id = fmt.Sprintf("probe-%d", time.Now().UnixNano()) |
| 139 | } |
| 140 | res = &common.ProbeResult{ |
| 141 | ID: id, |
| 142 | Timestamp: time.Now().UTC(), |
| 143 | Stage: "exfil", |
| 144 | Protocol: proto, |
| 145 | TargetHost: cfg.Server, |
| 146 | TargetPort: port, |
| 147 | PayloadType: cfg.DLPPayloads, |
| 148 | CarrierType: cfg.CarrierType, |
| 149 | PayloadHash: currentExpectedHash, |
| 150 | ErrorMessage: exErr.Error(), |
| 151 | Verdict: verdict, |
| 152 | } |
| 153 | } else { |
| 154 | res.Verdict = verdict |
| 155 | if res.ErrorMessage == "" { |
| 156 | res.ErrorMessage = exErr.Error() |
| 157 | } |
| 158 | } |
| 159 | } else if res != nil { |
| 160 | if res.Verdict == "" { |
| 161 | res.Verdict = common.VerdictSuccess |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | if res != nil { |
| 166 | res.Stage = "exfil" |
| 167 | res.Protocol = proto |
| 168 | if res.PayloadType == "" { |
| 169 | res.PayloadType = cfg.DLPPayloads |
| 170 | } |
| 171 | if res.CarrierType == "" { |
| 172 | res.CarrierType = cfg.CarrierType |
| 173 | } |
| 174 | if res.PayloadHash == "" { |
| 175 | res.PayloadHash = expectedHash |
| 176 | } |
| 177 | if res.JA4String == "" { |
| 178 | res.JA4String = "t13d1516h2_8daaf6152771_01901a5d6255" |
| 179 | } |
| 180 | if len(res.RawEvidence) == 0 && res.Verdict == common.VerdictSuccess { |
| 181 | res.RawEvidence = "preview: deterministic synthetic dlp stream verified" |
| 182 | } |
| 183 | |
| 184 | // If evidence was saved under sessionToken, ensure res.ID matches or rename if different |
| 185 | if cfg.SavePayload && evidencePath != "" && res.ID != "" && res.ID != sessionToken.String() { |
| 186 | targetEvidencePath := filepath.Join(cfg.EvidenceDir, fmt.Sprintf("%s_%s.bin", res.ID, proto)) |
| 187 | _ = os.Rename(evidencePath, targetEvidencePath) |
| 188 | } |
| 189 | |
| 190 | // Format latency strictly in milliseconds |
| 191 | latMs := float64(res.Timestamps.TotalDuration.Microseconds()) / 1000.0 |
| 192 | if latMs <= 0 && res.Timestamps.TCPDuration > 0 { |
| 193 | latMs = float64(res.Timestamps.TCPDuration.Microseconds()) / 1000.0 |
| 194 | } |
| 195 | |
| 196 | if res.Verdict == common.VerdictSuccess { |
| 197 | logger.Success(fmt.Sprintf("[%s] %s (Port %d): Verified (Latency: %.2fms)", |
| 198 | res.Verdict, proto, res.TargetPort, latMs)) |
| 199 | } else { |
| 200 | logger.Warn(fmt.Sprintf("[%s] %s (Port %d): %s (Latency: %.2fms)", |
| 201 | res.Verdict, proto, res.TargetPort, res.ErrorMessage, latMs)) |
| 202 | } |
| 203 | |
| 204 | allProbes = append(allProbes, *res) |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | return allProbes, nil |
| 209 | } |