From 4f906bddd3f4261b2d45bf37a4adfe795c42967e Mon Sep 17 00:00:00 2001 From: Bryan McNulty Date: Thu, 17 Apr 2025 09:55:07 -0500 Subject: Update output,IO; add output support to WMI --- .gitignore | 2 + TODO.md | 9 +- cmd/args.go | 175 +++++++++++++++---------------- cmd/dcom.go | 95 +++++++---------- cmd/root.go | 23 ++-- cmd/tsch.go | 74 ++++++------- cmd/wmi.go | 4 +- pkg/goexec/dcom/mmc.go | 73 ++++++------- pkg/goexec/dcom/module.go | 209 +++++++++++++++++++------------------ pkg/goexec/io.go | 106 +++++++++---------- pkg/goexec/tsch/create.go | 2 +- pkg/goexec/tsch/demand.go | 114 ++++++++++---------- pkg/goexec/tsch/module.go | 261 ++++++++++++++++++++++------------------------ pkg/goexec/wmi/proc.go | 85 ++++++++------- 14 files changed, 598 insertions(+), 634 deletions(-) diff --git a/.gitignore b/.gitignore index 2686bb1..6ff46b3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ /go.work.sum *.pcap *.pcapng +*.log +*.json diff --git a/TODO.md b/TODO.md index 308a8e4..bedc988 100644 --- a/TODO.md +++ b/TODO.md @@ -6,10 +6,10 @@ - [X] Clean up TSCH module - [X] Session hijacking -- [ ] Add more trigger types -- [ ] Add command to tsch - update task if it already exists. See https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/849c131a-64e4-46ef-b015-9d4c599c5167 (`flags` argument) - [X] Generate random name/path - [X] Output +- [ ] Add more trigger types +- [ ] Add command to tsch - update task if it already exists. See https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/849c131a-64e4-46ef-b015-9d4c599c5167 (`flags` argument) ### SCMR @@ -24,12 +24,13 @@ - [X] Add DCOM module - [X] MMC20.Application method - [ ] Output +- [ ] ShellWindows & ShellBrowserWindow ### WMI - [X] Add WMI module - [X] Clean up WMI module -- [ ] Output +- [X] Output - [ ] WMI `reg` subcommand - read & edit the registry - [ ] File transfer functionality @@ -43,11 +44,9 @@ ## Resolve Eventually -- [ ] Packet stub encryption for ncacn_ip_tcp doesn't appear to be working... - [ ] `--shell` option - [ ] Add Go tests - [ ] ability to specify multiple targets -- [ ] Standardize modules to interface for future use ### WinRM diff --git a/cmd/args.go b/cmd/args.go index b2b6fe2..ca61c22 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -1,142 +1,139 @@ package cmd import ( - "context" - "errors" - "fmt" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "os" + "context" + "errors" + "fmt" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "os" ) func registerRpcFlags(cmd *cobra.Command) { - rpcFlags := pflag.NewFlagSet("RPC", pflag.ExitOnError) + 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") + 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.PersistentFlags().AddFlagSet(rpcFlags) - cmd.MarkFlagsMutuallyExclusive("endpoint", "epm-filter") - cmd.MarkFlagsMutuallyExclusive("no-epm", "epm-filter") + cmd.MarkFlagsMutuallyExclusive("endpoint", "epm-filter") + cmd.MarkFlagsMutuallyExclusive("no-epm", "epm-filter") } func registerProcessExecutionArgs(cmd *cobra.Command) { - group := pflag.NewFlagSet("Execution", pflag.ExitOnError) + 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") + group.StringVarP(&exec.Input.Arguments, "args", "a", "", "Command line arguments") + group.StringVarP(&exec.Input.Command, "command", "c", "", "Windows process command line (executable & arguments)") + group.StringVarP(&exec.Input.Executable, "executable", "e", "", "Windows executable to invoke") - cmd.PersistentFlags().AddFlagSet(group) + cmd.PersistentFlags().AddFlagSet(group) - cmd.MarkFlagsOneRequired("executable", "command") - cmd.MarkFlagsMutuallyExclusive("executable", "command") + cmd.MarkFlagsOneRequired("executable", "command") + cmd.MarkFlagsMutuallyExclusive("executable", "command") } func registerExecutionOutputArgs(cmd *cobra.Command) { - group := pflag.NewFlagSet("Output", pflag.ExitOnError) + 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") + 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) + 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) { + return func(cmd *cobra.Command, args []string) (err error) { - for _, req := range reqs { - if err = req(cmd, args); err != nil { - return - } - } - return - } + 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) { + return func(cmd *cobra.Command, args []string) (err error) { - if len(args) != 1 { - return errors.New("command require exactly one positional argument: [target]") - } + 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, 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 - } + 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"), + return args( + argsTarget("cifs"), - func(_ *cobra.Command, _ []string) error { + func(_ *cobra.Command, _ []string) error { - smbClient.Credential = credential - smbClient.Target = target - smbClient.Proxy = proxy + smbClient.Credential = credential + smbClient.Target = target + smbClient.Proxy = proxy - return smbClient.Parse(context.TODO()) - }, - ) + return smbClient.Parse(context.TODO()) + }, + ) } func argsRpcClient(proto string) func(cmd *cobra.Command, args []string) error { - return args( - argsTarget(proto), + return args( + argsTarget(proto), - func(cmd *cobra.Command, args []string) (err error) { + func(cmd *cobra.Command, args []string) (err error) { - rpcClient.Target = target - rpcClient.Credential = credential - rpcClient.Proxy = proxy + rpcClient.Target = target + rpcClient.Credential = credential + rpcClient.Proxy = proxy - return rpcClient.Parse(context.TODO()) - }, - ) + return rpcClient.Parse(context.TODO()) + }, + ) } func argsOutput(methods ...string) func(cmd *cobra.Command, args []string) error { - var as []func(*cobra.Command, []string) error + var as []func(*cobra.Command, []string) error - for _, method := range methods { - if method == "smb" { - as = append(as, argsSmbClient()) - } - } + for _, method := range methods { + if method == "smb" { + as = append(as, argsSmbClient()) + } + } - return args(append(as, func(*cobra.Command, []string) (err error) { + return args(append(as, func(*cobra.Command, []string) (err error) { - if outputPath != "" { - if outputPath == "-" { - exec.Output.Writer = os.Stdout + if outputPath != "" { + if outputPath == "-" { + exec.Output.Writer = os.Stdout - } else if outputPath != "" { - - if exec.Output.Writer, err = os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE, 0644); err != nil { - log.Fatal().Err(err).Msg("Failed to open output file") - } - } - } - return - })...) + } else if exec.Output.Writer, err = os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE, 0644); err != nil { + log.Fatal().Err(err).Msg("Failed to open output file") + } + } + return + })...) } diff --git a/cmd/dcom.go b/cmd/dcom.go index 5bcec78..c671252 100644 --- a/cmd/dcom.go +++ b/cmd/dcom.go @@ -1,37 +1,40 @@ package cmd import ( - "context" - 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() { - registerRpcFlags(dcomCmd) - dcomMmcCmdInit() - dcomCmd.AddCommand(dcomMmcCmd) + registerRpcFlags(dcomCmd) + dcomMmcCmdInit() + dcomCmd.AddCommand(dcomMmcCmd) } func dcomMmcCmdInit() { - dcomMmcCmd.Flags().StringVarP(&dcomMmc.WorkingDirectory, "directory", "d", `C:\`, "Working directory") - dcomMmcCmd.Flags().StringVar(&dcomMmc.WindowState, "window", "Minimized", "Window state") + dcomMmcCmd.Flags().StringVarP(&dcomMmc.WorkingDirectory, "directory", "d", `C:\`, "Working directory") + dcomMmcCmd.Flags().StringVar(&dcomMmc.WindowState, "window", "Minimized", "Window state") - registerProcessExecutionArgs(dcomMmcCmd) + registerProcessExecutionArgs(dcomMmcCmd) + registerExecutionOutputArgs(dcomMmcCmd) } var ( - 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: + 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. @@ -41,39 +44,19 @@ 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: 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") - } - - defer func() { - closeErr := rpcClient.Close(ctx) - if closeErr != nil { - log.Error().Err(closeErr).Msg("Failed to close connection") - } - }() - - if err = dcomMmc.Init(ctx, &rpcClient); err != nil { - log.Error().Err(err).Msg("Module initialization failed") - returnCode = 1 - return - } - - if err = dcomMmc.Execute(ctx, exec.Input); err != nil { - log.Error().Err(err).Msg("Execution failed") - returnCode = 1 - } - }, - } + Args: args( + argsRpcClient("host"), + argsOutput("smb"), + ), + Run: func(cmd *cobra.Command, args []string) { + dcomMmc.Dcom.Client = &rpcClient + dcomMmc.IO = exec + + ctx := log.WithContext(gssapi.NewSecurityContext(context.TODO())) + + 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 3d696aa..913a44a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -54,15 +54,17 @@ var ( log = log.Level(zerolog.DebugLevel) } - 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, - DeleteOutputFile: exec.Output.NoDelete, // TEMP + if outputPath != "" { + 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, + DeleteOutputFile: !exec.Output.NoDelete, + } } } }, @@ -81,13 +83,10 @@ func init() { dcomCmdInit() rootCmd.AddCommand(dcomCmd) - wmiCmdInit() rootCmd.AddCommand(wmiCmd) - scmrCmdInit() rootCmd.AddCommand(scmrCmd) - tschCmdInit() rootCmd.AddCommand(tschCmd) } diff --git a/cmd/tsch.go b/cmd/tsch.go index 328adcd..2e8370e 100644 --- a/cmd/tsch.go +++ b/cmd/tsch.go @@ -2,6 +2,7 @@ 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" @@ -20,42 +21,19 @@ func tschCmdInit() { tschCmd.AddCommand(tschCreateCmd) } -func argsTschTaskIdentifiers(name, path string) error { - switch { - case path != "": - return tschexec.ValidateTaskPath(path) - case name != "": - return tschexec.ValidateTaskName(name) - default: - } - return nil -} - -func argsTschDemand(_ *cobra.Command, _ []string) error { - return argsTschTaskIdentifiers(tschDemand.TaskName, tschDemand.TaskPath) -} - -func argsTschCreate(_ *cobra.Command, _ []string) error { - return argsTschTaskIdentifiers(tschCreate.TaskName, tschCreate.TaskPath) -} - 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().StringVarP(&tschTask, "task", "t", "", "Name or path of the new task") 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().StringVarP(&tschTask, "task", "t", "", "Name or path of the new task") + tschCreateCmd.Flags().DurationVar(&tschCreate.StopDelay, "delay-stop", 5*time.Second, "Delay between task execution and termination. This won't stop the spawned process") 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") @@ -64,14 +42,27 @@ func tschCreateCmdInit() { registerProcessExecutionArgs(tschCreateCmd) registerExecutionOutputArgs(tschCreateCmd) +} - tschCreateCmd.MarkFlagsMutuallyExclusive("name", "path") +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 name or path: %q", tschTask) + } + return nil } var ( tschDemand tschexec.TschDemand tschCreate tschexec.TschCreate + tschTask string + tschCmd = &cobra.Command{ Use: "tsch", Short: "Establish execution via Windows Task Scheduler (MS-TSCH)", @@ -93,18 +84,18 @@ References: Args: args( argsRpcClient("cifs"), argsOutput("smb"), - argsTschDemand, + argsTask, ), - Run: func(cmd *cobra.Command, args []string) { - tschDemand.Client = &rpcClient + Run: func(*cobra.Command, []string) { tschDemand.IO = exec + tschDemand.Client = &rpcClient + tschDemand.TaskPath = tschTask - if tschDemand.TaskName == "" && tschDemand.TaskPath == "" { - tschDemand.TaskPath = `\` + util.RandomString() - } - - ctx := log.WithContext(gssapi.NewSecurityContext(context.TODO())) + 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") @@ -129,18 +120,17 @@ References: Args: args( argsRpcClient("cifs"), argsOutput("smb"), - argsTschCreate, + argsTask, ), - Run: func(cmd *cobra.Command, args []string) { + Run: func(*cobra.Command, []string) { tschCreate.Tsch.Client = &rpcClient tschCreate.IO = exec - if tschCreate.TaskName == "" && tschDemand.TaskPath == "" { - tschCreate.TaskPath = `\` + util.RandomString() - } - - ctx := log.WithContext(gssapi.NewSecurityContext(context.TODO())) + 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") diff --git a/cmd/wmi.go b/cmd/wmi.go index 6fde395..42475f0 100644 --- a/cmd/wmi.go +++ b/cmd/wmi.go @@ -28,7 +28,7 @@ func wmiCallCmdInit() { 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"})`) + wmiCallCmd.Flags().StringVarP(&wmiArguments, "args", "A", "{}", `WMI Method argument(s) in JSON dictionary format (i.e. {"Command":"calc.exe"})`) if err := wmiCallCmd.MarkFlagRequired("class"); err != nil { panic(err) @@ -131,7 +131,7 @@ References: ctx := log.With(). Str("module", "wmi"). Str("method", "proc"). - Logger().WithContext(gssapi.NewSecurityContext(context.TODO())) + Logger().WithContext(gssapi.NewSecurityContext(context.Background())) if err := goexec.ExecuteCleanMethod(ctx, &wmiProc, &exec); err != nil { log.Fatal().Err(err).Msg("Operation failed") diff --git a/pkg/goexec/dcom/mmc.go b/pkg/goexec/dcom/mmc.go index 9c92af3..ecb3a74 100644 --- a/pkg/goexec/dcom/mmc.go +++ b/pkg/goexec/dcom/mmc.go @@ -1,50 +1,51 @@ package dcomexec import ( - "context" - "fmt" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/rs/zerolog" + "context" + "fmt" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/rs/zerolog" ) const ( - MethodMmc = "MMC" // MMC20.Application::Document.ActiveView.ExecuteShellCommand + MethodMmc = "MMC" // MMC20.Application::Document.ActiveView.ExecuteShellCommand ) type DcomMmc struct { - DcomExec + Dcom - WorkingDirectory string - WindowState string + IO goexec.ExecutionIO + + WorkingDirectory string + WindowState string } // Execute will perform command execution via the MMC20.Application DCOM object. -func (m *DcomMmc) Execute(ctx context.Context, in *goexec.ExecutionInput) (err error) { - - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName). - Str("method", MethodMmc). - Logger() - - method := "Document.ActiveView.ExecuteShellCommand" - - var args = in.Arguments - if args == "" { - args = " " // the process arguments can't be a blank string - } - - // Arguments must be passed in reverse order - if _, err := callComMethod(ctx, - m.dispatchClient, - method, - stringToVariant(m.WindowState), - stringToVariant(in.Arguments), - stringToVariant(m.WorkingDirectory), - stringToVariant(in.Executable)); err != nil { - - log.Error().Err(err).Msg("Failed to call method") - return fmt.Errorf("call %q: %w", method, err) - } - log.Info().Msg("Method call successful") - return +func (m *DcomMmc) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) { + + log := zerolog.Ctx(ctx).With(). + Str("module", ModuleName). + Str("method", MethodMmc). + Logger() + + method := "Document.ActiveView.ExecuteShellCommand" + + cmdline := execIO.CommandLine() + proc := cmdline[0] + args := cmdline[1] + + // Arguments must be passed in reverse order + if _, err := callComMethod(ctx, + m.dispatchClient, + method, + stringToVariant(m.WindowState), + stringToVariant(args), + stringToVariant(m.WorkingDirectory), + stringToVariant(proc)); err != nil { + + log.Error().Err(err).Msg("Failed to call method") + return fmt.Errorf("call %q: %w", method, err) + } + log.Info().Msg("Method call successful") + return } diff --git a/pkg/goexec/dcom/module.go b/pkg/goexec/dcom/module.go index 47dc7ca..fd331d2 100644 --- a/pkg/goexec/dcom/module.go +++ b/pkg/goexec/dcom/module.go @@ -1,111 +1,120 @@ package dcomexec import ( - "context" - "errors" - "fmt" - "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" - "github.com/oiweiwei/go-msrpc/dcerpc" - "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/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/msrpc/dcom" + "github.com/oiweiwei/go-msrpc/msrpc/dcom/iremotescmactivator/v0" + "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0" + "github.com/rs/zerolog" ) const ( - ModuleName = "DCOM" + ModuleName = "DCOM" ) -type DcomExec struct { - client *dce.Client - dispatchClient idispatch.DispatchClient +type Dcom struct { + goexec.Cleaner + + Client *dce.Client + + dispatchClient idispatch.DispatchClient +} + +func (m *Dcom) Connect(ctx context.Context) (err error) { + + if err = m.Client.Connect(ctx); err == nil { + m.AddCleaner(m.Client.Close) + } + return } -func (m *DcomExec) Init(ctx context.Context, c *dce.Client) (err error) { - - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName).Logger() - - m.client = c - - if m.client.Dce() == nil { - return errors.New("DCE connection not initialized") - } - - opts := []dcerpc.Option{ - dcerpc.WithSign(), - } - - inst := &dcom.InstantiationInfoData{ - ClassID: &MmcClsid, - 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") - } - - oIPID := pi.InterfaceData[0].IPID() - opts = append(opts, si.RemoteReply.OXIDBindings.EndpointsByProtocol("ncacn_ip_tcp")...) // TODO - - err = c.Reconnect(ctx, opts...) - if err != nil { - return err - } - log.Info().Msg("created new DCERPC dialer") - - m.dispatchClient, err = idispatch.NewDispatchClient(ctx, c.Dce(), dcom.WithIPID(oIPID)) - if err != nil { - return err - } - log.Info().Msg("created IDispatch client") - - 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") + } + + opts := []dcerpc.Option{ + dcerpc.WithSign(), + } + + inst := &dcom.InstantiationInfoData{ + ClassID: &MmcClsid, + 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") + } + + opts = append(opts, si.RemoteReply.OXIDBindings.EndpointsByProtocol("ncacn_ip_tcp")...) // TODO + + 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/io.go b/pkg/goexec/io.go index c26fc7f..6bfb76e 100644 --- a/pkg/goexec/io.go +++ b/pkg/goexec/io.go @@ -1,94 +1,84 @@ package goexec import ( - "bytes" - "context" - "fmt" - "io" - "os" + "context" + "fmt" + "io" + "strings" ) type OutputProvider interface { - GetOutput(ctx context.Context, writer io.Writer) (err error) - Clean(ctx context.Context) (err error) + GetOutput(ctx context.Context, writer io.Writer) (err error) + Clean(ctx context.Context) (err error) } type ExecutionIO struct { - Cleaner + Cleaner - Input *ExecutionInput - Output *ExecutionOutput + Input *ExecutionInput + Output *ExecutionOutput } type ExecutionOutput struct { - NoDelete bool - RemotePath string - Provider OutputProvider - Writer io.WriteCloser + NoDelete bool + RemotePath string + Provider OutputProvider + Writer io.WriteCloser } type ExecutionInput struct { - FilePath string - Executable string - ExecutablePath string - Arguments string - CommandLine string + FilePath string + Executable string + ExecutablePath string + Arguments string + Command string } func (execIO *ExecutionIO) GetOutput(ctx context.Context) (err error) { - if execIO.Output.Provider != nil { - return execIO.Output.Provider.GetOutput(ctx, execIO.Output.Writer) - } - return nil + if execIO.Output.Provider != nil { + return execIO.Output.Provider.GetOutput(ctx, execIO.Output.Writer) + } + return nil } -func (execIO *ExecutionIO) CommandLine() string { - return execIO.Input.Command() +func (execIO *ExecutionIO) CommandLine() (cmd []string) { + if execIO.Output.Provider != nil && execIO.Output.RemotePath != "" { + return []string{ + `C:\Windows\System32\cmd.exe`, + fmt.Sprintf(`/C %s > %s 2>&1`, execIO.Input.String(), execIO.Output.RemotePath), + } + } + return execIO.Input.CommandLine() } func (execIO *ExecutionIO) Clean(ctx context.Context) (err error) { - if execIO.Output.Provider != nil { - return execIO.Output.Provider.Clean(ctx) - } - return nil + if execIO.Output.Provider != nil { + return execIO.Output.Provider.Clean(ctx) + } + return nil } func (execIO *ExecutionIO) String() (cmd string) { - - cmd = execIO.Input.Command() - - if execIO.Output.Provider != nil && execIO.Output.RemotePath != "" { - return fmt.Sprintf(`C:\Windows\System32\cmd.exe /C %s > %s`, cmd, execIO.Output.RemotePath) - } - return + return strings.Join(execIO.CommandLine(), " ") } -func (i *ExecutionInput) Command() string { - - if i.CommandLine == "" { +func (i *ExecutionInput) CommandLine() (cmd []string) { + cmd = make([]string, 2) + cmd[1] = i.Arguments - if i.ExecutablePath != "" { - i.CommandLine = i.ExecutablePath + switch { + case i.Command != "": + return strings.SplitN(i.Command, " ", 2) - } else if i.Executable != "" { - i.CommandLine = i.Executable - } + case i.ExecutablePath != "": + cmd[0] = i.ExecutablePath - if i.Arguments != "" { - i.CommandLine += " " + i.Arguments - } - } - return i.CommandLine + case i.Executable != "": + cmd[0] = i.Executable + } + return cmd } func (i *ExecutionInput) String() string { - return i.Command() -} - -func (i *ExecutionInput) UploadReader(_ context.Context) (reader io.Reader, err error) { - - if i.FilePath != "" { - return os.OpenFile(i.FilePath, os.O_RDONLY, 0) - } - return bytes.NewBufferString(i.Command()), nil + return strings.Join(i.CommandLine(), " ") } diff --git a/pkg/goexec/tsch/create.go b/pkg/goexec/tsch/create.go index 8c99c82..43964e4 100644 --- a/pkg/goexec/tsch/create.go +++ b/pkg/goexec/tsch/create.go @@ -32,7 +32,7 @@ func (m *TschCreate) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (e log := zerolog.Ctx(ctx).With(). Str("module", ModuleName). Str("method", MethodCreate). - Str("task", m.TaskName). + Str("task", m.TaskPath). Logger() startTime := time.Now().UTC().Add(m.StartDelay) diff --git a/pkg/goexec/tsch/demand.go b/pkg/goexec/tsch/demand.go index 11522dd..ed6c043 100644 --- a/pkg/goexec/tsch/demand.go +++ b/pkg/goexec/tsch/demand.go @@ -1,81 +1,81 @@ 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" ) 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, in *goexec.ExecutionIO) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName). - Str("method", MethodDemand). - Str("task", m.TaskName). - Logger() + 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{}, - }, - in, - ) - if err != nil { - return err - } + path, err := m.registerTask(ctx, + ®isterOptions{ + AllowStartOnDemand: true, + AllowHardTerminate: true, + Hidden: !m.NotHidden, + triggers: taskTriggers{}, + }, + in, + ) + if err != nil { + return err + } - log.Info().Msg("Task registered") + log.Info().Msg("Task registered") - if !m.NoDelete { - m.AddCleaner(func(ctxInner context.Context) error { - return m.deleteTask(ctxInner, path) - }) - } + if !m.NoDelete { + m.AddCleaner(func(ctxInner context.Context) error { + return m.deleteTask(ctxInner, path) + }) + } - if !m.NoStart { + if !m.NoStart { - var flags uint32 - if m.SessionId != 0 { - flags |= 4 - } + var flags uint32 + if m.SessionId != 0 { + flags |= 4 + } - runResponse, err := m.tsch.Run(ctx, &itaskschedulerservice.RunRequest{ - Path: path, - Flags: flags, - SessionID: m.SessionId, - }) + 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) - } + 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.Info().Msg("Task started successfully") + } + return } diff --git a/pkg/goexec/tsch/module.go b/pkg/goexec/tsch/module.go index 13d7b24..74ded2f 100644 --- a/pkg/goexec/tsch/module.go +++ b/pkg/goexec/tsch/module.go @@ -1,173 +1,162 @@ 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/msrpc/tsch/itaskschedulerservice/v1" - "github.com/rs/zerolog" - "strings" + "context" + "encoding/xml" + "errors" + "fmt" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" + "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" + "github.com/rs/zerolog" ) const ( - ModuleName = "TSCH" + ModuleName = "TSCH" ) type Tsch struct { - goexec.Cleaner + goexec.Cleaner - Client *dce.Client - tsch itaskschedulerservice.TaskSchedulerServiceClient + Client *dce.Client + tsch itaskschedulerservice.TaskSchedulerServiceClient - TaskName string - 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.AddCleaner(m.Client.Close) - } - return + if err = m.Client.Connect(ctx); err == nil { + m.AddCleaner(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()) - return -} - -func (m *Tsch) taskPath() string { - if m.TaskPath == "" { - m.TaskPath = `\` + m.TaskName - } - return m.TaskPath + // Create ITaskSchedulerService Client + m.tsch, err = itaskschedulerservice.NewTaskSchedulerServiceClient(ctx, m.Client.Dce()) + 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.TaskName). - Logger() - - ctx = log.WithContext(ctx) - - principalId := "1" - - 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, // TODO: dynamic - UserID: m.UserSid, - RunLevel: "HighestAvailable", - }, - }} - - e := taskActionExec{} - - if ea := strings.SplitN(in.String(), " ", 2); len(ea) == 1 { - e.Command = ea[0] - } else { - e.Command = ea[0] - e.Arguments = ea[1] - } - - actions := taskActions{ - Context: principalId, - Exec: []taskActionExec{e}, - } - - def := task{ - 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) - } - - return registerResponse.ActualPath, nil + log := zerolog.Ctx(ctx).With(). + Str("task", m.TaskPath). + Logger() + + ctx = log.WithContext(ctx) + + principalId := "1" // This value can be anything + + 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 := task{ + 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/wmi/proc.go b/pkg/goexec/wmi/proc.go index 444643b..fe22bdf 100644 --- a/pkg/goexec/wmi/proc.go +++ b/pkg/goexec/wmi/proc.go @@ -1,54 +1,59 @@ package wmiexec import ( - "context" - "errors" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/rs/zerolog" + "context" + "errors" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/rs/zerolog" ) const ( - MethodProc = "Proc" + MethodProc = "Proc" ) type WmiProc struct { - Wmi - IO goexec.ExecutionIO - WorkingDirectory string + Wmi + IO goexec.ExecutionIO + WorkingDirectory string } func (m *WmiProc) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("module", ModuleName). - Str("method", MethodProc). - Logger() - ctx = log.WithContext(ctx) - - if execIO == nil { - return errors.New("execution IO is nil") - } - - out, err := m.query(ctx, - "Win32_Process", - "Create", - - map[string]any{ - "CommandLine": execIO.String(), - "WorkingDir": m.WorkingDirectory, - }, - ) - if err != nil { - return - } - - if pid := out["ProcessId"].(uint32); pid != 0 { - log = log.With().Uint32("pid", pid).Logger() - } - log.Info().Err(err).Msg("Process created") - - if ret := out["ReturnValue"].(uint32); ret != 0 { - log.Error().Err(err).Uint32("return", ret).Msg("Process returned non-zero exit code") - } - return + log := zerolog.Ctx(ctx).With(). + Str("module", ModuleName). + Str("method", MethodProc). + Logger() + ctx = log.WithContext(ctx) + + if execIO == nil { + return errors.New("execution IO is nil") + } + + out, err := m.query(ctx, + "Win32_Process", + "Create", + map[string]any{ + "CommandLine": execIO.String(), + "WorkingDir": m.WorkingDirectory, + }, + ) + if err != nil { + return + } + + if pid, ok := out["ProcessId"].(uint32); pid != 0 { + log = log.With().Uint32("pid", pid).Logger() + + } else if !ok { + return errors.New("process creation failed") + } + log.Info().Err(err).Msg("Process created") + + if ret, ok := out["ReturnValue"].(uint32); ret != 0 { + log.Error().Err(err).Uint32("return", ret).Msg("Process returned non-zero exit code") + + } else if !ok { + return errors.New("invalid call response") + } + return } -- cgit v1.2.3