aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan McNulty <bryanmcnulty@protonmail.com>2025-04-20 18:23:36 -0500
committerBryan McNulty <bryanmcnulty@protonmail.com>2025-04-20 18:23:36 -0500
commit1168c8657117cb72426e9e2bfc68bf8ae9575bb1 (patch)
treeb6735b553e80719ccf453bde8db694e192bac8ee
parent6ade3ddd945e50d7a145294ac4681489be5d22f8 (diff)
downloadgoexec-1168c8657117cb72426e9e2bfc68bf8ae9575bb1.tar.gz
goexec-1168c8657117cb72426e9e2bfc68bf8ae9575bb1.zip
Improve smb.OutputFileFetcher; introduce stage input
-rw-r--r--TODO.md4
-rw-r--r--cmd/args.go7
-rw-r--r--cmd/root.go34
-rw-r--r--cmd/scmr.go3
-rw-r--r--cmd/tsch.go388
-rw-r--r--pkg/goexec/io.go9
-rw-r--r--pkg/goexec/method.go12
-rw-r--r--pkg/goexec/smb/client.go159
-rw-r--r--pkg/goexec/smb/input.go66
-rw-r--r--pkg/goexec/smb/output.go34
10 files changed, 416 insertions, 300 deletions
diff --git a/TODO.md b/TODO.md
index 6b54044..86fb2c3 100644
--- a/TODO.md
+++ b/TODO.md
@@ -8,14 +8,14 @@
- [X] Session hijacking
- [X] Generate random name/path
- [X] Output
-- [ ] Add command to tsch - update task if it already exists. See https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/849c131a-64e4-46ef-b015-9d4c599c5167 (`flags` argument)
+- [X] Add command to tsch - update task if it already exists. See https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/849c131a-64e4-46ef-b015-9d4c599c5167 (`flags` argument)
### SCMR
- [X] Clean up SCMR module
- [X] add dynamic string binding support
- [X] general clean up. Use TSCH & WMI as reference
-- [X] Output
+- [ ] Output
- [ ] Fix SCMR `change` method so that dependencies field isn't permanently overwritten
### DCOM
diff --git a/cmd/args.go b/cmd/args.go
index 8af8598..109a528 100644
--- a/cmd/args.go
+++ b/cmd/args.go
@@ -29,8 +29,13 @@ func registerNetworkFlags(fs *pflag.FlagSet) {
//cmd.MarkFlagsMutuallyExclusive("no-epm", "epm-filter")
}
+func registerStageFlags(fs *pflag.FlagSet) {
+ fs.StringVarP(&stageFilePath, "stage", "E", "", "File to stage and execute")
+ //fs.StringVarP(&stageArgs ...)
+}
+
func registerExecutionFlags(fs *pflag.FlagSet) {
- fs.StringVarP(&exec.Input.Executable, "executable", "e", "", "Windows executable to invoke")
+ fs.StringVarP(&exec.Input.Executable, "exec", "e", "", "Remote Windows executable to invoke")
fs.StringVarP(&exec.Input.Arguments, "args", "a", "", "Process command line arguments")
fs.StringVarP(&exec.Input.Command, "command", "c", "", "Windows process command line (executable & arguments)")
diff --git a/cmd/root.go b/cmd/root.go
index b1feaf2..a648b32 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -2,11 +2,11 @@ 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/google/uuid"
"github.com/oiweiwei/go-msrpc/ssp"
"github.com/oiweiwei/go-msrpc/ssp/gssapi"
"github.com/rs/zerolog"
@@ -55,10 +55,13 @@ var (
defaultAuthFlags, defaultLogFlags, defaultNetRpcFlags *flagSet
- returnCode int
- outputMethod string
- outputPath string
- proxy string
+ returnCode int
+
+ // === IO ===
+ stageFilePath string
+ outputMethod string
+ outputPath string
+ // ==========
// === Logging ===
logJson bool // Log output in JSON lines
@@ -70,8 +73,11 @@ var (
log zerolog.Logger
// ===============
+ // === Network ===
+ proxy string
rpcClient dce.Client
smbClient smb.Client
+ // ===============
exec = goexec.ExecutionIO{
Input: new(goexec.ExecutionInput),
@@ -86,15 +92,15 @@ var (
Use: "goexec",
Short: `goexec - Windows remote execution multitool`,
Long: `
- ___ ___ ___ _ _ ___ ___
-| . | . | -_|_'_| -_| _|
-|_ |___|___|_,_|___|___|
-|___|
+ ___ ___ ___ _ _ ___ ___
+ | . | . | -_|_'_| -_| _|
+ |_ |___|___|_,_|___|___|
+ |___|
Authors: FalconOps LLC (@FalconOpsLLC),
Bryan McNulty (@bryanmcnulty)
-> Goexec is designed to facilitate remote execution on Windows systems,
+> Goexec is designed to achieve remote execution on Windows systems,
while providing an extremely flexible CLI and a strong focus on OPSEC.
`,
@@ -130,11 +136,12 @@ Authors: FalconOps LLC (@FalconOpsLLC),
if outputPath != "" {
if outputMethod == "smb" {
if exec.Output.RemotePath == "" {
- exec.Output.RemotePath = util.RandomWindowsTempFile()
+ exec.Output.RemotePath = `C:\Windows\Temp\` + uuid.NewString()
}
exec.Output.Provider = &smb.OutputFileFetcher{
Client: &smbClient,
- Share: `C$`,
+ Share: `ADMIN$`, // TODO: dynamic
+ SharePath: `C:\Windows`,
File: exec.Output.RemotePath,
DeleteOutputFile: !exec.Output.NoDelete,
}
@@ -147,6 +154,9 @@ Authors: FalconOps LLC (@FalconOpsLLC),
if err := logFile.Close(); err != nil {
// ...
}
+ if err := exec.Input.StageFile.Close(); err != nil {
+ // ...
+ }
},
}
)
diff --git a/cmd/scmr.go b/cmd/scmr.go
index 5ad8f21..4edcf22 100644
--- a/cmd/scmr.go
+++ b/cmd/scmr.go
@@ -75,6 +75,7 @@ func scmrChangeCmdInit() {
// TODO: SCMR output
//registerExecutionOutputFlags(scmrChangeExecFlags.Flags)
+ //registerStageFlags(scmrChangeExecFlags.Flags)
cmdFlags[scmrChangeCmd] = []*flagSet{
scmrChangeFlags,
@@ -186,7 +187,7 @@ References:
ctx := log.With().
Str("module", "scmr").
- Str("method", "create").
+ Str("method", "change").
Logger().WithContext(gssapi.NewSecurityContext(context.Background()))
if err := goexec.ExecuteCleanMethod(ctx, &scmrChange, &exec); err != nil {
diff --git a/cmd/tsch.go b/cmd/tsch.go
index f52a064..d9eee9a 100644
--- a/cmd/tsch.go
+++ b/cmd/tsch.go
@@ -1,232 +1,232 @@
package cmd
import (
- "context"
- "fmt"
- "github.com/FalconOpsLLC/goexec/internal/util"
- "github.com/FalconOpsLLC/goexec/pkg/goexec"
- tschexec "github.com/FalconOpsLLC/goexec/pkg/goexec/tsch"
- "github.com/oiweiwei/go-msrpc/ssp/gssapi"
- "github.com/spf13/cobra"
- "time"
+ "context"
+ "fmt"
+ "github.com/FalconOpsLLC/goexec/internal/util"
+ "github.com/FalconOpsLLC/goexec/pkg/goexec"
+ tschexec "github.com/FalconOpsLLC/goexec/pkg/goexec/tsch"
+ "github.com/oiweiwei/go-msrpc/ssp/gssapi"
+ "github.com/spf13/cobra"
+ "time"
)
func tschCmdInit() {
- cmdFlags[tschCmd] = []*flagSet{
- defaultAuthFlags,
- defaultLogFlags,
- defaultNetRpcFlags,
- }
- tschDemandCmdInit()
- tschCreateCmdInit()
- tschChangeCmdInit()
-
- tschCmd.PersistentFlags().AddFlagSet(defaultAuthFlags.Flags)
- tschCmd.PersistentFlags().AddFlagSet(defaultLogFlags.Flags)
- tschCmd.PersistentFlags().AddFlagSet(defaultNetRpcFlags.Flags)
- tschCmd.AddCommand(tschDemandCmd, tschCreateCmd, tschChangeCmd)
+ cmdFlags[tschCmd] = []*flagSet{
+ defaultAuthFlags,
+ defaultLogFlags,
+ defaultNetRpcFlags,
+ }
+ tschDemandCmdInit()
+ tschCreateCmdInit()
+ tschChangeCmdInit()
+
+ tschCmd.PersistentFlags().AddFlagSet(defaultAuthFlags.Flags)
+ tschCmd.PersistentFlags().AddFlagSet(defaultLogFlags.Flags)
+ tschCmd.PersistentFlags().AddFlagSet(defaultNetRpcFlags.Flags)
+ tschCmd.AddCommand(tschDemandCmd, tschCreateCmd, tschChangeCmd)
}
func tschDemandCmdInit() {
- tschDemandFlags := newFlagSet("Task Scheduler")
+ tschDemandFlags := newFlagSet("Task Scheduler")
- tschDemandFlags.Flags.StringVarP(&tschTask, "task", "t", "", "Name or path of the new task")
- tschDemandFlags.Flags.Uint32Var(&tschDemand.SessionId, "session", 0, "Hijack existing session given the session ID")
- tschDemandFlags.Flags.StringVar(&tschDemand.UserSid, "sid", "S-1-5-18", "User SID to impersonate")
- tschDemandFlags.Flags.BoolVar(&tschDemand.NoDelete, "no-delete", false, "Don't delete task after execution")
+ tschDemandFlags.Flags.StringVarP(&tschTask, "task", "t", "", "Name or path of the new task")
+ tschDemandFlags.Flags.Uint32Var(&tschDemand.SessionId, "session", 0, "Hijack existing session given the session ID")
+ tschDemandFlags.Flags.StringVar(&tschDemand.UserSid, "sid", "S-1-5-18", "User SID to impersonate")
+ tschDemandFlags.Flags.BoolVar(&tschDemand.NoDelete, "no-delete", false, "Don't delete task after execution")
- tschDemandExecFlags := newFlagSet("Execution")
+ tschDemandExecFlags := newFlagSet("Execution")
- registerExecutionFlags(tschDemandExecFlags.Flags)
- registerExecutionOutputFlags(tschDemandExecFlags.Flags)
+ registerExecutionFlags(tschDemandExecFlags.Flags)
+ registerExecutionOutputFlags(tschDemandExecFlags.Flags)
- cmdFlags[tschDemandCmd] = []*flagSet{
- tschDemandFlags,
- tschDemandExecFlags,
- defaultAuthFlags,
- defaultLogFlags,
- defaultNetRpcFlags,
- }
+ cmdFlags[tschDemandCmd] = []*flagSet{
+ tschDemandFlags,
+ tschDemandExecFlags,
+ defaultAuthFlags,
+ defaultLogFlags,
+ defaultNetRpcFlags,
+ }
- tschDemandCmd.Flags().AddFlagSet(tschDemandFlags.Flags)
- tschDemandCmd.Flags().AddFlagSet(tschDemandExecFlags.Flags)
- tschDemandCmd.MarkFlagsOneRequired("executable", "command")
+ tschDemandCmd.Flags().AddFlagSet(tschDemandFlags.Flags)
+ tschDemandCmd.Flags().AddFlagSet(tschDemandExecFlags.Flags)
+ tschDemandCmd.MarkFlagsOneRequired("exec", "command")
}
func tschCreateCmdInit() {
- tschCreateFlags := newFlagSet("Task Scheduler")
-
- tschCreateFlags.Flags.StringVarP(&tschTask, "task", "t", "", "Name or path of the new task")
- tschCreateFlags.Flags.DurationVar(&tschCreate.StopDelay, "delay-stop", 5*time.Second, "Delay between task execution and termination. This won't stop the spawned process")
- tschCreateFlags.Flags.DurationVar(&tschCreate.StartDelay, "start-delay", 5*time.Second, "Delay between task registration and execution")
- //tschCreateFlags.Flags.DurationVar(&tschCreate.DeleteDelay, "delete-delay", 0*time.Second, "Delay between task termination and deletion")
- tschCreateFlags.Flags.BoolVar(&tschCreate.NoDelete, "no-delete", false, "Don't delete task after execution")
- tschCreateFlags.Flags.BoolVar(&tschCreate.CallDelete, "call-delete", false, "Directly call SchRpcDelete to delete task")
- tschCreateFlags.Flags.StringVar(&tschCreate.UserSid, "sid", "S-1-5-18", "User `SID` to impersonate")
-
- tschCreateExecFlags := newFlagSet("Execution")
-
- registerExecutionFlags(tschCreateExecFlags.Flags)
- registerExecutionOutputFlags(tschCreateExecFlags.Flags)
-
- cmdFlags[tschCreateCmd] = []*flagSet{
- tschCreateFlags,
- tschCreateExecFlags,
- defaultAuthFlags,
- defaultLogFlags,
- defaultNetRpcFlags,
- }
-
- tschCreateCmd.Flags().AddFlagSet(tschCreateFlags.Flags)
- tschCreateCmd.Flags().AddFlagSet(tschCreateExecFlags.Flags)
- tschCreateCmd.MarkFlagsOneRequired("executable", "command")
+ tschCreateFlags := newFlagSet("Task Scheduler")
+
+ tschCreateFlags.Flags.StringVarP(&tschTask, "task", "t", "", "Name or path of the new task")
+ tschCreateFlags.Flags.DurationVar(&tschCreate.StopDelay, "delay-stop", 5*time.Second, "Delay between task execution and termination. This won't stop the spawned process")
+ tschCreateFlags.Flags.DurationVar(&tschCreate.StartDelay, "start-delay", 5*time.Second, "Delay between task registration and execution")
+ //tschCreateFlags.Flags.DurationVar(&tschCreate.DeleteDelay, "delete-delay", 0*time.Second, "Delay between task termination and deletion")
+ tschCreateFlags.Flags.BoolVar(&tschCreate.NoDelete, "no-delete", false, "Don't delete task after execution")
+ tschCreateFlags.Flags.BoolVar(&tschCreate.CallDelete, "call-delete", false, "Directly call SchRpcDelete to delete task")
+ tschCreateFlags.Flags.StringVar(&tschCreate.UserSid, "sid", "S-1-5-18", "User `SID` to impersonate")
+
+ tschCreateExecFlags := newFlagSet("Execution")
+
+ registerExecutionFlags(tschCreateExecFlags.Flags)
+ registerExecutionOutputFlags(tschCreateExecFlags.Flags)
+
+ cmdFlags[tschCreateCmd] = []*flagSet{
+ tschCreateFlags,
+ tschCreateExecFlags,
+ defaultAuthFlags,
+ defaultLogFlags,
+ defaultNetRpcFlags,
+ }
+
+ tschCreateCmd.Flags().AddFlagSet(tschCreateFlags.Flags)
+ tschCreateCmd.Flags().AddFlagSet(tschCreateExecFlags.Flags)
+ tschCreateCmd.MarkFlagsOneRequired("exec", "command")
}
func tschChangeCmdInit() {
- tschChangeFlags := newFlagSet("Task Scheduler")
-
- tschChangeFlags.Flags.StringVarP(&tschChange.TaskPath, "task", "t", "", "Path to existing task")
- tschChangeFlags.Flags.BoolVar(&tschChange.NoStart, "no-start", false, "Don't start the task")
- tschChangeFlags.Flags.BoolVar(&tschChange.NoRevert, "no-revert", false, "Don't restore the original task definition")
-
- tschChangeExecFlags := newFlagSet("Execution")
-
- registerExecutionFlags(tschChangeExecFlags.Flags)
- registerExecutionOutputFlags(tschChangeExecFlags.Flags)
-
- cmdFlags[tschChangeCmd] = []*flagSet{
- tschChangeFlags,
- tschChangeExecFlags,
- defaultAuthFlags,
- defaultLogFlags,
- defaultNetRpcFlags,
- }
-
- tschChangeCmd.Flags().AddFlagSet(tschChangeFlags.Flags)
- tschChangeCmd.Flags().AddFlagSet(tschChangeExecFlags.Flags)
-
- // Constraints
- {
- if err := tschChangeCmd.MarkFlagRequired("task"); err != nil {
- panic(err)
- }
- tschChangeCmd.MarkFlagsOneRequired("executable", "command")
- }
+ tschChangeFlags := newFlagSet("Task Scheduler")
+
+ tschChangeFlags.Flags.StringVarP(&tschChange.TaskPath, "task", "t", "", "Path to existing task")
+ tschChangeFlags.Flags.BoolVar(&tschChange.NoStart, "no-start", false, "Don't start the task")
+ tschChangeFlags.Flags.BoolVar(&tschChange.NoRevert, "no-revert", false, "Don't restore the original task definition")
+
+ tschChangeExecFlags := newFlagSet("Execution")
+
+ registerExecutionFlags(tschChangeExecFlags.Flags)
+ registerExecutionOutputFlags(tschChangeExecFlags.Flags)
+
+ cmdFlags[tschChangeCmd] = []*flagSet{
+ tschChangeFlags,
+ tschChangeExecFlags,
+ defaultAuthFlags,
+ defaultLogFlags,
+ defaultNetRpcFlags,
+ }
+
+ tschChangeCmd.Flags().AddFlagSet(tschChangeFlags.Flags)
+ tschChangeCmd.Flags().AddFlagSet(tschChangeExecFlags.Flags)
+
+ // Constraints
+ {
+ if err := tschChangeCmd.MarkFlagRequired("task"); err != nil {
+ panic(err)
+ }
+ tschChangeCmd.MarkFlagsOneRequired("exec", "command")
+ }
}
func argsTask(*cobra.Command, []string) error {
- switch {
- case tschTask == "":
- tschTask = `\` + util.RandomString()
- case tschexec.ValidateTaskPath(tschTask) == nil:
- case tschexec.ValidateTaskName(tschTask) == nil:
- tschTask = `\` + tschTask
- default:
- return fmt.Errorf("invalid task Label or path: %q", tschTask)
- }
- return nil
+ switch {
+ case tschTask == "":
+ tschTask = `\` + util.RandomString()
+ case tschexec.ValidateTaskPath(tschTask) == nil:
+ case tschexec.ValidateTaskName(tschTask) == nil:
+ tschTask = `\` + tschTask
+ default:
+ return fmt.Errorf("invalid task Label or path: %q", tschTask)
+ }
+ return nil
}
var (
- tschDemand tschexec.TschDemand
- tschCreate tschexec.TschCreate
- tschChange tschexec.TschChange
-
- tschTask string
-
- tschCmd = &cobra.Command{
- Use: "tsch",
- Short: "Execute with Windows Task Scheduler (MS-TSCH)",
- GroupID: "module",
- Args: cobra.NoArgs,
- }
-
- tschDemandCmd = &cobra.Command{
- Use: "demand [target]",
- Short: "Register a remote scheduled task and demand immediate start",
- Long: `Description:
+ tschDemand tschexec.TschDemand
+ tschCreate tschexec.TschCreate
+ tschChange tschexec.TschChange
+
+ tschTask string
+
+ tschCmd = &cobra.Command{
+ Use: "tsch",
+ Short: "Execute with Windows Task Scheduler (MS-TSCH)",
+ GroupID: "module",
+ Args: cobra.NoArgs,
+ }
+
+ tschDemandCmd = &cobra.Command{
+ Use: "demand [target]",
+ Short: "Register a remote scheduled task and demand immediate start",
+ Long: `Description:
Similar to the create method, the demand method will call SchRpcRegisterTask,
But rather than setting a defined time when the task will start, it will
additionally call SchRpcRun to forcefully start the task.
`,
- Args: args(
- argsRpcClient("cifs"),
- argsOutput("smb"),
- argsTask,
- ),
-
- Run: func(*cobra.Command, []string) {
- tschDemand.IO = exec
- tschDemand.Client = &rpcClient
- tschDemand.TaskPath = tschTask
-
- ctx := log.With().
- Str("module", "tsch").
- Str("method", "demand").
- Logger().WithContext(gssapi.NewSecurityContext(context.TODO()))
-
- if err := goexec.ExecuteCleanMethod(ctx, &tschDemand, &exec); err != nil {
- log.Fatal().Err(err).Msg("Operation failed")
- }
- },
- }
- tschCreateCmd = &cobra.Command{
- Use: "create [target]",
- Short: "Create a remote scheduled task with an automatic start time",
- Long: `Description:
+ Args: args(
+ argsRpcClient("cifs"),
+ argsOutput("smb"),
+ argsTask,
+ ),
+
+ Run: func(*cobra.Command, []string) {
+ tschDemand.IO = exec
+ tschDemand.Client = &rpcClient
+ tschDemand.TaskPath = tschTask
+
+ ctx := log.With().
+ Str("module", "tsch").
+ Str("method", "demand").
+ Logger().WithContext(gssapi.NewSecurityContext(context.TODO()))
+
+ if err := goexec.ExecuteCleanMethod(ctx, &tschDemand, &exec); err != nil {
+ log.Fatal().Err(err).Msg("Operation failed")
+ }
+ },
+ }
+ tschCreateCmd = &cobra.Command{
+ Use: "create [target]",
+ Short: "Create a remote scheduled task with an automatic start time",
+ Long: `Description:
The create method calls SchRpcRegisterTask to register a scheduled task
with an automatic start time.This method avoids directly calling SchRpcRun,
and can even avoid calling SchRpcDelete by populating the DeleteExpiredTaskAfter
Setting.
`,
- Args: args(
- argsRpcClient("cifs"),
- argsOutput("smb"),
- argsTask,
- ),
-
- Run: func(*cobra.Command, []string) {
- tschCreate.Tsch.Client = &rpcClient
- tschCreate.IO = exec
- tschCreate.TaskPath = tschTask
-
- ctx := log.With().
- Str("module", "tsch").
- Str("method", "create").
- Logger().WithContext(gssapi.NewSecurityContext(context.TODO()))
-
- if err := goexec.ExecuteCleanMethod(ctx, &tschCreate, &exec); err != nil {
- log.Fatal().Err(err).Msg("Operation failed")
- }
- },
- }
- tschChangeCmd = &cobra.Command{
- Use: "change [target]",
- Short: "Modify an existing task to spawn an arbitrary process",
- Long: `Description:
+ Args: args(
+ argsRpcClient("cifs"),
+ argsOutput("smb"),
+ argsTask,
+ ),
+
+ Run: func(*cobra.Command, []string) {
+ tschCreate.Tsch.Client = &rpcClient
+ tschCreate.IO = exec
+ tschCreate.TaskPath = tschTask
+
+ ctx := log.With().
+ Str("module", "tsch").
+ Str("method", "create").
+ Logger().WithContext(gssapi.NewSecurityContext(context.TODO()))
+
+ if err := goexec.ExecuteCleanMethod(ctx, &tschCreate, &exec); err != nil {
+ log.Fatal().Err(err).Msg("Operation failed")
+ }
+ },
+ }
+ tschChangeCmd = &cobra.Command{
+ Use: "change [target]",
+ Short: "Modify an existing task to spawn an arbitrary process",
+ Long: `Description:
The change method calls SchRpcRetrieveTask to fetch the definition of an existing
task (-t), then modifies the task definition to spawn a process`,
- Args: args(
- argsRpcClient("cifs"),
- argsOutput("smb"),
-
- func(*cobra.Command, []string) error {
- return tschexec.ValidateTaskPath(tschChange.TaskPath)
- },
- ),
-
- Run: func(*cobra.Command, []string) {
- tschChange.Tsch.Client = &rpcClient
- tschChange.IO = exec
-
- ctx := log.With().
- Str("module", "tsch").
- Str("method", "change").
- Logger().WithContext(gssapi.NewSecurityContext(context.TODO()))
-
- if err := goexec.ExecuteCleanMethod(ctx, &tschChange, &exec); err != nil {
- log.Fatal().Err(err).Msg("Operation failed")
- }
- },
- }
+ Args: args(
+ argsRpcClient("cifs"),
+ argsOutput("smb"),
+
+ func(*cobra.Command, []string) error {
+ return tschexec.ValidateTaskPath(tschChange.TaskPath)
+ },
+ ),
+
+ Run: func(*cobra.Command, []string) {
+ tschChange.Tsch.Client = &rpcClient
+ tschChange.IO = exec
+
+ ctx := log.With().
+ Str("module", "tsch").
+ Str("method", "change").
+ Logger().WithContext(gssapi.NewSecurityContext(context.TODO()))
+
+ if err := goexec.ExecuteCleanMethod(ctx, &tschChange, &exec); err != nil {
+ log.Fatal().Err(err).Msg("Operation failed")
+ }
+ },
+ }
)
diff --git a/pkg/goexec/io.go b/pkg/goexec/io.go
index ab2b704..1d4358f 100644
--- a/pkg/goexec/io.go
+++ b/pkg/goexec/io.go
@@ -27,7 +27,7 @@ type ExecutionOutput struct {
}
type ExecutionInput struct {
- FilePath string
+ StageFile io.ReadCloser
Executable string
ExecutablePath string
Arguments string
@@ -89,3 +89,10 @@ func (i *ExecutionInput) CommandLine() (cmd []string) {
func (i *ExecutionInput) String() string {
return strings.Join(i.CommandLine(), " ")
}
+
+func (i *ExecutionInput) Reader() (reader io.Reader) {
+ if i.StageFile != nil {
+ return i.StageFile
+ }
+ return strings.NewReader(i.String())
+}
diff --git a/pkg/goexec/method.go b/pkg/goexec/method.go
index e57442f..88898f2 100644
--- a/pkg/goexec/method.go
+++ b/pkg/goexec/method.go
@@ -102,17 +102,15 @@ func ExecuteCleanAuxiliaryMethod(ctx context.Context, module CleanAuxiliaryMetho
func ExecuteCleanMethod(ctx context.Context, module CleanExecutionMethod, execIO *ExecutionIO) (err error) {
log := zerolog.Ctx(ctx)
- defer func() {
- if err = module.Clean(ctx); err != nil {
- log.Error().Err(err).Msg("Module cleanup failed")
- err = nil
- }
- }()
-
if err = ExecuteMethod(ctx, module, execIO); err != nil {
return
}
+ if err = module.Clean(ctx); err != nil {
+ log.Error().Err(err).Msg("Module cleanup failed")
+ err = nil
+ }
+
if execIO.Output != nil && execIO.Output.Provider != nil {
log.Info().Msg("Collecting output")
diff --git a/pkg/goexec/smb/client.go b/pkg/goexec/smb/client.go
index 3b41e39..d95481c 100644
--- a/pkg/goexec/smb/client.go
+++ b/pkg/goexec/smb/client.go
@@ -1,112 +1,123 @@
package smb
import (
- "context"
- "errors"
- "fmt"
- "github.com/oiweiwei/go-smb2.fork"
- "github.com/rs/zerolog"
- "net"
+ "context"
+ "errors"
+ "fmt"
+ "github.com/oiweiwei/go-smb2.fork"
+ "github.com/rs/zerolog"
+ "net"
)
type Client struct {
- ClientOptions
+ ClientOptions
- conn net.Conn
- sess *smb2.Session
- mount *smb2.Share
+ conn net.Conn
+ sess *smb2.Session
+ mount *smb2.Share
+
+ connected bool
+ share string
}
func (c *Client) Session() (sess *smb2.Session) {
- return c.sess
+ return c.sess
}
func (c *Client) String() string {
- return ClientName
+ return ClientName
}
func (c *Client) Logger(ctx context.Context) zerolog.Logger {
- return zerolog.Ctx(ctx).With().Str("client", c.String()).Logger()
+ return zerolog.Ctx(ctx).With().Str("client", c.String()).Logger()
}
func (c *Client) Mount(ctx context.Context, share string) (err error) {
- if c.sess == nil {
- return errors.New("SMB session not initialized")
- }
+ if c.sess == nil {
+ return errors.New("SMB session not initialized")
+ }
- c.mount, err = c.sess.Mount(share)
- zerolog.Ctx(ctx).Debug().Str("share", share).Msg("Mounted SMB share")
+ c.mount, err = c.sess.Mount(share)
+ zerolog.Ctx(ctx).Debug().Str("share", share).Msg("Mounted SMB share")
+ c.share = share
- return
+ return
}
func (c *Client) Connect(ctx context.Context) (err error) {
- log := c.Logger(ctx)
- {
- if c.netDialer == nil {
- panic(fmt.Errorf("TCP dialer not initialized"))
- }
- if c.dialer == nil {
- panic(fmt.Errorf("%s dialer not initialized", c.String()))
- }
- }
+ log := c.Logger(ctx)
+ {
+ if c.netDialer == nil {
+ panic(fmt.Errorf("TCP dialer not initialized"))
+ }
+ if c.dialer == nil {
+ panic(fmt.Errorf("%s dialer not initialized", c.String()))
+ }
+ }
- // Establish TCP connection
- c.conn, err = c.netDialer.Dial("tcp", net.JoinHostPort(c.Host, fmt.Sprintf("%d", c.Port)))
+ // Establish TCP connection
+ c.conn, err = c.netDialer.Dial("tcp", net.JoinHostPort(c.Host, fmt.Sprintf("%d", c.Port)))
- if err != nil {
- return err
- }
+ if err != nil {
+ return err
+ }
- log = log.With().Str("address", c.conn.RemoteAddr().String()).Logger()
- log.Debug().Msgf("Connected to %s server", c.String())
+ log = log.With().Str("address", c.conn.RemoteAddr().String()).Logger()
+ log.Debug().Msgf("Connected to %s server", c.String())
- // Open SMB session
- c.sess, err = c.dialer.DialContext(ctx, c.conn)
+ // Open SMB session
+ c.sess, err = c.dialer.DialContext(ctx, c.conn)
- if err != nil {
- log.Error().Err(err).Msgf("Failed to open %s session", c.String())
- return fmt.Errorf("dial %s: %w", c.String(), err)
- }
+ if err != nil {
+ log.Error().Err(err).Msgf("Failed to open %s session", c.String())
+ return fmt.Errorf("dial %s: %w", c.String(), err)
+ }
+ log.Debug().Msgf("Opened %s session", c.String())
- log.Debug().Msgf("Opened %s session", c.String())
+ c.connected = true
- return
+ return
}
func (c *Client) Close(ctx context.Context) (err error) {
- log := c.Logger(ctx)
-
- // Close SMB session
- if c.sess != nil {
- defer func() {
- if err = c.sess.Logoff(); err != nil {
- log.Debug().Err(err).Msgf("Failed to discard SMB session")
- }
- log.Debug().Msgf("Discarded SMB session")
- }()
-
- } else if c.conn != nil {
-
- defer func() {
- if err = c.conn.Close(); err != nil {
- log.Debug().Err(err).Msgf("Failed to disconnect SMB client")
- }
- log.Debug().Msgf("Disconnected SMB session")
- }()
- }
-
- // Unmount SMB share
- if c.mount != nil {
- defer func() {
- if err = c.mount.Umount(); err != nil {
- log.Debug().Err(err).Msg("Failed to unmount share")
- }
- log.Debug().Msg("Unmounted file share")
- }()
- }
- return
+ log := c.Logger(ctx)
+
+ c.connected = false
+
+ // Close SMB session
+ if c.sess != nil {
+ defer func() {
+ if err = c.sess.Logoff(); err != nil {
+ log.Debug().Err(err).Msgf("Failed to discard SMB session")
+ } else {
+ log.Debug().Msg("Discarded SMB session")
+ }
+ }()
+
+ } else if c.conn != nil {
+
+ defer func() {
+ if err = c.conn.Close(); err != nil {
+ log.Debug().Err(err).Msgf("Failed to disconnect SMB client")
+ } else {
+ log.Debug().Msg("Disconnected SMB client")
+ }
+ }()
+ }
+
+ // Unmount SMB share
+ if c.mount != nil {
+ defer func() {
+ if err = c.mount.Umount(); err != nil {
+ log.Debug().Err(err).Msg("Failed to unmount share")
+ } else {
+ log.Debug().Msg("Unmounted file share")
+ }
+ c.share = ""
+ }()
+ }
+ return
}
diff --git a/pkg/goexec/smb/input.go b/pkg/goexec/smb/input.go
new file mode 100644
index 0000000..b9cb3bc
--- /dev/null
+++ b/pkg/goexec/smb/input.go
@@ -0,0 +1,66 @@
+package smb
+
+import (
+ "context"
+ "fmt"
+ "github.com/FalconOpsLLC/goexec/pkg/goexec"
+ "io"
+ "os"
+ "path"
+ "strings"
+)
+
+type FileStager struct {
+ goexec.Cleaner
+
+ Client *Client
+
+ Share string
+ SharePath string
+ File string
+ relativePath string
+ ForceReconnect bool
+ DeleteStage bool
+}
+
+func (o *FileStager) Stage(ctx context.Context, reader io.Reader) (err error) {
+
+ o.relativePath = path.Join(
+ strings.ReplaceAll(pathPrefix.ReplaceAllString(o.SharePath, ""), `\`, "/"),
+ strings.ReplaceAll(pathPrefix.ReplaceAllString(o.File, ""), `\`, "/"),
+ )
+
+ if o.ForceReconnect || !o.Client.connected {
+ err = o.Client.Connect(ctx)
+ if err != nil {
+ return
+ }
+ defer o.AddCleaners(o.Client.Close)
+ }
+
+ if o.ForceReconnect || o.Client.share != o.Share {
+ err = o.Client.Mount(ctx, o.Share)
+ if err != nil {
+ return
+ }
+ }
+
+ writer, err := o.Client.mount.OpenFile(o.relativePath, os.O_WRONLY, 0644)
+ if err != nil {
+ return fmt.Errorf("open remote file for writing: %w", err)
+ }
+
+ if _, err = io.Copy(writer, reader); err != nil {
+ return
+ }
+
+ o.AddCleaners(func(_ context.Context) error { return writer.Close() })
+
+ if o.DeleteStage {
+ o.AddCleaners(func(_ context.Context) error {
+ return o.Client.mount.Remove(o.relativePath)
+ })
+ }
+
+ return
+}
diff --git a/pkg/goexec/smb/output.go b/pkg/goexec/smb/output.go
index f0a656d..d75cddb 100644
--- a/pkg/goexec/smb/output.go
+++ b/pkg/goexec/smb/output.go
@@ -4,9 +4,12 @@ import (
"context"
"errors"
"github.com/FalconOpsLLC/goexec/pkg/goexec"
+ "github.com/rs/zerolog"
"io"
"os"
+ "path/filepath"
"regexp"
+ "strings"
"time"
)
@@ -22,8 +25,10 @@ type OutputFileFetcher struct {
Client *Client
Share string
+ SharePath string
File string
DeleteOutputFile bool
+ ForceReconnect bool
PollInterval time.Duration
PollTimeout time.Duration
@@ -32,6 +37,8 @@ type OutputFileFetcher struct {
func (o *OutputFileFetcher) GetOutput(ctx context.Context, writer io.Writer) (err error) {
+ log := zerolog.Ctx(ctx)
+
if o.PollInterval == 0 {
o.PollInterval = DefaultOutputPollInterval
}
@@ -39,17 +46,28 @@ func (o *OutputFileFetcher) GetOutput(ctx context.Context, writer io.Writer) (er
o.PollTimeout = DefaultOutputPollTimeout
}
- o.relativePath = pathPrefix.ReplaceAllString(o.File, "")
+ shp := pathPrefix.ReplaceAllString(strings.ToLower(strings.ReplaceAll(o.SharePath, `\`, "/")), "")
+ fp := pathPrefix.ReplaceAllString(strings.ToLower(strings.ReplaceAll(o.File, `\`, "/")), "")
- err = o.Client.Connect(ctx)
- if err != nil {
+ if o.relativePath, err = filepath.Rel(shp, fp); err != nil {
return
}
- defer o.AddCleaners(o.Client.Close)
- err = o.Client.Mount(ctx, o.Share)
- if err != nil {
- return
+ log.Info().Str("path", o.relativePath).Msg("Fetching output file")
+
+ if o.ForceReconnect || !o.Client.connected {
+ err = o.Client.Connect(ctx)
+ if err != nil {
+ return
+ }
+ defer o.AddCleaners(o.Client.Close)
+ }
+
+ if o.ForceReconnect || o.Client.share != o.Share {
+ err = o.Client.Mount(ctx, o.Share)
+ if err != nil {
+ return
+ }
}
stopAt := time.Now().Add(o.PollTimeout)
@@ -57,7 +75,7 @@ func (o *OutputFileFetcher) GetOutput(ctx context.Context, writer io.Writer) (er
for {
if time.Now().After(stopAt) {
- return errors.New("output timeout")
+ return errors.New("execution output timeout")
}
if reader, err = o.Client.mount.OpenFile(o.relativePath, os.O_RDONLY, 0); err == nil {
break