diff options
author | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-04-18 04:07:54 -0500 |
---|---|---|
committer | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-04-18 04:07:54 -0500 |
commit | e16fcfc6ce0bdffdac4a73b5922a792972348a80 (patch) | |
tree | 0c8f01493e18395306eb414d2889847f55894fd2 | |
parent | 5d2734e51b62f7048dc8be25cca05fb71da4f521 (diff) | |
download | goexec-e16fcfc6ce0bdffdac4a73b5922a792972348a80.tar.gz goexec-e16fcfc6ce0bdffdac4a73b5922a792972348a80.zip |
Some CLI tweaks
-rw-r--r-- | cmd/dcom.go | 89 | ||||
-rw-r--r-- | cmd/root.go | 342 | ||||
-rw-r--r-- | cmd/scmr.go | 15 | ||||
-rw-r--r-- | cmd/tsch.go | 11 | ||||
-rw-r--r-- | cmd/wmi.go | 213 | ||||
-rw-r--r-- | go.mod | 3 | ||||
-rw-r--r-- | go.sum | 19 | ||||
-rw-r--r-- | pkg/goexec/dcom/mmc.go | 68 | ||||
-rw-r--r-- | pkg/goexec/dcom/module.go | 211 |
9 files changed, 503 insertions, 468 deletions
diff --git a/cmd/dcom.go b/cmd/dcom.go index c671252..f4e4f91 100644 --- a/cmd/dcom.go +++ b/cmd/dcom.go @@ -1,62 +1,63 @@ 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() { - 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) - registerExecutionOutputArgs(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: "Execute with Distributed Component Object Model (MS-DCOM)", + GroupID: "module", + Args: cobra.NoArgs, + } + + 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 execute system commands. + and ultimately spawn a process on the remote host. References: - https://www.scorpiones.io/articles/lateral-movement-using-dcom-objects - https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/ - https://github.com/fortra/impacket/blob/master/examples/dcomexec.py - https://learn.microsoft.com/en-us/previous-versions/windows/desktop/mmc/view-executeshellcommand + - https://www.scorpiones.io/articles/lateral-movement-using-dcom-objects + - https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/ + - https://github.com/fortra/impacket/blob/master/examples/dcomexec.py + - https://learn.microsoft.com/en-us/previous-versions/windows/desktop/mmc/view-executeshellcommand `, - 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") - } - }, - } + 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 300588d..815730e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,167 +1,201 @@ package cmd import ( - "fmt" - "github.com/FalconOpsLLC/goexec/internal/util" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" - "github.com/FalconOpsLLC/goexec/pkg/goexec/smb" - "github.com/RedTeamPentesting/adauth" - "github.com/oiweiwei/go-msrpc/ssp" - "github.com/oiweiwei/go-msrpc/ssp/gssapi" - "github.com/rs/zerolog" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "io" - "os" + "fmt" + "github.com/FalconOpsLLC/goexec/internal/util" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + "github.com/FalconOpsLLC/goexec/pkg/goexec/dce" + "github.com/FalconOpsLLC/goexec/pkg/goexec/smb" + "github.com/RedTeamPentesting/adauth" + "github.com/oiweiwei/go-msrpc/ssp" + "github.com/oiweiwei/go-msrpc/ssp/gssapi" + "github.com/rs/zerolog" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "golang.org/x/term" + "io" + "os" ) var ( - returnCode int - outputMethod string - outputPath string - proxy 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 - // =============== - - rpcClient dce.Client - smbClient smb.Client - - 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: `Windows remote execution multitool`, - Long: ``, - - 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 - } - 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) - } - - if proxy != "" { - rpcClient.Proxy = proxy - smbClient.Proxy = proxy - } - - 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, - } - } - } - return - }, - - PersistentPostRun: func(cmd *cobra.Command, args []string) { - if err := logFile.Close(); err != nil { - // ... - } - }, - } + flagGroups = map[string]*pflag.FlagSet{} + + returnCode int + outputMethod string + outputPath string + proxy 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 + // =============== + + rpcClient dce.Client + smbClient smb.Client + + 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: `Windows remote execution multitool`, + Long: ``, + + 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 + } + 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) + } + + if proxy != "" { + rpcClient.Proxy = proxy + smbClient.Proxy = proxy + } + + 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, + } + } + } + return + }, + + PersistentPostRun: func(cmd *cobra.Command, args []string) { + if err := logFile.Close(); err != nil { + // ... + } + }, + } ) +func addFlagSet(fs *pflag.FlagSet) { + flagGroups[fs.Name()] = fs +} + +func moduleFlags(cmd *cobra.Command, module string) (fs *pflag.FlagSet) { + fs, _ = flagGroups[module] + return +} + +// Uses the users terminal size or width of 80 if cannot determine users width +// Based on https://github.com/spf13/cobra/issues/1805#issuecomment-1246192724 +func wrappedFlagUsages(cmd *pflag.FlagSet) string { + fd := int(os.Stdout.Fd()) + width := 80 + + // Get the terminal width and dynamically set + termWidth, _, err := term.GetSize(fd) + if err == nil { + width = termWidth + } + + return cmd.FlagUsagesWrapped(width - 1) +} + func init() { - // Auth init - { - gssapi.AddMechanism(ssp.SPNEGO) - gssapi.AddMechanism(ssp.NTLM) - gssapi.AddMechanism(ssp.KRB5) - } - - // Cobra init - { - cobra.EnableCommandSorting = false - - rootCmd.InitDefaultVersionFlag() - rootCmd.InitDefaultHelpCmd() - - // Logging flags - { - logOpts := pflag.NewFlagSet("Logging", pflag.ExitOnError) - logOpts.BoolVar(&logDebug, "debug", false, "Enable debug logging") - logOpts.BoolVar(&logJson, "json", false, "Write logging output in JSON lines") - logOpts.BoolVar(&logQuiet, "quiet", false, "Disable info logging") - logOpts.StringVarP(&logOutput, "log-file", "O", "", "Write JSON logging output to file") - rootCmd.PersistentFlags().AddFlagSet(logOpts) - } - - // Global networking flags - { - netOpts := pflag.NewFlagSet("Network", pflag.ExitOnError) - netOpts.StringVarP(&proxy, "proxy", "x", "", "Proxy URL") - rootCmd.PersistentFlags().AddFlagSet(netOpts) - } - - // Authentication flags - { - adAuthOpts = &adauth.Options{ - Debug: log.Debug().Msgf, - } - authOpts := pflag.NewFlagSet("Authentication", pflag.ExitOnError) - adAuthOpts.RegisterFlags(authOpts) - rootCmd.PersistentFlags().AddFlagSet(authOpts) - } - - // 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) + } + + // Cobra init + { + cobra.EnableCommandSorting = false + + rootCmd.InitDefaultVersionFlag() + rootCmd.InitDefaultHelpCmd() + + modules := &cobra.Group{ + ID: "module", + Title: "Execution Modules:", + } + rootCmd.AddGroup(modules) + + // Logging flags + { + logOpts := pflag.NewFlagSet("Logging", pflag.ExitOnError) + logOpts.BoolVar(&logDebug, "debug", false, "Enable debug logging") + logOpts.BoolVar(&logJson, "json", false, "Write logging output in JSON lines") + logOpts.BoolVar(&logQuiet, "quiet", false, "Disable info logging") + logOpts.StringVarP(&logOutput, "log-file", "O", "", "Write JSON logging output to file") + rootCmd.PersistentFlags().AddFlagSet(logOpts) + flagGroups["Logging"] = logOpts + } + + // Global networking flags + { + netOpts := pflag.NewFlagSet("Network", pflag.ExitOnError) + netOpts.StringVarP(&proxy, "proxy", "x", "", "Proxy URL") + rootCmd.PersistentFlags().AddFlagSet(netOpts) + } + + // Authentication flags + { + adAuthOpts = &adauth.Options{ + Debug: log.Debug().Msgf, + } + authOpts := pflag.NewFlagSet("Authentication", pflag.ExitOnError) + adAuthOpts.RegisterFlags(authOpts) + rootCmd.PersistentFlags().AddFlagSet(authOpts) + } + + // 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/scmr.go b/cmd/scmr.go index 08e23d7..b67b2a6 100644 --- a/cmd/scmr.go +++ b/cmd/scmr.go @@ -64,20 +64,21 @@ var ( scmrDelete scmrexec.ScmrDelete scmrCmd = &cobra.Command{ - Use: "scmr", - Short: "Establish execution via SCMR", - Args: cobra.NoArgs, + Use: "scmr", + Short: "Execute with Service Control Manager Remote (MS-SCMR)", + GroupID: "module", + Args: cobra.NoArgs, } scmrCreateCmd = &cobra.Command{ Use: "create [target]", - Short: "Create & run a new Windows service to gain execution", + Short: "Spawn a remote process by creating & running a Windows service", Long: `Description: - The create method calls RCreateServiceW to create a new Windows service with - the provided executable & arguments as the lpBinaryPathName + The create method calls RCreateServiceW to create a new Windows service on the + remote target with the provided executable & arguments as the lpBinaryPathName References: - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/6a8ca926-9477-4dd4-b766-692fab07227e + - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/6a8ca926-9477-4dd4-b766-692fab07227e `, Args: argsRpcClient("cifs"), diff --git a/cmd/tsch.go b/cmd/tsch.go index a647f06..0ed5d9f 100644 --- a/cmd/tsch.go +++ b/cmd/tsch.go @@ -64,9 +64,10 @@ var ( tschTask string tschCmd = &cobra.Command{ - Use: "tsch", - Short: "Establish execution via Windows Task Scheduler (MS-TSCH)", - Args: cobra.NoArgs, + Use: "tsch", + Short: "Execute with Windows Task Scheduler (MS-TSCH)", + GroupID: "module", + Args: cobra.NoArgs, } tschDemandCmd = &cobra.Command{ @@ -78,8 +79,8 @@ var ( additionally call SchRpcRun to forcefully start the task. References: - SchRpcRegisterTask - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/849c131a-64e4-46ef-b015-9d4c599c5167 - SchRpcRun - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/77f2250d-500a-40ee-be18-c82f7079c4f0 + - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/849c131a-64e4-46ef-b015-9d4c599c5167 + - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/77f2250d-500a-40ee-be18-c82f7079c4f0 `, Args: args( argsRpcClient("cifs"), @@ -1,117 +1,118 @@ package cmd import ( - "context" - "encoding/json" - "fmt" - "github.com/FalconOpsLLC/goexec/pkg/goexec" - wmiexec "github.com/FalconOpsLLC/goexec/pkg/goexec/wmi" - "github.com/oiweiwei/go-msrpc/ssp/gssapi" - "github.com/spf13/cobra" + "context" + "encoding/json" + "fmt" + "github.com/FalconOpsLLC/goexec/pkg/goexec" + wmiexec "github.com/FalconOpsLLC/goexec/pkg/goexec/wmi" + "github.com/oiweiwei/go-msrpc/ssp/gssapi" + "github.com/spf13/cobra" ) func wmiCmdInit() { - registerRpcFlags(wmiCmd) + registerRpcFlags(wmiCmd) - wmiCallCmdInit() - wmiCmd.AddCommand(wmiCallCmd) + wmiCallCmdInit() + wmiCmd.AddCommand(wmiCallCmd) - wmiProcCmdInit() - wmiCmd.AddCommand(wmiProcCmd) + wmiProcCmdInit() + wmiCmd.AddCommand(wmiProcCmd) } func wmiCallArgs(_ *cobra.Command, _ []string) error { - return json.Unmarshal([]byte(wmiArguments), &wmiCall.Args) + return json.Unmarshal([]byte(wmiArguments), &wmiCall.Args) } 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. {"Command":"calc.exe"})`) - - if err := wmiCallCmd.MarkFlagRequired("class"); err != nil { - panic(err) - } - if err := wmiCallCmd.MarkFlagRequired("method"); err != nil { - panic(err) - } + 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. {"Command":"calc.exe"})`) + + if err := wmiCallCmd.MarkFlagRequired("class"); err != nil { + panic(err) + } + if err := wmiCallCmd.MarkFlagRequired("method"); err != nil { + panic(err) + } } func wmiProcCmdInit() { - wmiProcCmd.Flags().StringVarP(&wmiProc.Resource, "namespace", "n", "//./root/cimv2", "WMI namespace") - wmiProcCmd.Flags().StringVarP(&wmiProc.WorkingDirectory, "directory", "d", `C:\`, "Working directory") + wmiProcCmd.Flags().StringVarP(&wmiProc.Resource, "namespace", "n", "//./root/cimv2", "WMI namespace") + wmiProcCmd.Flags().StringVarP(&wmiProc.WorkingDirectory, "directory", "d", `C:\`, "Working directory") - registerProcessExecutionArgs(wmiProcCmd) - registerExecutionOutputArgs(wmiProcCmd) + registerProcessExecutionArgs(wmiProcCmd) + registerExecutionOutputArgs(wmiProcCmd) } var ( - wmiCall = wmiexec.WmiCall{} - wmiProc = wmiexec.WmiProc{} - - wmiArguments string - - wmiCmd = &cobra.Command{ - Use: "wmi", - Short: "Establish execution via Windows Management Instrumentation Remote Protocol (MS-WMI)", - Args: cobra.NoArgs, - } - - wmiCallCmd = &cobra.Command{ - Use: "call", - Short: "Execute specified WMI method", - Long: `Description: + wmiCall = wmiexec.WmiCall{} + wmiProc = wmiexec.WmiProc{} + + wmiArguments string + + wmiCmd = &cobra.Command{ + Use: "wmi", + Short: "Execute with Windows Management Instrumentation (MS-WMI)", + GroupID: "module", + Args: cobra.NoArgs, + } + + wmiCallCmd = &cobra.Command{ + Use: "call", + Short: "Execute specified WMI method", + Long: `Description: The call method creates an instance of the specified WMI class (-c), then calls the provided method (-m) with the provided arguments (-A). References: https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-classes `, - Args: args(argsRpcClient("host"), wmiCallArgs), - - Run: func(cmd *cobra.Command, args []string) { - var err error - - ctx := gssapi.NewSecurityContext(context.Background()) - - ctx = log.With(). - Str("module", "wmi"). - Str("method", "call"). - 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 = wmiCall.Init(ctx); err != nil { - log.Error().Err(err).Msg("Module initialization failed") - returnCode = 2 - return - } - - out, err := wmiCall.Call(ctx) - if err != nil { - log.Error().Err(err).Msg("Call failed") - returnCode = 4 - return - } - fmt.Println(string(out)) - }, - } - - wmiProcCmd = &cobra.Command{ - Use: "proc", - Short: "Start a Windows process", - Long: `Description: + Args: args(argsRpcClient("host"), wmiCallArgs), + + Run: func(cmd *cobra.Command, args []string) { + var err error + + ctx := gssapi.NewSecurityContext(context.Background()) + + ctx = log.With(). + Str("module", "wmi"). + Str("method", "call"). + 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 = wmiCall.Init(ctx); err != nil { + log.Error().Err(err).Msg("Module initialization failed") + returnCode = 2 + return + } + + out, err := wmiCall.Call(ctx) + if err != nil { + log.Error().Err(err).Msg("Call failed") + returnCode = 4 + return + } + fmt.Println(string(out)) + }, + } + + wmiProcCmd = &cobra.Command{ + Use: "proc", + Short: "Start a Windows process", + Long: `Description: The proc method creates an instance of the Win32_Process WMI class, then calls the Win32_Process.Create method with the provided command (-c), and optional working directory (-d). @@ -119,23 +120,23 @@ References: References: https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-process `, - Args: args( - argsOutput("smb"), - argsRpcClient("host"), - ), - - Run: func(cmd *cobra.Command, args []string) { - wmiProc.Client = &rpcClient - wmiProc.IO = exec - - ctx := log.With(). - Str("module", "wmi"). - Str("method", "proc"). - Logger().WithContext(gssapi.NewSecurityContext(context.Background())) - - if err := goexec.ExecuteCleanMethod(ctx, &wmiProc, &exec); err != nil { - log.Fatal().Err(err).Msg("Operation failed") - } - }, - } + Args: args( + argsOutput("smb"), + argsRpcClient("host"), + ), + + Run: func(cmd *cobra.Command, args []string) { + wmiProc.Client = &rpcClient + wmiProc.IO = exec + + ctx := log.With(). + Str("module", "wmi"). + Str("method", "proc"). + Logger().WithContext(gssapi.NewSecurityContext(context.Background())) + + if err := goexec.ExecuteCleanMethod(ctx, &wmiProc, &exec); err != nil { + log.Fatal().Err(err).Msg("Operation failed") + } + }, + } ) @@ -6,10 +6,12 @@ require ( github.com/RedTeamPentesting/adauth v0.2.0 github.com/google/uuid v1.6.0 github.com/oiweiwei/go-msrpc v1.2.5 + github.com/oiweiwei/go-smb2.fork v1.0.0 github.com/rs/zerolog v1.34.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 golang.org/x/net v0.39.0 + golang.org/x/term v0.31.0 ) require ( @@ -25,7 +27,6 @@ require ( github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/oiweiwei/go-smb2.fork v1.0.0 // indirect github.com/oiweiwei/gokrb5.fork/v9 v9.0.2 // indirect golang.org/x/crypto v0.37.0 // indirect golang.org/x/sys v0.32.0 // indirect @@ -1,5 +1,3 @@ -github.com/RedTeamPentesting/adauth v0.1.1-0.20250304075117-acd47d454877 h1:n5V0EER+EvvmUZitR5zGFUMyoIHnI12SWFMciI7kh70= -github.com/RedTeamPentesting/adauth v0.1.1-0.20250304075117-acd47d454877/go.mod h1:iHf/fY7CueB7qLHZ5YgTZXvrVCSLJy4+tAifOSNLAFQ= github.com/RedTeamPentesting/adauth v0.2.0 h1:pNb4xcEd/oG/JY8aXkBm8Y0veVaUA/CsKFhgEgNCPik= github.com/RedTeamPentesting/adauth v0.2.0/go.mod h1:AhWZEgl98YkKEta6kXTOSocQh2SbJJZg4nKqF+uY4bc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -44,8 +42,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/oiweiwei/go-msrpc v1.2.4 h1:edFTNHkXqH/cssj0MDf1eRjW8xavdFNN2OrlteC3dRk= -github.com/oiweiwei/go-msrpc v1.2.4/go.mod h1:ev+Bg4HdktdaLvwQ2RcwTlgvx7boe+fskcdUlesepdM= github.com/oiweiwei/go-msrpc v1.2.5 h1:nIWoU7MWLk5l8vb0pgQ+D67GjDRPC4ybiR+OJtgDWdk= github.com/oiweiwei/go-msrpc v1.2.5/go.mod h1:WoWRPfm90vRNZDJCwOiUXy39vjyQMAFrFj0zkWTThwY= github.com/oiweiwei/go-smb2.fork v1.0.0 h1:xHq/eYPM8hQEO/nwCez8YwHWHC8mlcsgw/Neu52fPN4= @@ -55,10 +51,7 @@ github.com/oiweiwei/gokrb5.fork/v9 v9.0.2/go.mod h1:KEnkAYUYqZ5VwzxLFbv3JHlRhCvd github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -79,8 +72,6 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -90,10 +81,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -107,19 +94,17 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/goexec/dcom/mmc.go b/pkg/goexec/dcom/mmc.go index ecb3a74..993d1bb 100644 --- a/pkg/goexec/dcom/mmc.go +++ b/pkg/goexec/dcom/mmc.go @@ -1,51 +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 { - Dcom + Dcom - IO goexec.ExecutionIO + IO goexec.ExecutionIO - WorkingDirectory string - WindowState string + WorkingDirectory string + WindowState string } // Execute will perform command execution via the MMC20.Application DCOM object. 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 + 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 71e4f6e..40804c3 100644 --- a/pkg/goexec/dcom/module.go +++ b/pkg/goexec/dcom/module.go @@ -1,120 +1,131 @@ 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/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/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" ) const ( - ModuleName = "DCOM" + ModuleName = "DCOM" ) type Dcom struct { - goexec.Cleaner + goexec.Cleaner - Client *dce.Client + 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.AddCleaner(m.Client.Close) - } - return + if err = m.Client.Connect(ctx); err == nil { + m.AddCleaner(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") - } - - 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")...) - - 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") + } + + opts = append(opts, si.RemoteReply.OXIDBindings.EndpointsByProtocol("ncacn_ip_tcp")...) + + 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 } |