aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorBryan McNulty <bryanmcnulty@protonmail.com>2025-04-16 12:11:58 -0500
committerBryan McNulty <bryanmcnulty@protonmail.com>2025-04-16 12:11:58 -0500
commit55eb4275fb760ac7a3ce1444f5ae0ded9e2ff91c (patch)
treeedf4ec3b814fb10ccdbf759a62819a865d3e8141 /cmd
parenta827b67d47cba7b02ea9599fe6bb88ffb3a6967d (diff)
downloadgoexec-55eb4275fb760ac7a3ce1444f5ae0ded9e2ff91c.tar.gz
goexec-55eb4275fb760ac7a3ce1444f5ae0ded9e2ff91c.zip
rewrote everything lol
Diffstat (limited to 'cmd')
-rw-r--r--cmd/args.go126
-rw-r--r--cmd/dcom.go102
-rw-r--r--cmd/root.go172
-rw-r--r--cmd/rpc.go86
-rw-r--r--cmd/scmr.go243
-rw-r--r--cmd/tsch.go303
-rw-r--r--cmd/wmi.go169
7 files changed, 611 insertions, 590 deletions
diff --git a/cmd/args.go b/cmd/args.go
new file mode 100644
index 0000000..50e7c74
--- /dev/null
+++ b/cmd/args.go
@@ -0,0 +1,126 @@
+package cmd
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+)
+
+func registerRpcFlags(cmd *cobra.Command) {
+ rpcFlags := pflag.NewFlagSet("RPC", pflag.ExitOnError)
+
+ rpcFlags.BoolVar(&rpcClient.NoEpm, "no-epm", false, "Do not use EPM to automatically detect endpoints")
+ //rpcFlags.BoolVar(&rpcClient.Options.EpmAuto, "epm-auto", false, "Automatically detect endpoints instead of using the module defaults")
+ rpcFlags.BoolVar(&rpcClient.NoSign, "no-sign", false, "Disable signing on DCE messages")
+ rpcFlags.BoolVar(&rpcClient.NoSeal, "no-seal", false, "Disable packet stub encryption on DCE messages")
+ rpcFlags.StringVar(&rpcClient.Filter, "epm-filter", "", "String binding to filter endpoints returned by EPM")
+ rpcFlags.StringVar(&rpcClient.Endpoint, "endpoint", "", "Explicit RPC endpoint definition")
+
+ cmd.PersistentFlags().AddFlagSet(rpcFlags)
+
+ cmd.MarkFlagsMutuallyExclusive("endpoint", "epm-filter")
+ cmd.MarkFlagsMutuallyExclusive("no-epm", "epm-filter")
+}
+
+func registerProcessExecutionArgs(cmd *cobra.Command) {
+ group := pflag.NewFlagSet("Execution", pflag.ExitOnError)
+
+ group.StringVarP(&exec.Input.Arguments, "args", "a", "", "Command line arguments")
+ group.StringVarP(&exec.Input.CommandLine, "command", "c", "", "Windows process command line (executable & arguments)")
+ group.StringVarP(&exec.Input.Executable, "executable", "e", "", "Windows executable to invoke")
+
+ cmd.PersistentFlags().AddFlagSet(group)
+
+ cmd.MarkFlagsOneRequired("executable", "command")
+ cmd.MarkFlagsMutuallyExclusive("executable", "command")
+}
+
+func registerExecutionOutputArgs(cmd *cobra.Command) {
+ group := pflag.NewFlagSet("Output", pflag.ExitOnError)
+
+ group.StringVarP(&outputPath, "output", "o", "", `Fetch execution output to file or "-" for standard output`)
+ group.StringVarP(&outputMethod, "output-method", "m", "smb", "Method to fetch execution output")
+ group.StringVar(&exec.Output.RemotePath, "remote-output", "", "Location to temporarily store output on remote filesystem")
+ group.BoolVar(&exec.Output.NoDelete, "no-delete-output", false, "Preserve output file on remote filesystem")
+
+ cmd.PersistentFlags().AddFlagSet(group)
+}
+
+func args(reqs ...func(*cobra.Command, []string) error) (fn func(*cobra.Command, []string) error) {
+
+ return func(cmd *cobra.Command, args []string) (err error) {
+
+ for _, req := range reqs {
+ if err = req(cmd, args); err != nil {
+ return
+ }
+ }
+ return
+ }
+}
+
+func argsTarget(proto string) func(cmd *cobra.Command, args []string) error {
+
+ return func(cmd *cobra.Command, args []string) (err error) {
+
+ if len(args) != 1 {
+ return errors.New("command require exactly one positional argument: [target]")
+ }
+
+ if credential, target, err = authOpts.WithTarget(context.TODO(), proto, args[0]); err != nil {
+ return fmt.Errorf("failed to parse target: %w", err)
+ }
+
+ if credential == nil {
+ return errors.New("no credentials supplied")
+ }
+ if target == nil {
+ return errors.New("no target supplied")
+ }
+ return
+ }
+}
+
+func argsSmbClient() func(cmd *cobra.Command, args []string) error {
+ return args(
+ argsTarget("cifs"),
+
+ func(_ *cobra.Command, _ []string) error {
+
+ smbClient.Credential = credential
+ smbClient.Target = target
+ smbClient.Proxy = proxy
+
+ return smbClient.Parse(context.TODO())
+ },
+ )
+}
+
+func argsRpcClient(proto string) func(cmd *cobra.Command, args []string) error {
+ return args(
+ argsTarget(proto),
+
+ func(cmd *cobra.Command, args []string) (err error) {
+
+ rpcClient.Target = target
+ rpcClient.Credential = credential
+ rpcClient.Proxy = proxy
+
+ return rpcClient.Parse(context.TODO())
+ },
+ )
+}
+
+func argsOutput(methods ...string) func(cmd *cobra.Command, args []string) error {
+
+ var as []func(*cobra.Command, []string) error
+
+ for _, method := range methods {
+ if method == "smb" {
+ as = append(as, argsSmbClient())
+ }
+ }
+ return args(as...)
+}
diff --git a/cmd/dcom.go b/cmd/dcom.go
index d105b0c..5bcec78 100644
--- a/cmd/dcom.go
+++ b/cmd/dcom.go
@@ -1,38 +1,37 @@
package cmd
import (
- "github.com/FalconOpsLLC/goexec/internal/exec"
- dcomexec "github.com/FalconOpsLLC/goexec/internal/exec/dcom"
- "github.com/spf13/cobra"
+ "context"
+ dcomexec "github.com/FalconOpsLLC/goexec/pkg/goexec/dcom"
+ "github.com/oiweiwei/go-msrpc/ssp/gssapi"
+ "github.com/spf13/cobra"
)
func dcomCmdInit() {
- registerRpcFlags(dcomCmd)
- dcomMmcCmdInit()
- dcomCmd.AddCommand(dcomMmcCmd)
+ registerRpcFlags(dcomCmd)
+ dcomMmcCmdInit()
+ dcomCmd.AddCommand(dcomMmcCmd)
}
func dcomMmcCmdInit() {
- dcomMmcCmd.Flags().StringVarP(&executable, "executable", "e", "", "Remote Windows executable to invoke")
- dcomMmcCmd.Flags().StringVarP(&workingDirectory, "directory", "d", `C:\`, "Working directory")
- dcomMmcCmd.Flags().StringVarP(&executableArgs, "args", "a", "", "Process command line")
- dcomMmcCmd.Flags().StringVar(&windowState, "window", "Minimized", "Window state")
- dcomMmcCmd.Flags().StringVarP(&command, "command", "c", ``, "Windows executable & arguments to run")
+ dcomMmcCmd.Flags().StringVarP(&dcomMmc.WorkingDirectory, "directory", "d", `C:\`, "Working directory")
+ dcomMmcCmd.Flags().StringVar(&dcomMmc.WindowState, "window", "Minimized", "Window state")
- dcomMmcCmd.MarkFlagsOneRequired("executable", "command")
- dcomMmcCmd.MarkFlagsMutuallyExclusive("executable", "command")
+ registerProcessExecutionArgs(dcomMmcCmd)
}
var (
- dcomCmd = &cobra.Command{
- Use: "dcom",
- Short: "Establish execution via DCOM",
- Args: cobra.NoArgs,
- }
- dcomMmcCmd = &cobra.Command{
- Use: "mmc [target]",
- Short: "Establish execution via the DCOM MMC20.Application object",
- Long: `Description:
+ dcomMmc dcomexec.DcomMmc
+
+ dcomCmd = &cobra.Command{
+ Use: "dcom",
+ Short: "Establish execution via DCOM",
+ Args: cobra.NoArgs,
+ }
+ dcomMmcCmd = &cobra.Command{
+ Use: "mmc [target]",
+ Short: "Establish execution via the DCOM MMC20.Application object",
+ Long: `Description:
The mmc method uses the exposed MMC20.Application object to call Document.ActiveView.ShellExec,
and ultimately execute system commands.
@@ -42,34 +41,39 @@ References:
https://github.com/fortra/impacket/blob/master/examples/dcomexec.py
https://learn.microsoft.com/en-us/previous-versions/windows/desktop/mmc/view-executeshellcommand
`,
- Args: needsRpcTarget("host"),
- Run: func(cmd *cobra.Command, args []string) {
+ Args: argsRpcClient("host"),
+ Run: func(cmd *cobra.Command, args []string) {
+ var err error
+
+ ctx := gssapi.NewSecurityContext(context.Background())
+
+ ctx = log.With().
+ Str("module", "dcom").
+ Str("method", "mmc").
+ Logger().
+ WithContext(ctx)
+
+ if err = rpcClient.Connect(ctx); err != nil {
+ log.Fatal().Err(err).Msg("Connection failed")
+ }
- ctx = log.With().
- Str("module", "dcom").
- Str("method", "mmc").
- Logger().WithContext(ctx)
+ defer func() {
+ closeErr := rpcClient.Close(ctx)
+ if closeErr != nil {
+ log.Error().Err(closeErr).Msg("Failed to close connection")
+ }
+ }()
- module := dcomexec.Module{}
- connCfg := &exec.ConnectionConfig{
- ConnectionMethod: exec.ConnectionMethodDCE,
- ConnectionMethodConfig: dceConfig,
- }
- execCfg := &exec.ExecutionConfig{
- ExecutableName: executable,
- ExecutableArgs: executableArgs,
- ExecutionMethod: dcomexec.MethodMmc,
+ if err = dcomMmc.Init(ctx, &rpcClient); err != nil {
+ log.Error().Err(err).Msg("Module initialization failed")
+ returnCode = 1
+ return
+ }
- ExecutionMethodConfig: dcomexec.MethodMmcConfig{
- WorkingDirectory: workingDirectory,
- WindowState: windowState,
- },
- }
- if err := module.Connect(ctx, creds, target, connCfg); err != nil {
- log.Fatal().Err(err).Msg("Connection failed")
- } else if err = module.Exec(ctx, execCfg); err != nil {
- log.Fatal().Err(err).Msg("Execution failed")
- }
- },
- }
+ if err = dcomMmc.Execute(ctx, exec.Input); err != nil {
+ log.Error().Err(err).Msg("Execution failed")
+ returnCode = 1
+ }
+ },
+ }
)
diff --git a/cmd/root.go b/cmd/root.go
index cbc38c0..733bb75 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,122 +1,105 @@
package cmd
import (
- "context"
"fmt"
+ "github.com/FalconOpsLLC/goexec/internal/util"
+ "github.com/FalconOpsLLC/goexec/pkg/goexec"
+ "github.com/FalconOpsLLC/goexec/pkg/goexec/dce"
+ "github.com/FalconOpsLLC/goexec/pkg/goexec/smb"
"github.com/RedTeamPentesting/adauth"
+ "github.com/oiweiwei/go-msrpc/ssp"
+ "github.com/oiweiwei/go-msrpc/ssp/gssapi"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
- "net/url"
"os"
- "regexp"
- "strings"
)
var (
- //logFile string
- log zerolog.Logger
- ctx context.Context
- authOpts *adauth.Options
-
- hostname string
- proxyStr string
- proxyUrl *url.URL
-
- // Root flags
- unsafe bool // not implemented
- debug bool
-
- // Generic flags
- command string
- executable string
- executablePath string
- executableArgs string
- workingDirectory string
- windowState string
+ debug bool
+ logJson bool
+ returnCode int
+ outputMethod string
+ outputPath string
+ proxy string
+
+ log zerolog.Logger
+
+ rpcClient dce.Client
+ smbClient smb.Client
+
+ exec = goexec.ExecutionIO{
+ Input: new(goexec.ExecutionInput),
+ Output: new(goexec.ExecutionOutput),
+ }
+
+ authOpts *adauth.Options
+ credential *adauth.Credential
+ target *adauth.Target
rootCmd = &cobra.Command{
- Use: "goexec",
- PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
- // For modules that require a full executable path
- if executablePath != "" && !regexp.MustCompile(`^([a-zA-Z]:)?\\`).MatchString(executablePath) {
- return fmt.Errorf("executable path (-e) must be an absolute Windows path, i.e. C:\\Windows\\System32\\cmd.exe")
- }
- if command != "" {
- p := strings.SplitN(command, " ", 2)
- executable = p[0]
- if len(p) > 1 {
- executableArgs = p[1]
- }
+ Use: "goexec",
+ Short: `Windows remote execution multitool`,
+ Long: `TODO`,
+
+ PersistentPreRun: func(cmd *cobra.Command, args []string) {
+
+ if logJson {
+ log = zerolog.New(os.Stderr)
+ } else {
+ log = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr})
}
- log = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel).With().Timestamp().Logger()
+
+ log = log.Level(zerolog.InfoLevel).With().Timestamp().Logger()
if debug {
log = log.Level(zerolog.DebugLevel)
}
- return
+
+ if outputMethod == "smb" {
+ if exec.Output.RemotePath == "" {
+ exec.Output.RemotePath = util.RandomWindowsTempFile()
+ }
+ exec.Output.Provider = &smb.OutputFileFetcher{
+ Client: &smbClient,
+ Share: `C$`,
+ File: exec.Output.RemotePath,
+ }
+ }
},
}
)
-func needs(reqs ...func(*cobra.Command, []string) error) (fn func(*cobra.Command, []string) error) {
- return func(cmd *cobra.Command, args []string) (err error) {
- for _, req := range reqs {
- if err = req(cmd, args); err != nil {
- return
- }
- }
- return
- }
-}
+func init() {
+ // Cobra init
+ {
+ cobra.EnableCommandSorting = false
-func needsTarget(proto string) func(cmd *cobra.Command, args []string) error {
+ rootCmd.InitDefaultVersionFlag()
+ rootCmd.InitDefaultHelpCmd()
+ rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging")
+ rootCmd.PersistentFlags().BoolVar(&logJson, "log-json", false, "Log in JSON format")
- return func(cmd *cobra.Command, args []string) (err error) {
- if proxyStr != "" {
- if proxyUrl, err = url.Parse(proxyStr); err != nil {
- return fmt.Errorf("failed to parse proxy URL %q: %w", proxyStr, err)
- }
- }
- if len(args) != 1 {
- return fmt.Errorf("command require exactly one positional argument: [target]")
- }
- if creds, target, err = authOpts.WithTarget(ctx, proto, 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")
- }
- if hostname, err = target.Hostname(ctx); err != nil {
- log.Debug().Err(err).Msg("Could not get target hostname")
- }
- return
+ dcomCmdInit()
+ rootCmd.AddCommand(dcomCmd)
+
+ wmiCmdInit()
+ rootCmd.AddCommand(wmiCmd)
+
+ scmrCmdInit()
+ rootCmd.AddCommand(scmrCmd)
+
+ tschCmdInit()
+ rootCmd.AddCommand(tschCmd)
}
-}
-func init() {
- ctx = context.Background()
-
- cobra.EnableCommandSorting = false
-
- rootCmd.InitDefaultVersionFlag()
- rootCmd.InitDefaultHelpCmd()
- rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging")
- rootCmd.PersistentFlags().StringVarP(&proxyStr, "proxy", "x", "", "Proxy URL")
- rootCmd.PersistentFlags().BoolVar(&unsafe, "unsafe", false, "[NOT IMPLEMENTED] Don't ask for permission to run unsafe actions")
-
- authOpts = &adauth.Options{Debug: log.Debug().Msgf}
- authOpts.RegisterFlags(rootCmd.PersistentFlags())
-
- scmrCmdInit()
- rootCmd.AddCommand(scmrCmd)
- tschCmdInit()
- rootCmd.AddCommand(tschCmd)
- wmiCmdInit()
- rootCmd.AddCommand(wmiCmd)
- dcomCmdInit()
- rootCmd.AddCommand(dcomCmd)
+ // Auth init
+ {
+ gssapi.AddMechanism(ssp.SPNEGO)
+ gssapi.AddMechanism(ssp.NTLM)
+ gssapi.AddMechanism(ssp.KRB5)
+
+ authOpts = &adauth.Options{Debug: log.Debug().Msgf}
+ authOpts.RegisterFlags(rootCmd.PersistentFlags())
+ }
}
func Execute() {
@@ -124,4 +107,5 @@ func Execute() {
fmt.Println(err)
os.Exit(1)
}
+ os.Exit(returnCode)
}
diff --git a/cmd/rpc.go b/cmd/rpc.go
deleted file mode 100644
index cfc7a9d..0000000
--- a/cmd/rpc.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package cmd
-
-import (
- "fmt"
- "github.com/FalconOpsLLC/goexec/internal/client/dce"
- "github.com/oiweiwei/go-msrpc/dcerpc"
- "github.com/spf13/cobra"
- "github.com/spf13/pflag"
- "golang.org/x/net/proxy"
- "regexp"
-)
-
-func needsRpcTarget(proto string) func(cmd *cobra.Command, args []string) error {
- return func(cmd *cobra.Command, args []string) (err error) {
-
- if err = needsTarget(proto)(cmd, args); err != nil {
- return err
- }
- if proxyUrl != nil {
- if netDialer, err := proxy.FromURL(proxyUrl, nil); err != nil {
- return fmt.Errorf("proxy dialer from URL: %w", err)
- } else if dceDialer, ok := netDialer.(dcerpc.Dialer); !ok {
- return fmt.Errorf("failed to cast %T to dcerpc.Dialer", netDialer)
- } else {
- dceConfig.Options = append(dceConfig.Options, dcerpc.WithDialer(dceDialer))
- }
- }
- if argDceStringBinding != "" {
- dceConfig.Endpoint, err = dcerpc.ParseStringBinding(argDceStringBinding)
- if err != nil {
- return fmt.Errorf("failed to parse RPC endpoint: %w", err)
- }
- dceConfig.NoEpm = true // If an explicit endpoint is set, don't use EPM
-
- } else if argDceEpmFilter != "" {
- // This ensures that filters like "ncacn_ip_tcp" will be made into a valid binding (i.e. "ncacn_ip_tcp:")
- if ok, err := regexp.MatchString(`^\w+$`, argDceEpmFilter); err == nil && ok {
- argDceEpmFilter += ":"
- }
- dceConfig.EpmFilter, err = dcerpc.ParseStringBinding(argDceEpmFilter)
- if err != nil {
- return fmt.Errorf("failed to parse EPM filter: %w", err)
- }
- }
- if hostname != "" {
- dceConfig.DceOptions = append(dceConfig.DceOptions, dcerpc.WithTargetName(fmt.Sprintf("%s/%s", proto, hostname)))
- }
- if !argDceNoSign {
- dceConfig.DceOptions = append(dceConfig.DceOptions, dcerpc.WithSign())
- dceConfig.EpmOptions = append(dceConfig.EpmOptions, dcerpc.WithSign())
- }
- if argDceNoSeal {
- dceConfig.DceOptions = append(dceConfig.DceOptions, dcerpc.WithInsecure())
- } else {
- dceConfig.DceOptions = append(dceConfig.DceOptions, dcerpc.WithSeal(), dcerpc.WithSecurityLevel(dcerpc.AuthLevelPktPrivacy))
- dceConfig.EpmOptions = append(dceConfig.EpmOptions, dcerpc.WithSeal(), dcerpc.WithSecurityLevel(dcerpc.AuthLevelPktPrivacy))
- }
- return nil
- }
-}
-
-var (
- // DCE arguments
- argDceStringBinding string
- argDceEpmFilter string
- argDceNoSeal bool
- argDceNoSign bool
-
- // DCE options
- dceStringBinding *dcerpc.StringBinding
- dceConfig dce.ConnectionMethodDCEConfig
-)
-
-func registerRpcFlags(cmd *cobra.Command) {
- rpcFlags := pflag.NewFlagSet("RPC", pflag.ExitOnError)
- rpcFlags.BoolVar(&dceConfig.NoEpm, "no-epm", false, "Do not use EPM to automatically detect endpoints")
- rpcFlags.BoolVar(&dceConfig.EpmAuto, "epm-auto", false, "Automatically detect endpoints instead of using the module defaults")
- rpcFlags.BoolVar(&argDceNoSign, "no-sign", false, "Disable signing on DCE messages")
- rpcFlags.BoolVar(&argDceNoSeal, "no-seal", false, "Disable packet stub encryption on DCE messages")
- rpcFlags.StringVarP(&argDceEpmFilter, "epm-filter", "F", "", "String binding to filter endpoints returned by EPM")
- rpcFlags.StringVar(&argDceStringBinding, "endpoint", "", "Explicit RPC endpoint definition")
- cmd.PersistentFlags().AddFlagSet(rpcFlags)
-
- cmd.MarkFlagsMutuallyExclusive("endpoint", "epm-filter")
- cmd.MarkFlagsMutuallyExclusive("no-epm", "epm-filter")
-}
diff --git a/cmd/scmr.go b/cmd/scmr.go
index 11e1379..08e23d7 100644
--- a/cmd/scmr.go
+++ b/cmd/scmr.go
@@ -1,14 +1,12 @@
package cmd
import (
- "github.com/FalconOpsLLC/goexec/internal/exec"
+ "context"
"github.com/FalconOpsLLC/goexec/internal/util"
- "github.com/FalconOpsLLC/goexec/internal/windows"
- "github.com/RedTeamPentesting/adauth"
- "github.com/oiweiwei/go-msrpc/dcerpc"
+ "github.com/oiweiwei/go-msrpc/ssp/gssapi"
"github.com/spf13/cobra"
- scmrexec "github.com/FalconOpsLLC/goexec/internal/exec/scmr"
+ scmrexec "github.com/FalconOpsLLC/goexec/pkg/goexec/scmr"
)
func scmrCmdInit() {
@@ -22,53 +20,55 @@ func scmrCmdInit() {
}
func scmrCreateCmdInit() {
- scmrCreateCmd.Flags().StringVarP(&scmrDisplayName, "display-name", "n", "", "Display name of service to create")
- scmrCreateCmd.Flags().StringVarP(&scmrServiceName, "service-name", "s", "", "Name of service to create")
- scmrCreateCmd.Flags().BoolVar(&scmrNoDelete, "no-delete", false, "Don't delete service after execution")
- scmrCreateCmd.Flags().BoolVar(&scmrNoStart, "no-start", false, "Don't start service")
- scmrCreateCmd.Flags().StringVarP(&executablePath, "executable-path", "f", "", "Full path to a remote Windows executable file")
- scmrCreateCmd.Flags().StringVarP(&executableArgs, "args", "a", "", "Arguments to pass to the executable")
- scmrCreateCmd.Flags().BoolVarP(&scmrOutput, "output", "O", false, "Fetch program output")
+ scmrCreateCmd.Flags().StringVarP(&scmrCreate.DisplayName, "display-name", "n", "", "Display name of service to create")
+ scmrCreateCmd.Flags().StringVarP(&scmrCreate.ServiceName, "service-name", "s", "", "Name of service to create")
+ scmrCreateCmd.Flags().BoolVar(&scmrCreate.NoDelete, "no-delete", false, "Don't delete service after execution")
+ scmrCreateCmd.Flags().BoolVar(&scmrCreate.NoStart, "no-start", false, "Don't start service")
+
+ scmrCreateCmd.Flags().StringVarP(&exec.Input.ExecutablePath, "executable-path", "f", "", "Full path to a remote Windows executable")
+ scmrCreateCmd.Flags().StringVarP(&exec.Input.Arguments, "args", "a", "", "Arguments to pass to the executable")
+
+ scmrCreateCmd.MarkFlagsMutuallyExclusive("no-delete", "no-start")
+
if err := scmrCreateCmd.MarkFlagRequired("executable-path"); err != nil {
panic(err)
}
}
func scmrChangeCmdInit() {
- scmrChangeCmd.Flags().StringVarP(&scmrDisplayName, "display-name", "n", "", "Display name of service to create")
- scmrChangeCmd.Flags().BoolVar(&scmrNoStart, "no-start", false, "Don't start service")
- scmrChangeCmd.Flags().StringVarP(&scmrServiceName, "service-name", "s", "", "Name of service to modify")
- scmrChangeCmd.Flags().StringVarP(&executablePath, "executable-path", "f", "", "Full path to remote Windows executable")
- scmrChangeCmd.Flags().StringVarP(&executableArgs, "args", "a", "", "Arguments to pass to executable")
+ scmrChangeCmd.Flags().BoolVar(&scmrChange.NoStart, "no-start", false, "Don't start service")
+ scmrChangeCmd.Flags().StringVarP(&scmrChange.ServiceName, "service-name", "s", "", "Name of service to modify")
+
+ scmrChangeCmd.Flags().StringVarP(&exec.Input.ExecutablePath, "executable-path", "f", "", "Full path to remote Windows executable")
+ scmrChangeCmd.Flags().StringVarP(&exec.Input.Arguments, "args", "a", "", "Arguments to pass to executable")
+
if err := scmrChangeCmd.MarkFlagRequired("service-name"); err != nil {
panic(err)
}
+ if err := scmrCreateCmd.MarkFlagRequired("executable-path"); err != nil {
+ panic(err)
+ }
}
func scmrDeleteCmdInit() {
- scmrDeleteCmd.Flags().StringArrayVarP(&scmrServiceNames, "service-name", "s", scmrServiceNames, "Name of service(s) to delete")
+ scmrDeleteCmd.Flags().StringVarP(&scmrDelete.ServiceName, "service-name", "s", scmrDelete.ServiceName, "Name of service to delete")
+
if err := scmrDeleteCmd.MarkFlagRequired("service-name"); err != nil {
panic(err)
}
}
var (
- // scmr arguments
- scmrServiceName string
- scmrServiceNames []string
- scmrDisplayName string
- scmrNoDelete bool
- scmrNoStart bool
- scmrOutput bool
-
- creds *adauth.Credential
- target *adauth.Target
+ scmrCreate scmrexec.ScmrCreate
+ scmrChange scmrexec.ScmrChange
+ scmrDelete scmrexec.ScmrDelete
scmrCmd = &cobra.Command{
Use: "scmr",
Short: "Establish execution via SCMR",
Args: cobra.NoArgs,
}
+
scmrCreateCmd = &cobra.Command{
Use: "create [target]",
Short: "Create & run a new Windows service to gain execution",
@@ -79,138 +79,145 @@ var (
References:
https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/6a8ca926-9477-4dd4-b766-692fab07227e
`,
- Args: needs(needsTarget("host"), needsRpcTarget("host")),
+ Args: argsRpcClient("cifs"),
+
Run: func(cmd *cobra.Command, args []string) {
+ var err error
+
+ ctx := gssapi.NewSecurityContext(context.Background())
- if scmrServiceName == "" {
- log.Warn().Msg("No service name was specified, using random string")
- scmrServiceName = util.RandomString()
+ ctx = log.With().
+ Str("module", "scmr").
+ Str("method", "create").
+ Logger().
+ WithContext(ctx)
+
+ if scmrCreate.ServiceName == "" {
+ log.Warn().Msg("No service name was provided. Using a random string")
+ scmrCreate.ServiceName = util.RandomString()
}
- if scmrNoDelete {
+
+ if scmrCreate.NoDelete {
log.Warn().Msg("Service will not be deleted after execution")
}
- if scmrDisplayName == "" {
+
+ if scmrCreate.DisplayName == "" {
log.Debug().Msg("No display name specified, using service name as display name")
- scmrDisplayName = scmrServiceName
+ scmrCreate.DisplayName = scmrCreate.ServiceName
}
- executor := scmrexec.Module{}
- cleanCfg := &exec.CleanupConfig{
- CleanupMethod: scmrexec.CleanupMethodDelete,
- CleanupMethodConfig: scmrexec.CleanupMethodDeleteConfig{},
- }
- connCfg := &exec.ConnectionConfig{
- ConnectionMethod: exec.ConnectionMethodDCE,
- ConnectionMethodConfig: dceConfig,
- }
- execCfg := &exec.ExecutionConfig{
- ExecutablePath: executablePath,
- ExecutableArgs: executableArgs,
- ReturnOutput: scmrOutput,
- ExecutionMethod: scmrexec.MethodCreate,
-
- ExecutionMethodConfig: scmrexec.MethodCreateConfig{
- NoDelete: scmrNoDelete,
- ServiceName: util.RandomStringIfBlank(scmrServiceName),
- DisplayName: scmrDisplayName,
- ServiceType: windows.SERVICE_WIN32_OWN_PROCESS,
- StartType: windows.SERVICE_DEMAND_START,
- },
- }
- ctx = log.With().
- Str("module", "scmr").
- Str("method", "create").
- Logger().WithContext(ctx)
-
- if err := executor.Connect(ctx, creds, target, connCfg); err != nil {
+ if err = rpcClient.Connect(ctx); err != nil {
log.Fatal().Err(err).Msg("Connection failed")
}
- if !scmrNoDelete {
- defer func() {
- if err := executor.Cleanup(ctx, cleanCfg); err != nil {
- log.Error().Err(err).Msg("Cleanup failed")
- }
- }()
+
+ defer func() {
+ closeErr := rpcClient.Close(ctx)
+ if closeErr != nil {
+ log.Error().Err(closeErr).Msg("Failed to close connection")
+ }
+ }()
+
+ defer func() {
+ cleanErr := scmrCreate.Clean(ctx)
+ if cleanErr != nil {
+ log.Warn().Err(cleanErr).Msg("Clean operation failed")
+ }
+ }()
+
+ if err = scmrCreate.Init(ctx, &rpcClient); err != nil {
+ log.Error().Err(err).Msg("Module initialization failed")
+ returnCode = 2
+ return
}
- if err := executor.Exec(ctx, execCfg); err != nil {
+
+ if err = scmrCreate.Execute(ctx, exec.Input); err != nil {
log.Error().Err(err).Msg("Execution failed")
+ returnCode = 4
}
},
}
+
scmrChangeCmd = &cobra.Command{
Use: "change [target]",
Short: "Change an existing Windows service to gain execution",
- Args: needs(needsTarget("host"), needsRpcTarget("host")),
+ Args: argsRpcClient("cifs"),
Run: func(cmd *cobra.Command, args []string) {
+ var err error
+
+ ctx := gssapi.NewSecurityContext(context.Background())
- executor := scmrexec.Module{}
- cleanCfg := &exec.CleanupConfig{
- CleanupMethod: scmrexec.CleanupMethodRevert,
- CleanupMethodConfig: scmrexec.CleanupMethodRevertConfig{},
- }
- connCfg := &exec.ConnectionConfig{
- ConnectionMethod: exec.ConnectionMethodDCE,
- ConnectionMethodConfig: dceConfig,
- }
- execCfg := &exec.ExecutionConfig{
- ExecutablePath: executablePath,
- ExecutableArgs: executableArgs,
- ExecutionMethod: scmrexec.MethodChange,
-
- ExecutionMethodConfig: scmrexec.MethodChangeConfig{
- NoStart: scmrNoStart,
- ServiceName: scmrServiceName,
- },
- }
ctx = log.With().
Str("module", "scmr").
Str("method", "change").
- Logger().WithContext(ctx)
+ Logger().
+ WithContext(ctx)
- if err := executor.Connect(ctx, creds, target, connCfg); err != nil {
+ if err = rpcClient.Connect(ctx); err != nil {
log.Fatal().Err(err).Msg("Connection failed")
}
- if !scmrNoDelete {
- defer func() {
- if err := executor.Cleanup(ctx, cleanCfg); err != nil {
- log.Error().Err(err).Msg("Cleanup failed")
- }
- }()
+
+ defer func() {
+ closeErr := rpcClient.Close(ctx)
+ if closeErr != nil {
+ log.Error().Err(closeErr).Msg("Failed to close connection")
+ }
+ }()
+
+ defer func() {
+ cleanErr := scmrChange.Clean(ctx)
+ if cleanErr != nil {
+ log.Warn().Err(cleanErr).Msg("Clean operation failed")
+ }
+ }()
+
+ if err = scmrChange.Init(ctx, &rpcClient); err != nil {
+ log.Error().Err(err).Msg("Module initialization failed")
+ returnCode = 2
+ return
}
- if err := executor.Exec(ctx, execCfg); err != nil {
+
+ if err = scmrChange.Execute(ctx, exec.Input); err != nil {
log.Error().Err(err).Msg("Execution failed")
+ returnCode = 4
}
},
}
scmrDeleteCmd = &cobra.Command{
Use: "delete [target]",
Short: "Delete an existing Windows service",
- Long: `Description:
-TODO
-`,
- Args: needs(needsTarget("host"), needsRpcTarget("host")),
+ Long: `TODO`,
+
+ Args: argsRpcClient("cifs"),
Run: func(cmd *cobra.Command, args []string) {
- dceConfig.DceOptions = append(dceConfig.DceOptions, dcerpc.WithInsecure())
+ var err error
+
+ ctx := gssapi.NewSecurityContext(context.Background())
- executor := scmrexec.Module{}
- cleanCfg := &exec.CleanupConfig{
- CleanupMethod: scmrexec.CleanupMethodDelete,
- CleanupMethodConfig: scmrexec.CleanupMethodDeleteConfig{ServiceNames: scmrServiceNames},
- }
- connCfg := &exec.ConnectionConfig{
- ConnectionMethod: exec.ConnectionMethodDCE,
- ConnectionMethodConfig: dceConfig,
- }
ctx = log.With().
Str("module", "scmr").
Str("method", "delete").
- Logger().WithContext(ctx)
+ Logger().
+ WithContext(ctx)
- if err := executor.Connect(ctx, creds, target, connCfg); err != nil {
+ if err = rpcClient.Connect(ctx); err != nil {
log.Fatal().Err(err).Msg("Connection failed")
+ }
+
+ defer func() {
+ closeErr := rpcClient.Close(ctx)
+ if closeErr != nil {
+ log.Error().Err(closeErr).Msg("Failed to close connection")
+ }
+ }()
+
+ if err = scmrDelete.Init(ctx, &rpcClient); err != nil {
+ log.Error().Err(err).Msg("Module initialization failed")
+ returnCode = 2
+ }
- } else if err = executor.Cleanup(ctx, cleanCfg); err != nil {
- log.Fatal().Err(err).Msg("Delete failed")
+ if err = scmrDelete.Clean(ctx); err != nil {
+ log.Warn().Err(err).Msg("Clean failed")
+ returnCode = 4
}
},
}
diff --git a/cmd/tsch.go b/cmd/tsch.go
index f377d56..7c0fdde 100644
--- a/cmd/tsch.go
+++ b/cmd/tsch.go
@@ -1,154 +1,89 @@
package cmd
import (
- "fmt"
- "github.com/FalconOpsLLC/goexec/internal/exec"
- "github.com/FalconOpsLLC/goexec/internal/exec/tsch"
+ "context"
+ "github.com/FalconOpsLLC/goexec/internal/util"
+ "github.com/FalconOpsLLC/goexec/pkg/goexec"
+ tschexec "github.com/FalconOpsLLC/goexec/pkg/goexec/tsch"
+ "github.com/oiweiwei/go-msrpc/ssp/gssapi"
"github.com/spf13/cobra"
- "regexp"
+ "io"
+ "os"
"time"
)
func tschCmdInit() {
registerRpcFlags(tschCmd)
- tschDeleteCmdInit()
- tschCmd.AddCommand(tschDeleteCmd)
- tschRegisterCmdInit()
- tschCmd.AddCommand(tschRegisterCmd)
tschDemandCmdInit()
tschCmd.AddCommand(tschDemandCmd)
+
+ tschCreateCmdInit()
+ tschCmd.AddCommand(tschCreateCmd)
}
-func tschDeleteCmdInit() {
- tschDeleteCmd.Flags().StringVarP(&tschTaskPath, "path", "t", "", "Scheduled task path")
- if err := tschDeleteCmd.MarkFlagRequired("path"); err != nil {
- panic(err)
+func argsTschTaskIdentifiers(name, path string) error {
+ switch {
+ case path != "":
+ return tschexec.ValidateTaskPath(path)
+ case name != "":
+ return tschexec.ValidateTaskName(name)
+ default:
}
+ return nil
}
-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(&tschTaskName, "name", "n", "", "Target task name")
- tschDemandCmd.Flags().BoolVar(&tschNoDelete, "no-delete", false, "Don't delete task after execution")
- tschDemandCmd.Flags().Uint32Var(&tschSessionId, "session-id", 0, "Hijack existing session")
- if err := tschDemandCmd.MarkFlagRequired("executable"); err != nil {
- panic(err)
- }
+func argsTschDemand(_ *cobra.Command, _ []string) error {
+ return argsTschTaskIdentifiers(tschDemand.TaskName, tschDemand.TaskPath)
}
-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(&tschTaskName, "name", "n", "", "Target task name")
- tschRegisterCmd.Flags().DurationVar(&tschStopDelay, "delay-stop", 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", 5*time.Second, "Delay between task registration and execution")
- tschRegisterCmd.Flags().DurationVarP(&tschDeleteDelay, "delay-delete", "D", 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")
-
- if err := tschRegisterCmd.MarkFlagRequired("executable"); err != nil {
- panic(err)
- }
+func argsTschCreate(_ *cobra.Command, _ []string) error {
+ return argsTschTaskIdentifiers(tschCreate.TaskName, tschCreate.TaskPath)
}
-func tschArgs(principal string) func(cmd *cobra.Command, args []string) error {
- return func(cmd *cobra.Command, args []string) error {
- if tschTaskPath != "" && !tschTaskPathRegex.MatchString(tschTaskPath) {
- return fmt.Errorf("invalid task path: %s", tschTaskPath)
- }
- if tschTaskName != "" {
- if !tschTaskNameRegex.MatchString(tschTaskName) {
- return fmt.Errorf("invalid task name: %s", tschTaskName)
-
- } else if tschTaskPath == "" {
- tschTaskPath = `\` + tschTaskName
- }
- }
- return needsRpcTarget(principal)(cmd, args)
- }
+func tschDemandCmdInit() {
+ tschDemandCmd.Flags().StringVarP(&tschDemand.TaskName, "name", "t", "", "Name of task to register")
+ tschDemandCmd.Flags().StringVarP(&tschDemand.TaskPath, "path", "P", "", "Path of task to register")
+ tschDemandCmd.Flags().Uint32Var(&tschDemand.SessionId, "session", 0, "Hijack existing session given the session ID")
+ tschDemandCmd.Flags().BoolVar(&tschDemand.NoDelete, "no-delete", false, "Don't delete task after execution")
+ tschDemandCmd.Flags().StringVar(&tschDemand.UserSid, "sid", "S-1-5-18", "User SID to impersonate")
+
+ registerProcessExecutionArgs(tschDemandCmd)
+ registerExecutionOutputArgs(tschDemandCmd)
+
+ tschDemandCmd.MarkFlagsMutuallyExclusive("name", "path")
+}
+
+func tschCreateCmdInit() {
+ tschCreateCmd.Flags().StringVarP(&tschCreate.TaskName, "name", "t", "", "Name of task to register")
+ tschCreateCmd.Flags().StringVarP(&tschCreate.TaskPath, "path", "P", "", "Path of task to register")
+ tschCreateCmd.Flags().DurationVar(&tschCreate.StopDelay, "delay-stop", 5*time.Second, "Delay between task execution and termination. This will not stop the process spawned by the task")
+ tschCreateCmd.Flags().DurationVar(&tschCreate.StartDelay, "start-delay", 5*time.Second, "Delay between task registration and execution")
+ tschCreateCmd.Flags().DurationVar(&tschCreate.DeleteDelay, "delete-delay", 0*time.Second, "Delay between task termination and deletion")
+ tschCreateCmd.Flags().BoolVar(&tschCreate.NoDelete, "no-delete", false, "Don't delete task after execution")
+ tschCreateCmd.Flags().BoolVar(&tschCreate.CallDelete, "call-delete", false, "Directly call SchRpcDelete to delete task")
+ tschCreateCmd.Flags().StringVar(&tschCreate.UserSid, "sid", "S-1-5-18", "User SID to impersonate")
+
+ registerProcessExecutionArgs(tschCreateCmd)
+
+ tschCreateCmd.MarkFlagsMutuallyExclusive("name", "path")
}
var (
- tschSessionId uint32
- tschNoDelete bool
- tschCallDelete bool
- tschDeleteDelay time.Duration
- tschStopDelay time.Duration
- tschDelay time.Duration
- tschTaskName string
- tschTaskPath string
-
- tschTaskPathRegex = regexp.MustCompile(`^\\[^ :/\\][^:/]*$`)
- tschTaskNameRegex = regexp.MustCompile(`^[^ :/\\][^:/\\]*$`)
+ tschDemand tschexec.TschDemand
+ tschCreate tschexec.TschCreate
tschCmd = &cobra.Command{
Use: "tsch",
- Short: "Establish execution via TSCH",
+ Short: "Establish execution via Windows Task Scheduler (MS-TSCH)",
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: tschArgs("cifs"),
- Run: func(cmd *cobra.Command, args []string) {
-
- log = log.With().
- Str("module", "tsch").
- Str("method", "register").
- Logger()
- if tschNoDelete {
- log.Warn().Msg("Task will not be deleted after execution")
- }
-
- module := tschexec.Module{}
- connCfg := &exec.ConnectionConfig{
- ConnectionMethod: exec.ConnectionMethodDCE,
- ConnectionMethodConfig: dceConfig,
- }
- execCfg := &exec.ExecutionConfig{
- ExecutableName: executable,
- ExecutableArgs: executableArgs,
- ExecutionMethod: tschexec.MethodRegister,
-
- ExecutionMethodConfig: tschexec.MethodRegisterConfig{
- NoDelete: tschNoDelete,
- CallDelete: tschCallDelete,
- StartDelay: tschDelay,
- StopDelay: tschStopDelay,
- DeleteDelay: tschDeleteDelay,
- TaskPath: tschTaskPath,
- },
- }
- if err := module.Connect(log.WithContext(ctx), creds, target, connCfg); err != nil {
- log.Fatal().Err(err).Msg("Connection failed")
- } else if err = module.Exec(log.WithContext(ctx), execCfg); err != nil {
- log.Fatal().Err(err).Msg("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,
+ Similar to the create 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.
@@ -156,68 +91,110 @@ 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: tschArgs("cifs"),
+ Args: args(
+ argsRpcClient("cifs"),
+ argsSmbClient(),
+ argsTschDemand,
+ ),
+
Run: func(cmd *cobra.Command, args []string) {
+ var err error
+
+ tschDemand.Client = &rpcClient
+ tschDemand.IO = exec
- log = log.With().
- Str("module", "tsch").
- Str("method", "register").
- Logger()
- if tschNoDelete {
- log.Warn().Msg("Task will not be deleted after execution")
+ if tschDemand.TaskName == "" && tschDemand.TaskPath == "" {
+ tschDemand.TaskPath = `\` + util.RandomString()
}
- module := tschexec.Module{}
- connCfg := &exec.ConnectionConfig{
- ConnectionMethod: exec.ConnectionMethodDCE,
- ConnectionMethodConfig: dceConfig,
+
+ ctx := log.WithContext(gssapi.NewSecurityContext(context.TODO()))
+
+ var writer io.WriteCloser
+
+ if outputPath == "-" {
+ writer = os.Stdout
+
+ } else if outputPath != "" {
+
+ if writer, err = os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE, 0644); err != nil {
+ log.Fatal().Err(err).Msg("Failed to open output file")
+ }
+ defer writer.Close()
}
- execCfg := &exec.ExecutionConfig{
- ExecutableName: executable,
- ExecutableArgs: executableArgs,
- ExecutionMethod: tschexec.MethodDemand,
-
- ExecutionMethodConfig: tschexec.MethodDemandConfig{
- NoDelete: tschNoDelete,
- TaskPath: tschTaskPath,
- SessionId: tschSessionId,
- },
+
+ if err = goexec.ExecuteCleanMethod(ctx, &tschDemand, &exec); err != nil {
+ log.Fatal().Err(err).Msg("Operation failed")
}
- if err := module.Connect(log.WithContext(ctx), creds, target, connCfg); err != nil {
- log.Fatal().Err(err).Msg("Connection failed")
- } else if err = module.Exec(log.WithContext(ctx), execCfg); err != nil {
- log.Fatal().Err(err).Msg("Execution failed")
+
+ if outputPath != "" {
+ if reader, err := tschDemand.GetOutput(ctx); err == nil {
+ _, err = io.Copy(writer, reader)
+
+ } else {
+ log.Error().Err(err).Msg("Failed to get process execution output")
+ returnCode = 2
+ }
}
},
}
- tschDeleteCmd = &cobra.Command{
- Use: "delete [target]",
- Short: "Manually delete a scheduled task",
+ tschCreateCmd = &cobra.Command{
+ Use: "create [target]",
+ Short: "Create a remote scheduled task with an automatic start time",
Long: `Description:
- The delete method manually deletes a scheduled task by calling SchRpcDelete
+ The create 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: tschArgs("cifs"),
+ Args: args(
+ argsRpcClient("cifs"),
+ argsSmbClient(),
+ argsTschCreate,
+ ),
+
Run: func(cmd *cobra.Command, args []string) {
- log = log.With().
- Str("module", "tsch").
- Str("method", "delete").
- Logger()
-
- module := tschexec.Module{}
- connCfg := &exec.ConnectionConfig{
- ConnectionMethod: exec.ConnectionMethodDCE,
- ConnectionMethodConfig: dceConfig,
+ var err error
+
+ tschCreate.Tsch.Client = &rpcClient
+ tschCreate.IO = exec
+
+ if tschCreate.TaskName == "" && tschDemand.TaskPath == "" {
+ tschCreate.TaskPath = `\` + util.RandomString()
}
- cleanCfg := &exec.CleanupConfig{
- CleanupMethod: tschexec.MethodDelete,
- CleanupMethodConfig: tschexec.MethodDeleteConfig{TaskPath: tschTaskPath},
+
+ ctx := log.WithContext(gssapi.NewSecurityContext(context.TODO()))
+
+ var writer io.WriteCloser
+
+ if outputPath == "-" {
+ writer = os.Stdout
+
+ } else if outputPath != "" {
+
+ if writer, err = os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE, 0644); err != nil {
+ log.Fatal().Err(err).Msg("Failed to open output file")
+ }
+ defer writer.Close()
}
- if err := module.Connect(log.WithContext(ctx), creds, target, connCfg); err != nil {
- log.Fatal().Err(err).Msg("Connection failed")
- } else if err := module.Cleanup(log.WithContext(ctx), cleanCfg); err != nil {
- log.Fatal().Err(err).Msg("Cleanup failed")
+
+ if err = goexec.ExecuteCleanMethod(ctx, &tschDemand, &exec); err != nil {
+ log.Fatal().Err(err).Msg("Operation failed")
+ }
+
+ if outputPath != "" {
+ if reader, err := tschDemand.GetOutput(ctx); err == nil {
+ _, err = io.Copy(writer, reader)
+
+ } else {
+ log.Error().Err(err).Msg("Failed to get process execution output")
+ returnCode = 2
+ }
}
},
}
diff --git a/cmd/wmi.go b/cmd/wmi.go
index 59096ec..196ff82 100644
--- a/cmd/wmi.go
+++ b/cmd/wmi.go
@@ -1,52 +1,65 @@
package cmd
import (
+ "context"
"encoding/json"
"fmt"
- "github.com/FalconOpsLLC/goexec/internal/exec"
- wmiexec "github.com/FalconOpsLLC/goexec/internal/exec/wmi"
+ "github.com/FalconOpsLLC/goexec/pkg/goexec"
+ wmiexec "github.com/FalconOpsLLC/goexec/pkg/goexec/wmi"
+ "github.com/oiweiwei/go-msrpc/ssp/gssapi"
"github.com/spf13/cobra"
+ "io"
+ "os"
)
func wmiCmdInit() {
registerRpcFlags(wmiCmd)
+
wmiCallCmdInit()
wmiCmd.AddCommand(wmiCallCmd)
- wmiProcessCmdInit()
- wmiCmd.AddCommand(wmiProcessCmd)
+
+ wmiProcCmdInit()
+ wmiCmd.AddCommand(wmiProcCmd)
+}
+
+func wmiCallArgs(_ *cobra.Command, _ []string) error {
+ return json.Unmarshal([]byte(wmiArguments), &wmiCall.Args)
}
func wmiCallCmdInit() {
- wmiCallCmd.Flags().StringVarP(&dceConfig.Resource, "namespace", "n", "//./root/cimv2", "WMI namespace")
- wmiCallCmd.Flags().StringVarP(&wmi.Class, "class", "C", "", `WMI class to instantiate (i.e. "Win32_Process")`)
- wmiCallCmd.Flags().StringVarP(&wmi.Method, "method", "m", "", `WMI Method to call (i.e. "Create")`)
- wmiCallCmd.Flags().StringVarP(&wmi.Args, "args", "A", "{}", `WMI Method argument(s) in JSON dictionary format (i.e. {"CommandLine":"calc.exe"})`)
+ wmiCallCmd.Flags().StringVarP(&wmiCall.Resource, "namespace", "n", "//./root/cimv2", "WMI namespace")
+ wmiCallCmd.Flags().StringVarP(&wmiCall.Class, "class", "C", "", `WMI class to instantiate (i.e. "Win32_Process")`)
+ wmiCallCmd.Flags().StringVarP(&wmiCall.Method, "method", "m", "", `WMI Method to call (i.e. "Create")`)
+ wmiCallCmd.Flags().StringVarP(&wmiArguments, "args", "A", "{}", `WMI Method argument(s) in JSON dictionary format (i.e. {"CommandLine":"calc.exe"})`)
+
+ if err := wmiCallCmd.MarkFlagRequired("class"); err != nil {
+ panic(err)
+ }
if err := wmiCallCmd.MarkFlagRequired("method"); err != nil {
panic(err)
}
}
-func wmiProcessCmdInit() {
- wmiProcessCmd.Flags().StringVarP(&command, "command", "c", "", "Process command line")
- wmiProcessCmd.Flags().StringVarP(&workingDirectory, "directory", "d", `C:\`, "Working directory")
- if err := wmiProcessCmd.MarkFlagRequired("command"); err != nil {
- panic(err)
- }
+func wmiProcCmdInit() {
+ wmiProcCmd.Flags().StringVarP(&wmiProc.Resource, "namespace", "n", "//./root/cimv2", "WMI namespace")
+ wmiProcCmd.Flags().StringVarP(&wmiProc.WorkingDirectory, "directory", "d", `C:\`, "Working directory")
+
+ registerProcessExecutionArgs(wmiProcCmd)
+ registerExecutionOutputArgs(wmiProcCmd)
}
var (
- wmi struct {
- Class string
- Method string
- Args string
- }
- wmiMethodArgsMap map[string]any
+ wmiCall = wmiexec.WmiCall{}
+ wmiProc = wmiexec.WmiProc{}
+
+ wmiArguments string
wmiCmd = &cobra.Command{
Use: "wmi",
- Short: "Establish execution via WMI",
+ Short: "Establish execution via wmi",
Args: cobra.NoArgs,
}
+
wmiCallCmd = &cobra.Command{
Use: "call",
Short: "Execute specified WMI method",
@@ -56,52 +69,48 @@ var (
References:
https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-classes
- `,
- Args: needs(needsTarget("cifs"), needsRpcTarget("cifs"), func(cmd *cobra.Command, args []string) (err error) {
- if err = json.Unmarshal([]byte(wmi.Args), &wmiMethodArgsMap); err != nil {
- err = fmt.Errorf("parse JSON arguments: %w", err)
- }
- return
- }),
+`,
+ Args: args(argsRpcClient("host"), wmiCallArgs),
+
Run: func(cmd *cobra.Command, args []string) {
- executor := wmiexec.Module{}
- cleanCfg := &exec.CleanupConfig{} // TODO
- connCfg := &exec.ConnectionConfig{
- ConnectionMethod: exec.ConnectionMethodDCE,
- ConnectionMethodConfig: dceConfig,
- }
+ var err error
- execCfg := &exec.ExecutionConfig{
- ExecutableName: executable,
- ExecutableArgs: executableArgs,
- ExecutionMethod: wmiexec.MethodCall,
- ExecutionMethodConfig: wmiexec.MethodCallConfig{
- Class: wmi.Class,
- Method: wmi.Method,
- Arguments: wmiMethodArgsMap,
- },
- }
+ ctx := gssapi.NewSecurityContext(context.Background())
ctx = log.With().
Str("module", "wmi").
- Str("method", "proc").
- Logger().WithContext(ctx)
+ Str("method", "call").
+ Logger().
+ WithContext(ctx)
- if err := executor.Connect(ctx, creds, target, connCfg); err != nil {
+ if err = rpcClient.Connect(ctx); err != nil {
log.Fatal().Err(err).Msg("Connection failed")
}
+
defer func() {
- if err := executor.Cleanup(ctx, cleanCfg); err != nil {
- log.Error().Err(err).Msg("Cleanup failed")
+ closeErr := rpcClient.Close(ctx)
+ if closeErr != nil {
+ log.Error().Err(closeErr).Msg("Failed to close connection")
}
}()
- if err := executor.Exec(ctx, execCfg); err != nil {
- log.Error().Err(err).Msg("Execution failed")
+
+ if err = wmiCall.Init(ctx); err != nil {
+ log.Error().Err(err).Msg("Module initialization failed")
+ returnCode = 2
+ return
}
+
+ out, err := wmiCall.Call(ctx)
+ if err != nil {
+ log.Error().Err(err).Msg("Call failed")
+ returnCode = 4
+ return
+ }
+ fmt.Println(string(out))
},
}
- wmiProcessCmd = &cobra.Command{
+ wmiProcCmd = &cobra.Command{
Use: "proc",
Short: "Start a Windows process",
Long: `Description:
@@ -112,41 +121,41 @@ References:
References:
https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-process
`,
- Args: needs(needsTarget("cifs"), needsRpcTarget("cifs")),
+ Args: args(argsOutput("smb"), argsRpcClient("host")),
+
Run: func(cmd *cobra.Command, args []string) {
+ var err error
+
+ wmiProc.Client = &rpcClient
+ wmiProc.IO = exec
+
+ ctx := log.WithContext(gssapi.NewSecurityContext(context.TODO()))
+
+ var writer io.WriteCloser
+
+ if outputPath == "-" {
+ writer = os.Stdout
- executor := wmiexec.Module{}
- cleanCfg := &exec.CleanupConfig{} // TODO
- connCfg := &exec.ConnectionConfig{
- ConnectionMethod: exec.ConnectionMethodDCE,
- ConnectionMethodConfig: dceConfig,
+ } else if outputPath != "" {
+
+ if writer, err = os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE, 0644); err != nil {
+ log.Fatal().Err(err).Msg("Failed to open output file")
+ }
+ defer writer.Close()
}
- execCfg := &exec.ExecutionConfig{
- ExecutableName: executable,
- ExecutableArgs: executableArgs,
- ExecutionMethod: wmiexec.MethodProcess,
-
- ExecutionMethodConfig: wmiexec.MethodProcessConfig{
- Command: command,
- WorkingDirectory: workingDirectory,
- },
+
+ if err = goexec.ExecuteCleanMethod(ctx, &wmiProc, &exec); err != nil {
+ log.Fatal().Err(err).Msg("Operation failed")
}
- ctx = log.With().
- Str("module", "wmi").
- Str("method", "proc").
- Logger().WithContext(ctx)
+ if outputPath != "" {
+ if reader, err := wmiProc.GetOutput(ctx); err == nil {
+ _, err = io.Copy(writer, reader)
- if err := executor.Connect(ctx, creds, target, connCfg); err != nil {
- log.Fatal().Err(err).Msg("Connection failed")
- }
- defer func() {
- if err := executor.Cleanup(ctx, cleanCfg); err != nil {
- log.Error().Err(err).Msg("Cleanup failed")
+ } else {
+ log.Error().Err(err).Msg("Failed to get process execution output")
+ returnCode = 2
}
- }()
- if err := executor.Exec(ctx, execCfg); err != nil {
- log.Error().Err(err).Msg("Execution failed")
}
},
}