Improve smb.OutputFileFetcher; introduce stage input

This commit is contained in:
Bryan McNulty
2025-04-20 18:23:36 -05:00
parent 6ade3ddd94
commit 1168c86571
10 changed files with 387 additions and 271 deletions
+2 -2
View File
@@ -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
+6 -1
View File
@@ -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)")
+22 -12
View File
@@ -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 {
// ...
}
},
}
)
+2 -1
View File
@@ -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 {
+169 -169
View File
@@ -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()
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)
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 := 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")
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")
tschCreateExecFlags := newFlagSet("Execution")
registerExecutionFlags(tschCreateExecFlags.Flags)
registerExecutionOutputFlags(tschCreateExecFlags.Flags)
registerExecutionFlags(tschCreateExecFlags.Flags)
registerExecutionOutputFlags(tschCreateExecFlags.Flags)
cmdFlags[tschCreateCmd] = []*flagSet{
tschCreateFlags,
tschCreateExecFlags,
defaultAuthFlags,
defaultLogFlags,
defaultNetRpcFlags,
}
cmdFlags[tschCreateCmd] = []*flagSet{
tschCreateFlags,
tschCreateExecFlags,
defaultAuthFlags,
defaultLogFlags,
defaultNetRpcFlags,
}
tschCreateCmd.Flags().AddFlagSet(tschCreateFlags.Flags)
tschCreateCmd.Flags().AddFlagSet(tschCreateExecFlags.Flags)
tschCreateCmd.MarkFlagsOneRequired("executable", "command")
tschCreateCmd.Flags().AddFlagSet(tschCreateFlags.Flags)
tschCreateCmd.Flags().AddFlagSet(tschCreateExecFlags.Flags)
tschCreateCmd.MarkFlagsOneRequired("exec", "command")
}
func tschChangeCmdInit() {
tschChangeFlags := newFlagSet("Task Scheduler")
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")
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")
tschChangeExecFlags := newFlagSet("Execution")
registerExecutionFlags(tschChangeExecFlags.Flags)
registerExecutionOutputFlags(tschChangeExecFlags.Flags)
registerExecutionFlags(tschChangeExecFlags.Flags)
registerExecutionOutputFlags(tschChangeExecFlags.Flags)
cmdFlags[tschChangeCmd] = []*flagSet{
tschChangeFlags,
tschChangeExecFlags,
defaultAuthFlags,
defaultLogFlags,
defaultNetRpcFlags,
}
cmdFlags[tschChangeCmd] = []*flagSet{
tschChangeFlags,
tschChangeExecFlags,
defaultAuthFlags,
defaultLogFlags,
defaultNetRpcFlags,
}
tschChangeCmd.Flags().AddFlagSet(tschChangeFlags.Flags)
tschChangeCmd.Flags().AddFlagSet(tschChangeExecFlags.Flags)
tschChangeCmd.Flags().AddFlagSet(tschChangeFlags.Flags)
tschChangeCmd.Flags().AddFlagSet(tschChangeExecFlags.Flags)
// Constraints
{
if err := tschChangeCmd.MarkFlagRequired("task"); err != nil {
panic(err)
}
tschChangeCmd.MarkFlagsOneRequired("executable", "command")
}
// 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
tschDemand tschexec.TschDemand
tschCreate tschexec.TschCreate
tschChange tschexec.TschChange
tschTask string
tschTask string
tschCmd = &cobra.Command{
Use: "tsch",
Short: "Execute with Windows Task Scheduler (MS-TSCH)",
GroupID: "module",
Args: cobra.NoArgs,
}
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:
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,
),
Args: args(
argsRpcClient("cifs"),
argsOutput("smb"),
argsTask,
),
Run: func(*cobra.Command, []string) {
tschDemand.IO = exec
tschDemand.Client = &rpcClient
tschDemand.TaskPath = tschTask
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()))
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:
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,
),
Args: args(
argsRpcClient("cifs"),
argsOutput("smb"),
argsTask,
),
Run: func(*cobra.Command, []string) {
tschCreate.Tsch.Client = &rpcClient
tschCreate.IO = exec
tschCreate.TaskPath = tschTask
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()))
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:
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"),
Args: args(
argsRpcClient("cifs"),
argsOutput("smb"),
func(*cobra.Command, []string) error {
return tschexec.ValidateTaskPath(tschChange.TaskPath)
},
),
func(*cobra.Command, []string) error {
return tschexec.ValidateTaskPath(tschChange.TaskPath)
},
),
Run: func(*cobra.Command, []string) {
tschChange.Tsch.Client = &rpcClient
tschChange.IO = exec
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()))
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")
}
},
}
if err := goexec.ExecuteCleanMethod(ctx, &tschChange, &exec); err != nil {
log.Fatal().Err(err).Msg("Operation failed")
}
},
}
)
+8 -1
View File
@@ -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())
}
+5 -7
View File
@@ -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")
+81 -70
View File
@@ -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)
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")
}()
c.connected = false
} else if c.conn != nil {
// 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")
}
}()
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")
}()
}
} else if c.conn != nil {
// 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
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
}
+66
View File
@@ -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
}
+26 -8
View File
@@ -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