aboutsummaryrefslogtreecommitdiff
path: root/pkg/exec/scmr/exec.go
blob: c2702a398340066ab91ab599db0fc99c6af5d6b1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package scmrexec

import (
	"context"
	"errors"
	"fmt"
	"github.com/FalconOpsLLC/goexec/pkg/client/dcerpc"
	"github.com/FalconOpsLLC/goexec/pkg/exec"
	"github.com/FalconOpsLLC/goexec/pkg/windows"
	"github.com/bryanmcnulty/adauth"
	"github.com/rs/zerolog"
)

const (
	MethodCreate string = "create"
	MethodModify string = "modify"

	ServiceModifyAccess uint32 = windows.SERVICE_QUERY_CONFIG | windows.SERVICE_CHANGE_CONFIG | windows.SERVICE_STOP | windows.SERVICE_START | windows.SERVICE_DELETE
	ServiceCreateAccess uint32 = windows.SC_MANAGER_CREATE_SERVICE | windows.SERVICE_START | windows.SERVICE_STOP | windows.SERVICE_DELETE
	ServiceAllAccess    uint32 = ServiceCreateAccess | ServiceModifyAccess
)

func (mod *Module) createClients(ctx context.Context) (cleanup func(cCtx context.Context), err error) {

	cleanup = func(context.Context) {
		if mod.dce != nil {
			mod.log.Debug().Msg("Cleaning up clients")
			if err := mod.dce.Close(ctx); err != nil {
				mod.log.Error().Err(err).Msg("Failed to destroy DCE connection")
			}
		}
	}
	cleanup(ctx)
	mod.dce = dcerpc.NewDCEClient(ctx, false, &dcerpc.SmbConfig{Port: 445})
	cleanup = func(context.Context) {}

	if err = mod.dce.Connect(ctx, mod.creds, mod.target); err != nil {
		return nil, fmt.Errorf("connection to DCERPC failed: %w", err)
	}
	mod.ctl, err = mod.dce.OpenSvcctl(ctx)
	return
}

func (mod *Module) Exec(ctx context.Context, creds *adauth.Credential, target *adauth.Target, ecfg *exec.ExecutionConfig) (err error) {

	vctx := context.WithoutCancel(ctx)
	mod.log = zerolog.Ctx(ctx).With().
		Str("module", "scmr").
		Str("method", ecfg.ExecutionMethod).Logger()
	mod.creds = creds
	mod.target = target

	if ecfg.ExecutionMethod == MethodCreate {
		if cfg, ok := ecfg.ExecutionMethodConfig.(MethodCreateConfig); !ok || cfg.ServiceName == "" {
			return errors.New("invalid configuration")
		} else {
			if cleanup, err := mod.createClients(ctx); err != nil {
				return fmt.Errorf("failed to create client: %w", err)
			} else {
				mod.log.Debug().Msg("Created clients")
				defer cleanup(ctx)
			}
			svc := &service{
				createConfig: &cfg,
				name:         cfg.ServiceName,
			}
			scm, code, err := mod.openSCM(ctx)
			if err != nil {
				return fmt.Errorf("failed to open SCM with code %d: %w", code, err)
			}
			mod.log.Debug().Msg("Opened handle to SCM")
			code, err = mod.createService(ctx, scm, svc, ecfg)
			if err != nil {
				return fmt.Errorf("failed to create service with code %d: %w", code, err)
			}
			mod.log.Info().Str("service", svc.name).Msg("Service created")
			// From here on out, make sure the service is properly deleted, even if the connection drops or something fails.
			if !cfg.NoDelete {
				defer func() {
					// TODO: stop service?
					if code, err = mod.deleteService(ctx, scm, svc); err != nil {
						mod.log.Error().Err(err).Msg("Failed to delete service") // TODO
					}
					mod.log.Info().Str("service", svc.name).Msg("Service deleted successfully")
				}()
			}
			if code, err = mod.startService(ctx, scm, svc); err != nil {
				if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
					// In case of timeout or cancel, try to reestablish a connection to restore the service
					mod.log.Info().Msg("Service start timeout/cancelled. Execution likely successful")
					mod.log.Info().Msg("Reconnecting for cleanup procedure")
					ctx = vctx

					if _, err = mod.createClients(ctx); err != nil {
						mod.log.Error().Err(err).Msg("Reconnect failed")

					} else if scm, code, err = mod.openSCM(ctx); err != nil {
						mod.log.Error().Err(err).Msg("Failed to reopen SCM")

					} else if svc.handle, code, err = mod.openService(ctx, scm, svc.name); err != nil {
						mod.log.Error().Str("service", svc.name).Err(err).Msg("Failed to reopen service handle")

					} else {
						mod.log.Debug().Str("service", svc.name).Msg("Reconnection successful")
					}
				} else {
					mod.log.Error().Err(err).Msg("Failed to start service")
				}
			} else {
				mod.log.Info().Str("service", svc.name).Msg("Execution successful")
			}
		}
	} else if ecfg.ExecutionMethod == MethodModify {
		// Use service modification method
		if cfg, ok := ecfg.ExecutionMethodConfig.(MethodModifyConfig); !ok || cfg.ServiceName == "" {
			return errors.New("invalid configuration")

		} else {
			// Ensure that a command (executable full path + args) is supplied
			cmd := ecfg.GetRawCommand()
			if cmd == "" {
				return errors.New("no command provided")
			}

			// Initialize protocol clients
			if cleanup, err := mod.createClients(ctx); err != nil {
				return fmt.Errorf("failed to create client: %w", err)
			} else {
				mod.log.Debug().Msg("Created clients")
				defer cleanup(ctx)
			}
			svc := &service{modifyConfig: &cfg, name: cfg.ServiceName}

			// Open SCM handle
			scm, code, err := mod.openSCM(ctx)
			if err != nil {
				return fmt.Errorf("failed to create service with code %d: %w", code, err)
			}
			mod.log.Debug().Msg("Opened handle to SCM")

			// Open service handle
			if svc.handle, code, err = mod.openService(ctx, scm, svc.name); err != nil {
				return fmt.Errorf("failed to open service with code %d: %w", code, err)
			}
			mod.log.Debug().Str("service", svc.name).Msg("Opened service")

			// Stop service before editing
			if !cfg.NoStart {
				if code, err = mod.stopService(ctx, scm, svc); err != nil {
					mod.log.Warn().Err(err).Msg("Failed to stop existing service")
				} else if code == windows.ERROR_SERVICE_NOT_ACTIVE {
					mod.log.Debug().Str("service", svc.name).Msg("Service is not running")
				} else {
					mod.log.Info().Str("service", svc.name).Msg("Stopped existing service")
					defer func() {
						if code, err = mod.startService(ctx, scm, svc); err != nil {
							mod.log.Error().Err(err).Msg("Failed to restore service state to running")
						}
					}()
				}
			}
			if code, err = mod.queryServiceConfig(ctx, svc); err != nil {
				return fmt.Errorf("failed to query service configuration with code %d: %w", code, err)
			}
			mod.log.Debug().
				Str("service", svc.name).
				Str("command", svc.svcConfig.BinaryPathName).Msg("Fetched existing service configuration")

			// Change service configuration
			if code, err = mod.changeServiceConfigBinary(ctx, svc, cmd); err != nil {
				return fmt.Errorf("failed to edit service configuration with code %d: %w", code, err)
			}
			defer func() {
				// Revert configuration
				if code, err = mod.changeServiceConfigBinary(ctx, svc, svc.svcConfig.BinaryPathName); err != nil {
					mod.log.Error().Err(err).Msg("Failed to restore service configuration")
				} else {
					mod.log.Info().Str("service", svc.name).Msg("Restored service configuration")
				}
			}()
			mod.log.Info().
				Str("service", svc.name).
				Str("command", cmd).Msg("Changed service configuration")

			// Start service
			if !cfg.NoStart {
				if code, err = mod.startService(ctx, scm, svc); err != nil {
					if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
						// In case of timeout or cancel, try to reestablish a connection to restore the service
						mod.log.Info().Msg("Service start timeout/cancelled. Execution likely successful")
						mod.log.Info().Msg("Reconnecting for cleanup procedure")
						ctx = vctx

						if _, err = mod.createClients(ctx); err != nil {
							mod.log.Error().Err(err).Msg("Reconnect failed")

						} else if scm, code, err = mod.openSCM(ctx); err != nil {
							mod.log.Error().Err(err).Msg("Failed to reopen SCM")

						} else if svc.handle, code, err = mod.openService(ctx, scm, svc.name); err != nil {
							mod.log.Error().Str("service", svc.name).Err(err).Msg("Failed to reopen service handle")

						} else {
							mod.log.Debug().Str("service", svc.name).Msg("Reconnection successful")
						}
					} else {
						mod.log.Error().Err(err).Msg("Failed to start service")
					}
				} else {
					mod.log.Info().Str("service", svc.name).Msg("Started service")
				}
				defer func() {
					// Stop service
					if code, err = mod.stopService(ctx, scm, svc); err != nil {
						mod.log.Error().Err(err).Msg("Failed to stop service")
					} else {
						mod.log.Info().Str("service", svc.name).Msg("Stopped service")
					}
				}()
			}
		}
	} else {
		return fmt.Errorf("invalid method: %s", ecfg.ExecutionMethod)
	}
	return err
}