● 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)
Code 174 Commits 1
contraband / pkg / client / stage_exfil.go
← Back to directory
209 lines · 6.0 KB
Raw
1package client
2
3import (
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
23func 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}