diff options
author | Bryan McNulty <bryan@falconops.com> | 2025-04-26 15:27:23 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-26 15:27:23 -0500 |
commit | 27d183952ad83c871264b558d9bde405da553ec9 (patch) | |
tree | e9e80bc0daab7e636d3cb084ceb07c22cbc56467 | |
parent | bac8bd6865a4dc6ad6cbd791a0141d4e5d988007 (diff) | |
parent | aadf3819a559db8186ba92078dfc33d0f92e47ed (diff) | |
download | goexec-27d183952ad83c871264b558d9bde405da553ec9.tar.gz goexec-27d183952ad83c871264b558d9bde405da553ec9.zip |
Merge pull request #9 from FalconOpsLLC/dev
WMI module bug fixed, update actions, add remote error refs
-rw-r--r-- | .github/workflows/go.yml | 13 | ||||
-rw-r--r-- | cmd/args.go | 3 | ||||
-rw-r--r-- | cmd/dcom.go | 110 | ||||
-rw-r--r-- | cmd/root.go | 506 | ||||
-rw-r--r-- | cmd/tsch.go | 386 | ||||
-rw-r--r-- | pkg/goexec/dce/client.go | 3 | ||||
-rw-r--r-- | pkg/goexec/dcom/dcom.go | 44 | ||||
-rw-r--r-- | pkg/goexec/dcom/module.go | 247 | ||||
-rw-r--r-- | pkg/goexec/dcom/util.go | 125 | ||||
-rw-r--r-- | pkg/goexec/scmr/create.go | 219 | ||||
-rw-r--r-- | pkg/goexec/scmr/module.go | 3 | ||||
-rw-r--r-- | pkg/goexec/scmr/scmr.go | 8 | ||||
-rw-r--r-- | pkg/goexec/tsch/change.go | 219 | ||||
-rw-r--r-- | pkg/goexec/tsch/demand.go | 133 | ||||
-rw-r--r-- | pkg/goexec/tsch/module.go | 255 | ||||
-rw-r--r-- | pkg/goexec/tsch/task/action.go | 2 | ||||
-rw-r--r-- | pkg/goexec/tsch/tsch.go | 3 | ||||
-rw-r--r-- | pkg/goexec/wmi/module.go | 15 |
18 files changed, 1167 insertions, 1127 deletions
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 58cbddb..d66ea98 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,5 +1,6 @@ # This workflow will build a golang project # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go +# Adapted from https://github.com/RedTeamPentesting/adauth/blob/main/.github/workflows/check.yml :) name: Go @@ -20,6 +21,18 @@ jobs: with: go-version: ${{ matrix.go-version }} + - name: Lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.64 + args: --verbose --timeout 5m + + - name: Check go.mod + run: | + echo "check if go.mod is up to date" + go mod tidy + git diff --exit-code go.mod + - name: Build run: go build -v ./... diff --git a/cmd/args.go b/cmd/args.go index 9568395..40b9701 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -29,10 +29,13 @@ func registerNetworkFlags(fs *pflag.FlagSet) { //cmd.MarkFlagsMutuallyExclusive("no-epm", "epm-filter") } +// FUTURE: automatically stage & execute file +/* func registerStageFlags(fs *pflag.FlagSet) { fs.StringVarP(&stageFilePath, "stage", "E", "", "File to stage and execute") //fs.StringVarP(&stageArgs ...) } +*/ func registerExecutionFlags(fs *pflag.FlagSet) { fs.StringVarP(&exec.Input.Executable, "exec", "e", "", "Remote Windows executable to invoke") diff --git a/cmd/dcom.go b/cmd/dcom.go index 89fac18..1a8d031 100644 --- a/cmd/dcom.go +++ b/cmd/dcom.go @@ -1,80 +1,80 @@ package cmd import ( - "context" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - dcomexec "github.com/FalconOpsLLC/goexec/pkg/goexec/dcom" - "github.com/oiweiwei/go-msrpc/ssp/gssapi" - "github.com/spf13/cobra" + "context" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + dcomexec "github.com/FalconOpsLLC/goexec/pkg/goexec/dcom" + "github.com/oiweiwei/go-msrpc/ssp/gssapi" + "github.com/spf13/cobra" ) func dcomCmdInit() { - cmdFlags[dcomCmd] = []*flagSet{ - defaultAuthFlags, - defaultLogFlags, - defaultNetRpcFlags, - } - dcomMmcCmdInit() + cmdFlags[dcomCmd] = []*flagSet{ + defaultAuthFlags, + defaultLogFlags, + defaultNetRpcFlags, + } + dcomMmcCmdInit() - dcomCmd.PersistentFlags().AddFlagSet(defaultAuthFlags.Flags) - dcomCmd.PersistentFlags().AddFlagSet(defaultLogFlags.Flags) - dcomCmd.PersistentFlags().AddFlagSet(defaultNetRpcFlags.Flags) - dcomCmd.AddCommand(dcomMmcCmd) + dcomCmd.PersistentFlags().AddFlagSet(defaultAuthFlags.Flags) + dcomCmd.PersistentFlags().AddFlagSet(defaultLogFlags.Flags) + dcomCmd.PersistentFlags().AddFlagSet(defaultNetRpcFlags.Flags) + dcomCmd.AddCommand(dcomMmcCmd) } func dcomMmcCmdInit() { - dcomMmcExecFlags := newFlagSet("Execution") + dcomMmcExecFlags := newFlagSet("Execution") - registerExecutionFlags(dcomMmcExecFlags.Flags) - registerExecutionOutputFlags(dcomMmcExecFlags.Flags) + registerExecutionFlags(dcomMmcExecFlags.Flags) + registerExecutionOutputFlags(dcomMmcExecFlags.Flags) - dcomMmcExecFlags.Flags.StringVar(&dcomMmc.WorkingDirectory, "directory", `C:\`, "Working `directory`") - dcomMmcExecFlags.Flags.StringVar(&dcomMmc.WindowState, "window", "Minimized", "Window state") + dcomMmcExecFlags.Flags.StringVar(&dcomMmc.WorkingDirectory, "directory", `C:\`, "Working `directory`") + dcomMmcExecFlags.Flags.StringVar(&dcomMmc.WindowState, "window", "Minimized", "Window state") - cmdFlags[dcomMmcCmd] = []*flagSet{ - dcomMmcExecFlags, - defaultAuthFlags, - defaultLogFlags, - defaultNetRpcFlags, - } + cmdFlags[dcomMmcCmd] = []*flagSet{ + dcomMmcExecFlags, + defaultAuthFlags, + defaultLogFlags, + defaultNetRpcFlags, + } - dcomMmcCmd.Flags().AddFlagSet(dcomMmcExecFlags.Flags) + dcomMmcCmd.Flags().AddFlagSet(dcomMmcExecFlags.Flags) } var ( - dcomMmc dcomexec.DcomMmc + dcomMmc dcomexec.DcomMmc - dcomCmd = &cobra.Command{ - Use: "dcom", - Short: "Execute with Distributed Component Object Model (MS-DCOM)", - Long: `Description: + dcomCmd = &cobra.Command{ + Use: "dcom", + Short: "Execute with Distributed Component Object Model (MS-DCOM)", + Long: `Description: The dcom module uses exposed Distributed Component Object Model (DCOM) objects to spawn processes.`, - GroupID: "module", - Args: cobra.NoArgs, - } + GroupID: "module", + Args: cobra.NoArgs, + } - dcomMmcCmd = &cobra.Command{ - Use: "mmc [target]", - Short: "Execute with the DCOM MMC20.Application object", - Long: `Description: + dcomMmcCmd = &cobra.Command{ + Use: "mmc [target]", + Short: "Execute with the DCOM MMC20.Application object", + Long: `Description: The mmc method uses the exposed MMC20.Application object to call Document.ActiveView.ShellExec, and ultimately spawn a process on the remote host.`, - Args: args( - argsRpcClient("host"), - argsOutput("smb"), - ), - Run: func(cmd *cobra.Command, args []string) { - dcomMmc.Dcom.Client = &rpcClient - dcomMmc.IO = exec + Args: args( + argsRpcClient("host"), + argsOutput("smb"), + ), + Run: func(cmd *cobra.Command, args []string) { + dcomMmc.Client = &rpcClient + dcomMmc.IO = exec - ctx := log.With(). - Str("module", "dcom"). - Str("method", "mmc"). - Logger().WithContext(gssapi.NewSecurityContext(context.Background())) + ctx := log.With(). + Str("module", "dcom"). + Str("method", "mmc"). + Logger().WithContext(gssapi.NewSecurityContext(context.Background())) - if err := goexec.ExecuteCleanMethod(ctx, &dcomMmc, &exec); err != nil { - log.Fatal().Err(err).Msg("Operation failed") - } - }, - } + if err := goexec.ExecuteCleanMethod(ctx, &dcomMmc, &exec); err != nil { + log.Fatal().Err(err).Msg("Operation failed") + } + }, + } ) diff --git a/cmd/root.go b/cmd/root.go index 96173a4..a66fa62 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,26 +1,26 @@ package cmd import ( - "fmt" - "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/google/uuid" - "github.com/oiweiwei/go-msrpc/ssp" - "github.com/oiweiwei/go-msrpc/ssp/gssapi" - "github.com/rs/zerolog" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "golang.org/x/term" - "io" - "os" - "runtime/pprof" + "fmt" + "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/google/uuid" + "github.com/oiweiwei/go-msrpc/ssp" + "github.com/oiweiwei/go-msrpc/ssp/gssapi" + "github.com/rs/zerolog" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "golang.org/x/term" + "io" + "os" + "runtime/pprof" ) type flagSet struct { - Label string - Flags *pflag.FlagSet + Label string + Flags *pflag.FlagSet } const helpTemplate = `Usage:{{if .Runnable}} @@ -52,55 +52,55 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e ` var ( - cmdFlags = make(map[*cobra.Command][]*flagSet) - - defaultAuthFlags, defaultLogFlags, defaultNetRpcFlags *flagSet - - returnCode int - toClose []io.Closer - - // === IO === - stageFilePath string - outputMethod string - outputPath string - // ========== - - // === Logging === - logJson bool // Log output in JSON lines - logDebug bool // Output debug log messages - logQuiet bool // Suppress logging output - logOutput string // Log output file - logLevel zerolog.Level = zerolog.InfoLevel - logFile io.WriteCloser = os.Stderr - log zerolog.Logger - // =============== - - // === Network === - proxy string - rpcClient dce.Client - smbClient smb.Client - // =============== - - // === Resource profiling === - cpuProfile string - memProfile string - cpuProfileFile io.WriteCloser - memProfileFile io.WriteCloser - // ========================== - - exec = goexec.ExecutionIO{ - Input: new(goexec.ExecutionInput), - Output: new(goexec.ExecutionOutput), - } - - adAuthOpts *adauth.Options - credential *adauth.Credential - target *adauth.Target - - rootCmd = &cobra.Command{ - Use: "goexec", - Short: `goexec - Windows remote execution multitool`, - Long: ` + cmdFlags = make(map[*cobra.Command][]*flagSet) + + defaultAuthFlags, defaultLogFlags, defaultNetRpcFlags *flagSet + + returnCode int + toClose []io.Closer + + // === IO === + //stageFilePath string // FUTURE + outputMethod string + outputPath string + // ========== + + // === Logging === + logJson bool // Log output in JSON lines + logDebug bool // Output debug log messages + logQuiet bool // Suppress logging output + logOutput string // Log output file + logLevel = zerolog.InfoLevel + logFile io.WriteCloser = os.Stderr + log zerolog.Logger + // =============== + + // === Network === + proxy string + rpcClient dce.Client + smbClient smb.Client + // =============== + + // === Resource profiling === + cpuProfile string + memProfile string + cpuProfileFile io.WriteCloser + memProfileFile io.WriteCloser + // ========================== + + exec = goexec.ExecutionIO{ + Input: new(goexec.ExecutionInput), + Output: new(goexec.ExecutionOutput), + } + + adAuthOpts *adauth.Options + credential *adauth.Credential + target *adauth.Target + + rootCmd = &cobra.Command{ + Use: "goexec", + Short: `goexec - Windows remote execution multitool`, + Long: ` ___ ___ ___ _ _ ___ ___ | . | . | -_|_'_| -_| _| |_ |___|___|_,_|___|___| @@ -113,200 +113,200 @@ Authors: FalconOps LLC (@FalconOpsLLC), while providing an extremely flexible CLI and a strong focus on OPSEC. `, - PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { - - // Parse logging options - { - if logOutput != "" { - logFile, err = os.OpenFile(logOutput, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) - if err != nil { - return - } - toClose = append(toClose, logFile) - logJson = true - } - if logQuiet { - logLevel = zerolog.ErrorLevel - } else if logDebug { - logLevel = zerolog.DebugLevel - } - if logJson { - log = zerolog.New(logFile).With().Timestamp().Logger() - } else { - log = zerolog.New(zerolog.ConsoleWriter{Out: logFile}).With().Timestamp().Logger() - } - log = log.Level(logLevel) - } - - // CPU / memory profiling - { - if cpuProfile != "" { - if cpuProfileFile, err = os.OpenFile(cpuProfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil { - log.Error().Err(err).Msg("Failed to open CPU profile for writing") - return - } - toClose = append(toClose, cpuProfileFile) - - if err = pprof.StartCPUProfile(cpuProfileFile); err != nil { - log.Error().Err(err).Msg("Failed to start CPU profile") - return - } - } - if memProfile != "" { - if memProfileFile, err = os.OpenFile(memProfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil { - log.Error().Err(err).Msg("Failed to open memory profile for writing") - return - } - toClose = append(toClose, memProfileFile) - } - } - - if proxy != "" { - rpcClient.Proxy = proxy - smbClient.Proxy = proxy - } - - if outputPath != "" { - if outputMethod == "smb" { - if exec.Output.RemotePath == "" { - exec.Output.RemotePath = `C:\Windows\Temp\` + uuid.NewString() - } - exec.Output.Provider = &smb.OutputFileFetcher{ - Client: &smbClient, - Share: `ADMIN$`, // TODO: dynamic - SharePath: `C:\Windows`, - File: exec.Output.RemotePath, - DeleteOutputFile: !exec.Output.NoDelete, - } - } - } - return - }, - - PersistentPostRun: func(cmd *cobra.Command, args []string) { - - if memProfileFile != nil { - if err := pprof.WriteHeapProfile(memProfileFile); err != nil { - log.Error().Err(err).Msg("Failed to write memory profile") - return - } - } - - if cpuProfileFile != nil { - pprof.StopCPUProfile() - } - - for _, c := range toClose { - if c != nil { - if err := c.Close(); err != nil { - log.Warn().Err(err).Msg("Failed to close stream") - } - } - } - - if exec.Input != nil && exec.Input.StageFile != nil { - if err := exec.Input.StageFile.Close(); err != nil { - // ... - } - } - }, - } + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + + // Parse logging options + { + if logOutput != "" { + logFile, err = os.OpenFile(logOutput, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return + } + toClose = append(toClose, logFile) + logJson = true + } + if logQuiet { + logLevel = zerolog.ErrorLevel + } else if logDebug { + logLevel = zerolog.DebugLevel + } + if logJson { + log = zerolog.New(logFile).With().Timestamp().Logger() + } else { + log = zerolog.New(zerolog.ConsoleWriter{Out: logFile}).With().Timestamp().Logger() + } + log = log.Level(logLevel) + } + + // CPU / memory profiling + { + if cpuProfile != "" { + if cpuProfileFile, err = os.OpenFile(cpuProfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil { + log.Error().Err(err).Msg("Failed to open CPU profile for writing") + return + } + toClose = append(toClose, cpuProfileFile) + + if err = pprof.StartCPUProfile(cpuProfileFile); err != nil { + log.Error().Err(err).Msg("Failed to start CPU profile") + return + } + } + if memProfile != "" { + if memProfileFile, err = os.OpenFile(memProfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil { + log.Error().Err(err).Msg("Failed to open memory profile for writing") + return + } + toClose = append(toClose, memProfileFile) + } + } + + if proxy != "" { + rpcClient.Proxy = proxy + smbClient.Proxy = proxy + } + + if outputPath != "" { + if outputMethod == "smb" { + if exec.Output.RemotePath == "" { + exec.Output.RemotePath = `C:\Windows\Temp\` + uuid.NewString() + } + exec.Output.Provider = &smb.OutputFileFetcher{ + Client: &smbClient, + Share: `ADMIN$`, // TODO: dynamic + SharePath: `C:\Windows`, + File: exec.Output.RemotePath, + DeleteOutputFile: !exec.Output.NoDelete, + } + } + } + return + }, + + PersistentPostRun: func(cmd *cobra.Command, args []string) { + + if memProfileFile != nil { + if err := pprof.WriteHeapProfile(memProfileFile); err != nil { + log.Error().Err(err).Msg("Failed to write memory profile") + return + } + } + + if cpuProfileFile != nil { + pprof.StopCPUProfile() + } + + if exec.Input != nil && exec.Input.StageFile != nil { + if err := exec.Input.StageFile.Close(); err != nil { + log.Warn().Err(err).Msg("Failed to close stage file") + } + } + + for _, c := range toClose { + if c != nil { + if err := c.Close(); err != nil { + log.Warn().Err(err).Msg("Failed to close stream") + } + } + } + }, + } ) func newFlagSet(name string) *flagSet { - flags := pflag.NewFlagSet(name, pflag.ExitOnError) - flags.SortFlags = false - return &flagSet{ - Label: name, - Flags: flags, - } + flags := pflag.NewFlagSet(name, pflag.ExitOnError) + flags.SortFlags = false + return &flagSet{ + Label: name, + Flags: flags, + } } func init() { - // Auth init - { - gssapi.AddMechanism(ssp.SPNEGO) - gssapi.AddMechanism(ssp.NTLM) - gssapi.AddMechanism(ssp.KRB5) - } - - // CPU / Memory profiling - { - rootCmd.PersistentFlags().StringVar(&cpuProfile, "cpu-profile", "", "Write CPU profile to `file`") - rootCmd.PersistentFlags().StringVar(&memProfile, "mem-profile", "", "Write memory profile to `file`") - - if err := rootCmd.PersistentFlags().MarkHidden("cpu-profile"); err != nil { - panic(err) - } - if err := rootCmd.PersistentFlags().MarkHidden("mem-profile"); err != nil { - panic(err) - } - } - - // Cobra init - { - cobra.EnableCommandSorting = false - { - defaultNetRpcFlags = newFlagSet("Network") - registerNetworkFlags(defaultNetRpcFlags.Flags) - } - { - defaultLogFlags = newFlagSet("Logging") - registerLoggingFlags(defaultLogFlags.Flags) - } - { - defaultAuthFlags = newFlagSet("Authentication") - adAuthOpts = &adauth.Options{ - Debug: log.Debug().Msgf, - } - adAuthOpts.RegisterFlags(defaultAuthFlags.Flags) - } - - modules := &cobra.Group{ - ID: "module", - Title: "Execution Commands:", - } - rootCmd.AddGroup(modules) - - cmdFlags[rootCmd] = []*flagSet{ - defaultLogFlags, - defaultAuthFlags, - } - - cobra.AddTemplateFunc("flags", func(fs *pflag.FlagSet) string { - if width, _, err := term.GetSize(int(os.Stdout.Fd())); err == nil { - return fs.FlagUsagesWrapped(width - 1) - } - return fs.FlagUsagesWrapped(80 - 1) - }) - - cobra.AddTemplateFunc("cmdFlags", func(cmd *cobra.Command) []*flagSet { - return cmdFlags[cmd] - }) - - rootCmd.InitDefaultVersionFlag() - rootCmd.InitDefaultHelpCmd() - rootCmd.SetHelpTemplate("{{if (ne .Long \"\")}}{{.Long}}\n\n{{end}}" + helpTemplate) - rootCmd.SetUsageTemplate(helpTemplate) - - // Modules init - { - dcomCmdInit() - rootCmd.AddCommand(dcomCmd) - wmiCmdInit() - rootCmd.AddCommand(wmiCmd) - scmrCmdInit() - rootCmd.AddCommand(scmrCmd) - tschCmdInit() - rootCmd.AddCommand(tschCmd) - } - } + // Auth init + { + gssapi.AddMechanism(ssp.SPNEGO) + gssapi.AddMechanism(ssp.NTLM) + gssapi.AddMechanism(ssp.KRB5) + } + + // CPU / Memory profiling + { + rootCmd.PersistentFlags().StringVar(&cpuProfile, "cpu-profile", "", "Write CPU profile to `file`") + rootCmd.PersistentFlags().StringVar(&memProfile, "mem-profile", "", "Write memory profile to `file`") + + if err := rootCmd.PersistentFlags().MarkHidden("cpu-profile"); err != nil { + panic(err) + } + if err := rootCmd.PersistentFlags().MarkHidden("mem-profile"); err != nil { + panic(err) + } + } + + // Cobra init + { + cobra.EnableCommandSorting = false + { + defaultNetRpcFlags = newFlagSet("Network") + registerNetworkFlags(defaultNetRpcFlags.Flags) + } + { + defaultLogFlags = newFlagSet("Logging") + registerLoggingFlags(defaultLogFlags.Flags) + } + { + defaultAuthFlags = newFlagSet("Authentication") + adAuthOpts = &adauth.Options{ + Debug: log.Debug().Msgf, + } + adAuthOpts.RegisterFlags(defaultAuthFlags.Flags) + } + + modules := &cobra.Group{ + ID: "module", + Title: "Execution Commands:", + } + rootCmd.AddGroup(modules) + + cmdFlags[rootCmd] = []*flagSet{ + defaultLogFlags, + defaultAuthFlags, + } + + cobra.AddTemplateFunc("flags", func(fs *pflag.FlagSet) string { + if width, _, err := term.GetSize(int(os.Stdout.Fd())); err == nil { + return fs.FlagUsagesWrapped(width - 1) + } + return fs.FlagUsagesWrapped(80 - 1) + }) + + cobra.AddTemplateFunc("cmdFlags", func(cmd *cobra.Command) []*flagSet { + return cmdFlags[cmd] + }) + + rootCmd.InitDefaultVersionFlag() + rootCmd.InitDefaultHelpCmd() + rootCmd.SetHelpTemplate("{{if (ne .Long \"\")}}{{.Long}}\n\n{{end}}" + helpTemplate) + rootCmd.SetUsageTemplate(helpTemplate) + + // Modules init + { + dcomCmdInit() + rootCmd.AddCommand(dcomCmd) + wmiCmdInit() + rootCmd.AddCommand(wmiCmd) + scmrCmdInit() + rootCmd.AddCommand(scmrCmd) + tschCmdInit() + rootCmd.AddCommand(tschCmd) + } + } } func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } - os.Exit(returnCode) + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } + os.Exit(returnCode) } diff --git a/cmd/tsch.go b/cmd/tsch.go index ec3504f..f58caf6 100644 --- a/cmd/tsch.go +++ b/cmd/tsch.go @@ -1,233 +1,233 @@ package cmd import ( - "context" - "fmt" - "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" - "time" + "context" + "fmt" + "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" + "time" ) func tschCmdInit() { - cmdFlags[tschCmd] = []*flagSet{ - defaultAuthFlags, - defaultLogFlags, - defaultNetRpcFlags, - } - tschDemandCmdInit() - tschCreateCmdInit() - tschChangeCmdInit() - - tschCmd.PersistentFlags().AddFlagSet(defaultAuthFlags.Flags) - tschCmd.PersistentFlags().AddFlagSet(defaultLogFlags.Flags) - tschCmd.PersistentFlags().AddFlagSet(defaultNetRpcFlags.Flags) - tschCmd.AddCommand(tschDemandCmd, tschCreateCmd, tschChangeCmd) + cmdFlags[tschCmd] = []*flagSet{ + defaultAuthFlags, + defaultLogFlags, + defaultNetRpcFlags, + } + tschDemandCmdInit() + tschCreateCmdInit() + tschChangeCmdInit() + + tschCmd.PersistentFlags().AddFlagSet(defaultAuthFlags.Flags) + tschCmd.PersistentFlags().AddFlagSet(defaultLogFlags.Flags) + tschCmd.PersistentFlags().AddFlagSet(defaultNetRpcFlags.Flags) + tschCmd.AddCommand(tschDemandCmd, tschCreateCmd, tschChangeCmd) } func tschDemandCmdInit() { - tschDemandFlags := newFlagSet("Task Scheduler") + tschDemandFlags := newFlagSet("Task Scheduler") - tschDemandFlags.Flags.StringVarP(&tschTask, "task", "t", "", "Name or path of the new task") - tschDemandFlags.Flags.Uint32Var(&tschDemand.SessionId, "session", 0, "Hijack existing session given the session `ID`") - tschDemandFlags.Flags.StringVar(&tschDemand.UserSid, "sid", "S-1-5-18", "User `SID` to impersonate") - tschDemandFlags.Flags.BoolVar(&tschDemand.NoDelete, "no-delete", false, "Don't delete task after execution") + tschDemandFlags.Flags.StringVarP(&tschTask, "task", "t", "", "Name or path of the new task") + tschDemandFlags.Flags.Uint32Var(&tschDemand.SessionId, "session", 0, "Hijack existing session given the session `ID`") + tschDemandFlags.Flags.StringVar(&tschDemand.UserSid, "sid", "S-1-5-18", "User `SID` to impersonate") + tschDemandFlags.Flags.BoolVar(&tschDemand.NoDelete, "no-delete", false, "Don't delete task after execution") - tschDemandExecFlags := newFlagSet("Execution") + tschDemandExecFlags := newFlagSet("Execution") - registerExecutionFlags(tschDemandExecFlags.Flags) - registerExecutionOutputFlags(tschDemandExecFlags.Flags) + registerExecutionFlags(tschDemandExecFlags.Flags) + registerExecutionOutputFlags(tschDemandExecFlags.Flags) - cmdFlags[tschDemandCmd] = []*flagSet{ - tschDemandFlags, - tschDemandExecFlags, - defaultAuthFlags, - defaultLogFlags, - defaultNetRpcFlags, - } + cmdFlags[tschDemandCmd] = []*flagSet{ + tschDemandFlags, + tschDemandExecFlags, + defaultAuthFlags, + defaultLogFlags, + defaultNetRpcFlags, + } - tschDemandCmd.Flags().AddFlagSet(tschDemandFlags.Flags) - tschDemandCmd.Flags().AddFlagSet(tschDemandExecFlags.Flags) - tschDemandCmd.MarkFlagsOneRequired("exec", "command") + tschDemandCmd.Flags().AddFlagSet(tschDemandFlags.Flags) + tschDemandCmd.Flags().AddFlagSet(tschDemandExecFlags.Flags) + tschDemandCmd.MarkFlagsOneRequired("exec", "command") } func tschCreateCmdInit() { - tschCreateFlags := newFlagSet("Task Scheduler") - - tschCreateFlags.Flags.StringVarP(&tschTask, "task", "t", "", "Name or path of the new task") - tschCreateFlags.Flags.DurationVar(&tschCreate.StopDelay, "delay-stop", 5*time.Second, "Delay between task execution and termination. This won't stop the spawned process") - tschCreateFlags.Flags.DurationVar(&tschCreate.StartDelay, "start-delay", 5*time.Second, "Delay between task registration and execution") - //tschCreateFlags.Flags.DurationVar(&tschCreate.DeleteDelay, "delete-delay", 0*time.Second, "Delay between task termination and deletion") - tschCreateFlags.Flags.BoolVar(&tschCreate.NoDelete, "no-delete", false, "Don't delete task after execution") - tschCreateFlags.Flags.BoolVar(&tschCreate.CallDelete, "call-delete", false, "Directly call SchRpcDelete to delete task") - tschCreateFlags.Flags.StringVar(&tschCreate.UserSid, "sid", "S-1-5-18", "User `SID` to impersonate") - - tschCreateExecFlags := newFlagSet("Execution") - - registerExecutionFlags(tschCreateExecFlags.Flags) - registerExecutionOutputFlags(tschCreateExecFlags.Flags) - - cmdFlags[tschCreateCmd] = []*flagSet{ - tschCreateFlags, - tschCreateExecFlags, - defaultAuthFlags, - defaultLogFlags, - defaultNetRpcFlags, - } - - tschCreateCmd.Flags().AddFlagSet(tschCreateFlags.Flags) - tschCreateCmd.Flags().AddFlagSet(tschCreateExecFlags.Flags) - tschCreateCmd.MarkFlagsOneRequired("exec", "command") + tschCreateFlags := newFlagSet("Task Scheduler") + + tschCreateFlags.Flags.StringVarP(&tschTask, "task", "t", "", "Name or path of the new task") + tschCreateFlags.Flags.DurationVar(&tschCreate.StopDelay, "delay-stop", 5*time.Second, "Delay between task execution and termination. This won't stop the spawned process") + tschCreateFlags.Flags.DurationVar(&tschCreate.StartDelay, "start-delay", 5*time.Second, "Delay between task registration and execution") + //tschCreateFlags.Flags.DurationVar(&tschCreate.DeleteDelay, "delete-delay", 0*time.Second, "Delay between task termination and deletion") + tschCreateFlags.Flags.BoolVar(&tschCreate.NoDelete, "no-delete", false, "Don't delete task after execution") + tschCreateFlags.Flags.BoolVar(&tschCreate.CallDelete, "call-delete", false, "Directly call SchRpcDelete to delete task") + tschCreateFlags.Flags.StringVar(&tschCreate.UserSid, "sid", "S-1-5-18", "User `SID` to impersonate") + + tschCreateExecFlags := newFlagSet("Execution") + + registerExecutionFlags(tschCreateExecFlags.Flags) + registerExecutionOutputFlags(tschCreateExecFlags.Flags) + + cmdFlags[tschCreateCmd] = []*flagSet{ + tschCreateFlags, + tschCreateExecFlags, + defaultAuthFlags, + defaultLogFlags, + defaultNetRpcFlags, + } + + tschCreateCmd.Flags().AddFlagSet(tschCreateFlags.Flags) + tschCreateCmd.Flags().AddFlagSet(tschCreateExecFlags.Flags) + tschCreateCmd.MarkFlagsOneRequired("exec", "command") } func tschChangeCmdInit() { - tschChangeFlags := newFlagSet("Task Scheduler") - - tschChangeFlags.Flags.StringVarP(&tschChange.TaskPath, "task", "t", "", "Path to existing task") - tschChangeFlags.Flags.BoolVar(&tschChange.NoStart, "no-start", false, "Don't start the task") - tschChangeFlags.Flags.BoolVar(&tschChange.NoRevert, "no-revert", false, "Don't restore the original task definition") - - tschChangeExecFlags := newFlagSet("Execution") - - registerExecutionFlags(tschChangeExecFlags.Flags) - registerExecutionOutputFlags(tschChangeExecFlags.Flags) - - cmdFlags[tschChangeCmd] = []*flagSet{ - tschChangeFlags, - tschChangeExecFlags, - defaultAuthFlags, - defaultLogFlags, - defaultNetRpcFlags, - } - - tschChangeCmd.Flags().AddFlagSet(tschChangeFlags.Flags) - tschChangeCmd.Flags().AddFlagSet(tschChangeExecFlags.Flags) - - // Constraints - { - if err := tschChangeCmd.MarkFlagRequired("task"); err != nil { - panic(err) - } - tschChangeCmd.MarkFlagsOneRequired("exec", "command") - } + tschChangeFlags := newFlagSet("Task Scheduler") + + tschChangeFlags.Flags.StringVarP(&tschChange.TaskPath, "task", "t", "", "Path to existing task") + tschChangeFlags.Flags.BoolVar(&tschChange.NoStart, "no-start", false, "Don't start the task") + tschChangeFlags.Flags.BoolVar(&tschChange.NoRevert, "no-revert", false, "Don't restore the original task definition") + + tschChangeExecFlags := newFlagSet("Execution") + + registerExecutionFlags(tschChangeExecFlags.Flags) + registerExecutionOutputFlags(tschChangeExecFlags.Flags) + + cmdFlags[tschChangeCmd] = []*flagSet{ + tschChangeFlags, + tschChangeExecFlags, + defaultAuthFlags, + defaultLogFlags, + defaultNetRpcFlags, + } + + tschChangeCmd.Flags().AddFlagSet(tschChangeFlags.Flags) + tschChangeCmd.Flags().AddFlagSet(tschChangeExecFlags.Flags) + + // Constraints + { + if err := tschChangeCmd.MarkFlagRequired("task"); err != nil { + panic(err) + } + tschChangeCmd.MarkFlagsOneRequired("exec", "command") + } } func argsTask(*cobra.Command, []string) error { - switch { - case tschTask == "": - tschTask = `\` + util.RandomString() - case tschexec.ValidateTaskPath(tschTask) == nil: - case tschexec.ValidateTaskName(tschTask) == nil: - tschTask = `\` + tschTask - default: - return fmt.Errorf("invalid task Label or path: %q", tschTask) - } - return nil + switch { + case tschTask == "": + tschTask = `\` + util.RandomString() + case tschexec.ValidateTaskPath(tschTask) == nil: + case tschexec.ValidateTaskName(tschTask) == nil: + tschTask = `\` + tschTask + default: + return fmt.Errorf("invalid task Label or path: %q", tschTask) + } + return nil } var ( - tschDemand tschexec.TschDemand - tschCreate tschexec.TschCreate - tschChange tschexec.TschChange + tschDemand tschexec.TschDemand + tschCreate tschexec.TschCreate + tschChange tschexec.TschChange - tschTask string + tschTask string - tschCmd = &cobra.Command{ - Use: "tsch", - Short: "Execute with Windows Task Scheduler (MS-TSCH)", - Long: `Description: + tschCmd = &cobra.Command{ + Use: "tsch", + Short: "Execute with Windows Task Scheduler (MS-TSCH)", + Long: `Description: The tsch module makes use of the Windows Task Scheduler service (MS-TSCH) to spawn processes on the remote target.`, - GroupID: "module", - Args: cobra.NoArgs, - } - - tschDemandCmd = &cobra.Command{ - Use: "demand [target]", - Short: "Register a remote scheduled task and demand immediate start", - Long: `Description: + GroupID: "module", + Args: cobra.NoArgs, + } + + tschDemandCmd = &cobra.Command{ + Use: "demand [target]", + Short: "Register a remote scheduled task and demand immediate start", + Long: `Description: 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.`, - Args: args( - argsRpcClient("cifs"), - argsOutput("smb"), - argsTask, - ), - - Run: func(*cobra.Command, []string) { - tschDemand.IO = exec - tschDemand.Client = &rpcClient - tschDemand.TaskPath = tschTask - - ctx := log.With(). - Str("module", "tsch"). - Str("method", "demand"). - Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) - - if err := goexec.ExecuteCleanMethod(ctx, &tschDemand, &exec); err != nil { - log.Fatal().Err(err).Msg("Operation failed") - } - }, - } - tschCreateCmd = &cobra.Command{ - Use: "create [target]", - Short: "Create a remote scheduled task with an automatic start time", - Long: `Description: + Args: args( + argsRpcClient("cifs"), + argsOutput("smb"), + argsTask, + ), + + Run: func(*cobra.Command, []string) { + tschDemand.IO = exec + tschDemand.Client = &rpcClient + tschDemand.TaskPath = tschTask + + ctx := log.With(). + Str("module", "tsch"). + Str("method", "demand"). + Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) + + if err := goexec.ExecuteCleanMethod(ctx, &tschDemand, &exec); err != nil { + log.Fatal().Err(err).Msg("Operation failed") + } + }, + } + tschCreateCmd = &cobra.Command{ + Use: "create [target]", + Short: "Create a remote scheduled task with an automatic start time", + Long: `Description: 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.`, - Args: args( - argsRpcClient("cifs"), - argsOutput("smb"), - argsTask, - ), - - Run: func(*cobra.Command, []string) { - tschCreate.Tsch.Client = &rpcClient - tschCreate.IO = exec - tschCreate.TaskPath = tschTask - - ctx := log.With(). - Str("module", "tsch"). - Str("method", "create"). - Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) - - if err := goexec.ExecuteCleanMethod(ctx, &tschCreate, &exec); err != nil { - log.Fatal().Err(err).Msg("Operation failed") - } - }, - } - tschChangeCmd = &cobra.Command{ - Use: "change [target]", - Short: "Modify an existing task to spawn an arbitrary process", - Long: `Description: + Args: args( + argsRpcClient("cifs"), + argsOutput("smb"), + argsTask, + ), + + Run: func(*cobra.Command, []string) { + tschCreate.Client = &rpcClient + tschCreate.IO = exec + tschCreate.TaskPath = tschTask + + ctx := log.With(). + Str("module", "tsch"). + Str("method", "create"). + Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) + + if err := goexec.ExecuteCleanMethod(ctx, &tschCreate, &exec); err != nil { + log.Fatal().Err(err).Msg("Operation failed") + } + }, + } + tschChangeCmd = &cobra.Command{ + Use: "change [target]", + Short: "Modify an existing task to spawn an arbitrary process", + Long: `Description: The change method calls SchRpcRetrieveTask to fetch the definition of an existing task (-t), then modifies the task definition to spawn a process`, - Args: args( - argsRpcClient("cifs"), - argsOutput("smb"), - - func(*cobra.Command, []string) error { - return tschexec.ValidateTaskPath(tschChange.TaskPath) - }, - ), - - Run: func(*cobra.Command, []string) { - tschChange.Tsch.Client = &rpcClient - tschChange.IO = exec - - ctx := log.With(). - Str("module", "tsch"). - Str("method", "change"). - Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) - - if err := goexec.ExecuteCleanMethod(ctx, &tschChange, &exec); err != nil { - log.Fatal().Err(err).Msg("Operation failed") - } - }, - } + Args: args( + argsRpcClient("cifs"), + argsOutput("smb"), + + func(*cobra.Command, []string) error { + return tschexec.ValidateTaskPath(tschChange.TaskPath) + }, + ), + + Run: func(*cobra.Command, []string) { + tschChange.Client = &rpcClient + tschChange.IO = exec + + ctx := log.With(). + Str("module", "tsch"). + Str("method", "change"). + Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) + + if err := goexec.ExecuteCleanMethod(ctx, &tschChange, &exec); err != nil { + log.Fatal().Err(err).Msg("Operation failed") + } + }, + } ) diff --git a/pkg/goexec/dce/client.go b/pkg/goexec/dce/client.go index 0918933..567d619 100644 --- a/pkg/goexec/dce/client.go +++ b/pkg/goexec/dce/client.go @@ -13,8 +13,7 @@ import ( type Client struct { Options - conn dcerpc.Conn - hostname string + conn dcerpc.Conn } func (c *Client) String() string { diff --git a/pkg/goexec/dcom/dcom.go b/pkg/goexec/dcom/dcom.go index 41f48d6..a504e1a 100644 --- a/pkg/goexec/dcom/dcom.go +++ b/pkg/goexec/dcom/dcom.go @@ -1,33 +1,33 @@ package dcomexec import ( - googleUUID "github.com/google/uuid" - "github.com/oiweiwei/go-msrpc/midl/uuid" - "github.com/oiweiwei/go-msrpc/msrpc/dcom" - "github.com/oiweiwei/go-msrpc/msrpc/dtyp" + googleUUID "github.com/google/uuid" + "github.com/oiweiwei/go-msrpc/midl/uuid" + "github.com/oiweiwei/go-msrpc/msrpc/dcom" + "github.com/oiweiwei/go-msrpc/msrpc/dtyp" ) const ( - LcEnglishUs uint32 = 0x409 + LcEnglishUs uint32 = 0x409 ) var ( - ShellWindowsUuid = uuid.MustParse("9BA05972-F6A8-11CF-A442-00A0C90A8F39") - Mmc20Uuid = uuid.MustParse("49B2791A-B1AE-4C90-9B8E-E860BA07F889") + //ShellWindowsUuid = uuid.MustParse("9BA05972-F6A8-11CF-A442-00A0C90A8F39") + //Mmc20Uuid = uuid.MustParse("49B2791A-B1AE-4C90-9B8E-E860BA07F889") - RandCid = dcom.CID(*dtyp.GUIDFromUUID(uuid.MustParse(googleUUID.NewString()))) - IDispatchIID = &dcom.IID{ - Data1: 0x20400, - Data2: 0x0, - Data3: 0x0, - Data4: []byte{0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46}, - } - ComVersion = &dcom.COMVersion{ - MajorVersion: 5, - MinorVersion: 7, - } - ORPCThis = &dcom.ORPCThis{ - Version: ComVersion, - CID: &RandCid, - } + RandCid = dcom.CID(*dtyp.GUIDFromUUID(uuid.MustParse(googleUUID.NewString()))) + IDispatchIID = &dcom.IID{ + Data1: 0x20400, + Data2: 0x0, + Data3: 0x0, + Data4: []byte{0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46}, + } + ComVersion = &dcom.COMVersion{ + MajorVersion: 5, + MinorVersion: 7, + } + ORPCThis = &dcom.ORPCThis{ + Version: ComVersion, + CID: &RandCid, + } ) diff --git a/pkg/goexec/dcom/module.go b/pkg/goexec/dcom/module.go index 1aa44c1..b6fc4d4 100644 --- a/pkg/goexec/dcom/module.go +++ b/pkg/goexec/dcom/module.go @@ -1,142 +1,145 @@ package dcomexec import ( - "context" - "errors" - "fmt" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/midl/uuid" - "github.com/oiweiwei/go-msrpc/msrpc/dcom" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/iremotescmactivator/v0" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0" - "github.com/oiweiwei/go-msrpc/msrpc/dtyp" - "github.com/rs/zerolog" + "context" + "errors" + "fmt" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" + "github.com/oiweiwei/go-msrpc/dcerpc" + "github.com/oiweiwei/go-msrpc/midl/uuid" + "github.com/oiweiwei/go-msrpc/msrpc/dcom" + "github.com/oiweiwei/go-msrpc/msrpc/dcom/iremotescmactivator/v0" + "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0" + "github.com/oiweiwei/go-msrpc/msrpc/dtyp" + "github.com/rs/zerolog" + + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus" + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32" ) const ( - ModuleName = "DCOM" + ModuleName = "DCOM" ) type Dcom struct { - goexec.Cleaner - goexec.Executor + goexec.Cleaner + goexec.Executor - Client *dce.Client - ClassID string + Client *dce.Client + ClassID string - dispatchClient idispatch.DispatchClient + dispatchClient idispatch.DispatchClient } func (m *Dcom) Connect(ctx context.Context) (err error) { - if err = m.Client.Connect(ctx); err == nil { - m.AddCleaners(m.Client.Close) - } - return + if err = m.Client.Connect(ctx); err == nil { + m.AddCleaners(m.Client.Close) + } + return } func (m *Dcom) Init(ctx context.Context) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName).Logger() - - if m.Client == nil || m.Client.Dce() == nil { - return errors.New("DCE connection not initialized") - } - - m.ClassID = "49B2791A-B1AE-4C90-9B8E-E860BA07F889" - //m.ClassID = "9BA05972-F6A8-11CF-A442-00A0C90A8F39" - class := dcom.ClassID(*dtyp.GUIDFromUUID(uuid.MustParse(m.ClassID))) - - if class.GUID() == nil { - return fmt.Errorf("invalid class ID: %s", m.ClassID) - } - - opts := []dcerpc.Option{ - dcerpc.WithSign(), - } - - inst := &dcom.InstantiationInfoData{ - ClassID: &class, - IID: []*dcom.IID{IDispatchIID}, - ClientCOMVersion: ComVersion, - } - ac := &dcom.ActivationContextInfoData{} - loc := &dcom.LocationInfoData{} - scm := &dcom.SCMRequestInfoData{ - RemoteRequest: &dcom.CustomRemoteRequestSCMInfo{ - RequestedProtocolSequences: []uint16{7}, - }, - } - - ap := &dcom.ActivationProperties{ - DestinationContext: 2, - Properties: []dcom.ActivationProperty{inst, ac, loc, scm}, - } - - apin, err := ap.ActivationPropertiesIn() - if err != nil { - return err - } - - act, err := iremotescmactivator.NewRemoteSCMActivatorClient(ctx, m.Client.Dce()) - if err != nil { - return err - } - - cr, err := act.RemoteCreateInstance(ctx, &iremotescmactivator.RemoteCreateInstanceRequest{ - ORPCThis: &dcom.ORPCThis{ - Version: ComVersion, - Flags: 1, - CID: &RandCid, - }, - ActPropertiesIn: apin, - }) - if err != nil { - return err - } - log.Info().Msg("RemoteCreateInstance succeeded") - - apout := new(dcom.ActivationProperties) - if err = apout.Parse(cr.ActPropertiesOut); err != nil { - return err - } - si := apout.SCMReplyInfoData() - pi := apout.PropertiesOutInfo() - - if si == nil { - return fmt.Errorf("remote create instance response: SCMReplyInfoData is nil") - } - - if pi == nil { - return fmt.Errorf("remote create instance response: PropertiesOutInfo is nil") - } - - // Ensure that the string bindings don't contain the target hostname - for _, bind := range si.RemoteReply.OXIDBindings.GetStringBindings() { - stringBinding, err := dcerpc.ParseStringBinding("ncacn_ip_tcp:" + bind.NetworkAddr) // TODO: try bind.String() - - if err != nil { - log.Debug().Err(err).Msg("Failed to parse string binding") - continue - } - stringBinding.NetworkAddress = "" - opts = append(opts, dcerpc.WithEndpoint(stringBinding.String())) - } - - err = m.Client.Reconnect(ctx, opts...) - if err != nil { - return err - } - log.Info().Msg("created new DCERPC dialer") - - m.dispatchClient, err = idispatch.NewDispatchClient(ctx, m.Client.Dce(), dcom.WithIPID(pi.InterfaceData[0].IPID())) - if err != nil { - return err - } - log.Info().Msg("created IDispatch Client") - - return + log := zerolog.Ctx(ctx).With(). + Str("module", ModuleName).Logger() + + if m.Client == nil || m.Client.Dce() == nil { + return errors.New("DCE connection not initialized") + } + + m.ClassID = "49B2791A-B1AE-4C90-9B8E-E860BA07F889" + //m.ClassID = "9BA05972-F6A8-11CF-A442-00A0C90A8F39" + class := dcom.ClassID(*dtyp.GUIDFromUUID(uuid.MustParse(m.ClassID))) + + if class.GUID() == nil { + return fmt.Errorf("invalid class ID: %s", m.ClassID) + } + + opts := []dcerpc.Option{ + dcerpc.WithSign(), + } + + inst := &dcom.InstantiationInfoData{ + ClassID: &class, + IID: []*dcom.IID{IDispatchIID}, + ClientCOMVersion: ComVersion, + } + ac := &dcom.ActivationContextInfoData{} + loc := &dcom.LocationInfoData{} + scm := &dcom.SCMRequestInfoData{ + RemoteRequest: &dcom.CustomRemoteRequestSCMInfo{ + RequestedProtocolSequences: []uint16{7}, + }, + } + + ap := &dcom.ActivationProperties{ + DestinationContext: 2, + Properties: []dcom.ActivationProperty{inst, ac, loc, scm}, + } + + apin, err := ap.ActivationPropertiesIn() + if err != nil { + return err + } + + act, err := iremotescmactivator.NewRemoteSCMActivatorClient(ctx, m.Client.Dce()) + if err != nil { + return err + } + + cr, err := act.RemoteCreateInstance(ctx, &iremotescmactivator.RemoteCreateInstanceRequest{ + ORPCThis: &dcom.ORPCThis{ + Version: ComVersion, + Flags: 1, + CID: &RandCid, + }, + ActPropertiesIn: apin, + }) + if err != nil { + return err + } + log.Info().Msg("RemoteCreateInstance succeeded") + + apout := new(dcom.ActivationProperties) + if err = apout.Parse(cr.ActPropertiesOut); err != nil { + return err + } + si := apout.SCMReplyInfoData() + pi := apout.PropertiesOutInfo() + + if si == nil { + return fmt.Errorf("remote create instance response: SCMReplyInfoData is nil") + } + + if pi == nil { + return fmt.Errorf("remote create instance response: PropertiesOutInfo is nil") + } + + // Ensure that the string bindings don't contain the target hostname + for _, bind := range si.RemoteReply.OXIDBindings.GetStringBindings() { + stringBinding, err := dcerpc.ParseStringBinding("ncacn_ip_tcp:" + bind.NetworkAddr) // TODO: try bind.String() + + if err != nil { + log.Debug().Err(err).Msg("Failed to parse string binding") + continue + } + stringBinding.NetworkAddress = "" + opts = append(opts, dcerpc.WithEndpoint(stringBinding.String())) + } + + err = m.Client.Reconnect(ctx, opts...) + if err != nil { + return err + } + log.Info().Msg("created new DCERPC dialer") + + m.dispatchClient, err = idispatch.NewDispatchClient(ctx, m.Client.Dce(), dcom.WithIPID(pi.InterfaceData[0].IPID())) + if err != nil { + return err + } + log.Info().Msg("created IDispatch Client") + + return } diff --git a/pkg/goexec/dcom/util.go b/pkg/goexec/dcom/util.go index 5ed8a60..9d7850d 100644 --- a/pkg/goexec/dcom/util.go +++ b/pkg/goexec/dcom/util.go @@ -1,85 +1,88 @@ package dcomexec import ( - "context" - "fmt" - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/msrpc/dcom" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0" - "strings" + "context" + "fmt" + "github.com/oiweiwei/go-msrpc/dcerpc" + "github.com/oiweiwei/go-msrpc/msrpc/dcom" + "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut" + "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0" + "strings" + + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus" + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32" ) func callComMethod(ctx context.Context, dc idispatch.DispatchClient, method string, args ...*oaut.Variant) (ir *idispatch.InvokeResponse, err error) { - parts := strings.Split(method, ".") + parts := strings.Split(method, ".") - var id *dcom.IPID - var gr *idispatch.GetIDsOfNamesResponse + var id *dcom.IPID + var gr *idispatch.GetIDsOfNamesResponse - for i, obj := range parts { + for i, obj := range parts { - var opts []dcerpc.CallOption + var opts []dcerpc.CallOption - if id != nil { - opts = append(opts, dcom.WithIPID(id)) - } + if id != nil { + opts = append(opts, dcom.WithIPID(id)) + } - gr, err = dc.GetIDsOfNames(ctx, &idispatch.GetIDsOfNamesRequest{ - This: ORPCThis, - IID: &dcom.IID{}, - LocaleID: LcEnglishUs, + gr, err = dc.GetIDsOfNames(ctx, &idispatch.GetIDsOfNamesRequest{ + This: ORPCThis, + IID: &dcom.IID{}, + LocaleID: LcEnglishUs, - Names: []string{obj + "\x00"}, - }, opts...) + Names: []string{obj + "\x00"}, + }, opts...) - if err != nil { - return nil, fmt.Errorf("get dispatch ID of name %q: %w", obj, err) - } + if err != nil { + return nil, fmt.Errorf("get dispatch ID of name %q: %w", obj, err) + } - if len(gr.DispatchID) < 1 { - return nil, fmt.Errorf("dispatch ID of name %q not found", obj) - } + if len(gr.DispatchID) < 1 { + return nil, fmt.Errorf("dispatch ID of name %q not found", obj) + } - irq := &idispatch.InvokeRequest{ - This: ORPCThis, - IID: &dcom.IID{}, - LocaleID: LcEnglishUs, + irq := &idispatch.InvokeRequest{ + This: ORPCThis, + IID: &dcom.IID{}, + LocaleID: LcEnglishUs, - DispatchIDMember: gr.DispatchID[0], - } + DispatchIDMember: gr.DispatchID[0], + } - if i >= len(parts)-1 { - irq.Flags = 1 - irq.DispatchParams = &oaut.DispatchParams{ArgsCount: uint32(len(args)), Args: args} - return dc.Invoke(ctx, irq, opts...) - } - irq.Flags = 2 + if i >= len(parts)-1 { + irq.Flags = 1 + irq.DispatchParams = &oaut.DispatchParams{ArgsCount: uint32(len(args)), Args: args} + return dc.Invoke(ctx, irq, opts...) + } + irq.Flags = 2 - ir, err = dc.Invoke(ctx, irq, opts...) - if err != nil { - return nil, fmt.Errorf("get properties of object %q: %w", obj, err) - } + ir, err = dc.Invoke(ctx, irq, opts...) + if err != nil { + return nil, fmt.Errorf("get properties of object %q: %w", obj, err) + } - di, ok := ir.VarResult.VarUnion.GetValue().(*oaut.Dispatch) - if !ok { - return nil, fmt.Errorf("invalid dispatch object for %q", obj) - } - id = di.InterfacePointer().GetStandardObjectReference().Std.IPID - } - return + di, ok := ir.VarResult.VarUnion.GetValue().(*oaut.Dispatch) + if !ok { + return nil, fmt.Errorf("invalid dispatch object for %q", obj) + } + id = di.InterfacePointer().GetStandardObjectReference().Std.IPID + } + return } func stringToVariant(s string) *oaut.Variant { - return &oaut.Variant{ - Size: 5, - VT: 8, - VarUnion: &oaut.Variant_VarUnion{ - Value: &oaut.Variant_VarUnion_BSTR{ - BSTR: &oaut.String{ - Data: s, - }, - }, - }, - } + return &oaut.Variant{ + Size: 5, + VT: 8, + VarUnion: &oaut.Variant_VarUnion{ + Value: &oaut.Variant_VarUnion_BSTR{ + BSTR: &oaut.String{ + Data: s, + }, + }, + }, + } } diff --git a/pkg/goexec/scmr/create.go b/pkg/goexec/scmr/create.go index b0f8774..f0245af 100644 --- a/pkg/goexec/scmr/create.go +++ b/pkg/goexec/scmr/create.go @@ -1,126 +1,129 @@ package scmrexec import ( - "context" - "fmt" - "github.com/FalconOpsLLC/goexec/internal/util" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2" - "github.com/rs/zerolog" + "context" + "fmt" + "github.com/FalconOpsLLC/goexec/internal/util" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2" + "github.com/rs/zerolog" + + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus" + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32" ) const ( - MethodCreate = "Create" + MethodCreate = "Create" ) type ScmrCreate struct { - Scmr - goexec.Cleaner - goexec.Executor + Scmr + goexec.Cleaner + goexec.Executor - IO goexec.ExecutionIO + IO goexec.ExecutionIO - NoDelete bool - NoStart bool - ServiceName string - DisplayName string + NoDelete bool + NoStart bool + ServiceName string + DisplayName string } func (m *ScmrCreate) ensure() { - if m.ServiceName == "" { - m.ServiceName = util.RandomString() - } - if m.DisplayName == "" { - m.DisplayName = m.ServiceName - } + if m.ServiceName == "" { + m.ServiceName = util.RandomString() + } + if m.DisplayName == "" { + m.DisplayName = m.ServiceName + } } func (m *ScmrCreate) Execute(ctx context.Context, in *goexec.ExecutionIO) (err error) { - m.ensure() - - log := zerolog.Ctx(ctx).With(). - Str("service", m.ServiceName).Logger() - - svc := &service{name: m.ServiceName} - - resp, err := m.ctl.CreateServiceW(ctx, &svcctl.CreateServiceWRequest{ - ServiceManager: m.scm, - ServiceName: m.ServiceName, - DisplayName: m.DisplayName, - BinaryPathName: in.String(), - ServiceType: ServiceWin32OwnProcess, - StartType: ServiceDemandStart, - DesiredAccess: ServiceAllAccess, // TODO: Replace - }) - - if err != nil { - log.Error().Err(err).Msg("Create service request failed") - return fmt.Errorf("create service request: %w", err) - } - - if resp.Return != 0 { - log.Error().Err(err).Msg("Failed to create service") - return fmt.Errorf("create service returned non-zero exit code: %02x", resp.Return) - } - - if !m.NoDelete { - m.AddCleaners(func(ctxInner context.Context) error { - - r, errInner := m.ctl.DeleteService(ctxInner, &svcctl.DeleteServiceRequest{ - Service: svc.handle, - }) - if errInner != nil { - return fmt.Errorf("delete service: %w", errInner) - } - if r.Return != 0 { - return fmt.Errorf("delete service returned non-zero exit code: %02x", r.Return) - } - log.Info().Msg("Deleted service") - - return nil - }) - } - - m.AddCleaners(func(ctxInner context.Context) error { - - r, errInner := m.ctl.CloseService(ctxInner, &svcctl.CloseServiceRequest{ - ServiceObject: svc.handle, - }) - if errInner != nil { - return fmt.Errorf("close service: %w", errInner) - } - if r.Return != 0 { - return fmt.Errorf("close service returned non-zero exit code: %02x", r.Return) - } - log.Info().Msg("Closed service handle") - - return nil - }) - - log.Info().Msg("Created service") - svc.handle = resp.Service - - if !m.NoStart { - - err = m.startService(ctx, svc) - - if err != nil { - log.Error().Err(err).Msg("Failed to start service") - return fmt.Errorf("start service: %w", err) - } - } - if svc.handle == nil { - - if err = m.Reconnect(ctx); err != nil { - return err - } - svc, err = m.openService(ctx, svc.name) - - if err != nil { - log.Error().Err(err).Msg("Failed to reopen service handle") - return fmt.Errorf("reopen service: %w", err) - } - } - - return + m.ensure() + + log := zerolog.Ctx(ctx).With(). + Str("service", m.ServiceName).Logger() + + svc := &service{name: m.ServiceName} + + resp, err := m.ctl.CreateServiceW(ctx, &svcctl.CreateServiceWRequest{ + ServiceManager: m.scm, + ServiceName: m.ServiceName, + DisplayName: m.DisplayName, + BinaryPathName: in.String(), + ServiceType: ServiceWin32OwnProcess, + StartType: ServiceDemandStart, + DesiredAccess: ServiceAllAccess, // TODO: Replace + }) + + if err != nil { + log.Error().Err(err).Msg("Create service request failed") + return fmt.Errorf("create service request: %w", err) + } + + if resp.Return != 0 { + log.Error().Err(err).Msg("Failed to create service") + return fmt.Errorf("create service returned non-zero exit code: %02x", resp.Return) + } + + if !m.NoDelete { + m.AddCleaners(func(ctxInner context.Context) error { + + r, errInner := m.ctl.DeleteService(ctxInner, &svcctl.DeleteServiceRequest{ + Service: svc.handle, + }) + if errInner != nil { + return fmt.Errorf("delete service: %w", errInner) + } + if r.Return != 0 { + return fmt.Errorf("delete service returned non-zero exit code: %02x", r.Return) + } + log.Info().Msg("Deleted service") + + return nil + }) + } + + m.AddCleaners(func(ctxInner context.Context) error { + + r, errInner := m.ctl.CloseService(ctxInner, &svcctl.CloseServiceRequest{ + ServiceObject: svc.handle, + }) + if errInner != nil { + return fmt.Errorf("close service: %w", errInner) + } + if r.Return != 0 { + return fmt.Errorf("close service returned non-zero exit code: %02x", r.Return) + } + log.Info().Msg("Closed service handle") + + return nil + }) + + log.Info().Msg("Created service") + svc.handle = resp.Service + + if !m.NoStart { + + err = m.startService(ctx, svc) + + if err != nil { + log.Error().Err(err).Msg("Failed to start service") + return fmt.Errorf("start service: %w", err) + } + } + if svc.handle == nil { + + if err = m.Reconnect(ctx); err != nil { + return err + } + svc, err = m.openService(ctx, svc.name) + + if err != nil { + log.Error().Err(err).Msg("Failed to reopen service handle") + return fmt.Errorf("reopen service: %w", err) + } + } + + return } diff --git a/pkg/goexec/scmr/module.go b/pkg/goexec/scmr/module.go index 708da23..56ce3fd 100644 --- a/pkg/goexec/scmr/module.go +++ b/pkg/goexec/scmr/module.go @@ -11,6 +11,9 @@ import ( "github.com/oiweiwei/go-msrpc/midl/uuid" "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2" "github.com/rs/zerolog" + + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus" + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32" ) type Scmr struct { diff --git a/pkg/goexec/scmr/scmr.go b/pkg/goexec/scmr/scmr.go index 4831ac7..696415b 100644 --- a/pkg/goexec/scmr/scmr.go +++ b/pkg/goexec/scmr/scmr.go @@ -42,10 +42,10 @@ const ( SERVICE_STOPPED uint32 = 0x00000001 */ - ServiceDeleteAccess uint32 = ServiceDelete - ServiceModifyAccess uint32 = ServiceQueryConfig | ServiceChangeConfig | ServiceStop | ServiceStart | ServiceDelete - ServiceCreateAccess uint32 = ScManagerCreateService | ServiceStart | ServiceStop | ServiceDelete - ServiceAllAccess uint32 = ServiceCreateAccess | ServiceModifyAccess + ServiceDeleteAccess = ServiceDelete + ServiceModifyAccess = ServiceQueryConfig | ServiceChangeConfig | ServiceStop | ServiceStart | ServiceDelete + ServiceCreateAccess = ScManagerCreateService | ServiceStart | ServiceStop | ServiceDelete + ServiceAllAccess = ServiceCreateAccess | ServiceModifyAccess ) type service struct { diff --git a/pkg/goexec/tsch/change.go b/pkg/goexec/tsch/change.go index 44877c9..908b25a 100644 --- a/pkg/goexec/tsch/change.go +++ b/pkg/goexec/tsch/change.go @@ -1,152 +1,155 @@ package tschexec import ( - "context" - "encoding/xml" - "fmt" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/FalconOpsLLC/goexec/pkg/goexec/tsch/task" - "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" - "github.com/rs/zerolog" - "regexp" - "time" + "context" + "encoding/xml" + "fmt" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/FalconOpsLLC/goexec/pkg/goexec/tsch/task" + "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" + "github.com/rs/zerolog" + "regexp" + "time" + + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus" + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32" ) const ( - FlagTaskUpdate uint32 = 0b_00000000_00000000_00000000_00000100 - MethodChange = "Change" - DefaultWaitTime = 1 * time.Second + FlagTaskUpdate uint32 = 0b_00000000_00000000_00000000_00000100 + MethodChange = "Change" + DefaultWaitTime = 1 * time.Second ) type TschChange struct { - Tsch - goexec.Executor - goexec.Cleaner + Tsch + goexec.Executor + goexec.Cleaner - IO goexec.ExecutionIO + IO goexec.ExecutionIO - WorkingDirectory string - NoStart bool - NoRevert bool - WaitTime time.Duration + WorkingDirectory string + NoStart bool + NoRevert bool + WaitTime time.Duration } func (m *TschChange) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName). - Str("method", MethodChange). - Str("task", m.TaskPath). - Logger() + log := zerolog.Ctx(ctx).With(). + Str("module", ModuleName). + Str("method", MethodChange). + Str("task", m.TaskPath). + Logger() - retrieveResponse, err := m.tsch.RetrieveTask(ctx, &itaskschedulerservice.RetrieveTaskRequest{ - Path: m.TaskPath, - }) + retrieveResponse, err := m.tsch.RetrieveTask(ctx, &itaskschedulerservice.RetrieveTaskRequest{ + Path: m.TaskPath, + }) - if err != nil { - log.Error().Err(err).Msg("Failed to retrieve task") - return fmt.Errorf("retrieve task: %w", err) - } - if retrieveResponse.Return != 0 { - log.Error().Err(err).Str("code", fmt.Sprintf("0x%02x", retrieveResponse.Return)). - Msg("Failed to retrieve task") - return fmt.Errorf("retrieve task returned non-zero exit code: %02x", retrieveResponse.Return) - } + if err != nil { + log.Error().Err(err).Msg("Failed to retrieve task") + return fmt.Errorf("retrieve task: %w", err) + } + if retrieveResponse.Return != 0 { + log.Error().Err(err).Str("code", fmt.Sprintf("0x%02x", retrieveResponse.Return)). + Msg("Failed to retrieve task") + return fmt.Errorf("retrieve task returned non-zero exit code: %02x", retrieveResponse.Return) + } - log.Info().Msg("Successfully retrieved existing task definition") - log.Debug().Str("xml", retrieveResponse.XML).Msg("Got task definition") + log.Info().Msg("Successfully retrieved existing task definition") + log.Debug().Str("xml", retrieveResponse.XML).Msg("Got task definition") - tk := task.Task{} + tk := task.Task{} - enc := regexp.MustCompile(`(?i)^<\?xml .*?\?>`) - tkStr := enc.ReplaceAllString(retrieveResponse.XML, `<?xml version="1.0" encoding="utf-8"?>`) + enc := regexp.MustCompile(`(?i)^<\?xml .*?\?>`) + tkStr := enc.ReplaceAllString(retrieveResponse.XML, `<?xml version="1.0" encoding="utf-8"?>`) - if err = xml.Unmarshal([]byte(tkStr), &tk); err != nil { - log.Error().Err(err).Msg("Failed to unmarshal task XML") + if err = xml.Unmarshal([]byte(tkStr), &tk); err != nil { + log.Error().Err(err).Msg("Failed to unmarshal task XML") - return fmt.Errorf("unmarshal task XML: %w", err) - } + return fmt.Errorf("unmarshal task XML: %w", err) + } - cmd := execIO.CommandLine() + cmd := execIO.CommandLine() - tk.Actions.Exec = append(tk.Actions.Exec, task.ExecAction{ - Command: cmd[0], - Arguments: cmd[1], - WorkingDirectory: m.WorkingDirectory, - }) + tk.Actions.Exec = append(tk.Actions.Exec, task.ExecAction{ + Command: cmd[0], + Arguments: cmd[1], + WorkingDirectory: m.WorkingDirectory, + }) - doc, err := xml.Marshal(tk) + doc, err := xml.Marshal(tk) - if err != nil { - log.Error().Err(err).Msg("failed to marshal task XML") - return fmt.Errorf("marshal task: %w", err) - } + if err != nil { + log.Error().Err(err).Msg("failed to marshal task XML") + return fmt.Errorf("marshal task: %w", err) + } - taskXml := TaskXmlHeader + string(doc) - log.Debug().Str("xml", taskXml).Msg("Serialized new task") + taskXml := TaskXmlHeader + string(doc) + log.Debug().Str("xml", taskXml).Msg("Serialized new task") - registerResponse, err := m.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{ - Path: m.TaskPath, - XML: taskXml, - Flags: FlagTaskUpdate, - }) + registerResponse, err := m.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{ + Path: m.TaskPath, + XML: taskXml, + Flags: FlagTaskUpdate, + }) - if !m.NoRevert { + if !m.NoRevert { - m.AddCleaners(func(ctxInner context.Context) error { + m.AddCleaners(func(ctxInner context.Context) error { - revertResponse, err := m.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{ - Path: m.TaskPath, - XML: retrieveResponse.XML, - Flags: FlagTaskUpdate, - }) + revertResponse, err := m.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{ + Path: m.TaskPath, + XML: retrieveResponse.XML, + Flags: FlagTaskUpdate, + }) - if err != nil { - return err - } - if revertResponse.Return != 0 { - return fmt.Errorf("revert task definition returned non-zero exit code: %02x", revertResponse.Return) - } - return nil - }) - } + if err != nil { + return err + } + if revertResponse.Return != 0 { + return fmt.Errorf("revert task definition returned non-zero exit code: %02x", revertResponse.Return) + } + return nil + }) + } - if err != nil { - log.Error().Err(err).Msg("Failed to update task") + if err != nil { + log.Error().Err(err).Msg("Failed to update task") - return fmt.Errorf("update task: %w", err) - } - if registerResponse.Return != 0 { - log.Error().Err(err).Str("code", fmt.Sprintf("0x%02x", registerResponse.Return)).Msg("Failed to update task definition") + return fmt.Errorf("update task: %w", err) + } + if registerResponse.Return != 0 { + log.Error().Err(err).Str("code", fmt.Sprintf("0x%02x", registerResponse.Return)).Msg("Failed to update task definition") - return fmt.Errorf("update task returned non-zero exit code: %02x", registerResponse.Return) - } - log.Info().Msg("Successfully updated task definition") + return fmt.Errorf("update task returned non-zero exit code: %02x", registerResponse.Return) + } + log.Info().Msg("Successfully updated task definition") - if !m.NoStart { + if !m.NoStart { - runResponse, err := m.tsch.Run(ctx, &itaskschedulerservice.RunRequest{ - Path: m.TaskPath, - }) + runResponse, err := m.tsch.Run(ctx, &itaskschedulerservice.RunRequest{ + Path: m.TaskPath, + }) - if err != nil { - log.Error().Err(err).Msg("Failed to run modified task") + if err != nil { + log.Error().Err(err).Msg("Failed to run modified task") - return fmt.Errorf("run task: %w", err) - } + return fmt.Errorf("run task: %w", err) + } - if ret := uint32(runResponse.Return); ret != 0 { - log.Error().Str("code", fmt.Sprintf("0x%08x", ret)).Msg("Run task returned non-zero exit code") + if ret := uint32(runResponse.Return); ret != 0 { + log.Error().Str("code", fmt.Sprintf("0x%08x", ret)).Msg("Run task returned non-zero exit code") - return fmt.Errorf("run task returned non-zero exit code: 0x%08x", ret) - } + return fmt.Errorf("run task returned non-zero exit code: 0x%08x", ret) + } - log.Info().Msg("Successfully started modified task") - } + log.Info().Msg("Successfully started modified task") + } - if m.WaitTime <= 0 { - m.WaitTime = DefaultWaitTime - } - time.Sleep(m.WaitTime) - return + if m.WaitTime <= 0 { + m.WaitTime = DefaultWaitTime + } + time.Sleep(m.WaitTime) + return } diff --git a/pkg/goexec/tsch/demand.go b/pkg/goexec/tsch/demand.go index 74a41fe..62e546f 100644 --- a/pkg/goexec/tsch/demand.go +++ b/pkg/goexec/tsch/demand.go @@ -1,81 +1,84 @@ package tschexec import ( - "context" - "fmt" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" - "github.com/rs/zerolog" + "context" + "fmt" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" + "github.com/rs/zerolog" + + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus" + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32" ) const ( - MethodDemand = "Demand" + MethodDemand = "Demand" ) type TschDemand struct { - Tsch - goexec.Executor - goexec.Cleaner + Tsch + goexec.Executor + goexec.Cleaner - IO goexec.ExecutionIO + IO goexec.ExecutionIO - NoDelete bool - NoStart bool - SessionId uint32 + NoDelete bool + NoStart bool + SessionId uint32 } func (m *TschDemand) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName). - Str("method", MethodDemand). - Str("task", m.TaskPath). - Logger() - - path, err := m.registerTask(ctx, - ®isterOptions{ - AllowStartOnDemand: true, - AllowHardTerminate: true, - Hidden: !m.NotHidden, - triggers: taskTriggers{}, - }, - execIO, - ) - if err != nil { - return err - } - - log.Info().Msg("Task registered") - - if !m.NoDelete { - m.AddCleaners(func(ctxInner context.Context) error { - return m.deleteTask(ctxInner, path) - }) - } - - if !m.NoStart { - - var flags uint32 - if m.SessionId != 0 { - flags |= 4 - } - - runResponse, err := m.tsch.Run(ctx, &itaskschedulerservice.RunRequest{ - Path: path, - Flags: flags, - SessionID: m.SessionId, - }) - - if err != nil { - log.Error().Err(err).Msg("Failed to run task") - return fmt.Errorf("run task: %w", err) - } - if ret := uint32(runResponse.Return); ret != 0 { - log.Error().Str("code", fmt.Sprintf("0x%08x", ret)).Msg("Task returned non-zero exit code") - return fmt.Errorf("task returned non-zero exit code: 0x%08x", ret) - } - - log.Info().Msg("Task started successfully") - } - return + log := zerolog.Ctx(ctx).With(). + Str("module", ModuleName). + Str("method", MethodDemand). + Str("task", m.TaskPath). + Logger() + + path, err := m.registerTask(ctx, + ®isterOptions{ + AllowStartOnDemand: true, + AllowHardTerminate: true, + Hidden: !m.NotHidden, + triggers: taskTriggers{}, + }, + execIO, + ) + if err != nil { + return err + } + + log.Info().Msg("Task registered") + + if !m.NoDelete { + m.AddCleaners(func(ctxInner context.Context) error { + return m.deleteTask(ctxInner, path) + }) + } + + if !m.NoStart { + + var flags uint32 + if m.SessionId != 0 { + flags |= 4 + } + + runResponse, err := m.tsch.Run(ctx, &itaskschedulerservice.RunRequest{ + Path: path, + Flags: flags, + SessionID: m.SessionId, + }) + + if err != nil { + log.Error().Err(err).Msg("Failed to run task") + return fmt.Errorf("run task: %w", err) + } + if ret := uint32(runResponse.Return); ret != 0 { + log.Error().Str("code", fmt.Sprintf("0x%08x", ret)).Msg("Task returned non-zero exit code") + return fmt.Errorf("task returned non-zero exit code: 0x%08x", ret) + } + + log.Info().Msg("Task started successfully") + } + return } diff --git a/pkg/goexec/tsch/module.go b/pkg/goexec/tsch/module.go index 677856d..6ca4cca 100644 --- a/pkg/goexec/tsch/module.go +++ b/pkg/goexec/tsch/module.go @@ -1,163 +1,166 @@ package tschexec import ( - "context" - "encoding/xml" - "errors" - "fmt" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" - "github.com/rs/zerolog" + "context" + "encoding/xml" + "errors" + "fmt" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" + "github.com/oiweiwei/go-msrpc/dcerpc" + "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" + "github.com/rs/zerolog" + + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus" + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32" ) const ( - ModuleName = "TSCH" + ModuleName = "TSCH" ) type Tsch struct { - goexec.Cleaner + goexec.Cleaner - Client *dce.Client - tsch itaskschedulerservice.TaskSchedulerServiceClient + Client *dce.Client + tsch itaskschedulerservice.TaskSchedulerServiceClient - TaskPath string - UserSid string - NotHidden bool + TaskPath string + UserSid string + NotHidden bool } type registerOptions struct { - AllowStartOnDemand bool - AllowHardTerminate bool - StartWhenAvailable bool - Hidden bool - DeleteAfter string + AllowStartOnDemand bool + AllowHardTerminate bool + StartWhenAvailable bool + Hidden bool + DeleteAfter string - triggers taskTriggers + triggers taskTriggers } func (m *Tsch) Connect(ctx context.Context) (err error) { - if err = m.Client.Connect(ctx); err == nil { - m.AddCleaners(m.Client.Close) - } - return + if err = m.Client.Connect(ctx); err == nil { + m.AddCleaners(m.Client.Close) + } + return } func (m *Tsch) Init(ctx context.Context) (err error) { - if m.Client.Dce() == nil { - return errors.New("DCE connection not initialized") - } + if m.Client.Dce() == nil { + return errors.New("DCE connection not initialized") + } - // Create ITaskSchedulerService Client - m.tsch, err = itaskschedulerservice.NewTaskSchedulerServiceClient(ctx, m.Client.Dce(), dcerpc.WithSeal()) - return + // Create ITaskSchedulerService Client + m.tsch, err = itaskschedulerservice.NewTaskSchedulerServiceClient(ctx, m.Client.Dce(), dcerpc.WithSeal()) + return } func (m *Tsch) registerTask(ctx context.Context, opts *registerOptions, in *goexec.ExecutionIO) (path string, err error) { - log := zerolog.Ctx(ctx).With(). - Str("task", m.TaskPath). - Logger() - - ctx = log.WithContext(ctx) - - principalId := "LocalSystem" - - settings := taskSettings{ - MultipleInstancesPolicy: "IgnoreNew", - IdleSettings: taskIdleSettings{ - StopOnIdleEnd: true, - RestartOnIdle: false, - }, - Enabled: true, - Priority: 7, // a pretty standard value for scheduled tasks - AllowHardTerminate: opts.AllowHardTerminate, - AllowStartOnDemand: opts.AllowStartOnDemand, - Hidden: opts.Hidden, - StartWhenAvailable: opts.StartWhenAvailable, - DeleteExpiredTaskAfter: opts.DeleteAfter, - } - - principals := taskPrincipals{ - Principals: []taskPrincipal{ - { - ID: principalId, - UserID: m.UserSid, - RunLevel: "HighestAvailable", - }, - }} - - cmdline := in.CommandLine() - - actions := taskActions{ - Context: principalId, - Exec: []taskActionExec{ - { - Command: cmdline[0], - Arguments: cmdline[1], - }, - }, - } - - def := simpleTask{ - TaskVersion: TaskXmlVersion, - TaskNamespace: TaskXmlNamespace, - Triggers: opts.triggers, - Actions: actions, - Principals: principals, - Settings: settings, - } - - // Generate task XML content. See https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/0d6383e4-de92-43e7-b0bb-a60cfa36379f - - doc, err := xml.Marshal(def) - - if err != nil { - log.Error().Err(err).Msg("failed to marshal task XML") - return "", fmt.Errorf("marshal task: %w", err) - } - - taskXml := TaskXmlHeader + string(doc) - - log.Debug().Str("content", taskXml).Msg("Generated task XML") - - registerResponse, err := m.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{ - Path: m.TaskPath, - XML: taskXml, - Flags: 0, // FEATURE: dynamic - SDDL: "", - LogonType: 0, // FEATURE: dynamic - CredsCount: 0, - Creds: nil, - }) - - if err != nil { - log.Error().Err(err).Msg("Failed to register task") - return "", fmt.Errorf("register task: %w", err) - } - log.Info().Msg("Scheduled task registered") - - return registerResponse.ActualPath, nil + log := zerolog.Ctx(ctx).With(). + Str("task", m.TaskPath). + Logger() + + ctx = log.WithContext(ctx) + + principalId := "LocalSystem" + + settings := taskSettings{ + MultipleInstancesPolicy: "IgnoreNew", + IdleSettings: taskIdleSettings{ + StopOnIdleEnd: true, + RestartOnIdle: false, + }, + Enabled: true, + Priority: 7, // a pretty standard value for scheduled tasks + AllowHardTerminate: opts.AllowHardTerminate, + AllowStartOnDemand: opts.AllowStartOnDemand, + Hidden: opts.Hidden, + StartWhenAvailable: opts.StartWhenAvailable, + DeleteExpiredTaskAfter: opts.DeleteAfter, + } + + principals := taskPrincipals{ + Principals: []taskPrincipal{ + { + ID: principalId, + UserID: m.UserSid, + RunLevel: "HighestAvailable", + }, + }} + + cmdline := in.CommandLine() + + actions := taskActions{ + Context: principalId, + Exec: []taskActionExec{ + { + Command: cmdline[0], + Arguments: cmdline[1], + }, + }, + } + + def := simpleTask{ + TaskVersion: TaskXmlVersion, + TaskNamespace: TaskXmlNamespace, + Triggers: opts.triggers, + Actions: actions, + Principals: principals, + Settings: settings, + } + + // Generate task XML content. See https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/0d6383e4-de92-43e7-b0bb-a60cfa36379f + + doc, err := xml.Marshal(def) + + if err != nil { + log.Error().Err(err).Msg("failed to marshal task XML") + return "", fmt.Errorf("marshal task: %w", err) + } + + taskXml := TaskXmlHeader + string(doc) + + log.Debug().Str("content", taskXml).Msg("Generated task XML") + + registerResponse, err := m.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{ + Path: m.TaskPath, + XML: taskXml, + Flags: 0, // FEATURE: dynamic + SDDL: "", + LogonType: 0, // FEATURE: dynamic + CredsCount: 0, + Creds: nil, + }) + + if err != nil { + log.Error().Err(err).Msg("Failed to register task") + return "", fmt.Errorf("register task: %w", err) + } + log.Info().Msg("Scheduled task registered") + + return registerResponse.ActualPath, nil } func (m *Tsch) deleteTask(ctx context.Context, taskPath string) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("path", taskPath).Logger() + log := zerolog.Ctx(ctx).With(). + Str("path", taskPath).Logger() - _, err = m.tsch.Delete(ctx, &itaskschedulerservice.DeleteRequest{ - Path: taskPath, - }) + _, err = m.tsch.Delete(ctx, &itaskschedulerservice.DeleteRequest{ + Path: taskPath, + }) - if err != nil { - log.Error().Err(err).Msg("Failed to delete task") - return fmt.Errorf("delete task: %w", err) - } + if err != nil { + log.Error().Err(err).Msg("Failed to delete task") + return fmt.Errorf("delete task: %w", err) + } - log.Info().Msg("Task deleted") + log.Info().Msg("Task deleted") - return + return } diff --git a/pkg/goexec/tsch/task/action.go b/pkg/goexec/tsch/task/action.go index de6c29f..903c633 100644 --- a/pkg/goexec/tsch/task/action.go +++ b/pkg/goexec/tsch/task/action.go @@ -85,7 +85,7 @@ type ShowMessageAction struct { // NamedValues holds zero or more <Value name="…">…</Value> entries. type NamedValues struct { - XMLName xml.Name `xml:"HeaderFields"` + XMLName xml.Name //`xml:"HeaderFields"` Value []NamedValue `xml:"Value"` } diff --git a/pkg/goexec/tsch/tsch.go b/pkg/goexec/tsch/tsch.go index ae65ca7..3cbcb2c 100644 --- a/pkg/goexec/tsch/tsch.go +++ b/pkg/goexec/tsch/tsch.go @@ -92,6 +92,8 @@ type simpleTask struct { Settings taskSettings `xml:"Settings"` } +/* + // newSettings just creates a taskSettings instance with the necessary values + a few dynamic ones func newSettings(terminate, onDemand, startWhenAvailable bool) *taskSettings { return &taskSettings{ @@ -140,6 +142,7 @@ func newTask(se *taskSettings, pr []taskPrincipal, tr taskTriggers, cmd, args st }, } } +*/ // xmlDuration is a *very* simple implementation of xs:duration - only accepts +seconds func xmlDuration(dur time.Duration) string { diff --git a/pkg/goexec/wmi/module.go b/pkg/goexec/wmi/module.go index 90bbaa8..3ae98cc 100644 --- a/pkg/goexec/wmi/module.go +++ b/pkg/goexec/wmi/module.go @@ -14,6 +14,10 @@ import ( "github.com/oiweiwei/go-msrpc/msrpc/dcom/wmi/iwbemservices/v0" "github.com/oiweiwei/go-msrpc/msrpc/dcom/wmio/query" "github.com/rs/zerolog" + + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus" + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32" + _ "github.com/oiweiwei/go-msrpc/msrpc/erref/wmi" ) const ( @@ -72,8 +76,7 @@ func (m *Wmi) Init(ctx context.Context) (err error) { var newOpts []dcerpc.Option for _, bind := range actResponse.OXIDBindings.GetStringBindings() { - stringBinding, err := dcerpc.ParseStringBinding("ncacn_ip_tcp:" + bind.NetworkAddr) // TODO: try bind.String() - + stringBinding, err := dcerpc.ParseStringBinding(bind.String()) if err != nil { log.Debug().Err(err).Msg("Failed to parse string binding") continue @@ -102,13 +105,13 @@ func (m *Wmi) Init(ctx context.Context) (err error) { NetworkResource: m.Resource, }) - log.Info().Msg("Completed NTLMLogin operation") - if err != nil { log.Error().Err(err).Msg("Failed to login on remote instance") return fmt.Errorf("login: IWbemLevel1Login::NTLMLogin: %w", err) } + log.Info().Msg("Completed NTLMLogin operation") + ipid = login.Namespace.InterfacePointer().IPID() m.servicesClient, err = iwbemservices.NewServicesClient(ctx, m.Client.Dce(), dcom.WithIPID(ipid)) @@ -122,9 +125,7 @@ func (m *Wmi) Init(ctx context.Context) (err error) { return } -func (m *Wmi) query(ctx context.Context, class, method string, values map[string]any) (outValues map[string]any, err error) { - outValues = make(map[string]any) - +func (m *Wmi) query(ctx context.Context, class, method string, values map[string]any) (map[string]any, error) { if m.servicesClient == nil { return nil, errors.New("module has not been initialized") } |