Files
2025-02-18 15:17:51 +01:00

339 lines
13 KiB
Go

// Package dcerpc implements the DCE/RPC (C706) client with MS-RPCE extensions.
//
// # Manual
//
// The package is used by the generated stub/client code, so any examples
// make sense only within the scope of using generated clients.
//
// One of the key concepts in DCE/RPC is interface. Interface is just set of endopints
// serving certain logical purpose that are handled by server.
//
// From client perspective, interface is represented by presentation context.
// Presentation context is simply a Interface ID (Syntax UUID) and Interface Version
// (Syntax Version) and client-generated unique integer identifier.
// Syntax UUID and Syntax Version are also called as Abstract Sytnax.
//
// When we call NewXXXClient on the connection, generated stub code provides the
// abstract syntax as an option, and then performs the Bind request, which
// creates new Presentation Context (Abstract Syntax + unique identifier) and
// associates it with particular endpoint to which we are connecting.
//
// Endpoint can be either the named pipe name on the IPC$ share, or the TCP/IP
// port.
//
// So, Presentation Context and Endpoint make up a client connection.
//
// Let's begin with simpliest program that connects to EPM and lists all the
// endpoints registered on the server:
//
// import (
// "fmt"
//
// "github.com/oiweiwei/go-msrpc/dcerpc"
//
// "github.com/oiweiwei/go-msrpc/epm/epm/v3"
// )
//
// func main() {
//
// // create a new connection to the server my-server.com.
// // note, that no actual connection will be created, since we don't
// // know at this point, to which port and protocol we should connect.
// conn, err := dcerpc.Dial(context.TODO(), "my-server.com")
// if err != nil {
// // exit.
// }
//
// // close the connection (and all opened transports).
// defer conn.Close(context.TODO())
//
// // establish connection to the epm client, port 135.
// // internally, all stubs add WithAbstractSyntax option when establishing the connection.
// cli, err := epm.NewClient(ctx, conn, dcerpc.WithInsecure(), dcerpc.WithEndpoint(":135"))
// if err != nil {
// // exit.
// }
//
// // execute lookup request remote procedure call.
// resp, err := cli.Lookup(context.TODO(), &epm.LookupRequest{MaxEntries: 500})
// if err != nil {
// // exit.
// }
//
// // print results.
// for i, entries := range resp.Entries {
// fmt.Printf("[%d] %s \n", i, entries.Tower.Binding().StringBinding)
// }
// }
//
// # Security
//
// The example above doesn't use any security, which is often not true for the real world
// applications.
//
// DCE/RPC provides multiple ways to configure the security level:
//
// * Insecure: no security.
//
// * Packet (not used).
//
// * Connect: establish context but do not provide integrity/confidentiality services.
//
// * Sign: integrity.
//
// * Seal: confidentiality.
//
// In order to establish the security context, first that we need to do is provision the
// SSP packages and credentials. This can be done in two ways. First, provision via global settings.
//
// import (
// "github.com/oiweiwei/go-msrpc/ssp"
// "github.com/oiweiwei/go-msrpc/ssp/credential"
// "github.com/oiweiwei/go-msrpc/ssp/gssapi"
// )
//
// func init() {
// // This way we add the credentials from the password.
// gssapi.AddCredential(credential.NewFromPassword(os.Getenv("USERNAME"), os.Getenv("PASSWORD")))
// // This way we provision the security package.
// gssapi.AddMechanism(ssp.NTLM)
// }
//
// func main() {
//
// // ...
//
// // create new security context.
// ctx = gssapi.NewSecurityContext(ctx)
//
// // We use credentials to establish the NTLM security for Singing the requests/responses.
// cli, err := epm.NewClient(ctx, conn, dcerpc.WithSign(), dcerpc.WithEndpoint(":135"))
//
// // ...
// }
//
// Second, we can provision packages directly when creating the connection.
//
// cli, err := epm.NewClient(ctx, conn,
// dcerpc.WithCredential(creds),
// // only top-level mechanism is used, when this mechanism is SPNEGO, it will
// // combine all the mechanisms and will perform the negotiation with server.
// dcerpc.WithMechanism(ssp.SPNEGO),
// dcerpc.WithMechanism(ssp.KRB5),
// dcerpc.WithMechanism(ssp.NTLM),
// dcerpc.WithSeal(),
// dcerpc.WithEndpoint(":135"))
//
// Or by combination of both approaches (specify mechanisms globally, and credentials locally and so on).
//
// ctx := gssapi.NewSecurityContext(ctx, ssp.NTLM)
//
// cli, err := epm.NewClient(ctx, conn, dcerpc.WithCredential(creds), ...)
//
// Security context can be altered by client:
//
// cli.AlterContext(ctx, dcerpc.WithMechanism(ssp.KRB5), dcerpc.WithSeal())
//
// ## Security Context Use-Cases
//
// As you may have noticed, the security can be configured on many levels, so
// to summarize following is a set of use-cases and proposed approach:
//
// ### Global Configuration
//
// Use init function to establish the mechanism / credentials once and for all connections:
//
// func init() {
// gssapi.AddCredential(credential.NewFromPassword(os.Getenv("USERNAME"), os.Getenv("PASSWORD")))
// gssapi.AddMechanism(ssp.NTLM)
// }
//
// Note, that you cannot add mechanism to the global configuration more than once, as it is not allowed
// by GSSAPI package. If you want to have a freedom of choice for the configuration, use dcerpc.WithMechanism
// per-client options, or NewSecurityContext's gssapi.Option for Reusable Configuration scenario.
//
// ### Reusable Configuration
//
// When you're creating multiple clients over the same endpoint (same named pipe, or TCP port)
// it may be desired to not establish a new security context every time you instantinate the client.
//
// To achieve this, you can define credentials / SSP mechanisms per security context:
//
// ctx := gssapi.NewSecurityContext(ctx, ssp.NTLM, yourCreds)
//
// cli1, err := epm.NewEpmClient(
// ctx, // this context contains NTLM SSP and yourCreds
// dcerpc.WithSign(), // MUST specify for the initial context establishment.
// dcerpc.WithEndpoint(":135"))
//
// cli2, err := iobjectexporter.NewObjectExporterClient(
// ctx, // this contains already established context.
// // dcerpc.WithSign(): DON'T specify it, as you are going to use same TCP connection / and context.
// dcerpc.WithEndpoint(":135"))
//
// As an effect, both cli1 and cli2 will use same security context identifier.
//
// ## Acquire Security Context Attributes
//
// After establishing the security context, you can acquire security attributes from the
// client using connection context:
//
// epmCli, err := epm.NewClient(ctx, conn, dcerpc.WithSeal(), dcerpc.WithEndpoint(":135"))
// if err != nil {
// // handle error
// }
//
// // retrieve the negotiated session key.
// key, ok := gssapi.GetAttribute(epmCli.Conn().Context(), gssapi.AttributeSessionKey)
// if ok {
// fmt.Printf("Session Key: %x\n", key)
// }
//
// // retrieve the smb session key.
// key, ok := gssapi.GetAttribute(epmCli.Conn().Context(), gssapi.AttributeSMBSessionKey)
// if ok {
// fmt.Printf("SMB Session Key: %x\n", key)
// }
//
// // retrieve the smb application key. (SMB3+)
// key, ok := gssapi.GetAttribute(epmCli.Conn().Context(), gssapi.AttributeSMBApplicationKey)
// if ok {
// fmt.Printf("SMB Application Key: %x\n", key)
// }
//
// // retrieve the smb key effective for LSA encryption operations.
// // this key depends on the dialect and RPC transport security level.
// // ie for any SMB dialect if RPC transport security was negotiated, the key
// // will be a constsant value of "SystemLibraryDTC".
// // For SMB3+ it will be the SMBApplicationKey, and for other cases it will be
// // negotiated session key.
// key, ok := gssapi.GetAttribute(epmCli.Conn().Context(), gssapi.AttributeSMBEffectiveSessionKey)
// if ok {
// fmt.Printf("SMB Effective Session Key: %x\n", key)
// }
//
// # Per-Client Configuration
//
// When you wish for each client to have different security context / credentials / mechanism
// you should use dcerpc.WithMechanism and dcerpc.WithCredential:
//
// cli, err := epm.NewClient(ctx, conn,
// dcerpc.WithMechanism(ssp.NTLM),
// dcerpc.WithCredential(creds),
// dcerpc.WithSeal(),
// dcerpc.WithEndpoint(":135"))
//
// ## Kerberos
//
// Kerberos uses several environment variables, KRB5_CONFIG to specify the path
// to the kerberos 5 config file, and KRB5_CCACHE for credentials cache path.
//
// The following kerberos configuration should be sufficient to connect to
// the MSAD KDC:
//
// [realms]
// CONTOSO.NET = {
// kdc = win2019-0-1.contoso.net
// admin_server = win2019-0-1.contoso.net
// }
//
// [libdefaults]
// default_realm = CONTOSO.NET
// default_tkt_enctypes = rc4-hmac # or aes128-cts-hmac-sha1-96 or aes256-cts-hmac-sha1-96
// default_tgs_enctypes = rc4-hmac # or aes128-cts-hmac-sha1-96 or aes256-cts-hmac-sha1-96
//
// To acquire the credentials cache you must perform kinit:
//
// $ KRB5_CONFIG=path/to/config.conf kinit <Username> -c /path/to/output.ccache
// > Password for <Username>@<Domain>:
//
// The cache can be used via environment variable:
//
// $ export KRB5_CCACHE=/path/to/output.ccache ./your-dcerpc-prog
//
// Note that kerberos requires valid service principal name, like "host/my-server.com".
//
// # Verification
//
// MS-RPCE provides a feature to include unprotected header parts into the request payload
// and hence add integrity check for them when header signing is not supported.
//
// Use WithVerifyBitMask, WithVerifyHeader2, WithVerifyPresentation options in order
// to include verification trailer to the request.
//
// cli, err := epm.NewClient(ctx, conn,
// // ... other opts ...
// dcerpc.WithVerifyBitMask(true), // required verification
// dcerpc.WithVerifyPresentation(false), // optional verification
// dcerpc.WithEndpoint(":135"))
//
// # String Binding
//
// String binding is a special syntax used by DCE/RPC (MS-RPCE) to describe the ways to
// locate the interface.
//
// The string binding syntax is following:
//
// [ 'ObjectUUID' '@' ] ProtocolSequence ':' NetworkAddress '[' Endpoint ']'
//
// For example:
//
// "ncacn_ip_tcp:[135]" // TCP/IP on Port 135.
// "ncacn_np:WIN2019[winreg]" // Named Pipe "winreg" over SMB IPC$ share, WIN2019 is a NetBIOS name.
//
// # Endpont Mapping
//
// Endpoint mapper is not only the service of its own, but also a core component
// in DCE/RPC architecture. The endpoint mapper interface (EPM) is used to determine
// the endpoints (port or named pipes) that are used by interfaces.
//
// Some endpoints are so-called well-known endpoint, that is, they are always the same
// for any system, for example, EPM itself has a well-known endpoint ncacn_ip_tcp:[135], and
// ncacn_np:[epmapper].
//
// You can use the well_known endpoint mapper like following:
//
// import "github.com/oiweiwei/go-msrpc/well_known"
//
// // most of the well-known endpoints are SMB endpoints.
// // (note that SMB will perform significantly slower).
// conn, err := dcerpc.Dial(ctx, "my-server.com", well_known.EndpointMapper())
//
// But most of the endpoints are dynamic, like DNSServer, DHCPServer, Eventlog TCP/IP endpoints.
// To locate them Endpoint mapper must be used:
//
// import "github.com/oiweiwei/go-msrpc/epm/epm/v3"
//
// // specify EPMv3 endpoint mapper, it will use well_known.EndpointMapper()
// // internally to determine it's own address, by you can also force
// // it to use SMB by replacing "my-server.com" with "ncacn_np:my-server.com[epmapper]"
// // or passing dcerpc.WithEndpoint("ncacn_np:[epmapper]") as an option.
// conn, err := dcerpc.Dial(ctx, "my-server.com", epm.EndpointMapper(ctx, "my-server.com", dcerpc.WithSeal()))
//
// # Error Handling
//
// The "github.com/oiweiwei/go-msrpc/msrpc/erref" package contains the error handlers for
// the list of known errors. If you will import any of the packages inside it, any error
// in `Return` will be automatically matched and converted to human-readable strings.
//
// // importing win32 errors will convert the error codes into human-readable errors.
// // go run examples/rrp.go # with erorr
// // key enumerate: query_info: dcerpc: invoke: /winreg/v1/BaseRegQueryInfoKey: response: decode packet: win32: RPC_X_BAD_STUB_DATA (0x000006f7): The stub received bad data.
// // go run examples/rrp.go # without error
// // key enumerate: query_info: dcerpc: invoke: /winreg/v1/BaseRegQueryInfoKey: response: decode packet: error: code: 0x000006f7
// import _ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32"
//
// # SMB Performance
//
// Note that using SMB may slow-down the performance, since every request write and response read
// are dedicated SMB Write and SMB Read calls. To improve the performance, SMB transaction support
// must be implemented.
//
// Also note, that the only working security scenario for SMB is WithInsecure and WithSeal, where
// second will also slow-down the performace.
//
// # Examples
//
// See github.com/oiweiwei/go-msrpc/examples for more examples.
package dcerpc