From 90038dcfc2bc20e861c70f55c54a5668a02517f3 Mon Sep 17 00:00:00 2001 From: psycep Date: Wed, 22 Apr 2026 09:54:09 -0500 Subject: [PATCH 1/7] transport: add SOCKS5 proxy support with UDP-under-proxy guard Splits pkg/transport into a router (tcp.go) plus two platform-specific direct dialers: direct_libc.go preserves the existing cgo libc connect() path on Unix (so proxychains can still hook it), and direct_portable.go provides a pure-Go fallback for CGO_ENABLED=0 and Windows builds. Adds proxy.go with: - Configure(Options) / ConfigureProxy to wire a SOCKS5 URL (socks5 or socks5h), with ALL_PROXY / all_proxy env fallback read directly via os.Getenv (not proxy.FromEnvironment, whose sync.Once cache is not test-friendly). - DialContext routing through the configured proxy, or directDial. - DialUDP returning ErrUDPUnderProxy when proxied, rather than silently leaking UDP packets past the SOCKS5. - IsProxyConfigured and ProxyURL for callers that need to short-circuit features that can't be tunneled. - libcForwarder so the TCP leg to the SOCKS5 proxy itself still goes through libc, enabling proxychains -> gopacket -> -proxy chaining. Tests: - export_test.go exposes ResetForTest for test isolation (Configure panics on double-call, so each test resets package state). - socks5_server_test.go hand-rolls a minimal in-process SOCKS5 server (CONNECT + no-auth, ~130 LOC, no new deps). - proxy_test.go covers invalid scheme rejection, socks5/socks5h accept, ALL_PROXY env fallback, double-call panic, default state, DialUDP/DialContext UDP rejection, credential redaction, direct-path behavior when unconfigured, and an end-to-end round-trip through the in-process SOCKS5. --- pkg/transport/direct_libc.go | 137 ++++++++++++++++ pkg/transport/direct_portable.go | 38 +++++ pkg/transport/export_test.go | 22 +++ pkg/transport/proxy.go | 165 +++++++++++++++++++ pkg/transport/proxy_test.go | 242 ++++++++++++++++++++++++++++ pkg/transport/socks5_server_test.go | 147 +++++++++++++++++ pkg/transport/tcp.go | 122 +++----------- 7 files changed, 774 insertions(+), 99 deletions(-) create mode 100644 pkg/transport/direct_libc.go create mode 100644 pkg/transport/direct_portable.go create mode 100644 pkg/transport/export_test.go create mode 100644 pkg/transport/proxy.go create mode 100644 pkg/transport/proxy_test.go create mode 100644 pkg/transport/socks5_server_test.go diff --git a/pkg/transport/direct_libc.go b/pkg/transport/direct_libc.go new file mode 100644 index 0000000..d9f9b95 --- /dev/null +++ b/pkg/transport/direct_libc.go @@ -0,0 +1,137 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build cgo && !windows + +package transport + +/* +#include +#include +#include +#include +#include +#include +#include + +// libc_dial connects via libc's getaddrinfo + connect, which ARE hookable by LD_PRELOAD. +// Returns the file descriptor on success, or -1 (getaddrinfo fail) / -2 (connect fail) on error. +int libc_dial(const char *host, const char *port, int timeout_sec) { + struct addrinfo hints, *res, *p; + int sockfd; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + int rv = getaddrinfo(host, port, &hints, &res); + if (rv != 0) { + return -1; + } + + for (p = res; p != NULL; p = p->ai_next) { + sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sockfd == -1) continue; + + // Set connect timeout via SO_SNDTIMEO (Linux honors this for connect()) + if (timeout_sec > 0) { + struct timeval tv; + tv.tv_sec = timeout_sec; + tv.tv_usec = 0; + setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + } + + if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + continue; + } + break; + } + + freeaddrinfo(res); + + if (p == NULL) return -2; + + // Clear the send timeout so it doesn't affect subsequent writes + if (timeout_sec > 0) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + } + + return sockfd; +} +*/ +import "C" + +import ( + "context" + "fmt" + "net" + "os" + "strings" + "time" + "unsafe" +) + +// directDial opens a connection via libc connect(), bypassing any configured +// proxy. The libc path is what LD_PRELOAD-based proxies like proxychains hook. +// UDP falls through to net.Dial because libc_dial is wired for SOCK_STREAM only +// and LD_PRELOAD proxies rarely handle UDP anyway. +func directDial(network, address string, timeoutSec int) (net.Conn, error) { + if strings.HasPrefix(network, "udp") { + return net.Dial(network, address) + } + + host, port, err := splitHostPort(address) + if err != nil { + return nil, err + } + + cHost := C.CString(host) + cPort := C.CString(port) + defer C.free(unsafe.Pointer(cHost)) + defer C.free(unsafe.Pointer(cPort)) + + fd := C.libc_dial(cHost, cPort, C.int(timeoutSec)) + if fd == -1 { + return nil, fmt.Errorf("getaddrinfo failed for %s", address) + } + if fd == -2 { + return nil, fmt.Errorf("connect failed for %s", address) + } + + f := os.NewFile(uintptr(fd), fmt.Sprintf("tcp:%s", address)) + conn, err := net.FileConn(f) + f.Close() // FileConn dups the fd + if err != nil { + return nil, fmt.Errorf("FileConn failed: %w", err) + } + return conn, nil +} + +// directDialContext dials directly, honoring ctx's deadline for the connect timeout. +func directDialContext(ctx context.Context, network, address string) (net.Conn, error) { + timeout := DefaultTimeout + if dl, ok := ctx.Deadline(); ok { + if d := time.Until(dl); d > 0 { + timeout = int(d.Seconds()) + if timeout < 1 { + timeout = 1 + } + } + } + return directDial(network, address, timeout) +} diff --git a/pkg/transport/direct_portable.go b/pkg/transport/direct_portable.go new file mode 100644 index 0000000..63e81a1 --- /dev/null +++ b/pkg/transport/direct_portable.go @@ -0,0 +1,38 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !cgo || windows + +package transport + +import ( + "context" + "net" + "time" +) + +// directDial opens a connection using the Go standard net.Dialer, bypassing +// any configured proxy. This path is used on Windows and on CGO_ENABLED=0 +// builds. It does not interoperate with LD_PRELOAD proxies like proxychains. +// Users on those platforms should use the -proxy flag instead. +func directDial(network, address string, timeoutSec int) (net.Conn, error) { + d := net.Dialer{Timeout: time.Duration(timeoutSec) * time.Second} + return d.Dial(network, address) +} + +// directDialContext dials directly using ctx for timeout/cancellation. +func directDialContext(ctx context.Context, network, address string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, network, address) +} diff --git a/pkg/transport/export_test.go b/pkg/transport/export_test.go new file mode 100644 index 0000000..6f07658 --- /dev/null +++ b/pkg/transport/export_test.go @@ -0,0 +1,22 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package transport + +// ResetForTest clears package-level proxy state so a subsequent Configure +// call won't panic on the one-shot guard. Only linked into test binaries. +func ResetForTest() { + proxyHolder.Store(nil) + configured.Store(false) +} diff --git a/pkg/transport/proxy.go b/pkg/transport/proxy.go new file mode 100644 index 0000000..769e90c --- /dev/null +++ b/pkg/transport/proxy.go @@ -0,0 +1,165 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package transport + +import ( + "context" + "errors" + "fmt" + "net" + "net/url" + "os" + "strings" + "sync/atomic" + + "golang.org/x/net/proxy" +) + +// ErrUDPUnderProxy is returned by DialUDP when a proxy is configured. SOCKS5 +// UDP ASSOCIATE is rarely supported by proxies and servers, and silently +// leaking UDP packets from the attacker host when a proxy is configured would +// reveal the operator's real source IP. Callers should arrange for direct +// operation, or skip the UDP-dependent feature under -proxy. +var ErrUDPUnderProxy = errors.New("UDP disabled under -proxy; the underlying feature cannot be tunneled") + +// Options holds runtime configuration for the transport layer. +type Options struct { + // Proxy, if non-empty, is a SOCKS5 URL that outbound TCP is routed through. + // Accepted schemes: socks5, socks5h. When empty, the ALL_PROXY / all_proxy + // environment variables are consulted. + Proxy string +} + +// proxyHolder points to the configured proxy dialer, or nil for direct. +// Stored atomically so Dial is lock-free on the hot path. +var proxyHolder atomic.Pointer[dialerHolder] + +type dialerHolder struct { + cd proxy.ContextDialer + url string +} + +var configured atomic.Bool + +// Configure initializes the transport layer. Must be called exactly once, +// typically from flags.Parse at tool startup. Panics on subsequent calls so a +// misconfigured tool fails loudly rather than silently racing on package state. +func Configure(opts Options) error { + if !configured.CompareAndSwap(false, true) { + panic("transport.Configure called more than once") + } + + // We intentionally don't use proxy.FromEnvironment here: it caches the env + // value via sync.Once for the process lifetime, which breaks tests and + // prevents consistent URL validation for env-supplied values. + fromEnv := false + if opts.Proxy == "" { + envURL := os.Getenv("ALL_PROXY") + if envURL == "" { + envURL = os.Getenv("all_proxy") + } + if envURL == "" { + return nil + } + opts.Proxy = envURL + fromEnv = true + } + + u, err := url.Parse(opts.Proxy) + if err != nil { + return fmt.Errorf("transport: invalid proxy URL %q: %v", opts.Proxy, err) + } + switch strings.ToLower(u.Scheme) { + case "socks5", "socks5h": + // x/net/proxy treats both identically (always sends hostname to the + // proxy; server decides resolution). socks5h is the documented + // recommendation because it mirrors proxychains' proxy_dns=on default. + default: + return fmt.Errorf("transport: unsupported proxy scheme %q (supported: socks5, socks5h)", u.Scheme) + } + + // Route the TCP connection to the SOCKS5 server through the libc dialer so + // LD_PRELOAD-based proxies (proxychains) can still hook it, useful for + // chaining: proxychains -> gopacket -> -proxy SOCKS5 -> target. + d, err := proxy.FromURL(u, libcForwarder{}) + if err != nil { + return fmt.Errorf("transport: initialize proxy dialer for %q: %v", opts.Proxy, err) + } + cd, ok := d.(proxy.ContextDialer) + if !ok { + return fmt.Errorf("transport: proxy dialer %T does not implement ContextDialer", d) + } + urlLabel := u.Redacted() + if fromEnv { + urlLabel += " (from ALL_PROXY)" + } + proxyHolder.Store(&dialerHolder{cd: cd, url: urlLabel}) + return nil +} + +// IsProxyConfigured reports whether a proxy is in effect. Callers that cannot +// meaningfully operate through a proxy (UDP probes, local-IP discovery) should +// consult this and short-circuit. +func IsProxyConfigured() bool { return proxyHolder.Load() != nil } + +// ProxyURL returns the configured proxy URL (redacted), or "" if none. +func ProxyURL() string { + if h := proxyHolder.Load(); h != nil { + return h.url + } + return "" +} + +// libcForwarder is the base proxy.Dialer used to reach the SOCKS5 server +// itself. Goes through directDial so the TCP connect to the proxy is still +// hookable by LD_PRELOAD. +type libcForwarder struct{} + +func (libcForwarder) Dial(network, address string) (net.Conn, error) { + return directDial(network, address, DefaultTimeout) +} + +func (libcForwarder) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + return directDialContext(ctx, network, address) +} + +// DialContext is a context-aware variant of Dial, suitable as an +// http.Transport.DialContext. Routes through the configured proxy if set. +// UDP under -proxy returns ErrUDPUnderProxy rather than letting a cryptic +// "network not implemented" bubble up from the SOCKS5 layer. +func DialContext(ctx context.Context, network, address string) (net.Conn, error) { + if h := proxyHolder.Load(); h != nil { + if strings.HasPrefix(network, "udp") { + return nil, ErrUDPUnderProxy + } + return h.cd.DialContext(ctx, network, address) + } + return directDialContext(ctx, network, address) +} + +// DialUDP opens a connected UDP socket to address. Returns ErrUDPUnderProxy +// if a proxy is configured. SOCKS5 UDP ASSOCIATE is rarely supported and +// silently bypassing the proxy under -proxy would reveal the operator's real +// source IP. +func DialUDP(address string) (*net.UDPConn, error) { + if IsProxyConfigured() { + return nil, ErrUDPUnderProxy + } + addr, err := net.ResolveUDPAddr("udp", address) + if err != nil { + return nil, err + } + return net.DialUDP("udp", nil, addr) +} diff --git a/pkg/transport/proxy_test.go b/pkg/transport/proxy_test.go new file mode 100644 index 0000000..8be905b --- /dev/null +++ b/pkg/transport/proxy_test.go @@ -0,0 +1,242 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package transport + +import ( + "errors" + "io" + "net" + "strings" + "testing" +) + +func TestConfigureRejectsInvalidScheme(t *testing.T) { + t.Cleanup(ResetForTest) + err := Configure(Options{Proxy: "http://127.0.0.1:8080"}) + if err == nil || !strings.Contains(err.Error(), "unsupported proxy scheme") { + t.Fatalf("want unsupported scheme error, got %v", err) + } +} + +func TestConfigureRejectsMalformedURL(t *testing.T) { + t.Cleanup(ResetForTest) + err := Configure(Options{Proxy: "::not a url::"}) + if err == nil { + t.Fatalf("want error for malformed URL, got nil") + } +} + +func TestConfigureAcceptsSocks5(t *testing.T) { + t.Cleanup(ResetForTest) + if err := Configure(Options{Proxy: "socks5://127.0.0.1:1080"}); err != nil { + t.Fatalf("socks5:// should be accepted: %v", err) + } + if !IsProxyConfigured() { + t.Fatal("IsProxyConfigured should be true after successful Configure") + } +} + +func TestConfigureAcceptsSocks5h(t *testing.T) { + t.Cleanup(ResetForTest) + if err := Configure(Options{Proxy: "socks5h://127.0.0.1:1080"}); err != nil { + t.Fatalf("socks5h:// should be accepted: %v", err) + } +} + +func TestConfigureFromEnv(t *testing.T) { + t.Cleanup(ResetForTest) + t.Setenv("ALL_PROXY", "socks5h://127.0.0.1:1080") + if err := Configure(Options{Proxy: ""}); err != nil { + t.Fatalf("env-based configure should succeed: %v", err) + } + if !IsProxyConfigured() { + t.Fatal("ALL_PROXY should enable proxy") + } +} + +func TestConfigureDoubleCallPanics(t *testing.T) { + t.Cleanup(ResetForTest) + if err := Configure(Options{}); err != nil { + t.Fatalf("first Configure failed: %v", err) + } + defer func() { + if r := recover(); r == nil { + t.Fatal("second Configure should panic") + } + }() + _ = Configure(Options{}) +} + +func TestIsProxyConfiguredFalseByDefault(t *testing.T) { + t.Cleanup(ResetForTest) + t.Setenv("ALL_PROXY", "") + if err := Configure(Options{}); err != nil { + t.Fatalf("Configure: %v", err) + } + if IsProxyConfigured() { + t.Fatal("IsProxyConfigured should be false with no -proxy and no ALL_PROXY") + } +} + +func TestDialUDPReturnsErrWhenProxied(t *testing.T) { + t.Cleanup(ResetForTest) + if err := Configure(Options{Proxy: "socks5h://127.0.0.1:1080"}); err != nil { + t.Fatalf("Configure: %v", err) + } + _, err := DialUDP("127.0.0.1:53") + if !errors.Is(err, ErrUDPUnderProxy) { + t.Fatalf("want ErrUDPUnderProxy, got %v", err) + } +} + +func TestDialContextReturnsErrOnUDPUnderProxy(t *testing.T) { + t.Cleanup(ResetForTest) + if err := Configure(Options{Proxy: "socks5h://127.0.0.1:1080"}); err != nil { + t.Fatalf("Configure: %v", err) + } + _, err := DialContext(t.Context(), "udp", "127.0.0.1:53") + if !errors.Is(err, ErrUDPUnderProxy) { + t.Fatalf("want ErrUDPUnderProxy, got %v", err) + } +} + +func TestProxyURLRedactsCredentials(t *testing.T) { + t.Cleanup(ResetForTest) + if err := Configure(Options{Proxy: "socks5h://user:s3cret@127.0.0.1:1080"}); err != nil { + t.Fatalf("Configure: %v", err) + } + got := ProxyURL() + if strings.Contains(got, "s3cret") { + t.Fatalf("ProxyURL leaked password: %q", got) + } + if !strings.Contains(got, "user") || !strings.Contains(got, "127.0.0.1:1080") { + t.Fatalf("ProxyURL unexpected shape: %q", got) + } +} + +func TestProxyURLEmptyWhenUnconfigured(t *testing.T) { + t.Cleanup(ResetForTest) + t.Setenv("ALL_PROXY", "") + if err := Configure(Options{}); err != nil { + t.Fatalf("Configure: %v", err) + } + if got := ProxyURL(); got != "" { + t.Fatalf("ProxyURL should be empty when unconfigured, got %q", got) + } +} + +// TestDialDirectWithNoProxy verifies that with no -proxy and no ALL_PROXY, +// transport.Dial reaches a target directly (via directDial). Guards against +// regressions where proxy plumbing accidentally intercepts the no-proxy path. +func TestDialDirectWithNoProxy(t *testing.T) { + t.Cleanup(ResetForTest) + t.Setenv("ALL_PROXY", "") + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + defer ln.Close() + go func() { + for { + c, err := ln.Accept() + if err != nil { + return + } + go func() { + defer c.Close() + _, _ = io.Copy(c, c) + }() + } + }() + + if err := Configure(Options{}); err != nil { + t.Fatalf("Configure: %v", err) + } + if IsProxyConfigured() { + t.Fatal("no proxy should be configured") + } + + conn, err := Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial direct: %v", err) + } + defer conn.Close() + + want := []byte("direct-path-ok") + if _, err := conn.Write(want); err != nil { + t.Fatalf("write: %v", err) + } + got := make([]byte, len(want)) + if _, err := io.ReadFull(conn, got); err != nil { + t.Fatalf("read: %v", err) + } + if string(got) != string(want) { + t.Fatalf("echo mismatch: got %q want %q", got, want) + } +} + +// TestEndToEndDialThroughSocks5 wires Configure to an in-process SOCKS5 server +// and verifies a TCP round-trip flows through it. +func TestEndToEndDialThroughSocks5(t *testing.T) { + t.Cleanup(ResetForTest) + + // Echo server, the "real" target. + echoLn, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen echo: %v", err) + } + defer echoLn.Close() + go func() { + for { + c, err := echoLn.Accept() + if err != nil { + return + } + go func() { + defer c.Close() + _, _ = io.Copy(c, c) + }() + } + }() + + socks := newTestSOCKS5(t) + + if err := Configure(Options{Proxy: "socks5h://" + socks.addr}); err != nil { + t.Fatalf("Configure: %v", err) + } + + conn, err := Dial("tcp", echoLn.Addr().String()) + if err != nil { + t.Fatalf("Dial through proxy: %v", err) + } + defer conn.Close() + + want := []byte("hello-over-socks5") + if _, err := conn.Write(want); err != nil { + t.Fatalf("write: %v", err) + } + got := make([]byte, len(want)) + if _, err := io.ReadFull(conn, got); err != nil { + t.Fatalf("read: %v", err) + } + if string(got) != string(want) { + t.Fatalf("echo mismatch: got %q want %q", got, want) + } + + if n := socks.connects.Load(); n != 1 { + t.Fatalf("SOCKS5 server saw %d CONNECTs, want 1", n) + } +} diff --git a/pkg/transport/socks5_server_test.go b/pkg/transport/socks5_server_test.go new file mode 100644 index 0000000..b2a1497 --- /dev/null +++ b/pkg/transport/socks5_server_test.go @@ -0,0 +1,147 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package transport + +import ( + "encoding/binary" + "fmt" + "io" + "net" + "sync" + "sync/atomic" + "testing" +) + +// testSOCKS5 is a minimal SOCKS5 server for use in transport tests. It +// supports only CONNECT with no authentication (method 0x00) and only IPv4 +// and DOMAIN address types. It is NOT a production SOCKS5 implementation. +type testSOCKS5 struct { + ln net.Listener + addr string + wg sync.WaitGroup + connects atomic.Int32 // number of successful CONNECTs handled +} + +// newTestSOCKS5 starts a SOCKS5 server on 127.0.0.1 with an ephemeral port. +// It terminates when t.Cleanup runs. +func newTestSOCKS5(t *testing.T) *testSOCKS5 { + t.Helper() + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + s := &testSOCKS5{ln: ln, addr: ln.Addr().String()} + s.wg.Add(1) + go s.serve() + t.Cleanup(func() { + ln.Close() + s.wg.Wait() + }) + return s +} + +func (s *testSOCKS5) serve() { + defer s.wg.Done() + for { + c, err := s.ln.Accept() + if err != nil { + return + } + s.wg.Add(1) + go func() { + defer s.wg.Done() + s.handle(c) + }() + } +} + +func (s *testSOCKS5) handle(client net.Conn) { + defer client.Close() + + // Greeting: VER | NMETHODS | METHODS... + hdr := make([]byte, 2) + if _, err := io.ReadFull(client, hdr); err != nil { + return + } + if hdr[0] != 0x05 { + return + } + methods := make([]byte, hdr[1]) + if _, err := io.ReadFull(client, methods); err != nil { + return + } + // Accept only NO AUTH (0x00) + if _, err := client.Write([]byte{0x05, 0x00}); err != nil { + return + } + + // Request: VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT + req := make([]byte, 4) + if _, err := io.ReadFull(client, req); err != nil { + return + } + if req[0] != 0x05 || req[1] != 0x01 /* CONNECT */ { + _, _ = client.Write([]byte{0x05, 0x07, 0x00, 0x01, 0, 0, 0, 0, 0, 0}) // cmd not supported + return + } + + var host string + switch req[3] { + case 0x01: // IPv4 + b := make([]byte, 4) + if _, err := io.ReadFull(client, b); err != nil { + return + } + host = net.IP(b).String() + case 0x03: // DOMAIN + lb := make([]byte, 1) + if _, err := io.ReadFull(client, lb); err != nil { + return + } + b := make([]byte, lb[0]) + if _, err := io.ReadFull(client, b); err != nil { + return + } + host = string(b) + default: + _, _ = client.Write([]byte{0x05, 0x08, 0x00, 0x01, 0, 0, 0, 0, 0, 0}) // atyp not supported + return + } + + pb := make([]byte, 2) + if _, err := io.ReadFull(client, pb); err != nil { + return + } + port := binary.BigEndian.Uint16(pb) + + target, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + _, _ = client.Write([]byte{0x05, 0x05, 0x00, 0x01, 0, 0, 0, 0, 0, 0}) // conn refused + return + } + defer target.Close() + + // Reply: VER | REP=0 | RSV | ATYP=1 | BND.ADDR=0 | BND.PORT=0 + if _, err := client.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}); err != nil { + return + } + s.connects.Add(1) + + // Splice both directions + done := make(chan struct{}, 2) + go func() { _, _ = io.Copy(target, client); done <- struct{}{} }() + go func() { _, _ = io.Copy(client, target); done <- struct{}{} }() + <-done +} diff --git a/pkg/transport/tcp.go b/pkg/transport/tcp.go index 173bcdb..fed17d3 100644 --- a/pkg/transport/tcp.go +++ b/pkg/transport/tcp.go @@ -14,116 +14,44 @@ package transport -/* -#include -#include -#include -#include -#include -#include -#include - -// libc_dial connects via libc's getaddrinfo + connect, which ARE hookable by LD_PRELOAD. -// Returns the file descriptor on success, or -1 (getaddrinfo fail) / -2 (connect fail) on error. -int libc_dial(const char *host, const char *port, int timeout_sec) { - struct addrinfo hints, *res, *p; - int sockfd; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - - int rv = getaddrinfo(host, port, &hints, &res); - if (rv != 0) { - return -1; - } - - for (p = res; p != NULL; p = p->ai_next) { - sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); - if (sockfd == -1) continue; - - // Set connect timeout via SO_SNDTIMEO (Linux honors this for connect()) - if (timeout_sec > 0) { - struct timeval tv; - tv.tv_sec = timeout_sec; - tv.tv_usec = 0; - setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - } - - if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { - close(sockfd); - continue; - } - break; - } - - freeaddrinfo(res); - - if (p == NULL) return -2; - - // Clear the send timeout so it doesn't affect subsequent writes - if (timeout_sec > 0) { - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 0; - setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - } - - return sockfd; -} -*/ -import "C" - import ( + "context" "crypto/tls" "fmt" "net" - "os" "strings" - "unsafe" + "time" ) // DefaultTimeout is the default connect timeout in seconds. const DefaultTimeout = 30 -// Dial connects to the address on the named network using libc's connect(), -// which is hookable by LD_PRELOAD-based proxies like proxychains. -// The address must be in "host:port" format. +// Dial opens a TCP connection. Routes through the configured proxy if one was +// set via Configure; otherwise uses the platform's direct dialer (libc +// connect() on Unix/cgo, net.Dialer elsewhere). func Dial(network, address string) (net.Conn, error) { return DialTimeout(network, address, DefaultTimeout) } -// DialTimeout connects using libc's connect() with the given timeout in seconds. +// DialTimeout is Dial with an explicit connect timeout in seconds. +// A non-positive timeoutSec is normalized to DefaultTimeout so the direct and +// proxy branches behave consistently. func DialTimeout(network, address string, timeoutSec int) (net.Conn, error) { - host, port, err := splitHostPort(address) - if err != nil { - return nil, err - } - - cHost := C.CString(host) - cPort := C.CString(port) - defer C.free(unsafe.Pointer(cHost)) - defer C.free(unsafe.Pointer(cPort)) - - fd := C.libc_dial(cHost, cPort, C.int(timeoutSec)) - if fd == -1 { - return nil, fmt.Errorf("getaddrinfo failed for %s", address) + if timeoutSec <= 0 { + timeoutSec = DefaultTimeout } - if fd == -2 { - return nil, fmt.Errorf("connect failed for %s", address) - } - - // Convert C file descriptor to Go net.Conn - f := os.NewFile(uintptr(fd), fmt.Sprintf("tcp:%s", address)) - conn, err := net.FileConn(f) - f.Close() // FileConn dups the fd, so close the original - if err != nil { - return nil, fmt.Errorf("FileConn failed: %w", err) + if h := proxyHolder.Load(); h != nil { + if strings.HasPrefix(network, "udp") { + return nil, ErrUDPUnderProxy + } + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSec)*time.Second) + defer cancel() + return h.cd.DialContext(ctx, network, address) } - return conn, nil + return directDial(network, address, timeoutSec) } -// DialTLS connects via libc then wraps the connection in TLS. +// DialTLS opens a TCP connection (via proxy if configured) and wraps it in TLS. func DialTLS(network, address string, config *tls.Config) (*tls.Conn, error) { rawConn, err := Dial(network, address) if err != nil { @@ -142,24 +70,20 @@ func DialTLS(network, address string, config *tls.Config) (*tls.Conn, error) { return tlsConn, nil } -// Dialer provides a way to establish connections via libc. +// Dialer is a value-typed dialer suitable for APIs that expect a struct with a +// Dial method (e.g. pkg/smb, pkg/ldap). Respects the configured proxy. type Dialer struct { TimeoutSec int } -// Dial establishes a TCP connection to the specified address using libc's connect(). +// Dial establishes a TCP connection to address. Routes through the proxy if configured. func (d *Dialer) Dial(network, address string) (net.Conn, error) { - timeout := d.TimeoutSec - if timeout == 0 { - timeout = DefaultTimeout - } - return DialTimeout(network, address, timeout) + return DialTimeout(network, address, d.TimeoutSec) } func splitHostPort(address string) (host, port string, err error) { host, port, err = net.SplitHostPort(address) if err != nil { - // Try treating the whole thing as a host (no port) if !strings.Contains(address, ":") { return address, "", fmt.Errorf("missing port in address: %s", address) } From 7a1914a07757a599381eea5e09c028ddaa7fa6dd Mon Sep 17 00:00:00 2001 From: psycep Date: Wed, 22 Apr 2026 09:54:09 -0500 Subject: [PATCH 2/7] flags: add -proxy flag and RegisterProxyFlag helper Introduces three exports in pkg/flags: - ProxyFlagUsage: the shared -proxy usage string, so tools that hand-roll their flag setup render identical help text to Parse(). - ConfigureProxy(url): calls transport.Configure and exits on error. Centralizes the "fail loud on bad -proxy" behavior. - RegisterProxyFlag(): registers -proxy on the default flag.CommandLine and returns a finalizer to call after flag.Parse(). Two-line integration for tools that don't use flags.Parse(). Parse() now uses these internally. It also calls ConfigureProxy and applies Debug/Timestamp unconditionally rather than returning early when NArg == 0; tools that take all their config via flags (listeners, etc.) were previously losing -proxy because the early return skipped Configure. --- pkg/flags/flags.go | 47 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/pkg/flags/flags.go b/pkg/flags/flags.go index bb4590a..0e1748e 100644 --- a/pkg/flags/flags.go +++ b/pkg/flags/flags.go @@ -23,6 +23,7 @@ import ( "gopacket/internal/build" "gopacket/pkg/session" + "gopacket/pkg/transport" ) // ExtraUsageLine is appended to the "Usage: tool [options] target" line (e.g. "[maxRid]") @@ -46,6 +47,7 @@ type Options struct { TargetIP string Port int IPv6 bool + Proxy string // Utility InputFile string @@ -62,6 +64,29 @@ type Options struct { Arguments []string } +// ProxyFlagUsage is the shared usage string for -proxy. Keep tools consistent +// so the flag behaves identically whether registered via Parse() or +// RegisterProxyFlag(). +const ProxyFlagUsage = "SOCKS5 proxy URL (e.g. socks5h://127.0.0.1:1080). Routes TCP through the proxy. UDP features are disabled. If unset, ALL_PROXY env is consulted." + +// ConfigureProxy wires the transport layer with the given proxy URL. Exits on +// error so a misconfigured -proxy fails the tool instead of silently bypassing +// the proxy. Call after flag.Parse(). +func ConfigureProxy(proxyURL string) { + if err := transport.Configure(transport.Options{Proxy: proxyURL}); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } +} + +// RegisterProxyFlag registers -proxy on the default flag.CommandLine and +// returns a finalizer to call after flag.Parse(). Intended for tools that +// hand-roll their flag setup instead of using Parse(). +func RegisterProxyFlag() func() { + proxyURL := flag.String("proxy", "", ProxyFlagUsage) + return func() { ConfigureProxy(*proxyURL) } +} + // CheckHelp scans os.Args for -h/--help anywhere and shows usage if found. // Call this after setting flag.Usage but before flag.Parse(). // This handles the case where -h appears after positional arguments, @@ -90,6 +115,7 @@ func Parse() *Options { flag.StringVar(&opts.TargetIP, "target-ip", "", "IP Address of the target machine") flag.IntVar(&opts.Port, "port", 445, "Destination port to connect to SMB Server") flag.BoolVar(&opts.IPv6, "6", false, "Connect via IPv6") + flag.StringVar(&opts.Proxy, "proxy", "", ProxyFlagUsage) flag.StringVar(&opts.InputFile, "inputfile", "", "input file with list of entries") flag.StringVar(&opts.OutputFile, "outputfile", "", "base output filename") @@ -120,6 +146,17 @@ func Parse() *Options { flag.Parse() + // Set global settings and configure transport unconditionally, even when + // no positional args were given. Tools that take config entirely via + // flags (e.g. listeners) still need Debug/Timestamp/-proxy to take effect. + if opts.Debug { + build.Debug = true + } + if opts.Timestamp { + build.Timestamp = true + } + ConfigureProxy(opts.Proxy) + // Handle Positional Arguments (target + optional command/args) if flag.NArg() == 0 { return opts @@ -129,14 +166,6 @@ func Parse() *Options { opts.Arguments = flag.Args()[1:] } - // Set Global Settings - if opts.Debug { - build.Debug = true - } - if opts.Timestamp { - build.Timestamp = true - } - return opts } @@ -154,7 +183,7 @@ func printBanner() { func printGroupedHelp() { authFlags := []string{"hashes", "no-pass", "k", "aesKey", "keytab"} - connFlags := []string{"6", "dc-host", "dc-ip", "target-ip", "port"} + connFlags := []string{"6", "dc-host", "dc-ip", "target-ip", "port", "proxy"} miscFlags := []string{"inputfile", "outputfile", "ts", "debug"} // Maps to store flags From efccf922280a5872e21872750bd58a10cb77deb0 Mon Sep 17 00:00:00 2001 From: psycep Date: Wed, 22 Apr 2026 09:54:09 -0500 Subject: [PATCH 3/7] relay, tds: route outbound through transport - pkg/relay/http_client.go, winrm_client.go: swap the http.Transport DialContext from &net.Dialer{Timeout: 10s}.DialContext to a closure that calls transport.DialContext. NTLM relay now respects -proxy. - pkg/relay/socks.go: add a comment on the HTTP DNS passthrough path explaining why it intentionally uses net.DialTimeout rather than transport. That path is the outbound leg of our own SOCKS5 server, so routing it through -proxy would double-tunnel the operator's proxy through the operator's proxy. - pkg/tds/sqlr.go: SQL Server Browser discovery (UDP 1434) now uses transport.DialUDP, which surfaces ErrUDPUnderProxy when a proxy is configured rather than silently bypassing it. --- pkg/relay/http_client.go | 15 ++++++++++----- pkg/relay/socks.go | 3 +++ pkg/relay/winrm_client.go | 15 ++++++++++----- pkg/tds/sqlr.go | 24 +++++++----------------- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/pkg/relay/http_client.go b/pkg/relay/http_client.go index 057a5df..f505a5e 100644 --- a/pkg/relay/http_client.go +++ b/pkg/relay/http_client.go @@ -15,6 +15,7 @@ package relay import ( + "context" "crypto/tls" "encoding/base64" "fmt" @@ -24,8 +25,10 @@ import ( "net/http" "regexp" "strings" + "time" "gopacket/internal/build" + "gopacket/pkg/transport" ) // HTTPRelayClient implements ProtocolClient for relaying NTLM auth to HTTP/HTTPS targets. @@ -73,20 +76,22 @@ func (c *HTTPRelayClient) SetPath(path string) { func (c *HTTPRelayClient) InitConnection() error { // Use a custom transport that keeps connections alive for the NTLM handshake. // The entire 3-leg NTLM exchange must happen on the same TCP connection. - transport := &http.Transport{ + tr := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, DisableKeepAlives: false, // Force single connection reuse for NTLM auth MaxIdleConnsPerHost: 1, - DialContext: (&net.Dialer{ - Timeout: 10 * 1e9, // 10 seconds - }).DialContext, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + return transport.DialContext(ctx, network, addr) + }, } c.httpClient = &http.Client{ - Transport: transport, + Transport: tr, // Don't follow redirects — we need to see the raw 401/302 responses CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse diff --git a/pkg/relay/socks.go b/pkg/relay/socks.go index 38d7ee4..4c425f9 100644 --- a/pkg/relay/socks.go +++ b/pkg/relay/socks.go @@ -447,6 +447,9 @@ func (s *SOCKSServer) handleConnection(conn net.Conn) { func (s *SOCKSServer) handleDNSPassthrough(clientConn net.Conn, host string, port int) { target := fmt.Sprintf("%s:%d", host, port) + // Intentionally uses net.DialTimeout, not transport.DialTimeout: this is + // the outbound leg of our own SOCKS5 server, so routing it through the + // operator's -proxy would double-tunnel. remotConn, err := net.DialTimeout("tcp", target, 10*time.Second) if err != nil { if build.Debug { diff --git a/pkg/relay/winrm_client.go b/pkg/relay/winrm_client.go index 88a0afe..59ad560 100644 --- a/pkg/relay/winrm_client.go +++ b/pkg/relay/winrm_client.go @@ -15,6 +15,7 @@ package relay import ( + "context" "crypto/tls" "encoding/base64" "fmt" @@ -24,8 +25,10 @@ import ( "net/http" "regexp" "strings" + "time" "gopacket/internal/build" + "gopacket/pkg/transport" ) // WinRMRelayClient implements ProtocolClient for relaying NTLM auth to WinRM targets. @@ -50,20 +53,22 @@ func NewWinRMRelayClient(addr string, useTLS bool) *WinRMRelayClient { // InitConnection creates the HTTP client with connection reuse for NTLM handshake. // Implements ProtocolClient. func (c *WinRMRelayClient) InitConnection() error { - transport := &http.Transport{ + tr := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, DisableKeepAlives: false, // Force single connection reuse for NTLM auth MaxIdleConnsPerHost: 1, - DialContext: (&net.Dialer{ - Timeout: 10 * 1e9, // 10 seconds - }).DialContext, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + return transport.DialContext(ctx, network, addr) + }, } c.httpClient = &http.Client{ - Transport: transport, + Transport: tr, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, diff --git a/pkg/tds/sqlr.go b/pkg/tds/sqlr.go index fb46edd..c1f1baa 100644 --- a/pkg/tds/sqlr.go +++ b/pkg/tds/sqlr.go @@ -16,9 +16,10 @@ package tds import ( "fmt" - "net" "strings" "time" + + "gopacket/pkg/transport" ) // SQLRInstance represents a discovered SQL Server instance @@ -31,15 +32,10 @@ type SQLRInstance struct { NamedPipe string } -// GetInstances queries the SQL Server Browser service for instances +// GetInstances queries the SQL Server Browser service for instances. +// Fails under -proxy because SQL Browser is UDP and cannot be tunneled. func GetInstances(server string, timeout time.Duration) ([]SQLRInstance, error) { - // Create UDP connection - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", server, SQLRPort)) - if err != nil { - return nil, err - } - - conn, err := net.DialUDP("udp", nil, addr) + conn, err := transport.DialUDP(fmt.Sprintf("%s:%d", server, SQLRPort)) if err != nil { return nil, err } @@ -66,15 +62,9 @@ func GetInstances(server string, timeout time.Duration) ([]SQLRInstance, error) return parseInstanceResponse(buf[:n]) } -// GetInstancePort queries for a specific instance's port +// GetInstancePort queries for a specific instance's port. Fails under -proxy (UDP). func GetInstancePort(server, instance string, timeout time.Duration) (int, error) { - // Create UDP connection - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", server, SQLRPort)) - if err != nil { - return 0, err - } - - conn, err := net.DialUDP("udp", nil, addr) + conn, err := transport.DialUDP(fmt.Sprintf("%s:%d", server, SQLRPort)) if err != nil { return 0, err } From 990c8bbca032d11ca641de0ab90ab85fb765803b Mon Sep 17 00:00:00 2001 From: psycep Date: Wed, 22 Apr 2026 09:54:09 -0500 Subject: [PATCH 4/7] tools: migrate flags.Parse tools to transport Four tools that already used flags.Parse (and therefore already had -proxy registered transparently) still reached out via net.Dial* or ran DNS resolution past the proxy. Migrate the network calls: - tools/GetADComputers: custom net.Resolver now dials via transport.DialContext, so UDP DNS through the DC surfaces ErrUDPUnderProxy cleanly rather than bypassing -proxy. - tools/changepasswd: kpasswd TCP dial now uses transport.DialTimeout. - tools/smbexec: getLocalIP() short-circuits when transport.IsProxyConfigured(). The "dial UDP to find the local source address" trick has no meaning when traffic flows through a SOCKS5 proxy. - tools/raiseChild: the DNS forest-FQDN fallback (net.LookupHost) is skipped under -proxy with a message pointing the user to -parent-dc, since net.LookupHost goes to the OS resolver and would leak DNS. --- tools/GetADComputers/main.go | 7 ++++--- tools/changepasswd/main.go | 4 ++-- tools/raiseChild/main.go | 10 +++++++++- tools/smbexec/main.go | 10 +++++++++- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/tools/GetADComputers/main.go b/tools/GetADComputers/main.go index 3a00ec2..15ed989 100644 --- a/tools/GetADComputers/main.go +++ b/tools/GetADComputers/main.go @@ -28,6 +28,7 @@ import ( "gopacket/pkg/flags" "gopacket/pkg/ldap" "gopacket/pkg/session" + "gopacket/pkg/transport" ) var ( @@ -161,12 +162,12 @@ func resolveHostname(hostname, dnsServer string) string { return "" } - // Use custom resolver to query the DC's DNS + // Custom resolver using the DC's DNS. Under -proxy, UDP DNS cannot be + // tunneled via SOCKS5; callers should pass an IP directly in that case. resolver := &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - d := net.Dialer{Timeout: 2 * time.Second} - return d.DialContext(ctx, "udp", dnsServer+":53") + return transport.DialContext(ctx, network, dnsServer+":53") }, } diff --git a/tools/changepasswd/main.go b/tools/changepasswd/main.go index d54120e..2875170 100644 --- a/tools/changepasswd/main.go +++ b/tools/changepasswd/main.go @@ -20,7 +20,6 @@ import ( "fmt" "io" "log" - "net" "os" "strings" "time" @@ -33,6 +32,7 @@ import ( "gopacket/pkg/ldap" "gopacket/pkg/session" "gopacket/pkg/smb" + "gopacket/pkg/transport" goldap "github.com/go-ldap/ldap/v3" krbclient "github.com/jcmturner/gokrb5/v8/client" @@ -616,7 +616,7 @@ func doKpasswd(opts *flags.Options, target session.Target, targetUsername, targe // sendKpasswd sends a kpasswd request to the KDC on TCP port 464 and returns the response. func sendKpasswd(kdc string, data []byte) ([]byte, error) { addr := fmt.Sprintf("%s:464", kdc) - conn, err := net.DialTimeout("tcp", addr, 10*time.Second) + conn, err := transport.DialTimeout("tcp", addr, 10) if err != nil { return nil, fmt.Errorf("failed to connect to kpasswd %s: %v", addr, err) } diff --git a/tools/raiseChild/main.go b/tools/raiseChild/main.go index 641ee82..cf48b33 100644 --- a/tools/raiseChild/main.go +++ b/tools/raiseChild/main.go @@ -35,6 +35,7 @@ import ( "gopacket/pkg/kerberos" "gopacket/pkg/session" "gopacket/pkg/smb" + "gopacket/pkg/transport" ) var ( @@ -356,7 +357,14 @@ func discoverParentDC(forestFQDN string, target session.Target, creds *session.C } } - // Fallback: DNS resolution of forest FQDN + // Fallback: DNS resolution of forest FQDN. Disabled under -proxy because + // net.LookupHost uses the OS resolver, which would silently bypass the + // configured SOCKS5 proxy and leak DNS. Callers should pass the parent DC + // explicitly via -parent-dc in that case. + if transport.IsProxyConfigured() { + fmt.Println("[!] DNS fallback disabled under -proxy, pass parent DC explicitly via -parent-dc") + return "" + } addrs, err := net.LookupHost(forestFQDN) if err == nil && len(addrs) > 0 { fmt.Printf("[*] DNS resolved %s to %s\n", forestFQDN, addrs[0]) diff --git a/tools/smbexec/main.go b/tools/smbexec/main.go index 39686fa..f7ea088 100644 --- a/tools/smbexec/main.go +++ b/tools/smbexec/main.go @@ -36,6 +36,7 @@ import ( "gopacket/pkg/flags" "gopacket/pkg/session" "gopacket/pkg/smb" + "gopacket/pkg/transport" ) var ( @@ -446,8 +447,15 @@ func (e *SMBExec) finish() { } } -// getLocalIP determines the local IP address used to reach the target host +// getLocalIP determines the local IP address used to reach the target host. +// Under -proxy the concept doesn't apply (traffic flows via the proxy, not +// directly from this host), so we return "" and let the caller fall back. +// The UDP socket here doesn't actually send packets — it just asks the kernel +// which source address would be picked for a hypothetical connection. func getLocalIP(targetHost string) string { + if transport.IsProxyConfigured() { + return "" + } conn, err := net.Dial("udp", targetHost+":445") if err != nil { return "" From bf605498aa16f82691090c7499cea8ea2818880d Mon Sep 17 00:00:00 2001 From: psycep Date: Wed, 22 Apr 2026 09:54:09 -0500 Subject: [PATCH 5/7] tools: wire -proxy into hand-rolled flag tools Four tools register their flags directly via the stdlib flag package rather than going through flags.Parse, so they didn't inherit -proxy and transport.Configure was never called (silent no-op for the user). Wires each to flags.RegisterProxyFlag, a two-line integration that registers -proxy and returns a finalizer to call after flag.Parse(): - tools/CheckLDAPStatus - tools/DumpNTLMInfo - tools/rpcmap (also adds the pkg/flags import) - tools/mssqlinstance (also adds the pkg/flags import; -proxy will immediately error with ErrUDPUnderProxy since the tool's whole job is a UDP SQL Browser probe, but the error is surfaced clearly instead of silently bypassing) --- tools/CheckLDAPStatus/main.go | 9 ++++++--- tools/DumpNTLMInfo/main.go | 2 ++ tools/mssqlinstance/main.go | 3 +++ tools/rpcmap/main.go | 3 +++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/tools/CheckLDAPStatus/main.go b/tools/CheckLDAPStatus/main.go index 6350df6..5d90736 100644 --- a/tools/CheckLDAPStatus/main.go +++ b/tools/CheckLDAPStatus/main.go @@ -52,9 +52,11 @@ func main() { // Standard flags debug := flag.Bool("debug", false, "Turn DEBUG output ON") ts := flag.Bool("ts", false, "Adds timestamp to every logging output") + configureProxy := flags.RegisterProxyFlag() flags.CheckHelp() flag.Parse() + configureProxy() // Validate required flags if *dcHost == "" && (*dcIP == "" || *domain == "") { @@ -120,12 +122,13 @@ func (c *LDAPChecker) Run() error { } func (c *LDAPChecker) listDCs() ([]string, error) { - // Create custom resolver using the DC as DNS server + // Custom resolver using the DC as DNS server. Under -proxy the resolver's + // UDP attempt will fail at the SOCKS5 layer; supply -dc-host directly to + // skip this lookup when proxied. resolver := &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - d := net.Dialer{Timeout: c.timeout} - return d.DialContext(ctx, "udp", net.JoinHostPort(c.dcIP, "53")) + return transport.DialContext(ctx, network, net.JoinHostPort(c.dcIP, "53")) }, } diff --git a/tools/DumpNTLMInfo/main.go b/tools/DumpNTLMInfo/main.go index 056ef00..91baf3c 100644 --- a/tools/DumpNTLMInfo/main.go +++ b/tools/DumpNTLMInfo/main.go @@ -88,9 +88,11 @@ func main() { debug := flag.Bool("debug", false, "Turn DEBUG output ON") ts := flag.Bool("ts", false, "Adds timestamp to every logging output") + configureProxy := flags.RegisterProxyFlag() flags.CheckHelp() flag.Parse() + configureProxy() if flag.NArg() < 1 { flag.Usage() diff --git a/tools/mssqlinstance/main.go b/tools/mssqlinstance/main.go index 2070792..5395f07 100644 --- a/tools/mssqlinstance/main.go +++ b/tools/mssqlinstance/main.go @@ -20,6 +20,7 @@ import ( "os" "time" + "gopacket/pkg/flags" "gopacket/pkg/tds" ) @@ -58,7 +59,9 @@ Note: Requires the SQL Server Browser service to be running on the target. `, os.Args[0], os.Args[0]) } + configureProxy := flags.RegisterProxyFlag() flag.Parse() + configureProxy() if flag.NArg() < 1 { flag.Usage() diff --git a/tools/rpcmap/main.go b/tools/rpcmap/main.go index 2025260..36ff748 100644 --- a/tools/rpcmap/main.go +++ b/tools/rpcmap/main.go @@ -27,6 +27,7 @@ import ( "gopacket/internal/build" "gopacket/pkg/dcerpc" "gopacket/pkg/dcerpc/epmapper" + "gopacket/pkg/flags" "gopacket/pkg/transport" ) @@ -111,7 +112,9 @@ Examples: `, os.Args[0], os.Args[0], os.Args[0]) } + configureProxy := flags.RegisterProxyFlag() flag.Parse() + configureProxy() if flag.NArg() < 1 { flag.Usage() From f67bee32cbf6a0fe84f7a7f5bc4ecb6cc9504a5c Mon Sep 17 00:00:00 2001 From: psycep Date: Wed, 22 Apr 2026 09:54:09 -0500 Subject: [PATCH 6/7] docs: document -proxy and UDP-under-proxy limitation - README.md: expand "Proxychains Support" into "Proxy Support" with two subsections (proxychains via LD_PRELOAD, and the new -proxy SOCKS5 flag). Adds -proxy to the Common Flags table, adds a quick example, and updates the final Notes line so "all tools work through proxychains" also covers -proxy. - KNOWN_ISSUES.md: add entry #8 "UDP Features Disabled Under -proxy" with a table mapping affected tools to concrete workarounds (supply -port directly, pass -dc-host / -dc-ip / -target-ip, supply -parent-dc). Renumbers "Remaining Gaps" to #9. --- KNOWN_ISSUES.md | 22 +++++++++++++++++++++- README.md | 25 +++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index 762cf81..19b946c 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -102,7 +102,27 @@ When reporting, please include: --- -## 8. Remaining Gaps (Low Priority) +## 8. UDP Features Disabled Under `-proxy` + +**Symptom:** Tools that depend on UDP fail with `UDP disabled under -proxy; the underlying feature cannot be tunneled` when `-proxy` (or `ALL_PROXY`) is set. + +**Details:** SOCKS5 UDP ASSOCIATE is rarely implemented correctly by proxy servers and client libraries. Silently bypassing the proxy for UDP when `-proxy` is configured would leak the operator's real source IP. gopacket therefore refuses UDP when proxied and surfaces a clear error through `transport.ErrUDPUnderProxy`. + +**Affected features:** + +| Feature | Tool(s) | Workaround | +|---------|---------|------------| +| SQL Server Browser discovery (UDP 1434) | `mssqlinstance` | Specify the port directly with `-port 1433` on `mssqlclient`; skip auto-discovery | +| DNS SRV lookup routed through the DC | `CheckLDAPStatus` | Pass `-dc-host ` to skip discovery | +| DNS hostname resolution via the DC | `GetADComputers` | Pass the target as an IP, or `-dc-ip ` | +| Forest-FQDN DNS fallback | `raiseChild` | Pass `-parent-dc ` explicitly | +| Local source-IP discovery | `smbexec` | Set `-target-ip ` manually | + +**Status:** By design. UDP tunneling over SOCKS5 is not a gopacket goal. If your workflow genuinely needs UDP over a proxy, use `proxychains` (which hooks libc at a lower level and can intercept UDP sockets) or a full VPN instead of `-proxy`. + +--- + +## 9. Remaining Gaps (Low Priority) These Impacket features are not yet implemented due to infrastructure requirements: diff --git a/README.md b/README.md index 6743193..5ef624f 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,11 @@ To uninstall: ./install.sh --uninstall ``` -## Proxychains Support +## Proxy Support + +gopacket supports two independent proxying paths. They can also be chained. + +### proxychains (LD_PRELOAD) All gopacket tools work through proxychains. Go binaries normally bypass proxychains because Go's runtime handles DNS and networking internally, skipping the `LD_PRELOAD` hooks that proxychains relies on. gopacket works around this by linking against the system C library for network operations, allowing proxychains to intercept connections normally. @@ -50,6 +54,19 @@ proxychains gopacket-secretsdump 'domain/user:password@target' proxychains gopacket-smbclient -k -no-pass 'domain/user@dc.domain.local' ``` +### Internal SOCKS5 proxy (`-proxy`) + +Every tool accepts `-proxy` to route outbound TCP through a SOCKS5 server without relying on `LD_PRELOAD`. Accepted schemes: `socks5` and `socks5h`. When `-proxy` is unset, the `ALL_PROXY` / `all_proxy` environment variables are consulted as a fallback. + +```bash +gopacket-secretsdump -proxy socks5h://127.0.0.1:1080 'domain/user:password@target' +ALL_PROXY=socks5h://127.0.0.1:1080 gopacket-smbclient 'domain/user:password@target' +``` + +UDP-dependent features are **disabled** under `-proxy` rather than silently leaking packets (SOCKS5 UDP ASSOCIATE is rarely supported by proxies, and bypassing the proxy for UDP would reveal the attacker's real source IP). Affected features and their workarounds are documented in [KNOWN_ISSUES.md #9](KNOWN_ISSUES.md). + +**Chaining:** `-proxy` is compatible with proxychains. The TCP connection to the SOCKS5 proxy itself still goes through libc `connect()`, so `proxychains → gopacket → -proxy → target` works for nested routing scenarios. + ## Documentation See the [Library Developer Guide](https://github.com/mandiant/gopacket/wiki) for full API documentation, code examples, and architecture overview for building custom tools on top of gopacket's 24 protocol packages. @@ -196,6 +213,7 @@ KRB5CCNAME=ticket.ccache gopacket-secretsdump -k -no-pass 'domain/user@target' | `-dc-ip IP` | IP address of the domain controller | | `-target-ip IP` | IP address of the target (when using hostname for Kerberos) | | `-port PORT` | Target port (defaults vary by tool) | +| `-proxy URL` | Route outbound TCP through a SOCKS5 proxy (e.g. `socks5h://127.0.0.1:1080`). UDP features are disabled. | | `-debug` | Enable debug output | ### Quick Examples @@ -218,6 +236,9 @@ sudo gopacket-ntlmrelayx -t smb://target -socks # LDAP relay for RBCD sudo gopacket-ntlmrelayx -t ldaps://dc01.corp.local --delegate-access + +# Route all outbound traffic through a SOCKS5 proxy +gopacket-secretsdump -proxy socks5h://127.0.0.1:1080 'corp.local/admin:pass@dc01.corp.local' ``` ## Library @@ -357,7 +378,7 @@ new tooling stays private. Open-sourcing gopacket narrows that gap. - Kerberos authentication requires a valid ccache file (TGT or service ticket) - For Kerberos, use the FQDN hostname - not an IP address - If `KRB5CCNAME` is not set, tools will look for `.ccache` in the current directory -- All tools work through proxychains +- All tools support both proxychains and an internal `-proxy` SOCKS5 flag (see Proxy Support) - This project is for authorized security testing and research purposes only ## License From e3f315d57b7dea9e12919238104388cdc031919d Mon Sep 17 00:00:00 2001 From: psycep Date: Wed, 22 Apr 2026 10:14:45 -0500 Subject: [PATCH 7/7] version: bump to v0.1.1-beta Bumps the version string across pkg/flags and all tool banners to v0.1.1-beta. Marks the first feature release after the initial beta (adds the -proxy socks flag and UDP-under-proxy guard landing in this PR). Note: the version string is currently duplicated 34 times across 27 tool binaries and flags.go. A follow-up PR should refactor every tool to call flags.Banner() instead of hardcoding the literal, so future bumps are one-line changes. --- pkg/flags/flags.go | 2 +- tools/CheckLDAPStatus/main.go | 2 +- tools/DumpNTLMInfo/main.go | 2 +- tools/describeTicket/main.go | 2 +- tools/getArch/main.go | 2 +- tools/karmaSMB/main.go | 2 +- tools/keylistattack/main.go | 2 +- tools/lookupsid/main.go | 2 +- tools/mssqlclient/main.go | 2 +- tools/mssqlinstance/main.go | 4 ++-- tools/net/main.go | 4 ++-- tools/ntfs-read/main.go | 2 +- tools/ping/main.go | 2 +- tools/ping6/main.go | 2 +- tools/rbcd/main.go | 2 +- tools/reg/main.go | 4 ++-- tools/rpcdump/main.go | 2 +- tools/rpcmap/main.go | 4 ++-- tools/samrdump/main.go | 2 +- tools/services/main.go | 4 ++-- tools/smbserver/main.go | 2 +- tools/sniff/main.go | 4 ++-- tools/sniffer/main.go | 2 +- tools/split/main.go | 2 +- tools/ticketer/main.go | 2 +- tools/tstool/main.go | 4 ++-- tools/wmipersist/main.go | 2 +- 27 files changed, 34 insertions(+), 34 deletions(-) diff --git a/pkg/flags/flags.go b/pkg/flags/flags.go index 0e1748e..4380bbe 100644 --- a/pkg/flags/flags.go +++ b/pkg/flags/flags.go @@ -170,7 +170,7 @@ func Parse() *Options { } // Version is the current gopacket release version. -const Version = "v0.1.0-beta" +const Version = "v0.1.1-beta" // Banner returns the standard gopacket banner string. func Banner() string { diff --git a/tools/CheckLDAPStatus/main.go b/tools/CheckLDAPStatus/main.go index 5d90736..a6343db 100644 --- a/tools/CheckLDAPStatus/main.go +++ b/tools/CheckLDAPStatus/main.go @@ -39,7 +39,7 @@ var ( func main() { flag.Usage = func() { - fmt.Fprintln(os.Stderr, "gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Fprintln(os.Stderr, "gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "LDAP signing and channel binding enumeration utility.") fmt.Fprintln(os.Stderr) diff --git a/tools/DumpNTLMInfo/main.go b/tools/DumpNTLMInfo/main.go index 91baf3c..cc86b82 100644 --- a/tools/DumpNTLMInfo/main.go +++ b/tools/DumpNTLMInfo/main.go @@ -76,7 +76,7 @@ const EPOCH_DIFF = 116444736000000000 func main() { flag.Usage = func() { - fmt.Fprintln(os.Stderr, "gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Fprintln(os.Stderr, "gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Do NTLM authentication and parse information.") fmt.Fprintln(os.Stderr) diff --git a/tools/describeTicket/main.go b/tools/describeTicket/main.go index 742136e..a74a0d4 100644 --- a/tools/describeTicket/main.go +++ b/tools/describeTicket/main.go @@ -966,7 +966,7 @@ func loadCCacheSafe(path string) (ccache *credentials.CCache, err error) { } func printUsage() { - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() fmt.Println("Parses a ccache ticket file and displays credential information.") fmt.Println("With a decryption key, decrypts the ticket and shows the full PAC.") diff --git a/tools/getArch/main.go b/tools/getArch/main.go index cdf1ce1..22921f0 100644 --- a/tools/getArch/main.go +++ b/tools/getArch/main.go @@ -123,7 +123,7 @@ func checkArch(machine string, timeoutSec int) { } func printUsage() { - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() fmt.Println("Gets the target system's OS architecture version") fmt.Println() diff --git a/tools/karmaSMB/main.go b/tools/karmaSMB/main.go index 5184ec2..d531c0b 100644 --- a/tools/karmaSMB/main.go +++ b/tools/karmaSMB/main.go @@ -275,7 +275,7 @@ var NDR_UUID = []byte{ func main() { flag.Usage = func() { - fmt.Fprintf(os.Stderr, "gopacket v0.1.0-beta - Copyright 2026 Google LLC\n\n") + fmt.Fprintf(os.Stderr, "gopacket v0.1.1-beta - Copyright 2026 Google LLC\n\n") fmt.Fprintf(os.Stderr, "For every file request received, this module will return the pathname\n") fmt.Fprintf(os.Stderr, "contents based on extension matching.\n\n") fmt.Fprintf(os.Stderr, "Usage: karmaSMB [options] pathname\n\n") diff --git a/tools/keylistattack/main.go b/tools/keylistattack/main.go index bddba47..626230f 100644 --- a/tools/keylistattack/main.go +++ b/tools/keylistattack/main.go @@ -73,7 +73,7 @@ func main() { } func usage() { - fmt.Fprintf(os.Stderr, `gopacket v0.1.0-beta - Copyright 2026 Google LLC + fmt.Fprintf(os.Stderr, `gopacket v0.1.1-beta - Copyright 2026 Google LLC Performs the KERB-KEY-LIST-REQ attack to dump secrets from the remote machine without executing any agent there. diff --git a/tools/lookupsid/main.go b/tools/lookupsid/main.go index a2c1b90..f28095b 100644 --- a/tools/lookupsid/main.go +++ b/tools/lookupsid/main.go @@ -63,7 +63,7 @@ func main() { } } - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() fmt.Printf("[*] Brute forcing SIDs at %s\n", target.Host) diff --git a/tools/mssqlclient/main.go b/tools/mssqlclient/main.go index bdf33bf..c4dc71f 100644 --- a/tools/mssqlclient/main.go +++ b/tools/mssqlclient/main.go @@ -42,7 +42,7 @@ func main() { os.Exit(1) } - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() // Parse target diff --git a/tools/mssqlinstance/main.go b/tools/mssqlinstance/main.go index 5395f07..62043b5 100644 --- a/tools/mssqlinstance/main.go +++ b/tools/mssqlinstance/main.go @@ -30,7 +30,7 @@ var ( func main() { flag.Usage = func() { - fmt.Fprintf(os.Stderr, `gopacket v0.1.0-beta - Copyright 2026 Google LLC + fmt.Fprintf(os.Stderr, `gopacket v0.1.1-beta - Copyright 2026 Google LLC SQL Server Browser Protocol discovery tool. @@ -68,7 +68,7 @@ Note: Requires the SQL Server Browser service to be running on the target. os.Exit(1) } - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() target := flag.Arg(0) diff --git a/tools/net/main.go b/tools/net/main.go index bcfd625..e8524a6 100644 --- a/tools/net/main.go +++ b/tools/net/main.go @@ -111,7 +111,7 @@ func main() { } } - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() // Connect via SMB @@ -196,7 +196,7 @@ func main() { } func printUsage() { - fmt.Fprintln(os.Stderr, "gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Fprintln(os.Stderr, "gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Usage: net [auth-flags] target [command-flags]") fmt.Fprintln(os.Stderr) diff --git a/tools/ntfs-read/main.go b/tools/ntfs-read/main.go index 7d5caa8..122582a 100644 --- a/tools/ntfs-read/main.go +++ b/tools/ntfs-read/main.go @@ -400,7 +400,7 @@ func cleanPath(path string) string { } func printUsage() { - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() fmt.Println("NTFS explorer (read-only)") fmt.Println() diff --git a/tools/ping/main.go b/tools/ping/main.go index 1355e36..0e333a6 100644 --- a/tools/ping/main.go +++ b/tools/ping/main.go @@ -32,7 +32,7 @@ const ( func main() { if len(os.Args) < 3 { - fmt.Fprintf(os.Stderr, "gopacket v0.1.0-beta - Copyright 2026 Google LLC\n\n") + fmt.Fprintf(os.Stderr, "gopacket v0.1.1-beta - Copyright 2026 Google LLC\n\n") fmt.Fprintf(os.Stderr, "Simple ICMP ping using raw sockets.\n\n") fmt.Fprintf(os.Stderr, "Usage: %s \n", os.Args[0]) fmt.Fprintf(os.Stderr, "\nNote: Requires root/CAP_NET_RAW privileges.\n") diff --git a/tools/ping6/main.go b/tools/ping6/main.go index 14b3961..ae43248 100644 --- a/tools/ping6/main.go +++ b/tools/ping6/main.go @@ -30,7 +30,7 @@ const ( ) func main() { - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() if len(os.Args) < 3 { diff --git a/tools/rbcd/main.go b/tools/rbcd/main.go index e762e93..9c45e28 100644 --- a/tools/rbcd/main.go +++ b/tools/rbcd/main.go @@ -30,7 +30,7 @@ import ( ) func printUsage() { - fmt.Fprintf(os.Stderr, `gopacket v0.1.0-beta - Copyright 2026 Google LLC + fmt.Fprintf(os.Stderr, `gopacket v0.1.1-beta - Copyright 2026 Google LLC usage: rbcd [-h] [-delegate-to DELEGATE_TO] [-delegate-from DELEGATE_FROM] [-action {read,write,remove,flush}] [-use-ldaps] [-debug] [-ts] diff --git a/tools/reg/main.go b/tools/reg/main.go index fe24c3d..326854b 100644 --- a/tools/reg/main.go +++ b/tools/reg/main.go @@ -107,7 +107,7 @@ func main() { } } - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() // Connect via SMB @@ -192,7 +192,7 @@ func main() { } func printUsage() { - fmt.Fprintln(os.Stderr, "gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Fprintln(os.Stderr, "gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Usage: reg [auth-flags] target [command-flags]") fmt.Fprintln(os.Stderr) diff --git a/tools/rpcdump/main.go b/tools/rpcdump/main.go index 7d9081b..64b0402 100644 --- a/tools/rpcdump/main.go +++ b/tools/rpcdump/main.go @@ -37,7 +37,7 @@ func main() { os.Exit(1) } - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() target, creds, err := session.ParseTargetString(opts.TargetStr) diff --git a/tools/rpcmap/main.go b/tools/rpcmap/main.go index 36ff748..bf32b42 100644 --- a/tools/rpcmap/main.go +++ b/tools/rpcmap/main.go @@ -90,7 +90,7 @@ type ifaceResult struct { func main() { flag.Usage = func() { - fmt.Fprintf(os.Stderr, `gopacket v0.1.0-beta - Copyright 2026 Google LLC + fmt.Fprintf(os.Stderr, `gopacket v0.1.1-beta - Copyright 2026 Google LLC Scans for listening MSRPC interfaces. Tries the MGMT interface first, falls back to UUID bruteforce if MGMT is not available. @@ -121,7 +121,7 @@ Examples: os.Exit(1) } - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() if *debug { diff --git a/tools/samrdump/main.go b/tools/samrdump/main.go index 9d66e80..8a32ac0 100644 --- a/tools/samrdump/main.go +++ b/tools/samrdump/main.go @@ -55,7 +55,7 @@ func main() { } } - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() fmt.Printf("[*] Retrieving endpoint list from %s\n", target.Host) diff --git a/tools/services/main.go b/tools/services/main.go index 3a969e4..371af9f 100644 --- a/tools/services/main.go +++ b/tools/services/main.go @@ -100,7 +100,7 @@ func main() { } } - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() // Connect via SMB @@ -390,7 +390,7 @@ func errorControlName(t uint32) string { } func printUsage() { - fmt.Fprintln(os.Stderr, "gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Fprintln(os.Stderr, "gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Usage: services [auth-flags] target [action-flags]") fmt.Fprintln(os.Stderr) diff --git a/tools/smbserver/main.go b/tools/smbserver/main.go index 7cd2844..7eada86 100644 --- a/tools/smbserver/main.go +++ b/tools/smbserver/main.go @@ -366,7 +366,7 @@ func (s *Server) GetShares() []*Share { func main() { flag.Usage = func() { - fmt.Fprintf(os.Stderr, "gopacket v0.1.0-beta - Copyright 2026 Google LLC\n\n") + fmt.Fprintf(os.Stderr, "gopacket v0.1.1-beta - Copyright 2026 Google LLC\n\n") fmt.Fprintf(os.Stderr, "usage: smbserver [-h] [-comment COMMENT] [-username USERNAME]\n") fmt.Fprintf(os.Stderr, " [-password PASSWORD] [-hashes LMHASH:NTHASH] [-ts]\n") fmt.Fprintf(os.Stderr, " [-debug] [-ip INTERFACE_ADDRESS] [-port PORT]\n") diff --git a/tools/sniff/main.go b/tools/sniff/main.go index 81b1909..b6bfb77 100644 --- a/tools/sniff/main.go +++ b/tools/sniff/main.go @@ -41,7 +41,7 @@ var ( func main() { flag.Usage = func() { - fmt.Fprintf(os.Stderr, `gopacket v0.1.0-beta - Copyright 2026 Google LLC + fmt.Fprintf(os.Stderr, `gopacket v0.1.1-beta - Copyright 2026 Google LLC Simple packet sniffer using pcap. @@ -63,7 +63,7 @@ Note: Requires root/CAP_NET_RAW privileges. flag.Parse() - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() // List interfaces mode diff --git a/tools/sniffer/main.go b/tools/sniffer/main.go index f0c0737..77bfe50 100644 --- a/tools/sniffer/main.go +++ b/tools/sniffer/main.go @@ -38,7 +38,7 @@ var protoMap = map[string]int{ } func main() { - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() // Default protocols if none specified diff --git a/tools/split/main.go b/tools/split/main.go index ed09ab2..1980828 100644 --- a/tools/split/main.go +++ b/tools/split/main.go @@ -59,7 +59,7 @@ type ConnectionWriter struct { } func main() { - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() fmt.Println("[!] This tool is deprecated and may be removed in future versions.") fmt.Println() diff --git a/tools/ticketer/main.go b/tools/ticketer/main.go index ba945aa..70d6c53 100644 --- a/tools/ticketer/main.go +++ b/tools/ticketer/main.go @@ -446,7 +446,7 @@ func parseExtraSIDs() []string { } func usage() { - fmt.Fprintf(os.Stderr, "gopacket v0.1.0-beta - Copyright 2026 Google LLC\n\n") + fmt.Fprintf(os.Stderr, "gopacket v0.1.1-beta - Copyright 2026 Google LLC\n\n") fmt.Fprintf(os.Stderr, "Creates Kerberos golden/silver/sapphire tickets based on user options\n\n") fmt.Fprintf(os.Stderr, "Usage: ticketer [options] \n\n") fmt.Fprintf(os.Stderr, "Positional:\n") diff --git a/tools/tstool/main.go b/tools/tstool/main.go index c899575..58a3a0e 100644 --- a/tools/tstool/main.go +++ b/tools/tstool/main.go @@ -108,7 +108,7 @@ func main() { } } - fmt.Println("gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Println("gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Println() // Connect via SMB @@ -809,7 +809,7 @@ func cmdMsg(smbClient *smb.Client, auth *authContext, sessionID int, title, mess } func printUsage() { - fmt.Fprintln(os.Stderr, "gopacket v0.1.0-beta - Copyright 2026 Google LLC") + fmt.Fprintln(os.Stderr, "gopacket v0.1.1-beta - Copyright 2026 Google LLC") fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Usage: tstool [auth-flags] target [action-flags]") fmt.Fprintln(os.Stderr) diff --git a/tools/wmipersist/main.go b/tools/wmipersist/main.go index 5fcd41b..e37b350 100644 --- a/tools/wmipersist/main.go +++ b/tools/wmipersist/main.go @@ -59,7 +59,7 @@ var ( ) func printUsage() { - fmt.Fprintf(os.Stderr, `gopacket v0.1.0-beta - Copyright 2026 Google LLC + fmt.Fprintf(os.Stderr, `gopacket v0.1.1-beta - Copyright 2026 Google LLC usage: wmipersist [-h] [-debug] [-ts] [-com-version MAJOR_VERSION:MINOR_VERSION] [-hashes LMHASH:NTHASH] [-no-pass] [-k] [-aesKey hex key]