diff options
author | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-03-07 08:52:48 -0600 |
---|---|---|
committer | Bryan McNulty <bryanmcnulty@protonmail.com> | 2025-03-07 08:52:48 -0600 |
commit | e87dd341dde93c289b6774f636e6767476b84a79 (patch) | |
tree | 3181b18f79b587bd04d98ed886f3505f37faeb2d /internal/exec/wmi/exec.go | |
parent | a5c860b8ab24c198b7390fbde90044754e35c1c5 (diff) | |
download | goexec-e87dd341dde93c289b6774f636e6767476b84a79.tar.gz goexec-e87dd341dde93c289b6774f636e6767476b84a79.zip |
Added wmiexec module + updated TODO
Diffstat (limited to 'internal/exec/wmi/exec.go')
-rw-r--r-- | internal/exec/wmi/exec.go | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/internal/exec/wmi/exec.go b/internal/exec/wmi/exec.go new file mode 100644 index 0000000..962fc60 --- /dev/null +++ b/internal/exec/wmi/exec.go @@ -0,0 +1,198 @@ +package wmiexec + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "github.com/FalconOpsLLC/goexec/internal/exec" + "github.com/RedTeamPentesting/adauth" + "github.com/RedTeamPentesting/adauth/dcerpcauth" + "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/oiweiwei/go-msrpc/ssp/gssapi" + "github.com/rs/zerolog" +) + +const ( + ProtocolSequenceRPC uint16 = 7 + ProtocolSequenceNP uint16 = 15 +) + +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("func", "Cleanup").Logger() + + if err = mod.dce.Close(ctx); err != nil { + log.Warn().Err(err).Msg("Failed to close DCERPC connection") + } + return +} + +func (mod *Module) Connect(ctx context.Context, creds *adauth.Credential, target *adauth.Target, _ *exec.ConnectionConfig) (err error) { + + var baseOpts, authOpts []dcerpc.Option + var ipid *dcom.IPID // This will store the IPID of the remote instance + var bind2Opts []dcerpc.Option + + ctx = gssapi.NewSecurityContext(ctx) + log := zerolog.Ctx(ctx).With(). + Str("func", "Connect").Logger() + + // Assemble DCERPC options + { + baseOpts = []dcerpc.Option{ + dcerpc.WithLogger(log), + dcerpc.WithSign(), // Enforce signing + dcerpc.WithSeal(), // Enforce packet stub encryption + } + // Add target name option if possible + if tn, err := target.Hostname(ctx); err == nil { + baseOpts = append(baseOpts, dcerpc.WithTargetName(tn)) + } else { + log.Debug().Err(err).Msg("Failed to get target hostname") + } + // Parse target and credentials + if authOpts, err = dcerpcauth.AuthenticationOptions(ctx, creds, target, &dcerpcauth.Options{}); err != nil { + return fmt.Errorf("parse authentication options: %w", err) + } + } + + // Establish first connection (REMACT) + { + // Connection options + rp := "ncacn_ip_tcp" // Underlying protocol + ro := 135 // RPC port + rb := fmt.Sprintf("%s:[%d]", rp, ro) // RPC binding + + // Create DCERPC dialer + mod.dce, err = dcerpc.Dial(ctx, target.AddressWithoutPort(), append(baseOpts, append(authOpts, dcerpc.WithEndpoint(rb))...)...) + if err != nil { + return fmt.Errorf("DCERPC dial: %w", err) + } + // Create remote activation client + ia, err := iactivation.NewActivationClient(ctx, mod.dce, append(baseOpts, dcerpc.WithEndpoint(rb))...) + if err != nil { + return fmt.Errorf("create activation client: %w", err) + } + // Send remote activation request + act, err := ia.RemoteActivation(ctx, &iactivation.RemoteActivationRequest{ + ORPCThis: ORPCThis, + ClassID: wmi.Level1LoginClassID.GUID(), + IIDs: []*dcom.IID{iwbemlevel1login.Level1LoginIID}, + RequestedProtocolSequences: []uint16{ProtocolSequenceRPC, ProtocolSequenceNP}, // TODO: dynamic + }) + 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 + + // This ensures that the original target address/hostname is used in the string binding + origBind := retBinds[0].String() + if bind, err := dcerpc.ParseStringBinding(origBind); err != nil { + log.Warn().Str("binding", origBind).Err(err).Msg("Failed to parse binding string returned by server") + bind2Opts = act.OXIDBindings.EndpointsByProtocol(rp) // Try using the server supplied string binding + } else { + bind.NetworkAddress = target.AddressWithoutPort() // Replace address/hostname in new string binding + bs := bind.String() + log.Info().Str("binding", bs).Msg("found binding") + bind2Opts = append(bind2Opts, dcerpc.WithEndpoint(bs)) // Use the new string binding + } + } + + // Establish second connection (WMI) + { + bind2Opts = append(bind2Opts, authOpts...) + mod.dce, err = dcerpc.Dial(ctx, target.AddressWithoutPort(), append(baseOpts, bind2Opts...)...) + if err != nil { + return fmt.Errorf("dial WMI: %w", err) + } + // Create login client + loginClient, err := iwbemlevel1login.NewLevel1LoginClient(ctx, mod.dce, append(baseOpts, dcom.WithIPID(ipid))...) + if err != nil { + return fmt.Errorf("initialize wbem login client: %w", err) + } + login, err := loginClient.NTLMLogin(ctx, &iwbemlevel1login.NTLMLoginRequest{ // TODO: Other login opts/methods? + This: ORPCThis, + NetworkResource: "//./root/cimv2", // TODO: make this dynamic + }) + 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("iwbemservices.NewServicesClient: %w", err) + } + } + return nil +} + +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 == MethodCustom { + if cfg, ok := ecfg.ExecutionMethodConfig.(MethodCustomConfig); !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 +} |