diff options
author | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-04-16 12:11:58 -0500 |
---|---|---|
committer | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-04-16 12:11:58 -0500 |
commit | 55eb4275fb760ac7a3ce1444f5ae0ded9e2ff91c (patch) | |
tree | edf4ec3b814fb10ccdbf759a62819a865d3e8141 /internal | |
parent | a827b67d47cba7b02ea9599fe6bb88ffb3a6967d (diff) | |
download | goexec-55eb4275fb760ac7a3ce1444f5ae0ded9e2ff91c.tar.gz goexec-55eb4275fb760ac7a3ce1444f5ae0ded9e2ff91c.zip |
rewrote everything lol
Diffstat (limited to 'internal')
-rw-r--r-- | internal/client/dce/dce.go | 74 | ||||
-rw-r--r-- | internal/exec/dcom/dcom.go | 65 | ||||
-rw-r--r-- | internal/exec/dcom/exec.go | 181 | ||||
-rw-r--r-- | internal/exec/dcom/module.go | 21 | ||||
-rw-r--r-- | internal/exec/exec.go | 54 | ||||
-rw-r--r-- | internal/exec/scmr/exec.go | 345 | ||||
-rw-r--r-- | internal/exec/scmr/module.go | 44 | ||||
-rw-r--r-- | internal/exec/scmr/service.go | 25 | ||||
-rw-r--r-- | internal/exec/tsch/exec.go | 189 | ||||
-rw-r--r-- | internal/exec/tsch/module.go | 44 | ||||
-rw-r--r-- | internal/exec/tsch/task.go | 85 | ||||
-rw-r--r-- | internal/exec/tsch/tsch.go | 141 | ||||
-rw-r--r-- | internal/exec/wmi/exec.go | 166 | ||||
-rw-r--r-- | internal/exec/wmi/module.go | 33 | ||||
-rw-r--r-- | internal/exec/wmi/wmi.go | 26 | ||||
-rw-r--r-- | internal/util/util.go | 5 |
16 files changed, 5 insertions, 1493 deletions
diff --git a/internal/client/dce/dce.go b/internal/client/dce/dce.go deleted file mode 100644 index 0230c2a..0000000 --- a/internal/client/dce/dce.go +++ /dev/null @@ -1,74 +0,0 @@ -package dce - -import ( - "context" - "errors" - "fmt" - "github.com/RedTeamPentesting/adauth" - "github.com/RedTeamPentesting/adauth/dcerpcauth" - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/midl/uuid" - "github.com/oiweiwei/go-msrpc/msrpc/epm/epm/v3" - "github.com/oiweiwei/go-msrpc/ssp/gssapi" - "github.com/rs/zerolog" -) - -type ConnectionMethodDCEConfig struct { - NoEpm bool // NoEpm disables EPM - EpmAuto bool // EpmAuto will find any suitable endpoint, without any filter - Endpoint *dcerpc.StringBinding // Endpoint is the explicit endpoint passed to dcerpc.WithEndpoint for use without EPM - EpmFilter *dcerpc.StringBinding // EpmFilter is the rough filter used to pick an EPM endpoint - Options []dcerpc.Option // Options stores the options that will be passed to all dialers - DceOptions []dcerpc.Option // DceOptions are the options passed to dcerpc.Dial - EpmOptions []dcerpc.Option // EpmOptions are the options passed to epm.EndpointMapper - Resource string // Resource stores the target network resource (usually for DCOM) -} - -func (cfg *ConnectionMethodDCEConfig) GetDce(ctx context.Context, cred *adauth.Credential, target *adauth.Target, endpoint, object string, arbOpts ...dcerpc.Option) (cc dcerpc.Conn, err error) { - - log := zerolog.Ctx(ctx).With(). - Str("client", "DCERPC").Logger() - - // Mandatory logging - dceOpts := append(append(cfg.Options, arbOpts...), append(cfg.DceOptions, dcerpc.WithLogger(log))...) - epmOpts := append(cfg.Options, append(cfg.EpmOptions, dcerpc.WithLogger(log))...) - - ctx = gssapi.NewSecurityContext(ctx) - auth, err := dcerpcauth.AuthenticationOptions(ctx, cred, target, &dcerpcauth.Options{}) - if err != nil { - log.Error().Err(err).Msg("Failed to parse authentication options") - return nil, fmt.Errorf("parse auth options: %w", err) - } - addr := target.AddressWithoutPort() - log = log.With().Str("address", addr).Logger() - - if object != "" { - if id, err := uuid.Parse(object); err != nil { - log.Error().Err(err).Msg("Failed to parse input object UUID") - } else { - dceOpts = append(dceOpts, dcerpc.WithObjectUUID(id)) - } - } - if cfg.Endpoint != nil { - dceOpts = append(dceOpts, dcerpc.WithEndpoint(cfg.Endpoint.String())) - log.Debug().Str("binding", cfg.Endpoint.String()).Msg("Using endpoint") - - } else if !cfg.NoEpm { - dceOpts = append(dceOpts, epm.EndpointMapper(ctx, addr, append(epmOpts, auth...)...)) - log.Debug().Msg("Using endpoint mapper") - - if cfg.EpmFilter != nil { - dceOpts = append(dceOpts, dcerpc.WithEndpoint(cfg.EpmFilter.String())) - log.Debug().Str("filter", cfg.EpmFilter.String()).Msg("Using endpoint filter") - } - } else if endpoint != "" { - dceOpts = append(dceOpts, dcerpc.WithEndpoint(endpoint)) - log.Debug().Str("endpoint", endpoint).Msg("Using default endpoint") - - } else { - log.Err(err).Msg("Invalid DCE connection options") - return nil, errors.New("get DCE: invalid connection options") - } - - return dcerpc.Dial(ctx, target.AddressWithoutPort(), append(dceOpts, auth...)...) -} diff --git a/internal/exec/dcom/dcom.go b/internal/exec/dcom/dcom.go deleted file mode 100644 index b96bbc8..0000000 --- a/internal/exec/dcom/dcom.go +++ /dev/null @@ -1,65 +0,0 @@ -package dcomexec - -import ( - "context" - "fmt" - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/msrpc/dcom" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0" - "strings" -) - -const ( - LC_ENGLISH_US uint32 = 0x409 -) - -func callMethod(ctx context.Context, dc idispatch.DispatchClient, method string, args ...*oaut.Variant) (ir *idispatch.InvokeResponse, err error) { - parts := strings.Split(method, ".") - - var id *dcom.IPID - var gr *idispatch.GetIDsOfNamesResponse - - for i, obj := range parts { - var opts []dcerpc.CallOption - if id != nil { - opts = append(opts, dcom.WithIPID(id)) - } - gr, err = dc.GetIDsOfNames(ctx, &idispatch.GetIDsOfNamesRequest{ - This: ORPCThis, - IID: &dcom.IID{}, - Names: []string{obj + "\x00"}, - LocaleID: LC_ENGLISH_US, - }, opts...) - - if err != nil { - return nil, fmt.Errorf("get dispatch ID of name %q: %w", obj, err) - } - if len(gr.DispatchID) < 1 { - return nil, fmt.Errorf("dispatch ID of name %q not found", obj) - } - irq := &idispatch.InvokeRequest{ - This: ORPCThis, - DispatchIDMember: gr.DispatchID[0], - IID: &dcom.IID{}, - LocaleID: LC_ENGLISH_US, - } - if i >= len(parts)-1 { - irq.Flags = 1 - irq.DispatchParams = &oaut.DispatchParams{ArgsCount: uint32(len(args)), Args: args} - return dc.Invoke(ctx, irq, opts...) - } - irq.Flags = 2 - ir, err = dc.Invoke(ctx, irq, opts...) - if err != nil { - return nil, fmt.Errorf("get properties of object %q: %w", obj, err) - } - di, ok := ir.VarResult.VarUnion.GetValue().(*oaut.Dispatch) - if !ok { - return nil, fmt.Errorf("invalid dispatch object for %q", obj) - } - id = di.InterfacePointer().GetStandardObjectReference().Std.IPID - } - - return -} diff --git a/internal/exec/dcom/exec.go b/internal/exec/dcom/exec.go deleted file mode 100644 index a34baf4..0000000 --- a/internal/exec/dcom/exec.go +++ /dev/null @@ -1,181 +0,0 @@ -package dcomexec - -import ( - "context" - "errors" - "fmt" - "github.com/FalconOpsLLC/goexec/internal/client/dce" - "github.com/FalconOpsLLC/goexec/internal/exec" - "github.com/RedTeamPentesting/adauth" - guuid "github.com/google/uuid" - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/midl/uuid" - "github.com/oiweiwei/go-msrpc/msrpc/dcom" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/iremotescmactivator/v0" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0" - "github.com/oiweiwei/go-msrpc/msrpc/dtyp" - "github.com/rs/zerolog" -) - -const ( - DefaultDcomEndpoint = "ncacn_ip_tcp:[135]" -) - -var ( - MmcUuid = uuid.MustParse("49B2791A-B1AE-4C90-9B8E-E860BA07F889") - ShellWindowsUuid = uuid.MustParse("9BA05972-F6A8-11CF-A442-00A0C90A8F39") - 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 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 -} diff --git a/internal/exec/dcom/module.go b/internal/exec/dcom/module.go deleted file mode 100644 index bbd50b5..0000000 --- a/internal/exec/dcom/module.go +++ /dev/null @@ -1,21 +0,0 @@ -package dcomexec - -import ( - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0" -) - -type Module struct { - dce dcerpc.Conn - dc idispatch.DispatchClient - hostname string -} - -type MethodMmcConfig struct { - WorkingDirectory string - WindowState string -} - -const ( - MethodMmc string = "mmc" -) diff --git a/internal/exec/exec.go b/internal/exec/exec.go deleted file mode 100644 index db83d91..0000000 --- a/internal/exec/exec.go +++ /dev/null @@ -1,54 +0,0 @@ -package exec - -import ( - "context" - "fmt" - "github.com/RedTeamPentesting/adauth" - "strings" -) - -const ( - ConnectionMethodDCE = "dcerpc" -) - -type ConnectionConfig struct { - ConnectionMethod string - ConnectionMethodConfig interface{} -} - -type CleanupConfig struct { - CleanupMethod string - CleanupMethodConfig interface{} -} - -type ExecutionConfig struct { - ExecutableName string // ExecutableName represents the name of the executable; i.e. "notepad.exe", "calc" - ExecutablePath string // ExecutablePath represents the full path to the executable; i.e. `C:\Windows\explorer.exe` - ExecutableArgs string // ExecutableArgs represents the arguments to be passed to the executable during execution; i.e. "/C whoami" - - ExecutionMethod string // ExecutionMethod represents the specific execution strategy used by the module. - ExecutionMethodConfig interface{} - ReturnOutput bool -} - -type ShellConfig struct { - ShellName string // ShellName specifies the name of the shell executable; i.e. "cmd.exe", "powershell" - ShellPath string // ShellPath is the full Windows path to the shell executable; i.e. `C:\Windows\System32\cmd.exe` -} - -type Module interface { - Connect(context.Context, *adauth.Credential, *adauth.Target, *ConnectionConfig) error - Exec(context.Context, *ExecutionConfig) error - Cleanup(context.Context, *CleanupConfig) error -} - -func (cfg *ExecutionConfig) GetRawCommand() string { - executable := cfg.ExecutablePath - if strings.Contains(executable, " ") { - executable = fmt.Sprintf("%q", executable) - } - if cfg.ExecutableArgs != "" { - return executable + " " + cfg.ExecutableArgs - } - return executable -} diff --git a/internal/exec/scmr/exec.go b/internal/exec/scmr/exec.go deleted file mode 100644 index 9d15dd5..0000000 --- a/internal/exec/scmr/exec.go +++ /dev/null @@ -1,345 +0,0 @@ -package scmrexec - -import ( - "context" - "errors" - "fmt" - "github.com/FalconOpsLLC/goexec/internal/client/dce" - "github.com/FalconOpsLLC/goexec/internal/exec" - "github.com/FalconOpsLLC/goexec/internal/util" - "github.com/FalconOpsLLC/goexec/internal/windows" - "github.com/RedTeamPentesting/adauth" - "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2" - "github.com/rs/zerolog" -) - -const ( - ScmrDefaultEndpoint = "ncacn_np:[svcctl]" - ScmrDefaultObject = "367ABB81-9844-35F1-AD32-98F038001003" -) - -func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target, ccfg *exec.ConnectionConfig) (err error) { - - log := zerolog.Ctx(ctx).With(). - Str("func", "Connect").Logger() - - if ccfg.ConnectionMethod == exec.ConnectionMethodDCE { - if cfg, ok := ccfg.ConnectionMethodConfig.(dce.ConnectionMethodDCEConfig); !ok { - return fmt.Errorf("invalid configuration for DCE connection method") - } else { - // Fetch target hostname - for opening SCM handle - if mod.hostname, err = target.Hostname(ctx); err != nil { - log.Debug().Err(err).Msg("Failed to get target hostname") - mod.hostname = util.RandomHostname() - err = nil - } - connect := func(ctx context.Context) error { - // Create DCE connection - if mod.dce, err = cfg.GetDce(ctx, creds, target, ScmrDefaultEndpoint, ScmrDefaultObject); err != nil { - log.Error().Err(err).Msg("Failed to initialize DCE dialer") - return fmt.Errorf("create DCE dialer: %w", err) - } - log.Info().Msg("DCE dialer initialized") - - // Create SVCCTL client - mod.ctl, err = svcctl.NewSvcctlClient(ctx, mod.dce) - if err != nil { - log.Error().Err(err).Msg("Failed to initialize SCMR client") - return fmt.Errorf("init SCMR client: %w", err) - } - log.Info().Msg("DCE connection successful") - return nil - } - mod.reconnect = func(c context.Context) error { - mod.dce = nil - mod.ctl = nil - return connect(c) - } - return connect(ctx) - } - } else { - return errors.New("unsupported connection method") - } -} - -func (mod *Module) Cleanup(ctx context.Context, ccfg *exec.CleanupConfig) (err error) { - - log := zerolog.Ctx(ctx).With(). - Str("method", ccfg.CleanupMethod). - Str("func", "Cleanup").Logger() - - if len(mod.services) == 0 { - if cfg, ok := ccfg.CleanupMethodConfig.(CleanupMethodDeleteConfig); ok && len(cfg.ServiceNames) > 0 { - for _, svcName := range cfg.ServiceNames { - if svcName != "" { - mod.services = append(mod.services, remoteService{name: svcName}) - } - } - } - } - if mod.dce == nil || mod.ctl == nil { - // Try to reconnect - if err := mod.reconnect(ctx); err != nil { - log.Error().Err(err).Msg("Reconnect failed") - return err - } - log.Info().Msg("Reconnect successful") - } - if mod.scm == nil { - // Open a handle to SCM (again) - if resp, err := mod.ctl.OpenSCMW(ctx, &svcctl.OpenSCMWRequest{ - MachineName: util.CheckNullString(mod.hostname), - DatabaseName: "ServicesActive\x00", - DesiredAccess: ServiceAllAccess, // TODO: Replace - }); err != nil { - log.Error().Err(err).Msg("Failed to open an SCM handle") - return err - } else { - mod.scm = resp.SCM - log.Info().Msg("Opened an SCM handle") - } - } - - for _, rsvc := range mod.services { - log = log.With().Str("service", rsvc.name).Logger() - - if rsvc.handle == nil { - // Open a handle to the service in question - if or, err := mod.ctl.OpenServiceW(ctx, &svcctl.OpenServiceWRequest{ - ServiceManager: mod.scm, - ServiceName: rsvc.name, - DesiredAccess: windows.SERVICE_DELETE | windows.SERVICE_CHANGE_CONFIG, - }); err != nil { - log.Error().Err(err).Msg("Failed to open a service handle") - continue - } else { - rsvc.handle = or.Service - } - log.Info().Msg("Service handle opened") - } - if ccfg.CleanupMethod == CleanupMethodDelete { - // Delete the service - if _, err = mod.ctl.DeleteService(ctx, &svcctl.DeleteServiceRequest{Service: rsvc.handle}); err != nil { - log.Error().Err(err).Msg("Failed to delete service") - continue - } - log.Info().Msg("Service deleted successfully") - - } else if ccfg.CleanupMethod == CleanupMethodRevert { - // Revert the service configuration & state - log.Info().Msg("Attempting to revert service configuration") - if _, err = mod.ctl.ChangeServiceConfigW(ctx, &svcctl.ChangeServiceConfigWRequest{ - Service: rsvc.handle, - //Dependencies: []byte(rsvc.originalConfig.Dependencies), // TODO: ensure this works - ServiceType: rsvc.originalConfig.ServiceType, - StartType: rsvc.originalConfig.StartType, - ErrorControl: rsvc.originalConfig.ErrorControl, - BinaryPathName: rsvc.originalConfig.BinaryPathName, - LoadOrderGroup: rsvc.originalConfig.LoadOrderGroup, - ServiceStartName: rsvc.originalConfig.ServiceStartName, - DisplayName: rsvc.originalConfig.DisplayName, - TagID: rsvc.originalConfig.TagID, - }); err != nil { - log.Error().Err(err).Msg("Failed to revert service configuration") - continue - } - log.Info().Msg("Service configuration reverted") - } - if _, err = mod.ctl.CloseService(ctx, &svcctl.CloseServiceRequest{ServiceObject: rsvc.handle}); err != nil { - log.Warn().Err(err).Msg("Failed to close service handle") - return nil - } - log.Info().Msg("Closed service handle") - } - 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 == MethodCreate { - if cfg, ok := ecfg.ExecutionMethodConfig.(MethodCreateConfig); !ok { - return errors.New("invalid configuration") - - } else { - svc := remoteService{ - name: cfg.ServiceName, - } - // Open a handle to SCM - if resp, err := mod.ctl.OpenSCMW(ctx, &svcctl.OpenSCMWRequest{ - MachineName: util.CheckNullString(mod.hostname), - DatabaseName: "ServicesActive\x00", - DesiredAccess: ServiceAllAccess, // TODO: Replace - }); err != nil { - log.Debug().Err(err).Msg("Failed to open SCM handle") - return fmt.Errorf("open SCM handle: %w", err) - } else { - mod.scm = resp.SCM - log.Info().Msg("Opened SCM handle") - } - // Create service - serviceName := util.RandomStringIfBlank(svc.name) - resp, err := mod.ctl.CreateServiceW(ctx, &svcctl.CreateServiceWRequest{ - ServiceManager: mod.scm, - ServiceName: serviceName, - DisplayName: util.RandomStringIfBlank(cfg.DisplayName), - BinaryPathName: ecfg.GetRawCommand(), - ServiceType: windows.SERVICE_WIN32_OWN_PROCESS, - StartType: windows.SERVICE_DEMAND_START, - DesiredAccess: ServiceAllAccess, // TODO: Replace - }) - if err != nil || resp == nil || resp.Return != 0 { - log.Error().Err(err).Msg("Failed to create service") - return fmt.Errorf("create service: %w", err) - } - defer func() { - mod.services = append(mod.services, svc) - }() - svc.handle = resp.Service - - log = log.With(). - Str("service", serviceName).Logger() - log.Info().Msg("Service created") - - // Start the service - sr, err := mod.ctl.StartServiceW(ctx, &svcctl.StartServiceWRequest{Service: svc.handle}) - if err != nil { - - if errors.Is(err, context.DeadlineExceeded) { // Check if execution timed out (execute "cmd.exe /c notepad" for test case) - log.Warn().Err(err).Msg("Service execution deadline exceeded") - // Connection closes, so we nullify the client variables and handles - mod.dce = nil - mod.ctl = nil - mod.scm = nil - svc.handle = nil - - } else if sr != nil && sr.Return == windows.ERROR_SERVICE_REQUEST_TIMEOUT { // Check for request timeout - log.Info().Msg("Received request timeout. Execution was likely successful") - - } else { - log.Error().Err(err).Msg("Failed to start service") - return fmt.Errorf("start service: %w", err) - } - // Inform the caller that execution was likely successful despite error - err = nil - } else { - log.Info().Msg("Started service") - } - } - } else if ecfg.ExecutionMethod == MethodChange { - if cfg, ok := ecfg.ExecutionMethodConfig.(MethodChangeConfig); !ok { - return errors.New("invalid configuration") - - } else { - svc := remoteService{ - name: cfg.ServiceName, - } - - // Open a handle to SCM - if resp, err := mod.ctl.OpenSCMW(ctx, &svcctl.OpenSCMWRequest{ - MachineName: util.CheckNullString(mod.hostname), - DatabaseName: "ServicesActive\x00", - DesiredAccess: ServiceAllAccess, // TODO: Replace - }); err != nil { - log.Debug().Err(err).Msg("Failed to open SCM handle") - return fmt.Errorf("open SCM handle: %w", err) - } else { - mod.scm = resp.SCM - log.Info().Msg("Opened SCM handle") - } - - // Open a handle to the desired service - if resp, err := mod.ctl.OpenServiceW(ctx, &svcctl.OpenServiceWRequest{ - ServiceManager: mod.scm, - ServiceName: svc.name, - DesiredAccess: ServiceAllAccess, // TODO: Replace - }); err != nil { - log.Error().Err(err).Msg("Failed to open service handle") - return fmt.Errorf("open service: %w", err) - } else { - svc.handle = resp.Service - } - - // Note original service status - if resp, err := mod.ctl.QueryServiceStatus(ctx, &svcctl.QueryServiceStatusRequest{ - Service: svc.handle, - }); err != nil { - log.Warn().Err(err).Msg("Failed to get service status") - } else { - svc.originalState = resp.ServiceStatus - } - - // Note original service configuration - if resp, err := mod.ctl.QueryServiceConfigW(ctx, &svcctl.QueryServiceConfigWRequest{ - Service: svc.handle, - BufferLength: 8 * 1024, - }); err != nil { - log.Error().Err(err).Msg("Failed to fetch service configuration") - return fmt.Errorf("get service config: %w", err) - } else { - log.Info().Str("binaryPath", resp.ServiceConfig.BinaryPathName).Msg("Fetched original service configuration") - svc.originalConfig = resp.ServiceConfig - } - - // Stop service if its running - if svc.originalState == nil || svc.originalState.CurrentState != windows.SERVICE_STOPPED { - if resp, err := mod.ctl.ControlService(ctx, &svcctl.ControlServiceRequest{ - Service: svc.handle, - Control: windows.SERVICE_STOPPED, - }); err != nil { - if resp != nil && resp.Return == windows.ERROR_SERVICE_NOT_ACTIVE { - log.Info().Msg("Service is already stopped") - } else { - log.Error().Err(err).Msg("Failed to stop service") - } - } else { - log.Info().Msg("Service stopped") - } - } - defer func() { - mod.services = append(mod.services, svc) - }() - // Change service configuration - if _, err = mod.ctl.ChangeServiceConfigW(ctx, &svcctl.ChangeServiceConfigWRequest{ - Service: svc.handle, - BinaryPathName: ecfg.GetRawCommand(), - //Dependencies: []byte(svc.originalConfig.Dependencies), // TODO: ensure this works - ServiceType: svc.originalConfig.ServiceType, - StartType: windows.SERVICE_DEMAND_START, - ErrorControl: svc.originalConfig.ErrorControl, - LoadOrderGroup: svc.originalConfig.LoadOrderGroup, - ServiceStartName: svc.originalConfig.ServiceStartName, - DisplayName: svc.originalConfig.DisplayName, - TagID: svc.originalConfig.TagID, - }); err != nil { - log.Error().Err(err).Msg("Failed to change service configuration") - return fmt.Errorf("change service configuration: %w", err) - } - log.Info().Msg("Successfully altered service configuration") - - if !cfg.NoStart { - if resp, err := mod.ctl.StartServiceW(ctx, &svcctl.StartServiceWRequest{Service: svc.handle}); err != nil { - - if errors.Is(err, context.DeadlineExceeded) { // Check if execution timed out (execute "cmd.exe /c notepad" for test case) - log.Warn().Err(err).Msg("Service execution deadline exceeded") - // Connection closes, so we nullify the client variables and handles - mod.dce = nil - mod.ctl = nil - mod.scm = nil - svc.handle = nil - - } else if resp != nil && resp.Return == windows.ERROR_SERVICE_REQUEST_TIMEOUT { // Check for request timeout - log.Info().Err(err).Msg("Received request timeout. Execution was likely successful") - } else { - log.Error().Err(err).Msg("Failed to start service") - return fmt.Errorf("start service: %w", err) - } - } - } - } - } - return -} diff --git a/internal/exec/scmr/module.go b/internal/exec/scmr/module.go deleted file mode 100644 index 0372668..0000000 --- a/internal/exec/scmr/module.go +++ /dev/null @@ -1,44 +0,0 @@ -package scmrexec - -import ( - "context" - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2" -) - -const ( - MethodCreate = "create" - MethodChange = "change" - - CleanupMethodDelete = "delete" - CleanupMethodRevert = "revert" -) - -type Module struct { - hostname string // The target hostname - dce dcerpc.Conn - reconnect func(context.Context) error - - ctl svcctl.SvcctlClient - scm *svcctl.Handle - services []remoteService -} - -type MethodCreateConfig struct { - NoDelete bool - ServiceName string - DisplayName string - ServiceType uint32 - StartType uint32 -} - -type MethodChangeConfig struct { - NoStart bool - ServiceName string -} - -type CleanupMethodDeleteConfig struct { - ServiceNames []string -} - -type CleanupMethodRevertConfig struct{} diff --git a/internal/exec/scmr/service.go b/internal/exec/scmr/service.go deleted file mode 100644 index 49c7506..0000000 --- a/internal/exec/scmr/service.go +++ /dev/null @@ -1,25 +0,0 @@ -package scmrexec - -import ( - "context" - "github.com/FalconOpsLLC/goexec/internal/windows" - "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2" -) - -const ( - ServiceDeleteAccess uint32 = windows.SERVICE_DELETE - ServiceModifyAccess uint32 = windows.SERVICE_QUERY_CONFIG | windows.SERVICE_CHANGE_CONFIG | windows.SERVICE_STOP | windows.SERVICE_START | windows.SERVICE_DELETE - ServiceCreateAccess uint32 = windows.SC_MANAGER_CREATE_SERVICE | windows.SERVICE_START | windows.SERVICE_STOP | windows.SERVICE_DELETE - ServiceAllAccess uint32 = ServiceCreateAccess | ServiceModifyAccess -) - -type remoteService struct { - name string - handle *svcctl.Handle - originalConfig *svcctl.QueryServiceConfigW - originalState *svcctl.ServiceStatus -} - -func (mod *Module) parseServiceDependencies(ctx context.Context) (err error) { - return nil // TODO -} diff --git a/internal/exec/tsch/exec.go b/internal/exec/tsch/exec.go deleted file mode 100644 index 49a2dc2..0000000 --- a/internal/exec/tsch/exec.go +++ /dev/null @@ -1,189 +0,0 @@ -package tschexec - -import ( - "context" - "errors" - "fmt" - "github.com/FalconOpsLLC/goexec/internal/client/dce" - "github.com/FalconOpsLLC/goexec/internal/exec" - "github.com/FalconOpsLLC/goexec/internal/util" - "github.com/RedTeamPentesting/adauth" - "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" - "github.com/rs/zerolog" - "time" -) - -const ( - TschDefaultEndpoint = "ncacn_np:[atsvc]" - TschDefaultObject = "86D35949-83C9-4044-B424-DB363231FD0C" -) - -// Connect to the target & initialize DCE & TSCH clients -func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target, ccfg *exec.ConnectionConfig) (err error) { - - log := zerolog.Ctx(ctx).With(). - Str("func", "Connect").Logger() - - if ccfg.ConnectionMethod == exec.ConnectionMethodDCE { - if cfg, ok := ccfg.ConnectionMethodConfig.(dce.ConnectionMethodDCEConfig); !ok { - return fmt.Errorf("invalid configuration for DCE connection method") - } else { - // Create DCERPC dialer - mod.dce, err = cfg.GetDce(ctx, creds, target, TschDefaultEndpoint, TschDefaultObject) - if err != nil { - log.Error().Err(err).Msg("Failed to create DCERPC dialer") - return fmt.Errorf("create DCERPC dialer: %w", err) - } - // Create ITaskSchedulerService client - mod.tsch, err = itaskschedulerservice.NewTaskSchedulerServiceClient(ctx, mod.dce) - if err != nil { - log.Error().Err(err).Msg("Failed to initialize TSCH client") - return fmt.Errorf("init TSCH client: %w", err) - } - log.Info().Msg("DCE connection successful") - } - } else { - return errors.New("unsupported connection method") - } - return -} - -func (mod *Module) Cleanup(ctx context.Context, ccfg *exec.CleanupConfig) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("method", ccfg.CleanupMethod). - Str("func", "Cleanup").Logger() - - if ccfg.CleanupMethod == MethodDelete { - if cfg, ok := ccfg.CleanupMethodConfig.(MethodDeleteConfig); !ok { - return errors.New("invalid configuration") - - } else { - log = log.With().Str("task", cfg.TaskPath).Logger() - log.Info().Msg("Manually deleting task") - - if err = mod.deleteTask(ctx, cfg.TaskPath); err == nil { - log.Info().Msg("Task deleted successfully") - } - } - } else if ccfg.CleanupMethod == "" { - return nil - } else { - return fmt.Errorf("unsupported cleanup method") - } - 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 == MethodRegister { - if cfg, ok := ecfg.ExecutionMethodConfig.(MethodRegisterConfig); !ok { - return errors.New("invalid configuration") - - } else { - startTime := time.Now().UTC().Add(cfg.StartDelay) - stopTime := startTime.Add(cfg.StopDelay) - - tr := taskTimeTrigger{ - StartBoundary: startTime.Format(TaskXMLDurationFormat), - Enabled: true, - } - tk := newTask(nil, nil, triggers{TimeTriggers: []taskTimeTrigger{tr}}, ecfg.ExecutableName, ecfg.ExecutableArgs) - - if !cfg.NoDelete && !cfg.CallDelete { - if cfg.StopDelay == 0 { - cfg.StopDelay = time.Second - } - tk.Settings.DeleteExpiredTaskAfter = xmlDuration(cfg.DeleteDelay) - tk.Triggers.TimeTriggers[0].EndBoundary = stopTime.Format(TaskXMLDurationFormat) - } - taskPath := cfg.TaskPath - if taskPath == "" { - log.Debug().Msg("Task path not defined. Using random path") - taskPath = `\` + util.RandomString() - } - // The taskPath is changed here to the *actual path returned by SchRpcRegisterTask - taskPath, err = mod.registerTask(ctx, *tk, taskPath) - if err != nil { - return fmt.Errorf("call registerTask: %w", err) - } - - if !cfg.NoDelete { - if cfg.CallDelete { - defer mod.deleteTask(ctx, taskPath) - - log.Info().Dur("ms", cfg.StartDelay).Msg("Waiting for task to run") - select { - case <-ctx.Done(): - log.Warn().Msg("Cancelling execution") - return err - case <-time.After(cfg.StartDelay + time.Second): // + one second for good measure - for { - if stat, err := mod.tsch.GetLastRunInfo(ctx, &itaskschedulerservice.GetLastRunInfoRequest{Path: taskPath}); err != nil { - log.Warn().Err(err).Msg("Failed to get last run info. Assuming task was executed") - break - } else if stat.LastRuntime.AsTime().IsZero() { - log.Warn().Msg("Task was not yet run. Waiting 10 additional seconds") - time.Sleep(10 * time.Second) - } else { - break - } - } - break - } - } else { - log.Info().Time("when", stopTime).Msg("Task is scheduled to delete") - } - } - } - } else if ecfg.ExecutionMethod == MethodDemand { - if cfg, ok := ecfg.ExecutionMethodConfig.(MethodDemandConfig); !ok { - return errors.New("invalid configuration") - - } else { - taskPath := cfg.TaskPath - - if taskPath == "" { - log.Debug().Msg("Task path not defined. Using random path") - taskPath = `\` + util.RandomString() - } - - st := newSettings(true, true, false) - tk := newTask(st, nil, triggers{}, ecfg.ExecutableName, ecfg.ExecutableArgs) - - // The taskPath is changed here to the *actual path returned by SchRpcRegisterTask - taskPath, err = mod.registerTask(ctx, *tk, taskPath) - if err != nil { - return fmt.Errorf("call registerTask: %w", err) - } - - if !cfg.NoDelete { - defer mod.deleteTask(ctx, taskPath) - } - - var flags uint32 - - if cfg.SessionId != 0 { - flags |= 4 - } - _, err := mod.tsch.Run(ctx, &itaskschedulerservice.RunRequest{ - Path: taskPath, - Flags: flags, - SessionID: cfg.SessionId, - }) - if err != nil { - log.Error().Str("task", taskPath).Err(err).Msg("Failed to run task") - return fmt.Errorf("force run task: %w", err) - } - log.Info().Str("task", taskPath).Msg("Started task") - } - - } else { - return fmt.Errorf("method '%s' not implemented", ecfg.ExecutionMethod) - } - - return nil -} diff --git a/internal/exec/tsch/module.go b/internal/exec/tsch/module.go deleted file mode 100644 index 33c0723..0000000 --- a/internal/exec/tsch/module.go +++ /dev/null @@ -1,44 +0,0 @@ -package tschexec - -import ( - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" - "time" -) - -type Module struct { - // dce holds the working DCE connection interface - dce dcerpc.Conn - // tsch holds the ITaskSchedulerService client - tsch itaskschedulerservice.TaskSchedulerServiceClient -} - -type MethodRegisterConfig struct { - NoDelete bool - CallDelete bool - TaskPath string - StartDelay time.Duration - StopDelay time.Duration - DeleteDelay time.Duration -} - -type MethodDemandConfig struct { - NoDelete bool - CallDelete bool - SessionId uint32 - TaskName string - TaskPath string - StopDelay time.Duration - DeleteDelay time.Duration -} - -type MethodDeleteConfig struct { - TaskPath string -} - -const ( - MethodRegister string = "register" - MethodDemand string = "demand" - MethodDelete string = "delete" - MethodChange string = "update" -) diff --git a/internal/exec/tsch/task.go b/internal/exec/tsch/task.go deleted file mode 100644 index 928d029..0000000 --- a/internal/exec/tsch/task.go +++ /dev/null @@ -1,85 +0,0 @@ -package tschexec - -import ( - "fmt" - "regexp" - "time" -) - -var ( - TaskPathRegex = regexp.MustCompile(`^\\[^ :/\\][^:/]*$`) - TaskNameRegex = regexp.MustCompile(`^[^ :/\\][^:/\\]*$`) -) - -// newSettings just creates a settings instance with the necessary values + a few dynamic ones -func newSettings(terminate, onDemand, startWhenAvailable bool) *settings { - return &settings{ - MultipleInstancesPolicy: "IgnoreNew", - AllowHardTerminate: terminate, - IdleSettings: idleSettings{ - StopOnIdleEnd: true, - RestartOnIdle: false, - }, - AllowStartOnDemand: onDemand, - Enabled: true, - Hidden: true, - Priority: 7, // a pretty standard value for scheduled tasks - StartWhenAvailable: startWhenAvailable, - } -} - -// newTask creates a task with any static values filled -func newTask(se *settings, pr []principal, tr triggers, cmd, args string) *task { - if se == nil { - se = newSettings(true, true, false) - } - if pr == nil || len(pr) == 0 { - pr = []principal{ - { - ID: "1", - UserID: "S-1-5-18", - RunLevel: "HighestAvailable", - }, - } - } - return &task{ - TaskVersion: "1.2", - TaskNamespace: "http://schemas.microsoft.com/windows/2004/02/mit/task", - Triggers: tr, - Principals: principals{Principals: pr}, - Settings: *se, - Actions: actions{ - Context: pr[0].ID, - Exec: []actionExec{ - { - Command: cmd, - Arguments: args, - }, - }, - }, - } -} - -// xmlDuration is a *very* simple implementation of xs:duration - only accepts +seconds -func xmlDuration(dur time.Duration) string { - if s := int(dur.Seconds()); s >= 0 { - return fmt.Sprintf(`PT%dS`, s) - } - return `PT0S` -} - -// ValidateTaskName will validate the provided task name according to https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/fa8809c8-4f0f-4c6d-994a-6c10308757c1 -func ValidateTaskName(taskName string) error { - if !TaskNameRegex.MatchString(taskName) { - return fmt.Errorf("invalid task name: %s", taskName) - } - return nil -} - -// ValidateTaskPath will validate the provided task path according to https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/fa8809c8-4f0f-4c6d-994a-6c10308757c1 -func ValidateTaskPath(taskPath string) error { - if !TaskPathRegex.MatchString(taskPath) { - return fmt.Errorf("invalid task path: %s", taskPath) - } - return nil -} diff --git a/internal/exec/tsch/tsch.go b/internal/exec/tsch/tsch.go deleted file mode 100644 index d47e513..0000000 --- a/internal/exec/tsch/tsch.go +++ /dev/null @@ -1,141 +0,0 @@ -package tschexec - -import ( - "context" - "encoding/xml" - "fmt" - "github.com/oiweiwei/go-msrpc/msrpc/tsch/itaskschedulerservice/v1" - "github.com/rs/zerolog" -) - -const ( - TaskXMLDurationFormat = "2006-01-02T15:04:05.9999999Z" - TaskXMLHeader = `<?xml version="1.0" encoding="UTF-16"?>` -) - -// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/0d6383e4-de92-43e7-b0bb-a60cfa36379f - -type triggers struct { - XMLName xml.Name `xml:"Triggers"` - TimeTriggers []taskTimeTrigger `xml:"TimeTrigger,omitempty"` -} - -type taskTimeTrigger struct { - XMLName xml.Name `xml:"TimeTrigger"` - StartBoundary string `xml:"StartBoundary,omitempty"` // Derived from time.Time - EndBoundary string `xml:"EndBoundary,omitempty"` // Derived from time.Time; must be > StartBoundary - Enabled bool `xml:"Enabled"` -} - -type idleSettings struct { - XMLName xml.Name `xml:"IdleSettings"` - StopOnIdleEnd bool `xml:"StopOnIdleEnd"` - RestartOnIdle bool `xml:"RestartOnIdle"` -} - -type settings struct { - XMLName xml.Name `xml:"Settings"` - Enabled bool `xml:"Enabled"` - Hidden bool `xml:"Hidden"` - DisallowStartIfOnBatteries bool `xml:"DisallowStartIfOnBatteries"` - StopIfGoingOnBatteries bool `xml:"StopIfGoingOnBatteries"` - AllowHardTerminate bool `xml:"AllowHardTerminate"` - RunOnlyIfNetworkAvailable bool `xml:"RunOnlyIfNetworkAvailable"` - AllowStartOnDemand bool `xml:"AllowStartOnDemand"` - WakeToRun bool `xml:"WakeToRun"` - RunOnlyIfIdle bool `xml:"RunOnlyIfIdle"` - StartWhenAvailable bool `xml:"StartWhenAvailable"` - Priority int `xml:"Priority,omitempty"` // 1 to 10 inclusive - MultipleInstancesPolicy string `xml:"MultipleInstancesPolicy,omitempty"` - ExecutionTimeLimit string `xml:"ExecutionTimeLimit,omitempty"` - DeleteExpiredTaskAfter string `xml:"DeleteExpiredTaskAfter,omitempty"` // Derived from time.Duration - IdleSettings idleSettings `xml:"IdleSettings,omitempty"` -} - -type actionExec struct { - XMLName xml.Name `xml:"Exec"` - Command string `xml:"Command"` - Arguments string `xml:"Arguments,omitempty"` -} - -type actions struct { - XMLName xml.Name `xml:"Actions"` - Context string `xml:"Context,attr"` - Exec []actionExec `xml:"Exec,omitempty"` -} - -type principals struct { - XMLName xml.Name `xml:"Principals"` - Principals []principal `xml:"Principal,omitempty"` -} - -type principal struct { - XMLName xml.Name `xml:"Principal"` - ID string `xml:"id,attr"` - UserID string `xml:"UserId"` - RunLevel string `xml:"RunLevel"` -} - -type task struct { - XMLName xml.Name `xml:"Task"` - TaskVersion string `xml:"version,attr"` - TaskNamespace string `xml:"xmlns,attr"` - Triggers triggers `xml:"Triggers"` - Actions actions `xml:"Actions"` - Principals principals `xml:"Principals"` - Settings settings `xml:"Settings"` -} - -// registerTask serializes and submits the provided task structure -func (mod *Module) registerTask(ctx context.Context, taskDef task, taskPath string) (path string, err error) { - - var taskXml string - - log := zerolog.Ctx(ctx).With(). - Str("module", "tsch"). - Str("func", "createTask").Logger() - - // Generate task XML content. See https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/0d6383e4-de92-43e7-b0bb-a60cfa36379f - { - doc, err := xml.Marshal(taskDef) - if err != nil { - log.Error().Err(err).Msg("failed to marshal task XML") - return "", fmt.Errorf("marshal task: %w", err) - } - taskXml = TaskXMLHeader + string(doc) - log.Debug().Str("content", taskXml).Msg("Generated task XML") - } - // Submit task - { - response, err := mod.tsch.RegisterTask(ctx, &itaskschedulerservice.RegisterTaskRequest{ - Path: taskPath, - XML: taskXml, - Flags: 0, // TODO - LogonType: 0, // TASK_LOGON_NONE - CredsCount: 0, - Creds: nil, - }) - if err != nil { - log.Error().Err(err).Msg("Failed to register task") - return "", fmt.Errorf("register task: %w", err) - } - log.Info().Str("path", taskPath).Msg("Task created successfully") - path = response.ActualPath - } - return -} - -func (mod *Module) deleteTask(ctx context.Context, taskPath string) (err error) { - - log := zerolog.Ctx(ctx).With(). - Str("module", "tsch"). - Str("path", taskPath). - Str("func", "deleteTask").Logger() - - if _, err = mod.tsch.Delete(ctx, &itaskschedulerservice.DeleteRequest{Path: taskPath}); err != nil { - log.Error().Err(err).Msg("Failed to delete task") - return fmt.Errorf("delete task: %w", err) - } - log.Info().Msg("Task deleted successfully") - return -} diff --git a/internal/exec/wmi/exec.go b/internal/exec/wmi/exec.go deleted file mode 100644 index 7ae33ba..0000000 --- a/internal/exec/wmi/exec.go +++ /dev/null @@ -1,166 +0,0 @@ -package wmiexec - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "github.com/FalconOpsLLC/goexec/internal/client/dce" - "github.com/FalconOpsLLC/goexec/internal/exec" - "github.com/RedTeamPentesting/adauth" - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/msrpc/dcom" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/iactivation/v0" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/wmi" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/wmi/iwbemlevel1login/v0" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/wmi/iwbemservices/v0" - "github.com/rs/zerolog" -) - -const ( - ProtocolSequenceRPC uint16 = 7 - ProtocolSequenceNP uint16 = 15 - DefaultWmiEndpoint string = "ncacn_ip_tcp:[135]" -) - -var ( - ComVersion = &dcom.COMVersion{ - MajorVersion: 5, - MinorVersion: 7, - } - ORPCThis = &dcom.ORPCThis{Version: ComVersion} -) - -func (mod *Module) Cleanup(ctx context.Context, _ *exec.CleanupConfig) (err error) { - - log := zerolog.Ctx(ctx).With(). - Str("module", "tsch"). - Str("func", "Cleanup").Logger() - - if err = mod.dce.Close(ctx); err != nil { - log.Warn().Err(err).Msg("Failed to close DCERPC connection") - } - mod.sc = nil - mod.dce = nil - return -} - -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}, // TODO: Named pipe support - }) - 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 _, b := range retBinds { - sb, err := dcerpc.ParseStringBinding("ncacn_ip_tcp:" + b.NetworkAddr) - if err != nil { - log.Debug().Err(err).Msg("Failed to parse string binding") - } - sb.NetworkAddress = target.AddressWithoutPort() - 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: cfg.Resource, - }) - 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("create services client: %w", err) - } - } - return -} - -func (mod *Module) Exec(ctx context.Context, ecfg *exec.ExecutionConfig) (err error) { - log := zerolog.Ctx(ctx).With(). - Str("module", "tsch"). - Str("method", ecfg.ExecutionMethod).Logger() - - if ecfg.ExecutionMethod == MethodCall { - if cfg, ok := ecfg.ExecutionMethodConfig.(MethodCallConfig); !ok { - return errors.New("invalid execution configuration") - - } else { - out, err := mod.query(ctx, cfg.Class, cfg.Method, cfg.Arguments) - if err != nil { - return fmt.Errorf("query: %w", err) - } - if outJson, err := json.Marshal(out); err != nil { - log.Error().Err(err).Msg("failed to marshal call output") - } else { - fmt.Println(string(outJson)) - } - } - } else if ecfg.ExecutionMethod == MethodProcess { - if cfg, ok := ecfg.ExecutionMethodConfig.(MethodProcessConfig); !ok { - return errors.New("invalid execution configuration") - } else { - out, err := mod.query(ctx, "Win32_Process", "Create", map[string]any{ - "CommandLine": cfg.Command, - "WorkingDir": cfg.WorkingDirectory, - }) - if err != nil { - return fmt.Errorf("query: %w", err) - } - if pid, ok := out["ProcessId"]; ok && pid != nil { - log.Info(). - Any("PID", pid). - Any("return", out["ReturnValue"]). - Msg("Process created") - } else { - log.Error(). - Any("return", out["ReturnValue"]). - Msg("Process creation failed") - return errors.New("failed to create process") - } - } - } else { - return errors.New("unsupported execution method") - } - return nil -} diff --git a/internal/exec/wmi/module.go b/internal/exec/wmi/module.go deleted file mode 100644 index 0e83aa8..0000000 --- a/internal/exec/wmi/module.go +++ /dev/null @@ -1,33 +0,0 @@ -package wmiexec - -import ( - "github.com/RedTeamPentesting/adauth" - "github.com/oiweiwei/go-msrpc/dcerpc" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/wmi/iwbemservices/v0" - "github.com/rs/zerolog" -) - -type Module struct { - creds *adauth.Credential - target *adauth.Target - - log zerolog.Logger - dce dcerpc.Conn - sc iwbemservices.ServicesClient -} - -type MethodCallConfig struct { - Class string - Method string - Arguments map[string]any -} - -type MethodProcessConfig struct { - Command string - WorkingDirectory string -} - -const ( - MethodCall = "call" - MethodProcess = "process" -) diff --git a/internal/exec/wmi/wmi.go b/internal/exec/wmi/wmi.go deleted file mode 100644 index dd003a3..0000000 --- a/internal/exec/wmi/wmi.go +++ /dev/null @@ -1,26 +0,0 @@ -package wmiexec - -import ( - "context" - "errors" - "fmt" - "github.com/oiweiwei/go-msrpc/msrpc/dcom/wmio/query" -) - -func (mod *Module) query(ctx context.Context, class, method string, values map[string]any) (outValues map[string]any, err error) { - outValues = make(map[string]any) - if mod.sc == nil { - err = errors.New("module has not been initialized") - return - } - if out, err := query.NewBuilder(ctx, mod.sc, ComVersion). - Spawn(class). // The class to instantiate (i.e. Win32_Process) - Method(method). // The method to call (i.e. Create) - Values(values). // The values to pass to method - Exec(). - Object(); err == nil { - return out.Values(), err - } - err = fmt.Errorf("(*query.Builder).Spawn: %w", err) - return -} diff --git a/internal/util/util.go b/internal/util/util.go index f8f12ba..555297a 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -1,6 +1,7 @@ package util import ( + "github.com/google/uuid" "math/rand" // not crypto secure "regexp" "strings" @@ -23,6 +24,10 @@ func RandomHostname() (hostname string) { } } +func RandomWindowsTempFile() string { + return `\Windows\Temp\` + strings.ToUpper(uuid.New().String()) +} + func RandomString() string { return RandomStringFromCharset(randStringCharset, rand.Intn(10)+6) } |