mirror of
https://github.com/FalconOpsLLC/goexec
synced 2026-06-06 15:44:27 +00:00
Improve smb.OutputFileFetcher; introduce stage input
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user