diff options
Diffstat (limited to 'internal/exec/tsch')
-rw-r--r-- | internal/exec/tsch/exec.go | 189 | ||||
-rw-r--r-- | internal/exec/tsch/module.go | 44 | ||||
-rw-r--r-- | internal/exec/tsch/task.go | 85 | ||||
-rw-r--r-- | internal/exec/tsch/tsch.go | 141 |
4 files changed, 0 insertions, 459 deletions
diff --git a/internal/exec/tsch/exec.go b/internal/exec/tsch/exec.go deleted file mode 100644 index 49a2dc2..0000000 --- a/internal/exec/tsch/exec.go +++ /dev/null @@ -1,189 +0,0 @@ -package tschexec - -import ( - "context" - "errors" - "fmt" - "github.com/FalconOpsLLC/goexec/internal/client/dce" - "github.com/FalconOpsLLC/goexec/internal/exec" - "github.com/FalconOpsLLC/goexec/internal/util" - "github.com/RedTeamPentesting/adauth" - "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" - "github.com/rs/zerolog" - "time" -) - -const ( - TschDefaultEndpoint = "ncacn_np:[atsvc]" - TschDefaultObject = "86D35949-83C9-4044-B424-DB363231FD0C" -) - -// 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) { - - log := zerolog.Ctx(ctx).With(). - Str("func", "Connect").Logger() - - if ccfg.ConnectionMethod == exec.ConnectionMethodDCE { - if cfg, ok := ccfg.ConnectionMethodConfig.(dce.ConnectionMethodDCEConfig); !ok { - return fmt.Errorf("invalid configuration for DCE connection method") - } else { - // Create DCERPC dialer - mod.dce, err = cfg.GetDce(ctx, creds, target, TschDefaultEndpoint, TschDefaultObject) - if err != nil { - log.Error().Err(err).Msg("Failed to create DCERPC dialer") - return fmt.Errorf("create DCERPC dialer: %w", err) - } - // Create ITaskSchedulerService client - mod.tsch, err = itaskschedulerservice.NewTaskSchedulerServiceClient(ctx, mod.dce) - if err != nil { - log.Error().Err(err).Msg("Failed to initialize TSCH client") - return fmt.Errorf("init TSCH client: %w", err) - } - log.Info().Msg("DCE connection successful") - } - } else { - return errors.New("unsupported connection method") - } - return -} - -func (mod *Module) Cleanup(ctx context.Context, ccfg *exec.CleanupConfig) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("method", ccfg.CleanupMethod). - Str("func", "Cleanup").Logger() - - if ccfg.CleanupMethod == MethodDelete { - if cfg, ok := ccfg.CleanupMethodConfig.(MethodDeleteConfig); !ok { - return errors.New("invalid configuration") - - } else { - log = log.With().Str("task", cfg.TaskPath).Logger() - log.Info().Msg("Manually deleting task") - - if err = mod.deleteTask(ctx, cfg.TaskPath); err == nil { - log.Info().Msg("Task deleted successfully") - } - } - } else if ccfg.CleanupMethod == "" { - return nil - } else { - return fmt.Errorf("unsupported cleanup method") - } - return -} - -func (mod *Module) Exec(ctx context.Context, ecfg *exec.ExecutionConfig) (err error) { - - log := zerolog.Ctx(ctx).With(). - Str("method", ecfg.ExecutionMethod). - Str("func", "Exec").Logger() - - if ecfg.ExecutionMethod == MethodRegister { - if cfg, ok := ecfg.ExecutionMethodConfig.(MethodRegisterConfig); !ok { - return errors.New("invalid configuration") - - } else { - startTime := time.Now().UTC().Add(cfg.StartDelay) - stopTime := startTime.Add(cfg.StopDelay) - - tr := taskTimeTrigger{ - StartBoundary: startTime.Format(TaskXMLDurationFormat), - Enabled: true, - } - tk := newTask(nil, nil, triggers{TimeTriggers: []taskTimeTrigger{tr}}, ecfg.ExecutableName, ecfg.ExecutableArgs) - - if !cfg.NoDelete && !cfg.CallDelete { - if cfg.StopDelay == 0 { - cfg.StopDelay = time.Second - } - tk.Settings.DeleteExpiredTaskAfter = xmlDuration(cfg.DeleteDelay) - tk.Triggers.TimeTriggers[0].EndBoundary = stopTime.Format(TaskXMLDurationFormat) - } - taskPath := cfg.TaskPath - if taskPath == "" { - log.Debug().Msg("Task path not defined. Using random path") - taskPath = `\` + util.RandomString() - } - // The taskPath is changed here to the *actual path returned by SchRpcRegisterTask - taskPath, err = mod.registerTask(ctx, *tk, taskPath) - if err != nil { - return fmt.Errorf("call registerTask: %w", err) - } - - if !cfg.NoDelete { - if cfg.CallDelete { - defer mod.deleteTask(ctx, taskPath) - - log.Info().Dur("ms", cfg.StartDelay).Msg("Waiting for task to run") - select { - case <-ctx.Done(): - log.Warn().Msg("Cancelling execution") - return err - case <-time.After(cfg.StartDelay + time.Second): // + one second for good measure - for { - if stat, err := mod.tsch.GetLastRunInfo(ctx, &itaskschedulerservice.GetLastRunInfoRequest{Path: taskPath}); err != nil { - log.Warn().Err(err).Msg("Failed to get last run info. Assuming task was executed") - break - } else if stat.LastRuntime.AsTime().IsZero() { - log.Warn().Msg("Task was not yet run. Waiting 10 additional seconds") - time.Sleep(10 * time.Second) - } else { - break - } - } - break - } - } else { - log.Info().Time("when", stopTime).Msg("Task is scheduled to delete") - } - } - } - } else if ecfg.ExecutionMethod == MethodDemand { - if cfg, ok := ecfg.ExecutionMethodConfig.(MethodDemandConfig); !ok { - return errors.New("invalid configuration") - - } else { - taskPath := cfg.TaskPath - - if taskPath == "" { - log.Debug().Msg("Task path not defined. Using random path") - taskPath = `\` + util.RandomString() - } - - st := newSettings(true, true, false) - tk := newTask(st, nil, triggers{}, ecfg.ExecutableName, ecfg.ExecutableArgs) - - // The taskPath is changed here to the *actual path returned by SchRpcRegisterTask - taskPath, err = mod.registerTask(ctx, *tk, taskPath) - if err != nil { - return fmt.Errorf("call registerTask: %w", err) - } - - if !cfg.NoDelete { - defer mod.deleteTask(ctx, taskPath) - } - - var flags uint32 - - if cfg.SessionId != 0 { - flags |= 4 - } - _, err := mod.tsch.Run(ctx, &itaskschedulerservice.RunRequest{ - Path: taskPath, - Flags: flags, - SessionID: cfg.SessionId, - }) - if err != nil { - log.Error().Str("task", taskPath).Err(err).Msg("Failed to run task") - return fmt.Errorf("force run task: %w", err) - } - log.Info().Str("task", taskPath).Msg("Started task") - } - - } else { - return fmt.Errorf("method '%s' not implemented", ecfg.ExecutionMethod) - } - - return nil -} diff --git a/internal/exec/tsch/module.go b/internal/exec/tsch/module.go deleted file mode 100644 index 33c0723..0000000 --- a/internal/exec/tsch/module.go +++ /dev/null @@ -1,44 +0,0 @@ -package tschexec - -import ( - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" - "time" -) - -type Module struct { - // dce holds the working DCE connection interface - dce dcerpc.Conn - // tsch holds the ITaskSchedulerService client - tsch itaskschedulerservice.TaskSchedulerServiceClient -} - -type MethodRegisterConfig struct { - NoDelete bool - CallDelete bool - TaskPath string - StartDelay time.Duration - StopDelay time.Duration - DeleteDelay time.Duration -} - -type MethodDemandConfig struct { - NoDelete bool - CallDelete bool - SessionId uint32 - TaskName string - TaskPath string - StopDelay time.Duration - DeleteDelay time.Duration -} - -type MethodDeleteConfig struct { - TaskPath string -} - -const ( - MethodRegister string = "register" - MethodDemand string = "demand" - MethodDelete string = "delete" - MethodChange string = "update" -) diff --git a/internal/exec/tsch/task.go b/internal/exec/tsch/task.go deleted file mode 100644 index 928d029..0000000 --- a/internal/exec/tsch/task.go +++ /dev/null @@ -1,85 +0,0 @@ -package tschexec - -import ( - "fmt" - "regexp" - "time" -) - -var ( - TaskPathRegex = regexp.MustCompile(`^\\[^ :/\\][^:/]*$`) - TaskNameRegex = regexp.MustCompile(`^[^ :/\\][^:/\\]*$`) -) - -// newSettings just creates a settings instance with the necessary values + a few dynamic ones -func newSettings(terminate, onDemand, startWhenAvailable bool) *settings { - return &settings{ - MultipleInstancesPolicy: "IgnoreNew", - AllowHardTerminate: terminate, - IdleSettings: idleSettings{ - StopOnIdleEnd: true, - RestartOnIdle: false, - }, - AllowStartOnDemand: onDemand, - Enabled: true, - Hidden: true, - Priority: 7, // a pretty standard value for scheduled tasks - StartWhenAvailable: startWhenAvailable, - } -} - -// newTask creates a task with any static values filled -func newTask(se *settings, pr []principal, tr triggers, cmd, args string) *task { - if se == nil { - se = newSettings(true, true, false) - } - if pr == nil || len(pr) == 0 { - pr = []principal{ - { - ID: "1", - UserID: "S-1-5-18", - RunLevel: "HighestAvailable", - }, - } - } - return &task{ - TaskVersion: "1.2", - TaskNamespace: "http://schemas.microsoft.com/windows/2004/02/mit/task", - Triggers: tr, - Principals: principals{Principals: pr}, - Settings: *se, - Actions: actions{ - Context: pr[0].ID, - Exec: []actionExec{ - { - Command: cmd, - Arguments: args, - }, - }, - }, - } -} - -// xmlDuration is a *very* simple implementation of xs:duration - only accepts +seconds -func xmlDuration(dur time.Duration) string { - if s := int(dur.Seconds()); s >= 0 { - return fmt.Sprintf(`PT%dS`, s) - } - return `PT0S` -} - -// ValidateTaskName will validate the provided task name according to https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/fa8809c8-4f0f-4c6d-994a-6c10308757c1 -func ValidateTaskName(taskName string) error { - if !TaskNameRegex.MatchString(taskName) { - return fmt.Errorf("invalid task name: %s", taskName) - } - return nil -} - -// ValidateTaskPath will validate the provided task path according to https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/fa8809c8-4f0f-4c6d-994a-6c10308757c1 -func ValidateTaskPath(taskPath string) error { - if !TaskPathRegex.MatchString(taskPath) { - return fmt.Errorf("invalid task path: %s", taskPath) - } - return nil -} diff --git a/internal/exec/tsch/tsch.go b/internal/exec/tsch/tsch.go deleted file mode 100644 index d47e513..0000000 --- a/internal/exec/tsch/tsch.go +++ /dev/null @@ -1,141 +0,0 @@ -package tschexec - -import ( - "context" - "encoding/xml" - "fmt" - "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" - "github.com/rs/zerolog" -) - -const ( - TaskXMLDurationFormat = "2006-01-02T15:04:05.9999999Z" - TaskXMLHeader = `<?xml version="1.0" encoding="UTF-16"?>` -) - -// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/0d6383e4-de92-43e7-b0bb-a60cfa36379f - -type triggers struct { - XMLName xml.Name `xml:"Triggers"` - TimeTriggers []taskTimeTrigger `xml:"TimeTrigger,omitempty"` -} - -type taskTimeTrigger struct { - XMLName xml.Name `xml:"TimeTrigger"` - StartBoundary string `xml:"StartBoundary,omitempty"` // Derived from time.Time - EndBoundary string `xml:"EndBoundary,omitempty"` // Derived from time.Time; must be > StartBoundary - Enabled bool `xml:"Enabled"` -} - -type idleSettings struct { - XMLName xml.Name `xml:"IdleSettings"` - StopOnIdleEnd bool `xml:"StopOnIdleEnd"` - RestartOnIdle bool `xml:"RestartOnIdle"` -} - -type settings struct { - XMLName xml.Name `xml:"Settings"` - Enabled bool `xml:"Enabled"` - Hidden bool `xml:"Hidden"` - DisallowStartIfOnBatteries bool `xml:"DisallowStartIfOnBatteries"` - StopIfGoingOnBatteries bool `xml:"StopIfGoingOnBatteries"` - AllowHardTerminate bool `xml:"AllowHardTerminate"` - RunOnlyIfNetworkAvailable bool `xml:"RunOnlyIfNetworkAvailable"` - AllowStartOnDemand bool `xml:"AllowStartOnDemand"` - WakeToRun bool `xml:"WakeToRun"` - RunOnlyIfIdle bool `xml:"RunOnlyIfIdle"` - StartWhenAvailable bool `xml:"StartWhenAvailable"` - Priority int `xml:"Priority,omitempty"` // 1 to 10 inclusive - MultipleInstancesPolicy string `xml:"MultipleInstancesPolicy,omitempty"` - ExecutionTimeLimit string `xml:"ExecutionTimeLimit,omitempty"` - DeleteExpiredTaskAfter string `xml:"DeleteExpiredTaskAfter,omitempty"` // Derived from time.Duration - IdleSettings idleSettings `xml:"IdleSettings,omitempty"` -} - -type actionExec struct { - XMLName xml.Name `xml:"Exec"` - Command string `xml:"Command"` - Arguments string `xml:"Arguments,omitempty"` -} - -type actions struct { - XMLName xml.Name `xml:"Actions"` - Context string `xml:"Context,attr"` - Exec []actionExec `xml:"Exec,omitempty"` -} - -type principals struct { - XMLName xml.Name `xml:"Principals"` - Principals []principal `xml:"Principal,omitempty"` -} - -type principal struct { - XMLName xml.Name `xml:"Principal"` - ID string `xml:"id,attr"` - UserID string `xml:"UserId"` - RunLevel string `xml:"RunLevel"` -} - -type task struct { - 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 -func (mod *Module) registerTask(ctx context.Context, taskDef task, taskPath string) (path string, err error) { - - var taskXml string - - log := zerolog.Ctx(ctx).With(). - Str("module", "tsch"). - Str("func", "createTask").Logger() - - // Generate task XML content. See https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/0d6383e4-de92-43e7-b0bb-a60cfa36379f - { - doc, err := xml.Marshal(taskDef) - if err != nil { - log.Error().Err(err).Msg("failed to marshal task XML") - return "", fmt.Errorf("marshal task: %w", err) - } - taskXml = TaskXMLHeader + string(doc) - log.Debug().Str("content", taskXml).Msg("Generated task XML") - } - // Submit task - { - response, err := mod.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{ - Path: taskPath, - XML: taskXml, - Flags: 0, // TODO - LogonType: 0, // TASK_LOGON_NONE - CredsCount: 0, - Creds: nil, - }) - if err != nil { - log.Error().Err(err).Msg("Failed to register task") - return "", fmt.Errorf("register task: %w", err) - } - log.Info().Str("path", taskPath).Msg("Task created successfully") - path = response.ActualPath - } - return -} - -func (mod *Module) deleteTask(ctx context.Context, taskPath string) (err error) { - - log := zerolog.Ctx(ctx).With(). - Str("module", "tsch"). - Str("path", taskPath). - Str("func", "deleteTask").Logger() - - if _, err = mod.tsch.Delete(ctx, &itaskschedulerservice.DeleteRequest{Path: taskPath}); err != nil { - log.Error().Err(err).Msg("Failed to delete task") - return fmt.Errorf("delete task: %w", err) - } - log.Info().Msg("Task deleted successfully") - return -} |