aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan McNulty <bryanmcnulty@protonmail.com>2025-03-12 10:36:38 -0500
committerBryan McNulty <bryanmcnulty@protonmail.com>2025-03-12 10:36:38 -0500
commitc29e70df5434a82ee43fa59826c67037d07d7b3a (patch)
treeadea65dcb7c7f2d3c461e0b98de444519c03bb42
parent8a2631d9348c81a724e30b0e2913f3e7bb1bb56f (diff)
downloadgoexec-c29e70df5434a82ee43fa59826c67037d07d7b3a.tar.gz
goexec-c29e70df5434a82ee43fa59826c67037d07d7b3a.zip
+Proxy support +Dockerfile
-rw-r--r--Dockerfile19
-rw-r--r--cmd/root.go181
-rw-r--r--cmd/rpc.go10
-rw-r--r--cmd/scmr.go6
-rw-r--r--go.mod2
-rw-r--r--internal/client/dce/dce.go15
6 files changed, 140 insertions, 93 deletions
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{})