aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorBryan McNulty <bryanmcnulty@protonmail.com>2025-04-20 05:16:35 -0500
committerBryan McNulty <bryanmcnulty@protonmail.com>2025-04-20 05:16:35 -0500
commit61578457eed9243d3be1bb120cce5995e149adec (patch)
tree143706cd5da71bc46bb5d98ca036c41ecf4a0f09 /pkg
parent82fc125fd02f236481b0fa581047979fc2845898 (diff)
downloadgoexec-61578457eed9243d3be1bb120cce5995e149adec.tar.gz
goexec-61578457eed9243d3be1bb120cce5995e149adec.zip
Implemented SCMR Clean*Method
Diffstat (limited to 'pkg')
-rw-r--r--pkg/goexec/clean.go8
-rw-r--r--pkg/goexec/dcom/dcom.go5
-rw-r--r--pkg/goexec/dcom/module.go15
-rw-r--r--pkg/goexec/exec.go13
-rw-r--r--pkg/goexec/io.go105
-rw-r--r--pkg/goexec/method.go5
-rw-r--r--pkg/goexec/module.go25
-rw-r--r--pkg/goexec/scmr/change.go12
-rw-r--r--pkg/goexec/scmr/create.go18
-rw-r--r--pkg/goexec/scmr/delete.go26
-rw-r--r--pkg/goexec/scmr/module.go121
-rw-r--r--pkg/goexec/scmr/scmr.go37
-rw-r--r--pkg/goexec/smb/output.go6
-rw-r--r--pkg/goexec/tsch/create.go194
-rw-r--r--pkg/goexec/tsch/demand.go114
-rw-r--r--pkg/goexec/tsch/module.go2
-rw-r--r--pkg/goexec/wmi/module.go4
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,
- &registerOptions{
- 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,
+ &registerOptions{
+ 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,
- &registerOptions{
- AllowStartOnDemand: true,
- AllowHardTerminate: true,
- Hidden: !m.NotHidden,
- triggers: taskTriggers{},
- },
- in,
- )
- if err != nil {
- return err
- }
+ path, err := m.registerTask(ctx,
+ &registerOptions{
+ 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")
}