● 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 / sweeper.go
← Back to directory
201 lines · 5.2 KB
Raw
1package sweep
2
3import (
4 "context"
5 "errors"
6 "sync"
7 "time"
8
9 "github.com/shehzade/contraband/pkg/common"
10)
11
12// SweepMode designates the transport layer protocol under test.
13type SweepMode string
14
15const (
16 ModeTCP SweepMode = "TCP"
17 ModeUDP SweepMode = "UDP"
18 ModeBoth SweepMode = "BOTH"
19)
20
21// SweepOptions configures the execution of the L4 sweep engine.
22type SweepOptions struct {
23 Mode SweepMode // ModeTCP, ModeUDP, ModeBoth (default: ModeTCP)
24 Concurrency int // 1 to 1000 (default: common.DefaultConcurrency = 100)
25 RateLimit int // Max packets per second; <=0 means unlimited (default: 500)
26 Timeout time.Duration // Socket dial/read timeout (default: 1500ms)
27 JitterMin time.Duration // Minimum randomized inter-probe delay (default: 5ms)
28 JitterMax time.Duration // Maximum randomized inter-probe delay (default: 50ms)
29 RequireReflection bool // If true, requires valid BannerReflection for VerdictSuccess (default: true)
30 Logger common.Logger // Leveled terminal/JSON logger
31}
32
33// DefaultSweepOptions returns production-grade defaults.
34func DefaultSweepOptions() SweepOptions {
35 return SweepOptions{
36 Mode: ModeTCP,
37 Concurrency: common.DefaultConcurrency,
38 RateLimit: common.DefaultRateLimit,
39 Timeout: common.DefaultTCPTimeout,
40 JitterMin: 5 * time.Millisecond,
41 JitterMax: 50 * time.Millisecond,
42 RequireReflection: true,
43 Logger: common.GetLogger(),
44 }
45}
46
47// Sweeper defines the interface for running concurrent egress sweeps.
48type Sweeper interface {
49 // Sweep executes a synchronous sweep across all target ports and returns results.
50 Sweep(ctx context.Context, targetHost string, ports []int) ([]common.ProbeResult, error)
51
52 // SweepStream executes the sweep and streams ProbeResult objects over out channel.
53 // The out channel is NOT closed by SweepStream (caller owns the channel).
54 SweepStream(ctx context.Context, targetHost string, ports []int, out chan<- common.ProbeResult) error
55}
56
57// Engine implements Sweeper using a concurrent worker pool and rate limiter.
58type Engine struct {
59 opts SweepOptions
60 limiter *TokenBucket
61}
62
63// NewEngine constructs a validated Engine instance.
64func NewEngine(opts SweepOptions) *Engine {
65 if opts.Concurrency < 1 {
66 opts.Concurrency = common.DefaultConcurrency
67 } else if opts.Concurrency > common.MaxConcurrency {
68 opts.Concurrency = common.MaxConcurrency
69 }
70 if opts.Timeout <= 0 {
71 opts.Timeout = common.DefaultTCPTimeout
72 }
73 if opts.Logger == nil {
74 opts.Logger = common.GetLogger()
75 }
76
77 var limiter *TokenBucket
78 if opts.RateLimit > 0 {
79 limiter = NewTokenBucket(opts.RateLimit, opts.RateLimit)
80 }
81
82 return &Engine{
83 opts: opts,
84 limiter: limiter,
85 }
86}
87
88// Sweep runs a full sweep across targetHost and ports synchronously.
89func (e *Engine) Sweep(ctx context.Context, targetHost string, ports []int) ([]common.ProbeResult, error) {
90 if len(ports) == 0 {
91 return []common.ProbeResult{}, nil
92 }
93
94 bufSize := len(ports)
95 if e.opts.Mode == ModeBoth {
96 bufSize = len(ports) * 2
97 }
98 out := make(chan common.ProbeResult, bufSize)
99 results := make([]common.ProbeResult, 0, bufSize)
100
101 errCh := make(chan error, 1)
102 go func() {
103 errCh <- e.SweepStream(ctx, targetHost, ports, out)
104 close(out)
105 }()
106
107 for res := range out {
108 results = append(results, res)
109 }
110
111 err := <-errCh
112 if err != nil && !errors.Is(err, context.Canceled) {
113 return results, err
114 }
115 return results, ctx.Err()
116}
117
118// SweepStream streams ProbeResults over out channel using a concurrent worker pool.
119func (e *Engine) SweepStream(ctx context.Context, targetHost string, ports []int, out chan<- common.ProbeResult) error {
120 if len(ports) == 0 {
121 return nil
122 }
123
124 type sweepJob struct {
125 port int
126 proto SweepMode
127 }
128
129 totalJobs := len(ports)
130 if e.opts.Mode == ModeBoth {
131 totalJobs = len(ports) * 2
132 }
133 jobs := make(chan sweepJob, totalJobs)
134
135 // Populate jobs
136 for _, p := range ports {
137 if e.opts.Mode == ModeBoth {
138 jobs <- sweepJob{port: p, proto: ModeTCP}
139 jobs <- sweepJob{port: p, proto: ModeUDP}
140 } else if e.opts.Mode == ModeUDP {
141 jobs <- sweepJob{port: p, proto: ModeUDP}
142 } else {
143 jobs <- sweepJob{port: p, proto: ModeTCP}
144 }
145 }
146 close(jobs)
147
148 numWorkers := e.opts.Concurrency
149 if numWorkers > totalJobs {
150 numWorkers = totalJobs
151 }
152 if numWorkers < 1 {
153 numWorkers = 1
154 }
155
156 var wg sync.WaitGroup
157 for i := 0; i < numWorkers; i++ {
158 wg.Add(1)
159 go func() {
160 defer wg.Done()
161 for {
162 select {
163 case <-ctx.Done():
164 return
165 case job, ok := <-jobs:
166 if !ok {
167 return
168 }
169
170 // Rate limit enforcement
171 if e.limiter != nil {
172 if err := e.limiter.Wait(ctx); err != nil {
173 return
174 }
175 }
176
177 // Evasion jitter
178 if err := ApplyJitter(ctx, e.opts.JitterMin, e.opts.JitterMax); err != nil {
179 return
180 }
181
182 var result common.ProbeResult
183 if job.proto == ModeUDP {
184 result = ProbeUDP(ctx, targetHost, job.port, e.opts.Timeout, e.opts.RequireReflection)
185 } else {
186 result = ProbeTCP(ctx, targetHost, job.port, e.opts.Timeout, e.opts.RequireReflection)
187 }
188
189 select {
190 case <-ctx.Done():
191 return
192 case out <- result:
193 }
194 }
195 }
196 }()
197 }
198
199 wg.Wait()
200 return nil
201}