diff options
author | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-04-20 05:16:35 -0500 |
---|---|---|
committer | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-04-20 05:16:35 -0500 |
commit | 61578457eed9243d3be1bb120cce5995e149adec (patch) | |
tree | 143706cd5da71bc46bb5d98ca036c41ecf4a0f09 /pkg | |
parent | 82fc125fd02f236481b0fa581047979fc2845898 (diff) | |
download | goexec-61578457eed9243d3be1bb120cce5995e149adec.tar.gz goexec-61578457eed9243d3be1bb120cce5995e149adec.zip |
Implemented SCMR Clean*Method
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/goexec/clean.go | 8 | ||||
-rw-r--r-- | pkg/goexec/dcom/dcom.go | 5 | ||||
-rw-r--r-- | pkg/goexec/dcom/module.go | 15 | ||||
-rw-r--r-- | pkg/goexec/exec.go | 13 | ||||
-rw-r--r-- | pkg/goexec/io.go | 105 | ||||
-rw-r--r-- | pkg/goexec/method.go | 5 | ||||
-rw-r--r-- | pkg/goexec/module.go | 25 | ||||
-rw-r--r-- | pkg/goexec/scmr/change.go | 12 | ||||
-rw-r--r-- | pkg/goexec/scmr/create.go | 18 | ||||
-rw-r--r-- | pkg/goexec/scmr/delete.go | 26 | ||||
-rw-r--r-- | pkg/goexec/scmr/module.go | 121 | ||||
-rw-r--r-- | pkg/goexec/scmr/scmr.go | 37 | ||||
-rw-r--r-- | pkg/goexec/smb/output.go | 6 | ||||
-rw-r--r-- | pkg/goexec/tsch/create.go | 194 | ||||
-rw-r--r-- | pkg/goexec/tsch/demand.go | 114 | ||||
-rw-r--r-- | pkg/goexec/tsch/module.go | 2 | ||||
-rw-r--r-- | pkg/goexec/wmi/module.go | 4 |
17 files changed, 362 insertions, 348 deletions
diff --git a/pkg/goexec/clean.go b/pkg/goexec/clean.go index a853dc4..6f0f5fa 100644 --- a/pkg/goexec/clean.go +++ b/pkg/goexec/clean.go @@ -5,16 +5,16 @@ import ( "github.com/rs/zerolog" ) -type CleanProvider interface { - Clean(ctx context.Context) (err error) +type Clean interface { + Clean(ctx context.Context) error } type Cleaner struct { workers []func(ctx context.Context) error } -func (c *Cleaner) AddCleaner(worker func(ctx context.Context) error) { - c.workers = append(c.workers, worker) +func (c *Cleaner) AddCleaners(workers ...func(ctx context.Context) error) { + c.workers = append(c.workers, workers...) } func (c *Cleaner) Clean(ctx context.Context) (err error) { diff --git a/pkg/goexec/dcom/dcom.go b/pkg/goexec/dcom/dcom.go index e66a382..41f48d6 100644 --- a/pkg/goexec/dcom/dcom.go +++ b/pkg/goexec/dcom/dcom.go @@ -1,7 +1,7 @@ package dcomexec import ( - guuid "github.com/google/uuid" + googleUUID "github.com/google/uuid" "github.com/oiweiwei/go-msrpc/midl/uuid" "github.com/oiweiwei/go-msrpc/msrpc/dcom" "github.com/oiweiwei/go-msrpc/msrpc/dtyp" @@ -15,7 +15,7 @@ var ( ShellWindowsUuid = uuid.MustParse("9BA05972-F6A8-11CF-A442-00A0C90A8F39") Mmc20Uuid = uuid.MustParse("49B2791A-B1AE-4C90-9B8E-E860BA07F889") - RandCid = dcom.CID(*dtyp.GUIDFromUUID(uuid.MustParse(guuid.NewString()))) + RandCid = dcom.CID(*dtyp.GUIDFromUUID(uuid.MustParse(googleUUID.NewString()))) IDispatchIID = &dcom.IID{ Data1: 0x20400, Data2: 0x0, @@ -26,7 +26,6 @@ var ( MajorVersion: 5, MinorVersion: 7, } - MmcClsid = dcom.ClassID(*dtyp.GUIDFromUUID(Mmc20Uuid)) ORPCThis = &dcom.ORPCThis{ Version: ComVersion, CID: &RandCid, diff --git a/pkg/goexec/dcom/module.go b/pkg/goexec/dcom/module.go index 40804c3..1aa44c1 100644 --- a/pkg/goexec/dcom/module.go +++ b/pkg/goexec/dcom/module.go @@ -21,6 +21,7 @@ const ( type Dcom struct { goexec.Cleaner + goexec.Executor Client *dce.Client ClassID string @@ -31,7 +32,7 @@ type Dcom struct { func (m *Dcom) Connect(ctx context.Context) (err error) { if err = m.Client.Connect(ctx); err == nil { - m.AddCleaner(m.Client.Close) + m.AddCleaners(m.Client.Close) } return } @@ -113,7 +114,17 @@ func (m *Dcom) Init(ctx context.Context) (err error) { return fmt.Errorf("remote create instance response: PropertiesOutInfo is nil") } - opts = append(opts, si.RemoteReply.OXIDBindings.EndpointsByProtocol("ncacn_ip_tcp")...) + // Ensure that the string bindings don't contain the target hostname + for _, bind := range si.RemoteReply.OXIDBindings.GetStringBindings() { + stringBinding, err := dcerpc.ParseStringBinding("ncacn_ip_tcp:" + bind.NetworkAddr) // TODO: try bind.String() + + if err != nil { + log.Debug().Err(err).Msg("Failed to parse string binding") + continue + } + stringBinding.NetworkAddress = "" + opts = append(opts, dcerpc.WithEndpoint(stringBinding.String())) + } err = m.Client.Reconnect(ctx, opts...) if err != nil { diff --git a/pkg/goexec/exec.go b/pkg/goexec/exec.go index cb44f19..04bcc4c 100644 --- a/pkg/goexec/exec.go +++ b/pkg/goexec/exec.go @@ -1,14 +1,5 @@ package goexec -import "context" - -type ExecutionProvider interface { - Execute(ctx context.Context, in *ExecutionInput) (err error) -} - -type Executor struct{} - -type CleanExecutionProvider interface { - ExecutionProvider - CleanProvider +// Executor is a structure shared by all execution methods +type Executor struct { } diff --git a/pkg/goexec/io.go b/pkg/goexec/io.go index 4fa7cb8..ab2b704 100644 --- a/pkg/goexec/io.go +++ b/pkg/goexec/io.go @@ -1,90 +1,91 @@ package goexec import ( - "context" - "fmt" - "io" - "strings" + "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 - Command 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) 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) 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() + 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) String() (cmd string) { - return strings.Join(execIO.CommandLine(), " ") +func (execIO *ExecutionIO) String() string { + cmd := execIO.CommandLine() + + // Ensure that executable paths are quoted + if strings.Contains(cmd[0], " ") { + return fmt.Sprintf(`%q %s`, cmd[0], strings.Join(cmd[1:], " ")) + } + return strings.Join(cmd, " ") } func (i *ExecutionInput) CommandLine() (cmd []string) { - cmd = make([]string, 2) - cmd[1] = i.Arguments - - switch { - case i.Command != "": - return strings.SplitN(i.Command, " ", 2) + cmd = make([]string, 2) + cmd[1] = i.Arguments - case i.ExecutablePath != "": - cmd[0] = i.ExecutablePath + switch { + case i.Command != "": + return strings.SplitN(i.Command, " ", 2) - case i.Executable != "": - cmd[0] = i.Executable - } + case i.ExecutablePath != "": + cmd[0] = i.ExecutablePath - // Ensure that executable paths are quoted - if strings.Contains(cmd[0], " ") { - cmd[0] = fmt.Sprintf(`%q`, cmd[0]) - } + case i.Executable != "": + cmd[0] = i.Executable + } - return cmd + return cmd } func (i *ExecutionInput) String() string { - return strings.Join(i.CommandLine(), " ") + return strings.Join(i.CommandLine(), " ") } diff --git a/pkg/goexec/method.go b/pkg/goexec/method.go index 97f6f3b..e57442f 100644 --- a/pkg/goexec/method.go +++ b/pkg/goexec/method.go @@ -11,10 +11,6 @@ type Method interface { Init(ctx context.Context) error } -type Clean interface { - Clean(ctx context.Context) error -} - type CleanMethod interface { Method Clean @@ -98,7 +94,6 @@ func ExecuteCleanAuxiliaryMethod(ctx context.Context, module CleanAuxiliaryMetho }() if err = ExecuteAuxiliaryMethod(ctx, module); err != nil { - log.Error().Err(err).Msg("Auxiliary method failed") return fmt.Errorf("execute auxiliary method: %w", err) } return diff --git a/pkg/goexec/module.go b/pkg/goexec/module.go deleted file mode 100644 index 6544b78..0000000 --- a/pkg/goexec/module.go +++ /dev/null @@ -1,25 +0,0 @@ -package goexec - -import "context" - -type Module interface { - Init(ctx context.Context) error -} - -type ExecutionModule interface { - Module - ExecutionProvider -} - -type CleanExecutionModule interface { - Module - ExecutionProvider - CleanProvider -} - -type CleanExecutionOutputModule interface { - Module - ExecutionProvider - CleanProvider - OutputProvider -} diff --git a/pkg/goexec/scmr/change.go b/pkg/goexec/scmr/change.go index 55b8fac..014ad08 100644 --- a/pkg/goexec/scmr/change.go +++ b/pkg/goexec/scmr/change.go @@ -17,15 +17,15 @@ type ScmrChange struct { goexec.Cleaner goexec.Executor + IO goexec.ExecutionIO + NoStart bool ServiceName string } -func (m *ScmrChange) Execute(ctx context.Context, in *goexec.ExecutionInput) (err error) { +func (m *ScmrChange) Execute(ctx context.Context, in *goexec.ExecutionIO) (err error) { log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName). - Str("method", MethodChange). Str("service", m.ServiceName). Logger() @@ -49,7 +49,7 @@ func (m *ScmrChange) Execute(ctx context.Context, in *goexec.ExecutionInput) (er svc.handle = openResponse.Service log.Info().Msg("Opened service handle") - defer m.AddCleaner(func(ctxInner context.Context) error { + defer m.AddCleaners(func(ctxInner context.Context) error { r, errInner := m.ctl.CloseService(ctxInner, &svcctl.CloseServiceRequest{ ServiceObject: svc.handle, @@ -65,7 +65,7 @@ func (m *ScmrChange) Execute(ctx context.Context, in *goexec.ExecutionInput) (er return nil }) - // Note original service configuration + // Note the original service configuration queryResponse, err := m.ctl.QueryServiceConfigW(ctx, &svcctl.QueryServiceConfigWRequest{ Service: svc.handle, BufferLength: 8 * 1024, @@ -99,7 +99,7 @@ func (m *ScmrChange) Execute(ctx context.Context, in *goexec.ExecutionInput) (er // TODO: restore state /* - defer m.AddCleaner(func(ctxInner context.Context) error { + defer m.AddCleaners(func(ctxInner context.Context) error { // ... return nil }) diff --git a/pkg/goexec/scmr/create.go b/pkg/goexec/scmr/create.go index 686c347..b0f8774 100644 --- a/pkg/goexec/scmr/create.go +++ b/pkg/goexec/scmr/create.go @@ -18,6 +18,8 @@ type ScmrCreate struct { goexec.Cleaner goexec.Executor + IO goexec.ExecutionIO + NoDelete bool NoStart bool ServiceName string @@ -33,14 +35,11 @@ func (m *ScmrCreate) ensure() { } } -func (m *ScmrCreate) Execute(ctx context.Context, in *goexec.ExecutionInput) (err error) { +func (m *ScmrCreate) Execute(ctx context.Context, in *goexec.ExecutionIO) (err error) { m.ensure() log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName). - Str("method", MethodCreate). - Str("service", m.ServiceName). - Logger() + Str("service", m.ServiceName).Logger() svc := &service{name: m.ServiceName} @@ -65,7 +64,7 @@ func (m *ScmrCreate) Execute(ctx context.Context, in *goexec.ExecutionInput) (er } if !m.NoDelete { - m.AddCleaner(func(ctxInner context.Context) error { + m.AddCleaners(func(ctxInner context.Context) error { r, errInner := m.ctl.DeleteService(ctxInner, &svcctl.DeleteServiceRequest{ Service: svc.handle, @@ -82,7 +81,7 @@ func (m *ScmrCreate) Execute(ctx context.Context, in *goexec.ExecutionInput) (er }) } - m.AddCleaner(func(ctxInner context.Context) error { + m.AddCleaners(func(ctxInner context.Context) error { r, errInner := m.ctl.CloseService(ctxInner, &svcctl.CloseServiceRequest{ ServiceObject: svc.handle, @@ -104,8 +103,10 @@ func (m *ScmrCreate) Execute(ctx context.Context, in *goexec.ExecutionInput) (er if !m.NoStart { err = m.startService(ctx, svc) + if err != nil { log.Error().Err(err).Msg("Failed to start service") + return fmt.Errorf("start service: %w", err) } } if svc.handle == nil { @@ -113,9 +114,10 @@ func (m *ScmrCreate) Execute(ctx context.Context, in *goexec.ExecutionInput) (er if err = m.Reconnect(ctx); err != nil { return err } - svc, err = m.openService(ctx, svc.name) + if err != nil { + log.Error().Err(err).Msg("Failed to reopen service handle") return fmt.Errorf("reopen service: %w", err) } } diff --git a/pkg/goexec/scmr/delete.go b/pkg/goexec/scmr/delete.go index d327a61..87bf49b 100644 --- a/pkg/goexec/scmr/delete.go +++ b/pkg/goexec/scmr/delete.go @@ -2,10 +2,7 @@ package scmrexec import ( "context" - "fmt" "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2" - "github.com/rs/zerolog" ) const ( @@ -16,31 +13,18 @@ type ScmrDelete struct { Scmr goexec.Cleaner + IO goexec.ExecutionIO + ServiceName string } -func (m *ScmrDelete) Clean(ctx context.Context) (err error) { - - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName). - Str("method", MethodDelete). - Str("service", m.ServiceName). - Logger() +func (m *ScmrDelete) Call(ctx context.Context) (err error) { svc, err := m.openService(ctx, m.ServiceName) if err != nil { return err } - deleteResponse, err := m.ctl.DeleteService(ctx, &svcctl.DeleteServiceRequest{ - Service: svc.handle, - }) - if err != nil { - return fmt.Errorf("delete service: %w", err) - } - if deleteResponse.Return != 0 { - return fmt.Errorf("delete service returned non-zero exit code: %02x", deleteResponse.Return) - } + defer m.AddCleaners(func(ctxInner context.Context) error { return m.closeService(ctx, svc) }) - log.Info().Msg("Deleted service") - return + return m.deleteService(ctx, svc) } diff --git a/pkg/goexec/scmr/module.go b/pkg/goexec/scmr/module.go index 0edd7a3..7b16247 100644 --- a/pkg/goexec/scmr/module.go +++ b/pkg/goexec/scmr/module.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/FalconOpsLLC/goexec/internal/util" + "github.com/FalconOpsLLC/goexec/pkg/goexec" "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" "github.com/oiweiwei/go-msrpc/dcerpc" "github.com/oiweiwei/go-msrpc/midl/uuid" @@ -12,70 +13,41 @@ import ( "github.com/rs/zerolog" ) +type Scmr struct { + goexec.Cleaner + + Client *dce.Client + ctl svcctl.SvcctlClient + scm *svcctl.Handle + + hostname string +} + const ( ModuleName = "SCMR" DefaultEndpoint = "ncacn_np:[svcctl]" ScmrUuid = "367ABB81-9844-35F1-AD32-98F038001003" - - ErrorServiceRequestTimeout uint32 = 0x0000041d - ErrorServiceNotActive uint32 = 0x00000426 - - ServiceDemandStart uint32 = 0x00000003 - ServiceWin32OwnProcess uint32 = 0x00000010 - - // https://learn.microsoft.com/en-us/windows/win32/services/service-security-and-access-rights - - ServiceQueryConfig uint32 = 0x00000001 - ServiceChangeConfig uint32 = 0x00000002 - ServiceStart uint32 = 0x00000010 - ServiceStop uint32 = 0x00000020 - ServiceDelete uint32 = 0x00010000 // special permission - ServiceControlStop uint32 = 0x00000001 - ScManagerCreateService uint32 = 0x00000002 - - /* - // Windows error codes - ERROR_FILE_NOT_FOUND uint32 = 0x00000002 - ERROR_SERVICE_DOES_NOT_EXIST uint32 = 0x00000424 - - // Windows service/scm constants - SERVICE_BOOT_START uint32 = 0x00000000 - SERVICE_SYSTEM_START uint32 = 0x00000001 - SERVICE_AUTO_START uint32 = 0x00000002 - SERVICE_DISABLED uint32 = 0x00000004 - - // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/4e91ff36-ab5f-49ed-a43d-a308e72b0b3c - SERVICE_CONTINUE_PENDING uint32 = 0x00000005 - SERVICE_PAUSE_PENDING uint32 = 0x00000006 - SERVICE_PAUSED uint32 = 0x00000007 - SERVICE_RUNNING uint32 = 0x00000004 - SERVICE_START_PENDING uint32 = 0x00000002 - SERVICE_STOP_PENDING uint32 = 0x00000003 - SERVICE_STOPPED uint32 = 0x00000001 - */ ) -type Scmr struct { - client *dce.Client - ctl svcctl.SvcctlClient - scm *svcctl.Handle +func (m *Scmr) Connect(ctx context.Context) (err error) { - hostname string + if err = m.Client.Connect(ctx); err == nil { + m.AddCleaners(m.Client.Close) + } + return } -func (m *Scmr) Init(ctx context.Context, c *dce.Client) (err error) { +func (m *Scmr) Init(ctx context.Context) (err error) { log := zerolog.Ctx(ctx).With(). Str("module", ModuleName).Logger() - m.client = c - - if m.client.Dce() == nil { + if m.Client == nil || m.Client.Dce() == nil { return errors.New("DCE connection not initialized") } - m.hostname, err = c.Target.Hostname(ctx) + m.hostname, err = m.Client.Target.Hostname(ctx) if err != nil { log.Debug().Err(err).Msg("Failed to determine target hostname") } @@ -83,7 +55,7 @@ func (m *Scmr) Init(ctx context.Context, c *dce.Client) (err error) { m.hostname = util.RandomHostname() } - m.ctl, err = svcctl.NewSvcctlClient(ctx, m.client.Dce(), dcerpc.WithObjectUUID(uuid.MustParse(ScmrUuid))) + m.ctl, err = svcctl.NewSvcctlClient(ctx, m.Client.Dce(), dcerpc.WithObjectUUID(uuid.MustParse(ScmrUuid))) if err != nil { log.Error().Err(err).Msg("Failed to initialize SVCCTL client") return fmt.Errorf("create SVCCTL client: %w", err) @@ -108,10 +80,10 @@ func (m *Scmr) Init(ctx context.Context, c *dce.Client) (err error) { func (m *Scmr) Reconnect(ctx context.Context) (err error) { - if err = m.client.Reconnect(ctx); err != nil { + if err = m.Client.Reconnect(ctx); err != nil { return fmt.Errorf("reconnect: %w", err) } - if err = m.Init(ctx, m.client); err != nil { + if err = m.Init(ctx); err != nil { return fmt.Errorf("reconnect SCMR: %w", err) } return @@ -143,7 +115,8 @@ func (m *Scmr) openService(ctx context.Context, name string) (svc *service, err func (m *Scmr) startService(ctx context.Context, svc *service) error { - log := zerolog.Ctx(ctx) + log := zerolog.Ctx(ctx).With(). + Str("service", svc.name).Logger() sr, err := m.ctl.StartServiceW(ctx, &svcctl.StartServiceWRequest{Service: svc.handle}) @@ -165,3 +138,49 @@ func (m *Scmr) startService(ctx context.Context, svc *service) error { log.Info().Msg("Service started successfully") return nil } + +func (m *Scmr) deleteService(ctx context.Context, svc *service) (err error) { + + log := zerolog.Ctx(ctx).With(). + Str("service", svc.name).Logger() + + deleteResponse, err := m.ctl.DeleteService(ctx, &svcctl.DeleteServiceRequest{ + Service: svc.handle, + }) + + if err != nil { + log.Error().Err(err).Msg("Failed to delete service") + return fmt.Errorf("delete service: %w", err) + } + + if deleteResponse.Return != 0 { + log.Error().Err(err).Str("code", fmt.Sprintf("0x%02x", deleteResponse.Return)).Msg("Failed to delete service") + return fmt.Errorf("delete service returned non-zero exit code: 0x%02x", deleteResponse.Return) + } + + log.Info().Msg("Deleted service") + return +} + +func (m *Scmr) closeService(ctx context.Context, svc *service) (err error) { + + log := zerolog.Ctx(ctx).With(). + Str("service", svc.name).Logger() + + closResponse, err := m.ctl.CloseService(ctx, &svcctl.CloseServiceRequest{ + ServiceObject: svc.handle, + }) + + if err != nil { + log.Error().Err(err).Msg("Failed to close service handle") + return fmt.Errorf("close service: %w", err) + } + + if closResponse.Return != 0 { + log.Error().Err(err).Str("code", fmt.Sprintf("0x%02x", closResponse.Return)).Msg("Failed to close service handle") + return fmt.Errorf("close service returned non-zero exit code: 0x%02x", closResponse.Return) + } + + log.Info().Msg("Closed service handle") + return +} diff --git a/pkg/goexec/scmr/scmr.go b/pkg/goexec/scmr/scmr.go index 921de7e..4831ac7 100644 --- a/pkg/goexec/scmr/scmr.go +++ b/pkg/goexec/scmr/scmr.go @@ -5,6 +5,43 @@ import ( ) const ( + ErrorServiceRequestTimeout uint32 = 0x0000041d + ErrorServiceNotActive uint32 = 0x00000426 + + ServiceDemandStart uint32 = 0x00000003 + ServiceWin32OwnProcess uint32 = 0x00000010 + + // https://learn.microsoft.com/en-us/windows/win32/services/service-security-and-access-rights + + ServiceQueryConfig uint32 = 0x00000001 + ServiceChangeConfig uint32 = 0x00000002 + ServiceStart uint32 = 0x00000010 + ServiceStop uint32 = 0x00000020 + ServiceDelete uint32 = 0x00010000 // special permission + ServiceControlStop uint32 = 0x00000001 + ScManagerCreateService uint32 = 0x00000002 + + /* + // Windows error codes + ERROR_FILE_NOT_FOUND uint32 = 0x00000002 + ERROR_SERVICE_DOES_NOT_EXIST uint32 = 0x00000424 + + // Windows service/scm constants + SERVICE_BOOT_START uint32 = 0x00000000 + SERVICE_SYSTEM_START uint32 = 0x00000001 + SERVICE_AUTO_START uint32 = 0x00000002 + SERVICE_DISABLED uint32 = 0x00000004 + + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/4e91ff36-ab5f-49ed-a43d-a308e72b0b3c + SERVICE_CONTINUE_PENDING uint32 = 0x00000005 + SERVICE_PAUSE_PENDING uint32 = 0x00000006 + SERVICE_PAUSED uint32 = 0x00000007 + SERVICE_RUNNING uint32 = 0x00000004 + SERVICE_START_PENDING uint32 = 0x00000002 + SERVICE_STOP_PENDING uint32 = 0x00000003 + SERVICE_STOPPED uint32 = 0x00000001 + */ + ServiceDeleteAccess uint32 = ServiceDelete ServiceModifyAccess uint32 = ServiceQueryConfig | ServiceChangeConfig | ServiceStop | ServiceStart | ServiceDelete ServiceCreateAccess uint32 = ScManagerCreateService | ServiceStart | ServiceStop | ServiceDelete diff --git a/pkg/goexec/smb/output.go b/pkg/goexec/smb/output.go index 25768d1..f0a656d 100644 --- a/pkg/goexec/smb/output.go +++ b/pkg/goexec/smb/output.go @@ -45,7 +45,7 @@ func (o *OutputFileFetcher) GetOutput(ctx context.Context, writer io.Writer) (er if err != nil { return } - defer o.AddCleaner(o.Client.Close) + defer o.AddCleaners(o.Client.Close) err = o.Client.Mount(ctx, o.Share) if err != nil { @@ -69,10 +69,10 @@ func (o *OutputFileFetcher) GetOutput(ctx context.Context, writer io.Writer) (er return } - o.AddCleaner(func(_ context.Context) error { return reader.Close() }) + o.AddCleaners(func(_ context.Context) error { return reader.Close() }) if o.DeleteOutputFile { - o.AddCleaner(func(_ context.Context) error { + o.AddCleaners(func(_ context.Context) error { return o.Client.mount.Remove(o.relativePath) }) } diff --git a/pkg/goexec/tsch/create.go b/pkg/goexec/tsch/create.go index 43964e4..5a2e7a1 100644 --- a/pkg/goexec/tsch/create.go +++ b/pkg/goexec/tsch/create.go @@ -1,111 +1,111 @@ package tschexec import ( - "context" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/rs/zerolog" - "time" + "context" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/rs/zerolog" + "time" ) const ( - MethodCreate = "Create" + MethodCreate = "Create" ) type TschCreate struct { - Tsch - goexec.Executor - goexec.Cleaner - - IO goexec.ExecutionIO - - NoDelete bool - CallDelete bool - StartDelay time.Duration - StopDelay time.Duration - DeleteDelay time.Duration - TimeOffset time.Duration - // FEATURE: more opts + Tsch + goexec.Executor + goexec.Cleaner + + IO goexec.ExecutionIO + + NoDelete bool + CallDelete bool + StartDelay time.Duration + StopDelay time.Duration + DeleteDelay time.Duration + TimeOffset time.Duration + // FEATURE: more opts } func (m *TschCreate) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName). - Str("method", MethodCreate). - Str("task", m.TaskPath). - Logger() - - startTime := time.Now().UTC().Add(m.StartDelay) - stopTime := startTime.Add(m.StopDelay) - - trigger := taskTimeTrigger{ - StartBoundary: startTime.Format(TaskXmlDurationFormat), - Enabled: true, - } - - var deleteAfter string - - if !m.NoDelete && !m.CallDelete { - - if m.StopDelay == 0 { - m.StopDelay = time.Second // value is required, 1 second by default - } - trigger.EndBoundary = stopTime.Format(TaskXmlDurationFormat) - deleteAfter = xmlDuration(m.DeleteDelay) - } - - path, err := m.registerTask(ctx, - ®isterOptions{ - AllowStartOnDemand: true, - AllowHardTerminate: true, - Hidden: !m.NotHidden, - triggers: taskTriggers{ - TimeTriggers: []taskTimeTrigger{trigger}, - }, - DeleteAfter: deleteAfter, - }, - execIO, - ) - if err != nil { - return err - } - - if !m.NoDelete { - if m.CallDelete { - - m.AddCleaner(func(ctxInner context.Context) error { - - log.Info().Msg("Waiting for task to start...") - - select { - case <-ctxInner.Done(): - log.Warn().Msg("Task deletion cancelled") - - case <-time.After(m.StartDelay + (5 * time.Second)): // 5 second buffer - /* - for { - stat, err := m.tsch.GetLastRunInfo(ctx, &itaskschedulerservice.GetLastRunInfoRequest{ - Path: path, - }) - if err != nil { - log.Warn().Err(err).Msg("Failed to get last run info. Assuming task was executed") - - } else if stat.LastRuntime.AsTime().IsZero() { - log.Warn().Msg("Task was not yet executed. Waiting 5 additional seconds") - - time.Sleep(5 * time.Second) - continue - } - break - } - */ - } - return m.deleteTask(ctxInner, path) - }) - - } else { - log.Info().Time("when", stopTime).Msg("Task is scheduled to delete") - } - } - return + log := zerolog.Ctx(ctx).With(). + Str("module", ModuleName). + Str("method", MethodCreate). + Str("task", m.TaskPath). + Logger() + + startTime := time.Now().UTC().Add(m.StartDelay) + stopTime := startTime.Add(m.StopDelay) + + trigger := taskTimeTrigger{ + StartBoundary: startTime.Format(TaskXmlDurationFormat), + Enabled: true, + } + + var deleteAfter string + + if !m.NoDelete && !m.CallDelete { + + if m.StopDelay == 0 { + m.StopDelay = time.Second // value is required, 1 second by default + } + trigger.EndBoundary = stopTime.Format(TaskXmlDurationFormat) + deleteAfter = xmlDuration(m.DeleteDelay) + } + + path, err := m.registerTask(ctx, + ®isterOptions{ + AllowStartOnDemand: true, + AllowHardTerminate: true, + Hidden: !m.NotHidden, + triggers: taskTriggers{ + TimeTriggers: []taskTimeTrigger{trigger}, + }, + DeleteAfter: deleteAfter, + }, + execIO, + ) + if err != nil { + return err + } + + if !m.NoDelete { + if m.CallDelete { + + m.AddCleaners(func(ctxInner context.Context) error { + + log.Info().Msg("Waiting for task to start...") + + select { + case <-ctxInner.Done(): + log.Warn().Msg("Task deletion cancelled") + + case <-time.After(m.StartDelay + (5 * time.Second)): // 5 second buffer + /* + for { + stat, err := m.tsch.GetLastRunInfo(ctx, &itaskschedulerservice.GetLastRunInfoRequest{ + Path: path, + }) + if err != nil { + log.Warn().Err(err).Msg("Failed to get last run info. Assuming task was executed") + + } else if stat.LastRuntime.AsTime().IsZero() { + log.Warn().Msg("Task was not yet executed. Waiting 5 additional seconds") + + time.Sleep(5 * time.Second) + continue + } + break + } + */ + } + return m.deleteTask(ctxInner, path) + }) + + } else { + log.Info().Time("when", stopTime).Msg("Task is scheduled to delete") + } + } + return } diff --git a/pkg/goexec/tsch/demand.go b/pkg/goexec/tsch/demand.go index ed6c043..c397453 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.TaskPath). - 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.AddCleaners(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 dd569cf..a02158b 100644 --- a/pkg/goexec/tsch/module.go +++ b/pkg/goexec/tsch/module.go @@ -39,7 +39,7 @@ type registerOptions struct { func (m *Tsch) Connect(ctx context.Context) (err error) { if err = m.Client.Connect(ctx); err == nil { - m.AddCleaner(m.Client.Close) + m.AddCleaners(m.Client.Close) } return } diff --git a/pkg/goexec/wmi/module.go b/pkg/goexec/wmi/module.go index e743139..7002acd 100644 --- a/pkg/goexec/wmi/module.go +++ b/pkg/goexec/wmi/module.go @@ -33,7 +33,7 @@ type Wmi struct { func (m *Wmi) Connect(ctx context.Context) (err error) { if err = m.Client.Connect(ctx); err == nil { - m.AddCleaner(m.Client.Close) + m.AddCleaners(m.Client.Close) } return } @@ -43,7 +43,7 @@ func (m *Wmi) Init(ctx context.Context) (err error) { log := zerolog.Ctx(ctx).With(). Str("module", ModuleName).Logger() - if m.Client.Dce() == nil { + if m.Client == nil || m.Client.Dce() == nil { return errors.New("DCE connection not initialized") } |