[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)
433 lines · 11.7 KB
| 1 | package multiplexer |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/binary" |
| 6 | "fmt" |
| 7 | "net" |
| 8 | "sync" |
| 9 | "sync/atomic" |
| 10 | "time" |
| 11 | |
| 12 | "github.com/shehzade/contraband/pkg/common" |
| 13 | "github.com/shehzade/contraband/pkg/server" |
| 14 | ) |
| 15 | |
| 16 | // PortBindStatus categorizes the binding outcome of an individual port. |
| 17 | type PortBindStatus string |
| 18 | |
| 19 | const ( |
| 20 | PortStatusBound PortBindStatus = "BOUND" |
| 21 | PortStatusOccupiedSkipped PortBindStatus = "OCCUPIED_SKIPPED" |
| 22 | PortStatusPermissionDenied PortBindStatus = "PERMISSION_DENIED" |
| 23 | PortStatusFailed PortBindStatus = "FAILED" |
| 24 | PortStatusClosed PortBindStatus = "CLOSED" |
| 25 | ) |
| 26 | |
| 27 | // ProbeType indicates the classification of an incoming connection. |
| 28 | type ProbeType string |
| 29 | |
| 30 | const ( |
| 31 | ProbeTypeSweep ProbeType = "SWEEP_PROBE" |
| 32 | ProbeTypeSmuggling ProbeType = "SMUGGLING_SESSION" |
| 33 | ProbeTypeUnknown ProbeType = "UNKNOWN" |
| 34 | ) |
| 35 | |
| 36 | // MultiplexerConfig defines runtime parameters for the multi-port reflector. |
| 37 | type MultiplexerConfig struct { |
| 38 | BindIP string `json:"bind_ip"` // e.g. "0.0.0.0" or "127.0.0.1" |
| 39 | Ports string `json:"ports"` // "quick", "standard", "full", or "80,443,8000-8100" |
| 40 | EnableTCP bool `json:"enable_tcp"` // Enable TCP listeners (default: true) |
| 41 | EnableUDP bool `json:"enable_udp"` // Enable UDP listeners (default: true) |
| 42 | EnableEBPF bool `json:"enable_ebpf"` // Attempt Linux eBPF/nftables full redirection |
| 43 | PeekTimeout time.Duration `json:"peek_timeout"` // TCP initial byte read deadline (default: 100ms) |
| 44 | SocketTimeout time.Duration `json:"socket_timeout"` // Inactive connection teardown deadline (default: 3s) |
| 45 | } |
| 46 | |
| 47 | // PortReport summarizes binding state across the requested port matrix. |
| 48 | type PortReport struct { |
| 49 | Port int `json:"port"` |
| 50 | Protocol string `json:"protocol"` // "TCP" or "UDP" |
| 51 | Status PortBindStatus `json:"status"` |
| 52 | Error string `json:"error,omitempty"` |
| 53 | } |
| 54 | |
| 55 | // MultiplexerMetrics provides live observability counters. |
| 56 | type MultiplexerMetrics struct { |
| 57 | RequestedPorts int `json:"requested_ports"` |
| 58 | BoundTCP int `json:"bound_tcp"` |
| 59 | BoundUDP int `json:"bound_udp"` |
| 60 | SkippedPorts int `json:"skipped_ports"` |
| 61 | ReflectedTCP int64 `json:"reflected_tcp"` |
| 62 | ReflectedUDP int64 `json:"reflected_udp"` |
| 63 | DemuxSmuggling int64 `json:"demux_smuggling"` |
| 64 | } |
| 65 | |
| 66 | // DemuxHandler defines the callback invoked when an incoming connection |
| 67 | // carries a data smuggling payload instead of a sweep probe. |
| 68 | type DemuxHandler = server.DemuxHandler |
| 69 | |
| 70 | // PortListener represents an active multiplexer port listener. |
| 71 | type PortListener struct { |
| 72 | Port int |
| 73 | Protocol string |
| 74 | Status PortBindStatus |
| 75 | TCP net.Listener |
| 76 | UDP net.PacketConn |
| 77 | } |
| 78 | |
| 79 | // Multiplexer defines the master interface for multi-port reflection. |
| 80 | type Multiplexer interface { |
| 81 | Start(ctx context.Context) error |
| 82 | Shutdown(ctx context.Context) error |
| 83 | GetPortReports() []PortReport |
| 84 | GetMetrics() MultiplexerMetrics |
| 85 | RegisterDemuxHandler(magic uint32, handler DemuxHandler) |
| 86 | GetPortListeners() []PortListener |
| 87 | } |
| 88 | |
| 89 | // UserspaceMultiplexer manages concurrent TCP net.Listener and UDP net.PacketConn instances. |
| 90 | type UserspaceMultiplexer struct { |
| 91 | mu sync.RWMutex |
| 92 | config MultiplexerConfig |
| 93 | ledger server.Ledger |
| 94 | logger common.Logger |
| 95 | safety *SafetyChecker |
| 96 | reflector *ReflectionEngine |
| 97 | demuxer *HeaderDemuxer |
| 98 | redirector *LinuxKernelRedirector |
| 99 | |
| 100 | tcpListeners map[int]net.Listener |
| 101 | udpConns map[int]net.PacketConn |
| 102 | portReports map[string]PortReport // "port/proto" -> PortReport |
| 103 | demuxHandlers map[uint32]DemuxHandler |
| 104 | |
| 105 | ctx context.Context |
| 106 | cancel context.CancelFunc |
| 107 | wg sync.WaitGroup |
| 108 | running atomic.Bool |
| 109 | |
| 110 | // Atomic telemetry counters |
| 111 | reflectedTCP atomic.Int64 |
| 112 | reflectedUDP atomic.Int64 |
| 113 | demuxSmuggling atomic.Int64 |
| 114 | } |
| 115 | |
| 116 | // NewUserspaceMultiplexer constructs a production-grade multiplexer instance. |
| 117 | func NewUserspaceMultiplexer(cfg MultiplexerConfig, ledger server.Ledger, logger common.Logger) (*UserspaceMultiplexer, error) { |
| 118 | if cfg.BindIP == "" { |
| 119 | cfg.BindIP = common.DefaultServerBind |
| 120 | } |
| 121 | if cfg.PeekTimeout <= 0 { |
| 122 | cfg.PeekTimeout = 100 * time.Millisecond |
| 123 | } |
| 124 | if cfg.SocketTimeout <= 0 { |
| 125 | cfg.SocketTimeout = 3 * time.Second |
| 126 | } |
| 127 | if logger == nil { |
| 128 | logger = common.GetLogger() |
| 129 | } |
| 130 | |
| 131 | safety := NewSafetyChecker(logger) |
| 132 | reflector := NewReflectionEngine(ledger, logger) |
| 133 | demuxer := NewHeaderDemuxer(cfg.PeekTimeout) |
| 134 | |
| 135 | return &UserspaceMultiplexer{ |
| 136 | config: cfg, |
| 137 | ledger: ledger, |
| 138 | logger: logger, |
| 139 | safety: safety, |
| 140 | reflector: reflector, |
| 141 | demuxer: demuxer, |
| 142 | tcpListeners: make(map[int]net.Listener), |
| 143 | udpConns: make(map[int]net.PacketConn), |
| 144 | portReports: make(map[string]PortReport), |
| 145 | demuxHandlers: make(map[uint32]DemuxHandler), |
| 146 | }, nil |
| 147 | } |
| 148 | |
| 149 | // Start parses port presets, executes safe binding, and spawns worker loops. |
| 150 | func (u *UserspaceMultiplexer) Start(ctx context.Context) error { |
| 151 | u.mu.Lock() |
| 152 | defer u.mu.Unlock() |
| 153 | |
| 154 | if u.running.Load() { |
| 155 | return fmt.Errorf("multiplexer already running") |
| 156 | } |
| 157 | |
| 158 | ports, err := common.ParsePortsField(u.config.Ports) |
| 159 | if err != nil { |
| 160 | return fmt.Errorf("failed to parse ports %q: %w", u.config.Ports, err) |
| 161 | } |
| 162 | |
| 163 | u.ctx, u.cancel = context.WithCancel(ctx) |
| 164 | u.running.Store(true) |
| 165 | |
| 166 | boundTCP := 0 |
| 167 | boundUDP := 0 |
| 168 | skipped := 0 |
| 169 | |
| 170 | // 1. Bind TCP Listeners |
| 171 | if u.config.EnableTCP { |
| 172 | for _, port := range ports { |
| 173 | ln, status, err := u.safety.SafeListenTCP(u.config.BindIP, port) |
| 174 | key := fmt.Sprintf("%d/TCP", port) |
| 175 | if err != nil { |
| 176 | u.portReports[key] = PortReport{Port: port, Protocol: "TCP", Status: status, Error: err.Error()} |
| 177 | continue |
| 178 | } |
| 179 | u.portReports[key] = PortReport{Port: port, Protocol: "TCP", Status: status} |
| 180 | if status == PortStatusBound { |
| 181 | u.tcpListeners[port] = ln |
| 182 | boundTCP++ |
| 183 | u.wg.Add(1) |
| 184 | go u.tcpAcceptLoop(ln, port) |
| 185 | } else { |
| 186 | skipped++ |
| 187 | } |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | // 2. Bind UDP Packet Connections |
| 192 | if u.config.EnableUDP { |
| 193 | for _, port := range ports { |
| 194 | pc, status, err := u.safety.SafeListenUDP(u.config.BindIP, port) |
| 195 | key := fmt.Sprintf("%d/UDP", port) |
| 196 | if err != nil { |
| 197 | u.portReports[key] = PortReport{Port: port, Protocol: "UDP", Status: status, Error: err.Error()} |
| 198 | continue |
| 199 | } |
| 200 | u.portReports[key] = PortReport{Port: port, Protocol: "UDP", Status: status} |
| 201 | if status == PortStatusBound { |
| 202 | u.udpConns[port] = pc |
| 203 | boundUDP++ |
| 204 | u.wg.Add(1) |
| 205 | go u.udpReadLoop(pc, port) |
| 206 | } else { |
| 207 | skipped++ |
| 208 | } |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | // 3. Optional Linux Kernel Redirection (eBPF / nftables) |
| 213 | if u.config.EnableEBPF { |
| 214 | targetPort := SelectTargetPort(ports) |
| 215 | u.redirector = NewLinuxKernelRedirector(targetPort, u.logger) |
| 216 | if err := u.redirector.Enable(); err != nil { |
| 217 | u.logger.Warnf("Kernel redirector initialization skipped (%v); operating in pure userspace multiplexer mode", err) |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | u.logger.Infof("Multiplexer active: bound %d TCP and %d UDP listeners (%d occupied/privileged ports skipped safely)", |
| 222 | boundTCP, boundUDP, skipped) |
| 223 | |
| 224 | return nil |
| 225 | } |
| 226 | |
| 227 | // SelectTargetPort selects the first non-protected port from candidate ports. |
| 228 | // If all candidate ports are protected or candidate list is empty, it returns default fallback port 8080. |
| 229 | func SelectTargetPort(ports []int) int { |
| 230 | for _, p := range ports { |
| 231 | if !IsProtectedPort(p) { |
| 232 | return p |
| 233 | } |
| 234 | } |
| 235 | return 8080 |
| 236 | } |
| 237 | |
| 238 | // tcpAcceptLoop handles incoming TCP connections on a designated port. |
| 239 | func (u *UserspaceMultiplexer) tcpAcceptLoop(ln net.Listener, port int) { |
| 240 | defer u.wg.Done() |
| 241 | for { |
| 242 | conn, err := ln.Accept() |
| 243 | if err != nil { |
| 244 | select { |
| 245 | case <-u.ctx.Done(): |
| 246 | return |
| 247 | default: |
| 248 | // Listener closed or transient error |
| 249 | return |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | u.wg.Add(1) |
| 254 | go func(c net.Conn) { |
| 255 | defer u.wg.Done() |
| 256 | u.handleTCPConn(c, port) |
| 257 | }(conn) |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | // handleTCPConn classifies connection via peeking and executes reflection or demux. |
| 262 | func (u *UserspaceMultiplexer) handleTCPConn(conn net.Conn, port int) { |
| 263 | defer conn.Close() |
| 264 | _ = conn.SetDeadline(time.Now().Add(u.config.SocketTimeout)) |
| 265 | |
| 266 | probeType, peeked, err := u.demuxer.Demux(conn) |
| 267 | if err != nil && probeType == ProbeTypeUnknown { |
| 268 | return |
| 269 | } |
| 270 | |
| 271 | switch probeType { |
| 272 | case ProbeTypeSmuggling: |
| 273 | u.demuxSmuggling.Add(1) |
| 274 | if len(peeked) >= 4 { |
| 275 | magic := binary.BigEndian.Uint32(peeked[0:4]) |
| 276 | u.mu.RLock() |
| 277 | handler, exists := u.demuxHandlers[magic] |
| 278 | u.mu.RUnlock() |
| 279 | if exists { |
| 280 | _ = handler(conn, peeked, port) |
| 281 | return |
| 282 | } |
| 283 | } |
| 284 | // If no dedicated smuggling handler registered, emit reflection fallback |
| 285 | _ = u.reflector.ReflectTCP(conn, port) |
| 286 | u.reflectedTCP.Add(1) |
| 287 | |
| 288 | case ProbeTypeSweep, ProbeTypeUnknown: |
| 289 | _ = u.reflector.ReflectTCP(conn, port) |
| 290 | u.reflectedTCP.Add(1) |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | // udpReadLoop reads incoming datagrams on a designated UDP port. |
| 295 | func (u *UserspaceMultiplexer) udpReadLoop(pc net.PacketConn, port int) { |
| 296 | defer u.wg.Done() |
| 297 | buf := make([]byte, 4096) |
| 298 | |
| 299 | for { |
| 300 | n, rAddr, err := pc.ReadFrom(buf) |
| 301 | if err != nil { |
| 302 | select { |
| 303 | case <-u.ctx.Done(): |
| 304 | return |
| 305 | default: |
| 306 | return |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | if n > 0 { |
| 311 | pkt := make([]byte, n) |
| 312 | copy(pkt, buf[:n]) |
| 313 | |
| 314 | u.wg.Add(1) |
| 315 | go func(data []byte, addr net.Addr) { |
| 316 | defer u.wg.Done() |
| 317 | _ = u.reflector.ReflectUDP(pc, addr, port, data) |
| 318 | u.reflectedUDP.Add(1) |
| 319 | }(pkt, rAddr) |
| 320 | } |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | // Shutdown gracefully stops all listeners and waits for in-flight workers. |
| 325 | func (u *UserspaceMultiplexer) Shutdown(ctx context.Context) error { |
| 326 | if !u.running.CompareAndSwap(true, false) { |
| 327 | return nil |
| 328 | } |
| 329 | |
| 330 | u.mu.Lock() |
| 331 | if u.redirector != nil { |
| 332 | _ = u.redirector.Disable() |
| 333 | } |
| 334 | if u.cancel != nil { |
| 335 | u.cancel() |
| 336 | } |
| 337 | |
| 338 | for port, ln := range u.tcpListeners { |
| 339 | _ = ln.Close() |
| 340 | key := fmt.Sprintf("%d/TCP", port) |
| 341 | if rep, ok := u.portReports[key]; ok { |
| 342 | rep.Status = PortStatusClosed |
| 343 | u.portReports[key] = rep |
| 344 | } |
| 345 | } |
| 346 | for port, pc := range u.udpConns { |
| 347 | _ = pc.Close() |
| 348 | key := fmt.Sprintf("%d/UDP", port) |
| 349 | if rep, ok := u.portReports[key]; ok { |
| 350 | rep.Status = PortStatusClosed |
| 351 | u.portReports[key] = rep |
| 352 | } |
| 353 | } |
| 354 | u.mu.Unlock() |
| 355 | |
| 356 | // Wait for goroutines with context timeout |
| 357 | done := make(chan struct{}) |
| 358 | go func() { |
| 359 | u.wg.Wait() |
| 360 | close(done) |
| 361 | }() |
| 362 | |
| 363 | select { |
| 364 | case <-done: |
| 365 | u.logger.Info("Multiplexer shutdown complete: all sockets closed cleanly") |
| 366 | return nil |
| 367 | case <-ctx.Done(): |
| 368 | return fmt.Errorf("multiplexer shutdown timed out: %w", ctx.Err()) |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | // RegisterDemuxHandler binds a magic-prefixed handler for data smuggling sessions. |
| 373 | func (u *UserspaceMultiplexer) RegisterDemuxHandler(magic uint32, handler DemuxHandler) { |
| 374 | u.mu.Lock() |
| 375 | defer u.mu.Unlock() |
| 376 | u.demuxHandlers[magic] = handler |
| 377 | } |
| 378 | |
| 379 | // GetPortReports returns a snapshot of port binding states. |
| 380 | func (u *UserspaceMultiplexer) GetPortReports() []PortReport { |
| 381 | u.mu.RLock() |
| 382 | defer u.mu.RUnlock() |
| 383 | reports := make([]PortReport, 0, len(u.portReports)) |
| 384 | for _, r := range u.portReports { |
| 385 | reports = append(reports, r) |
| 386 | } |
| 387 | return reports |
| 388 | } |
| 389 | |
| 390 | // GetPortListeners returns a list of active port listeners. |
| 391 | func (u *UserspaceMultiplexer) GetPortListeners() []PortListener { |
| 392 | u.mu.RLock() |
| 393 | defer u.mu.RUnlock() |
| 394 | listeners := make([]PortListener, 0) |
| 395 | for port, ln := range u.tcpListeners { |
| 396 | listeners = append(listeners, PortListener{ |
| 397 | Port: port, |
| 398 | Protocol: "TCP", |
| 399 | Status: PortStatusBound, |
| 400 | TCP: ln, |
| 401 | }) |
| 402 | } |
| 403 | for port, pc := range u.udpConns { |
| 404 | listeners = append(listeners, PortListener{ |
| 405 | Port: port, |
| 406 | Protocol: "UDP", |
| 407 | Status: PortStatusBound, |
| 408 | UDP: pc, |
| 409 | }) |
| 410 | } |
| 411 | return listeners |
| 412 | } |
| 413 | |
| 414 | // GetMetrics returns real-time reflection and demux counts. |
| 415 | func (u *UserspaceMultiplexer) GetMetrics() MultiplexerMetrics { |
| 416 | u.mu.RLock() |
| 417 | defer u.mu.RUnlock() |
| 418 | skipped := 0 |
| 419 | for _, rep := range u.portReports { |
| 420 | if rep.Status == PortStatusOccupiedSkipped || rep.Status == PortStatusPermissionDenied { |
| 421 | skipped++ |
| 422 | } |
| 423 | } |
| 424 | return MultiplexerMetrics{ |
| 425 | RequestedPorts: len(u.portReports), |
| 426 | BoundTCP: len(u.tcpListeners), |
| 427 | BoundUDP: len(u.udpConns), |
| 428 | SkippedPorts: skipped, |
| 429 | ReflectedTCP: u.reflectedTCP.Load(), |
| 430 | ReflectedUDP: u.reflectedUDP.Load(), |
| 431 | DemuxSmuggling: u.demuxSmuggling.Load(), |
| 432 | } |
| 433 | } |