diff options
author | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-03-04 03:05:53 -0600 |
---|---|---|
committer | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-03-04 03:05:53 -0600 |
commit | a5c860b8ab24c198b7390fbde90044754e35c1c5 (patch) | |
tree | 3118b27b5c76cab44bb61d83df750a9f00b4be00 /cmd | |
parent | 5a3bf6315aab33e6488734a579977836042b4aa1 (diff) | |
parent | f98989334bbe227bbe9dc4c84a2d0e34aa2fb86f (diff) | |
download | goexec-a5c860b8ab24c198b7390fbde90044754e35c1c5.tar.gz goexec-a5c860b8ab24c198b7390fbde90044754e35c1c5.zip |
Simple fixes
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/root.go | 26 | ||||
-rw-r--r-- | cmd/scmr.go | 49 | ||||
-rw-r--r-- | cmd/tsch.go | 158 |
3 files changed, 200 insertions, 33 deletions
diff --git a/cmd/root.go b/cmd/root.go index 00563c6..473f1ad 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,7 +3,7 @@ package cmd import ( "context" "fmt" - "github.com/bryanmcnulty/adauth" + "github.com/RedTeamPentesting/adauth" "github.com/rs/zerolog" "github.com/spf13/cobra" "os" @@ -16,11 +16,28 @@ var ( ctx context.Context authOpts *adauth.Options - debug, trace bool + debug bool command string + executable string executablePath string executableArgs string + needsTarget = 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, "cifs", 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") + } + return + } + rootCmd = &cobra.Command{ Use: "goexec", PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { @@ -42,13 +59,16 @@ func init() { rootCmd.InitDefaultVersionFlag() rootCmd.InitDefaultHelpCmd() - rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging") + rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging") authOpts = &adauth.Options{Debug: log.Debug().Msgf} authOpts.RegisterFlags(rootCmd.PersistentFlags()) scmrCmdInit() rootCmd.AddCommand(scmrCmd) + + tschCmdInit() + rootCmd.AddCommand(tschCmd) } func Execute() { diff --git a/cmd/scmr.go b/cmd/scmr.go index 150320c..8d453a5 100644 --- a/cmd/scmr.go +++ b/cmd/scmr.go @@ -1,37 +1,36 @@ package cmd import ( - "errors" "fmt" - "github.com/bryanmcnulty/adauth" + "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" - "github.com/FalconOpsLLC/goexec/pkg/exec" - scmrexec "github.com/FalconOpsLLC/goexec/pkg/exec/scmr" - "github.com/FalconOpsLLC/goexec/pkg/windows" + scmrexec "github.com/FalconOpsLLC/goexec/internal/exec/scmr" ) func scmrCmdInit() { - scmrCmd.PersistentFlags().StringVarP(&executablePath, "executable-path", "e", "", "Full path to remote Windows executable") + 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", "s", "", "Name of service to create or modify") - scmrCmd.PersistentFlags().BoolVar(&scmrNoStart, "no-start", false, "Don't start service after execution") + scmrCmd.PersistentFlags().StringVarP(&scmrName, "service-name", "s", "", "Name of service to create or modify") scmrCmd.MarkPersistentFlagRequired("executable-path") - scmrCmd.MarkPersistentFlagRequired("service") + scmrCmd.MarkPersistentFlagRequired("service-name") scmrCmd.AddCommand(scmrChangeCmd) - scmrChangeCmdInit() - scmrCmd.AddCommand(scmrCreateCmd) scmrCreateCmdInit() + scmrCmd.AddCommand(scmrCreateCmd) + scmrChangeCmdInit() } func scmrChangeCmdInit() { - // no unique flags + 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().StringVarP(&scmrDisplayName, "display-name", "n", "", "Display name new service") scmrCreateCmd.Flags().BoolVar(&scmrNoDelete, "no-delete", false, "Don't delete service after execution") } @@ -59,17 +58,7 @@ var ( scmrCmd = &cobra.Command{ Use: "scmr", Short: "Establish execution via SCMR", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) != 1 { - return errors.New(`command not set. Choose from (change, create)`) - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - if err := cmd.Help(); err != nil { - panic(err) - } - }, + Args: cobra.NoArgs, } scmrCreateCmd = &cobra.Command{ Use: "create [target]", @@ -83,13 +72,13 @@ var ( scmrDisplayName = scmrName log.Warn().Msg("No display name specified, using service name as display name") } - executor := scmrexec.Executor{} + executor := scmrexec.Module{} execCfg := &exec.ExecutionConfig{ ExecutablePath: executablePath, ExecutableArgs: executableArgs, - ExecutionMethod: scmrexec.MethodCreate, + ExecutionMethod: scmrexec2.MethodCreate, - ExecutionMethodConfig: scmrexec.MethodCreateConfig{ + ExecutionMethodConfig: scmrexec2.MethodCreateConfig{ NoDelete: scmrNoDelete, ServiceName: scmrName, DisplayName: scmrDisplayName, @@ -108,13 +97,13 @@ var ( Short: "Change an existing Windows service to gain execution", Args: scmrArgs, Run: func(cmd *cobra.Command, args []string) { - executor := scmrexec.Executor{} + executor := scmrexec.Module{} execCfg := &exec.ExecutionConfig{ ExecutablePath: executablePath, ExecutableArgs: executableArgs, - ExecutionMethod: scmrexec.MethodModify, + ExecutionMethod: scmrexec2.MethodModify, - ExecutionMethodConfig: scmrexec.MethodModifyConfig{ + ExecutionMethodConfig: scmrexec2.MethodModifyConfig{ NoStart: scmrNoStart, ServiceName: scmrName, }, diff --git a/cmd/tsch.go b/cmd/tsch.go new file mode 100644 index 0000000..05c55cf --- /dev/null +++ b/cmd/tsch.go @@ -0,0 +1,158 @@ +package cmd + +import ( + "github.com/FalconOpsLLC/goexec/internal/exec" + "github.com/FalconOpsLLC/goexec/internal/exec/tsch" + "github.com/spf13/cobra" + "time" +) + +func tschCmdInit() { + tschDeleteCmdInit() + tschCmd.AddCommand(tschDeleteCmd) + + tschRegisterCmdInit() + tschCmd.AddCommand(tschRegisterCmd) + + tschDemandCmdInit() + tschCmd.AddCommand(tschDemandCmd) +} + +func tschDeleteCmdInit() { + tschDeleteCmd.Flags().StringVarP(&tschTaskPath, "path", "t", "", "Scheduled task path") + tschDeleteCmd.MarkFlagRequired("path") +} + +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().BoolVar(&tschNoDelete, "no-delete", false, "Don't delete task after execution") + tschDemandCmd.MarkFlagRequired("executable") +} + +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().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") + tschRegisterCmd.Flags().BoolVar(&tschNoDelete, "no-delete", false, "Don't delete task after execution") + tschRegisterCmd.Flags().BoolVar(&tschCallDelete, "call-delete", false, "Directly call SchRpcDelete to delete task") + + tschRegisterCmd.MarkFlagsMutuallyExclusive("no-delete", "delay-delete") + tschRegisterCmd.MarkFlagsMutuallyExclusive("no-delete", "call-delete") + tschRegisterCmd.MarkFlagsMutuallyExclusive("delay-delete", "call-delete") + tschRegisterCmd.MarkFlagRequired("executable") +} + +var ( + tschNoDelete bool + tschCallDelete bool + tschDeleteDelay time.Duration + tschStopDelay time.Duration + tschDelay time.Duration + tschName string + tschTaskPath string + + tschCmd = &cobra.Command{ + Use: "tsch", + Short: "Establish execution via TSCH (ITaskSchedulerService)", + Args: cobra.NoArgs, + } + tschRegisterCmd = &cobra.Command{ + Use: "register [target]", + Short: "Register a remote scheduled task with an automatic start time", + Long: `Description: + The register method calls SchRpcRegisterTask to register a scheduled task + with an automatic start time.This method avoids directly calling SchRpcRun, + and can even avoid calling SchRpcDelete by populating the DeleteExpiredTaskAfter + Setting. + +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 + 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: needsTarget, + Run: func(cmd *cobra.Command, args []string) { + if tschNoDelete { + log.Warn().Msg("Task will not be deleted after execution") + } + module := tschexec.Module{} + execCfg := &exec.ExecutionConfig{ + ExecutableName: executable, + ExecutableArgs: executableArgs, + ExecutionMethod: tschexec.MethodRegister, + + ExecutionMethodConfig: tschexec.MethodRegisterConfig{ + NoDelete: tschNoDelete, + CallDelete: tschCallDelete, + StartDelay: tschDelay, + StopDelay: tschStopDelay, + DeleteDelay: tschDeleteDelay, + TaskName: tschName, + }, + } + if err := module.Exec(log.WithContext(ctx), creds, target, execCfg); err != nil { + log.Fatal().Err(err).Msg("TSCH execution failed") + } + }, + } + tschDemandCmd = &cobra.Command{ + Use: "demand [target]", + Short: "Register a remote scheduled task and demand immediate start", + Long: `Description: + Similar to the register method, the demand method will call SchRpcRegisterTask, + But rather than setting a defined time when the task will start, it will + additionally call SchRpcRun to forcefully start the task. + +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, + Run: func(cmd *cobra.Command, args []string) { + if tschNoDelete { + log.Warn().Msg("Task will not be deleted after execution") + } + module := tschexec.Module{} + execCfg := &exec.ExecutionConfig{ + ExecutableName: executable, + ExecutableArgs: executableArgs, + ExecutionMethod: tschexec.MethodDemand, + + ExecutionMethodConfig: tschexec.MethodDemandConfig{ + NoDelete: tschNoDelete, + TaskName: tschName, + }, + } + if err := module.Exec(log.WithContext(ctx), creds, target, execCfg); err != nil { + log.Fatal().Err(err).Msg("TSCH execution failed") + } + }, + } + tschDeleteCmd = &cobra.Command{ + Use: "delete [target]", + Short: "Manually delete a scheduled task", + Long: `Description: + The delete method manually deletes a scheduled task by calling SchRpcDelete + +References: + SchRpcDelete - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/360bb9b1-dd2a-4b36-83ee-21f12cb97cff +`, + Args: needsTarget, + Run: func(cmd *cobra.Command, args []string) { + module := tschexec.Module{} + cleanCfg := &exec.CleanupConfig{ + CleanupMethod: tschexec.MethodDelete, + CleanupMethodConfig: tschexec.MethodDeleteConfig{TaskPath: tschTaskPath}, + } + if err := module.Cleanup(log.WithContext(ctx), creds, target, cleanCfg); err != nil { + log.Fatal().Err(err).Msg("TSCH cleanup failed") + } + }, + } +) |