diff options
author | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-03-10 16:04:08 -0500 |
---|---|---|
committer | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-03-10 16:04:08 -0500 |
commit | 11741c4cde3d552211fbb04eddd719b3dc3bd472 (patch) | |
tree | 52f28ca2feacde039b7215fa3fd27b5a7ec02ed5 /internal | |
parent | ab141f2076b141bf885f56cb5730252cc2880041 (diff) | |
download | goexec-11741c4cde3d552211fbb04eddd719b3dc3bd472.tar.gz goexec-11741c4cde3d552211fbb04eddd719b3dc3bd472.zip |
Added basic dcom execution module
Diffstat (limited to 'internal')
-rw-r--r-- | internal/client/dce/dce.go | 2 | ||||
-rw-r--r-- | internal/exec/dcom/dcom.go | 65 | ||||
-rw-r--r-- | internal/exec/dcom/exec.go | 187 | ||||
-rw-r--r-- | internal/exec/dcom/module.go | 21 | ||||
-rw-r--r-- | internal/exec/scmr/exec.go | 2 | ||||
-rw-r--r-- | internal/exec/scmr/service.go | 26 |
6 files changed, 287 insertions, 16 deletions
diff --git a/internal/client/dce/dce.go b/internal/client/dce/dce.go index f58123b..85512ac 100644 --- a/internal/client/dce/dce.go +++ b/internal/client/dce/dce.go @@ -26,7 +26,7 @@ type ConnectionMethodDCEConfig struct { func (cfg *ConnectionMethodDCEConfig) GetDce(ctx context.Context, cred *adauth.Credential, target *adauth.Target, endpoint, object string, opts ...dcerpc.Option) (cc dcerpc.Conn, err error) { dceOpts := append(opts, cfg.DceOptions...) - epmOpts := append(opts, cfg.EpmOptions...) + epmOpts := cfg.EpmOptions log := zerolog.Ctx(ctx).With(). Str("client", "DCERPC").Logger() diff --git a/internal/exec/dcom/dcom.go b/internal/exec/dcom/dcom.go new file mode 100644 index 0000000..b96bbc8 --- /dev/null +++ b/internal/exec/dcom/dcom.go @@ -0,0 +1,65 @@ +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 new file mode 100644 index 0000000..297c26f --- /dev/null +++ b/internal/exec/dcom/exec.go @@ -0,0 +1,187 @@ +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") + //RandUuid = uuid.MustParse("dc95cac4-0d74-49eb-8947-570ad52ef215") + 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 fmt.Errorf("invalid configuration for DCE connection method") + } else { + opts := []dcerpc.Option{dcerpc.WithSign(), dcerpc.WithSecurityLevel(0)} + + // Fetch target hostname + if mod.hostname, err = target.Hostname(ctx); err != nil { + log.Debug().Err(err).Msg("Failed to get target hostname") + opts = append(opts, dcerpc.WithTargetName(mod.hostname)) + } + // 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")...) + 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 new file mode 100644 index 0000000..bbd50b5 --- /dev/null +++ b/internal/exec/dcom/module.go @@ -0,0 +1,21 @@ +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/scmr/exec.go b/internal/exec/scmr/exec.go index 7134df0..656b212 100644 --- a/internal/exec/scmr/exec.go +++ b/internal/exec/scmr/exec.go @@ -27,7 +27,6 @@ func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target 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") @@ -157,7 +156,6 @@ func (mod *Module) Cleanup(ctx context.Context, ccfg *exec.CleanupConfig) (err e func (mod *Module) Exec(ctx context.Context, ecfg *exec.ExecutionConfig) (err error) { - //vctx := context.WithoutCancel(ctx) log := zerolog.Ctx(ctx).With(). Str("method", ecfg.ExecutionMethod). Str("func", "Exec").Logger() diff --git a/internal/exec/scmr/service.go b/internal/exec/scmr/service.go index 9a580cb..49c7506 100644 --- a/internal/exec/scmr/service.go +++ b/internal/exec/scmr/service.go @@ -1,25 +1,25 @@ package scmrexec import ( - "context" - "github.com/FalconOpsLLC/goexec/internal/windows" - "github.com/oiweiwei/go-msrpc/msrpc/scmr/svcctl/v2" + "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 + 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 + name string + handle *svcctl.Handle + originalConfig *svcctl.QueryServiceConfigW + originalState *svcctl.ServiceStatus } -func (mod *Module) parseServiceDependencies(ctx context.Context, ) (err error) { - return nil +func (mod *Module) parseServiceDependencies(ctx context.Context) (err error) { + return nil // TODO } |