Skip to content

Commit 5d0e12e

Browse files
author
Bryan McNulty
authored
Merge pull request #11 from FalconOpsLLC/dev
Merge dev branch
2 parents ef08aea + 9638104 commit 5d0e12e

5 files changed

Lines changed: 151 additions & 121 deletions

File tree

TODO.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ We wanted to make development of this project as transparent as possible, so we'
4747
- [X] (Fixed) Proxy - EPM doesn't use the proxy dialer
4848
- [X] (Fixed) Kerberos requests don't dial through proxy
4949
- [X] (Fixed) Panic when closing nil log file
50-
- [ ] `scmr change` doesn't revert service cmdline
51-
- [ ] Fix SCMR `change` method so that dependencies field isn't permanently overwritten
50+
- [X] `scmr change` doesn't revert service cmdline
51+
- [X] Fix SCMR `change` method so that dependencies field isn't permanently overwritten
5252

5353
## Lower Priority
5454

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require (
1414
github.com/spf13/pflag v1.0.6
1515
golang.org/x/net v0.39.0
1616
golang.org/x/term v0.31.0
17+
golang.org/x/text v0.24.0
1718
)
1819

1920
require (
@@ -32,6 +33,5 @@ require (
3233
github.com/oiweiwei/gokrb5.fork/v9 v9.0.2 // indirect
3334
golang.org/x/crypto v0.37.0 // indirect
3435
golang.org/x/sys v0.32.0 // indirect
35-
golang.org/x/text v0.24.0 // indirect
3636
software.sslmate.com/src/go-pkcs12 v0.5.0 // indirect
3737
)

pkg/goexec/io.go

Lines changed: 58 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,99 @@
11
package goexec
22

33
import (
4-
"context"
5-
"fmt"
6-
"io"
7-
"strings"
4+
"context"
5+
"fmt"
6+
"io"
7+
"strings"
88
)
99

1010
type OutputProvider interface {
11-
GetOutput(ctx context.Context, writer io.Writer) (err error)
12-
Clean(ctx context.Context) (err error)
11+
GetOutput(ctx context.Context, writer io.Writer) (err error)
12+
Clean(ctx context.Context) (err error)
1313
}
1414

1515
type ExecutionIO struct {
16-
Cleaner
16+
Cleaner
1717

18-
Input *ExecutionInput
19-
Output *ExecutionOutput
18+
Input *ExecutionInput
19+
Output *ExecutionOutput
2020
}
2121

2222
type ExecutionOutput struct {
23-
NoDelete bool
24-
RemotePath string
25-
Provider OutputProvider
26-
Writer io.WriteCloser
23+
NoDelete bool
24+
RemotePath string
25+
Provider OutputProvider
26+
Writer io.WriteCloser
2727
}
2828

2929
type ExecutionInput struct {
30-
StageFile io.ReadCloser
31-
Executable string
32-
ExecutablePath string
33-
Arguments string
34-
Command string
30+
StageFile io.ReadCloser
31+
Executable string
32+
ExecutablePath string
33+
Arguments string
34+
Command string
3535
}
3636

3737
func (execIO *ExecutionIO) GetOutput(ctx context.Context) (err error) {
38-
if execIO.Output.Provider != nil {
39-
return execIO.Output.Provider.GetOutput(ctx, execIO.Output.Writer)
40-
}
41-
return nil
38+
if execIO.Output.Provider != nil {
39+
return execIO.Output.Provider.GetOutput(ctx, execIO.Output.Writer)
40+
}
41+
return nil
4242
}
4343

4444
func (execIO *ExecutionIO) Clean(ctx context.Context) (err error) {
45-
if execIO.Output.Provider != nil {
46-
return execIO.Output.Provider.Clean(ctx)
47-
}
48-
return nil
45+
if execIO.Output.Provider != nil {
46+
return execIO.Output.Provider.Clean(ctx)
47+
}
48+
return nil
4949
}
5050

5151
func (execIO *ExecutionIO) CommandLine() (cmd []string) {
52-
if execIO.Output.Provider != nil && execIO.Output.RemotePath != "" {
53-
return []string{
54-
`C:\Windows\System32\cmd.exe`,
55-
fmt.Sprintf(`/C %s > %s 2>&1`, execIO.Input.String(), execIO.Output.RemotePath),
56-
}
57-
}
58-
return execIO.Input.CommandLine()
52+
if execIO.Output.Provider != nil && execIO.Output.RemotePath != "" {
53+
return []string{
54+
`C:\Windows\System32\cmd.exe`,
55+
fmt.Sprintf(`/C %s > %s 2>&1`, execIO.Input.String(), execIO.Output.RemotePath),
56+
}
57+
}
58+
return execIO.Input.CommandLine()
5959
}
6060

61-
func (execIO *ExecutionIO) String() string {
62-
cmd := execIO.CommandLine()
63-
64-
// Ensure that executable paths are quoted
65-
if strings.Contains(cmd[0], " ") {
66-
return fmt.Sprintf(`%q %s`, cmd[0], strings.Join(cmd[1:], " "))
67-
}
68-
return strings.Join(cmd, " ")
61+
func (execIO *ExecutionIO) String() (str string) {
62+
cmd := execIO.CommandLine()
63+
// Ensure that executable paths are quoted
64+
if strings.Contains(cmd[0], " ") {
65+
str = fmt.Sprintf(`%q %s`, cmd[0], strings.Join(cmd[1:], " "))
66+
} else {
67+
str = strings.Join(cmd, " ")
68+
}
69+
return strings.Trim(str, " \t\n\r") // trim whitespace
6970
}
7071

7172
func (i *ExecutionInput) CommandLine() (cmd []string) {
72-
cmd = make([]string, 2)
73-
cmd[1] = i.Arguments
73+
cmd = make([]string, 2)
74+
cmd[1] = i.Arguments
7475

75-
switch {
76-
case i.Command != "":
77-
return strings.SplitN(i.Command, " ", 2)
76+
switch {
77+
case i.Command != "":
78+
return strings.SplitN(i.Command, " ", 2)
7879

79-
case i.ExecutablePath != "":
80-
cmd[0] = i.ExecutablePath
80+
case i.ExecutablePath != "":
81+
cmd[0] = i.ExecutablePath
8182

82-
case i.Executable != "":
83-
cmd[0] = i.Executable
84-
}
83+
case i.Executable != "":
84+
cmd[0] = i.Executable
85+
}
8586

86-
return cmd
87+
return cmd
8788
}
8889

8990
func (i *ExecutionInput) String() string {
90-
return strings.Join(i.CommandLine(), " ")
91+
return strings.Join(i.CommandLine(), " ")
9192
}
9293

9394
func (i *ExecutionInput) Reader() (reader io.Reader) {
94-
if i.StageFile != nil {
95-
return i.StageFile
96-
}
97-
return strings.NewReader(i.String())
95+
if i.StageFile != nil {
96+
return i.StageFile
97+
}
98+
return strings.NewReader(i.String())
9899
}

pkg/goexec/scmr/change.go

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package scmrexec
22

33
import (
44
"context"
5-
"errors"
65
"fmt"
76
"github.com/FalconOpsLLC/goexec/pkg/goexec"
87
"github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2"
@@ -24,6 +23,7 @@ type ScmrChange struct {
2423
IO goexec.ExecutionIO
2524

2625
NoStart bool
26+
NoRevert bool
2727
ServiceName string
2828
}
2929

@@ -107,36 +107,47 @@ func (m *ScmrChange) Execute(ctx context.Context, in *goexec.ExecutionIO) (err e
107107
LoadOrderGroup: svc.originalConfig.LoadOrderGroup,
108108
ServiceStartName: svc.originalConfig.ServiceStartName,
109109
TagID: svc.originalConfig.TagID,
110-
//Dependencies: []byte(svc.originalConfig.Dependencies), // TODO
110+
Dependencies: parseDependencies(svc.originalConfig.Dependencies),
111111
}
112112

113+
bpn := svc.originalConfig.BinaryPathName
114+
113115
_, err = m.ctl.ChangeServiceConfigW(ctx, req)
114116

115117
if err != nil {
116118
log.Error().Err(err).Msg("Failed to request service configuration change")
117119
return fmt.Errorf("change service config request: %w", err)
118120
}
119121

120-
m.AddCleaners(func(ctxInner context.Context) error {
121-
req.BinaryPathName = svc.originalConfig.BinaryPathName
122-
123-
if ctxInner.Err() != nil && errors.Is(ctxInner.Err(), context.DeadlineExceeded) {
124-
ctxInner = context.WithoutCancel(context.Background())
125-
}
126-
_, err := m.ctl.ChangeServiceConfigW(ctxInner, req)
127-
122+
if !m.NoStart {
123+
err = m.startService(ctx, svc)
128124
if err != nil {
129-
return fmt.Errorf("restore service config: %w", err)
125+
log.Error().Err(err).Msg("Failed to start service")
130126
}
131-
return nil
132-
})
127+
}
133128

134-
if !m.NoStart {
129+
if !m.NoRevert {
130+
if svc.handle == nil {
131+
132+
if err = m.Reconnect(ctx); err != nil {
133+
return err
134+
}
135+
svc, err = m.openService(ctx, svc.name)
136+
137+
if err != nil {
138+
log.Error().Err(err).Msg("Failed to reopen service handle")
139+
return fmt.Errorf("reopen service: %w", err)
140+
}
141+
}
142+
req.BinaryPathName = bpn
143+
req.Service = svc.handle
144+
_, err := m.ctl.ChangeServiceConfigW(ctx, req)
135145

136-
err = m.startService(ctx, svc)
137146
if err != nil {
138-
log.Error().Err(err).Msg("Failed to start service")
147+
log.Error().Err(err).Msg("Failed to restore original service configuration")
148+
return fmt.Errorf("restore service config: %w", err)
139149
}
150+
log.Info().Msg("Restored original service configuration")
140151
}
141152

142153
return

pkg/goexec/scmr/scmr.go

Lines changed: 63 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,73 @@
11
package scmrexec
22

33
import (
4-
"github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2"
4+
"github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2"
5+
"golang.org/x/text/encoding/unicode"
6+
"strings"
57
)
68

79
const (
8-
ErrorServiceRequestTimeout uint32 = 0x0000041d
9-
ErrorServiceNotActive uint32 = 0x00000426
10-
11-
ServiceDemandStart uint32 = 0x00000003
12-
ServiceWin32OwnProcess uint32 = 0x00000010
13-
14-
// https://learn.microsoft.com/en-us/windows/win32/services/service-security-and-access-rights
15-
16-
ServiceQueryConfig uint32 = 0x00000001
17-
ServiceChangeConfig uint32 = 0x00000002
18-
ServiceStart uint32 = 0x00000010
19-
ServiceStop uint32 = 0x00000020
20-
ServiceDelete uint32 = 0x00010000 // special permission
21-
ServiceControlStop uint32 = 0x00000001
22-
ScManagerCreateService uint32 = 0x00000002
23-
24-
/*
25-
// Windows error codes
26-
ERROR_FILE_NOT_FOUND uint32 = 0x00000002
27-
ERROR_SERVICE_DOES_NOT_EXIST uint32 = 0x00000424
28-
29-
// Windows service/scm constants
30-
SERVICE_BOOT_START uint32 = 0x00000000
31-
SERVICE_SYSTEM_START uint32 = 0x00000001
32-
SERVICE_AUTO_START uint32 = 0x00000002
33-
SERVICE_DISABLED uint32 = 0x00000004
34-
35-
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/4e91ff36-ab5f-49ed-a43d-a308e72b0b3c
36-
SERVICE_CONTINUE_PENDING uint32 = 0x00000005
37-
SERVICE_PAUSE_PENDING uint32 = 0x00000006
38-
SERVICE_PAUSED uint32 = 0x00000007
39-
SERVICE_RUNNING uint32 = 0x00000004
40-
SERVICE_START_PENDING uint32 = 0x00000002
41-
SERVICE_STOP_PENDING uint32 = 0x00000003
42-
SERVICE_STOPPED uint32 = 0x00000001
43-
*/
44-
45-
ServiceDeleteAccess = ServiceDelete
46-
ServiceModifyAccess = ServiceQueryConfig | ServiceChangeConfig | ServiceStop | ServiceStart | ServiceDelete
47-
ServiceCreateAccess = ScManagerCreateService | ServiceStart | ServiceStop | ServiceDelete
48-
ServiceAllAccess = ServiceCreateAccess | ServiceModifyAccess
10+
ErrorServiceRequestTimeout uint32 = 0x0000041d
11+
ErrorServiceNotActive uint32 = 0x00000426
12+
13+
ServiceDemandStart uint32 = 0x00000003
14+
ServiceWin32OwnProcess uint32 = 0x00000010
15+
16+
// https://learn.microsoft.com/en-us/windows/win32/services/service-security-and-access-rights
17+
18+
ServiceQueryConfig uint32 = 0x00000001
19+
ServiceChangeConfig uint32 = 0x00000002
20+
ServiceStart uint32 = 0x00000010
21+
ServiceStop uint32 = 0x00000020
22+
ServiceDelete uint32 = 0x00010000 // special permission
23+
ServiceControlStop uint32 = 0x00000001
24+
ScManagerCreateService uint32 = 0x00000002
25+
26+
/*
27+
// Windows error codes
28+
ERROR_FILE_NOT_FOUND uint32 = 0x00000002
29+
ERROR_SERVICE_DOES_NOT_EXIST uint32 = 0x00000424
30+
31+
// Windows service/scm constants
32+
SERVICE_BOOT_START uint32 = 0x00000000
33+
SERVICE_SYSTEM_START uint32 = 0x00000001
34+
SERVICE_AUTO_START uint32 = 0x00000002
35+
SERVICE_DISABLED uint32 = 0x00000004
36+
37+
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/4e91ff36-ab5f-49ed-a43d-a308e72b0b3c
38+
SERVICE_CONTINUE_PENDING uint32 = 0x00000005
39+
SERVICE_PAUSE_PENDING uint32 = 0x00000006
40+
SERVICE_PAUSED uint32 = 0x00000007
41+
SERVICE_RUNNING uint32 = 0x00000004
42+
SERVICE_START_PENDING uint32 = 0x00000002
43+
SERVICE_STOP_PENDING uint32 = 0x00000003
44+
SERVICE_STOPPED uint32 = 0x00000001
45+
*/
46+
47+
ServiceDeleteAccess = ServiceDelete
48+
ServiceModifyAccess = ServiceQueryConfig | ServiceChangeConfig | ServiceStop | ServiceStart | ServiceDelete
49+
ServiceCreateAccess = ScManagerCreateService | ServiceStart | ServiceStop | ServiceDelete
50+
ServiceAllAccess = ServiceCreateAccess | ServiceModifyAccess
4951
)
5052

5153
type service struct {
52-
name string
53-
handle *svcctl.Handle
54-
originalConfig *svcctl.QueryServiceConfigW
54+
name string
55+
handle *svcctl.Handle
56+
originalConfig *svcctl.QueryServiceConfigW
57+
}
58+
59+
// parseDependencies will parse the dependencies returned from a RQueryServiceConfigW
60+
// response (svcctl.QueryServiceConfigWResponse) into a raw byte array compatible with
61+
// the lpDependencies field as defined in the microsoft docs.
62+
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/3ab258d6-87b0-459e-8d83-a2cdd8038b78
63+
func parseDependencies(deps string) (out []byte) {
64+
if deps != "" && deps != "/" {
65+
66+
if out, err := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder().Bytes(
67+
[]byte(strings.ReplaceAll(deps, "/", "\x00") + "\x00"),
68+
); err == nil {
69+
return out
70+
}
71+
}
72+
return nil
5573
}

0 commit comments

Comments
 (0)