aboutsummaryrefslogtreecommitdiff
path: root/internal/exec/tsch
diff options
context:
space:
mode:
Diffstat (limited to 'internal/exec/tsch')
-rw-r--r--internal/exec/tsch/exec.go189
-rw-r--r--internal/exec/tsch/module.go44
-rw-r--r--internal/exec/tsch/task.go85
-rw-r--r--internal/exec/tsch/tsch.go141
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
-}