diff options
-rw-r--r-- | TODO.md | 2 | ||||
-rw-r--r-- | cmd/root.go | 4 | ||||
-rw-r--r-- | cmd/scmr.go | 3 | ||||
-rw-r--r-- | cmd/wmi.go | 240 | ||||
-rw-r--r-- | go.mod | 14 | ||||
-rw-r--r-- | go.sum | 15 | ||||
-rw-r--r-- | internal/exec/dcom/exec.go | 331 | ||||
-rw-r--r-- | internal/exec/exec.go | 3 | ||||
-rw-r--r-- | internal/exec/wmi/exec.go | 75 | ||||
-rw-r--r-- | internal/exec/wmi/module.go | 4 |
10 files changed, 394 insertions, 297 deletions
@@ -30,9 +30,9 @@ ### Other - [X] Add proxy support - see https://github.com/oiweiwei/go-msrpc/issues/21 +- [ ] `--unsafe` option - allow unsafe OPSEC (i.e. fetching execution output via file write/read) - [ ] Descriptions for all modules and methods - [ ] Add SMB file transfer interface -- [ ] `--ctf` option - allow unsafe OPSEC (i.e. fetching execution output via file write/read) - [ ] README ## Resolve Eventually diff --git a/cmd/root.go b/cmd/root.go index b44c50e..cbc38c0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,7 +23,8 @@ var ( proxyUrl *url.URL // Root flags - debug bool + unsafe bool // not implemented + debug bool // Generic flags command string @@ -103,6 +104,7 @@ func init() { rootCmd.InitDefaultHelpCmd() rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging") rootCmd.PersistentFlags().StringVarP(&proxyStr, "proxy", "x", "", "Proxy URL") + rootCmd.PersistentFlags().BoolVar(&unsafe, "unsafe", false, "[NOT IMPLEMENTED] Don't ask for permission to run unsafe actions") authOpts = &adauth.Options{Debug: log.Debug().Msgf} authOpts.RegisterFlags(rootCmd.PersistentFlags()) diff --git a/cmd/scmr.go b/cmd/scmr.go index ee2e9dc..11e1379 100644 --- a/cmd/scmr.go +++ b/cmd/scmr.go @@ -28,6 +28,7 @@ func scmrCreateCmdInit() { scmrCreateCmd.Flags().BoolVar(&scmrNoStart, "no-start", false, "Don't start service") scmrCreateCmd.Flags().StringVarP(&executablePath, "executable-path", "f", "", "Full path to a remote Windows executable file") scmrCreateCmd.Flags().StringVarP(&executableArgs, "args", "a", "", "Arguments to pass to the executable") + scmrCreateCmd.Flags().BoolVarP(&scmrOutput, "output", "O", false, "Fetch program output") if err := scmrCreateCmd.MarkFlagRequired("executable-path"); err != nil { panic(err) } @@ -58,6 +59,7 @@ var ( scmrDisplayName string scmrNoDelete bool scmrNoStart bool + scmrOutput bool creds *adauth.Credential target *adauth.Target @@ -104,6 +106,7 @@ References: execCfg := &exec.ExecutionConfig{ ExecutablePath: executablePath, ExecutableArgs: executableArgs, + ReturnOutput: scmrOutput, ExecutionMethod: scmrexec.MethodCreate, ExecutionMethodConfig: scmrexec.MethodCreateConfig{ @@ -1,108 +1,111 @@ package cmd import ( - "encoding/json" - "fmt" - "github.com/FalconOpsLLC/goexec/internal/exec" - wmiexec "github.com/FalconOpsLLC/goexec/internal/exec/wmi" - "github.com/spf13/cobra" - "regexp" - "strings" + "encoding/json" + "fmt" + "github.com/FalconOpsLLC/goexec/internal/exec" + wmiexec "github.com/FalconOpsLLC/goexec/internal/exec/wmi" + "github.com/spf13/cobra" ) func wmiCmdInit() { - wmiCustomCmdInit() - wmiCmd.AddCommand(wmiCustomCmd) - wmiProcessCmdInit() - wmiCmd.AddCommand(wmiProcessCmd) + registerRpcFlags(wmiCmd) + wmiCallCmdInit() + wmiCmd.AddCommand(wmiCallCmd) + wmiProcessCmdInit() + wmiCmd.AddCommand(wmiProcessCmd) } -func wmiCustomCmdInit() { - wmiCustomCmd.Flags().StringVarP(&wmiArgMethod, "method", "m", "", `WMI Method to use in the format CLASS.METHOD (i.e. "Win32_Process.Create")`) - wmiCustomCmd.Flags().StringVarP(&wmiArgMethodArgs, "args", "A", "{}", `WMI Method argument(s) in JSON dictionary format (i.e. {"CommandLine":"calc.exe"})`) - if err := wmiCustomCmd.MarkFlagRequired("method"); err != nil { - panic(err) - } +func wmiCallCmdInit() { + wmiCallCmd.Flags().StringVarP(&wmi.Method, "method", "m", "", `WMI Method to call (i.e. "Create")`) + wmiCallCmd.Flags().StringVarP(&wmi.Class, "class", "C", "", `WMI class to instantiate (i.e. "Win32_Process")`) + wmiCallCmd.Flags().StringVarP(&wmi.Args, "args", "A", "{}", `WMI Method argument(s) in JSON dictionary format (i.e. {"CommandLine":"calc.exe"})`) + if err := wmiCallCmd.MarkFlagRequired("method"); err != nil { + panic(err) + } } func wmiProcessCmdInit() { - wmiProcessCmd.Flags().StringVarP(&command, "command", "c", "", "Process command line") - wmiProcessCmd.Flags().StringVarP(&workingDirectory, "directory", "d", `C:\`, "Working directory") - if err := wmiProcessCmd.MarkFlagRequired("command"); err != nil { - panic(err) - } + wmiProcessCmd.Flags().StringVarP(&command, "command", "c", "", "Process command line") + wmiProcessCmd.Flags().StringVarP(&workingDirectory, "directory", "d", `C:\`, "Working directory") + if err := wmiProcessCmd.MarkFlagRequired("command"); err != nil { + panic(err) + } } var ( - // for custom method - wmiArgMethod string - wmiArgMethodArgs string - - wmiClass string - wmiMethod string - wmiMethodArgsMap map[string]any - methodRegex = regexp.MustCompile(`^\w+\.\w+$`) - - wmiCmd = &cobra.Command{ - Use: "wmi", - Short: "Establish execution via WMI", - Args: cobra.NoArgs, - } - wmiCustomCmd = &cobra.Command{ - Use: "custom", - Short: "Execute specified WMI method", - Long: `Description: - The custom method creates an instance of the specified WMI class (-c), + wmi struct { + Namespace string // TODO + Class string + Method string + Args string + } + wmiMethodArgsMap map[string]any + + wmiCmd = &cobra.Command{ + Use: "wmi", + Short: "Establish execution via WMI", + Args: cobra.NoArgs, + } + wmiCallCmd = &cobra.Command{ + Use: "call", + Short: "Execute specified WMI method", + Long: `Description: + The call method creates an instance of the specified WMI class (-c), then calls the provided method (-m) with the provided arguments (-A). References: https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-classes `, - Args: func(cmd *cobra.Command, args []string) (err error) { - if err = needsTarget("cifs")(cmd, args); err == nil { - if wmiArgMethod != "" && !methodRegex.MatchString(wmiArgMethod) { - return fmt.Errorf("invalid CLASS.METHOD syntax: %s", wmiArgMethod) - } - if err = json.Unmarshal([]byte(wmiArgMethodArgs), &wmiMethodArgsMap); err != nil { - err = fmt.Errorf("failed to parse JSON arguments: %w", err) - } - } - return - }, - Run: func(cmd *cobra.Command, args []string) { - module := wmiexec.Module{} - - connCfg := &exec.ConnectionConfig{} - cleanCfg := &exec.CleanupConfig{} - - parts := strings.SplitN(wmiArgMethod, ".", 2) - wmiClass = parts[0] - wmiMethod = parts[1] - - execCfg := &exec.ExecutionConfig{ - ExecutableName: executable, - ExecutableArgs: executableArgs, - ExecutionMethod: wmiexec.MethodCustom, - ExecutionMethodConfig: wmiexec.MethodCustomConfig{ - Class: wmiClass, - Method: wmiMethod, - Arguments: wmiMethodArgsMap, - }, - } - if err := module.Connect(log.WithContext(ctx), creds, target, connCfg); err != nil { - log.Fatal().Err(err).Msg("Connection failed") - } else if err := module.Exec(log.WithContext(ctx), execCfg); err != nil { - log.Fatal().Err(err).Msg("Execution failed") - } else if err := module.Cleanup(log.WithContext(ctx), cleanCfg); err != nil { - log.Error().Err(err).Msg("Cleanup failed") - } - }, - } - - wmiProcessCmd = &cobra.Command{ - Use: "proc", - Short: "Start a Windows process", - Long: `Description: + Args: needs(needsTarget("cifs"), needsRpcTarget("cifs"), func(cmd *cobra.Command, args []string) (err error) { + if err = json.Unmarshal([]byte(wmi.Args), &wmiMethodArgsMap); err != nil { + err = fmt.Errorf("parse JSON arguments: %w", err) + } + return + }), + Run: func(cmd *cobra.Command, args []string) { + + executor := wmiexec.Module{} + cleanCfg := &exec.CleanupConfig{} // TODO + connCfg := &exec.ConnectionConfig{ + ConnectionMethod: exec.ConnectionMethodDCE, + ConnectionMethodConfig: dceConfig, + } + + execCfg := &exec.ExecutionConfig{ + ExecutableName: executable, + ExecutableArgs: executableArgs, + ExecutionMethod: wmiexec.MethodCall, + ExecutionMethodConfig: wmiexec.MethodCallConfig{ + Class: wmi.Class, + Method: wmi.Method, + Arguments: wmiMethodArgsMap, + }, + } + + ctx = log.With(). + Str("module", "wmi"). + Str("method", "proc"). + Logger().WithContext(ctx) + + if err := executor.Connect(ctx, creds, target, connCfg); err != nil { + log.Fatal().Err(err).Msg("Connection failed") + } + defer func() { + if err := executor.Cleanup(ctx, cleanCfg); err != nil { + log.Error().Err(err).Msg("Cleanup failed") + } + }() + if err := executor.Exec(ctx, execCfg); err != nil { + log.Error().Err(err).Msg("Execution failed") + } + }, + } + + wmiProcessCmd = &cobra.Command{ + Use: "proc", + Short: "Start a Windows process", + Long: `Description: The proc method creates an instance of the Win32_Process WMI class, then calls the Win32_Process.Create method with the provided command (-c), and optional working directory (-d). @@ -110,31 +113,42 @@ References: References: https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-process `, - Args: needsTarget("cifs"), - Run: func(cmd *cobra.Command, args []string) { - log = log.With().Str("module", "wmi").Logger() - - module := wmiexec.Module{} - connCfg := &exec.ConnectionConfig{} - cleanCfg := &exec.CleanupConfig{} - - execCfg := &exec.ExecutionConfig{ - ExecutableName: executable, - ExecutableArgs: executableArgs, - ExecutionMethod: wmiexec.MethodProcess, - - ExecutionMethodConfig: wmiexec.MethodProcessConfig{ - Command: command, - WorkingDirectory: workingDirectory, - }, - } - if err := module.Connect(log.WithContext(ctx), creds, target, connCfg); err != nil { - log.Fatal().Err(err).Msg("Connection failed") - } else if err := module.Exec(log.WithContext(ctx), execCfg); err != nil { - log.Fatal().Err(err).Msg("Execution failed") - } else if err := module.Cleanup(log.WithContext(ctx), cleanCfg); err != nil { - log.Error().Err(err).Msg("Cleanup failed") - } - }, - } + Args: needs(needsTarget("cifs"), needsRpcTarget("cifs")), + Run: func(cmd *cobra.Command, args []string) { + + executor := wmiexec.Module{} + cleanCfg := &exec.CleanupConfig{} // TODO + connCfg := &exec.ConnectionConfig{ + ConnectionMethod: exec.ConnectionMethodDCE, + ConnectionMethodConfig: dceConfig, + } + execCfg := &exec.ExecutionConfig{ + ExecutableName: executable, + ExecutableArgs: executableArgs, + ExecutionMethod: wmiexec.MethodProcess, + + ExecutionMethodConfig: wmiexec.MethodProcessConfig{ + Command: command, + WorkingDirectory: workingDirectory, + }, + } + + ctx = log.With(). + Str("module", "wmi"). + Str("method", "proc"). + Logger().WithContext(ctx) + + if err := executor.Connect(ctx, creds, target, connCfg); err != nil { + log.Fatal().Err(err).Msg("Connection failed") + } + defer func() { + if err := executor.Cleanup(ctx, cleanCfg); err != nil { + log.Error().Err(err).Msg("Cleanup failed") + } + }() + if err := executor.Exec(ctx, execCfg); err != nil { + log.Error().Err(err).Msg("Execution failed") + } + }, + } ) @@ -1,15 +1,15 @@ module github.com/FalconOpsLLC/goexec -go 1.24.0 +go 1.24.1 require ( github.com/RedTeamPentesting/adauth v0.1.1-0.20250304075117-acd47d454877 github.com/google/uuid v1.6.0 - github.com/oiweiwei/go-msrpc v1.2.4 - github.com/rs/zerolog v1.33.0 + github.com/oiweiwei/go-msrpc v1.2.5 + github.com/rs/zerolog v1.34.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 - golang.org/x/net v0.35.0 + golang.org/x/net v0.39.0 ) require ( @@ -27,8 +27,8 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect 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/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect software.sslmate.com/src/go-pkcs12 v0.5.0 // indirect ) @@ -44,6 +44,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/oiweiwei/go-msrpc v1.2.4 h1:edFTNHkXqH/cssj0MDf1eRjW8xavdFNN2OrlteC3dRk= github.com/oiweiwei/go-msrpc v1.2.4/go.mod h1:ev+Bg4HdktdaLvwQ2RcwTlgvx7boe+fskcdUlesepdM= +github.com/oiweiwei/go-msrpc v1.2.5 h1:nIWoU7MWLk5l8vb0pgQ+D67GjDRPC4ybiR+OJtgDWdk= +github.com/oiweiwei/go-msrpc v1.2.5/go.mod h1:WoWRPfm90vRNZDJCwOiUXy39vjyQMAFrFj0zkWTThwY= github.com/oiweiwei/go-smb2.fork v1.0.0 h1:xHq/eYPM8hQEO/nwCez8YwHWHC8mlcsgw/Neu52fPN4= github.com/oiweiwei/go-smb2.fork v1.0.0/go.mod h1:h0CzLVvGAmq39izdYVHKyI5cLv6aHdbQAMKEe4dz4N8= github.com/oiweiwei/gokrb5.fork/v9 v9.0.2 h1:JNkvXMuOEWNXJKzLiyROGfdK31/1RQWA9e5gJxAsl50= @@ -52,8 +54,11 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= @@ -74,6 +79,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -83,6 +90,10 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -96,6 +107,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -105,6 +118,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/internal/exec/dcom/exec.go b/internal/exec/dcom/exec.go index b33791d..a34baf4 100644 --- a/internal/exec/dcom/exec.go +++ b/internal/exec/dcom/exec.go @@ -1,186 +1,181 @@ package dcomexec import ( - "context" - "errors" - "fmt" - "github.com/FalconOpsLLC/goexec/internal/client/dce" - "github.com/FalconOpsLLC/goexec/internal/exec" - "github.com/RedTeamPentesting/adauth" - guuid "github.com/google/uuid" - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/midl/uuid" - "github.com/oiweiwei/go-msrpc/msrpc/dcom" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/iremotescmactivator/v0" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0" - "github.com/oiweiwei/go-msrpc/msrpc/dtyp" - "github.com/rs/zerolog" + "context" + "errors" + "fmt" + "github.com/FalconOpsLLC/goexec/internal/client/dce" + "github.com/FalconOpsLLC/goexec/internal/exec" + "github.com/RedTeamPentesting/adauth" + guuid "github.com/google/uuid" + "github.com/oiweiwei/go-msrpc/dcerpc" + "github.com/oiweiwei/go-msrpc/midl/uuid" + "github.com/oiweiwei/go-msrpc/msrpc/dcom" + "github.com/oiweiwei/go-msrpc/msrpc/dcom/iremotescmactivator/v0" + "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut" + "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0" + "github.com/oiweiwei/go-msrpc/msrpc/dtyp" + "github.com/rs/zerolog" ) const ( - DefaultDcomEndpoint = "ncacn_ip_tcp:[135]" + DefaultDcomEndpoint = "ncacn_ip_tcp:[135]" ) var ( - MmcUuid = uuid.MustParse("49B2791A-B1AE-4C90-9B8E-E860BA07F889") - ShellWindowsUuid = uuid.MustParse("9BA05972-F6A8-11CF-A442-00A0C90A8F39") - RandCid = dcom.CID(*dtyp.GUIDFromUUID(uuid.MustParse(guuid.NewString()))) - IDispatchIID = &dcom.IID{ - Data1: 0x20400, - Data2: 0x0, - Data3: 0x0, - Data4: []byte{0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46}, - } - ComVersion = &dcom.COMVersion{ - MajorVersion: 5, - MinorVersion: 7, - } - MmcClsid = dcom.ClassID(*dtyp.GUIDFromUUID(MmcUuid)) - ORPCThis = &dcom.ORPCThis{ - Version: ComVersion, - CID: &RandCid, - } + MmcUuid = uuid.MustParse("49B2791A-B1AE-4C90-9B8E-E860BA07F889") + ShellWindowsUuid = uuid.MustParse("9BA05972-F6A8-11CF-A442-00A0C90A8F39") + RandCid = dcom.CID(*dtyp.GUIDFromUUID(uuid.MustParse(guuid.NewString()))) + IDispatchIID = &dcom.IID{ + Data1: 0x20400, + Data2: 0x0, + Data3: 0x0, + Data4: []byte{0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46}, + } + ComVersion = &dcom.COMVersion{ + MajorVersion: 5, + MinorVersion: 7, + } + MmcClsid = dcom.ClassID(*dtyp.GUIDFromUUID(MmcUuid)) + ORPCThis = &dcom.ORPCThis{ + Version: ComVersion, + CID: &RandCid, + } ) func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target, ccfg *exec.ConnectionConfig) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("method", ccfg.ConnectionMethod). - Str("func", "Exec").Logger() - - if ccfg.ConnectionMethod == exec.ConnectionMethodDCE { - if cfg, ok := ccfg.ConnectionMethodConfig.(dce.ConnectionMethodDCEConfig); !ok { - return fmt.Errorf("invalid configuration for DCE connection method") - } else { - opts := []dcerpc.Option{dcerpc.WithSign(), dcerpc.WithSecurityLevel(0)} - - // Fetch target hostname - if mod.hostname, err = target.Hostname(ctx); err != nil { - log.Debug().Err(err).Msg("Failed to get target hostname") - opts = append(opts, dcerpc.WithTargetName(mod.hostname)) - } - // Create DCE connection - if mod.dce, err = cfg.GetDce(ctx, creds, target, DefaultDcomEndpoint, "", opts...); err != nil { - log.Error().Err(err).Msg("Failed to initialize DCE dialer") - return fmt.Errorf("create DCE dialer: %w", err) - } - - inst := &dcom.InstantiationInfoData{ - ClassID: &MmcClsid, - IID: []*dcom.IID{IDispatchIID}, - ClientCOMVersion: ComVersion, - } - scm := &dcom.SCMRequestInfoData{ - RemoteRequest: &dcom.CustomRemoteRequestSCMInfo{ - RequestedProtocolSequences: []uint16{7}, - }, - } - loc := &dcom.LocationInfoData{} - ac := &dcom.ActivationContextInfoData{} - ap := &dcom.ActivationProperties{ - DestinationContext: 2, - Properties: []dcom.ActivationProperty{inst, ac, loc, scm}, - } - apin, err := ap.ActivationPropertiesIn() - if err != nil { - return err - } - act, err := iremotescmactivator.NewRemoteSCMActivatorClient(ctx, mod.dce) - if err != nil { - return err - } - cr, err := act.RemoteCreateInstance(ctx, &iremotescmactivator.RemoteCreateInstanceRequest{ - ORPCThis: &dcom.ORPCThis{ - Version: ComVersion, - Flags: 1, - CID: &RandCid, - }, - ActPropertiesIn: apin, - }) - if err != nil { - return err - } - log.Info().Msg("RemoteCreateInstance succeeded") - - apout := &dcom.ActivationProperties{} - if err = apout.Parse(cr.ActPropertiesOut); err != nil { - return err - } - si := apout.SCMReplyInfoData() - pi := apout.PropertiesOutInfo() - - if si == nil { - return fmt.Errorf("remote create instance response: SCMReplyInfoData is nil") - } - if pi == nil { - return fmt.Errorf("remote create instance response: PropertiesOutInfo is nil") - } - oIPID := pi.InterfaceData[0].IPID() - - opts = append(opts, si.RemoteReply.OXIDBindings.EndpointsByProtocol("ncacn_ip_tcp")...) - mod.dce, err = cfg.GetDce(ctx, creds, target, DefaultDcomEndpoint, "", opts...) - if err != nil { - return err - } - log.Info().Msg("created new DCERPC dialer") - - mod.dc, err = idispatch.NewDispatchClient(ctx, mod.dce, dcom.WithIPID(oIPID)) - if err != nil { - return err - } - log.Info().Msg("created IDispatch client") - } - } - return + log := zerolog.Ctx(ctx).With(). + Str("method", ccfg.ConnectionMethod). + Str("func", "Exec").Logger() + + if ccfg.ConnectionMethod == exec.ConnectionMethodDCE { + if cfg, ok := ccfg.ConnectionMethodConfig.(dce.ConnectionMethodDCEConfig); !ok { + return errors.New("invalid configuration for DCE connection method") + } else { + opts := []dcerpc.Option{dcerpc.WithSign(), dcerpc.WithSecurityLevel(0)} + + // Create DCE connection + if mod.dce, err = cfg.GetDce(ctx, creds, target, DefaultDcomEndpoint, "", opts...); err != nil { + log.Error().Err(err).Msg("Failed to initialize DCE dialer") + return fmt.Errorf("create DCE dialer: %w", err) + } + + inst := &dcom.InstantiationInfoData{ + ClassID: &MmcClsid, + IID: []*dcom.IID{IDispatchIID}, + ClientCOMVersion: ComVersion, + } + scm := &dcom.SCMRequestInfoData{ + RemoteRequest: &dcom.CustomRemoteRequestSCMInfo{ + RequestedProtocolSequences: []uint16{7}, + }, + } + loc := &dcom.LocationInfoData{} + ac := &dcom.ActivationContextInfoData{} + ap := &dcom.ActivationProperties{ + DestinationContext: 2, + Properties: []dcom.ActivationProperty{inst, ac, loc, scm}, + } + apin, err := ap.ActivationPropertiesIn() + if err != nil { + return err + } + act, err := iremotescmactivator.NewRemoteSCMActivatorClient(ctx, mod.dce) + if err != nil { + return err + } + cr, err := act.RemoteCreateInstance(ctx, &iremotescmactivator.RemoteCreateInstanceRequest{ + ORPCThis: &dcom.ORPCThis{ + Version: ComVersion, + Flags: 1, + CID: &RandCid, + }, + ActPropertiesIn: apin, + }) + if err != nil { + return err + } + log.Info().Msg("RemoteCreateInstance succeeded") + + apout := &dcom.ActivationProperties{} + if err = apout.Parse(cr.ActPropertiesOut); err != nil { + return err + } + si := apout.SCMReplyInfoData() + pi := apout.PropertiesOutInfo() + + if si == nil { + return fmt.Errorf("remote create instance response: SCMReplyInfoData is nil") + } + if pi == nil { + return fmt.Errorf("remote create instance response: PropertiesOutInfo is nil") + } + oIPID := pi.InterfaceData[0].IPID() + + opts = append(opts, si.RemoteReply.OXIDBindings.EndpointsByProtocol("ncacn_ip_tcp")...) // TODO + mod.dce, err = cfg.GetDce(ctx, creds, target, DefaultDcomEndpoint, "", opts...) + if err != nil { + return err + } + log.Info().Msg("created new DCERPC dialer") + + mod.dc, err = idispatch.NewDispatchClient(ctx, mod.dce, dcom.WithIPID(oIPID)) + if err != nil { + return err + } + log.Info().Msg("created IDispatch client") + } + } + return } func (mod *Module) Exec(ctx context.Context, ecfg *exec.ExecutionConfig) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("method", ecfg.ExecutionMethod). - Str("func", "Exec").Logger() - - if ecfg.ExecutionMethod == MethodMmc { - if cfg, ok := ecfg.ExecutionMethodConfig.(MethodMmcConfig); !ok { - return errors.New("invalid configuration") - - } else { - // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/mmc/view-executeshellcommand - method := "Document.ActiveView.ExecuteShellCommand" - log = log.With().Str("classMethod", method).Logger() - - log.Info(). - Str("executable", ecfg.ExecutableName). - Str("arguments", ecfg.ExecutableArgs).Msg("Attempting execution") - - command := &oaut.Variant{ - Size: 5, - VT: 8, - VarUnion: &oaut.Variant_VarUnion{Value: &oaut.Variant_VarUnion_BSTR{BSTR: &oaut.String{Data: ecfg.ExecutableName}}}, - } - directory := &oaut.Variant{ - Size: 5, - VT: 8, - VarUnion: &oaut.Variant_VarUnion{Value: &oaut.Variant_VarUnion_BSTR{BSTR: &oaut.String{Data: cfg.WorkingDirectory}}}, - } - parameters := &oaut.Variant{ - Size: 5, - VT: 8, - VarUnion: &oaut.Variant_VarUnion{Value: &oaut.Variant_VarUnion_BSTR{BSTR: &oaut.String{Data: ecfg.ExecutableArgs}}}, - } - windowState := &oaut.Variant{ - Size: 5, - VT: 8, - VarUnion: &oaut.Variant_VarUnion{Value: &oaut.Variant_VarUnion_BSTR{BSTR: &oaut.String{Data: cfg.WindowState}}}, - } - // Arguments must be passed in reverse order - if _, err := callMethod(ctx, mod.dc, method, windowState, parameters, directory, command); err != nil { - log.Error().Err(err).Msg("Failed to call method") - return fmt.Errorf("call %q: %w", method, err) - } - log.Info().Msg("Method call successful") - } - } - return nil + log := zerolog.Ctx(ctx).With(). + Str("method", ecfg.ExecutionMethod). + Str("func", "Exec").Logger() + + if ecfg.ExecutionMethod == MethodMmc { + if cfg, ok := ecfg.ExecutionMethodConfig.(MethodMmcConfig); !ok { + return errors.New("invalid configuration") + + } else { + // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/mmc/view-executeshellcommand + method := "Document.ActiveView.ExecuteShellCommand" + log = log.With().Str("classMethod", method).Logger() + + log.Info(). + Str("executable", ecfg.ExecutableName). + Str("arguments", ecfg.ExecutableArgs).Msg("Attempting execution") + + command := &oaut.Variant{ + Size: 5, + VT: 8, + VarUnion: &oaut.Variant_VarUnion{Value: &oaut.Variant_VarUnion_BSTR{BSTR: &oaut.String{Data: ecfg.ExecutableName}}}, + } + directory := &oaut.Variant{ + Size: 5, + VT: 8, + VarUnion: &oaut.Variant_VarUnion{Value: &oaut.Variant_VarUnion_BSTR{BSTR: &oaut.String{Data: cfg.WorkingDirectory}}}, + } + parameters := &oaut.Variant{ + Size: 5, + VT: 8, + VarUnion: &oaut.Variant_VarUnion{Value: &oaut.Variant_VarUnion_BSTR{BSTR: &oaut.String{Data: ecfg.ExecutableArgs}}}, + } + windowState := &oaut.Variant{ + Size: 5, + VT: 8, + VarUnion: &oaut.Variant_VarUnion{Value: &oaut.Variant_VarUnion_BSTR{BSTR: &oaut.String{Data: cfg.WindowState}}}, + } + // Arguments must be passed in reverse order + if _, err := callMethod(ctx, mod.dc, method, windowState, parameters, directory, command); err != nil { + log.Error().Err(err).Msg("Failed to call method") + return fmt.Errorf("call %q: %w", method, err) + } + log.Info().Msg("Method call successful") + } + } + return nil } diff --git a/internal/exec/exec.go b/internal/exec/exec.go index f3fe7ae..db83d91 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -28,8 +28,7 @@ type ExecutionConfig struct { ExecutionMethod string // ExecutionMethod represents the specific execution strategy used by the module. ExecutionMethodConfig interface{} - //ExecutionOutput string // not implemented - //ExecutionOutputConfig interface{} // not implemented + ReturnOutput bool } type ShellConfig struct { diff --git a/internal/exec/wmi/exec.go b/internal/exec/wmi/exec.go index 5f47738..b61bf5c 100644 --- a/internal/exec/wmi/exec.go +++ b/internal/exec/wmi/exec.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/FalconOpsLLC/goexec/internal/client/dce" "github.com/FalconOpsLLC/goexec/internal/exec" "github.com/RedTeamPentesting/adauth" "github.com/RedTeamPentesting/adauth/dcerpcauth" @@ -21,6 +22,7 @@ import ( const ( ProtocolSequenceRPC uint16 = 7 ProtocolSequenceNP uint16 = 15 + DefaultWmiEndpoint string = "ncacn_ip_tcp:[135]" ) var ( @@ -45,7 +47,74 @@ func (mod *Module) Cleanup(ctx context.Context, _ *exec.CleanupConfig) (err erro return } -func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target, _ *exec.ConnectionConfig) (err error) { +func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target, ccfg *exec.ConnectionConfig) (err error) { + + log := zerolog.Ctx(ctx).With(). + Str("method", ccfg.ConnectionMethod). + Str("func", "Connect").Logger() + + if cfg, ok := ccfg.ConnectionMethodConfig.(dce.ConnectionMethodDCEConfig); !ok { + return errors.New("invalid configuration for DCE connection method") + } else { + var dceOpts []dcerpc.Option + + // Create DCE connection + if mod.dce, err = cfg.GetDce(ctx, creds, target, DefaultWmiEndpoint, "", dceOpts...); err != nil { + log.Error().Err(err).Msg("Failed to initialize DCE dialer") + return fmt.Errorf("create DCE dialer: %w", err) + } + ia, err := iactivation.NewActivationClient(ctx, mod.dce) + if err != nil { + log.Error().Err(err).Msg("Failed to create activation client") + return fmt.Errorf("create activation client: %w", err) + } + act, err := ia.RemoteActivation(ctx, &iactivation.RemoteActivationRequest{ + ORPCThis: ORPCThis, + ClassID: wmi.Level1LoginClassID.GUID(), + IIDs: []*dcom.IID{iwbemlevel1login.Level1LoginIID}, + RequestedProtocolSequences: []uint16{ProtocolSequenceRPC, ProtocolSequenceNP}, // TODO: dynamic + }) + if err != nil { + return fmt.Errorf("request remote activation: %w", err) + } + if act.HResult != 0 { + return fmt.Errorf("remote activation failed with code %d", act.HResult) + } + retBinds := act.OXIDBindings.GetStringBindings() + if len(act.InterfaceData) < 1 || len(retBinds) < 1 { + return errors.New("remote activation failed") + } + ipid := act.InterfaceData[0].GetStandardObjectReference().Std.IPID + for _, sb := range retBinds { + //sb.NetworkAddr = target.AddressWithoutPort() // TODO: check if sb.NetworkAddr contains the port + dceOpts = append(dceOpts, dcerpc.WithEndpoint(sb.String())) + } + + if mod.dce, err = cfg.GetDce(ctx, creds, target, DefaultWmiEndpoint, "", dceOpts...); err != nil { + log.Error().Err(err).Msg("Failed to initialize secondary DCE dialer") + } + loginClient, err := iwbemlevel1login.NewLevel1LoginClient(ctx, mod.dce, dcom.WithIPID(ipid)) + if err != nil { + return fmt.Errorf("initialize wbem login client: %w", err) + } + login, err := loginClient.NTLMLogin(ctx, &iwbemlevel1login.NTLMLoginRequest{ + This: ORPCThis, + NetworkResource: "//./root/cimv2", // TODO: make this dynamic + }) + if err != nil { + return fmt.Errorf("ntlm login: %w", err) + } + + mod.sc, err = iwbemservices.NewServicesClient(ctx, mod.dce, dcom.WithIPID(login.Namespace.InterfacePointer().IPID())) + if err != nil { + return fmt.Errorf("iwbemservices.NewServicesClient: %w", err) + } + } + + return +} + +func (mod *Module) _Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target, _ *exec.ConnectionConfig) (err error) { var baseOpts, authOpts []dcerpc.Option var ipid *dcom.IPID // This will store the IPID of the remote instance @@ -157,8 +226,8 @@ func (mod *Module) Exec(ctx context.Context, ecfg *exec.ExecutionConfig) (err er Str("module", "tsch"). Str("method", ecfg.ExecutionMethod).Logger() - if ecfg.ExecutionMethod == MethodCustom { - if cfg, ok := ecfg.ExecutionMethodConfig.(MethodCustomConfig); !ok { + if ecfg.ExecutionMethod == MethodCall { + if cfg, ok := ecfg.ExecutionMethodConfig.(MethodCallConfig); !ok { return errors.New("invalid execution configuration") } else { diff --git a/internal/exec/wmi/module.go b/internal/exec/wmi/module.go index f90af42..0e83aa8 100644 --- a/internal/exec/wmi/module.go +++ b/internal/exec/wmi/module.go @@ -16,7 +16,7 @@ type Module struct { sc iwbemservices.ServicesClient } -type MethodCustomConfig struct { +type MethodCallConfig struct { Class string Method string Arguments map[string]any @@ -28,6 +28,6 @@ type MethodProcessConfig struct { } const ( - MethodCustom = "custom" + MethodCall = "call" MethodProcess = "process" ) |