From c29e70df5434a82ee43fa59826c67037d07d7b3a Mon Sep 17 00:00:00 2001 From: Bryan McNulty Date: Wed, 12 Mar 2025 10:36:38 -0500 Subject: +Proxy support +Dockerfile --- Dockerfile | 19 +++++ cmd/root.go | 181 +++++++++++++++++++++++++-------------------- cmd/rpc.go | 10 +++ cmd/scmr.go | 6 +- go.mod | 2 +- internal/client/dce/dce.go | 15 ++-- 6 files changed, 140 insertions(+), 93 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6de9006 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:1.24-alpine AS goexec-builder +LABEL builder="true" + +WORKDIR /go/src/ + +COPY cmd/ cmd/ +COPY internal/ internal/ +COPY main.go go.mod go.sum ./ + +ENV CGO_ENABLED=0 + +RUN go mod download +RUN go build -ldflags="-s -w" -o /go/bin/goexec + +FROM alpine:3 AS goexec +COPY --from="goexec-builder" /go/bin/goexec /usr/local/bin/goexec + +WORKDIR /io +ENTRYPOINT ["/usr/local/bin/goexec"] diff --git a/cmd/root.go b/cmd/root.go index f596c75..b44c50e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,104 +1,125 @@ package cmd import ( - "context" - "fmt" - "github.com/RedTeamPentesting/adauth" - "github.com/rs/zerolog" - "github.com/spf13/cobra" - "os" - "regexp" - "strings" + "context" + "fmt" + "github.com/RedTeamPentesting/adauth" + "github.com/rs/zerolog" + "github.com/spf13/cobra" + "net/url" + "os" + "regexp" + "strings" ) var ( - //logFile string - log zerolog.Logger - ctx context.Context - authOpts *adauth.Options + //logFile string + log zerolog.Logger + ctx context.Context + authOpts *adauth.Options - hostname string + hostname string + proxyStr string + proxyUrl *url.URL - // Root flags - debug bool + // Root flags + debug bool - // Generic flags - command string - executable string - executablePath string - executableArgs string - workingDirectory string - windowState string + // Generic flags + command string + executable string + executablePath string + executableArgs string + workingDirectory string + windowState string - rootCmd = &cobra.Command{ - Use: "goexec", - PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { - // For modules that require a full executable path - if executablePath != "" && !regexp.MustCompile(`^([a-zA-Z]:)?\\`).MatchString(executablePath) { - return fmt.Errorf("executable path (-e) must be an absolute Windows path, i.e. C:\\Windows\\System32\\cmd.exe") - } - if command != "" { - p := strings.SplitN(command, " ", 2) - executable = p[0] - if len(p) > 1 { - executableArgs = p[1] - } - } - log = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel).With().Timestamp().Logger() - if debug { - log = log.Level(zerolog.DebugLevel) - } - return - }, - } + rootCmd = &cobra.Command{ + Use: "goexec", + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + // For modules that require a full executable path + if executablePath != "" && !regexp.MustCompile(`^([a-zA-Z]:)?\\`).MatchString(executablePath) { + return fmt.Errorf("executable path (-e) must be an absolute Windows path, i.e. C:\\Windows\\System32\\cmd.exe") + } + if command != "" { + p := strings.SplitN(command, " ", 2) + executable = p[0] + if len(p) > 1 { + executableArgs = p[1] + } + } + log = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel).With().Timestamp().Logger() + if debug { + log = log.Level(zerolog.DebugLevel) + } + return + }, + } ) +func needs(reqs ...func(*cobra.Command, []string) error) (fn func(*cobra.Command, []string) error) { + return func(cmd *cobra.Command, args []string) (err error) { + for _, req := range reqs { + if err = req(cmd, args); err != nil { + return + } + } + return + } +} + func needsTarget(proto string) func(cmd *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) (err error) { - if len(args) != 1 { - return fmt.Errorf("command require exactly one positional argument: [target]") - } - if creds, target, err = authOpts.WithTarget(ctx, proto, args[0]); err != nil { - return fmt.Errorf("failed to parse target: %w", err) - } - if creds == nil { - return fmt.Errorf("no credentials supplied") - } - if target == nil { - return fmt.Errorf("no target supplied") - } - if hostname, err = target.Hostname(ctx); err != nil { - log.Debug().Err(err).Msg("Could not get target hostname") - } - return - } + + return func(cmd *cobra.Command, args []string) (err error) { + if proxyStr != "" { + if proxyUrl, err = url.Parse(proxyStr); err != nil { + return fmt.Errorf("failed to parse proxy URL %q: %w", proxyStr, err) + } + } + if len(args) != 1 { + return fmt.Errorf("command require exactly one positional argument: [target]") + } + if creds, target, err = authOpts.WithTarget(ctx, proto, args[0]); err != nil { + return fmt.Errorf("failed to parse target: %w", err) + } + if creds == nil { + return fmt.Errorf("no credentials supplied") + } + if target == nil { + return fmt.Errorf("no target supplied") + } + if hostname, err = target.Hostname(ctx); err != nil { + log.Debug().Err(err).Msg("Could not get target hostname") + } + return + } } func init() { - ctx = context.Background() + ctx = context.Background() - cobra.EnableCommandSorting = false + cobra.EnableCommandSorting = false - rootCmd.InitDefaultVersionFlag() - rootCmd.InitDefaultHelpCmd() - rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging") + rootCmd.InitDefaultVersionFlag() + rootCmd.InitDefaultHelpCmd() + rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging") + rootCmd.PersistentFlags().StringVarP(&proxyStr, "proxy", "x", "", "Proxy URL") - authOpts = &adauth.Options{Debug: log.Debug().Msgf} - authOpts.RegisterFlags(rootCmd.PersistentFlags()) + authOpts = &adauth.Options{Debug: log.Debug().Msgf} + authOpts.RegisterFlags(rootCmd.PersistentFlags()) - scmrCmdInit() - rootCmd.AddCommand(scmrCmd) - tschCmdInit() - rootCmd.AddCommand(tschCmd) - wmiCmdInit() - rootCmd.AddCommand(wmiCmd) - dcomCmdInit() - rootCmd.AddCommand(dcomCmd) + scmrCmdInit() + rootCmd.AddCommand(scmrCmd) + tschCmdInit() + rootCmd.AddCommand(tschCmd) + wmiCmdInit() + rootCmd.AddCommand(wmiCmd) + dcomCmdInit() + rootCmd.AddCommand(dcomCmd) } func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } } diff --git a/cmd/rpc.go b/cmd/rpc.go index d539400..cfc7a9d 100644 --- a/cmd/rpc.go +++ b/cmd/rpc.go @@ -6,6 +6,7 @@ import ( "github.com/oiweiwei/go-msrpc/dcerpc" "github.com/spf13/cobra" "github.com/spf13/pflag" + "golang.org/x/net/proxy" "regexp" ) @@ -15,6 +16,15 @@ func needsRpcTarget(proto string) func(cmd *cobra.Command, args []string) error if err = needsTarget(proto)(cmd, args); err != nil { return err } + if proxyUrl != nil { + if netDialer, err := proxy.FromURL(proxyUrl, nil); err != nil { + return fmt.Errorf("proxy dialer from URL: %w", err) + } else if dceDialer, ok := netDialer.(dcerpc.Dialer); !ok { + return fmt.Errorf("failed to cast %T to dcerpc.Dialer", netDialer) + } else { + dceConfig.Options = append(dceConfig.Options, dcerpc.WithDialer(dceDialer)) + } + } if argDceStringBinding != "" { dceConfig.Endpoint, err = dcerpc.ParseStringBinding(argDceStringBinding) if err != nil { diff --git a/cmd/scmr.go b/cmd/scmr.go index 9df9ef1..ee2e9dc 100644 --- a/cmd/scmr.go +++ b/cmd/scmr.go @@ -77,7 +77,7 @@ var ( References: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/6a8ca926-9477-4dd4-b766-692fab07227e `, - Args: needsRpcTarget("cifs"), + Args: needs(needsTarget("host"), needsRpcTarget("host")), Run: func(cmd *cobra.Command, args []string) { if scmrServiceName == "" { @@ -137,7 +137,7 @@ References: scmrChangeCmd = &cobra.Command{ Use: "change [target]", Short: "Change an existing Windows service to gain execution", - Args: needsRpcTarget("cifs"), + Args: needs(needsTarget("host"), needsRpcTarget("host")), Run: func(cmd *cobra.Command, args []string) { executor := scmrexec.Module{} @@ -185,7 +185,7 @@ References: Long: `Description: TODO `, - Args: needsRpcTarget("cifs"), + Args: needs(needsTarget("host"), needsRpcTarget("host")), Run: func(cmd *cobra.Command, args []string) { dceConfig.DceOptions = append(dceConfig.DceOptions, dcerpc.WithInsecure()) diff --git a/go.mod b/go.mod index 58828e7..2e9e839 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/rs/zerolog v1.33.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 + golang.org/x/net v0.35.0 ) require ( @@ -27,7 +28,6 @@ require ( github.com/oiweiwei/go-smb2.fork v1.0.0 // indirect github.com/oiweiwei/gokrb5.fork/v9 v9.0.2 // indirect golang.org/x/crypto v0.35.0 // indirect - golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect software.sslmate.com/src/go-pkcs12 v0.5.0 // indirect diff --git a/internal/client/dce/dce.go b/internal/client/dce/dce.go index 85512ac..142eda9 100644 --- a/internal/client/dce/dce.go +++ b/internal/client/dce/dce.go @@ -14,26 +14,23 @@ import ( ) type ConnectionMethodDCEConfig struct { - NoEpm bool // NoEpm disables EPM - EpmAuto bool // EpmAuto will find any suitable endpoint, without any filter - Insecure bool - NoSign bool + NoEpm bool // NoEpm disables EPM + EpmAuto bool // EpmAuto will find any suitable endpoint, without any filter Endpoint *dcerpc.StringBinding // Endpoint is the explicit endpoint passed to dcerpc.WithEndpoint for use without EPM EpmFilter *dcerpc.StringBinding // EpmFilter is the rough filter used to pick an EPM endpoint + Options []dcerpc.Option // Options stores the options that will be passed to all dialers DceOptions []dcerpc.Option // DceOptions are the options passed to dcerpc.Dial EpmOptions []dcerpc.Option // EpmOptions are the options passed to epm.EndpointMapper } -func (cfg *ConnectionMethodDCEConfig) GetDce(ctx context.Context, cred *adauth.Credential, target *adauth.Target, endpoint, object string, opts ...dcerpc.Option) (cc dcerpc.Conn, err error) { - dceOpts := append(opts, cfg.DceOptions...) - epmOpts := cfg.EpmOptions +func (cfg *ConnectionMethodDCEConfig) GetDce(ctx context.Context, cred *adauth.Credential, target *adauth.Target, endpoint, object string, arbOpts ...dcerpc.Option) (cc dcerpc.Conn, err error) { log := zerolog.Ctx(ctx).With(). Str("client", "DCERPC").Logger() // Mandatory logging - dceOpts = append(dceOpts, dcerpc.WithLogger(log)) - epmOpts = append(epmOpts, dcerpc.WithLogger(log)) + dceOpts := append(append(cfg.Options, arbOpts...), append(cfg.DceOptions, dcerpc.WithLogger(log))...) + epmOpts := append(cfg.Options, append(cfg.EpmOptions, dcerpc.WithLogger(log))...) ctx = gssapi.NewSecurityContext(ctx) auth, err := dcerpcauth.AuthenticationOptions(ctx, cred, target, &dcerpcauth.Options{}) -- cgit v1.2.3