diff options
author | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-02-26 19:42:55 -0600 |
---|---|---|
committer | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-02-26 19:42:55 -0600 |
commit | 0591eed275fd24e5c1b959e1a182bea6f6275c34 (patch) | |
tree | d32e53640e65627180de9a290449309591bae771 /cmd | |
parent | 930699ae66d1ddaf44fec71e04c33ab193531584 (diff) | |
download | goexec-0591eed275fd24e5c1b959e1a182bea6f6275c34.tar.gz goexec-0591eed275fd24e5c1b959e1a182bea6f6275c34.zip |
Initial commit +scmrexec
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/root.go | 59 | ||||
-rw-r--r-- | cmd/scmr.go | 126 |
2 files changed, 185 insertions, 0 deletions
diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..00563c6 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "context" + "fmt" + "github.com/bryanmcnulty/adauth" + "github.com/rs/zerolog" + "github.com/spf13/cobra" + "os" + "regexp" +) + +var ( + //logFile string + log zerolog.Logger + ctx context.Context + authOpts *adauth.Options + + debug, trace bool + command string + executablePath string + executableArgs 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") + } + log = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel).With().Timestamp().Logger() + if debug { + log = log.Level(zerolog.DebugLevel) + } + return + }, + } +) + +func init() { + ctx = context.Background() + + rootCmd.InitDefaultVersionFlag() + rootCmd.InitDefaultHelpCmd() + rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging") + + authOpts = &adauth.Options{Debug: log.Debug().Msgf} + authOpts.RegisterFlags(rootCmd.PersistentFlags()) + + scmrCmdInit() + rootCmd.AddCommand(scmrCmd) +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/scmr.go b/cmd/scmr.go new file mode 100644 index 0000000..e142cd7 --- /dev/null +++ b/cmd/scmr.go @@ -0,0 +1,126 @@ +package cmd + +import ( + "errors" + "fmt" + "github.com/bryanmcnulty/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" +) + +func scmrCmdInit() { + scmrCmd.PersistentFlags().StringVarP(&executablePath, "executable-path", "e", "", "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.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") +} + +func scmrCreateCmdInit() { + scmrCreateCmd.Flags().BoolVar(&scmrNoDelete, "no-delete", false, "Don't delete service after execution") +} + +var ( + // 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 + } + + creds *adauth.Credential + target *adauth.Target + + 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) + } + }, + } + 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.Executor{} + execCfg := &exec.ExecutionConfig{ + ExecutablePath: executablePath, + ExecutableArgs: executableArgs, + ExecutionMethod: scmrexec.MethodCreate, + + 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.Executor{} + execCfg := &exec.ExecutionConfig{ + ExecutablePath: executablePath, + ExecutableArgs: executableArgs, + ExecutionMethod: scmrexec.MethodModify, + + 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") + } + }, + } +) |