client
import "github.com/TheManticoreProject/Manticore/network/dcerpc/v5/client"
Package client implements the high-level connection-oriented DCE/RPC client: it binds to an interface and issues calls over a transport.
The flow follows [C706] chapter 12 and [MS-RPCE]:
- Bind sends a bind PDU proposing the transport’s max_xmit_frag / max_recv_frag, a single presentation context (the target abstract syntax with the NDR 2.0 transfer syntax), and records the server’s negotiated fragment sizes from the bind_ack.
- Call serializes a request, fragments it so that no fragment exceeds the negotiated send size (PFC_FIRST_FRAG on the first, PFC_LAST_FRAG on the last, both on a single fragment), writes every fragment, then reassembles the response fragments until PFC_LAST_FRAG. A fault PDU is returned as an error.
The call_id is constant across the fragments of one call and is incremented for each new call, as required by [C706] section 12.6.2.
References:
- [C706] chapter 12 (connection-oriented protocol), “Fragmentation and Reassembly”: https://pubs.opengroup.org/onlinepubs/9629399/chap12.htm
- [MS-RPCE] 3.3.1.5.3 Bind Time Feature Negotiation and 2.2.2 PDU definitions: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/87964b3c-1785-4aae-a993-734999441ed3
- [MS-RPCE] 2.1.1.2 SMB (NCACN_NP): https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/7063c7bd-b48b-42e7-9154-3c2ec4113c0d
Index
- type Client
- func NewClient(t transport.Transport) *Client
- func (c *Client) Bind(abstractSyntax syntax.SyntaxID) error
- func (c *Client) Call(opnum uint16, stub []byte) ([]byte, error)
- func (c *Client) Close() error
- func (c *Client) Invoke(in ndr.Call, out any) error
- func (c *Client) NegotiatedSyntax() ndr.Syntax
- func (c *Client) PreferNDR64(enable bool)
- func (c *Client) SessionKey() []byte
- func (c *Client) SetAuth(authType, authLevel uint8, creds *credentials.Credentials) error
- func (c *Client) SetAuthKerberos(authLevel uint8, kc *kerberos.KerberosClient, spn string) error
- func (c *Client) SetAuthProvider(authType, authLevel uint8, sec SecurityContext, bindToken []byte) error
- type SecurityContext
type Client
Client is a connection-oriented DCE/RPC client bound to a single presentation context on a transport.
type Client struct {
// contains filtered or unexported fields
}
func NewClient
func NewClient(t transport.Transport) *Client
NewClient returns a DCE/RPC client over the supplied transport. The transport must be ready to Connect (for ncacn_np, its SMB session and IPC$ tree connect are already established).
func (*Client) Bind
func (c *Client) Bind(abstractSyntax syntax.SyntaxID) error
Bind connects the transport and binds to the interface identified by abstractSyntax. It proposes the NDR 2.0 transfer syntax in presentation context 0, and — when PreferNDR64 is enabled — NDR64 in a second context (the established negotiation pattern: one transfer syntax per context, [MS-RPCE] 2.2.2.4). The transfer syntax the server accepts is recorded for subsequent calls, preferring NDR64 when both are accepted. It returns an error if the server rejects the bind (bind_nak) or accepts no context.
func (*Client) Call
func (c *Client) Call(opnum uint16, stub []byte) ([]byte, error)
Call invokes the method identified by opnum, sending stub as its marshalled arguments and returning the marshalled results. A fault PDU is returned as a *pdu.Fault error.
func (*Client) Close
func (c *Client) Close() error
Close closes the underlying transport.
func (*Client) Invoke
func (c *Client) Invoke(in ndr.Call, out any) error
Invoke is the declarative counterpart to Call: it marshals the NDR request structure in (whose Opnum selects the method and whose exported fields are the [in] parameters), issues the call, and unmarshals the response into out (a pointer to the [out] parameter structure). out may be nil when the response carries no data. A fault PDU is returned as a *pdu.Fault error, as with Call.
func (*Client) NegotiatedSyntax
func (c *Client) NegotiatedSyntax() ndr.Syntax
NegotiatedSyntax reports the transfer syntax the server accepted at Bind. It is meaningful only after a successful Bind.
func (*Client) PreferNDR64
func (c *Client) PreferNDR64(enable bool)
PreferNDR64 controls whether Bind proposes the NDR64 transfer syntax in addition to NDR 2.0 and uses it when the server accepts it. It must be called before Bind. The default is false (NDR 2.0 only).
func (*Client) SessionKey
func (c *Client) SessionKey() []byte
SessionKey returns the NTLM exported session key established during an authenticated Bind, or nil if the bind was anonymous or has not completed. It is the key DRSUAPI uses to decrypt replicated secrets (the OID/PEK unwrap in IDL_DRSGetNCChanges); most callers do not need it. The returned slice is the client’s own copy — do not mutate it.
func (*Client) SetAuth
func (c *Client) SetAuth(authType, authLevel uint8, creds *credentials.Credentials) error
SetAuth enables RPC-level authentication for subsequent binds. authType selects the security provider (only NTLM, pdu.AuthTypeNTLMSSP, is supported) and authLevel the protection applied to each PDU: pdu.AuthLevelConnect authenticates the bind only; pdu.AuthLevelCall and pdu.AuthLevelPkt attach a per-PDU authenticity verifier; pdu.AuthLevelPktIntegrity additionally signs the request data; and pdu.AuthLevelPktPrivacy signs and seals it. creds may carry a cleartext password or, for pass-the-hash, an NT hash with no password. It must be called before Bind.
AuthLevelCall is promoted to AuthLevelPkt: it has no distinct meaning for the connection-oriented protocol, where the runtime uses packet-level protection instead ([MS-RPCE] 2.2.1.1.8).
func (*Client) SetAuthKerberos
func (c *Client) SetAuthKerberos(authLevel uint8, kc *kerberos.KerberosClient, spn string) error
SetAuthKerberos configures an authenticated bind using native Kerberos. It acquires a TGT (if the client does not already hold one) and a service ticket for spn, builds the KRB_AP_REQ carried in the bind’s auth verifier, and installs a per-message security context. Mutual authentication is always requested so the acceptor’s KRB_AP_REP can be verified.
The auth level selects both the RPC security provider and the per-PDU protection:
- CONNECT authenticates the bind only, as raw Kerberos (auth_type 0x10) with a GSS-wrapped AP-REQ and a single mutual-auth round.
- PKT / PKT_INTEGRITY protect each PDU with a GSS MIC. Windows requires these to be negotiated through SPNEGO (auth_type 0x09, RPC_C_AUTHN_GSS_NEGOTIATE) with GSS_C_DCE_STYLE: a three-leg handshake (bind AP-REQ, bind_ack AP-REP, alter_context with the initiator’s own AP-REP) establishes the context. The MIC token type follows the negotiated session key: an RFC 4121 CFX MIC (tok_id 04 04) for an AES ticket, or the RFC 4757 MIC (tok_id 01 01) for an RC4 ticket.
- PKT_PRIVACY (sealing) uses the same SPNEGO/DCE-style handshake and, matching the MIC path, seals the stub with the enctype-appropriate GSS Wrap token: an RFC 4121 CFX Wrap (tok_id 05 04) for AES or the RFC 4757 Wrap (tok_id 02 01) for RC4. The stub is encrypted in place while the PDU header and sec_trailer stay sign-only, and the Wrap token travels in the auth_value.
Call before Bind.
func (*Client) SetAuthProvider
func (c *Client) SetAuthProvider(authType, authLevel uint8, sec SecurityContext, bindToken []byte) error
SetAuthProvider configures an authenticated bind with a caller-supplied single-leg security provider, for SSPs whose session key is established out of band and whose bind completes in one round trip (no challenge/auth3) — Netlogon (auth_type 0x44) in particular. sec protects and unprotects each PDU; bindToken is the auth_value carried in the bind PDU (for Netlogon, a marshalled NL_AUTH_MESSAGE), or nil. This is the seam that keeps the client free of any specific SSP’s dependencies: the provider and its bind token are built by the caller. Unlike SetAuth (NTLM), the security context is active immediately, so protected calls work as soon as Bind returns. Call before Bind.
type SecurityContext
SecurityContext protects outbound request stubs and unprotects inbound response stubs for an authenticated connection-oriented RPC session. Each security provider (NTLM today, Netlogon next) supplies its own auth_value token format and chooses which bytes it signs: NTLM signs the whole PDU and so uses signedRegion, whereas a provider that signs only the stub can ignore signedRegion and operate on stub. The client owns PDU framing (stub padding, header fields, sec_trailer), which is provider-independent ([MS-RPCE] 2.2.2.11).
type SecurityContext interface {
// AuthValueLen returns the auth_value byte length this provider emits, given whether the
// request is sealed (PKT_PRIVACY) or only signed (PKT/PKT_INTEGRITY) and the length of the
// padded stub being protected. It must be known before the PDU header is marshalled, since
// it feeds auth_length and frag_length. Most providers emit a fixed-size token independent
// of the stub (NTLM, and the RC4 Kerberos tokens); the AES Kerberos Wrap token grows with
// the stub's block padding, so stubLen is consulted. A negative stubLen requests the
// largest possible length, for worst-case trailer sizing before the stub length is fixed.
AuthValueLen(seal bool, stubLen int) int
// ProtectRequest produces the on-wire stub and the auth_value for one outbound request
// fragment. signedRegion is the marshalled PDU from the header through the sec_trailer
// (auth_value excluded) computed over the plaintext stub; stub is the padded stub as a
// separate buffer. When seal is true the returned stub is encrypted, otherwise it equals
// stub. signedRegion MUST NOT be mutated.
ProtectRequest(signedRegion, stub []byte, seal bool) (onWireStub, authValue []byte, err error)
// UnprotectResponse recovers the plaintext stub from one inbound response fragment.
// signedRegion and stub are overlapping views into the same fragment buffer — stub is a
// sub-slice of signedRegion — so when seal is true the provider decrypts the stub in
// place (which updates signedRegion) before verifying integrity. It returns the recovered
// plaintext stub (still including any auth padding, which the caller strips).
UnprotectResponse(signedRegion, stub, authValue []byte, seal bool) (plainStub []byte, err error)
}