● 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 / sweep / tcp.go
← Back to directory
190 lines · 5.5 KB
Raw
1package sweep
2
3import (
4 "bufio"
5 "context"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "io"
10 "net"
11 "strconv"
12 "strings"
13 "time"
14
15 "github.com/shehzade/contraband/pkg/common"
16)
17
18// ProbeTCP performs an egress probe against targetHost:port using TCP.
19func ProbeTCP(ctx context.Context, targetHost string, port int, timeout time.Duration, requireReflection bool) common.ProbeResult {
20 res, err := common.NewProbeResult("sweep", "tcp", targetHost, port)
21 if err != nil {
22 return common.ProbeResult{
23 Timestamp: time.Now().UTC(),
24 Stage: "sweep",
25 Protocol: "tcp",
26 TargetHost: targetHost,
27 TargetPort: port,
28 Verdict: common.VerdictBlockedL4,
29 ErrorMessage: fmt.Sprintf("failed to initialize probe: %v", err),
30 }
31 }
32
33 targetAddr := net.JoinHostPort(targetHost, strconv.Itoa(port))
34 dialer := &net.Dialer{
35 Timeout: timeout,
36 KeepAlive: -1, // Disable keepalive for ephemeral sweep sockets
37 }
38
39 start := time.Now().UTC()
40 res.Timestamps.StartTCP = start
41
42 conn, dialErr := dialer.DialContext(ctx, "tcp", targetAddr)
43 if dialErr != nil {
44 teardown := time.Now().UTC()
45 res.Timestamps.TeardownDone = teardown
46 res.Timestamps.ComputeDurations()
47
48 if ctx.Err() != nil {
49 res.Verdict = common.VerdictBlockedL4
50 res.ErrorMessage = fmt.Sprintf("L4_CANCELED: %v", ctx.Err())
51 return res
52 }
53
54 classifyTCPDialError(dialErr, &res)
55 return res
56 }
57
58 connected := time.Now().UTC()
59 res.Timestamps.ConnectedTCP = connected
60 res.LocalAddr = conn.LocalAddr().String()
61
62 // Fail fast if context was canceled immediately after dialing
63 if ctx.Err() != nil {
64 _ = conn.Close()
65 teardown := time.Now().UTC()
66 res.Timestamps.TeardownDone = teardown
67 res.Timestamps.ComputeDurations()
68 res.Verdict = common.VerdictBlockedL4
69 res.ErrorMessage = fmt.Sprintf("L4_CANCELED: %v", ctx.Err())
70 return res
71 }
72
73 // Set deadline for reflection banner read
74 _ = conn.SetReadDeadline(time.Now().Add(timeout))
75
76 // Ensure context cancellation interrupts any blocking I/O immediately
77 done := make(chan struct{})
78 defer close(done)
79 go func() {
80 select {
81 case <-ctx.Done():
82 _ = conn.SetDeadline(time.Now())
83 case <-done:
84 }
85 }()
86
87 reader := bufio.NewReaderSize(conn, 4096)
88 bannerLine, readErr := reader.ReadBytes('\n')
89 ack := time.Now().UTC()
90 res.Timestamps.AckReceived = ack
91
92 _ = conn.Close()
93 teardown := time.Now().UTC()
94 res.Timestamps.TeardownDone = teardown
95 res.Timestamps.ComputeDurations()
96
97 if readErr != nil {
98 if ctx.Err() != nil {
99 res.Verdict = common.VerdictBlockedL4
100 res.ErrorMessage = fmt.Sprintf("L4_CANCELED: %v", ctx.Err())
101 return res
102 }
103
104 if errors.Is(readErr, io.EOF) || strings.Contains(readErr.Error(), "connection reset") {
105 res.Verdict = common.VerdictBlockedL7
106 res.ErrorMessage = fmt.Sprintf("L7_RESET: connection established but reset before banner: %v", readErr)
107 return res
108 }
109 var netErr net.Error
110 if errors.As(readErr, &netErr) && netErr.Timeout() {
111 if requireReflection {
112 res.Verdict = common.VerdictBlockedL7
113 res.ErrorMessage = fmt.Sprintf("L7_TIMEOUT: TCP connected but no reflection banner within %v", timeout)
114 } else {
115 // Pure TCP connect mode: handshake established
116 res.Verdict = common.VerdictSuccess
117 }
118 return res
119 }
120 res.Verdict = common.VerdictBlockedL7
121 res.ErrorMessage = fmt.Sprintf("L7_READ_ERROR: %v", readErr)
122 return res
123 }
124
125 if ctx.Err() != nil {
126 res.Verdict = common.VerdictBlockedL4
127 res.ErrorMessage = fmt.Sprintf("L4_CANCELED: %v", ctx.Err())
128 return res
129 }
130
131 res.BytesRecv = int64(len(bannerLine))
132 rawStr := strings.TrimSpace(string(bannerLine))
133 res.RawEvidence = rawStr
134
135 // Parse JSON reflection banner
136 var banner common.BannerReflection
137 if err := json.Unmarshal([]byte(rawStr), &banner); err != nil {
138 if requireReflection {
139 res.Verdict = common.VerdictBlockedL7
140 res.ErrorMessage = fmt.Sprintf("L7_BANNER_INVALID: non-JSON response received: %v", err)
141 } else {
142 res.Verdict = common.VerdictSuccess
143 res.ErrorMessage = "FOREIGN_SERVICE: valid TCP connection established with non-reflection service"
144 }
145 return res
146 }
147
148 if banner.Status != "OK" || banner.Port != port || !strings.EqualFold(banner.Proto, "TCP") {
149 if requireReflection {
150 res.Verdict = common.VerdictBlockedL7
151 res.ErrorMessage = fmt.Sprintf("L7_BANNER_MISMATCH: status=%s port=%d (expected %d) proto=%s", banner.Status, banner.Port, port, banner.Proto)
152 } else {
153 res.Verdict = common.VerdictSuccess
154 res.ErrorMessage = "FOREIGN_SERVICE: valid TCP connection established with non-reflection service"
155 }
156 return res
157 }
158
159 // Verified Bidirectional Egress
160 res.Verdict = common.VerdictSuccess
161 if banner.RemoteIP != "" {
162 res.LocalAddr = banner.RemoteIP // Record server-observed public IP
163 }
164 return res
165}
166
167func classifyTCPDialError(err error, res *common.ProbeResult) {
168 res.Verdict = common.VerdictBlockedL4
169 errStr := err.Error()
170
171 var netErr net.Error
172 if errors.As(err, &netErr) && netErr.Timeout() {
173 res.ErrorMessage = fmt.Sprintf("L4_FILTERED: connection timed out (%v)", err)
174 return
175 }
176 if strings.Contains(errStr, "connection reset") {
177 res.Verdict = common.VerdictBlockedL7
178 res.ErrorMessage = fmt.Sprintf("L7_RESET: connection established but reset before banner: %v", err)
179 return
180 }
181 if strings.Contains(errStr, "connection refused") {
182 res.ErrorMessage = fmt.Sprintf("L4_CLOSED: connection refused (%v)", err)
183 return
184 }
185 if strings.Contains(errStr, "network is unreachable") || strings.Contains(errStr, "no route to host") {
186 res.ErrorMessage = fmt.Sprintf("L4_UNREACHABLE: %v", err)
187 return
188 }
189 res.ErrorMessage = fmt.Sprintf("L4_DIAL_ERROR: %v", err)
190}