aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorBryan McNulty <bryanmcnulty@protonmail.com>2025-03-04 03:05:53 -0600
committerBryan McNulty <bryanmcnulty@protonmail.com>2025-03-04 03:05:53 -0600
commita5c860b8ab24c198b7390fbde90044754e35c1c5 (patch)
tree3118b27b5c76cab44bb61d83df750a9f00b4be00 /cmd
parent5a3bf6315aab33e6488734a579977836042b4aa1 (diff)
parentf98989334bbe227bbe9dc4c84a2d0e34aa2fb86f (diff)
downloadgoexec-a5c860b8ab24c198b7390fbde90044754e35c1c5.tar.gz
goexec-a5c860b8ab24c198b7390fbde90044754e35c1c5.zip
Simple fixes
Diffstat (limited to 'cmd')
-rw-r--r--cmd/root.go26
-rw-r--r--cmd/scmr.go49
-rw-r--r--cmd/tsch.go158
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")
+ }
+ },
+ }
+)