aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan McNulty <bryanmcnulty@protonmail.com>2025-03-10 16:04:08 -0500
committerBryan McNulty <bryanmcnulty@protonmail.com>2025-03-10 16:04:08 -0500
commit11741c4cde3d552211fbb04eddd719b3dc3bd472 (patch)
tree52f28ca2feacde039b7215fa3fd27b5a7ec02ed5
parentab141f2076b141bf885f56cb5730252cc2880041 (diff)
downloadgoexec-11741c4cde3d552211fbb04eddd719b3dc3bd472.tar.gz
goexec-11741c4cde3d552211fbb04eddd719b3dc3bd472.zip
Added basic dcom execution module
-rw-r--r--cmd/dcom.go75
-rw-r--r--cmd/root.go135
-rw-r--r--cmd/rpc.go103
-rw-r--r--cmd/wmi.go228
-rw-r--r--go.mod4
-rw-r--r--go.sum4
-rw-r--r--internal/client/dce/dce.go2
-rw-r--r--internal/exec/dcom/dcom.go65
-rw-r--r--internal/exec/dcom/exec.go187
-rw-r--r--internal/exec/dcom/module.go21
-rw-r--r--internal/exec/scmr/exec.go2
-rw-r--r--internal/exec/scmr/service.go26
12 files changed, 605 insertions, 247 deletions
diff --git a/cmd/dcom.go b/cmd/dcom.go
new file mode 100644
index 0000000..d105b0c
--- /dev/null
+++ b/cmd/dcom.go
@@ -0,0 +1,75 @@
+package cmd
+
+import (
+ "github.com/FalconOpsLLC/goexec/internal/exec"
+ dcomexec "github.com/FalconOpsLLC/goexec/internal/exec/dcom"
+ "github.com/spf13/cobra"
+)
+
+func dcomCmdInit() {
+ registerRpcFlags(dcomCmd)
+ dcomMmcCmdInit()
+ dcomCmd.AddCommand(dcomMmcCmd)
+}
+
+func dcomMmcCmdInit() {
+ dcomMmcCmd.Flags().StringVarP(&executable, "executable", "e", "", "Remote Windows executable to invoke")
+ dcomMmcCmd.Flags().StringVarP(&workingDirectory, "directory", "d", `C:\`, "Working directory")
+ dcomMmcCmd.Flags().StringVarP(&executableArgs, "args", "a", "", "Process command line")
+ dcomMmcCmd.Flags().StringVar(&windowState, "window", "Minimized", "Window state")
+ dcomMmcCmd.Flags().StringVarP(&command, "command", "c", ``, "Windows executable & arguments to run")
+
+ dcomMmcCmd.MarkFlagsOneRequired("executable", "command")
+ dcomMmcCmd.MarkFlagsMutuallyExclusive("executable", "command")
+}
+
+var (
+ dcomCmd = &cobra.Command{
+ Use: "dcom",
+ Short: "Establish execution via DCOM",
+ Args: cobra.NoArgs,
+ }
+ dcomMmcCmd = &cobra.Command{
+ Use: "mmc [target]",
+ Short: "Establish execution via the DCOM MMC20.Application object",
+ Long: `Description:
+ The mmc method uses the exposed MMC20.Application object to call Document.ActiveView.ShellExec,
+ and ultimately execute system commands.
+
+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
+`,
+ Args: needsRpcTarget("host"),
+ Run: func(cmd *cobra.Command, args []string) {
+
+ ctx = log.With().
+ Str("module", "dcom").
+ Str("method", "mmc").
+ Logger().WithContext(ctx)
+
+ module := dcomexec.Module{}
+ connCfg := &exec.ConnectionConfig{
+ ConnectionMethod: exec.ConnectionMethodDCE,
+ ConnectionMethodConfig: dceConfig,
+ }
+ execCfg := &exec.ExecutionConfig{
+ ExecutableName: executable,
+ ExecutableArgs: executableArgs,
+ ExecutionMethod: dcomexec.MethodMmc,
+
+ ExecutionMethodConfig: dcomexec.MethodMmcConfig{
+ WorkingDirectory: workingDirectory,
+ WindowState: windowState,
+ },
+ }
+ if err := module.Connect(ctx, creds, target, connCfg); err != nil {
+ log.Fatal().Err(err).Msg("Connection failed")
+ } else if err = module.Exec(ctx, execCfg); err != nil {
+ log.Fatal().Err(err).Msg("Execution failed")
+ }
+ },
+ }
+)
diff --git a/cmd/root.go b/cmd/root.go
index f083063..3f17253 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,83 +1,94 @@
package cmd
import (
- "context"
- "fmt"
- "github.com/RedTeamPentesting/adauth"
- "github.com/rs/zerolog"
- "github.com/spf13/cobra"
- "os"
- "regexp"
+ "context"
+ "fmt"
+ "github.com/RedTeamPentesting/adauth"
+ "github.com/rs/zerolog"
+ "github.com/spf13/cobra"
+ "os"
+ "regexp"
+ "strings"
)
var (
- //logFile string
- log zerolog.Logger
- ctx context.Context
- authOpts *adauth.Options
+ //logFile string
+ log zerolog.Logger
+ ctx context.Context
+ authOpts *adauth.Options
- debug bool
- command string
- executable string
- executablePath string
- executableArgs string
- workingDirectory string
+ debug bool
+ command string
+ executable string
+ executablePath string
+ executableArgs string
+ workingDirectory string
+ windowState string
- rootCmd = &cobra.Command{
- Use: "goexec",
- PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
- // For modules that require a full executable path
- if executablePath != "" && !regexp.MustCompile(`^([a-zA-Z]:)?\\`).MatchString(executablePath) {
- return fmt.Errorf("executable path (-e) must be an absolute Windows path, i.e. C:\\Windows\\System32\\cmd.exe")
- }
- log = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel).With().Timestamp().Logger()
- if debug {
- log = log.Level(zerolog.DebugLevel)
- }
- return
- },
- }
+ rootCmd = &cobra.Command{
+ Use: "goexec",
+ PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
+ // For modules that require a full executable path
+ if executablePath != "" && !regexp.MustCompile(`^([a-zA-Z]:)?\\`).MatchString(executablePath) {
+ return fmt.Errorf("executable path (-e) must be an absolute Windows path, i.e. C:\\Windows\\System32\\cmd.exe")
+ }
+ if command != "" {
+ p := strings.SplitN(command, " ", 2)
+ executable = p[0]
+ if len(p) > 1 {
+ executableArgs = p[1]
+ }
+ }
+ log = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel).With().Timestamp().Logger()
+ if debug {
+ log = log.Level(zerolog.DebugLevel)
+ }
+ return
+ },
+ }
)
func needsTarget(proto string) func(cmd *cobra.Command, args []string) error {
- return func(cmd *cobra.Command, args []string) (err error) {
- if len(args) != 1 {
- return fmt.Errorf("command require exactly one positional argument: [target]")
- }
- if creds, target, err = authOpts.WithTarget(ctx, proto, args[0]); err != nil {
- return fmt.Errorf("failed to parse target: %w", err)
- }
- if creds == nil {
- return fmt.Errorf("no credentials supplied")
- }
- if target == nil {
- return fmt.Errorf("no target supplied")
- }
- return
- }
+ return func(cmd *cobra.Command, args []string) (err error) {
+ if len(args) != 1 {
+ return fmt.Errorf("command require exactly one positional argument: [target]")
+ }
+ if creds, target, err = authOpts.WithTarget(ctx, proto, args[0]); err != nil {
+ return fmt.Errorf("failed to parse target: %w", err)
+ }
+ if creds == nil {
+ return fmt.Errorf("no credentials supplied")
+ }
+ if target == nil {
+ return fmt.Errorf("no target supplied")
+ }
+ return
+ }
}
func init() {
- ctx = context.Background()
+ ctx = context.Background()
- rootCmd.InitDefaultVersionFlag()
- rootCmd.InitDefaultHelpCmd()
- rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging")
+ rootCmd.InitDefaultVersionFlag()
+ rootCmd.InitDefaultHelpCmd()
+ rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging")
- authOpts = &adauth.Options{Debug: log.Debug().Msgf}
- authOpts.RegisterFlags(rootCmd.PersistentFlags())
+ authOpts = &adauth.Options{Debug: log.Debug().Msgf}
+ authOpts.RegisterFlags(rootCmd.PersistentFlags())
- scmrCmdInit()
- rootCmd.AddCommand(scmrCmd)
- tschCmdInit()
- rootCmd.AddCommand(tschCmd)
- wmiCmdInit()
- rootCmd.AddCommand(wmiCmd)
+ scmrCmdInit()
+ rootCmd.AddCommand(scmrCmd)
+ tschCmdInit()
+ rootCmd.AddCommand(tschCmd)
+ wmiCmdInit()
+ rootCmd.AddCommand(wmiCmd)
+ dcomCmdInit()
+ rootCmd.AddCommand(dcomCmd)
}
func Execute() {
- if err := rootCmd.Execute(); err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
+ if err := rootCmd.Execute(); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
}
diff --git a/cmd/rpc.go b/cmd/rpc.go
index 7b5b214..816e17f 100644
--- a/cmd/rpc.go
+++ b/cmd/rpc.go
@@ -1,67 +1,70 @@
package cmd
import (
- "fmt"
- "github.com/FalconOpsLLC/goexec/internal/client/dce"
- "github.com/oiweiwei/go-msrpc/dcerpc"
- "github.com/spf13/cobra"
- "regexp"
+ "fmt"
+ "github.com/FalconOpsLLC/goexec/internal/client/dce"
+ "github.com/oiweiwei/go-msrpc/dcerpc"
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+ "regexp"
)
func needsRpcTarget(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 argDceStringBinding != "" {
- dceConfig.Endpoint, err = dcerpc.ParseStringBinding(argDceStringBinding)
- if err != nil {
- return fmt.Errorf("failed to parse RPC endpoint: %w", err)
- }
- dceConfig.NoEpm = true // If an explicit endpoint is set, don't use EPM
+ if argDceStringBinding != "" {
+ dceConfig.Endpoint, err = dcerpc.ParseStringBinding(argDceStringBinding)
+ if err != nil {
+ return fmt.Errorf("failed to parse RPC endpoint: %w", err)
+ }
+ dceConfig.NoEpm = true // If an explicit endpoint is set, don't use EPM
- } else if argDceEpmFilter != "" {
- // This ensures that filters like "ncacn_ip_tcp" will be made into a valid binding (i.e. "ncacn_ip_tcp:")
- if ok, err := regexp.MatchString(`^\w+$`, argDceEpmFilter); err == nil && ok {
- argDceEpmFilter += ":"
- }
- dceConfig.EpmFilter, err = dcerpc.ParseStringBinding(argDceEpmFilter)
- if err != nil {
- return fmt.Errorf("failed to parse EPM filter: %w", err)
- }
- }
- if !argDceNoSign {
- dceConfig.DceOptions = append(dceConfig.DceOptions, dcerpc.WithSign())
- dceConfig.EpmOptions = append(dceConfig.EpmOptions, dcerpc.WithSign())
- }
- if argDceNoSeal {
- dceConfig.DceOptions = append(dceConfig.DceOptions, dcerpc.WithInsecure())
- } else {
- dceConfig.DceOptions = append(dceConfig.DceOptions, dcerpc.WithSeal(), dcerpc.WithSecurityLevel(dcerpc.AuthLevelPktPrivacy))
- dceConfig.EpmOptions = append(dceConfig.EpmOptions, dcerpc.WithSeal(), dcerpc.WithSecurityLevel(dcerpc.AuthLevelPktPrivacy))
- }
- return needsTarget(proto)(cmd, args)
- }
+ } else if argDceEpmFilter != "" {
+ // This ensures that filters like "ncacn_ip_tcp" will be made into a valid binding (i.e. "ncacn_ip_tcp:")
+ if ok, err := regexp.MatchString(`^\w+$`, argDceEpmFilter); err == nil && ok {
+ argDceEpmFilter += ":"
+ }
+ dceConfig.EpmFilter, err = dcerpc.ParseStringBinding(argDceEpmFilter)
+ if err != nil {
+ return fmt.Errorf("failed to parse EPM filter: %w", err)
+ }
+ }
+ if !argDceNoSign {
+ dceConfig.DceOptions = append(dceConfig.DceOptions, dcerpc.WithSign())
+ dceConfig.EpmOptions = append(dceConfig.EpmOptions, dcerpc.WithSign())
+ }
+ if argDceNoSeal {
+ dceConfig.DceOptions = append(dceConfig.DceOptions, dcerpc.WithInsecure())
+ } else {
+ dceConfig.DceOptions = append(dceConfig.DceOptions, dcerpc.WithSeal(), dcerpc.WithSecurityLevel(dcerpc.AuthLevelPktPrivacy))
+ dceConfig.EpmOptions = append(dceConfig.EpmOptions, dcerpc.WithSeal(), dcerpc.WithSecurityLevel(dcerpc.AuthLevelPktPrivacy))
+ }
+ return needsTarget(proto)(cmd, args)
+ }
}
var (
- // DCE arguments
- argDceStringBinding string
- argDceEpmFilter string
- argDceNoSeal bool
- argDceNoSign bool
+ // DCE arguments
+ argDceStringBinding string
+ argDceEpmFilter string
+ argDceNoSeal bool
+ argDceNoSign bool
- // DCE options
- dceStringBinding *dcerpc.StringBinding
- dceConfig dce.ConnectionMethodDCEConfig
+ // DCE options
+ dceStringBinding *dcerpc.StringBinding
+ dceConfig dce.ConnectionMethodDCEConfig
)
func registerRpcFlags(cmd *cobra.Command) {
- cmd.PersistentFlags().BoolVar(&dceConfig.NoEpm, "no-epm", false, "Do not use EPM to automatically detect endpoints")
- cmd.PersistentFlags().BoolVar(&dceConfig.EpmAuto, "epm-auto", false, "Automatically detect endpoints instead of using the module defaults")
- cmd.PersistentFlags().BoolVar(&argDceNoSign, "no-sign", false, "Disable signing on DCE messages")
- cmd.PersistentFlags().BoolVar(&argDceNoSeal, "no-seal", false, "Disable packet stub encryption on DCE messages")
- cmd.PersistentFlags().StringVarP(&argDceEpmFilter, "epm-filter", "F", "", "String binding to filter endpoints returned by EPM")
- cmd.PersistentFlags().StringVar(&argDceStringBinding, "endpoint", "", "Explicit RPC endpoint definition")
+ rpcFlags := pflag.NewFlagSet("RPC", pflag.ExitOnError)
+ rpcFlags.BoolVar(&dceConfig.NoEpm, "no-epm", false, "Do not use EPM to automatically detect endpoints")
+ rpcFlags.BoolVar(&dceConfig.EpmAuto, "epm-auto", false, "Automatically detect endpoints instead of using the module defaults")
+ rpcFlags.BoolVar(&argDceNoSign, "no-sign", false, "Disable signing on DCE messages")
+ rpcFlags.BoolVar(&argDceNoSeal, "no-seal", false, "Disable packet stub encryption on DCE messages")
+ rpcFlags.StringVarP(&argDceEpmFilter, "epm-filter", "F", "", "String binding to filter endpoints returned by EPM")
+ rpcFlags.StringVar(&argDceStringBinding, "endpoint", "", "Explicit RPC endpoint definition")
+ cmd.PersistentFlags().AddFlagSet(rpcFlags)
- cmd.MarkFlagsMutuallyExclusive("endpoint", "epm-filter")
- cmd.MarkFlagsMutuallyExclusive("no-epm", "epm-filter")
+ cmd.MarkFlagsMutuallyExclusive("endpoint", "epm-filter")
+ cmd.MarkFlagsMutuallyExclusive("no-epm", "epm-filter")
}
diff --git a/cmd/wmi.go b/cmd/wmi.go
index cba2473..df047a2 100644
--- a/cmd/wmi.go
+++ b/cmd/wmi.go
@@ -1,142 +1,140 @@
package cmd
import (
- "encoding/json"
- "fmt"
- "github.com/FalconOpsLLC/goexec/internal/exec"
- wmiexec "github.com/FalconOpsLLC/goexec/internal/exec/wmi"
- "github.com/spf13/cobra"
- "regexp"
- "strings"
+ "encoding/json"
+ "fmt"
+ "github.com/FalconOpsLLC/goexec/internal/exec"
+ wmiexec "github.com/FalconOpsLLC/goexec/internal/exec/wmi"
+ "github.com/spf13/cobra"
+ "regexp"
+ "strings"
)
func wmiCmdInit() {
- wmiCustomCmdInit()
- wmiCmd.AddCommand(wmiCustomCmd)
- wmiProcessCmdInit()
- wmiCmd.AddCommand(wmiProcessCmd)
+ wmiCustomCmdInit()
+ wmiCmd.AddCommand(wmiCustomCmd)
+ wmiProcessCmdInit()
+ wmiCmd.AddCommand(wmiProcessCmd)
}
func wmiCustomCmdInit() {
- wmiCustomCmd.Flags().StringVarP(&wmiArgMethod, "method", "m", "", `WMI Method to use in the format CLASS.METHOD (i.e. "Win32_Process.Create")`)
- wmiCustomCmd.Flags().StringVarP(&wmiArgMethodArgs, "args", "A", "{}", `WMI Method argument(s) in JSON dictionary format (i.e. {"CommandLine":"calc.exe"})`)
- if err := wmiCustomCmd.MarkFlagRequired("method"); err != nil {
- panic(err)
- }
+ wmiCustomCmd.Flags().StringVarP(&wmiArgMethod, "method", "m", "", `WMI Method to use in the format CLASS.METHOD (i.e. "Win32_Process.Create")`)
+ wmiCustomCmd.Flags().StringVarP(&wmiArgMethodArgs, "args", "A", "{}", `WMI Method argument(s) in JSON dictionary format (i.e. {"CommandLine":"calc.exe"})`)
+ if err := wmiCustomCmd.MarkFlagRequired("method"); err != nil {
+ panic(err)
+ }
}
func wmiProcessCmdInit() {
- wmiProcessCmd.Flags().StringVarP(&command, "command", "c", "", "Process command line")
- wmiProcessCmd.Flags().StringVarP(&workingDirectory, "directory", "d", `C:\`, "Working directory")
- if err := wmiProcessCmd.MarkFlagRequired("command"); err != nil {
- panic(err)
- }
+ wmiProcessCmd.Flags().StringVarP(&command, "command", "c", "", "Process command line")
+ wmiProcessCmd.Flags().StringVarP(&workingDirectory, "directory", "d", `C:\`, "Working directory")
+ if err := wmiProcessCmd.MarkFlagRequired("command"); err != nil {
+ panic(err)
+ }
}
var (
- // for custom method
- wmiArgMethod string
- wmiArgMethodArgs string
-
- wmiClass string
- wmiMethod string
- wmiMethodArgsMap map[string]any
- methodRegex = regexp.MustCompile(`^\w+\.\w+$`)
-
- wmiCmd = &cobra.Command{
- Use: "wmi",
- Short: "Establish execution via WMI",
- Args: cobra.NoArgs,
- }
- wmiCustomCmd = &cobra.Command{
- Use: "custom",
- Short: "Execute specified WMI method",
- Long: `Description:
+ // for custom method
+ wmiArgMethod string
+ wmiArgMethodArgs string
+
+ wmiClass string
+ wmiMethod string
+ wmiMethodArgsMap map[string]any
+ methodRegex = regexp.MustCompile(`^\w+\.\w+$`)
+
+ wmiCmd = &cobra.Command{
+ Use: "wmi",
+ Short: "Establish execution via WMI",
+ Args: cobra.NoArgs,
+ }
+ wmiCustomCmd = &cobra.Command{
+ Use: "custom",
+ Short: "Execute specified WMI method",
+ Long: `Description:
The custom 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: func(cmd *cobra.Command, args []string) (err error) {
- if err = needsTarget("cifs")(cmd, args); err == nil {
- if wmiArgMethod != "" && !methodRegex.MatchString(wmiArgMethod) {
- return fmt.Errorf("invalid CLASS.METHOD syntax: %s", wmiArgMethod)
- }
- if err = json.Unmarshal([]byte(wmiArgMethodArgs), &wmiMethodArgsMap); err != nil {
- err = fmt.Errorf("failed to parse JSON arguments: %w", err)
- }
- }
- return
- },
- Run: func(cmd *cobra.Command, args []string) {
- module := wmiexec.Module{}
-
- connCfg := &exec.ConnectionConfig{}
- cleanCfg := &exec.CleanupConfig{}
-
- parts := strings.SplitN(wmiArgMethod, ".", 2)
- wmiClass = parts[0]
- wmiMethod = parts[1]
-
- execCfg := &exec.ExecutionConfig{
- ExecutableName: executable,
- ExecutableArgs: executableArgs,
- ExecutionMethod: wmiexec.MethodCustom,
- ExecutionMethodConfig: wmiexec.MethodCustomConfig{
- Class: wmiClass,
- Method: wmiMethod,
- Arguments: wmiMethodArgsMap,
- },
- }
- if err := module.Connect(log.WithContext(ctx), creds, target, connCfg); err != nil {
- log.Fatal().Err(err).Msg("Connection failed")
-
- } else if err := module.Exec(log.WithContext(ctx), execCfg); err != nil {
- log.Fatal().Err(err).Msg("Execution failed")
-
- } else if err := module.Cleanup(log.WithContext(ctx), cleanCfg); err != nil {
- log.Error().Err(err).Msg("Cleanup failed")
- }
- },
- }
-
- wmiProcessCmd = &cobra.Command{
- Use: "process",
- Short: "Create a Windows process",
- Long: `Description:
- The process method creates an instance of the Win32_Process WMI class,
- then calls the Win32_Process.Create method with the provided command (-c),
+ Args: func(cmd *cobra.Command, args []string) (err error) {
+ if err = needsTarget("cifs")(cmd, args); err == nil {
+ if wmiArgMethod != "" && !methodRegex.MatchString(wmiArgMethod) {
+ return fmt.Errorf("invalid CLASS.METHOD syntax: %s", wmiArgMethod)
+ }
+ if err = json.Unmarshal([]byte(wmiArgMethodArgs), &wmiMethodArgsMap); err != nil {
+ err = fmt.Errorf("failed to parse JSON arguments: %w", err)
+ }
+ }
+ return
+ },
+ Run: func(cmd *cobra.Command, args []string) {
+ module := wmiexec.Module{}
+
+ connCfg := &exec.ConnectionConfig{}
+ cleanCfg := &exec.CleanupConfig{}
+
+ parts := strings.SplitN(wmiArgMethod, ".", 2)
+ wmiClass = parts[0]
+ wmiMethod = parts[1]
+
+ execCfg := &exec.ExecutionConfig{
+ ExecutableName: executable,
+ ExecutableArgs: executableArgs,
+ ExecutionMethod: wmiexec.MethodCustom,
+ ExecutionMethodConfig: wmiexec.MethodCustomConfig{
+ Class: wmiClass,
+ Method: wmiMethod,
+ Arguments: wmiMethodArgsMap,
+ },
+ }
+ if err := module.Connect(log.WithContext(ctx), creds, target, connCfg); err != nil {
+ log.Fatal().Err(err).Msg("Connection failed")
+ } else if err := module.Exec(log.WithContext(ctx), execCfg); err != nil {
+ log.Fatal().Err(err).Msg("Execution failed")
+ } else if err := module.Cleanup(log.WithContext(ctx), cleanCfg); err != nil {
+ log.Error().Err(err).Msg("Cleanup failed")
+ }
+ },
+ }
+
+ wmiProcessCmd = &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).
References:
https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-process
`,
- Args: needsTarget("cifs"),
- Run: func(cmd *cobra.Command, args []string) {
- log = log.With().Str("module", "wmi").Logger()
-
- module := wmiexec.Module{}
- connCfg := &exec.ConnectionConfig{}
- cleanCfg := &exec.CleanupConfig{}
-
- execCfg := &exec.ExecutionConfig{
- ExecutableName: executable,
- ExecutableArgs: executableArgs,
- ExecutionMethod: wmiexec.MethodProcess,
-
- ExecutionMethodConfig: wmiexec.MethodProcessConfig{
- Command: command,
- WorkingDirectory: workingDirectory,
- },
- }
- if err := module.Connect(log.WithContext(ctx), creds, target, connCfg); err != nil {
- log.Fatal().Err(err).Msg("Connection failed")
- } else if err := module.Exec(log.WithContext(ctx), execCfg); err != nil {
- log.Fatal().Err(err).Msg("Execution failed")
- } else if err := module.Cleanup(log.WithContext(ctx), cleanCfg); err != nil {
- log.Error().Err(err).Msg("Cleanup failed")
- }
- },
- }
+ Args: needsTarget("cifs"),
+ Run: func(cmd *cobra.Command, args []string) {
+ log = log.With().Str("module", "wmi").Logger()
+
+ module := wmiexec.Module{}
+ connCfg := &exec.ConnectionConfig{}
+ cleanCfg := &exec.CleanupConfig{}
+
+ execCfg := &exec.ExecutionConfig{
+ ExecutableName: executable,
+ ExecutableArgs: executableArgs,
+ ExecutionMethod: wmiexec.MethodProcess,
+
+ ExecutionMethodConfig: wmiexec.MethodProcessConfig{
+ Command: command,
+ WorkingDirectory: workingDirectory,
+ },
+ }
+ if err := module.Connect(log.WithContext(ctx), creds, target, connCfg); err != nil {
+ log.Fatal().Err(err).Msg("Connection failed")
+ } else if err := module.Exec(log.WithContext(ctx), execCfg); err != nil {
+ log.Fatal().Err(err).Msg("Execution failed")
+ } else if err := module.Cleanup(log.WithContext(ctx), cleanCfg); err != nil {
+ log.Error().Err(err).Msg("Cleanup failed")
+ }
+ },
+ }
)
diff --git a/go.mod b/go.mod
index a921866..56ba734 100644
--- a/go.mod
+++ b/go.mod
@@ -5,9 +5,10 @@ go 1.24.0
require (
github.com/RedTeamPentesting/adauth v0.1.1-0.20250304075117-acd47d454877
github.com/google/uuid v1.6.0
- github.com/oiweiwei/go-msrpc v1.2.1
+ github.com/oiweiwei/go-msrpc v1.2.3
github.com/rs/zerolog v1.33.0
github.com/spf13/cobra v1.9.1
+ github.com/spf13/pflag v1.0.6
)
require (
@@ -25,7 +26,6 @@ require (
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
- github.com/spf13/pflag v1.0.6 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect
diff --git a/go.sum b/go.sum
index 790a126..00f7472 100644
--- a/go.sum
+++ b/go.sum
@@ -42,8 +42,8 @@ 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.1 h1:jpt7DYCefrJhDS+C9GvhgFls9zTh05eWDw3mwRToqKc=
-github.com/oiweiwei/go-msrpc v1.2.1/go.mod h1:ev+Bg4HdktdaLvwQ2RcwTlgvx7boe+fskcdUlesepdM=
+github.com/oiweiwei/go-msrpc v1.2.3 h1:Wluv8bB0+Gxo+sMcU+0+a5WThqAEm1l84eHBQOWXoBU=
+github.com/oiweiwei/go-msrpc v1.2.3/go.mod h1:ev+Bg4HdktdaLvwQ2RcwTlgvx7boe+fskcdUlesepdM=
github.com/oiweiwei/go-smb2.fork v1.0.0 h1:xHq/eYPM8hQEO/nwCez8YwHWHC8mlcsgw/Neu52fPN4=
github.com/oiweiwei/go-smb2.fork v1.0.0/go.mod h1:h0CzLVvGAmq39izdYVHKyI5cLv6aHdbQAMKEe4dz4N8=
github.com/oiweiwei/gokrb5.fork/v9 v9.0.2 h1:JNkvXMuOEWNXJKzLiyROGfdK31/1RQWA9e5gJxAsl50=
diff --git a/internal/client/dce/dce.go b/internal/client/dce/dce.go
index f58123b..85512ac 100644
--- a/internal/client/dce/dce.go
+++ b/internal/client/dce/dce.go
@@ -26,7 +26,7 @@ type ConnectionMethodDCEConfig struct {
func (cfg *ConnectionMethodDCEConfig) GetDce(ctx context.Context, cred *adauth.Credential, target *adauth.Target, endpoint, object string, opts ...dcerpc.Option) (cc dcerpc.Conn, err error) {
dceOpts := append(opts, cfg.DceOptions...)
- epmOpts := append(opts, cfg.EpmOptions...)
+ epmOpts := cfg.EpmOptions
log := zerolog.Ctx(ctx).With().
Str("client", "DCERPC").Logger()
diff --git a/internal/exec/dcom/dcom.go b/internal/exec/dcom/dcom.go
new file mode 100644
index 0000000..b96bbc8
--- /dev/null
+++ b/internal/exec/dcom/dcom.go
@@ -0,0 +1,65 @@
+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"
+)
+
+const (
+ LC_ENGLISH_US uint32 = 0x409
+)
+
+func callMethod(ctx context.Context, dc idispatch.DispatchClient, method string, args ...*oaut.Variant) (ir *idispatch.InvokeResponse, err error) {
+ parts := strings.Split(method, ".")
+
+ var id *dcom.IPID
+ var gr *idispatch.GetIDsOfNamesResponse
+
+ for i, obj := range parts {
+ var opts []dcerpc.CallOption
+ if id != nil {
+ opts = append(opts, dcom.WithIPID(id))
+ }
+ gr, err = dc.GetIDsOfNames(ctx, &idispatch.GetIDsOfNamesRequest{
+ This: ORPCThis,
+ IID: &dcom.IID{},
+ Names: []string{obj + "\x00"},
+ LocaleID: LC_ENGLISH_US,
+ }, opts...)
+
+ 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)
+ }
+ irq := &idispatch.InvokeRequest{
+ This: ORPCThis,
+ DispatchIDMember: gr.DispatchID[0],
+ IID: &dcom.IID{},
+ LocaleID: LC_ENGLISH_US,
+ }
+ 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)
+ }
+ 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
+}
diff --git a/internal/exec/dcom/exec.go b/internal/exec/dcom/exec.go
new file mode 100644
index 0000000..297c26f
--- /dev/null
+++ b/internal/exec/dcom/exec.go
@@ -0,0 +1,187 @@
+package dcomexec
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "github.com/FalconOpsLLC/goexec/internal/client/dce"
+ "github.com/FalconOpsLLC/goexec/internal/exec"
+ "github.com/RedTeamPentesting/adauth"
+ guuid "github.com/google/uuid"
+ "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"
+ "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0"
+ "github.com/oiweiwei/go-msrpc/msrpc/dtyp"
+ "github.com/rs/zerolog"
+)
+
+const (
+ DefaultDcomEndpoint = "ncacn_ip_tcp:[135]"
+)
+
+var (
+ MmcUuid = uuid.MustParse("49B2791A-B1AE-4C90-9B8E-E860BA07F889")
+ ShellWindowsUuid = uuid.MustParse("9BA05972-F6A8-11CF-A442-00A0C90A8F39")
+ //RandUuid = uuid.MustParse("dc95cac4-0d74-49eb-8947-570ad52ef215")
+ RandCid = dcom.CID(*dtyp.GUIDFromUUID(uuid.MustParse(guuid.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,
+ }
+ MmcClsid = dcom.ClassID(*dtyp.GUIDFromUUID(MmcUuid))
+ ORPCThis = &dcom.ORPCThis{
+ Version: ComVersion,
+ CID: &RandCid,
+ }
+)
+
+func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target, ccfg *exec.ConnectionConfig) (err error) {
+
+ log := zerolog.Ctx(ctx).With().
+ Str("method", ccfg.ConnectionMethod).
+ Str("func", "Exec").Logger()
+
+ if ccfg.ConnectionMethod == exec.ConnectionMethodDCE {
+ if cfg, ok := ccfg.ConnectionMethodConfig.(dce.ConnectionMethodDCEConfig); !ok {
+ return fmt.Errorf("invalid configuration for DCE connection method")
+ } else {
+ opts := []dcerpc.Option{dcerpc.WithSign(), dcerpc.WithSecurityLevel(0)}
+
+ // Fetch target hostname
+ if mod.hostname, err = target.Hostname(ctx); err != nil {
+ log.Debug().Err(err).Msg("Failed to get target hostname")
+ opts = append(opts, dcerpc.WithTargetName(mod.hostname))
+ }
+ // Create DCE connection
+ if mod.dce, err = cfg.GetDce(ctx, creds, target, DefaultDcomEndpoint, "", opts...); err != nil {
+ log.Error().Err(err).Msg("Failed to initialize DCE dialer")
+ return fmt.Errorf("create DCE dialer: %w", err)
+ }
+
+ inst := &dcom.InstantiationInfoData{
+ ClassID: &MmcClsid,
+ IID: []*dcom.IID{IDispatchIID},
+ ClientCOMVersion: ComVersion,
+ }
+ scm := &dcom.SCMRequestInfoData{
+ RemoteRequest: &dcom.CustomRemoteRequestSCMInfo{
+ RequestedProtocolSequences: []uint16{7},
+ },
+ }
+ loc := &dcom.LocationInfoData{}
+ ac := &dcom.ActivationContextInfoData{}
+ 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, mod.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 := &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")...)
+ mod.dce, err = cfg.GetDce(ctx, creds, target, DefaultDcomEndpoint, "", opts...)
+ if err != nil {
+ return err
+ }
+ log.Info().Msg("created new DCERPC dialer")
+
+ mod.dc, err = idispatch.NewDispatchClient(ctx, mod.dce, dcom.WithIPID(oIPID))
+ if err != nil {
+ return err
+ }
+ log.Info().Msg("created IDispatch client")
+ }
+ }
+ return
+}
+
+func (mod *Module) Exec(ctx context.Context, ecfg *exec.ExecutionConfig) (err error) {
+
+ log := zerolog.Ctx(ctx).With().
+ Str("method", ecfg.ExecutionMethod).
+ Str("func", "Exec").Logger()
+
+ if ecfg.ExecutionMethod == MethodMmc {
+ if cfg, ok := ecfg.ExecutionMethodConfig.(MethodMmcConfig); !ok {
+ return errors.New("invalid configuration")
+
+ } else {
+ // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/mmc/view-executeshellcommand
+ method := "Document.ActiveView.ExecuteShellCommand"
+ log = log.With().Str("classMethod", method).Logger()
+
+ log.Info().
+ Str("executable", ecfg.ExecutableName).
+ Str("arguments", ecfg.ExecutableArgs).Msg("Attempting execution")
+
+ command := &oaut.Variant{
+ Size: 5,
+ VT: 8,
+ VarUnion: &oaut.Variant_VarUnion{Value: &oaut.Variant_VarUnion_BSTR{BSTR: &oaut.String{Data: ecfg.ExecutableName}}},
+ }
+ directory := &oaut.Variant{
+ Size: 5,
+ VT: 8,
+ VarUnion: &oaut.Variant_VarUnion{Value: &oaut.Variant_VarUnion_BSTR{BSTR: &oaut.String{Data: cfg.WorkingDirectory}}},
+ }
+ parameters := &oaut.Variant{
+ Size: 5,
+ VT: 8,
+ VarUnion: &oaut.Variant_VarUnion{Value: &oaut.Variant_VarUnion_BSTR{BSTR: &oaut.String{Data: ecfg.ExecutableArgs}}},
+ }
+ windowState := &oaut.Variant{
+ Size: 5,
+ VT: 8,
+ VarUnion: &oaut.Variant_VarUnion{Value: &oaut.Variant_VarUnion_BSTR{BSTR: &oaut.String{Data: cfg.WindowState}}},
+ }
+ // Arguments must be passed in reverse order
+ if _, err := callMethod(ctx, mod.dc, method, windowState, parameters, directory, command); 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 nil
+}
diff --git a/internal/exec/dcom/module.go b/internal/exec/dcom/module.go
new file mode 100644
index 0000000..bbd50b5
--- /dev/null
+++ b/internal/exec/dcom/module.go
@@ -0,0 +1,21 @@
+package dcomexec
+
+import (
+ "github.com/oiweiwei/go-msrpc/dcerpc"
+ "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0"
+)
+
+type Module struct {
+ dce dcerpc.Conn
+ dc idispatch.DispatchClient
+ hostname string
+}
+
+type MethodMmcConfig struct {
+ WorkingDirectory string
+ WindowState string
+}
+
+const (
+ MethodMmc string = "mmc"
+)
diff --git a/internal/exec/scmr/exec.go b/internal/exec/scmr/exec.go
index 7134df0..656b212 100644
--- a/internal/exec/scmr/exec.go
+++ b/internal/exec/scmr/exec.go
@@ -27,7 +27,6 @@ func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target
if cfg, ok := ccfg.ConnectionMethodConfig.(dce.ConnectionMethodDCEConfig); !ok {
return fmt.Errorf("invalid configuration for DCE connection method")
} else {
-
// Fetch target hostname - for opening SCM handle
if mod.hostname, err = target.Hostname(ctx); err != nil {
log.Debug().Err(err).Msg("Failed to get target hostname")
@@ -157,7 +156,6 @@ func (mod *Module) Cleanup(ctx context.Context, ccfg *exec.CleanupConfig) (err e
func (mod *Module) Exec(ctx context.Context, ecfg *exec.ExecutionConfig) (err error) {
- //vctx := context.WithoutCancel(ctx)
log := zerolog.Ctx(ctx).With().
Str("method", ecfg.ExecutionMethod).
Str("func", "Exec").Logger()
diff --git a/internal/exec/scmr/service.go b/internal/exec/scmr/service.go
index 9a580cb..49c7506 100644
--- a/internal/exec/scmr/service.go
+++ b/internal/exec/scmr/service.go
@@ -1,25 +1,25 @@
package scmrexec
import (
- "context"
- "github.com/FalconOpsLLC/goexec/internal/windows"
- "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2"
+ "context"
+ "github.com/FalconOpsLLC/goexec/internal/windows"
+ "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2"
)
const (
- ServiceDeleteAccess uint32 = windows.SERVICE_DELETE
- ServiceModifyAccess uint32 = windows.SERVICE_QUERY_CONFIG | windows.SERVICE_CHANGE_CONFIG | windows.SERVICE_STOP | windows.SERVICE_START | windows.SERVICE_DELETE
- ServiceCreateAccess uint32 = windows.SC_MANAGER_CREATE_SERVICE | windows.SERVICE_START | windows.SERVICE_STOP | windows.SERVICE_DELETE
- ServiceAllAccess uint32 = ServiceCreateAccess | ServiceModifyAccess
+ ServiceDeleteAccess uint32 = windows.SERVICE_DELETE
+ ServiceModifyAccess uint32 = windows.SERVICE_QUERY_CONFIG | windows.SERVICE_CHANGE_CONFIG | windows.SERVICE_STOP | windows.SERVICE_START | windows.SERVICE_DELETE
+ ServiceCreateAccess uint32 = windows.SC_MANAGER_CREATE_SERVICE | windows.SERVICE_START | windows.SERVICE_STOP | windows.SERVICE_DELETE
+ ServiceAllAccess uint32 = ServiceCreateAccess | ServiceModifyAccess
)
type remoteService struct {
- name string
- handle *svcctl.Handle
- originalConfig *svcctl.QueryServiceConfigW
- originalState *svcctl.ServiceStatus
+ name string
+ handle *svcctl.Handle
+ originalConfig *svcctl.QueryServiceConfigW
+ originalState *svcctl.ServiceStatus
}
-func (mod *Module) parseServiceDependencies(ctx context.Context, ) (err error) {
- return nil
+func (mod *Module) parseServiceDependencies(ctx context.Context) (err error) {
+ return nil // TODO
}