From 7770ce5c15dff99c1100f9fe6e41c502a94cd713 Mon Sep 17 00:00:00 2001 From: Bryan McNulty Date: Sat, 26 Apr 2025 05:18:48 -0500 Subject: Fixed some more linter issues --- cmd/args.go | 3 + cmd/root.go | 18 +-- cmd/tsch.go | 386 +++++++++++++++++++++++------------------------ pkg/goexec/dce/client.go | 3 +- pkg/goexec/tsch/tsch.go | 3 + 5 files changed, 209 insertions(+), 204 deletions(-) diff --git a/cmd/args.go b/cmd/args.go index 9568395..40b9701 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -29,10 +29,13 @@ func registerNetworkFlags(fs *pflag.FlagSet) { //cmd.MarkFlagsMutuallyExclusive("no-epm", "epm-filter") } +// FUTURE: automatically stage & execute file +/* func registerStageFlags(fs *pflag.FlagSet) { fs.StringVarP(&stageFilePath, "stage", "E", "", "File to stage and execute") //fs.StringVarP(&stageArgs ...) } +*/ func registerExecutionFlags(fs *pflag.FlagSet) { fs.StringVarP(&exec.Input.Executable, "exec", "e", "", "Remote Windows executable to invoke") diff --git a/cmd/root.go b/cmd/root.go index 12f3e19..a66fa62 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -60,9 +60,9 @@ var ( toClose []io.Closer // === IO === - stageFilePath string - outputMethod string - outputPath string + //stageFilePath string // FUTURE + outputMethod string + outputPath string // ========== // === Logging === @@ -196,6 +196,12 @@ Authors: FalconOps LLC (@FalconOpsLLC), pprof.StopCPUProfile() } + if exec.Input != nil && exec.Input.StageFile != nil { + if err := exec.Input.StageFile.Close(); err != nil { + log.Warn().Err(err).Msg("Failed to close stage file") + } + } + for _, c := range toClose { if c != nil { if err := c.Close(); err != nil { @@ -203,12 +209,6 @@ Authors: FalconOps LLC (@FalconOpsLLC), } } } - - if exec.Input != nil && exec.Input.StageFile != nil { - if err := exec.Input.StageFile.Close(); err != nil { - // ... - } - } }, } ) diff --git a/cmd/tsch.go b/cmd/tsch.go index ec3504f..f58caf6 100644 --- a/cmd/tsch.go +++ b/cmd/tsch.go @@ -1,233 +1,233 @@ package cmd import ( - "context" - "fmt" - "github.com/FalconOpsLLC/goexec/internal/util" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - tschexec "github.com/FalconOpsLLC/goexec/pkg/goexec/tsch" - "github.com/oiweiwei/go-msrpc/ssp/gssapi" - "github.com/spf13/cobra" - "time" + "context" + "fmt" + "github.com/FalconOpsLLC/goexec/internal/util" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + tschexec "github.com/FalconOpsLLC/goexec/pkg/goexec/tsch" + "github.com/oiweiwei/go-msrpc/ssp/gssapi" + "github.com/spf13/cobra" + "time" ) func tschCmdInit() { - cmdFlags[tschCmd] = []*flagSet{ - defaultAuthFlags, - defaultLogFlags, - defaultNetRpcFlags, - } - tschDemandCmdInit() - tschCreateCmdInit() - tschChangeCmdInit() - - tschCmd.PersistentFlags().AddFlagSet(defaultAuthFlags.Flags) - tschCmd.PersistentFlags().AddFlagSet(defaultLogFlags.Flags) - tschCmd.PersistentFlags().AddFlagSet(defaultNetRpcFlags.Flags) - tschCmd.AddCommand(tschDemandCmd, tschCreateCmd, tschChangeCmd) + cmdFlags[tschCmd] = []*flagSet{ + defaultAuthFlags, + defaultLogFlags, + defaultNetRpcFlags, + } + tschDemandCmdInit() + tschCreateCmdInit() + tschChangeCmdInit() + + tschCmd.PersistentFlags().AddFlagSet(defaultAuthFlags.Flags) + tschCmd.PersistentFlags().AddFlagSet(defaultLogFlags.Flags) + tschCmd.PersistentFlags().AddFlagSet(defaultNetRpcFlags.Flags) + tschCmd.AddCommand(tschDemandCmd, tschCreateCmd, tschChangeCmd) } func tschDemandCmdInit() { - tschDemandFlags := newFlagSet("Task Scheduler") + tschDemandFlags := newFlagSet("Task Scheduler") - tschDemandFlags.Flags.StringVarP(&tschTask, "task", "t", "", "Name or path of the new task") - tschDemandFlags.Flags.Uint32Var(&tschDemand.SessionId, "session", 0, "Hijack existing session given the session `ID`") - tschDemandFlags.Flags.StringVar(&tschDemand.UserSid, "sid", "S-1-5-18", "User `SID` to impersonate") - tschDemandFlags.Flags.BoolVar(&tschDemand.NoDelete, "no-delete", false, "Don't delete task after execution") + tschDemandFlags.Flags.StringVarP(&tschTask, "task", "t", "", "Name or path of the new task") + tschDemandFlags.Flags.Uint32Var(&tschDemand.SessionId, "session", 0, "Hijack existing session given the session `ID`") + tschDemandFlags.Flags.StringVar(&tschDemand.UserSid, "sid", "S-1-5-18", "User `SID` to impersonate") + tschDemandFlags.Flags.BoolVar(&tschDemand.NoDelete, "no-delete", false, "Don't delete task after execution") - tschDemandExecFlags := newFlagSet("Execution") + tschDemandExecFlags := newFlagSet("Execution") - registerExecutionFlags(tschDemandExecFlags.Flags) - registerExecutionOutputFlags(tschDemandExecFlags.Flags) + registerExecutionFlags(tschDemandExecFlags.Flags) + registerExecutionOutputFlags(tschDemandExecFlags.Flags) - cmdFlags[tschDemandCmd] = []*flagSet{ - tschDemandFlags, - tschDemandExecFlags, - defaultAuthFlags, - defaultLogFlags, - defaultNetRpcFlags, - } + cmdFlags[tschDemandCmd] = []*flagSet{ + tschDemandFlags, + tschDemandExecFlags, + defaultAuthFlags, + defaultLogFlags, + defaultNetRpcFlags, + } - tschDemandCmd.Flags().AddFlagSet(tschDemandFlags.Flags) - tschDemandCmd.Flags().AddFlagSet(tschDemandExecFlags.Flags) - tschDemandCmd.MarkFlagsOneRequired("exec", "command") + tschDemandCmd.Flags().AddFlagSet(tschDemandFlags.Flags) + tschDemandCmd.Flags().AddFlagSet(tschDemandExecFlags.Flags) + tschDemandCmd.MarkFlagsOneRequired("exec", "command") } func tschCreateCmdInit() { - tschCreateFlags := newFlagSet("Task Scheduler") - - tschCreateFlags.Flags.StringVarP(&tschTask, "task", "t", "", "Name or path of the new task") - tschCreateFlags.Flags.DurationVar(&tschCreate.StopDelay, "delay-stop", 5*time.Second, "Delay between task execution and termination. This won't stop the spawned process") - tschCreateFlags.Flags.DurationVar(&tschCreate.StartDelay, "start-delay", 5*time.Second, "Delay between task registration and execution") - //tschCreateFlags.Flags.DurationVar(&tschCreate.DeleteDelay, "delete-delay", 0*time.Second, "Delay between task termination and deletion") - tschCreateFlags.Flags.BoolVar(&tschCreate.NoDelete, "no-delete", false, "Don't delete task after execution") - tschCreateFlags.Flags.BoolVar(&tschCreate.CallDelete, "call-delete", false, "Directly call SchRpcDelete to delete task") - tschCreateFlags.Flags.StringVar(&tschCreate.UserSid, "sid", "S-1-5-18", "User `SID` to impersonate") - - tschCreateExecFlags := newFlagSet("Execution") - - registerExecutionFlags(tschCreateExecFlags.Flags) - registerExecutionOutputFlags(tschCreateExecFlags.Flags) - - cmdFlags[tschCreateCmd] = []*flagSet{ - tschCreateFlags, - tschCreateExecFlags, - defaultAuthFlags, - defaultLogFlags, - defaultNetRpcFlags, - } - - tschCreateCmd.Flags().AddFlagSet(tschCreateFlags.Flags) - tschCreateCmd.Flags().AddFlagSet(tschCreateExecFlags.Flags) - tschCreateCmd.MarkFlagsOneRequired("exec", "command") + tschCreateFlags := newFlagSet("Task Scheduler") + + tschCreateFlags.Flags.StringVarP(&tschTask, "task", "t", "", "Name or path of the new task") + tschCreateFlags.Flags.DurationVar(&tschCreate.StopDelay, "delay-stop", 5*time.Second, "Delay between task execution and termination. This won't stop the spawned process") + tschCreateFlags.Flags.DurationVar(&tschCreate.StartDelay, "start-delay", 5*time.Second, "Delay between task registration and execution") + //tschCreateFlags.Flags.DurationVar(&tschCreate.DeleteDelay, "delete-delay", 0*time.Second, "Delay between task termination and deletion") + tschCreateFlags.Flags.BoolVar(&tschCreate.NoDelete, "no-delete", false, "Don't delete task after execution") + tschCreateFlags.Flags.BoolVar(&tschCreate.CallDelete, "call-delete", false, "Directly call SchRpcDelete to delete task") + tschCreateFlags.Flags.StringVar(&tschCreate.UserSid, "sid", "S-1-5-18", "User `SID` to impersonate") + + tschCreateExecFlags := newFlagSet("Execution") + + registerExecutionFlags(tschCreateExecFlags.Flags) + registerExecutionOutputFlags(tschCreateExecFlags.Flags) + + cmdFlags[tschCreateCmd] = []*flagSet{ + tschCreateFlags, + tschCreateExecFlags, + defaultAuthFlags, + defaultLogFlags, + defaultNetRpcFlags, + } + + tschCreateCmd.Flags().AddFlagSet(tschCreateFlags.Flags) + tschCreateCmd.Flags().AddFlagSet(tschCreateExecFlags.Flags) + tschCreateCmd.MarkFlagsOneRequired("exec", "command") } func tschChangeCmdInit() { - tschChangeFlags := newFlagSet("Task Scheduler") - - tschChangeFlags.Flags.StringVarP(&tschChange.TaskPath, "task", "t", "", "Path to existing task") - tschChangeFlags.Flags.BoolVar(&tschChange.NoStart, "no-start", false, "Don't start the task") - tschChangeFlags.Flags.BoolVar(&tschChange.NoRevert, "no-revert", false, "Don't restore the original task definition") - - tschChangeExecFlags := newFlagSet("Execution") - - registerExecutionFlags(tschChangeExecFlags.Flags) - registerExecutionOutputFlags(tschChangeExecFlags.Flags) - - cmdFlags[tschChangeCmd] = []*flagSet{ - tschChangeFlags, - tschChangeExecFlags, - defaultAuthFlags, - defaultLogFlags, - defaultNetRpcFlags, - } - - tschChangeCmd.Flags().AddFlagSet(tschChangeFlags.Flags) - tschChangeCmd.Flags().AddFlagSet(tschChangeExecFlags.Flags) - - // Constraints - { - if err := tschChangeCmd.MarkFlagRequired("task"); err != nil { - panic(err) - } - tschChangeCmd.MarkFlagsOneRequired("exec", "command") - } + tschChangeFlags := newFlagSet("Task Scheduler") + + tschChangeFlags.Flags.StringVarP(&tschChange.TaskPath, "task", "t", "", "Path to existing task") + tschChangeFlags.Flags.BoolVar(&tschChange.NoStart, "no-start", false, "Don't start the task") + tschChangeFlags.Flags.BoolVar(&tschChange.NoRevert, "no-revert", false, "Don't restore the original task definition") + + tschChangeExecFlags := newFlagSet("Execution") + + registerExecutionFlags(tschChangeExecFlags.Flags) + registerExecutionOutputFlags(tschChangeExecFlags.Flags) + + cmdFlags[tschChangeCmd] = []*flagSet{ + tschChangeFlags, + tschChangeExecFlags, + defaultAuthFlags, + defaultLogFlags, + defaultNetRpcFlags, + } + + tschChangeCmd.Flags().AddFlagSet(tschChangeFlags.Flags) + tschChangeCmd.Flags().AddFlagSet(tschChangeExecFlags.Flags) + + // Constraints + { + if err := tschChangeCmd.MarkFlagRequired("task"); err != nil { + panic(err) + } + tschChangeCmd.MarkFlagsOneRequired("exec", "command") + } } func argsTask(*cobra.Command, []string) error { - switch { - case tschTask == "": - tschTask = `\` + util.RandomString() - case tschexec.ValidateTaskPath(tschTask) == nil: - case tschexec.ValidateTaskName(tschTask) == nil: - tschTask = `\` + tschTask - default: - return fmt.Errorf("invalid task Label or path: %q", tschTask) - } - return nil + switch { + case tschTask == "": + tschTask = `\` + util.RandomString() + case tschexec.ValidateTaskPath(tschTask) == nil: + case tschexec.ValidateTaskName(tschTask) == nil: + tschTask = `\` + tschTask + default: + return fmt.Errorf("invalid task Label or path: %q", tschTask) + } + return nil } var ( - tschDemand tschexec.TschDemand - tschCreate tschexec.TschCreate - tschChange tschexec.TschChange + tschDemand tschexec.TschDemand + tschCreate tschexec.TschCreate + tschChange tschexec.TschChange - tschTask string + tschTask string - tschCmd = &cobra.Command{ - Use: "tsch", - Short: "Execute with Windows Task Scheduler (MS-TSCH)", - Long: `Description: + tschCmd = &cobra.Command{ + Use: "tsch", + Short: "Execute with Windows Task Scheduler (MS-TSCH)", + Long: `Description: The tsch module makes use of the Windows Task Scheduler service (MS-TSCH) to spawn processes on the remote target.`, - GroupID: "module", - Args: cobra.NoArgs, - } - - tschDemandCmd = &cobra.Command{ - Use: "demand [target]", - Short: "Register a remote scheduled task and demand immediate start", - Long: `Description: + GroupID: "module", + Args: cobra.NoArgs, + } + + tschDemandCmd = &cobra.Command{ + Use: "demand [target]", + Short: "Register a remote scheduled task and demand immediate start", + Long: `Description: Similar to the create 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.`, - Args: args( - argsRpcClient("cifs"), - argsOutput("smb"), - argsTask, - ), - - Run: func(*cobra.Command, []string) { - tschDemand.IO = exec - tschDemand.Client = &rpcClient - tschDemand.TaskPath = tschTask - - ctx := log.With(). - Str("module", "tsch"). - Str("method", "demand"). - Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) - - if err := goexec.ExecuteCleanMethod(ctx, &tschDemand, &exec); err != nil { - log.Fatal().Err(err).Msg("Operation failed") - } - }, - } - tschCreateCmd = &cobra.Command{ - Use: "create [target]", - Short: "Create a remote scheduled task with an automatic start time", - Long: `Description: + Args: args( + argsRpcClient("cifs"), + argsOutput("smb"), + argsTask, + ), + + Run: func(*cobra.Command, []string) { + tschDemand.IO = exec + tschDemand.Client = &rpcClient + tschDemand.TaskPath = tschTask + + ctx := log.With(). + Str("module", "tsch"). + Str("method", "demand"). + Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) + + if err := goexec.ExecuteCleanMethod(ctx, &tschDemand, &exec); err != nil { + log.Fatal().Err(err).Msg("Operation failed") + } + }, + } + tschCreateCmd = &cobra.Command{ + Use: "create [target]", + Short: "Create a remote scheduled task with an automatic start time", + Long: `Description: The create 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.`, - Args: args( - argsRpcClient("cifs"), - argsOutput("smb"), - argsTask, - ), - - Run: func(*cobra.Command, []string) { - tschCreate.Tsch.Client = &rpcClient - tschCreate.IO = exec - tschCreate.TaskPath = tschTask - - ctx := log.With(). - Str("module", "tsch"). - Str("method", "create"). - Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) - - if err := goexec.ExecuteCleanMethod(ctx, &tschCreate, &exec); err != nil { - log.Fatal().Err(err).Msg("Operation failed") - } - }, - } - tschChangeCmd = &cobra.Command{ - Use: "change [target]", - Short: "Modify an existing task to spawn an arbitrary process", - Long: `Description: + Args: args( + argsRpcClient("cifs"), + argsOutput("smb"), + argsTask, + ), + + Run: func(*cobra.Command, []string) { + tschCreate.Client = &rpcClient + tschCreate.IO = exec + tschCreate.TaskPath = tschTask + + ctx := log.With(). + Str("module", "tsch"). + Str("method", "create"). + Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) + + if err := goexec.ExecuteCleanMethod(ctx, &tschCreate, &exec); err != nil { + log.Fatal().Err(err).Msg("Operation failed") + } + }, + } + tschChangeCmd = &cobra.Command{ + Use: "change [target]", + Short: "Modify an existing task to spawn an arbitrary process", + Long: `Description: The change method calls SchRpcRetrieveTask to fetch the definition of an existing task (-t), then modifies the task definition to spawn a process`, - Args: args( - argsRpcClient("cifs"), - argsOutput("smb"), - - func(*cobra.Command, []string) error { - return tschexec.ValidateTaskPath(tschChange.TaskPath) - }, - ), - - Run: func(*cobra.Command, []string) { - tschChange.Tsch.Client = &rpcClient - tschChange.IO = exec - - ctx := log.With(). - Str("module", "tsch"). - Str("method", "change"). - Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) - - if err := goexec.ExecuteCleanMethod(ctx, &tschChange, &exec); err != nil { - log.Fatal().Err(err).Msg("Operation failed") - } - }, - } + Args: args( + argsRpcClient("cifs"), + argsOutput("smb"), + + func(*cobra.Command, []string) error { + return tschexec.ValidateTaskPath(tschChange.TaskPath) + }, + ), + + Run: func(*cobra.Command, []string) { + tschChange.Client = &rpcClient + tschChange.IO = exec + + ctx := log.With(). + Str("module", "tsch"). + Str("method", "change"). + Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) + + if err := goexec.ExecuteCleanMethod(ctx, &tschChange, &exec); err != nil { + log.Fatal().Err(err).Msg("Operation failed") + } + }, + } ) diff --git a/pkg/goexec/dce/client.go b/pkg/goexec/dce/client.go index 0918933..567d619 100644 --- a/pkg/goexec/dce/client.go +++ b/pkg/goexec/dce/client.go @@ -13,8 +13,7 @@ import ( type Client struct { Options - conn dcerpc.Conn - hostname string + conn dcerpc.Conn } func (c *Client) String() string { diff --git a/pkg/goexec/tsch/tsch.go b/pkg/goexec/tsch/tsch.go index ae65ca7..3cbcb2c 100644 --- a/pkg/goexec/tsch/tsch.go +++ b/pkg/goexec/tsch/tsch.go @@ -92,6 +92,8 @@ type simpleTask struct { Settings taskSettings `xml:"Settings"` } +/* + // newSettings just creates a taskSettings instance with the necessary values + a few dynamic ones func newSettings(terminate, onDemand, startWhenAvailable bool) *taskSettings { return &taskSettings{ @@ -140,6 +142,7 @@ func newTask(se *taskSettings, pr []taskPrincipal, tr taskTriggers, cmd, args st }, } } +*/ // xmlDuration is a *very* simple implementation of xs:duration - only accepts +seconds func xmlDuration(dur time.Duration) string { -- cgit v1.2.3