aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorBryan McNulty <bryanmcnulty@protonmail.com>2025-04-16 12:11:58 -0500
committerBryan McNulty <bryanmcnulty@protonmail.com>2025-04-16 12:11:58 -0500
commit55eb4275fb760ac7a3ce1444f5ae0ded9e2ff91c (patch)
treeedf4ec3b814fb10ccdbf759a62819a865d3e8141 /internal
parenta827b67d47cba7b02ea9599fe6bb88ffb3a6967d (diff)
downloadgoexec-55eb4275fb760ac7a3ce1444f5ae0ded9e2ff91c.tar.gz
goexec-55eb4275fb760ac7a3ce1444f5ae0ded9e2ff91c.zip
rewrote everything lol
Diffstat (limited to 'internal')
-rw-r--r--internal/client/dce/dce.go74
-rw-r--r--internal/exec/dcom/dcom.go65
-rw-r--r--internal/exec/dcom/exec.go181
-rw-r--r--internal/exec/dcom/module.go21
-rw-r--r--internal/exec/exec.go54
-rw-r--r--internal/exec/scmr/exec.go345
-rw-r--r--internal/exec/scmr/module.go44
-rw-r--r--internal/exec/scmr/service.go25
-rw-r--r--internal/exec/tsch/exec.go189
-rw-r--r--internal/exec/tsch/module.go44
-rw-r--r--internal/exec/tsch/task.go85
-rw-r--r--internal/exec/tsch/tsch.go141
-rw-r--r--internal/exec/wmi/exec.go166
-rw-r--r--internal/exec/wmi/module.go33
-rw-r--r--internal/exec/wmi/wmi.go26
-rw-r--r--internal/util/util.go5
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)
}