Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <hostname>` to skip discovery |
| DNS hostname resolution via the DC | `GetADComputers` | Pass the target as an IP, or `-dc-ip <ip>` |
| Forest-FQDN DNS fallback | `raiseChild` | Pass `-parent-dc <ip>` explicitly |
| Local source-IP discovery | `smbexec` | Set `-target-ip <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:

Expand Down
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 `<username>.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
Expand Down
49 changes: 39 additions & 10 deletions pkg/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]")
Expand All @@ -46,6 +47,7 @@ type Options struct {
TargetIP string
Port int
IPv6 bool
Proxy string

// Utility
InputFile string
Expand All @@ -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,
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand All @@ -129,19 +166,11 @@ 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
}

// 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 {
Expand All @@ -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
Expand Down
15 changes: 10 additions & 5 deletions pkg/relay/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package relay

import (
"context"
"crypto/tls"
"encoding/base64"
"fmt"
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions pkg/relay/socks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
15 changes: 10 additions & 5 deletions pkg/relay/winrm_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package relay

import (
"context"
"crypto/tls"
"encoding/base64"
"fmt"
Expand All @@ -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.
Expand All @@ -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
},
Expand Down
24 changes: 7 additions & 17 deletions pkg/tds/sqlr.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ package tds

import (
"fmt"
"net"
"strings"
"time"

"gopacket/pkg/transport"
)

// SQLRInstance represents a discovered SQL Server instance
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down
Loading