diff options
author | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-03-08 08:35:09 -0600 |
---|---|---|
committer | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-03-08 08:35:09 -0600 |
commit | 8360747c22987c8f7de7b4d19cf2d6ee68994183 (patch) | |
tree | 937bcd554ee9b7442e66bfa771b6a25016202211 | |
parent | 7574b7370be083ff563fa8ad6d01d5ac776d7e4d (diff) | |
download | goexec-8360747c22987c8f7de7b4d19cf2d6ee68994183.tar.gz goexec-8360747c22987c8f7de7b4d19cf2d6ee68994183.zip |
Some quick fixes and validations
-rw-r--r-- | TODO.md | 16 | ||||
-rw-r--r-- | cmd/scmr.go | 184 | ||||
-rw-r--r-- | cmd/tsch.go | 36 | ||||
-rw-r--r-- | internal/exec/tsch/exec.go | 22 | ||||
-rw-r--r-- | internal/exec/tsch/tsch.go | 18 |
5 files changed, 144 insertions, 132 deletions
@@ -4,9 +4,17 @@ ### Higher Priority - [X] Add WMI module +- [X] Clean up TSCH module + +- [ ] Clean up SCMR module + - [ ] add dynamic string binding support + - [ ] general clean up. Use TSCH & WMI as reference + +- [ ] Add DCOM module + - [ ] MMC20.Application method + - [ ] Add psexec module (RemComSvc) -- [ ] Testing against different Windows machines -- [ ] Testing from Windows (compile to PE) + - [ ] Add support for dynamic service executable (of course) ### Other - [ ] Fix SCMR `change` method so that dependencies field isn't permanently overwritten @@ -14,6 +22,10 @@ - [ ] Standardize modules to interface for future use - [ ] Add command to tsch - update task if it already exists. See https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/849c131a-64e4-46ef-b015-9d4c599c5167 (`flags` argument) +### Testing +- [ ] Testing against different Windows machines & versions +- [ ] Testing from Windows (compile to PE) + ## Resolve Eventually ### Higher Priority diff --git a/cmd/scmr.go b/cmd/scmr.go index 8d453a5..f2f0d51 100644 --- a/cmd/scmr.go +++ b/cmd/scmr.go @@ -1,116 +1,116 @@ package cmd import ( - "fmt" - "github.com/FalconOpsLLC/goexec/internal/exec" - scmrexec2 "github.com/FalconOpsLLC/goexec/internal/exec/scmr" - "github.com/FalconOpsLLC/goexec/internal/windows" - "github.com/RedTeamPentesting/adauth" - "github.com/spf13/cobra" + "fmt" + "github.com/FalconOpsLLC/goexec/internal/exec" + "github.com/FalconOpsLLC/goexec/internal/windows" + "github.com/RedTeamPentesting/adauth" + "github.com/spf13/cobra" - scmrexec "github.com/FalconOpsLLC/goexec/internal/exec/scmr" + scmrexec "github.com/FalconOpsLLC/goexec/internal/exec/scmr" ) func scmrCmdInit() { - scmrCmd.PersistentFlags().StringVarP(&executablePath, "executable-path", "f", "", "Full path to remote Windows executable") - scmrCmd.PersistentFlags().StringVarP(&executableArgs, "args", "a", "", "Arguments to pass to executable") - scmrCmd.PersistentFlags().StringVarP(&scmrName, "service-name", "s", "", "Name of service to create or modify") + registerRpcFlags(scmrCmd) + scmrCmd.PersistentFlags().StringVarP(&executablePath, "executable-path", "f", "", "Full path to remote Windows executable") + scmrCmd.PersistentFlags().StringVarP(&executableArgs, "args", "a", "", "Arguments to pass to executable") + scmrCmd.PersistentFlags().StringVarP(&scmrName, "service-name", "s", "", "Name of service to create or modify") - scmrCmd.MarkPersistentFlagRequired("executable-path") - scmrCmd.MarkPersistentFlagRequired("service-name") + scmrCmd.MarkPersistentFlagRequired("executable-path") + scmrCmd.MarkPersistentFlagRequired("service-name") - scmrCmd.AddCommand(scmrChangeCmd) - scmrCreateCmdInit() - scmrCmd.AddCommand(scmrCreateCmd) - scmrChangeCmdInit() + scmrCmd.AddCommand(scmrChangeCmd) + scmrCreateCmdInit() + scmrCmd.AddCommand(scmrCreateCmd) + scmrChangeCmdInit() } func scmrChangeCmdInit() { - scmrChangeCmd.Flags().StringVarP(&scmrDisplayName, "display-name", "n", "", "Display name of service to create") - scmrChangeCmd.Flags().BoolVar(&scmrNoStart, "no-start", false, "Don't start service") + scmrChangeCmd.Flags().StringVarP(&scmrDisplayName, "display-name", "n", "", "Display name of service to create") + scmrChangeCmd.Flags().BoolVar(&scmrNoStart, "no-start", false, "Don't start service") } func scmrCreateCmdInit() { - scmrCreateCmd.Flags().BoolVar(&scmrNoDelete, "no-delete", false, "Don't delete service after execution") + scmrCreateCmd.Flags().BoolVar(&scmrNoDelete, "no-delete", false, "Don't delete service after execution") } var ( - // scmr arguments - scmrName string - scmrDisplayName string - scmrNoDelete bool - scmrNoStart bool + // scmr arguments + scmrName string + scmrDisplayName string + scmrNoDelete bool + scmrNoStart bool - scmrArgs = func(cmd *cobra.Command, args []string) (err error) { - if len(args) != 1 { - return fmt.Errorf("expected exactly 1 positional argument, got %d", len(args)) - } - if creds, target, err = authOpts.WithTarget(ctx, "cifs", args[0]); err != nil { - return fmt.Errorf("failed to parse target: %w", err) - } - log.Debug().Str("target", args[0]).Msg("Resolved target") - return nil - } + scmrArgs = func(cmd *cobra.Command, args []string) (err error) { + if len(args) != 1 { + return fmt.Errorf("expected exactly 1 positional argument, got %d", len(args)) + } + if creds, target, err = authOpts.WithTarget(ctx, "cifs", args[0]); err != nil { + return fmt.Errorf("failed to parse target: %w", err) + } + log.Debug().Str("target", args[0]).Msg("Resolved target") + return nil + } - creds *adauth.Credential - target *adauth.Target + creds *adauth.Credential + target *adauth.Target - scmrCmd = &cobra.Command{ - Use: "scmr", - Short: "Establish execution via SCMR", - Args: cobra.NoArgs, - } - scmrCreateCmd = &cobra.Command{ - Use: "create [target]", - Short: "Create & run a new Windows service to gain execution", - Args: scmrArgs, - RunE: func(cmd *cobra.Command, args []string) (err error) { - if scmrNoDelete { - log.Warn().Msg("Service will not be deleted after execution") - } - if scmrDisplayName == "" { - scmrDisplayName = scmrName - log.Warn().Msg("No display name specified, using service name as display name") - } - executor := scmrexec.Module{} - execCfg := &exec.ExecutionConfig{ - ExecutablePath: executablePath, - ExecutableArgs: executableArgs, - ExecutionMethod: scmrexec2.MethodCreate, + scmrCmd = &cobra.Command{ + Use: "scmr", + Short: "Establish execution via SCMR", + Args: cobra.NoArgs, + } + scmrCreateCmd = &cobra.Command{ + Use: "create [target]", + Short: "Create & run a new Windows service to gain execution", + Args: scmrArgs, + RunE: func(cmd *cobra.Command, args []string) (err error) { + if scmrNoDelete { + log.Warn().Msg("Service will not be deleted after execution") + } + if scmrDisplayName == "" { + scmrDisplayName = scmrName + log.Warn().Msg("No display name specified, using service name as display name") + } + executor := scmrexec.Module{} + execCfg := &exec.ExecutionConfig{ + ExecutablePath: executablePath, + ExecutableArgs: executableArgs, + ExecutionMethod: scmrexec.MethodCreate, - ExecutionMethodConfig: scmrexec2.MethodCreateConfig{ - NoDelete: scmrNoDelete, - ServiceName: scmrName, - DisplayName: scmrDisplayName, - ServiceType: windows.SERVICE_WIN32_OWN_PROCESS, - StartType: windows.SERVICE_DEMAND_START, - }, - } - if err := executor.Exec(log.WithContext(ctx), creds, target, execCfg); err != nil { - log.Fatal().Err(err).Msg("SCMR execution failed") - } - return nil - }, - } - scmrChangeCmd = &cobra.Command{ - Use: "change [target]", - Short: "Change an existing Windows service to gain execution", - Args: scmrArgs, - Run: func(cmd *cobra.Command, args []string) { - executor := scmrexec.Module{} - execCfg := &exec.ExecutionConfig{ - ExecutablePath: executablePath, - ExecutableArgs: executableArgs, - ExecutionMethod: scmrexec2.MethodModify, + ExecutionMethodConfig: scmrexec.MethodCreateConfig{ + NoDelete: scmrNoDelete, + ServiceName: scmrName, + DisplayName: scmrDisplayName, + ServiceType: windows.SERVICE_WIN32_OWN_PROCESS, + StartType: windows.SERVICE_DEMAND_START, + }, + } + if err := executor.Exec(log.WithContext(ctx), creds, target, execCfg); err != nil { + log.Fatal().Err(err).Msg("SCMR execution failed") + } + return nil + }, + } + scmrChangeCmd = &cobra.Command{ + Use: "change [target]", + Short: "Change an existing Windows service to gain execution", + Args: scmrArgs, + Run: func(cmd *cobra.Command, args []string) { + executor := scmrexec.Module{} + execCfg := &exec.ExecutionConfig{ + ExecutablePath: executablePath, + ExecutableArgs: executableArgs, + ExecutionMethod: scmrexec.MethodModify, - ExecutionMethodConfig: scmrexec2.MethodModifyConfig{ - NoStart: scmrNoStart, - ServiceName: scmrName, - }, - } - if err := executor.Exec(log.WithContext(ctx), creds, target, execCfg); err != nil { - log.Fatal().Err(err).Msg("SCMR execution failed") - } - }, - } + ExecutionMethodConfig: scmrexec.MethodModifyConfig{ + NoStart: scmrNoStart, + ServiceName: scmrName, + }, + } + if err := executor.Exec(log.WithContext(ctx), creds, target, execCfg); err != nil { + log.Fatal().Err(err).Msg("SCMR execution failed") + } + }, + } ) diff --git a/cmd/tsch.go b/cmd/tsch.go index 8011cf2..78d71cc 100644 --- a/cmd/tsch.go +++ b/cmd/tsch.go @@ -1,10 +1,12 @@ package cmd import ( + "fmt" "github.com/FalconOpsLLC/goexec/internal/client/dce" "github.com/FalconOpsLLC/goexec/internal/exec" "github.com/FalconOpsLLC/goexec/internal/exec/tsch" "github.com/spf13/cobra" + "regexp" "time" ) @@ -29,7 +31,7 @@ func tschDeleteCmdInit() { func tschDemandCmdInit() { tschDemandCmd.Flags().StringVarP(&executable, "executable", "e", "", "Remote Windows executable to invoke") tschDemandCmd.Flags().StringVarP(&executableArgs, "args", "a", "", "Arguments to pass to executable") - tschDemandCmd.Flags().StringVarP(&tschName, "name", "n", "", "Target task name") + tschDemandCmd.Flags().StringVarP(&tschTaskName, "name", "n", "", "Target task name") tschDemandCmd.Flags().BoolVar(&tschNoDelete, "no-delete", false, "Don't delete task after execution") if err := tschDemandCmd.MarkFlagRequired("executable"); err != nil { panic(err) @@ -39,7 +41,7 @@ func tschDemandCmdInit() { func tschRegisterCmdInit() { tschRegisterCmd.Flags().StringVarP(&executable, "executable", "e", "", "Remote Windows executable to invoke") tschRegisterCmd.Flags().StringVarP(&executableArgs, "args", "a", "", "Arguments to pass to executable") - tschRegisterCmd.Flags().StringVarP(&tschName, "name", "n", "", "Target task name") + tschRegisterCmd.Flags().StringVarP(&tschTaskName, "name", "n", "", "Target task name") tschRegisterCmd.Flags().DurationVar(&tschStopDelay, "delay-stop", time.Duration(5*time.Second), "Delay between task execution and termination. This will not stop the process spawned by the task") tschRegisterCmd.Flags().DurationVarP(&tschDelay, "delay-start", "d", time.Duration(5*time.Second), "Delay between task registration and execution") tschRegisterCmd.Flags().DurationVarP(&tschDeleteDelay, "delay-delete", "D", time.Duration(0*time.Second), "Delay between task termination and deletion") @@ -55,15 +57,35 @@ func tschRegisterCmdInit() { } } +func tschArgs(principal string) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + if tschTaskPath != "" && !tschTaskPathRegex.MatchString(tschTaskPath) { + return fmt.Errorf("invalid task path: %s", tschTaskPath) + } + if tschTaskName != "" { + if !tschTaskNameRegex.MatchString(tschTaskName) { + return fmt.Errorf("invalid task name: %s", tschTaskName) + + } else if tschTaskPath == "" { + tschTaskPath = `\` + tschTaskName + } + } + return needsRpcTarget(principal)(cmd, args) + } +} + var ( tschNoDelete bool tschCallDelete bool tschDeleteDelay time.Duration tschStopDelay time.Duration tschDelay time.Duration - tschName string + tschTaskName string tschTaskPath string + tschTaskPathRegex = regexp.MustCompile(`^\\[^ :/\\][^:/]*$`) + tschTaskNameRegex = regexp.MustCompile(`^[^ :/\\][^:/\\]*$`) + tschCmd = &cobra.Command{ Use: "tsch", Short: "Establish execution via TSCH (ITaskSchedulerService)", @@ -84,7 +106,7 @@ References: SchRpcDelete - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/360bb9b1-dd2a-4b36-83ee-21f12cb97cff DeleteExpiredTaskAfter - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/6bfde6fe-440e-4ddd-b4d6-c8fc0bc06fae `, - Args: needsRpcTarget("cifs"), + Args: tschArgs("cifs"), Run: func(cmd *cobra.Command, args []string) { log = log.With(). @@ -137,7 +159,7 @@ References: SchRpcRegisterTask - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/849c131a-64e4-46ef-b015-9d4c599c5167 SchRpcRun - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/77f2250d-500a-40ee-be18-c82f7079c4f0 `, - Args: needsTarget("cifs"), + Args: tschArgs("cifs"), Run: func(cmd *cobra.Command, args []string) { log = log.With(). @@ -163,7 +185,7 @@ References: ExecutionMethodConfig: tschexec.MethodDemandConfig{ NoDelete: tschNoDelete, - TaskName: tschName, + TaskPath: tschTaskPath, }, } if err := module.Connect(log.WithContext(ctx), creds, target, connCfg); err != nil { @@ -182,7 +204,7 @@ References: References: SchRpcDelete - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/360bb9b1-dd2a-4b36-83ee-21f12cb97cff `, - Args: needsTarget("cifs"), + Args: tschArgs("cifs"), Run: func(cmd *cobra.Command, args []string) { log = log.With(). Str("module", "tsch"). diff --git a/internal/exec/tsch/exec.go b/internal/exec/tsch/exec.go index d205776..c238f41 100644 --- a/internal/exec/tsch/exec.go +++ b/internal/exec/tsch/exec.go @@ -25,23 +25,13 @@ const ( var ( TschRpcUuid = uuid.MustParse("86D35949-83C9-4044-B424-DB363231FD0C") SupportedEndpointProtocols = []string{"ncacn_np", "ncacn_ip_tcp"} - - defaultStringBinding *dcerpc.StringBinding - initErr error ) -func init() { - if defaultStringBinding, initErr = dcerpc.ParseStringBinding(DefaultEndpoint); initErr != nil { - panic(initErr) - } -} - // Connect to the target & initialize DCE & TSCH clients func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target, ccfg *exec.ConnectionConfig) (err error) { //var port uint16 var endpoint string = DefaultEndpoint - //var stringBinding = defaultStringBinding var epmOpts []dcerpc.Option var dceOpts []dcerpc.Option @@ -82,17 +72,7 @@ func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target } log = log.With().Str("endpoint", endpoint).Logger() log.Info().Msg("Connecting to target") - /* - if !cfg.NoEpm { - mapperOpts := append(dceOpts, ao...) - dceOpts = append(dceOpts, - epm.EndpointMapper(ctx, target.AddressWithoutPort(), mapperOpts...), - dcerpc.WithEndpoint(fmt.Sprintf("%s:", stringBinding.ProtocolSequence.String()))) - - } else { - dceOpts = append(dceOpts, dcerpc.WithEndpoint(stringBinding.String())) - } - */ + // Create DCERPC dialer mod.dce, err = dcerpc.Dial(ctx, target.AddressWithoutPort(), append(dceOpts, ao...)...) if err != nil { diff --git a/internal/exec/tsch/tsch.go b/internal/exec/tsch/tsch.go index f2476f1..d47e513 100644 --- a/internal/exec/tsch/tsch.go +++ b/internal/exec/tsch/tsch.go @@ -55,7 +55,7 @@ type settings struct { type actionExec struct { XMLName xml.Name `xml:"Exec"` Command string `xml:"Command"` - Arguments string `xml:"Arguments"` + Arguments string `xml:"Arguments,omitempty"` } type actions struct { @@ -77,14 +77,13 @@ type principal struct { } type task struct { - XMLName xml.Name `xml:"Task"` - TaskVersion string `xml:"version,attr"` - TaskNamespace string `xml:"xmlns,attr"` - //TimeTriggers []taskTimeTrigger `xml:"Triggers>TimeTrigger,omitempty"` // TODO: triggers type - Triggers triggers `xml:"Triggers"` - Actions actions `xml:"Actions"` - Principals principals `xml:"Principals"` - Settings settings `xml:"Settings"` + XMLName xml.Name `xml:"Task"` + TaskVersion string `xml:"version,attr"` + TaskNamespace string `xml:"xmlns,attr"` + Triggers triggers `xml:"Triggers"` + Actions actions `xml:"Actions"` + Principals principals `xml:"Principals"` + Settings settings `xml:"Settings"` } // registerTask serializes and submits the provided task structure @@ -106,7 +105,6 @@ func (mod *Module) registerTask(ctx context.Context, taskDef task, taskPath stri taskXml = TaskXMLHeader + string(doc) log.Debug().Str("content", taskXml).Msg("Generated task XML") } - // Submit task { response, err := mod.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{ |