aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO.md4
-rw-r--r--go.mod2
-rw-r--r--pkg/goexec/io.go115
-rw-r--r--pkg/goexec/scmr/change.go43
-rw-r--r--pkg/goexec/scmr/scmr.go108
5 files changed, 151 insertions, 121 deletions
diff --git a/TODO.md b/TODO.md
index 7972808..9c708f3 100644
--- a/TODO.md
+++ b/TODO.md
@@ -47,8 +47,8 @@ We wanted to make development of this project as transparent as possible, so we'
- [X] (Fixed) Proxy - EPM doesn't use the proxy dialer
- [X] (Fixed) Kerberos requests don't dial through proxy
- [X] (Fixed) Panic when closing nil log file
-- [ ] `scmr change` doesn't revert service cmdline
-- [ ] Fix SCMR `change` method so that dependencies field isn't permanently overwritten
+- [X] `scmr change` doesn't revert service cmdline
+- [X] Fix SCMR `change` method so that dependencies field isn't permanently overwritten
## Lower Priority
diff --git a/go.mod b/go.mod
index a0ba519..88b8d67 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +14,7 @@ require (
github.com/spf13/pflag v1.0.6
golang.org/x/net v0.39.0
golang.org/x/term v0.31.0
+ golang.org/x/text v0.24.0
)
require (
@@ -32,6 +33,5 @@ require (
github.com/oiweiwei/gokrb5.fork/v9 v9.0.2 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/sys v0.32.0 // indirect
- golang.org/x/text v0.24.0 // indirect
software.sslmate.com/src/go-pkcs12 v0.5.0 // indirect
)
diff --git a/pkg/goexec/io.go b/pkg/goexec/io.go
index 1d4358f..c8ad417 100644
--- a/pkg/goexec/io.go
+++ b/pkg/goexec/io.go
@@ -1,98 +1,99 @@
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 {
- StageFile io.ReadCloser
- Executable string
- ExecutablePath string
- Arguments string
- Command string
+ StageFile io.ReadCloser
+ 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() 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 (execIO *ExecutionIO) String() (str string) {
+ cmd := execIO.CommandLine()
+ // Ensure that executable paths are quoted
+ if strings.Contains(cmd[0], " ") {
+ str = fmt.Sprintf(`%q %s`, cmd[0], strings.Join(cmd[1:], " "))
+ } else {
+ str = strings.Join(cmd, " ")
+ }
+ return strings.Trim(str, " \t\n\r") // trim whitespace
}
func (i *ExecutionInput) CommandLine() (cmd []string) {
- cmd = make([]string, 2)
- cmd[1] = i.Arguments
+ cmd = make([]string, 2)
+ cmd[1] = i.Arguments
- switch {
- case i.Command != "":
- return strings.SplitN(i.Command, " ", 2)
+ switch {
+ case i.Command != "":
+ return strings.SplitN(i.Command, " ", 2)
- case i.ExecutablePath != "":
- cmd[0] = i.ExecutablePath
+ case i.ExecutablePath != "":
+ cmd[0] = i.ExecutablePath
- case i.Executable != "":
- cmd[0] = i.Executable
- }
+ 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(), " ")
}
func (i *ExecutionInput) Reader() (reader io.Reader) {
- if i.StageFile != nil {
- return i.StageFile
- }
- return strings.NewReader(i.String())
+ if i.StageFile != nil {
+ return i.StageFile
+ }
+ return strings.NewReader(i.String())
}
diff --git a/pkg/goexec/scmr/change.go b/pkg/goexec/scmr/change.go
index 3dd506e..eaf4c25 100644
--- a/pkg/goexec/scmr/change.go
+++ b/pkg/goexec/scmr/change.go
@@ -2,7 +2,6 @@ package scmrexec
import (
"context"
- "errors"
"fmt"
"github.com/FalconOpsLLC/goexec/pkg/goexec"
"github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2"
@@ -24,6 +23,7 @@ type ScmrChange struct {
IO goexec.ExecutionIO
NoStart bool
+ NoRevert bool
ServiceName string
}
@@ -107,9 +107,11 @@ func (m *ScmrChange) Execute(ctx context.Context, in *goexec.ExecutionIO) (err e
LoadOrderGroup: svc.originalConfig.LoadOrderGroup,
ServiceStartName: svc.originalConfig.ServiceStartName,
TagID: svc.originalConfig.TagID,
- //Dependencies: []byte(svc.originalConfig.Dependencies), // TODO
+ Dependencies: parseDependencies(svc.originalConfig.Dependencies),
}
+ bpn := svc.originalConfig.BinaryPathName
+
_, err = m.ctl.ChangeServiceConfigW(ctx, req)
if err != nil {
@@ -117,26 +119,35 @@ func (m *ScmrChange) Execute(ctx context.Context, in *goexec.ExecutionIO) (err e
return fmt.Errorf("change service config request: %w", err)
}
- m.AddCleaners(func(ctxInner context.Context) error {
- req.BinaryPathName = svc.originalConfig.BinaryPathName
-
- if ctxInner.Err() != nil && errors.Is(ctxInner.Err(), context.DeadlineExceeded) {
- ctxInner = context.WithoutCancel(context.Background())
- }
- _, err := m.ctl.ChangeServiceConfigW(ctxInner, req)
-
+ if !m.NoStart {
+ err = m.startService(ctx, svc)
if err != nil {
- return fmt.Errorf("restore service config: %w", err)
+ log.Error().Err(err).Msg("Failed to start service")
}
- return nil
- })
+ }
- if !m.NoStart {
+ if !m.NoRevert {
+ if svc.handle == nil {
+
+ 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)
+ }
+ }
+ req.BinaryPathName = bpn
+ req.Service = svc.handle
+ _, err := m.ctl.ChangeServiceConfigW(ctx, req)
- err = m.startService(ctx, svc)
if err != nil {
- log.Error().Err(err).Msg("Failed to start service")
+ log.Error().Err(err).Msg("Failed to restore original service configuration")
+ return fmt.Errorf("restore service config: %w", err)
}
+ log.Info().Msg("Restored original service configuration")
}
return
diff --git a/pkg/goexec/scmr/scmr.go b/pkg/goexec/scmr/scmr.go
index 696415b..3bf416c 100644
--- a/pkg/goexec/scmr/scmr.go
+++ b/pkg/goexec/scmr/scmr.go
@@ -1,55 +1,73 @@
package scmrexec
import (
- "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2"
+ "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2"
+ "golang.org/x/text/encoding/unicode"
+ "strings"
)
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 = ServiceDelete
- ServiceModifyAccess = ServiceQueryConfig | ServiceChangeConfig | ServiceStop | ServiceStart | ServiceDelete
- ServiceCreateAccess = ScManagerCreateService | ServiceStart | ServiceStop | ServiceDelete
- ServiceAllAccess = ServiceCreateAccess | ServiceModifyAccess
+ 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 = ServiceDelete
+ ServiceModifyAccess = ServiceQueryConfig | ServiceChangeConfig | ServiceStop | ServiceStart | ServiceDelete
+ ServiceCreateAccess = ScManagerCreateService | ServiceStart | ServiceStop | ServiceDelete
+ ServiceAllAccess = ServiceCreateAccess | ServiceModifyAccess
)
type service struct {
- name string
- handle *svcctl.Handle
- originalConfig *svcctl.QueryServiceConfigW
+ name string
+ handle *svcctl.Handle
+ originalConfig *svcctl.QueryServiceConfigW
+}
+
+// parseDependencies will parse the dependencies returned from a RQueryServiceConfigW
+// response (svcctl.QueryServiceConfigWResponse) into a raw byte array compatible with
+// the lpDependencies field as defined in the microsoft docs.
+// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/3ab258d6-87b0-459e-8d83-a2cdd8038b78
+func parseDependencies(deps string) (out []byte) {
+ if deps != "" && deps != "/" {
+
+ if out, err := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder().Bytes(
+ []byte(strings.ReplaceAll(deps, "/", "\x00") + "\x00"),
+ ); err == nil {
+ return out
+ }
+ }
+ return nil
}