From 04066372096719ccb28cff2ec23b202d398579db Mon Sep 17 00:00:00 2001 From: aimogging Date: Sat, 25 Apr 2026 23:14:29 -0400 Subject: [PATCH 1/2] wmi/dcom: route dcerpc.Dial through transport.Dialer for proxy support wmiexec, wmiquery, wmipersist, and dcomexec called upstream go-msrpc/dcerpc.Dial directly with no WithDialer option, so connections fell back to net.Dialer. That bypassed both the SOCKS5 dialer that -proxy and ALL_PROXY configure, and the libc connect() hook that proxychains relies on. Tools that go through pkg/dcerpc.DialTCP (secretsdump et al.) were unaffected. Add a DialContext method to transport.Dialer so it satisfies the upstream dcerpc.Dialer interface, then pass dcerpc.WithDialer(&transport.Dialer{}) into every dcerpc.Dial call in the four affected tools (the EPM connect on 135 and the OXID-resolved high-port connect). Verified end-to-end against a live target: -proxy, ALL_PROXY, and proxychains all succeed and tcpdump on the egress interface shows zero packets to the target on port 135. --- pkg/transport/tcp.go | 10 +++++++++- tools/dcomexec/main.go | 10 ++++++++-- tools/wmiexec/main.go | 10 ++++++++-- tools/wmipersist/main.go | 10 ++++++++-- tools/wmiquery/main.go | 10 ++++++++-- 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/pkg/transport/tcp.go b/pkg/transport/tcp.go index fed17d3..721c0ae 100644 --- a/pkg/transport/tcp.go +++ b/pkg/transport/tcp.go @@ -71,7 +71,8 @@ func DialTLS(network, address string, config *tls.Config) (*tls.Conn, error) { } // 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. +// Dial or DialContext method (e.g. pkg/smb, pkg/ldap, go-msrpc/dcerpc). +// Respects the configured proxy. type Dialer struct { TimeoutSec int } @@ -81,6 +82,13 @@ func (d *Dialer) Dial(network, address string) (net.Conn, error) { return DialTimeout(network, address, d.TimeoutSec) } +// DialContext establishes a TCP connection, honoring ctx for cancellation and +// deadline. Routes through the proxy if configured. Satisfies the +// go-msrpc/dcerpc.Dialer interface. +func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + return DialContext(ctx, network, address) +} + func splitHostPort(address string) (host, port string, err error) { host, port, err = net.SplitHostPort(address) if err != nil { diff --git a/tools/dcomexec/main.go b/tools/dcomexec/main.go index 8750fb1..6133736 100644 --- a/tools/dcomexec/main.go +++ b/tools/dcomexec/main.go @@ -56,6 +56,7 @@ import ( "github.com/mandiant/gopacket/pkg/kerberos" "github.com/mandiant/gopacket/pkg/session" "github.com/mandiant/gopacket/pkg/smb" + "github.com/mandiant/gopacket/pkg/transport" ) // IDispatch invoke flags @@ -330,8 +331,13 @@ func (e *DCOMExec) connect() error { host = e.creds.DCIP } + // Route DCE/RPC TCP connects through gopacket's transport so -proxy / + // proxychains take effect. Without this, upstream falls back to net.Dialer + // which bypasses both the SOCKS5 dialer and the libc connect() hook. + dialer := dcerpc.WithDialer(&transport.Dialer{}) + // Connect to endpoint mapper (port 135) - conn, err := dcerpc.Dial(e.ctx, net.JoinHostPort(host, "135")) + conn, err := dcerpc.Dial(e.ctx, net.JoinHostPort(host, "135"), dialer) if err != nil { return fmt.Errorf("failed to connect to endpoint mapper: %v", err) } @@ -400,7 +406,7 @@ func (e *DCOMExec) connect() error { } // Connect to the OXID endpoint - oxidConn, err := dcerpc.Dial(e.ctx, host, endpoints...) + oxidConn, err := dcerpc.Dial(e.ctx, host, append([]dcerpc.Option{dialer}, endpoints...)...) if err != nil { return fmt.Errorf("failed to connect to OXID endpoint: %v", err) } diff --git a/tools/wmiexec/main.go b/tools/wmiexec/main.go index 80aec45..fced359 100644 --- a/tools/wmiexec/main.go +++ b/tools/wmiexec/main.go @@ -59,6 +59,7 @@ import ( "github.com/mandiant/gopacket/pkg/kerberos" "github.com/mandiant/gopacket/pkg/session" "github.com/mandiant/gopacket/pkg/smb" + "github.com/mandiant/gopacket/pkg/transport" ) var ( @@ -193,9 +194,14 @@ func main() { ctx := gssapi.NewSecurityContext(context.Background()) + // Route DCE/RPC TCP connects through gopacket's transport so -proxy / + // proxychains take effect. Without this, upstream falls back to net.Dialer + // which bypasses both the SOCKS5 dialer and the libc connect() hook. + dialer := dcerpc.WithDialer(&transport.Dialer{}) + // 1. Connect to Endpoint Mapper (Port 135) log.Info().Msgf("Connecting to %s:135", target.Host) - cc, err := dcerpc.Dial(ctx, net.JoinHostPort(target.Host, "135")) + cc, err := dcerpc.Dial(ctx, net.JoinHostPort(target.Host, "135"), dialer) if err != nil { fmt.Fprintf(os.Stderr, "[-] Dial 135 failed: %v\n", err) os.Exit(1) @@ -249,7 +255,7 @@ func main() { os.Exit(1) } - wcc, err := dcerpc.Dial(ctx, target.Host, endpoints...) + wcc, err := dcerpc.Dial(ctx, target.Host, append([]dcerpc.Option{dialer}, endpoints...)...) if err != nil { fmt.Fprintf(os.Stderr, "[-] Dial WMI failed: %v\n", err) os.Exit(1) diff --git a/tools/wmipersist/main.go b/tools/wmipersist/main.go index 366865f..1f6f060 100644 --- a/tools/wmipersist/main.go +++ b/tools/wmipersist/main.go @@ -52,6 +52,7 @@ import ( "github.com/mandiant/gopacket/pkg/flags" "github.com/mandiant/gopacket/pkg/kerberos" "github.com/mandiant/gopacket/pkg/session" + "github.com/mandiant/gopacket/pkg/transport" ) var ( @@ -289,9 +290,14 @@ func main() { connectAddr = target.IP } + // Route DCE/RPC TCP connects through gopacket's transport so -proxy / + // proxychains take effect. Without this, upstream falls back to net.Dialer + // which bypasses both the SOCKS5 dialer and the libc connect() hook. + dialer := dcerpc.WithDialer(&transport.Dialer{}) + // 1. Connect to Endpoint Mapper (Port 135) log.Info().Msgf("Connecting to %s:135", connectAddr) - cc, err := dcerpc.Dial(ctx, net.JoinHostPort(connectAddr, "135")) + cc, err := dcerpc.Dial(ctx, net.JoinHostPort(connectAddr, "135"), dialer) if err != nil { fmt.Fprintf(os.Stderr, "[-] Dial 135 failed: %v\n", err) os.Exit(1) @@ -350,7 +356,7 @@ func main() { os.Exit(1) } - wcc, err := dcerpc.Dial(ctx, connectAddr, endpoints...) + wcc, err := dcerpc.Dial(ctx, connectAddr, append([]dcerpc.Option{dialer}, endpoints...)...) if err != nil { fmt.Fprintf(os.Stderr, "[-] Dial WMI failed: %v\n", err) os.Exit(1) diff --git a/tools/wmiquery/main.go b/tools/wmiquery/main.go index c85086f..e6cec13 100644 --- a/tools/wmiquery/main.go +++ b/tools/wmiquery/main.go @@ -56,6 +56,7 @@ import ( "github.com/mandiant/gopacket/pkg/flags" "github.com/mandiant/gopacket/pkg/kerberos" "github.com/mandiant/gopacket/pkg/session" + "github.com/mandiant/gopacket/pkg/transport" ) // WBEM flags @@ -204,9 +205,14 @@ func main() { ctx := gssapi.NewSecurityContext(context.Background()) + // Route DCE/RPC TCP connects through gopacket's transport so -proxy / + // proxychains take effect. Without this, upstream falls back to net.Dialer + // which bypasses both the SOCKS5 dialer and the libc connect() hook. + dialer := dcerpc.WithDialer(&transport.Dialer{}) + // 1. Connect to Endpoint Mapper (Port 135) log.Info().Msgf("Connecting to %s:135", target.Host) - cc, err := dcerpc.Dial(ctx, net.JoinHostPort(target.Host, "135")) + cc, err := dcerpc.Dial(ctx, net.JoinHostPort(target.Host, "135"), dialer) if err != nil { fmt.Fprintf(os.Stderr, "[-] Dial 135 failed: %v\n", err) os.Exit(1) @@ -265,7 +271,7 @@ func main() { os.Exit(1) } - wcc, err := dcerpc.Dial(ctx, target.Host, endpoints...) + wcc, err := dcerpc.Dial(ctx, target.Host, append([]dcerpc.Option{dialer}, endpoints...)...) if err != nil { fmt.Fprintf(os.Stderr, "[-] Dial WMI failed: %v\n", err) os.Exit(1) From 86639f942fdd9922ab01875c8d9e19f99ff634fb Mon Sep 17 00:00:00 2001 From: aimogging Date: Mon, 27 Apr 2026 00:08:35 -0400 Subject: [PATCH 2/2] wmiexec: move sharing violation check to smbClient.Rm --- tools/wmiexec/main.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/wmiexec/main.go b/tools/wmiexec/main.go index fced359..111a727 100644 --- a/tools/wmiexec/main.go +++ b/tools/wmiexec/main.go @@ -516,15 +516,16 @@ func (e *WMIExec) retrieveOutput(filename string) (string, error) { content, err := smbClient.Cat(filename) if err == nil { // Delete the output file - smbClient.Rm(filename) + if err := smbClient.Rm(filename); err != nil { + // If sharing violation, command is still running + if strings.Contains(err.Error(), "share access flags are incompatible") { + e.log.Debug().Msg("Output file in use, waiting...") + continue + } + } return content, nil } - // If sharing violation, command is still running - if strings.Contains(err.Error(), "STATUS_SHARING_VIOLATION") { - e.log.Debug().Msg("Output file in use, waiting...") - continue - } // If file not found, keep waiting if strings.Contains(err.Error(), "STATUS_OBJECT_NAME_NOT_FOUND") {