aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO.md2
-rw-r--r--cmd/root.go4
-rw-r--r--cmd/scmr.go3
-rw-r--r--cmd/wmi.go240
-rw-r--r--go.mod14
-rw-r--r--go.sum15
-rw-r--r--internal/exec/dcom/exec.go331
-rw-r--r--internal/exec/exec.go3
-rw-r--r--internal/exec/wmi/exec.go75
-rw-r--r--internal/exec/wmi/module.go4
10 files changed, 394 insertions, 297 deletions
diff --git a/TODO.md b/TODO.md
index ca4358d..a2e1d0e 100644
--- a/TODO.md
+++ b/TODO.md
@@ -30,9 +30,9 @@
### Other
- [X] Add proxy support - see https://github.com/oiweiwei/go-msrpc/issues/21
+- [ ] `--unsafe` option - allow unsafe OPSEC (i.e. fetching execution output via file write/read)
- [ ] Descriptions for all modules and methods
- [ ] Add SMB file transfer interface
-- [ ] `--ctf` option - allow unsafe OPSEC (i.e. fetching execution output via file write/read)
- [ ] README
## Resolve Eventually
diff --git a/cmd/root.go b/cmd/root.go
index b44c50e..cbc38c0 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -23,7 +23,8 @@ var (
proxyUrl *url.URL
// Root flags
- debug bool
+ unsafe bool // not implemented
+ debug bool
// Generic flags
command string
@@ -103,6 +104,7 @@ func init() {
rootCmd.InitDefaultHelpCmd()
rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging")
rootCmd.PersistentFlags().StringVarP(&proxyStr, "proxy", "x", "", "Proxy URL")
+ rootCmd.PersistentFlags().BoolVar(&unsafe, "unsafe", false, "[NOT IMPLEMENTED] Don't ask for permission to run unsafe actions")
authOpts = &adauth.Options{Debug: log.Debug().Msgf}
authOpts.RegisterFlags(rootCmd.PersistentFlags())
diff --git a/cmd/scmr.go b/cmd/scmr.go
index ee2e9dc..11e1379 100644
--- a/cmd/scmr.go
+++ b/cmd/scmr.go
@@ -28,6 +28,7 @@ func scmrCreateCmdInit() {
scmrCreateCmd.Flags().BoolVar(&scmrNoStart, "no-start", false, "Don't start service")
scmrCreateCmd.Flags().StringVarP(&executablePath, "executable-path", "f", "", "Full path to a remote Windows executable file")
scmrCreateCmd.Flags().StringVarP(&executableArgs, "args", "a", "", "Arguments to pass to the executable")
+ scmrCreateCmd.Flags().BoolVarP(&scmrOutput, "output", "O", false, "Fetch program output")
if err := scmrCreateCmd.MarkFlagRequired("executable-path"); err != nil {
panic(err)
}
@@ -58,6 +59,7 @@ var (
scmrDisplayName string
scmrNoDelete bool
scmrNoStart bool
+ scmrOutput bool
creds *adauth.Credential
target *adauth.Target
@@ -104,6 +106,7 @@ References:
execCfg := &exec.ExecutionConfig{
ExecutablePath: executablePath,
ExecutableArgs: executableArgs,
+ ReturnOutput: scmrOutput,
ExecutionMethod: scmrexec.MethodCreate,
ExecutionMethodConfig: scmrexec.MethodCreateConfig{
diff --git a/cmd/wmi.go b/cmd/wmi.go
index df047a2..74996b7 100644
--- a/cmd/wmi.go
+++ b/cmd/wmi.go
@@ -1,108 +1,111 @@
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"
)
func wmiCmdInit() {
- wmiCustomCmdInit()
- wmiCmd.AddCommand(wmiCustomCmd)
- wmiProcessCmdInit()
- wmiCmd.AddCommand(wmiProcessCmd)
+ registerRpcFlags(wmiCmd)
+ wmiCallCmdInit()
+ wmiCmd.AddCommand(wmiCallCmd)
+ 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)
- }
+func wmiCallCmdInit() {
+ wmiCallCmd.Flags().StringVarP(&wmi.Method, "method", "m", "", `WMI Method to call (i.e. "Create")`)
+ wmiCallCmd.Flags().StringVarP(&wmi.Class, "class", "C", "", `WMI class to instantiate (i.e. "Win32_Process")`)
+ wmiCallCmd.Flags().StringVarP(&wmi.Args, "args", "A", "{}", `WMI Method argument(s) in JSON dictionary format (i.e. {"CommandLine":"calc.exe"})`)
+ if err := wmiCallCmd.MarkFlagRequired("method"); err != nil {
+ panic(err)
+ }
}
func wmiProcessCmdInit() {
- wmiProcessCmd.Flags().StringVarP(&command, "command", "c", "", "Process command line")
- wmiProcessCmd.Flags().StringVarP(&workingDirectory, "directory", "d", `C:\`, "Working directory")
- if err := wmiProcessCmd.MarkFlagRequired("command"); err != nil {
- panic(err)
- }
+ 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:
- The custom method creates an instance of the specified WMI class (-c),
+ wmi struct {
+ Namespace string // TODO
+ Class string
+ Method string
+ Args string
+ }
+ wmiMethodArgsMap map[string]any
+
+ wmiCmd = &cobra.Command{
+ Use: "wmi",
+ Short: "Establish execution via WMI",
+ 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: 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:
+ Args: needs(needsTarget("cifs"), needsRpcTarget("cifs"), func(cmd *cobra.Command, args []string) (err error) {
+ if err = json.Unmarshal([]byte(wmi.Args), &wmiMethodArgsMap); err != nil {
+ err = fmt.Errorf("parse JSON arguments: %w", err)
+ }
+ return
+ }),
+ Run: func(cmd *cobra.Command, args []string) {
+
+ executor := wmiexec.Module{}
+ cleanCfg := &exec.CleanupConfig{} // TODO
+ connCfg := &exec.ConnectionConfig{
+ ConnectionMethod: exec.ConnectionMethodDCE,
+ ConnectionMethodConfig: dceConfig,
+ }
+
+ execCfg := &exec.ExecutionConfig{
+ ExecutableName: executable,
+ ExecutableArgs: executableArgs,
+ ExecutionMethod: wmiexec.MethodCall,
+ ExecutionMethodConfig: wmiexec.MethodCallConfig{
+ Class: wmi.Class,
+ Method: wmi.Method,
+ Arguments: wmiMethodArgsMap,
+ },
+ }
+
+ ctx = log.With().
+ Str("module", "wmi").
+ Str("method", "proc").
+ Logger().WithContext(ctx)
+
+ if err := executor.Connect(ctx, creds, target, connCfg); err != nil {
+ log.Fatal().Err(err).Msg("Connection failed")
+ }
+ defer func() {
+ if err := executor.Cleanup(ctx, cleanCfg); err != nil {
+ log.Error().Err(err).Msg("Cleanup failed")
+ }
+ }()
+ if err := executor.Exec(ctx, execCfg); err != nil {
+ log.Error().Err(err).Msg("Execution 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).
@@ -110,31 +113,42 @@ References:
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: needs(needsTarget("cifs"), needsRpcTarget("cifs")),
+ Run: func(cmd *cobra.Command, args []string) {
+
+ executor := wmiexec.Module{}
+ cleanCfg := &exec.CleanupConfig{} // TODO
+ connCfg := &exec.ConnectionConfig{
+ ConnectionMethod: exec.ConnectionMethodDCE,
+ ConnectionMethodConfig: dceConfig,
+ }
+ execCfg := &exec.ExecutionConfig{
+ ExecutableName: executable,
+ ExecutableArgs: executableArgs,
+ ExecutionMethod: wmiexec.MethodProcess,
+
+ ExecutionMethodConfig: wmiexec.MethodProcessConfig{
+ Command: command,
+ WorkingDirectory: workingDirectory,
+ },
+ }
+
+ ctx = log.With().
+ Str("module", "wmi").
+ Str("method", "proc").
+ Logger().WithContext(ctx)
+
+ if err := executor.Connect(ctx, creds, target, connCfg); err != nil {
+ log.Fatal().Err(err).Msg("Connection failed")
+ }
+ defer func() {
+ if err := executor.Cleanup(ctx, cleanCfg); err != nil {
+ log.Error().Err(err).Msg("Cleanup failed")
+ }
+ }()
+ if err := executor.Exec(ctx, execCfg); err != nil {
+ log.Error().Err(err).Msg("Execution failed")
+ }
+ },
+ }
)
diff --git a/go.mod b/go.mod
index 2e9e839..793956b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,15 +1,15 @@
module github.com/FalconOpsLLC/goexec
-go 1.24.0
+go 1.24.1
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.4
- github.com/rs/zerolog v1.33.0
+ github.com/oiweiwei/go-msrpc v1.2.5
+ 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.35.0
+ golang.org/x/net v0.39.0
)
require (
@@ -27,8 +27,8 @@ 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
- golang.org/x/crypto v0.35.0 // indirect
- golang.org/x/sys v0.30.0 // indirect
- golang.org/x/text v0.22.0 // indirect
+ golang.org/x/crypto v0.37.0 // indirect
+ golang.org/x/sys v0.32.0 // indirect
+ golang.org/x/text v0.24.0 // indirect
software.sslmate.com/src/go-pkcs12 v0.5.0 // indirect
)
diff --git a/go.sum b/go.sum
index 1105eac..b2629fd 100644
--- a/go.sum
+++ b/go.sum
@@ -44,6 +44,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
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=
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=
@@ -52,8 +54,11 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
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=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
@@ -74,6 +79,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
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=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -83,6 +90,10 @@ 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=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -96,6 +107,8 @@ 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=
@@ -105,6 +118,8 @@ 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=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
diff --git a/internal/exec/dcom/exec.go b/internal/exec/dcom/exec.go
index b33791d..a34baf4 100644
--- a/internal/exec/dcom/exec.go
+++ b/internal/exec/dcom/exec.go
@@ -1,186 +1,181 @@
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"
+ "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]"
+ DefaultDcomEndpoint = "ncacn_ip_tcp:[135]"
)
var (
- MmcUuid = uuid.MustParse("49B2791A-B1AE-4C90-9B8E-E860BA07F889")
- ShellWindowsUuid = uuid.MustParse("9BA05972-F6A8-11CF-A442-00A0C90A8F39")
- 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,
- }
+ MmcUuid = uuid.MustParse("49B2791A-B1AE-4C90-9B8E-E860BA07F889")
+ ShellWindowsUuid = uuid.MustParse("9BA05972-F6A8-11CF-A442-00A0C90A8F39")
+ 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
+ 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 errors.New("invalid configuration for DCE connection method")
+ } else {
+ opts := []dcerpc.Option{dcerpc.WithSign(), dcerpc.WithSecurityLevel(0)}
+
+ // 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")...) // TODO
+ 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
+ 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/exec.go b/internal/exec/exec.go
index f3fe7ae..db83d91 100644
--- a/internal/exec/exec.go
+++ b/internal/exec/exec.go
@@ -28,8 +28,7 @@ type ExecutionConfig struct {
ExecutionMethod string // ExecutionMethod represents the specific execution strategy used by the module.
ExecutionMethodConfig interface{}
- //ExecutionOutput string // not implemented
- //ExecutionOutputConfig interface{} // not implemented
+ ReturnOutput bool
}
type ShellConfig struct {
diff --git a/internal/exec/wmi/exec.go b/internal/exec/wmi/exec.go
index 5f47738..b61bf5c 100644
--- a/internal/exec/wmi/exec.go
+++ b/internal/exec/wmi/exec.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ "github.com/FalconOpsLLC/goexec/internal/client/dce"
"github.com/FalconOpsLLC/goexec/internal/exec"
"github.com/RedTeamPentesting/adauth"
"github.com/RedTeamPentesting/adauth/dcerpcauth"
@@ -21,6 +22,7 @@ import (
const (
ProtocolSequenceRPC uint16 = 7
ProtocolSequenceNP uint16 = 15
+ DefaultWmiEndpoint string = "ncacn_ip_tcp:[135]"
)
var (
@@ -45,7 +47,74 @@ func (mod *Module) Cleanup(ctx context.Context, _ *exec.CleanupConfig) (err erro
return
}
-func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target, _ *exec.ConnectionConfig) (err error) {
+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", "Connect").Logger()
+
+ if cfg, ok := ccfg.ConnectionMethodConfig.(dce.ConnectionMethodDCEConfig); !ok {
+ return errors.New("invalid configuration for DCE connection method")
+ } else {
+ var dceOpts []dcerpc.Option
+
+ // Create DCE connection
+ if mod.dce, err = cfg.GetDce(ctx, creds, target, DefaultWmiEndpoint, "", dceOpts...); err != nil {
+ log.Error().Err(err).Msg("Failed to initialize DCE dialer")
+ return fmt.Errorf("create DCE dialer: %w", err)
+ }
+ ia, err := iactivation.NewActivationClient(ctx, mod.dce)
+ if err != nil {
+ log.Error().Err(err).Msg("Failed to create activation client")
+ return fmt.Errorf("create activation client: %w", err)
+ }
+ act, err := ia.RemoteActivation(ctx, &iactivation.RemoteActivationRequest{
+ ORPCThis: ORPCThis,
+ ClassID: wmi.Level1LoginClassID.GUID(),
+ IIDs: []*dcom.IID{iwbemlevel1login.Level1LoginIID},
+ RequestedProtocolSequences: []uint16{ProtocolSequenceRPC, ProtocolSequenceNP}, // TODO: dynamic
+ })
+ if err != nil {
+ return fmt.Errorf("request remote activation: %w", err)
+ }
+ if act.HResult != 0 {
+ return fmt.Errorf("remote activation failed with code %d", act.HResult)
+ }
+ retBinds := act.OXIDBindings.GetStringBindings()
+ if len(act.InterfaceData) < 1 || len(retBinds) < 1 {
+ return errors.New("remote activation failed")
+ }
+ ipid := act.InterfaceData[0].GetStandardObjectReference().Std.IPID
+ for _, sb := range retBinds {
+ //sb.NetworkAddr = target.AddressWithoutPort() // TODO: check if sb.NetworkAddr contains the port
+ dceOpts = append(dceOpts, dcerpc.WithEndpoint(sb.String()))
+ }
+
+ if mod.dce, err = cfg.GetDce(ctx, creds, target, DefaultWmiEndpoint, "", dceOpts...); err != nil {
+ log.Error().Err(err).Msg("Failed to initialize secondary DCE dialer")
+ }
+ loginClient, err := iwbemlevel1login.NewLevel1LoginClient(ctx, mod.dce, dcom.WithIPID(ipid))
+ if err != nil {
+ return fmt.Errorf("initialize wbem login client: %w", err)
+ }
+ login, err := loginClient.NTLMLogin(ctx, &iwbemlevel1login.NTLMLoginRequest{
+ This: ORPCThis,
+ NetworkResource: "//./root/cimv2", // TODO: make this dynamic
+ })
+ if err != nil {
+ return fmt.Errorf("ntlm login: %w", err)
+ }
+
+ mod.sc, err = iwbemservices.NewServicesClient(ctx, mod.dce, dcom.WithIPID(login.Namespace.InterfacePointer().IPID()))
+ if err != nil {
+ return fmt.Errorf("iwbemservices.NewServicesClient: %w", err)
+ }
+ }
+
+ return
+}
+
+func (mod *Module) _Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target, _ *exec.ConnectionConfig) (err error) {
var baseOpts, authOpts []dcerpc.Option
var ipid *dcom.IPID // This will store the IPID of the remote instance
@@ -157,8 +226,8 @@ func (mod *Module) Exec(ctx context.Context, ecfg *exec.ExecutionConfig) (err er
Str("module", "tsch").
Str("method", ecfg.ExecutionMethod).Logger()
- if ecfg.ExecutionMethod == MethodCustom {
- if cfg, ok := ecfg.ExecutionMethodConfig.(MethodCustomConfig); !ok {
+ if ecfg.ExecutionMethod == MethodCall {
+ if cfg, ok := ecfg.ExecutionMethodConfig.(MethodCallConfig); !ok {
return errors.New("invalid execution configuration")
} else {
diff --git a/internal/exec/wmi/module.go b/internal/exec/wmi/module.go
index f90af42..0e83aa8 100644
--- a/internal/exec/wmi/module.go
+++ b/internal/exec/wmi/module.go
@@ -16,7 +16,7 @@ type Module struct {
sc iwbemservices.ServicesClient
}
-type MethodCustomConfig struct {
+type MethodCallConfig struct {
Class string
Method string
Arguments map[string]any
@@ -28,6 +28,6 @@ type MethodProcessConfig struct {
}
const (
- MethodCustom = "custom"
+ MethodCall = "call"
MethodProcess = "process"
)