aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan McNulty <bryanmcnulty@protonmail.com>2025-03-04 03:05:53 -0600
committerBryan McNulty <bryanmcnulty@protonmail.com>2025-03-04 03:05:53 -0600
commita5c860b8ab24c198b7390fbde90044754e35c1c5 (patch)
tree3118b27b5c76cab44bb61d83df750a9f00b4be00
parent5a3bf6315aab33e6488734a579977836042b4aa1 (diff)
parentf98989334bbe227bbe9dc4c84a2d0e34aa2fb86f (diff)
downloadgoexec-a5c860b8ab24c198b7390fbde90044754e35c1c5.tar.gz
goexec-a5c860b8ab24c198b7390fbde90044754e35c1c5.zip
Simple fixes
-rw-r--r--TODO.md8
-rw-r--r--cmd/root.go26
-rw-r--r--cmd/scmr.go49
-rw-r--r--cmd/tsch.go158
-rw-r--r--go.mod10
-rw-r--r--go.sum20
-rw-r--r--internal/client/dcerpc/client.go (renamed from pkg/client/dcerpc/client.go)2
-rw-r--r--internal/client/dcerpc/dcerpc.go (renamed from pkg/client/dcerpc/dcerpc.go)8
-rw-r--r--internal/client/dcerpc/smb.go (renamed from pkg/client/dcerpc/smb.go)0
-rw-r--r--internal/exec/exec.go (renamed from pkg/exec/exec.go)12
-rw-r--r--internal/exec/scmr/exec.go226
-rw-r--r--internal/exec/scmr/module.go (renamed from pkg/exec/scmr/module.go)6
-rw-r--r--internal/exec/scmr/scmr.go (renamed from pkg/exec/scmr/scmr.go)72
-rw-r--r--internal/exec/tsch/exec.go289
-rw-r--r--internal/exec/tsch/module.go48
-rw-r--r--internal/exec/tsch/tsch.go102
-rw-r--r--internal/windows/const.go (renamed from pkg/windows/const.go)0
-rw-r--r--pkg/exec/scmr/exec.go224
18 files changed, 943 insertions, 317 deletions
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..e20be1b
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,8 @@
+# TODO
+
+## High Priority
+- [ ] Add dcom module
+
+## Lower Priority
+- [ ] Add command to tsch - update task if it already exists. See https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/849c131a-64e4-46ef-b015-9d4c599c5167 (`flags` argument)
+- [ ] Add delete command to scmr - should look similar to `tsch delete` \ No newline at end of file
diff --git a/cmd/root.go b/cmd/root.go
index 00563c6..473f1ad 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -3,7 +3,7 @@ package cmd
import (
"context"
"fmt"
- "github.com/bryanmcnulty/adauth"
+ "github.com/RedTeamPentesting/adauth"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"os"
@@ -16,11 +16,28 @@ var (
ctx context.Context
authOpts *adauth.Options
- debug, trace bool
+ debug bool
command string
+ executable string
executablePath string
executableArgs string
+ needsTarget = func(cmd *cobra.Command, args []string) (err error) {
+ if len(args) != 1 {
+ return fmt.Errorf("command require exactly one positional argument: [target]")
+ }
+ if creds, target, err = authOpts.WithTarget(ctx, "cifs", args[0]); err != nil {
+ return fmt.Errorf("failed to parse target: %w", err)
+ }
+ if creds == nil {
+ return fmt.Errorf("no credentials supplied")
+ }
+ if target == nil {
+ return fmt.Errorf("no target supplied")
+ }
+ return
+ }
+
rootCmd = &cobra.Command{
Use: "goexec",
PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
@@ -42,13 +59,16 @@ func init() {
rootCmd.InitDefaultVersionFlag()
rootCmd.InitDefaultHelpCmd()
- rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging")
+ rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging")
authOpts = &adauth.Options{Debug: log.Debug().Msgf}
authOpts.RegisterFlags(rootCmd.PersistentFlags())
scmrCmdInit()
rootCmd.AddCommand(scmrCmd)
+
+ tschCmdInit()
+ rootCmd.AddCommand(tschCmd)
}
func Execute() {
diff --git a/cmd/scmr.go b/cmd/scmr.go
index 150320c..8d453a5 100644
--- a/cmd/scmr.go
+++ b/cmd/scmr.go
@@ -1,37 +1,36 @@
package cmd
import (
- "errors"
"fmt"
- "github.com/bryanmcnulty/adauth"
+ "github.com/FalconOpsLLC/goexec/internal/exec"
+ scmrexec2 "github.com/FalconOpsLLC/goexec/internal/exec/scmr"
+ "github.com/FalconOpsLLC/goexec/internal/windows"
+ "github.com/RedTeamPentesting/adauth"
"github.com/spf13/cobra"
- "github.com/FalconOpsLLC/goexec/pkg/exec"
- scmrexec "github.com/FalconOpsLLC/goexec/pkg/exec/scmr"
- "github.com/FalconOpsLLC/goexec/pkg/windows"
+ scmrexec "github.com/FalconOpsLLC/goexec/internal/exec/scmr"
)
func scmrCmdInit() {
- scmrCmd.PersistentFlags().StringVarP(&executablePath, "executable-path", "e", "", "Full path to remote Windows executable")
+ scmrCmd.PersistentFlags().StringVarP(&executablePath, "executable-path", "f", "", "Full path to remote Windows executable")
scmrCmd.PersistentFlags().StringVarP(&executableArgs, "args", "a", "", "Arguments to pass to executable")
- scmrCmd.PersistentFlags().StringVarP(&scmrName, "service", "s", "", "Name of service to create or modify")
- scmrCmd.PersistentFlags().BoolVar(&scmrNoStart, "no-start", false, "Don't start service after execution")
+ scmrCmd.PersistentFlags().StringVarP(&scmrName, "service-name", "s", "", "Name of service to create or modify")
scmrCmd.MarkPersistentFlagRequired("executable-path")
- scmrCmd.MarkPersistentFlagRequired("service")
+ scmrCmd.MarkPersistentFlagRequired("service-name")
scmrCmd.AddCommand(scmrChangeCmd)
- scmrChangeCmdInit()
- scmrCmd.AddCommand(scmrCreateCmd)
scmrCreateCmdInit()
+ scmrCmd.AddCommand(scmrCreateCmd)
+ scmrChangeCmdInit()
}
func scmrChangeCmdInit() {
- // no unique flags
+ scmrChangeCmd.Flags().StringVarP(&scmrDisplayName, "display-name", "n", "", "Display name of service to create")
+ scmrChangeCmd.Flags().BoolVar(&scmrNoStart, "no-start", false, "Don't start service")
}
func scmrCreateCmdInit() {
- scmrCreateCmd.Flags().StringVarP(&scmrDisplayName, "display-name", "n", "", "Display name new service")
scmrCreateCmd.Flags().BoolVar(&scmrNoDelete, "no-delete", false, "Don't delete service after execution")
}
@@ -59,17 +58,7 @@ var (
scmrCmd = &cobra.Command{
Use: "scmr",
Short: "Establish execution via SCMR",
- Args: func(cmd *cobra.Command, args []string) error {
- if len(args) != 1 {
- return errors.New(`command not set. Choose from (change, create)`)
- }
- return nil
- },
- Run: func(cmd *cobra.Command, args []string) {
- if err := cmd.Help(); err != nil {
- panic(err)
- }
- },
+ Args: cobra.NoArgs,
}
scmrCreateCmd = &cobra.Command{
Use: "create [target]",
@@ -83,13 +72,13 @@ var (
scmrDisplayName = scmrName
log.Warn().Msg("No display name specified, using service name as display name")
}
- executor := scmrexec.Executor{}
+ executor := scmrexec.Module{}
execCfg := &exec.ExecutionConfig{
ExecutablePath: executablePath,
ExecutableArgs: executableArgs,
- ExecutionMethod: scmrexec.MethodCreate,
+ ExecutionMethod: scmrexec2.MethodCreate,
- ExecutionMethodConfig: scmrexec.MethodCreateConfig{
+ ExecutionMethodConfig: scmrexec2.MethodCreateConfig{
NoDelete: scmrNoDelete,
ServiceName: scmrName,
DisplayName: scmrDisplayName,
@@ -108,13 +97,13 @@ var (
Short: "Change an existing Windows service to gain execution",
Args: scmrArgs,
Run: func(cmd *cobra.Command, args []string) {
- executor := scmrexec.Executor{}
+ executor := scmrexec.Module{}
execCfg := &exec.ExecutionConfig{
ExecutablePath: executablePath,
ExecutableArgs: executableArgs,
- ExecutionMethod: scmrexec.MethodModify,
+ ExecutionMethod: scmrexec2.MethodModify,
- ExecutionMethodConfig: scmrexec.MethodModifyConfig{
+ ExecutionMethodConfig: scmrexec2.MethodModifyConfig{
NoStart: scmrNoStart,
ServiceName: scmrName,
},
diff --git a/cmd/tsch.go b/cmd/tsch.go
new file mode 100644
index 0000000..05c55cf
--- /dev/null
+++ b/cmd/tsch.go
@@ -0,0 +1,158 @@
+package cmd
+
+import (
+ "github.com/FalconOpsLLC/goexec/internal/exec"
+ "github.com/FalconOpsLLC/goexec/internal/exec/tsch"
+ "github.com/spf13/cobra"
+ "time"
+)
+
+func tschCmdInit() {
+ tschDeleteCmdInit()
+ tschCmd.AddCommand(tschDeleteCmd)
+
+ tschRegisterCmdInit()
+ tschCmd.AddCommand(tschRegisterCmd)
+
+ tschDemandCmdInit()
+ tschCmd.AddCommand(tschDemandCmd)
+}
+
+func tschDeleteCmdInit() {
+ tschDeleteCmd.Flags().StringVarP(&tschTaskPath, "path", "t", "", "Scheduled task path")
+ tschDeleteCmd.MarkFlagRequired("path")
+}
+
+func tschDemandCmdInit() {
+ tschDemandCmd.Flags().StringVarP(&executable, "executable", "e", "", "Remote Windows executable to invoke")
+ tschDemandCmd.Flags().StringVarP(&executableArgs, "args", "a", "", "Arguments to pass to executable")
+ tschDemandCmd.Flags().StringVarP(&tschName, "name", "n", "", "Target task name")
+ tschDemandCmd.Flags().BoolVar(&tschNoDelete, "no-delete", false, "Don't delete task after execution")
+ tschDemandCmd.MarkFlagRequired("executable")
+}
+
+func tschRegisterCmdInit() {
+ tschRegisterCmd.Flags().StringVarP(&executable, "executable", "e", "", "Remote Windows executable to invoke")
+ tschRegisterCmd.Flags().StringVarP(&executableArgs, "args", "a", "", "Arguments to pass to executable")
+ tschRegisterCmd.Flags().StringVarP(&tschName, "name", "n", "", "Target task name")
+ tschRegisterCmd.Flags().DurationVar(&tschStopDelay, "delay-stop", time.Duration(5*time.Second), "Delay between task execution and termination. This will not stop the process spawned by the task")
+ tschRegisterCmd.Flags().DurationVarP(&tschDelay, "delay-start", "d", time.Duration(5*time.Second), "Delay between task registration and execution")
+ tschRegisterCmd.Flags().DurationVarP(&tschDeleteDelay, "delay-delete", "D", time.Duration(0*time.Second), "Delay between task termination and deletion")
+ tschRegisterCmd.Flags().BoolVar(&tschNoDelete, "no-delete", false, "Don't delete task after execution")
+ tschRegisterCmd.Flags().BoolVar(&tschCallDelete, "call-delete", false, "Directly call SchRpcDelete to delete task")
+
+ tschRegisterCmd.MarkFlagsMutuallyExclusive("no-delete", "delay-delete")
+ tschRegisterCmd.MarkFlagsMutuallyExclusive("no-delete", "call-delete")
+ tschRegisterCmd.MarkFlagsMutuallyExclusive("delay-delete", "call-delete")
+ tschRegisterCmd.MarkFlagRequired("executable")
+}
+
+var (
+ tschNoDelete bool
+ tschCallDelete bool
+ tschDeleteDelay time.Duration
+ tschStopDelay time.Duration
+ tschDelay time.Duration
+ tschName string
+ tschTaskPath string
+
+ tschCmd = &cobra.Command{
+ Use: "tsch",
+ Short: "Establish execution via TSCH (ITaskSchedulerService)",
+ Args: cobra.NoArgs,
+ }
+ tschRegisterCmd = &cobra.Command{
+ Use: "register [target]",
+ Short: "Register a remote scheduled task with an automatic start time",
+ Long: `Description:
+ The register method calls SchRpcRegisterTask to register a scheduled task
+ with an automatic start time.This method avoids directly calling SchRpcRun,
+ and can even avoid calling SchRpcDelete by populating the DeleteExpiredTaskAfter
+ Setting.
+
+References:
+ SchRpcRegisterTask - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/849c131a-64e4-46ef-b015-9d4c599c5167
+ SchRpcRun - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/77f2250d-500a-40ee-be18-c82f7079c4f0
+ SchRpcDelete - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/360bb9b1-dd2a-4b36-83ee-21f12cb97cff
+ DeleteExpiredTaskAfter - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/6bfde6fe-440e-4ddd-b4d6-c8fc0bc06fae
+`,
+ Args: needsTarget,
+ Run: func(cmd *cobra.Command, args []string) {
+ if tschNoDelete {
+ log.Warn().Msg("Task will not be deleted after execution")
+ }
+ module := tschexec.Module{}
+ execCfg := &exec.ExecutionConfig{
+ ExecutableName: executable,
+ ExecutableArgs: executableArgs,
+ ExecutionMethod: tschexec.MethodRegister,
+
+ ExecutionMethodConfig: tschexec.MethodRegisterConfig{
+ NoDelete: tschNoDelete,
+ CallDelete: tschCallDelete,
+ StartDelay: tschDelay,
+ StopDelay: tschStopDelay,
+ DeleteDelay: tschDeleteDelay,
+ TaskName: tschName,
+ },
+ }
+ if err := module.Exec(log.WithContext(ctx), creds, target, execCfg); err != nil {
+ log.Fatal().Err(err).Msg("TSCH execution failed")
+ }
+ },
+ }
+ tschDemandCmd = &cobra.Command{
+ Use: "demand [target]",
+ Short: "Register a remote scheduled task and demand immediate start",
+ Long: `Description:
+ Similar to the register method, the demand method will call SchRpcRegisterTask,
+ But rather than setting a defined time when the task will start, it will
+ additionally call SchRpcRun to forcefully start the task.
+
+References:
+ SchRpcRegisterTask - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/849c131a-64e4-46ef-b015-9d4c599c5167
+ SchRpcRun - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/77f2250d-500a-40ee-be18-c82f7079c4f0
+`,
+ Args: needsTarget,
+ Run: func(cmd *cobra.Command, args []string) {
+ if tschNoDelete {
+ log.Warn().Msg("Task will not be deleted after execution")
+ }
+ module := tschexec.Module{}
+ execCfg := &exec.ExecutionConfig{
+ ExecutableName: executable,
+ ExecutableArgs: executableArgs,
+ ExecutionMethod: tschexec.MethodDemand,
+
+ ExecutionMethodConfig: tschexec.MethodDemandConfig{
+ NoDelete: tschNoDelete,
+ TaskName: tschName,
+ },
+ }
+ if err := module.Exec(log.WithContext(ctx), creds, target, execCfg); err != nil {
+ log.Fatal().Err(err).Msg("TSCH execution failed")
+ }
+ },
+ }
+ tschDeleteCmd = &cobra.Command{
+ Use: "delete [target]",
+ Short: "Manually delete a scheduled task",
+ Long: `Description:
+ The delete method manually deletes a scheduled task by calling SchRpcDelete
+
+References:
+ SchRpcDelete - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/360bb9b1-dd2a-4b36-83ee-21f12cb97cff
+`,
+ Args: needsTarget,
+ Run: func(cmd *cobra.Command, args []string) {
+ module := tschexec.Module{}
+ cleanCfg := &exec.CleanupConfig{
+ CleanupMethod: tschexec.MethodDelete,
+ CleanupMethodConfig: tschexec.MethodDeleteConfig{TaskPath: tschTaskPath},
+ }
+ if err := module.Cleanup(log.WithContext(ctx), creds, target, cleanCfg); err != nil {
+ log.Fatal().Err(err).Msg("TSCH cleanup failed")
+ }
+ },
+ }
+)
diff --git a/go.mod b/go.mod
index 9da0e5e..2b64e5b 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/FalconOpsLLC/goexec
go 1.24.0
require (
- github.com/bryanmcnulty/adauth v0.0.0-20250227005704-df9302d730c2
+ github.com/RedTeamPentesting/adauth v0.1.1-0.20250304075117-acd47d454877
github.com/oiweiwei/go-msrpc v1.2.1
github.com/rs/zerolog v1.33.0
github.com/spf13/cobra v1.9.1
@@ -25,9 +25,9 @@ require (
github.com/oiweiwei/go-smb2.fork v1.0.0 // indirect
github.com/oiweiwei/gokrb5.fork/v9 v9.0.2 // indirect
github.com/spf13/pflag v1.0.6 // indirect
- golang.org/x/crypto v0.32.0 // indirect
- golang.org/x/net v0.34.0 // indirect
- golang.org/x/sys v0.29.0 // indirect
- golang.org/x/text v0.21.0 // indirect
+ golang.org/x/crypto v0.35.0 // indirect
+ golang.org/x/net v0.35.0 // indirect
+ golang.org/x/sys v0.30.0 // indirect
+ golang.org/x/text v0.22.0 // indirect
software.sslmate.com/src/go-pkcs12 v0.5.0 // indirect
)
diff --git a/go.sum b/go.sum
index 439cca5..a46de81 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/bryanmcnulty/adauth v0.0.0-20250227005704-df9302d730c2 h1:AxVqt9SbxfGqhEO8wwssTq0PYQfxuSIFUt4hMGV6KZw=
-github.com/bryanmcnulty/adauth v0.0.0-20250227005704-df9302d730c2/go.mod h1:h0dSYNaF2657vK+RY1ntfWKnSd6LlZHIWHyJq8D4VUA=
+github.com/RedTeamPentesting/adauth v0.1.1-0.20250304075117-acd47d454877 h1:n5V0EER+EvvmUZitR5zGFUMyoIHnI12SWFMciI7kh70=
+github.com/RedTeamPentesting/adauth v0.1.1-0.20250304075117-acd47d454877/go.mod h1:iHf/fY7CueB7qLHZ5YgTZXvrVCSLJy4+tAifOSNLAFQ=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -70,8 +70,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
-golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
-golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
+golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
+golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -79,8 +79,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
-golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
+golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
+golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -92,8 +92,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
-golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -101,8 +101,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
diff --git a/pkg/client/dcerpc/client.go b/internal/client/dcerpc/client.go
index 5b5b935..d9d8d71 100644
--- a/pkg/client/dcerpc/client.go
+++ b/internal/client/dcerpc/client.go
@@ -2,7 +2,7 @@ package dcerpc
import (
"context"
- "github.com/bryanmcnulty/adauth"
+ "github.com/RedTeamPentesting/adauth"
)
type Client interface {
diff --git a/pkg/client/dcerpc/dcerpc.go b/internal/client/dcerpc/dcerpc.go
index a22bff7..5c9a734 100644
--- a/pkg/client/dcerpc/dcerpc.go
+++ b/internal/client/dcerpc/dcerpc.go
@@ -4,8 +4,8 @@ import (
"context"
"errors"
"fmt"
- "github.com/bryanmcnulty/adauth"
- "github.com/bryanmcnulty/adauth/dcerpcauth"
+ "github.com/RedTeamPentesting/adauth"
+ "github.com/RedTeamPentesting/adauth/dcerpcauth"
"github.com/oiweiwei/go-msrpc/dcerpc"
"github.com/oiweiwei/go-msrpc/msrpc/epm/epm/v3"
"github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2"
@@ -65,6 +65,10 @@ func (client *DCEClient) OpenSvcctl(ctx context.Context) (ctl svcctl.SvcctlClien
return
}
+func (client *DCEClient) DCE() dcerpc.Conn {
+ return client.conn
+}
+
func (client *DCEClient) Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target, dialOpts ...dcerpc.Option) (err error) {
if creds != nil && target != nil {
authCtx := gssapi.NewSecurityContext(ctx)
diff --git a/pkg/client/dcerpc/smb.go b/internal/client/dcerpc/smb.go
index cab82eb..cab82eb 100644
--- a/pkg/client/dcerpc/smb.go
+++ b/internal/client/dcerpc/smb.go
diff --git a/pkg/exec/exec.go b/internal/exec/exec.go
index 6e18378..16fa543 100644
--- a/pkg/exec/exec.go
+++ b/internal/exec/exec.go
@@ -2,9 +2,14 @@ package exec
import (
"context"
- "github.com/bryanmcnulty/adauth"
+ "github.com/RedTeamPentesting/adauth"
)
+type CleanupConfig struct {
+ CleanupMethod string
+ CleanupMethodConfig interface{}
+}
+
type ExecutionConfig struct {
ExecutableName string // ExecutableName represents the name of the executable; i.e. "notepad.exe", "calc"
ExecutablePath string // ExecutablePath represents the full path to the executable; i.e. `C:\Windows\explorer.exe`
@@ -21,9 +26,10 @@ type ShellConfig struct {
ShellPath string // ShellPath is the full Windows path to the shell executable; i.e. `C:\Windows\System32\cmd.exe`
}
-type Executor interface {
+type Module interface {
// Exec performs a single execution task without the need to call Init.
- Exec(ctx context.Context, creds *adauth.Credential, target *adauth.Target, config *ExecutionConfig)
+ Exec(context.Context, *adauth.Credential, *adauth.Target, *ExecutionConfig) error
+ Cleanup(context.Context, *adauth.Credential, *adauth.Target, *CleanupConfig) error
// Init assigns the provided TODO
//Init(ctx context.Context, creds *adauth.Credential, target *adauth.Target)
diff --git a/internal/exec/scmr/exec.go b/internal/exec/scmr/exec.go
new file mode 100644
index 0000000..41c5d8a
--- /dev/null
+++ b/internal/exec/scmr/exec.go
@@ -0,0 +1,226 @@
+package scmrexec
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ dcerpc2 "github.com/FalconOpsLLC/goexec/internal/client/dcerpc"
+ "github.com/FalconOpsLLC/goexec/internal/exec"
+ "github.com/FalconOpsLLC/goexec/internal/windows"
+ "github.com/RedTeamPentesting/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 = dcerpc2.NewDCEClient(ctx, false, &dcerpc2.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
+}
diff --git a/pkg/exec/scmr/module.go b/internal/exec/scmr/module.go
index 1c855fb..95977b8 100644
--- a/pkg/exec/scmr/module.go
+++ b/internal/exec/scmr/module.go
@@ -1,13 +1,13 @@
package scmrexec
import (
- "github.com/bryanmcnulty/adauth"
+ "github.com/FalconOpsLLC/goexec/internal/client/dcerpc"
+ "github.com/RedTeamPentesting/adauth"
"github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2"
"github.com/rs/zerolog"
- "github.com/FalconOpsLLC/goexec/pkg/client/dcerpc"
)
-type Executor struct {
+type Module struct {
creds *adauth.Credential
target *adauth.Target
hostname string
diff --git a/pkg/exec/scmr/scmr.go b/internal/exec/scmr/scmr.go
index fbbb108..3a6ae08 100644
--- a/pkg/exec/scmr/scmr.go
+++ b/internal/exec/scmr/scmr.go
@@ -4,10 +4,10 @@ import (
"context"
"errors"
"fmt"
- "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2"
+ "github.com/FalconOpsLLC/goexec/internal/exec"
"github.com/FalconOpsLLC/goexec/internal/util"
- "github.com/FalconOpsLLC/goexec/pkg/exec"
- "github.com/FalconOpsLLC/goexec/pkg/windows"
+ "github.com/FalconOpsLLC/goexec/internal/windows"
+ "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2"
)
type service struct {
@@ -23,14 +23,14 @@ type service struct {
// openSCM opens a handle to SCM via ROpenSCManagerW
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/dc84adb3-d51d-48eb-820d-ba1c6ca5faf2
-func (executor *Executor) openSCM(ctx context.Context) (scm *svcctl.Handle, code uint32, err error) {
- if executor.ctl != nil {
+func (mod *Module) openSCM(ctx context.Context) (scm *svcctl.Handle, code uint32, err error) {
+ if mod.ctl != nil {
- hostname := executor.hostname
+ hostname := mod.hostname
if hostname == "" {
hostname = util.RandomHostname()
}
- if response, err := executor.ctl.OpenSCMW(ctx, &svcctl.OpenSCMWRequest{
+ if response, err := mod.ctl.OpenSCMW(ctx, &svcctl.OpenSCMWRequest{
MachineName: hostname + "\x00", // lpMachineName; The server's name (i.e. DC01, dc01.domain.local)
DatabaseName: "ServicesActive\x00", // lpDatabaseName; must be "ServicesActive" or "ServicesFailed"
DesiredAccess: ServiceModifyAccess, // dwDesiredAccess; requested access - appears to be ignored?
@@ -49,10 +49,10 @@ func (executor *Executor) openSCM(ctx context.Context) (scm *svcctl.Handle, code
// createService creates a service with the provided configuration via RCreateServiceW
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/6a8ca926-9477-4dd4-b766-692fab07227e
-func (executor *Executor) createService(ctx context.Context, scm *svcctl.Handle, scfg *service, ecfg *exec.ExecutionConfig) (code uint32, err error) {
- if executor.ctl != nil && scm != nil && scfg != nil && scfg.createConfig != nil {
+func (mod *Module) createService(ctx context.Context, scm *svcctl.Handle, scfg *service, ecfg *exec.ExecutionConfig) (code uint32, err error) {
+ if mod.ctl != nil && scm != nil && scfg != nil && scfg.createConfig != nil {
cfg := scfg.createConfig
- if response, err := executor.ctl.CreateServiceW(ctx, &svcctl.CreateServiceWRequest{
+ if response, err := mod.ctl.CreateServiceW(ctx, &svcctl.CreateServiceWRequest{
ServiceManager: scm,
ServiceName: cfg.ServiceName + "\x00",
DisplayName: cfg.DisplayName + "\x00",
@@ -76,9 +76,9 @@ func (executor *Executor) createService(ctx context.Context, scm *svcctl.Handle,
// openService opens a handle to a service given the service name (lpServiceName)
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/6d0a4225-451b-4132-894d-7cef7aecfd2d
-func (executor *Executor) openService(ctx context.Context, scm *svcctl.Handle, svcName string) (*svcctl.Handle, uint32, error) {
- if executor.ctl != nil && scm != nil {
- if openResponse, err := executor.ctl.OpenServiceW(ctx, &svcctl.OpenServiceWRequest{
+func (mod *Module) openService(ctx context.Context, scm *svcctl.Handle, svcName string) (*svcctl.Handle, uint32, error) {
+ if mod.ctl != nil && scm != nil {
+ if openResponse, err := mod.ctl.OpenServiceW(ctx, &svcctl.OpenServiceWRequest{
ServiceManager: scm,
ServiceName: svcName,
DesiredAccess: ServiceAllAccess,
@@ -99,9 +99,9 @@ func (executor *Executor) openService(ctx context.Context, scm *svcctl.Handle, s
// deleteService deletes an existing service with RDeleteService
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/6744cdb8-f162-4be0-bb31-98996b6495be
-func (executor *Executor) deleteService(ctx context.Context, scm *svcctl.Handle, svc *service) (code uint32, err error) {
- if executor.ctl != nil && scm != nil && svc != nil {
- if deleteResponse, err := executor.ctl.DeleteService(ctx, &svcctl.DeleteServiceRequest{Service: svc.handle}); err != nil {
+func (mod *Module) deleteService(ctx context.Context, scm *svcctl.Handle, svc *service) (code uint32, err error) {
+ if mod.ctl != nil && scm != nil && svc != nil {
+ if deleteResponse, err := mod.ctl.DeleteService(ctx, &svcctl.DeleteServiceRequest{Service: svc.handle}); err != nil {
defer func() {}()
if deleteResponse != nil {
return deleteResponse.Return, fmt.Errorf("delete service response: %w", err)
@@ -115,9 +115,9 @@ func (executor *Executor) deleteService(ctx context.Context, scm *svcctl.Handle,
// controlService sets the state of the provided process
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/e1c478be-117f-4512-9b67-17c20a48af97
-func (executor *Executor) controlService(ctx context.Context, scm *svcctl.Handle, svc *service, control uint32) (code uint32, err error) {
- if executor.ctl != nil && scm != nil && svc != nil {
- if controlResponse, err := executor.ctl.ControlService(ctx, &svcctl.ControlServiceRequest{
+func (mod *Module) controlService(ctx context.Context, scm *svcctl.Handle, svc *service, control uint32) (code uint32, err error) {
+ if mod.ctl != nil && scm != nil && svc != nil {
+ if controlResponse, err := mod.ctl.ControlService(ctx, &svcctl.ControlServiceRequest{
Service: svc.handle,
Control: control,
}); err != nil {
@@ -133,8 +133,8 @@ func (executor *Executor) controlService(ctx context.Context, scm *svcctl.Handle
// stopService sends stop signal to existing service using controlService
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/e1c478be-117f-4512-9b67-17c20a48af97
-func (executor *Executor) stopService(ctx context.Context, scm *svcctl.Handle, svc *service) (code uint32, err error) {
- if code, err = executor.controlService(ctx, scm, svc, windows.SERVICE_CONTROL_STOP); code == windows.ERROR_SERVICE_NOT_ACTIVE {
+func (mod *Module) stopService(ctx context.Context, scm *svcctl.Handle, svc *service) (code uint32, err error) {
+ if code, err = mod.controlService(ctx, scm, svc, windows.SERVICE_CONTROL_STOP); code == windows.ERROR_SERVICE_NOT_ACTIVE {
err = nil
}
return
@@ -142,9 +142,9 @@ func (executor *Executor) stopService(ctx context.Context, scm *svcctl.Handle, s
// startService starts the specified service with RStartServiceW
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/d9be95a2-cf01-4bdc-b30f-6fe4b37ada16
-func (executor *Executor) startService(ctx context.Context, scm *svcctl.Handle, svc *service) (code uint32, err error) {
- if executor.ctl != nil && scm != nil && svc != nil {
- if startResponse, err := executor.ctl.StartServiceW(ctx, &svcctl.StartServiceWRequest{Service: svc.handle}); err != nil {
+func (mod *Module) startService(ctx context.Context, scm *svcctl.Handle, svc *service) (code uint32, err error) {
+ if mod.ctl != nil && scm != nil && svc != nil {
+ if startResponse, err := mod.ctl.StartServiceW(ctx, &svcctl.StartServiceWRequest{Service: svc.handle}); err != nil {
if startResponse != nil {
// TODO: check if service is already running, return nil error if so
if startResponse.Return == windows.ERROR_SERVICE_REQUEST_TIMEOUT {
@@ -161,9 +161,9 @@ func (executor *Executor) startService(ctx context.Context, scm *svcctl.Handle,
// closeService closes the specified service handle using RCloseServiceHandle
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/a2a4e174-09fb-4e55-bad3-f77c4b13245c
-func (executor *Executor) closeService(ctx context.Context, svc *svcctl.Handle) (code uint32, err error) {
- if executor.ctl != nil && svc != nil {
- if closeResponse, err := executor.ctl.CloseService(ctx, &svcctl.CloseServiceRequest{ServiceObject: svc}); err != nil {
+func (mod *Module) closeService(ctx context.Context, svc *svcctl.Handle) (code uint32, err error) {
+ if mod.ctl != nil && svc != nil {
+ if closeResponse, err := mod.ctl.CloseService(ctx, &svcctl.CloseServiceRequest{ServiceObject: svc}); err != nil {
if closeResponse != nil {
return closeResponse.Return, fmt.Errorf("close service response: %w", err)
}
@@ -176,9 +176,9 @@ func (executor *Executor) closeService(ctx context.Context, svc *svcctl.Handle)
// getServiceConfig fetches the configuration details of a service given a handle passed in a service{} structure.
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/89e2d5b1-19cf-44ca-969f-38eea9fe7f3c
-func (executor *Executor) queryServiceConfig(ctx context.Context, svc *service) (code uint32, err error) {
- if executor.ctl != nil && svc != nil && svc.handle != nil {
- if getResponse, err := executor.ctl.QueryServiceConfigW(ctx, &svcctl.QueryServiceConfigWRequest{
+func (mod *Module) queryServiceConfig(ctx context.Context, svc *service) (code uint32, err error) {
+ if mod.ctl != nil && svc != nil && svc.handle != nil {
+ if getResponse, err := mod.ctl.QueryServiceConfigW(ctx, &svcctl.QueryServiceConfigWRequest{
Service: svc.handle,
BufferLength: 1024 * 8,
}); err != nil {
@@ -196,9 +196,9 @@ func (executor *Executor) queryServiceConfig(ctx context.Context, svc *service)
// queryServiceStatus fetches the state of the specified service
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/cf94d915-b4e1-40e5-872b-a9cb3ad09b46
-func (executor *Executor) queryServiceStatus(ctx context.Context, svc *service) (uint32, error) {
- if executor.ctl != nil && svc != nil {
- if queryResponse, err := executor.ctl.QueryServiceStatus(ctx, &svcctl.QueryServiceStatusRequest{Service: svc.handle}); err != nil {
+func (mod *Module) queryServiceStatus(ctx context.Context, svc *service) (uint32, error) {
+ if mod.ctl != nil && svc != nil {
+ if queryResponse, err := mod.ctl.QueryServiceStatus(ctx, &svcctl.QueryServiceStatusRequest{Service: svc.handle}); err != nil {
if queryResponse != nil {
return queryResponse.Return, fmt.Errorf("query service status response: %w", err)
}
@@ -213,9 +213,9 @@ func (executor *Executor) queryServiceStatus(ctx context.Context, svc *service)
// changeServiceConfigBinary edits the provided service's lpBinaryPathName
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/61ea7ed0-c49d-4152-a164-b4830f16c8a4
-func (executor *Executor) changeServiceConfigBinary(ctx context.Context, svc *service, bin string) (code uint32, err error) {
- if executor.ctl != nil && svc != nil && svc.handle != nil {
- if changeResponse, err := executor.ctl.ChangeServiceConfigW(ctx, &svcctl.ChangeServiceConfigWRequest{
+func (mod *Module) changeServiceConfigBinary(ctx context.Context, svc *service, bin string) (code uint32, err error) {
+ if mod.ctl != nil && svc != nil && svc.handle != nil {
+ if changeResponse, err := mod.ctl.ChangeServiceConfigW(ctx, &svcctl.ChangeServiceConfigWRequest{
Service: svc.handle,
ServiceType: svc.svcConfig.ServiceType,
StartType: svc.svcConfig.StartType,
diff --git a/internal/exec/tsch/exec.go b/internal/exec/tsch/exec.go
new file mode 100644
index 0000000..51f157c
--- /dev/null
+++ b/internal/exec/tsch/exec.go
@@ -0,0 +1,289 @@
+package tschexec
+
+import (
+ "context"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ dcerpc2 "github.com/FalconOpsLLC/goexec/internal/client/dcerpc"
+ "github.com/FalconOpsLLC/goexec/internal/exec"
+ "github.com/FalconOpsLLC/goexec/internal/util"
+ "github.com/RedTeamPentesting/adauth"
+ "github.com/oiweiwei/go-msrpc/dcerpc"
+ "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1"
+ "github.com/rs/zerolog"
+ "regexp"
+ "time"
+)
+
+const (
+ TaskXMLDurationFormat = "2006-01-02T15:04:05.9999999Z"
+ TaskXMLHeader = `<?xml version="1.0" encoding="UTF-16"?>`
+)
+
+var (
+ TaskPathRegex = regexp.MustCompile(`^\\[^ :/\\][^:/]*$`) // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/fa8809c8-4f0f-4c6d-994a-6c10308757c1
+ TaskNameRegex = regexp.MustCompile(`^[^ :/\\][^:/\\]*$`)
+)
+
+// *very* simple implementation of xs:duration - only accepts +seconds
+func xmlDuration(dur time.Duration) string {
+ if s := int(dur.Seconds()); s >= 0 {
+ return fmt.Sprintf(`PT%dS`, s)
+ }
+ return `PT0S`
+}
+
+// Connect to the target & initialize DCE & TSCH clients
+func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target) (err error) {
+ if mod.dce == nil {
+ mod.dce = dcerpc2.NewDCEClient(ctx, false, &dcerpc2.SmbConfig{})
+ if err = mod.dce.Connect(ctx, creds, target); err != nil {
+ return fmt.Errorf("DCE connect: %w", err)
+ } else if mod.tsch, err = itaskschedulerservice.NewTaskSchedulerServiceClient(ctx, mod.dce.DCE(), dcerpc.WithSecurityLevel(dcerpc.AuthLevelPktPrivacy)); err != nil {
+ return fmt.Errorf("init MS-TSCH client: %w", err)
+ }
+ mod.log.Info().Msg("DCE connection successful")
+ }
+ return
+}
+
+func (mod *Module) Cleanup(ctx context.Context, creds *adauth.Credential, target *adauth.Target, ccfg *exec.CleanupConfig) (err error) {
+ mod.log = zerolog.Ctx(ctx).With().
+ Str("module", "tsch").
+ Str("method", ccfg.CleanupMethod).Logger()
+ mod.creds = creds
+ mod.target = target
+
+ if ccfg.CleanupMethod == MethodDelete {
+ if cfg, ok := ccfg.CleanupMethodConfig.(MethodDeleteConfig); !ok {
+ return errors.New("invalid configuration")
+ } else {
+ if err = mod.Connect(ctx, creds, target); err != nil {
+ return fmt.Errorf("connect: %w", err)
+ } else if _, err = mod.tsch.Delete(ctx, &itaskschedulerservice.DeleteRequest{
+ Path: cfg.TaskPath,
+ Flags: 0,
+ }); err != nil {
+ mod.log.Error().Err(err).Str("task", cfg.TaskPath).Msg("Failed to delete task")
+ return fmt.Errorf("delete task: %w", err)
+ } else {
+ mod.log.Info().Str("task", cfg.TaskPath).Msg("Task deleted successfully")
+ }
+ }
+ } else {
+ return fmt.Errorf("method not implemented: %s", ccfg.CleanupMethod)
+ }
+ return
+}
+
+func (mod *Module) Exec(ctx context.Context, creds *adauth.Credential, target *adauth.Target, ecfg *exec.ExecutionConfig) (err error) {
+
+ mod.log = zerolog.Ctx(ctx).With().
+ Str("module", "tsch").
+ Str("method", ecfg.ExecutionMethod).Logger()
+ mod.creds = creds
+ mod.target = target
+
+ if ecfg.ExecutionMethod == MethodRegister {
+ if cfg, ok := ecfg.ExecutionMethodConfig.(MethodRegisterConfig); !ok {
+ return errors.New("invalid configuration")
+
+ } else {
+ startTime := time.Now().UTC().Add(cfg.StartDelay)
+ stopTime := startTime.Add(cfg.StopDelay)
+
+ task := &task{
+ TaskVersion: "1.2", // static
+ TaskNamespace: "http://schemas.microsoft.com/windows/2004/02/mit/task", // static
+ TimeTriggers: []taskTimeTrigger{
+ {
+ StartBoundary: startTime.Format(TaskXMLDurationFormat),
+ Enabled: true,
+ },
+ },
+ Principals: defaultPrincipals,
+ Settings: defaultSettings,
+ Actions: actions{
+ Context: defaultPrincipals.Principals[0].ID,
+ Exec: []actionExec{
+ {
+ Command: ecfg.ExecutableName,
+ Arguments: ecfg.ExecutableArgs,
+ },
+ },
+ },
+ }
+ if !cfg.NoDelete && !cfg.CallDelete {
+ if cfg.StopDelay == 0 {
+ // EndBoundary cannot be >= StartBoundary
+ cfg.StopDelay = 1 * time.Second
+ }
+ task.Settings.DeleteExpiredTaskAfter = xmlDuration(cfg.DeleteDelay)
+ task.TimeTriggers[0].EndBoundary = stopTime.Format(TaskXMLDurationFormat)
+ }
+
+ if doc, err := xml.Marshal(task); err != nil {
+ return fmt.Errorf("marshal task XML: %w", err)
+
+ } else {
+ mod.log.Debug().Str("task", string(doc)).Msg("Task XML generated")
+ docStr := TaskXMLHeader + string(doc)
+
+ taskPath := cfg.TaskPath
+ taskName := cfg.TaskName
+
+ if taskName == "" {
+ taskName = util.RandomString()
+ }
+ if taskPath == "" {
+ taskPath = `\` + taskName
+ }
+
+ if err = mod.Connect(ctx, creds, target); err != nil {
+ return fmt.Errorf("connect: %w", err)
+ }
+ defer func() {
+ if err = mod.dce.Close(ctx); err != nil {
+ mod.log.Warn().Err(err).Msg("Failed to dispose dce client")
+ } else {
+ mod.log.Debug().Msg("Disposed DCE client")
+ }
+ }()
+ var response *itaskschedulerservice.RegisterTaskResponse
+ if response, err = mod.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{
+ Path: taskPath,
+ XML: docStr,
+ Flags: 0, // TODO
+ LogonType: 0, // TASK_LOGON_NONE
+ CredsCount: 0,
+ Creds: nil,
+ }); err != nil {
+ return err
+
+ } else {
+ mod.log.Info().Str("path", response.ActualPath).Msg("Task registered successfully")
+
+ if !cfg.NoDelete {
+ if cfg.CallDelete {
+ defer func() {
+ if err = mod.Cleanup(ctx, creds, target, &exec.CleanupConfig{
+ CleanupMethod: MethodDelete,
+ CleanupMethodConfig: MethodDeleteConfig{TaskPath: taskPath},
+ }); err != nil {
+ mod.log.Error().Err(err).Msg("Failed to delete task")
+ }
+ }()
+ mod.log.Info().Dur("ms", cfg.StartDelay).Msg("Waiting for task to run")
+ select {
+ case <-ctx.Done():
+ mod.log.Warn().Msg("Cancelling execution")
+ return err
+ case <-time.After(cfg.StartDelay + (time.Second * 2)): // + two seconds
+ // TODO: check if task is running yet; delete if the wait period is over
+ break
+ }
+ return err
+ } else {
+ mod.log.Info().Time("when", stopTime).Msg("Task is scheduled to delete")
+ }
+ }
+ }
+ }
+ }
+ } else if ecfg.ExecutionMethod == MethodDemand {
+ if cfg, ok := ecfg.ExecutionMethodConfig.(MethodDemandConfig); !ok {
+ return errors.New("invalid configuration")
+
+ } else {
+ taskPath := cfg.TaskPath
+ taskName := cfg.TaskName
+
+ if taskName == "" {
+ mod.log.Debug().Msg("Task name not defined. Using random string")
+ taskName = util.RandomString()
+ }
+ if taskPath == "" {
+ taskPath = `\` + taskName
+ }
+ if !TaskNameRegex.MatchString(taskName) {
+ return fmt.Errorf("invalid task name: %s", taskName)
+ }
+ if !TaskPathRegex.MatchString(taskPath) {
+ return fmt.Errorf("invalid task path: %s", taskPath)
+ }
+
+ mod.log.Debug().Msg("Using demand method")
+ settings := defaultSettings
+ settings.AllowStartOnDemand = true
+ task := &task{
+ TaskVersion: "1.2", // static
+ TaskNamespace: "http://schemas.microsoft.com/windows/2004/02/mit/task", // static
+ Principals: defaultPrincipals,
+ Settings: defaultSettings,
+ Actions: actions{
+ Context: defaultPrincipals.Principals[0].ID,
+ Exec: []actionExec{
+ {
+ Command: ecfg.ExecutableName,
+ Arguments: ecfg.ExecutableArgs,
+ },
+ },
+ },
+ }
+ if doc, err := xml.Marshal(task); err != nil {
+ return fmt.Errorf("marshal task: %w", err)
+ } else {
+ docStr := TaskXMLHeader + string(doc)
+
+ if err = mod.Connect(ctx, creds, target); err != nil {
+ return fmt.Errorf("connect: %w", err)
+ }
+ defer func() {
+ if err = mod.dce.Close(ctx); err != nil {
+ mod.log.Warn().Err(err).Msg("Failed to dispose dce client")
+ } else {
+ mod.log.Debug().Msg("Disposed DCE client")
+ }
+ }()
+
+ var response *itaskschedulerservice.RegisterTaskResponse
+ if response, err = mod.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{
+ Path: taskPath,
+ XML: docStr,
+ Flags: 0, // TODO
+ LogonType: 0, // TASK_LOGON_NONE
+ CredsCount: 0,
+ Creds: nil,
+ }); err != nil {
+ return fmt.Errorf("register task: %w", err)
+
+ } else {
+ mod.log.Info().Str("task", response.ActualPath).Msg("Task registered successfully")
+ if !cfg.NoDelete {
+ defer func() {
+ if err = mod.Cleanup(ctx, creds, target, &exec.CleanupConfig{
+ CleanupMethod: MethodDelete,
+ CleanupMethodConfig: MethodDeleteConfig{TaskPath: taskPath},
+ }); err != nil {
+ mod.log.Error().Err(err).Msg("Failed to delete task")
+ }
+ }()
+ }
+ if _, err = mod.tsch.Run(ctx, &itaskschedulerservice.RunRequest{
+ Path: response.ActualPath,
+ Flags: 0, // Maybe we want to use these?
+ }); err != nil {
+ return err
+ } else {
+ mod.log.Info().Str("task", response.ActualPath).Msg("Started task")
+ }
+ }
+ }
+ }
+ } else {
+ return fmt.Errorf("method not implemented: %s", ecfg.ExecutionMethod)
+ }
+
+ return nil
+}
diff --git a/internal/exec/tsch/module.go b/internal/exec/tsch/module.go
new file mode 100644
index 0000000..dbd9ade
--- /dev/null
+++ b/internal/exec/tsch/module.go
@@ -0,0 +1,48 @@
+package tschexec
+
+import (
+ "github.com/FalconOpsLLC/goexec/internal/client/dcerpc"
+ "github.com/RedTeamPentesting/adauth"
+ "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1"
+ "github.com/rs/zerolog"
+ "time"
+)
+
+type Module struct {
+ creds *adauth.Credential
+ target *adauth.Target
+
+ log zerolog.Logger
+ dce *dcerpc.DCEClient
+ tsch itaskschedulerservice.TaskSchedulerServiceClient
+}
+
+type MethodRegisterConfig struct {
+ NoDelete bool
+ CallDelete bool
+ TaskName string
+ TaskPath string
+ StartDelay time.Duration
+ StopDelay time.Duration
+ DeleteDelay time.Duration
+}
+
+type MethodDemandConfig struct {
+ NoDelete bool
+ CallDelete bool
+ TaskName string
+ TaskPath string
+ StopDelay time.Duration
+ DeleteDelay time.Duration
+}
+
+type MethodDeleteConfig struct {
+ TaskPath string
+}
+
+const (
+ MethodRegister string = "register"
+ MethodDemand string = "demand"
+ MethodDelete string = "delete"
+ MethodChange string = "update"
+)
diff --git a/internal/exec/tsch/tsch.go b/internal/exec/tsch/tsch.go
new file mode 100644
index 0000000..bc3ed0b
--- /dev/null
+++ b/internal/exec/tsch/tsch.go
@@ -0,0 +1,102 @@
+package tschexec
+
+import (
+ "encoding/xml"
+)
+
+// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/0d6383e4-de92-43e7-b0bb-a60cfa36379f
+
+type taskTimeTrigger struct {
+ XMLName xml.Name `xml:"TimeTrigger"`
+ StartBoundary string `xml:"StartBoundary,omitempty"` // Derived from time.Time
+ EndBoundary string `xml:"EndBoundary,omitempty"` // Derived from time.Time; must be > StartBoundary
+ Enabled bool `xml:"Enabled"`
+}
+
+type idleSettings struct {
+ StopOnIdleEnd bool `xml:"StopOnIdleEnd"`
+ RestartOnIdle bool `xml:"RestartOnIdle"`
+}
+
+type settings struct {
+ XMLName xml.Name `xml:"Settings"`
+ Enabled bool `xml:"Enabled"`
+ Hidden bool `xml:"Hidden"`
+ DisallowStartIfOnBatteries bool `xml:"DisallowStartIfOnBatteries"`
+ StopIfGoingOnBatteries bool `xml:"StopIfGoingOnBatteries"`
+ AllowHardTerminate bool `xml:"AllowHardTerminate"`
+ RunOnlyIfNetworkAvailable bool `xml:"RunOnlyIfNetworkAvailable"`
+ AllowStartOnDemand bool `xml:"AllowStartOnDemand"`
+ WakeToRun bool `xml:"WakeToRun"`
+ RunOnlyIfIdle bool `xml:"RunOnlyIfIdle"`
+ StartWhenAvailable bool `xml:"StartWhenAvailable"`
+ Priority int `xml:"Priority,omitempty"` // 1 to 10 inclusive
+ MultipleInstancesPolicy string `xml:"MultipleInstancesPolicy,omitempty"`
+ ExecutionTimeLimit string `xml:"ExecutionTimeLimit,omitempty"`
+ DeleteExpiredTaskAfter string `xml:"DeleteExpiredTaskAfter,omitempty"` // Derived from time.Duration
+ IdleSettings idleSettings `xml:"IdleSettings,omitempty"`
+}
+
+type actionExec struct {
+ XMLName xml.Name `xml:"Exec"`
+ Command string `xml:"Command"`
+ Arguments string `xml:"Arguments"`
+}
+
+type actions struct {
+ XMLName xml.Name `xml:"Actions"`
+ Context string `xml:"Context,attr"`
+ Exec []actionExec `xml:"Exec,omitempty"`
+}
+
+type principals struct {
+ XMLName xml.Name `xml:"Principals"`
+ Principals []principal `xml:"Principal"`
+}
+
+type principal struct {
+ XMLName xml.Name `xml:"Principal"`
+ ID string `xml:"id,attr"`
+ UserID string `xml:"UserId"`
+ RunLevel string `xml:"RunLevel"`
+}
+
+type task struct {
+ XMLName xml.Name `xml:"Task"`
+ TaskVersion string `xml:"version,attr"`
+ TaskNamespace string `xml:"xmlns,attr"`
+ TimeTriggers []taskTimeTrigger `xml:"Triggers>TimeTrigger,omitempty"`
+ Actions actions `xml:"Actions"`
+ Principals principals `xml:"Principals"`
+ Settings settings `xml:"Settings"`
+}
+
+var (
+ defaultSettings = settings{
+ MultipleInstancesPolicy: "IgnoreNew",
+ DisallowStartIfOnBatteries: false,
+ StopIfGoingOnBatteries: false,
+ AllowHardTerminate: true,
+ RunOnlyIfNetworkAvailable: false,
+ IdleSettings: idleSettings{
+ StopOnIdleEnd: true,
+ RestartOnIdle: false,
+ },
+ AllowStartOnDemand: true,
+ Enabled: true,
+ Hidden: true,
+ RunOnlyIfIdle: false,
+ WakeToRun: false,
+ Priority: 7, // 7 is a pretty standard value for scheduled tasks
+ StartWhenAvailable: true,
+ }
+ defaultPrincipals = principals{
+ Principals: []principal{
+ {
+ ID: "SYSTEM",
+ UserID: "S-1-5-18",
+ RunLevel: "HighestAvailable",
+ },
+ },
+ }
+)
diff --git a/pkg/windows/const.go b/internal/windows/const.go
index 4c4fbe6..4c4fbe6 100644
--- a/pkg/windows/const.go
+++ b/internal/windows/const.go
diff --git a/pkg/exec/scmr/exec.go b/pkg/exec/scmr/exec.go
deleted file mode 100644
index b3c1531..0000000
--- a/pkg/exec/scmr/exec.go
+++ /dev/null
@@ -1,224 +0,0 @@
-package scmrexec
-
-import (
- "context"
- "errors"
- "fmt"
- "github.com/bryanmcnulty/adauth"
- "github.com/rs/zerolog"
- "github.com/FalconOpsLLC/goexec/pkg/client/dcerpc"
- "github.com/FalconOpsLLC/goexec/pkg/exec"
- "github.com/FalconOpsLLC/goexec/pkg/windows"
-)
-
-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 (executor *Executor) createClients(ctx context.Context) (cleanup func(cCtx context.Context), err error) {
-
- cleanup = func(context.Context) {
- if executor.dce != nil {
- executor.log.Debug().Msg("Cleaning up clients")
- if err := executor.dce.Close(ctx); err != nil {
- executor.log.Error().Err(err).Msg("Failed to destroy DCE connection")
- }
- }
- }
- cleanup(ctx)
- executor.dce = dcerpc.NewDCEClient(ctx, false, &dcerpc.SmbConfig{Port: 445})
- cleanup = func(context.Context) {}
-
- if err = executor.dce.Connect(ctx, executor.creds, executor.target); err != nil {
- return nil, fmt.Errorf("connection to DCERPC failed: %w", err)
- }
- executor.ctl, err = executor.dce.OpenSvcctl(ctx)
- return
-}
-
-func (executor *Executor) Exec(ctx context.Context, creds *adauth.Credential, target *adauth.Target, ecfg *exec.ExecutionConfig) (err error) {
-
- vctx := context.WithoutCancel(ctx)
- executor.log = zerolog.Ctx(ctx).With().
- Str("module", "scmr").
- Str("method", ecfg.ExecutionMethod).Logger()
- executor.creds = creds
- executor.target = target
-
- if ecfg.ExecutionMethod == MethodCreate {
- if cfg, ok := ecfg.ExecutionMethodConfig.(MethodCreateConfig); !ok || cfg.ServiceName == "" {
- return errors.New("invalid configuration")
- } else {
- if cleanup, err := executor.createClients(ctx); err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- } else {
- executor.log.Debug().Msg("Created clients")
- defer cleanup(ctx)
- }
- svc := &service{
- createConfig: &cfg,
- name: cfg.ServiceName,
- }
- scm, code, err := executor.openSCM(ctx)
- if err != nil {
- return fmt.Errorf("failed to open SCM with code %d: %w", code, err)
- }
- executor.log.Debug().Msg("Opened handle to SCM")
- code, err = executor.createService(ctx, scm, svc, ecfg)
- if err != nil {
- return fmt.Errorf("failed to create service with code %d: %w", code, err)
- }
- executor.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 = executor.deleteService(ctx, scm, svc); err != nil {
- executor.log.Error().Err(err).Msg("Failed to delete service") // TODO
- }
- executor.log.Info().Str("service", svc.name).Msg("Service deleted successfully")
- }()
- }
- if code, err = executor.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
- executor.log.Info().Msg("Service start timeout/cancelled. Execution likely successful")
- executor.log.Info().Msg("Reconnecting for cleanup procedure")
- ctx = vctx
-
- if _, err = executor.createClients(ctx); err != nil {
- executor.log.Error().Err(err).Msg("Reconnect failed")
-
- } else if scm, code, err = executor.openSCM(ctx); err != nil {
- executor.log.Error().Err(err).Msg("Failed to reopen SCM")
-
- } else if svc.handle, code, err = executor.openService(ctx, scm, svc.name); err != nil {
- executor.log.Error().Str("service", svc.name).Err(err).Msg("Failed to reopen service handle")
-
- } else {
- executor.log.Debug().Str("service", svc.name).Msg("Reconnection successful")
- }
- } else {
- executor.log.Error().Err(err).Msg("Failed to start service")
- }
- } else {
- executor.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 := executor.createClients(ctx); err != nil {
- return fmt.Errorf("failed to create client: %w", err)
- } else {
- executor.log.Debug().Msg("Created clients")
- defer cleanup(ctx)
- }
- svc := &service{modifyConfig: &cfg, name: cfg.ServiceName}
-
- // Open SCM handle
- scm, code, err := executor.openSCM(ctx)
- if err != nil {
- return fmt.Errorf("failed to create service with code %d: %w", code, err)
- }
- executor.log.Debug().Msg("Opened handle to SCM")
-
- // Open service handle
- if svc.handle, code, err = executor.openService(ctx, scm, svc.name); err != nil {
- return fmt.Errorf("failed to open service with code %d: %w", code, err)
- }
- executor.log.Debug().Str("service", svc.name).Msg("Opened service")
-
- // Stop service before editing
- if !cfg.NoStart {
- if code, err = executor.stopService(ctx, scm, svc); err != nil {
- executor.log.Warn().Err(err).Msg("Failed to stop existing service")
- } else if code == windows.ERROR_SERVICE_NOT_ACTIVE {
- executor.log.Debug().Str("service", svc.name).Msg("Service is not running")
- } else {
- executor.log.Info().Str("service", svc.name).Msg("Stopped existing service")
- defer func() {
- if code, err = executor.startService(ctx, scm, svc); err != nil {
- executor.log.Error().Err(err).Msg("Failed to restore service state to running")
- }
- }()
- }
- }
- if code, err = executor.queryServiceConfig(ctx, svc); err != nil {
- return fmt.Errorf("failed to query service configuration with code %d: %w", code, err)
- }
- executor.log.Debug().
- Str("service", svc.name).
- Str("command", svc.svcConfig.BinaryPathName).Msg("Fetched existing service configuration")
-
- // Change service configuration
- if code, err = executor.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 = executor.changeServiceConfigBinary(ctx, svc, svc.svcConfig.BinaryPathName); err != nil {
- executor.log.Error().Err(err).Msg("Failed to restore service configuration")
- } else {
- executor.log.Info().Str("service", svc.name).Msg("Restored service configuration")
- }
- }()
- executor.log.Info().
- Str("service", svc.name).
- Str("command", cmd).Msg("Changed service configuration")
-
- // Start service
- if !cfg.NoStart {
- if code, err = executor.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
- executor.log.Info().Msg("Service start timeout/cancelled. Execution likely successful")
- executor.log.Info().Msg("Reconnecting for cleanup procedure")
- ctx = vctx
-
- if _, err = executor.createClients(ctx); err != nil {
- executor.log.Error().Err(err).Msg("Reconnect failed")
-
- } else if scm, code, err = executor.openSCM(ctx); err != nil {
- executor.log.Error().Err(err).Msg("Failed to reopen SCM")
-
- } else if svc.handle, code, err = executor.openService(ctx, scm, svc.name); err != nil {
- executor.log.Error().Str("service", svc.name).Err(err).Msg("Failed to reopen service handle")
-
- } else {
- executor.log.Debug().Str("service", svc.name).Msg("Reconnection successful")
- }
- } else {
- executor.log.Error().Err(err).Msg("Failed to start service")
- }
- } else {
- executor.log.Info().Str("service", svc.name).Msg("Started service")
- }
- defer func() {
- // Stop service
- if code, err = executor.stopService(ctx, scm, svc); err != nil {
- executor.log.Error().Err(err).Msg("Failed to stop service")
- } else {
- executor.log.Info().Str("service", svc.name).Msg("Stopped service")
- }
- }()
- }
- }
- }
- return err
-}