diff options
author | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-04-17 09:55:07 -0500 |
---|---|---|
committer | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-04-17 09:55:07 -0500 |
commit | 4f906bddd3f4261b2d45bf37a4adfe795c42967e (patch) | |
tree | b926e0d5a3520234f08209db68069d780a9e9230 /pkg | |
parent | fc2ed14f92dd82268ca94d3d08c3760aba534d3f (diff) | |
download | goexec-4f906bddd3f4261b2d45bf37a4adfe795c42967e.tar.gz goexec-4f906bddd3f4261b2d45bf37a4adfe795c42967e.zip |
Update output,IO; add output support to WMI
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/goexec/dcom/mmc.go | 73 | ||||
-rw-r--r-- | pkg/goexec/dcom/module.go | 209 | ||||
-rw-r--r-- | pkg/goexec/io.go | 106 | ||||
-rw-r--r-- | pkg/goexec/tsch/create.go | 2 | ||||
-rw-r--r-- | pkg/goexec/tsch/demand.go | 114 | ||||
-rw-r--r-- | pkg/goexec/tsch/module.go | 261 | ||||
-rw-r--r-- | pkg/goexec/wmi/proc.go | 85 |
7 files changed, 422 insertions, 428 deletions
diff --git a/pkg/goexec/dcom/mmc.go b/pkg/goexec/dcom/mmc.go index 9c92af3..ecb3a74 100644 --- a/pkg/goexec/dcom/mmc.go +++ b/pkg/goexec/dcom/mmc.go @@ -1,50 +1,51 @@ package dcomexec import ( - "context" - "fmt" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/rs/zerolog" + "context" + "fmt" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/rs/zerolog" ) const ( - MethodMmc = "MMC" // MMC20.Application::Document.ActiveView.ExecuteShellCommand + MethodMmc = "MMC" // MMC20.Application::Document.ActiveView.ExecuteShellCommand ) type DcomMmc struct { - DcomExec + Dcom - WorkingDirectory string - WindowState string + IO goexec.ExecutionIO + + WorkingDirectory string + WindowState string } // Execute will perform command execution via the MMC20.Application DCOM object. -func (m *DcomMmc) Execute(ctx context.Context, in *goexec.ExecutionInput) (err error) { - - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName). - Str("method", MethodMmc). - Logger() - - method := "Document.ActiveView.ExecuteShellCommand" - - var args = in.Arguments - if args == "" { - args = " " // the process arguments can't be a blank string - } - - // Arguments must be passed in reverse order - if _, err := callComMethod(ctx, - m.dispatchClient, - method, - stringToVariant(m.WindowState), - stringToVariant(in.Arguments), - stringToVariant(m.WorkingDirectory), - stringToVariant(in.Executable)); err != nil { - - log.Error().Err(err).Msg("Failed to call method") - return fmt.Errorf("call %q: %w", method, err) - } - log.Info().Msg("Method call successful") - return +func (m *DcomMmc) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) { + + log := zerolog.Ctx(ctx).With(). + Str("module", ModuleName). + Str("method", MethodMmc). + Logger() + + method := "Document.ActiveView.ExecuteShellCommand" + + cmdline := execIO.CommandLine() + proc := cmdline[0] + args := cmdline[1] + + // Arguments must be passed in reverse order + if _, err := callComMethod(ctx, + m.dispatchClient, + method, + stringToVariant(m.WindowState), + stringToVariant(args), + stringToVariant(m.WorkingDirectory), + stringToVariant(proc)); err != nil { + + log.Error().Err(err).Msg("Failed to call method") + return fmt.Errorf("call %q: %w", method, err) + } + log.Info().Msg("Method call successful") + return } diff --git a/pkg/goexec/dcom/module.go b/pkg/goexec/dcom/module.go index 47dc7ca..fd331d2 100644 --- a/pkg/goexec/dcom/module.go +++ b/pkg/goexec/dcom/module.go @@ -1,111 +1,120 @@ package dcomexec import ( - "context" - "errors" - "fmt" - "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/msrpc/dcom" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/iremotescmactivator/v0" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0" - "github.com/rs/zerolog" + "context" + "errors" + "fmt" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" + "github.com/oiweiwei/go-msrpc/dcerpc" + "github.com/oiweiwei/go-msrpc/msrpc/dcom" + "github.com/oiweiwei/go-msrpc/msrpc/dcom/iremotescmactivator/v0" + "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0" + "github.com/rs/zerolog" ) const ( - ModuleName = "DCOM" + ModuleName = "DCOM" ) -type DcomExec struct { - client *dce.Client - dispatchClient idispatch.DispatchClient +type Dcom struct { + goexec.Cleaner + + Client *dce.Client + + dispatchClient idispatch.DispatchClient +} + +func (m *Dcom) Connect(ctx context.Context) (err error) { + + if err = m.Client.Connect(ctx); err == nil { + m.AddCleaner(m.Client.Close) + } + return } -func (m *DcomExec) Init(ctx context.Context, c *dce.Client) (err error) { - - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName).Logger() - - m.client = c - - if m.client.Dce() == nil { - return errors.New("DCE connection not initialized") - } - - opts := []dcerpc.Option{ - dcerpc.WithSign(), - } - - inst := &dcom.InstantiationInfoData{ - ClassID: &MmcClsid, - IID: []*dcom.IID{IDispatchIID}, - ClientCOMVersion: ComVersion, - } - ac := &dcom.ActivationContextInfoData{} - loc := &dcom.LocationInfoData{} - scm := &dcom.SCMRequestInfoData{ - RemoteRequest: &dcom.CustomRemoteRequestSCMInfo{ - RequestedProtocolSequences: []uint16{7}, - }, - } - - ap := &dcom.ActivationProperties{ - DestinationContext: 2, - Properties: []dcom.ActivationProperty{inst, ac, loc, scm}, - } - - apin, err := ap.ActivationPropertiesIn() - if err != nil { - return err - } - - act, err := iremotescmactivator.NewRemoteSCMActivatorClient(ctx, m.client.Dce()) - if err != nil { - return err - } - - cr, err := act.RemoteCreateInstance(ctx, &iremotescmactivator.RemoteCreateInstanceRequest{ - ORPCThis: &dcom.ORPCThis{ - Version: ComVersion, - Flags: 1, - CID: &RandCid, - }, - ActPropertiesIn: apin, - }) - if err != nil { - return err - } - log.Info().Msg("RemoteCreateInstance succeeded") - - apout := new(dcom.ActivationProperties) - if err = apout.Parse(cr.ActPropertiesOut); err != nil { - return err - } - si := apout.SCMReplyInfoData() - pi := apout.PropertiesOutInfo() - - if si == nil { - return fmt.Errorf("remote create instance response: SCMReplyInfoData is nil") - } - - if pi == nil { - return fmt.Errorf("remote create instance response: PropertiesOutInfo is nil") - } - - oIPID := pi.InterfaceData[0].IPID() - opts = append(opts, si.RemoteReply.OXIDBindings.EndpointsByProtocol("ncacn_ip_tcp")...) // TODO - - err = c.Reconnect(ctx, opts...) - if err != nil { - return err - } - log.Info().Msg("created new DCERPC dialer") - - m.dispatchClient, err = idispatch.NewDispatchClient(ctx, c.Dce(), dcom.WithIPID(oIPID)) - if err != nil { - return err - } - log.Info().Msg("created IDispatch client") - - return +func (m *Dcom) Init(ctx context.Context) (err error) { + + log := zerolog.Ctx(ctx).With(). + Str("module", ModuleName).Logger() + + if m.Client == nil || m.Client.Dce() == nil { + return errors.New("DCE connection not initialized") + } + + opts := []dcerpc.Option{ + dcerpc.WithSign(), + } + + inst := &dcom.InstantiationInfoData{ + ClassID: &MmcClsid, + IID: []*dcom.IID{IDispatchIID}, + ClientCOMVersion: ComVersion, + } + ac := &dcom.ActivationContextInfoData{} + loc := &dcom.LocationInfoData{} + scm := &dcom.SCMRequestInfoData{ + RemoteRequest: &dcom.CustomRemoteRequestSCMInfo{ + RequestedProtocolSequences: []uint16{7}, + }, + } + + ap := &dcom.ActivationProperties{ + DestinationContext: 2, + Properties: []dcom.ActivationProperty{inst, ac, loc, scm}, + } + + apin, err := ap.ActivationPropertiesIn() + if err != nil { + return err + } + + act, err := iremotescmactivator.NewRemoteSCMActivatorClient(ctx, m.Client.Dce()) + if err != nil { + return err + } + + cr, err := act.RemoteCreateInstance(ctx, &iremotescmactivator.RemoteCreateInstanceRequest{ + ORPCThis: &dcom.ORPCThis{ + Version: ComVersion, + Flags: 1, + CID: &RandCid, + }, + ActPropertiesIn: apin, + }) + if err != nil { + return err + } + log.Info().Msg("RemoteCreateInstance succeeded") + + apout := new(dcom.ActivationProperties) + if err = apout.Parse(cr.ActPropertiesOut); err != nil { + return err + } + si := apout.SCMReplyInfoData() + pi := apout.PropertiesOutInfo() + + if si == nil { + return fmt.Errorf("remote create instance response: SCMReplyInfoData is nil") + } + + if pi == nil { + return fmt.Errorf("remote create instance response: PropertiesOutInfo is nil") + } + + opts = append(opts, si.RemoteReply.OXIDBindings.EndpointsByProtocol("ncacn_ip_tcp")...) // TODO + + err = m.Client.Reconnect(ctx, opts...) + if err != nil { + return err + } + log.Info().Msg("created new DCERPC dialer") + + m.dispatchClient, err = idispatch.NewDispatchClient(ctx, m.Client.Dce(), dcom.WithIPID(pi.InterfaceData[0].IPID())) + if err != nil { + return err + } + log.Info().Msg("created IDispatch Client") + + return } diff --git a/pkg/goexec/io.go b/pkg/goexec/io.go index c26fc7f..6bfb76e 100644 --- a/pkg/goexec/io.go +++ b/pkg/goexec/io.go @@ -1,94 +1,84 @@ package goexec import ( - "bytes" - "context" - "fmt" - "io" - "os" + "context" + "fmt" + "io" + "strings" ) type OutputProvider interface { - GetOutput(ctx context.Context, writer io.Writer) (err error) - Clean(ctx context.Context) (err error) + GetOutput(ctx context.Context, writer io.Writer) (err error) + Clean(ctx context.Context) (err error) } type ExecutionIO struct { - Cleaner + Cleaner - Input *ExecutionInput - Output *ExecutionOutput + Input *ExecutionInput + Output *ExecutionOutput } type ExecutionOutput struct { - NoDelete bool - RemotePath string - Provider OutputProvider - Writer io.WriteCloser + NoDelete bool + RemotePath string + Provider OutputProvider + Writer io.WriteCloser } type ExecutionInput struct { - FilePath string - Executable string - ExecutablePath string - Arguments string - CommandLine string + FilePath string + Executable string + ExecutablePath string + Arguments string + Command string } func (execIO *ExecutionIO) GetOutput(ctx context.Context) (err error) { - if execIO.Output.Provider != nil { - return execIO.Output.Provider.GetOutput(ctx, execIO.Output.Writer) - } - return nil + if execIO.Output.Provider != nil { + return execIO.Output.Provider.GetOutput(ctx, execIO.Output.Writer) + } + return nil } -func (execIO *ExecutionIO) CommandLine() string { - return execIO.Input.Command() +func (execIO *ExecutionIO) CommandLine() (cmd []string) { + if execIO.Output.Provider != nil && execIO.Output.RemotePath != "" { + return []string{ + `C:\Windows\System32\cmd.exe`, + fmt.Sprintf(`/C %s > %s 2>&1`, execIO.Input.String(), execIO.Output.RemotePath), + } + } + return execIO.Input.CommandLine() } func (execIO *ExecutionIO) Clean(ctx context.Context) (err error) { - if execIO.Output.Provider != nil { - return execIO.Output.Provider.Clean(ctx) - } - return nil + if execIO.Output.Provider != nil { + return execIO.Output.Provider.Clean(ctx) + } + return nil } func (execIO *ExecutionIO) String() (cmd string) { - - cmd = execIO.Input.Command() - - if execIO.Output.Provider != nil && execIO.Output.RemotePath != "" { - return fmt.Sprintf(`C:\Windows\System32\cmd.exe /C %s > %s`, cmd, execIO.Output.RemotePath) - } - return + return strings.Join(execIO.CommandLine(), " ") } -func (i *ExecutionInput) Command() string { - - if i.CommandLine == "" { +func (i *ExecutionInput) CommandLine() (cmd []string) { + cmd = make([]string, 2) + cmd[1] = i.Arguments - if i.ExecutablePath != "" { - i.CommandLine = i.ExecutablePath + switch { + case i.Command != "": + return strings.SplitN(i.Command, " ", 2) - } else if i.Executable != "" { - i.CommandLine = i.Executable - } + case i.ExecutablePath != "": + cmd[0] = i.ExecutablePath - if i.Arguments != "" { - i.CommandLine += " " + i.Arguments - } - } - return i.CommandLine + case i.Executable != "": + cmd[0] = i.Executable + } + return cmd } func (i *ExecutionInput) String() string { - return i.Command() -} - -func (i *ExecutionInput) UploadReader(_ context.Context) (reader io.Reader, err error) { - - if i.FilePath != "" { - return os.OpenFile(i.FilePath, os.O_RDONLY, 0) - } - return bytes.NewBufferString(i.Command()), nil + return strings.Join(i.CommandLine(), " ") } diff --git a/pkg/goexec/tsch/create.go b/pkg/goexec/tsch/create.go index 8c99c82..43964e4 100644 --- a/pkg/goexec/tsch/create.go +++ b/pkg/goexec/tsch/create.go @@ -32,7 +32,7 @@ func (m *TschCreate) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (e log := zerolog.Ctx(ctx).With(). Str("module", ModuleName). Str("method", MethodCreate). - Str("task", m.TaskName). + Str("task", m.TaskPath). Logger() startTime := time.Now().UTC().Add(m.StartDelay) diff --git a/pkg/goexec/tsch/demand.go b/pkg/goexec/tsch/demand.go index 11522dd..ed6c043 100644 --- a/pkg/goexec/tsch/demand.go +++ b/pkg/goexec/tsch/demand.go @@ -1,81 +1,81 @@ package tschexec import ( - "context" - "fmt" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" - "github.com/rs/zerolog" + "context" + "fmt" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" + "github.com/rs/zerolog" ) const ( - MethodDemand = "Demand" + MethodDemand = "Demand" ) type TschDemand struct { - Tsch - goexec.Executor - goexec.Cleaner + Tsch + goexec.Executor + goexec.Cleaner - IO goexec.ExecutionIO + IO goexec.ExecutionIO - NoDelete bool - NoStart bool - SessionId uint32 + NoDelete bool + NoStart bool + SessionId uint32 } func (m *TschDemand) Execute(ctx context.Context, in *goexec.ExecutionIO) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName). - Str("method", MethodDemand). - Str("task", m.TaskName). - Logger() + log := zerolog.Ctx(ctx).With(). + Str("module", ModuleName). + Str("method", MethodDemand). + Str("task", m.TaskPath). + Logger() - path, err := m.registerTask(ctx, - ®isterOptions{ - AllowStartOnDemand: true, - AllowHardTerminate: true, - Hidden: !m.NotHidden, - triggers: taskTriggers{}, - }, - in, - ) - if err != nil { - return err - } + path, err := m.registerTask(ctx, + ®isterOptions{ + AllowStartOnDemand: true, + AllowHardTerminate: true, + Hidden: !m.NotHidden, + triggers: taskTriggers{}, + }, + in, + ) + if err != nil { + return err + } - log.Info().Msg("Task registered") + log.Info().Msg("Task registered") - if !m.NoDelete { - m.AddCleaner(func(ctxInner context.Context) error { - return m.deleteTask(ctxInner, path) - }) - } + if !m.NoDelete { + m.AddCleaner(func(ctxInner context.Context) error { + return m.deleteTask(ctxInner, path) + }) + } - if !m.NoStart { + if !m.NoStart { - var flags uint32 - if m.SessionId != 0 { - flags |= 4 - } + var flags uint32 + if m.SessionId != 0 { + flags |= 4 + } - runResponse, err := m.tsch.Run(ctx, &itaskschedulerservice.RunRequest{ - Path: path, - Flags: flags, - SessionID: m.SessionId, - }) + runResponse, err := m.tsch.Run(ctx, &itaskschedulerservice.RunRequest{ + Path: path, + Flags: flags, + SessionID: m.SessionId, + }) - if err != nil { - log.Error().Err(err).Msg("Failed to run task") - return fmt.Errorf("run task: %w", err) - } - if ret := uint32(runResponse.Return); ret != 0 { - log.Error().Str("code", fmt.Sprintf("0x%08x", ret)).Msg("Task returned non-zero exit code") - return fmt.Errorf("task returned non-zero exit code: 0x%08x", ret) - } + if err != nil { + log.Error().Err(err).Msg("Failed to run task") + return fmt.Errorf("run task: %w", err) + } + if ret := uint32(runResponse.Return); ret != 0 { + log.Error().Str("code", fmt.Sprintf("0x%08x", ret)).Msg("Task returned non-zero exit code") + return fmt.Errorf("task returned non-zero exit code: 0x%08x", ret) + } - log.Info().Msg("Task started successfully") - } - return + log.Info().Msg("Task started successfully") + } + return } diff --git a/pkg/goexec/tsch/module.go b/pkg/goexec/tsch/module.go index 13d7b24..74ded2f 100644 --- a/pkg/goexec/tsch/module.go +++ b/pkg/goexec/tsch/module.go @@ -1,173 +1,162 @@ package tschexec import ( - "context" - "encoding/xml" - "errors" - "fmt" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" - "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" - "github.com/rs/zerolog" - "strings" + "context" + "encoding/xml" + "errors" + "fmt" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" + "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" + "github.com/rs/zerolog" ) const ( - ModuleName = "TSCH" + ModuleName = "TSCH" ) type Tsch struct { - goexec.Cleaner + goexec.Cleaner - Client *dce.Client - tsch itaskschedulerservice.TaskSchedulerServiceClient + Client *dce.Client + tsch itaskschedulerservice.TaskSchedulerServiceClient - TaskName string - TaskPath string - UserSid string - NotHidden bool + TaskPath string + UserSid string + NotHidden bool } type registerOptions struct { - AllowStartOnDemand bool - AllowHardTerminate bool - StartWhenAvailable bool - Hidden bool - DeleteAfter string + AllowStartOnDemand bool + AllowHardTerminate bool + StartWhenAvailable bool + Hidden bool + DeleteAfter string - triggers taskTriggers + triggers taskTriggers } func (m *Tsch) Connect(ctx context.Context) (err error) { - if err = m.Client.Connect(ctx); err == nil { - m.AddCleaner(m.Client.Close) - } - return + if err = m.Client.Connect(ctx); err == nil { + m.AddCleaner(m.Client.Close) + } + return } func (m *Tsch) Init(ctx context.Context) (err error) { - if m.Client.Dce() == nil { - return errors.New("DCE connection not initialized") - } + if m.Client.Dce() == nil { + return errors.New("DCE connection not initialized") + } - // Create ITaskSchedulerService Client - m.tsch, err = itaskschedulerservice.NewTaskSchedulerServiceClient(ctx, m.Client.Dce()) - return -} - -func (m *Tsch) taskPath() string { - if m.TaskPath == "" { - m.TaskPath = `\` + m.TaskName - } - return m.TaskPath + // Create ITaskSchedulerService Client + m.tsch, err = itaskschedulerservice.NewTaskSchedulerServiceClient(ctx, m.Client.Dce()) + return } func (m *Tsch) registerTask(ctx context.Context, opts *registerOptions, in *goexec.ExecutionIO) (path string, err error) { - log := zerolog.Ctx(ctx).With(). - Str("task", m.TaskName). - Logger() - - ctx = log.WithContext(ctx) - - principalId := "1" - - settings := taskSettings{ - MultipleInstancesPolicy: "IgnoreNew", - IdleSettings: taskIdleSettings{ - StopOnIdleEnd: true, - RestartOnIdle: false, - }, - Enabled: true, - Priority: 7, // a pretty standard value for scheduled tasks - - AllowHardTerminate: opts.AllowHardTerminate, - AllowStartOnDemand: opts.AllowStartOnDemand, - Hidden: opts.Hidden, - StartWhenAvailable: opts.StartWhenAvailable, - DeleteExpiredTaskAfter: opts.DeleteAfter, - } - - principals := taskPrincipals{ - Principals: []taskPrincipal{ - { - ID: principalId, // TODO: dynamic - UserID: m.UserSid, - RunLevel: "HighestAvailable", - }, - }} - - e := taskActionExec{} - - if ea := strings.SplitN(in.String(), " ", 2); len(ea) == 1 { - e.Command = ea[0] - } else { - e.Command = ea[0] - e.Arguments = ea[1] - } - - actions := taskActions{ - Context: principalId, - Exec: []taskActionExec{e}, - } - - def := task{ - TaskVersion: TaskXmlVersion, - TaskNamespace: TaskXmlNamespace, - Triggers: opts.triggers, - Actions: actions, - Principals: principals, - Settings: settings, - } - - // 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(def) - - 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") - - registerResponse, err := m.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{ - Path: m.taskPath(), - XML: taskXml, - Flags: 0, // FEATURE: dynamic - SDDL: "", - LogonType: 0, // FEATURE: dynamic - CredsCount: 0, - Creds: nil, - }) - - if err != nil { - log.Error().Err(err).Msg("Failed to register task") - return "", fmt.Errorf("register task: %w", err) - } - - return registerResponse.ActualPath, nil + log := zerolog.Ctx(ctx).With(). + Str("task", m.TaskPath). + Logger() + + ctx = log.WithContext(ctx) + + principalId := "1" // This value can be anything + + settings := taskSettings{ + MultipleInstancesPolicy: "IgnoreNew", + IdleSettings: taskIdleSettings{ + StopOnIdleEnd: true, + RestartOnIdle: false, + }, + Enabled: true, + Priority: 7, // a pretty standard value for scheduled tasks + AllowHardTerminate: opts.AllowHardTerminate, + AllowStartOnDemand: opts.AllowStartOnDemand, + Hidden: opts.Hidden, + StartWhenAvailable: opts.StartWhenAvailable, + DeleteExpiredTaskAfter: opts.DeleteAfter, + } + + principals := taskPrincipals{ + Principals: []taskPrincipal{ + { + ID: principalId, + UserID: m.UserSid, + RunLevel: "HighestAvailable", + }, + }} + + cmdline := in.CommandLine() + + actions := taskActions{ + Context: principalId, + Exec: []taskActionExec{ + { + Command: cmdline[0], + Arguments: cmdline[1], + }, + }, + } + + def := task{ + TaskVersion: TaskXmlVersion, + TaskNamespace: TaskXmlNamespace, + Triggers: opts.triggers, + Actions: actions, + Principals: principals, + Settings: settings, + } + + // 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(def) + + 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") + + registerResponse, err := m.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{ + Path: m.TaskPath, + XML: taskXml, + Flags: 0, // FEATURE: dynamic + SDDL: "", + LogonType: 0, // FEATURE: dynamic + 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().Msg("Scheduled task registered") + + return registerResponse.ActualPath, nil } func (m *Tsch) deleteTask(ctx context.Context, taskPath string) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("path", taskPath).Logger() + log := zerolog.Ctx(ctx).With(). + Str("path", taskPath).Logger() - _, err = m.tsch.Delete(ctx, &itaskschedulerservice.DeleteRequest{ - Path: taskPath, - }) + _, err = m.tsch.Delete(ctx, &itaskschedulerservice.DeleteRequest{ + Path: taskPath, + }) - if err != nil { - log.Error().Err(err).Msg("Failed to delete task") - return fmt.Errorf("delete task: %w", err) - } + if err != nil { + log.Error().Err(err).Msg("Failed to delete task") + return fmt.Errorf("delete task: %w", err) + } - log.Info().Msg("Task deleted") + log.Info().Msg("Task deleted") - return + return } diff --git a/pkg/goexec/wmi/proc.go b/pkg/goexec/wmi/proc.go index 444643b..fe22bdf 100644 --- a/pkg/goexec/wmi/proc.go +++ b/pkg/goexec/wmi/proc.go @@ -1,54 +1,59 @@ package wmiexec import ( - "context" - "errors" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/rs/zerolog" + "context" + "errors" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/rs/zerolog" ) const ( - MethodProc = "Proc" + MethodProc = "Proc" ) type WmiProc struct { - Wmi - IO goexec.ExecutionIO - WorkingDirectory string + Wmi + IO goexec.ExecutionIO + WorkingDirectory string } func (m *WmiProc) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName). - Str("method", MethodProc). - Logger() - ctx = log.WithContext(ctx) - - if execIO == nil { - return errors.New("execution IO is nil") - } - - out, err := m.query(ctx, - "Win32_Process", - "Create", - - map[string]any{ - "CommandLine": execIO.String(), - "WorkingDir": m.WorkingDirectory, - }, - ) - if err != nil { - return - } - - if pid := out["ProcessId"].(uint32); pid != 0 { - log = log.With().Uint32("pid", pid).Logger() - } - log.Info().Err(err).Msg("Process created") - - if ret := out["ReturnValue"].(uint32); ret != 0 { - log.Error().Err(err).Uint32("return", ret).Msg("Process returned non-zero exit code") - } - return + log := zerolog.Ctx(ctx).With(). + Str("module", ModuleName). + Str("method", MethodProc). + Logger() + ctx = log.WithContext(ctx) + + if execIO == nil { + return errors.New("execution IO is nil") + } + + out, err := m.query(ctx, + "Win32_Process", + "Create", + map[string]any{ + "CommandLine": execIO.String(), + "WorkingDir": m.WorkingDirectory, + }, + ) + if err != nil { + return + } + + if pid, ok := out["ProcessId"].(uint32); pid != 0 { + log = log.With().Uint32("pid", pid).Logger() + + } else if !ok { + return errors.New("process creation failed") + } + log.Info().Err(err).Msg("Process created") + + if ret, ok := out["ReturnValue"].(uint32); ret != 0 { + log.Error().Err(err).Uint32("return", ret).Msg("Process returned non-zero exit code") + + } else if !ok { + return errors.New("invalid call response") + } + return } |