aboutsummaryrefslogtreecommitdiff
path: root/internal/exec/wmi/exec.go
diff options
context:
space:
mode:
authorBryan McNulty <bryanmcnulty@protonmail.com>2025-03-07 08:52:48 -0600
committerBryan McNulty <bryanmcnulty@protonmail.com>2025-03-07 08:52:48 -0600
commite87dd341dde93c289b6774f636e6767476b84a79 (patch)
tree3181b18f79b587bd04d98ed886f3505f37faeb2d /internal/exec/wmi/exec.go
parenta5c860b8ab24c198b7390fbde90044754e35c1c5 (diff)
downloadgoexec-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.go198
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
+}